eyeling 1.22.13 → 1.22.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/engine.js CHANGED
@@ -76,6 +76,19 @@ const deref = require('./deref');
76
76
 
77
77
  const hasOwn = Object.prototype.hasOwnProperty;
78
78
 
79
+ function __emptySubst() {
80
+ return Object.create(null);
81
+ }
82
+
83
+ function __cloneSubst(subst) {
84
+ if (!subst) return __emptySubst();
85
+ const out = Object.create(null);
86
+ for (const k in subst) {
87
+ if (hasOwn.call(subst, k)) out[k] = subst[k];
88
+ }
89
+ return out;
90
+ }
91
+
79
92
  let version = 'dev';
80
93
  try {
81
94
  // Node: keep package.json version if available
@@ -1287,7 +1300,7 @@ function pushFactIndexed(facts, tr) {
1287
1300
 
1288
1301
  function makeDerivedRecord(fact, rule, premises, subst, captureExplanations) {
1289
1302
  if (captureExplanations === false) return { fact };
1290
- return new DerivedFact(fact, rule, premises.slice(), { ...subst });
1303
+ return new DerivedFact(fact, rule, premises.slice(), __cloneSubst(subst));
1291
1304
  }
1292
1305
 
1293
1306
  function ensureBackRuleIndexes(backRules) {
@@ -1795,6 +1808,21 @@ function unifyOpenWithList(prefix, tailv, ys, subst) {
1795
1808
  return s2;
1796
1809
  }
1797
1810
 
1811
+ function graphTriplesContainVars(triples) {
1812
+ function termHasVar(t) {
1813
+ if (t instanceof Var) return true;
1814
+ if (t instanceof ListTerm) return t.elems.some(termHasVar);
1815
+ if (t instanceof OpenListTerm) return t.prefix.some(termHasVar) || true;
1816
+ if (t instanceof GraphTerm) return t.triples.some((tr) => termHasVar(tr.s) || termHasVar(tr.p) || termHasVar(tr.o));
1817
+ return false;
1818
+ }
1819
+
1820
+ for (const tr of triples) {
1821
+ if (termHasVar(tr.s) || termHasVar(tr.p) || termHasVar(tr.o)) return true;
1822
+ }
1823
+ return false;
1824
+ }
1825
+
1798
1826
  function unifyGraphTriples(xs, ys, subst) {
1799
1827
  if (xs.length !== ys.length) return null;
1800
1828
 
@@ -1863,7 +1891,7 @@ function unifyTermWithOptions(a, b, subst, opts) {
1863
1891
  const t = b;
1864
1892
  if (t instanceof Var && t.name === v) return subst;
1865
1893
  if (containsVarTerm(t, v)) return null;
1866
- const s2 = { ...subst };
1894
+ const s2 = __cloneSubst(subst);
1867
1895
  s2[v] = t;
1868
1896
  return s2;
1869
1897
  }
@@ -1956,17 +1984,22 @@ function unifyTermWithOptions(a, b, subst, opts) {
1956
1984
 
1957
1985
  // Graphs
1958
1986
  if (a instanceof GraphTerm && b instanceof GraphTerm) {
1959
- const protectedNamesA = collectProtectedNamesForTerm(aRaw, subst);
1960
- const protectedNamesB = collectProtectedNamesForTerm(bRaw, subst);
1961
- if (
1962
- alphaEqGraphTriples(a.triples, b.triples, {
1963
- protectedVarsA: protectedNamesA.protectedVars,
1964
- protectedVarsB: protectedNamesB.protectedVars,
1965
- protectedBlanksA: protectedNamesA.protectedBlanks,
1966
- protectedBlanksB: protectedNamesB.protectedBlanks,
1967
- })
1968
- ) {
1969
- return subst;
1987
+ // Only use alpha-equivalence as a binding-free fast path when both quoted
1988
+ // formulas are variable-free after substitution. If unbound variables remain,
1989
+ // they may be shared with the outer rule and must unify/bind structurally.
1990
+ if (!graphTriplesContainVars(a.triples) && !graphTriplesContainVars(b.triples)) {
1991
+ const protectedNamesA = collectProtectedNamesForTerm(aRaw, subst);
1992
+ const protectedNamesB = collectProtectedNamesForTerm(bRaw, subst);
1993
+ if (
1994
+ alphaEqGraphTriples(a.triples, b.triples, {
1995
+ protectedVarsA: protectedNamesA.protectedVars,
1996
+ protectedVarsB: protectedNamesB.protectedVars,
1997
+ protectedBlanksA: protectedNamesA.protectedBlanks,
1998
+ protectedBlanksB: protectedNamesB.protectedBlanks,
1999
+ })
2000
+ ) {
2001
+ return subst;
2002
+ }
1970
2003
  }
1971
2004
  return unifyGraphTriples(a.triples, b.triples, subst);
1972
2005
  }
@@ -2048,7 +2081,7 @@ function gcCompactForGoals(subst, goals, answerVars) {
2048
2081
  }
2049
2082
  }
2050
2083
 
2051
- const out = {};
2084
+ const out = __emptySubst();
2052
2085
  for (const k of Object.keys(subst)) {
2053
2086
  if (keep.has(k)) out[k] = subst[k];
2054
2087
  }
@@ -2111,7 +2144,7 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxR
2111
2144
  const allowDeferredBuiltins = !!(opts && opts.deferBuiltins);
2112
2145
 
2113
2146
  const initialGoals = Array.isArray(goals) ? goals.slice() : [];
2114
- const substMut = subst ? { ...subst } : {};
2147
+ const substMut = subst ? __cloneSubst(subst) : {};
2115
2148
  const initialVisited = visited ? visited.slice() : [];
2116
2149
 
2117
2150
  // Variables from the original goal list (needed by the caller to instantiate conclusions)
@@ -2347,17 +2380,22 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxR
2347
2380
 
2348
2381
  // Graphs
2349
2382
  if (a instanceof GraphTerm && b instanceof GraphTerm) {
2350
- const protectedNamesA = collectProtectedNamesForTerm(aRaw, substMut);
2351
- const protectedNamesB = collectProtectedNamesForTerm(bRaw, substMut);
2352
- if (
2353
- alphaEqGraphTriples(a.triples, b.triples, {
2354
- protectedVarsA: protectedNamesA.protectedVars,
2355
- protectedVarsB: protectedNamesB.protectedVars,
2356
- protectedBlanksA: protectedNamesA.protectedBlanks,
2357
- protectedBlanksB: protectedNamesB.protectedBlanks,
2358
- })
2359
- ) {
2360
- return true;
2383
+ // Only use alpha-equivalence as a binding-free fast path when both quoted
2384
+ // formulas are variable-free after substitution. If unbound variables remain,
2385
+ // they may be shared with the outer rule and must unify/bind structurally.
2386
+ if (!graphTriplesContainVars(a.triples) && !graphTriplesContainVars(b.triples)) {
2387
+ const protectedNamesA = collectProtectedNamesForTerm(aRaw, substMut);
2388
+ const protectedNamesB = collectProtectedNamesForTerm(bRaw, substMut);
2389
+ if (
2390
+ alphaEqGraphTriples(a.triples, b.triples, {
2391
+ protectedVarsA: protectedNamesA.protectedVars,
2392
+ protectedVarsB: protectedNamesB.protectedVars,
2393
+ protectedBlanksA: protectedNamesA.protectedBlanks,
2394
+ protectedBlanksB: protectedNamesB.protectedBlanks,
2395
+ })
2396
+ ) {
2397
+ return true;
2398
+ }
2361
2399
  }
2362
2400
  const merged = unifyGraphTriples(a.triples, b.triples, substMut);
2363
2401
  if (merged === null) return false;
@@ -2568,7 +2606,7 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxR
2568
2606
  const builtinMax = Number.isFinite(remaining) && !restGoals.length ? remaining : undefined;
2569
2607
 
2570
2608
  const builtinGoalForEval = goalPredicateIri === LOG_NS + 'rawType' ? rawGoal : goal0;
2571
- const builtinSubstForEval = goalPredicateIri === LOG_NS + 'rawType' ? substMut : {};
2609
+ const builtinSubstForEval = goalPredicateIri === LOG_NS + 'rawType' ? substMut : __emptySubst();
2572
2610
  let deltas = evalBuiltin(
2573
2611
  builtinGoalForEval,
2574
2612
  builtinSubstForEval,
@@ -2610,7 +2648,7 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxR
2610
2648
  subjectAndObjectAreFullyUnbound &&
2611
2649
  (!restGoals.length || dc >= goalsNow.length)
2612
2650
  ) {
2613
- deltas = [{}];
2651
+ deltas = [__emptySubst()];
2614
2652
  }
2615
2653
 
2616
2654
  if (deltas.length) {
@@ -3043,7 +3081,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
3043
3081
  const r = entry.rule;
3044
3082
  if (__skipForwardRuleNow(r)) continue;
3045
3083
 
3046
- const s = unifyTriple(entry.goal, fact, {});
3084
+ const s = unifyTriple(entry.goal, fact, __emptySubst());
3047
3085
  if (s === null) continue;
3048
3086
 
3049
3087
  const outcome = __emitForwardRuleSolution(r, entry.ruleIndex, s);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.22.13",
3
+ "version": "1.22.15",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [
package/test/api.test.js CHANGED
@@ -2278,6 +2278,34 @@ _:b a ex:Person ; ex:name "B" .
2278
2278
  {
2279
2279
  :test :is true .
2280
2280
  }.
2281
+ `,
2282
+ expect: [/^:test\s+:is\s+true\s*\./m],
2283
+ },
2284
+ {
2285
+ name: 'regression: quoted-formula backward-rule heads must bind shared variables for later goals',
2286
+ opt: { proofComments: false },
2287
+ input: `@prefix : <http://example.org/> .
2288
+ @prefix log: <http://www.w3.org/2000/10/swap/log#> .
2289
+
2290
+ { :a :b :c } a :Statement .
2291
+
2292
+ { ?A :has { ?S ?P ?O } }
2293
+ <=
2294
+ {
2295
+ ?A log:includes { ?S ?P ?O }.
2296
+ }.
2297
+
2298
+ {
2299
+ ?A a :Statement .
2300
+ ?A :has { ?S ?P ?O }.
2301
+ ?S log:rawType log:Other.
2302
+ ?P log:rawType log:Other.
2303
+ ?O log:rawType log:Other.
2304
+ }
2305
+ =>
2306
+ {
2307
+ :test :is true .
2308
+ }.
2281
2309
  `,
2282
2310
  expect: [/^:test\s+:is\s+true\s*\./m],
2283
2311
  },