eyeling 1.22.13 → 1.22.14
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/HANDBOOK.md +2 -0
- package/dist/browser/eyeling.browser.js +48 -22
- package/eyeling.js +57 -26
- package/lib/engine.js +47 -22
- package/package.json +1 -1
- package/test/api.test.js +28 -0
package/HANDBOOK.md
CHANGED
|
@@ -436,6 +436,8 @@ Important scope nuance: only blanks/variables that are local to the quoted formu
|
|
|
436
436
|
|
|
437
437
|
So `{ _:x :p :o }` obtained by substituting `?A = _:x` into `{ ?A :p :o }` must not alpha-match `{ _:b :p :o }` by renaming `_:x` to `_:b`.
|
|
438
438
|
|
|
439
|
+
A related operational detail matters for rule execution: alpha-equivalence is only a **binding-free shortcut** when both quoted formulas are variable-free after substitution. If unbound variables still remain inside the formulas, Eyeling must fall back to structural quoted-formula unification so shared outer rule variables can actually bind. Otherwise a premise such as `?A :has { ?S ?P ?O }` could appear to match while leaving `?S ?P ?O` unbound for later goals.
|
|
440
|
+
|
|
439
441
|
### 6.2 Groundness: “variables inside formulas do not leak”
|
|
440
442
|
|
|
441
443
|
Eyeling makes a deliberate choice about _groundness_:
|
|
@@ -7625,6 +7625,22 @@
|
|
|
7625
7625
|
return s2;
|
|
7626
7626
|
}
|
|
7627
7627
|
|
|
7628
|
+
function graphTriplesContainVars(triples) {
|
|
7629
|
+
function termHasVar(t) {
|
|
7630
|
+
if (t instanceof Var) return true;
|
|
7631
|
+
if (t instanceof ListTerm) return t.elems.some(termHasVar);
|
|
7632
|
+
if (t instanceof OpenListTerm) return t.prefix.some(termHasVar) || true;
|
|
7633
|
+
if (t instanceof GraphTerm)
|
|
7634
|
+
return t.triples.some((tr) => termHasVar(tr.s) || termHasVar(tr.p) || termHasVar(tr.o));
|
|
7635
|
+
return false;
|
|
7636
|
+
}
|
|
7637
|
+
|
|
7638
|
+
for (const tr of triples) {
|
|
7639
|
+
if (termHasVar(tr.s) || termHasVar(tr.p) || termHasVar(tr.o)) return true;
|
|
7640
|
+
}
|
|
7641
|
+
return false;
|
|
7642
|
+
}
|
|
7643
|
+
|
|
7628
7644
|
function unifyGraphTriples(xs, ys, subst) {
|
|
7629
7645
|
if (xs.length !== ys.length) return null;
|
|
7630
7646
|
|
|
@@ -7786,17 +7802,22 @@
|
|
|
7786
7802
|
|
|
7787
7803
|
// Graphs
|
|
7788
7804
|
if (a instanceof GraphTerm && b instanceof GraphTerm) {
|
|
7789
|
-
|
|
7790
|
-
|
|
7791
|
-
|
|
7792
|
-
|
|
7793
|
-
|
|
7794
|
-
|
|
7795
|
-
|
|
7796
|
-
|
|
7797
|
-
|
|
7798
|
-
|
|
7799
|
-
|
|
7805
|
+
// Only use alpha-equivalence as a binding-free fast path when both quoted
|
|
7806
|
+
// formulas are variable-free after substitution. If unbound variables remain,
|
|
7807
|
+
// they may be shared with the outer rule and must unify/bind structurally.
|
|
7808
|
+
if (!graphTriplesContainVars(a.triples) && !graphTriplesContainVars(b.triples)) {
|
|
7809
|
+
const protectedNamesA = collectProtectedNamesForTerm(aRaw, subst);
|
|
7810
|
+
const protectedNamesB = collectProtectedNamesForTerm(bRaw, subst);
|
|
7811
|
+
if (
|
|
7812
|
+
alphaEqGraphTriples(a.triples, b.triples, {
|
|
7813
|
+
protectedVarsA: protectedNamesA.protectedVars,
|
|
7814
|
+
protectedVarsB: protectedNamesB.protectedVars,
|
|
7815
|
+
protectedBlanksA: protectedNamesA.protectedBlanks,
|
|
7816
|
+
protectedBlanksB: protectedNamesB.protectedBlanks,
|
|
7817
|
+
})
|
|
7818
|
+
) {
|
|
7819
|
+
return subst;
|
|
7820
|
+
}
|
|
7800
7821
|
}
|
|
7801
7822
|
return unifyGraphTriples(a.triples, b.triples, subst);
|
|
7802
7823
|
}
|
|
@@ -8177,17 +8198,22 @@
|
|
|
8177
8198
|
|
|
8178
8199
|
// Graphs
|
|
8179
8200
|
if (a instanceof GraphTerm && b instanceof GraphTerm) {
|
|
8180
|
-
|
|
8181
|
-
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
|
|
8187
|
-
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
|
|
8201
|
+
// Only use alpha-equivalence as a binding-free fast path when both quoted
|
|
8202
|
+
// formulas are variable-free after substitution. If unbound variables remain,
|
|
8203
|
+
// they may be shared with the outer rule and must unify/bind structurally.
|
|
8204
|
+
if (!graphTriplesContainVars(a.triples) && !graphTriplesContainVars(b.triples)) {
|
|
8205
|
+
const protectedNamesA = collectProtectedNamesForTerm(aRaw, substMut);
|
|
8206
|
+
const protectedNamesB = collectProtectedNamesForTerm(bRaw, substMut);
|
|
8207
|
+
if (
|
|
8208
|
+
alphaEqGraphTriples(a.triples, b.triples, {
|
|
8209
|
+
protectedVarsA: protectedNamesA.protectedVars,
|
|
8210
|
+
protectedVarsB: protectedNamesB.protectedVars,
|
|
8211
|
+
protectedBlanksA: protectedNamesA.protectedBlanks,
|
|
8212
|
+
protectedBlanksB: protectedNamesB.protectedBlanks,
|
|
8213
|
+
})
|
|
8214
|
+
) {
|
|
8215
|
+
return true;
|
|
8216
|
+
}
|
|
8191
8217
|
}
|
|
8192
8218
|
const merged = unifyGraphTriples(a.triples, b.triples, substMut);
|
|
8193
8219
|
if (merged === null) return false;
|
package/eyeling.js
CHANGED
|
@@ -7605,6 +7605,22 @@ function unifyOpenWithList(prefix, tailv, ys, subst) {
|
|
|
7605
7605
|
return s2;
|
|
7606
7606
|
}
|
|
7607
7607
|
|
|
7608
|
+
|
|
7609
|
+
function graphTriplesContainVars(triples) {
|
|
7610
|
+
function termHasVar(t) {
|
|
7611
|
+
if (t instanceof Var) return true;
|
|
7612
|
+
if (t instanceof ListTerm) return t.elems.some(termHasVar);
|
|
7613
|
+
if (t instanceof OpenListTerm) return t.prefix.some(termHasVar) || true;
|
|
7614
|
+
if (t instanceof GraphTerm) return t.triples.some((tr) => termHasVar(tr.s) || termHasVar(tr.p) || termHasVar(tr.o));
|
|
7615
|
+
return false;
|
|
7616
|
+
}
|
|
7617
|
+
|
|
7618
|
+
for (const tr of triples) {
|
|
7619
|
+
if (termHasVar(tr.s) || termHasVar(tr.p) || termHasVar(tr.o)) return true;
|
|
7620
|
+
}
|
|
7621
|
+
return false;
|
|
7622
|
+
}
|
|
7623
|
+
|
|
7608
7624
|
function unifyGraphTriples(xs, ys, subst) {
|
|
7609
7625
|
if (xs.length !== ys.length) return null;
|
|
7610
7626
|
|
|
@@ -7766,17 +7782,22 @@ function unifyTermWithOptions(a, b, subst, opts) {
|
|
|
7766
7782
|
|
|
7767
7783
|
// Graphs
|
|
7768
7784
|
if (a instanceof GraphTerm && b instanceof GraphTerm) {
|
|
7769
|
-
|
|
7770
|
-
|
|
7771
|
-
|
|
7772
|
-
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
|
|
7776
|
-
|
|
7777
|
-
|
|
7778
|
-
|
|
7779
|
-
|
|
7785
|
+
// Only use alpha-equivalence as a binding-free fast path when both quoted
|
|
7786
|
+
// formulas are variable-free after substitution. If unbound variables remain,
|
|
7787
|
+
// they may be shared with the outer rule and must unify/bind structurally.
|
|
7788
|
+
if (!graphTriplesContainVars(a.triples) && !graphTriplesContainVars(b.triples)) {
|
|
7789
|
+
const protectedNamesA = collectProtectedNamesForTerm(aRaw, subst);
|
|
7790
|
+
const protectedNamesB = collectProtectedNamesForTerm(bRaw, subst);
|
|
7791
|
+
if (
|
|
7792
|
+
alphaEqGraphTriples(a.triples, b.triples, {
|
|
7793
|
+
protectedVarsA: protectedNamesA.protectedVars,
|
|
7794
|
+
protectedVarsB: protectedNamesB.protectedVars,
|
|
7795
|
+
protectedBlanksA: protectedNamesA.protectedBlanks,
|
|
7796
|
+
protectedBlanksB: protectedNamesB.protectedBlanks,
|
|
7797
|
+
})
|
|
7798
|
+
) {
|
|
7799
|
+
return subst;
|
|
7800
|
+
}
|
|
7780
7801
|
}
|
|
7781
7802
|
return unifyGraphTriples(a.triples, b.triples, subst);
|
|
7782
7803
|
}
|
|
@@ -8157,17 +8178,22 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxR
|
|
|
8157
8178
|
|
|
8158
8179
|
// Graphs
|
|
8159
8180
|
if (a instanceof GraphTerm && b instanceof GraphTerm) {
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
|
|
8168
|
-
|
|
8169
|
-
|
|
8170
|
-
|
|
8181
|
+
// Only use alpha-equivalence as a binding-free fast path when both quoted
|
|
8182
|
+
// formulas are variable-free after substitution. If unbound variables remain,
|
|
8183
|
+
// they may be shared with the outer rule and must unify/bind structurally.
|
|
8184
|
+
if (!graphTriplesContainVars(a.triples) && !graphTriplesContainVars(b.triples)) {
|
|
8185
|
+
const protectedNamesA = collectProtectedNamesForTerm(aRaw, substMut);
|
|
8186
|
+
const protectedNamesB = collectProtectedNamesForTerm(bRaw, substMut);
|
|
8187
|
+
if (
|
|
8188
|
+
alphaEqGraphTriples(a.triples, b.triples, {
|
|
8189
|
+
protectedVarsA: protectedNamesA.protectedVars,
|
|
8190
|
+
protectedVarsB: protectedNamesB.protectedVars,
|
|
8191
|
+
protectedBlanksA: protectedNamesA.protectedBlanks,
|
|
8192
|
+
protectedBlanksB: protectedNamesB.protectedBlanks,
|
|
8193
|
+
})
|
|
8194
|
+
) {
|
|
8195
|
+
return true;
|
|
8196
|
+
}
|
|
8171
8197
|
}
|
|
8172
8198
|
const merged = unifyGraphTriples(a.triples, b.triples, substMut);
|
|
8173
8199
|
if (merged === null) return false;
|
|
@@ -13004,7 +13030,8 @@ function liftBlankRuleVars(premise, conclusion) {
|
|
|
13004
13030
|
if (t instanceof OpenListTerm) return new OpenListTerm(t.prefix.map(convertQuotedPatternTerm), t.tailVar);
|
|
13005
13031
|
if (t instanceof GraphTerm) {
|
|
13006
13032
|
const triples = t.triples.map(
|
|
13007
|
-
(tr) =>
|
|
13033
|
+
(tr) =>
|
|
13034
|
+
new Triple(convertQuotedPatternTerm(tr.s), convertQuotedPatternTerm(tr.p), convertQuotedPatternTerm(tr.o)),
|
|
13008
13035
|
);
|
|
13009
13036
|
return copyQuotedGraphMetadata(t, new GraphTerm(triples));
|
|
13010
13037
|
}
|
|
@@ -13014,13 +13041,17 @@ function liftBlankRuleVars(premise, conclusion) {
|
|
|
13014
13041
|
function convertTerm(t, allowDirectQuotedPattern = false) {
|
|
13015
13042
|
if (t instanceof Blank) return blankToVar(t.label);
|
|
13016
13043
|
if (t instanceof ListTerm) return new ListTerm(t.elems.map((e) => convertTerm(e, false)));
|
|
13017
|
-
if (t instanceof OpenListTerm)
|
|
13044
|
+
if (t instanceof OpenListTerm)
|
|
13045
|
+
return new OpenListTerm(
|
|
13046
|
+
t.prefix.map((e) => convertTerm(e, false)),
|
|
13047
|
+
t.tailVar,
|
|
13048
|
+
);
|
|
13018
13049
|
if (t instanceof GraphTerm) return allowDirectQuotedPattern ? convertQuotedPatternTerm(t) : copyQuotedTerm(t);
|
|
13019
13050
|
return t;
|
|
13020
13051
|
}
|
|
13021
13052
|
|
|
13022
|
-
const newPremise = premise.map(
|
|
13023
|
-
new Triple(convertTerm(tr.s, true), convertTerm(tr.p, true), convertTerm(tr.o, true)),
|
|
13053
|
+
const newPremise = premise.map(
|
|
13054
|
+
(tr) => new Triple(convertTerm(tr.s, true), convertTerm(tr.p, true), convertTerm(tr.o, true)),
|
|
13024
13055
|
);
|
|
13025
13056
|
return [newPremise, conclusion];
|
|
13026
13057
|
}
|
package/lib/engine.js
CHANGED
|
@@ -1795,6 +1795,21 @@ function unifyOpenWithList(prefix, tailv, ys, subst) {
|
|
|
1795
1795
|
return s2;
|
|
1796
1796
|
}
|
|
1797
1797
|
|
|
1798
|
+
function graphTriplesContainVars(triples) {
|
|
1799
|
+
function termHasVar(t) {
|
|
1800
|
+
if (t instanceof Var) return true;
|
|
1801
|
+
if (t instanceof ListTerm) return t.elems.some(termHasVar);
|
|
1802
|
+
if (t instanceof OpenListTerm) return t.prefix.some(termHasVar) || true;
|
|
1803
|
+
if (t instanceof GraphTerm) return t.triples.some((tr) => termHasVar(tr.s) || termHasVar(tr.p) || termHasVar(tr.o));
|
|
1804
|
+
return false;
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
for (const tr of triples) {
|
|
1808
|
+
if (termHasVar(tr.s) || termHasVar(tr.p) || termHasVar(tr.o)) return true;
|
|
1809
|
+
}
|
|
1810
|
+
return false;
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1798
1813
|
function unifyGraphTriples(xs, ys, subst) {
|
|
1799
1814
|
if (xs.length !== ys.length) return null;
|
|
1800
1815
|
|
|
@@ -1956,17 +1971,22 @@ function unifyTermWithOptions(a, b, subst, opts) {
|
|
|
1956
1971
|
|
|
1957
1972
|
// Graphs
|
|
1958
1973
|
if (a instanceof GraphTerm && b instanceof GraphTerm) {
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1974
|
+
// Only use alpha-equivalence as a binding-free fast path when both quoted
|
|
1975
|
+
// formulas are variable-free after substitution. If unbound variables remain,
|
|
1976
|
+
// they may be shared with the outer rule and must unify/bind structurally.
|
|
1977
|
+
if (!graphTriplesContainVars(a.triples) && !graphTriplesContainVars(b.triples)) {
|
|
1978
|
+
const protectedNamesA = collectProtectedNamesForTerm(aRaw, subst);
|
|
1979
|
+
const protectedNamesB = collectProtectedNamesForTerm(bRaw, subst);
|
|
1980
|
+
if (
|
|
1981
|
+
alphaEqGraphTriples(a.triples, b.triples, {
|
|
1982
|
+
protectedVarsA: protectedNamesA.protectedVars,
|
|
1983
|
+
protectedVarsB: protectedNamesB.protectedVars,
|
|
1984
|
+
protectedBlanksA: protectedNamesA.protectedBlanks,
|
|
1985
|
+
protectedBlanksB: protectedNamesB.protectedBlanks,
|
|
1986
|
+
})
|
|
1987
|
+
) {
|
|
1988
|
+
return subst;
|
|
1989
|
+
}
|
|
1970
1990
|
}
|
|
1971
1991
|
return unifyGraphTriples(a.triples, b.triples, subst);
|
|
1972
1992
|
}
|
|
@@ -2347,17 +2367,22 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxR
|
|
|
2347
2367
|
|
|
2348
2368
|
// Graphs
|
|
2349
2369
|
if (a instanceof GraphTerm && b instanceof GraphTerm) {
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2370
|
+
// Only use alpha-equivalence as a binding-free fast path when both quoted
|
|
2371
|
+
// formulas are variable-free after substitution. If unbound variables remain,
|
|
2372
|
+
// they may be shared with the outer rule and must unify/bind structurally.
|
|
2373
|
+
if (!graphTriplesContainVars(a.triples) && !graphTriplesContainVars(b.triples)) {
|
|
2374
|
+
const protectedNamesA = collectProtectedNamesForTerm(aRaw, substMut);
|
|
2375
|
+
const protectedNamesB = collectProtectedNamesForTerm(bRaw, substMut);
|
|
2376
|
+
if (
|
|
2377
|
+
alphaEqGraphTriples(a.triples, b.triples, {
|
|
2378
|
+
protectedVarsA: protectedNamesA.protectedVars,
|
|
2379
|
+
protectedVarsB: protectedNamesB.protectedVars,
|
|
2380
|
+
protectedBlanksA: protectedNamesA.protectedBlanks,
|
|
2381
|
+
protectedBlanksB: protectedNamesB.protectedBlanks,
|
|
2382
|
+
})
|
|
2383
|
+
) {
|
|
2384
|
+
return true;
|
|
2385
|
+
}
|
|
2361
2386
|
}
|
|
2362
2387
|
const merged = unifyGraphTriples(a.triples, b.triples, substMut);
|
|
2363
2388
|
if (merged === null) return false;
|
package/package.json
CHANGED
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
|
},
|