eyeling 1.15.9 → 1.15.11
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 +4 -0
- package/README.md +2 -0
- package/examples/deck/faltings-genus2-finiteness.md +211 -0
- package/examples/faltings-genus2-finiteness.n3 +69 -0
- package/examples/french-cities.n3 +2 -0
- package/examples/jsonterm-advanced.n3 +92 -0
- package/examples/jsonterm.n3 +37 -0
- package/examples/output/faltings-genus2-finiteness.n3 +5 -0
- package/examples/output/french-cities.n3 +1 -18
- package/examples/output/jsonterm-advanced.n3 +8 -0
- package/examples/output/jsonterm.n3 +3 -0
- package/eyeling.js +212 -33
- package/lib/builtins.js +32 -0
- package/lib/engine.js +180 -33
- package/package.json +1 -1
- package/test/api.test.js +67 -0
package/HANDBOOK.md
CHANGED
|
@@ -376,6 +376,10 @@ That’s alpha-equivalence:
|
|
|
376
376
|
|
|
377
377
|
Eyeling implements alpha-equivalence by checking whether there exists a consistent renaming mapping between the two formulas’ variables/blanks that makes the triples match.
|
|
378
378
|
|
|
379
|
+
Important scope nuance: only blanks/variables that are local to the quoted formula participate in alpha-renaming. If a formula is being matched after an outer substitution has already instantiated part of it, those substituted terms are treated as fixed. In other words, alpha-equivalence may rename formula-local placeholders, but it must not rename names that came from the enclosing match. This prevents a substituted outer blank node from being confused with a local blank node inside the quoted formula.
|
|
380
|
+
|
|
381
|
+
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`.
|
|
382
|
+
|
|
379
383
|
### 6.2 Groundness: “variables inside formulas don’t leak”
|
|
380
384
|
|
|
381
385
|
Eyeling makes a deliberate choice about _groundness_:
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# eyeling
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/eyereasoner) [](https://doi.org/10.5281/zenodo.19068086)
|
|
4
|
+
|
|
3
5
|
A compact [Notation3 (N3)](https://notation3.org/) reasoner in **JavaScript**.
|
|
4
6
|
|
|
5
7
|
- Single self-contained bundle (`eyeling.js`), no external runtime dependencies
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# Faltings’ theorem (emulated) in Notation3: a genus‑2 curve over Q
|
|
2
|
+
|
|
3
|
+
This deck explains the example `faltings-genus2-finiteness.n3` ([Playground][1]).
|
|
4
|
+
|
|
5
|
+
The goal is to show—at a friendly, “wide audience” level—how an N3 file can _model_ a famous mathematical implication:
|
|
6
|
+
|
|
7
|
+
> “If a curve has genus ≥ 2, then it has only finitely many rational points.”
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## The problem in plain language
|
|
12
|
+
|
|
13
|
+
People often ask:
|
|
14
|
+
|
|
15
|
+
> “Which solutions can an equation have if you only allow fractions?”
|
|
16
|
+
|
|
17
|
+
A **rational point** means a solution where the coordinates are rational numbers (fractions like 3/7).
|
|
18
|
+
|
|
19
|
+
Some equations have **infinitely many** rational points. Others have **only finitely many**.
|
|
20
|
+
|
|
21
|
+
This example is about expressing the “only finitely many” conclusion as a machine-checkable **rule**.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## The concrete curve in this example
|
|
26
|
+
|
|
27
|
+
We model this curve:
|
|
28
|
+
|
|
29
|
+
\[ y^2 = x(x+1)(x-2)(x+2)(x-3) \]
|
|
30
|
+
|
|
31
|
+
You can spot some obvious rational solutions just by making the right-hand side zero:
|
|
32
|
+
|
|
33
|
+
- (0, 0)
|
|
34
|
+
- (-1, 0)
|
|
35
|
+
- (2, 0)
|
|
36
|
+
- (-2, 0)
|
|
37
|
+
- (3, 0)
|
|
38
|
+
|
|
39
|
+
The file includes these as example data points.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## What is “genus” (without the heavy math)?
|
|
44
|
+
|
|
45
|
+
A good mental model is:
|
|
46
|
+
|
|
47
|
+
- **genus 0**: sphere-like (0 holes)
|
|
48
|
+
- **genus 1**: donut-like (1 hole)
|
|
49
|
+
- **genus 2**: “two-hole donut”
|
|
50
|
+
- **genus ≥ 2**: more complicated surfaces
|
|
51
|
+
|
|
52
|
+
In algebraic geometry, _genus_ is a deep invariant, but for this deck you only need:
|
|
53
|
+
|
|
54
|
+
> genus is a number that measures how “holey” a curve is.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## The famous implication (Faltings’ theorem)
|
|
59
|
+
|
|
60
|
+
Very roughly:
|
|
61
|
+
|
|
62
|
+
- For genus 0: rational points are “none or infinite”
|
|
63
|
+
- For genus 1: rational points can be infinite, but structured (elliptic curves)
|
|
64
|
+
- For genus ≥ 2: rational points are **finite**
|
|
65
|
+
|
|
66
|
+
Faltings proved (in 1983) the last bullet (formerly the Mordell conjecture).
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## What this N3 example _does_
|
|
71
|
+
|
|
72
|
+
It does **not** re-prove the theorem.
|
|
73
|
+
|
|
74
|
+
Instead, it treats “Faltings’ theorem” as a named rule:
|
|
75
|
+
|
|
76
|
+
- If something is a curve,
|
|
77
|
+
- over a number field,
|
|
78
|
+
- with genus ≥ 2,
|
|
79
|
+
- then infer: “its rational points are finite.”
|
|
80
|
+
|
|
81
|
+
That’s the kind of modeling you do when you want a reasoner to apply well-known results reliably.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Why this is useful (even though it’s an emulation)
|
|
86
|
+
|
|
87
|
+
Think of it like a _library function_.
|
|
88
|
+
|
|
89
|
+
You may not re-derive calculus every time you compute a derivative; you rely on a trusted rule.
|
|
90
|
+
|
|
91
|
+
Similarly, you can:
|
|
92
|
+
|
|
93
|
+
- store curve facts as data,
|
|
94
|
+
- encode trusted theorems as rules,
|
|
95
|
+
- and let a reasoner apply them consistently.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## N3 in one minute
|
|
100
|
+
|
|
101
|
+
N3 is RDF + rules.
|
|
102
|
+
|
|
103
|
+
- **Facts** look like triples:
|
|
104
|
+
- `:C :genus 2.`
|
|
105
|
+
- **Rules** look like:
|
|
106
|
+
- `{ ... } => { ... } .`
|
|
107
|
+
|
|
108
|
+
Variables start with `?`, like `?curve`, `?g`.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## The data section (what we assert)
|
|
113
|
+
|
|
114
|
+
The file asserts:
|
|
115
|
+
|
|
116
|
+
```n3
|
|
117
|
+
:C a :Curve ;
|
|
118
|
+
:equation "y^2 = x(x+1)(x-2)(x+2)(x-3)" ;
|
|
119
|
+
:definedOver :Q ;
|
|
120
|
+
:genus 2 .
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
And it also asserts that `:Q` is a `:NumberField`, plus a few sample points.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## The rule section (the “theorem” as logic)
|
|
128
|
+
|
|
129
|
+
Here is the core rule (lightly formatted):
|
|
130
|
+
|
|
131
|
+
```n3
|
|
132
|
+
{
|
|
133
|
+
?curve a :Curve ;
|
|
134
|
+
:definedOver ?field ;
|
|
135
|
+
:genus ?g .
|
|
136
|
+
?field a :NumberField .
|
|
137
|
+
?g math:notLessThan 2 .
|
|
138
|
+
}
|
|
139
|
+
=>
|
|
140
|
+
{
|
|
141
|
+
?curve :coveredBy :FaltingsTheorem ;
|
|
142
|
+
:hasRationalPointsCardinality :Finite .
|
|
143
|
+
} .
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
That `math:notLessThan` is a standard N3 math builtin: it means “≥”.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## What gets inferred
|
|
151
|
+
|
|
152
|
+
Once a reasoner sees the facts:
|
|
153
|
+
|
|
154
|
+
- `:C a :Curve`
|
|
155
|
+
- `:C :definedOver :Q`
|
|
156
|
+
- `:C :genus 2`
|
|
157
|
+
- `:Q a :NumberField`
|
|
158
|
+
|
|
159
|
+
…the rule fires and it can derive:
|
|
160
|
+
|
|
161
|
+
```n3
|
|
162
|
+
:C :hasRationalPointsCardinality :Finite .
|
|
163
|
+
:C :coveredBy :FaltingsTheorem .
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
There’s also a small follow-on rule that derives a friendlier Boolean:
|
|
167
|
+
|
|
168
|
+
```n3
|
|
169
|
+
:C :doesNotHaveInfinitelyManyRationalPoints true .
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Important: what it does _not_ compute
|
|
175
|
+
|
|
176
|
+
This file does **not** find all rational points.
|
|
177
|
+
|
|
178
|
+
Faltings’ theorem is about **finiteness**, not an explicit list.
|
|
179
|
+
|
|
180
|
+
So the example’s job is:
|
|
181
|
+
|
|
182
|
+
- represent the curve,
|
|
183
|
+
- represent the theorem as a rule,
|
|
184
|
+
- and show that the reasoner can draw the finiteness conclusion.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Try it
|
|
189
|
+
|
|
190
|
+
### In your browser
|
|
191
|
+
|
|
192
|
+
Use the playground link at the top: [Playground][1].
|
|
193
|
+
|
|
194
|
+
### On the command line
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
eyeling faltings-genus2-finiteness.n3
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Where you can take this next
|
|
203
|
+
|
|
204
|
+
Easy extensions:
|
|
205
|
+
|
|
206
|
+
- Add more example curves with different genera
|
|
207
|
+
- Add rules that _classify_ genus based on curve families (toy versions)
|
|
208
|
+
- Connect this to a small “math knowledge base” of reusable lemmas
|
|
209
|
+
- Use the same pattern for other theorems: “if conditions, then property”
|
|
210
|
+
|
|
211
|
+
[1]: https://eyereasoner.github.io/eyeling/demo?url=https://raw.githubusercontent.com/eyereasoner/eyeling/refs/heads/main/examples/faltings-genus2-finiteness.n3 'Playground'
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# ==================================================================
|
|
2
|
+
# File: faltings-genus2-finiteness.n3
|
|
3
|
+
# Purpose:
|
|
4
|
+
# Encode a simple Notation3 example inspired by Faltings' theorem.
|
|
5
|
+
#
|
|
6
|
+
# Mathematical content:
|
|
7
|
+
# The curve
|
|
8
|
+
# y^2 = x(x+1)(x-2)(x+2)(x-3)
|
|
9
|
+
# is treated as a genus-2 curve over Q.
|
|
10
|
+
# By an N3 rule that emulates Faltings' theorem,
|
|
11
|
+
# any curve over a number field with genus >= 2
|
|
12
|
+
# is inferred to have only finitely many rational points.
|
|
13
|
+
#
|
|
14
|
+
# Notes:
|
|
15
|
+
# - This file is a logical emulation, not a formal proof.
|
|
16
|
+
# - Several explicit rational points are included as examples.
|
|
17
|
+
# ==================================================================
|
|
18
|
+
|
|
19
|
+
@prefix : <http://example.org/faltings#> .
|
|
20
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
21
|
+
@prefix math: <http://www.w3.org/2000/10/swap/math#> .
|
|
22
|
+
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
|
23
|
+
|
|
24
|
+
# --- the example curve ---
|
|
25
|
+
:C
|
|
26
|
+
a :Curve ;
|
|
27
|
+
:equation "y^2 = x(x+1)(x-2)(x+2)(x-3)" ;
|
|
28
|
+
:definedOver :Q ;
|
|
29
|
+
:genus 2 ;
|
|
30
|
+
rdfs:label "Example genus-2 curve" .
|
|
31
|
+
|
|
32
|
+
:Q a :NumberField ;
|
|
33
|
+
rdfs:label "the rationals" .
|
|
34
|
+
|
|
35
|
+
:FaltingsTheorem
|
|
36
|
+
a :Theorem ;
|
|
37
|
+
rdfs:label "If a curve over a number field has genus >= 2, then it has finitely many rational points." .
|
|
38
|
+
|
|
39
|
+
:Finite a :CardinalityClass .
|
|
40
|
+
|
|
41
|
+
# --- some explicit rational points on the curve ---
|
|
42
|
+
:P0 a :RationalPoint ; :onCurve :C ; :x 0 ; :y 0 .
|
|
43
|
+
:P1 a :RationalPoint ; :onCurve :C ; :x -1 ; :y 0 .
|
|
44
|
+
:P2 a :RationalPoint ; :onCurve :C ; :x 2 ; :y 0 .
|
|
45
|
+
:P3 a :RationalPoint ; :onCurve :C ; :x -2 ; :y 0 .
|
|
46
|
+
:P4 a :RationalPoint ; :onCurve :C ; :x 3 ; :y 0 .
|
|
47
|
+
|
|
48
|
+
# --- emulation of Faltings' theorem ---
|
|
49
|
+
{
|
|
50
|
+
?curve a :Curve ;
|
|
51
|
+
:definedOver ?field ;
|
|
52
|
+
:genus ?g .
|
|
53
|
+
?field a :NumberField .
|
|
54
|
+
?g math:notLessThan 2 .
|
|
55
|
+
}
|
|
56
|
+
=>
|
|
57
|
+
{
|
|
58
|
+
?curve :coveredBy :FaltingsTheorem ;
|
|
59
|
+
:hasRationalPointsCardinality :Finite .
|
|
60
|
+
} .
|
|
61
|
+
|
|
62
|
+
# --- optional derived statement ---
|
|
63
|
+
{
|
|
64
|
+
?curve :hasRationalPointsCardinality :Finite .
|
|
65
|
+
}
|
|
66
|
+
=>
|
|
67
|
+
{
|
|
68
|
+
?curve :doesNotHaveInfinitelyManyRationalPoints true .
|
|
69
|
+
} .
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# =================================================================
|
|
2
|
+
# More advanced jsonterm example.
|
|
3
|
+
#
|
|
4
|
+
# Mapping used:
|
|
5
|
+
# - JSON objects become anonymous node structures.
|
|
6
|
+
# - JSON arrays become N3 lists.
|
|
7
|
+
# - JSON null becomes a normal symbol here, j:null.
|
|
8
|
+
# - JSON booleans become ordinary boolean literals: true and false.
|
|
9
|
+
# =================================================================
|
|
10
|
+
|
|
11
|
+
@prefix ex: <https://example.org/#> .
|
|
12
|
+
@prefix j: <https://example.org/json#> .
|
|
13
|
+
|
|
14
|
+
[
|
|
15
|
+
j:id "user-101";
|
|
16
|
+
j:profile [
|
|
17
|
+
j:name "Alice";
|
|
18
|
+
j:active true;
|
|
19
|
+
j:address [
|
|
20
|
+
j:city "Ghent";
|
|
21
|
+
j:country "BE"
|
|
22
|
+
]
|
|
23
|
+
];
|
|
24
|
+
j:roles ("admin" "editor");
|
|
25
|
+
j:preferences [
|
|
26
|
+
j:languages ("nl" "fr" "en");
|
|
27
|
+
j:theme "dark";
|
|
28
|
+
j:beta false
|
|
29
|
+
];
|
|
30
|
+
j:orders (
|
|
31
|
+
[
|
|
32
|
+
j:id "ord-1";
|
|
33
|
+
j:total 125;
|
|
34
|
+
j:items ("book" "pen")
|
|
35
|
+
]
|
|
36
|
+
[
|
|
37
|
+
j:id "ord-2";
|
|
38
|
+
j:total 45;
|
|
39
|
+
j:items ("notebook");
|
|
40
|
+
j:coupon j:null
|
|
41
|
+
]
|
|
42
|
+
)
|
|
43
|
+
] ex:kind j:UserDocument .
|
|
44
|
+
|
|
45
|
+
{
|
|
46
|
+
?Doc ex:kind j:UserDocument .
|
|
47
|
+
?Doc j:profile [
|
|
48
|
+
j:name ?Name;
|
|
49
|
+
j:active true;
|
|
50
|
+
j:address [ j:country "BE" ]
|
|
51
|
+
] .
|
|
52
|
+
}
|
|
53
|
+
=>
|
|
54
|
+
{
|
|
55
|
+
?Doc ex:userName ?Name .
|
|
56
|
+
?Doc ex:eligibleForReview true .
|
|
57
|
+
} .
|
|
58
|
+
|
|
59
|
+
{
|
|
60
|
+
?Doc j:preferences [
|
|
61
|
+
j:languages ("nl" "fr" "en");
|
|
62
|
+
j:theme "dark";
|
|
63
|
+
j:beta false
|
|
64
|
+
] .
|
|
65
|
+
}
|
|
66
|
+
=>
|
|
67
|
+
{
|
|
68
|
+
?Doc ex:profileTag "multilingual-dark-profile" .
|
|
69
|
+
} .
|
|
70
|
+
|
|
71
|
+
{
|
|
72
|
+
?Doc j:orders (
|
|
73
|
+
[ j:id ?FirstId; j:total 125; j:items ("book" "pen") ]
|
|
74
|
+
[ j:id ?SecondId; j:total 45; j:items ("notebook"); j:coupon j:null ]
|
|
75
|
+
) .
|
|
76
|
+
}
|
|
77
|
+
=>
|
|
78
|
+
{
|
|
79
|
+
?Doc ex:hasHighValueStarterOrder true .
|
|
80
|
+
?Doc ex:hasCouponlessFollowup true .
|
|
81
|
+
} .
|
|
82
|
+
|
|
83
|
+
{
|
|
84
|
+
?Doc ex:eligibleForReview true .
|
|
85
|
+
?Doc ex:profileTag "multilingual-dark-profile" .
|
|
86
|
+
?Doc ex:hasHighValueStarterOrder true .
|
|
87
|
+
?Doc ex:hasCouponlessFollowup true .
|
|
88
|
+
}
|
|
89
|
+
=>
|
|
90
|
+
{
|
|
91
|
+
ex:test ex:is true .
|
|
92
|
+
} .
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# =================================================
|
|
2
|
+
# jsonterm example
|
|
3
|
+
#
|
|
4
|
+
# Mapping used:
|
|
5
|
+
# - JSON objects become anonymous node structures.
|
|
6
|
+
# - JSON arrays become N3 lists.
|
|
7
|
+
# - JSON null becomes a normal symbol here, j:null.
|
|
8
|
+
# =================================================
|
|
9
|
+
|
|
10
|
+
@prefix ex: <https://example.org/#> .
|
|
11
|
+
@prefix j: <https://example.org/json#> .
|
|
12
|
+
|
|
13
|
+
[
|
|
14
|
+
j:user "alice";
|
|
15
|
+
j:roles ("admin" j:null)
|
|
16
|
+
]
|
|
17
|
+
("likes" 1)
|
|
18
|
+
[
|
|
19
|
+
j:city "Paris";
|
|
20
|
+
j:coords (2.35 48.85)
|
|
21
|
+
] .
|
|
22
|
+
|
|
23
|
+
{
|
|
24
|
+
[
|
|
25
|
+
j:user ?U;
|
|
26
|
+
j:roles ("admin" j:null)
|
|
27
|
+
]
|
|
28
|
+
("likes" 1)
|
|
29
|
+
[
|
|
30
|
+
j:city "Paris";
|
|
31
|
+
j:coords (2.35 48.85)
|
|
32
|
+
] .
|
|
33
|
+
}
|
|
34
|
+
=>
|
|
35
|
+
{
|
|
36
|
+
ex:test ex:is true .
|
|
37
|
+
} .
|
|
@@ -1,23 +1,6 @@
|
|
|
1
1
|
@prefix : <http://www.agfa.com/w3c/euler/graph.axiom#> .
|
|
2
2
|
|
|
3
|
-
:paris :path :orleans .
|
|
4
|
-
:paris :path :chartres .
|
|
5
|
-
:paris :path :amiens .
|
|
6
|
-
:orleans :path :blois .
|
|
7
|
-
:orleans :path :bourges .
|
|
8
|
-
:blois :path :tours .
|
|
9
|
-
:chartres :path :lemans .
|
|
10
|
-
:lemans :path :angers .
|
|
11
|
-
:lemans :path :tours .
|
|
12
3
|
:angers :path :nantes .
|
|
13
|
-
:paris :path :blois .
|
|
14
|
-
:paris :path :bourges .
|
|
15
|
-
:paris :path :lemans .
|
|
16
|
-
:orleans :path :tours .
|
|
17
|
-
:chartres :path :angers .
|
|
18
|
-
:chartres :path :tours .
|
|
19
|
-
:lemans :path :nantes .
|
|
20
|
-
:paris :path :tours .
|
|
21
|
-
:paris :path :angers .
|
|
22
4
|
:chartres :path :nantes .
|
|
5
|
+
:lemans :path :nantes .
|
|
23
6
|
:paris :path :nantes .
|
package/eyeling.js
CHANGED
|
@@ -110,6 +110,35 @@ function makeBuiltins(deps) {
|
|
|
110
110
|
return { evalBuiltin, isBuiltinPred };
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
function __builtinCollectVarsInTerm(t, out) {
|
|
114
|
+
if (t instanceof Var) {
|
|
115
|
+
out.add(t.name);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (t instanceof ListTerm) {
|
|
119
|
+
for (const e of t.elems) __builtinCollectVarsInTerm(e, out);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (t instanceof OpenListTerm) {
|
|
123
|
+
for (const e of t.prefix) __builtinCollectVarsInTerm(e, out);
|
|
124
|
+
out.add(t.tailVar);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (t instanceof GraphTerm) {
|
|
128
|
+
for (const tr of t.triples) __builtinCollectVarsInTriple(tr, out);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function __builtinCollectVarsInTriple(tr, out) {
|
|
133
|
+
__builtinCollectVarsInTerm(tr.s, out);
|
|
134
|
+
__builtinCollectVarsInTerm(tr.p, out);
|
|
135
|
+
__builtinCollectVarsInTerm(tr.o, out);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function __builtinCollectVarsInTriples(triples, out) {
|
|
139
|
+
for (const tr of triples) __builtinCollectVarsInTriple(tr, out);
|
|
140
|
+
}
|
|
141
|
+
|
|
113
142
|
function literalHasLangTag(lit) {
|
|
114
143
|
// True iff the literal is a quoted string literal with a language tag suffix,
|
|
115
144
|
// e.g. "hello"@en or """hello"""@en.
|
|
@@ -3040,6 +3069,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
3040
3069
|
if (!(g.o instanceof GraphTerm)) return [];
|
|
3041
3070
|
|
|
3042
3071
|
const visited2 = [];
|
|
3072
|
+
const keepVars = new Set();
|
|
3073
|
+
if (g.s instanceof GraphTerm) __builtinCollectVarsInTriples(g.s.triples, keepVars);
|
|
3043
3074
|
// Start from the incoming substitution so bindings flow outward.
|
|
3044
3075
|
return proveGoals(
|
|
3045
3076
|
Array.from(g.o.triples),
|
|
@@ -3050,6 +3081,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
3050
3081
|
visited2,
|
|
3051
3082
|
varGen,
|
|
3052
3083
|
maxResults,
|
|
3084
|
+
keepVars.size ? { keepVars } : undefined,
|
|
3053
3085
|
);
|
|
3054
3086
|
}
|
|
3055
3087
|
|
|
@@ -5352,27 +5384,80 @@ function triplesListEqual(xs, ys) {
|
|
|
5352
5384
|
return true;
|
|
5353
5385
|
}
|
|
5354
5386
|
|
|
5355
|
-
|
|
5387
|
+
function collectProtectedNamesInTerm(t, protectedVars, protectedBlanks) {
|
|
5388
|
+
if (t instanceof Var) {
|
|
5389
|
+
protectedVars.add(t.name);
|
|
5390
|
+
return;
|
|
5391
|
+
}
|
|
5392
|
+
if (t instanceof Blank) {
|
|
5393
|
+
protectedBlanks.add(t.label);
|
|
5394
|
+
return;
|
|
5395
|
+
}
|
|
5396
|
+
if (t instanceof ListTerm) {
|
|
5397
|
+
for (const e of t.elems) collectProtectedNamesInTerm(e, protectedVars, protectedBlanks);
|
|
5398
|
+
return;
|
|
5399
|
+
}
|
|
5400
|
+
if (t instanceof OpenListTerm) {
|
|
5401
|
+
for (const e of t.prefix) collectProtectedNamesInTerm(e, protectedVars, protectedBlanks);
|
|
5402
|
+
protectedVars.add(t.tailVar);
|
|
5403
|
+
return;
|
|
5404
|
+
}
|
|
5405
|
+
if (t instanceof GraphTerm) {
|
|
5406
|
+
for (const tr of t.triples) {
|
|
5407
|
+
collectProtectedNamesInTerm(tr.s, protectedVars, protectedBlanks);
|
|
5408
|
+
collectProtectedNamesInTerm(tr.p, protectedVars, protectedBlanks);
|
|
5409
|
+
collectProtectedNamesInTerm(tr.o, protectedVars, protectedBlanks);
|
|
5410
|
+
}
|
|
5411
|
+
}
|
|
5412
|
+
}
|
|
5413
|
+
|
|
5414
|
+
function collectProtectedNamesFromSubst(subst) {
|
|
5415
|
+
const protectedVars = new Set();
|
|
5416
|
+
const protectedBlanks = new Set();
|
|
5417
|
+
if (!subst) return { protectedVars, protectedBlanks };
|
|
5418
|
+
for (const k in subst) {
|
|
5419
|
+
if (!Object.prototype.hasOwnProperty.call(subst, k)) continue;
|
|
5420
|
+
collectProtectedNamesInTerm(subst[k], protectedVars, protectedBlanks);
|
|
5421
|
+
}
|
|
5422
|
+
return { protectedVars, protectedBlanks };
|
|
5423
|
+
}
|
|
5424
|
+
|
|
5425
|
+
// Alpha-equivalence for quoted formulas, up to *local* variable and blank-node renaming.
|
|
5426
|
+
// Terms that originate from the surrounding substitution are treated as fixed and are
|
|
5427
|
+
// therefore not alpha-renamable inside the quoted formula.
|
|
5356
5428
|
// Treats a formula as an unordered set of triples (order-insensitive match).
|
|
5357
|
-
function alphaEqVarName(x, y, vmap) {
|
|
5429
|
+
function alphaEqVarName(x, y, vmap, protectedVarsA, protectedVarsB) {
|
|
5430
|
+
const xProtected = protectedVarsA && protectedVarsA.has(x);
|
|
5431
|
+
const yProtected = protectedVarsB && protectedVarsB.has(y);
|
|
5432
|
+
if (xProtected || yProtected) return xProtected && yProtected && x === y;
|
|
5358
5433
|
if (Object.prototype.hasOwnProperty.call(vmap, x)) return vmap[x] === y;
|
|
5359
5434
|
vmap[x] = y;
|
|
5360
5435
|
return true;
|
|
5361
5436
|
}
|
|
5362
5437
|
|
|
5363
|
-
function
|
|
5364
|
-
|
|
5438
|
+
function alphaEqBlankLabel(x, y, bmap, protectedBlanksA, protectedBlanksB) {
|
|
5439
|
+
const xProtected = protectedBlanksA && protectedBlanksA.has(x);
|
|
5440
|
+
const yProtected = protectedBlanksB && protectedBlanksB.has(y);
|
|
5441
|
+
if (xProtected || yProtected) return xProtected && yProtected && x === y;
|
|
5442
|
+
if (Object.prototype.hasOwnProperty.call(bmap, x)) return bmap[x] === y;
|
|
5443
|
+
bmap[x] = y;
|
|
5444
|
+
return true;
|
|
5445
|
+
}
|
|
5446
|
+
|
|
5447
|
+
function alphaEqTermInGraph(a, b, vmap, bmap, opts) {
|
|
5448
|
+
const protectedVarsA = opts && opts.protectedVarsA;
|
|
5449
|
+
const protectedVarsB = opts && opts.protectedVarsB;
|
|
5450
|
+
const protectedBlanksA = opts && opts.protectedBlanksA;
|
|
5451
|
+
const protectedBlanksB = opts && opts.protectedBlanksB;
|
|
5452
|
+
|
|
5453
|
+
// Blank nodes: renamable only when they are local to the formula.
|
|
5365
5454
|
if (a instanceof Blank && b instanceof Blank) {
|
|
5366
|
-
|
|
5367
|
-
const y = b.label;
|
|
5368
|
-
if (Object.prototype.hasOwnProperty.call(bmap, x)) return bmap[x] === y;
|
|
5369
|
-
bmap[x] = y;
|
|
5370
|
-
return true;
|
|
5455
|
+
return alphaEqBlankLabel(a.label, b.label, bmap, protectedBlanksA, protectedBlanksB);
|
|
5371
5456
|
}
|
|
5372
5457
|
|
|
5373
|
-
// Variables: renamable
|
|
5458
|
+
// Variables: renamable only when they are local to the formula.
|
|
5374
5459
|
if (a instanceof Var && b instanceof Var) {
|
|
5375
|
-
return alphaEqVarName(a.name, b.name, vmap);
|
|
5460
|
+
return alphaEqVarName(a.name, b.name, vmap, protectedVarsA, protectedVarsB);
|
|
5376
5461
|
}
|
|
5377
5462
|
|
|
5378
5463
|
if (a instanceof Iri && b instanceof Iri) return a.value === b.value;
|
|
@@ -5381,7 +5466,7 @@ function alphaEqTermInGraph(a, b, vmap, bmap) {
|
|
|
5381
5466
|
if (a instanceof ListTerm && b instanceof ListTerm) {
|
|
5382
5467
|
if (a.elems.length !== b.elems.length) return false;
|
|
5383
5468
|
for (let i = 0; i < a.elems.length; i++) {
|
|
5384
|
-
if (!alphaEqTermInGraph(a.elems[i], b.elems[i], vmap, bmap)) return false;
|
|
5469
|
+
if (!alphaEqTermInGraph(a.elems[i], b.elems[i], vmap, bmap, opts)) return false;
|
|
5385
5470
|
}
|
|
5386
5471
|
return true;
|
|
5387
5472
|
}
|
|
@@ -5389,29 +5474,30 @@ function alphaEqTermInGraph(a, b, vmap, bmap) {
|
|
|
5389
5474
|
if (a instanceof OpenListTerm && b instanceof OpenListTerm) {
|
|
5390
5475
|
if (a.prefix.length !== b.prefix.length) return false;
|
|
5391
5476
|
for (let i = 0; i < a.prefix.length; i++) {
|
|
5392
|
-
if (!alphaEqTermInGraph(a.prefix[i], b.prefix[i], vmap, bmap)) return false;
|
|
5477
|
+
if (!alphaEqTermInGraph(a.prefix[i], b.prefix[i], vmap, bmap, opts)) return false;
|
|
5393
5478
|
}
|
|
5394
|
-
// tailVar is a var-name string, so treat it as renamable too
|
|
5395
|
-
return alphaEqVarName(a.tailVar, b.tailVar, vmap);
|
|
5479
|
+
// tailVar is a var-name string, so treat it as renamable too when local.
|
|
5480
|
+
return alphaEqVarName(a.tailVar, b.tailVar, vmap, protectedVarsA, protectedVarsB);
|
|
5396
5481
|
}
|
|
5397
5482
|
|
|
5398
|
-
// Nested formulas: compare with fresh maps (separate scope)
|
|
5483
|
+
// Nested formulas: compare with fresh maps (separate scope), but keep the same
|
|
5484
|
+
// protected outer names so already-substituted terms stay fixed everywhere.
|
|
5399
5485
|
if (a instanceof GraphTerm && b instanceof GraphTerm) {
|
|
5400
|
-
return alphaEqGraphTriples(a.triples, b.triples);
|
|
5486
|
+
return alphaEqGraphTriples(a.triples, b.triples, opts);
|
|
5401
5487
|
}
|
|
5402
5488
|
|
|
5403
5489
|
return false;
|
|
5404
5490
|
}
|
|
5405
5491
|
|
|
5406
|
-
function alphaEqTripleInGraph(a, b, vmap, bmap) {
|
|
5492
|
+
function alphaEqTripleInGraph(a, b, vmap, bmap, opts) {
|
|
5407
5493
|
return (
|
|
5408
|
-
alphaEqTermInGraph(a.s, b.s, vmap, bmap) &&
|
|
5409
|
-
alphaEqTermInGraph(a.p, b.p, vmap, bmap) &&
|
|
5410
|
-
alphaEqTermInGraph(a.o, b.o, vmap, bmap)
|
|
5494
|
+
alphaEqTermInGraph(a.s, b.s, vmap, bmap, opts) &&
|
|
5495
|
+
alphaEqTermInGraph(a.p, b.p, vmap, bmap, opts) &&
|
|
5496
|
+
alphaEqTermInGraph(a.o, b.o, vmap, bmap, opts)
|
|
5411
5497
|
);
|
|
5412
5498
|
}
|
|
5413
5499
|
|
|
5414
|
-
function alphaEqGraphTriples(xs, ys) {
|
|
5500
|
+
function alphaEqGraphTriples(xs, ys, opts) {
|
|
5415
5501
|
if (xs.length !== ys.length) return false;
|
|
5416
5502
|
// Fast path: exact same sequence.
|
|
5417
5503
|
if (triplesListEqual(xs, ys)) return true;
|
|
@@ -5430,7 +5516,7 @@ function alphaEqGraphTriples(xs, ys) {
|
|
|
5430
5516
|
|
|
5431
5517
|
const v2 = { ...vmap };
|
|
5432
5518
|
const b2 = { ...bmap };
|
|
5433
|
-
if (!alphaEqTripleInGraph(x, y, v2, b2)) continue;
|
|
5519
|
+
if (!alphaEqTripleInGraph(x, y, v2, b2, opts)) continue;
|
|
5434
5520
|
|
|
5435
5521
|
used[j] = true;
|
|
5436
5522
|
if (step(i + 1, v2, b2)) return true;
|
|
@@ -5512,7 +5598,16 @@ function tripleFastKey(tr) {
|
|
|
5512
5598
|
}
|
|
5513
5599
|
|
|
5514
5600
|
function ensureFactIndexes(facts) {
|
|
5515
|
-
if (
|
|
5601
|
+
if (
|
|
5602
|
+
facts.__byPred &&
|
|
5603
|
+
facts.__byPS &&
|
|
5604
|
+
facts.__byPO &&
|
|
5605
|
+
facts.__wildPred &&
|
|
5606
|
+
facts.__wildPS &&
|
|
5607
|
+
facts.__wildPO &&
|
|
5608
|
+
facts.__keySet
|
|
5609
|
+
)
|
|
5610
|
+
return;
|
|
5516
5611
|
|
|
5517
5612
|
Object.defineProperty(facts, '__byPred', {
|
|
5518
5613
|
value: new Map(),
|
|
@@ -5529,6 +5624,21 @@ function ensureFactIndexes(facts) {
|
|
|
5529
5624
|
enumerable: false,
|
|
5530
5625
|
writable: true,
|
|
5531
5626
|
});
|
|
5627
|
+
Object.defineProperty(facts, '__wildPred', {
|
|
5628
|
+
value: [],
|
|
5629
|
+
enumerable: false,
|
|
5630
|
+
writable: true,
|
|
5631
|
+
});
|
|
5632
|
+
Object.defineProperty(facts, '__wildPS', {
|
|
5633
|
+
value: new Map(),
|
|
5634
|
+
enumerable: false,
|
|
5635
|
+
writable: true,
|
|
5636
|
+
});
|
|
5637
|
+
Object.defineProperty(facts, '__wildPO', {
|
|
5638
|
+
value: new Map(),
|
|
5639
|
+
enumerable: false,
|
|
5640
|
+
writable: true,
|
|
5641
|
+
});
|
|
5532
5642
|
Object.defineProperty(facts, '__keySet', {
|
|
5533
5643
|
value: new Set(),
|
|
5534
5644
|
enumerable: false,
|
|
@@ -5539,6 +5649,9 @@ function ensureFactIndexes(facts) {
|
|
|
5539
5649
|
}
|
|
5540
5650
|
|
|
5541
5651
|
function indexFact(facts, tr, idx) {
|
|
5652
|
+
const sk = termFastKey(tr.s);
|
|
5653
|
+
const ok = termFastKey(tr.o);
|
|
5654
|
+
|
|
5542
5655
|
if (tr.p instanceof Iri) {
|
|
5543
5656
|
// Use predicate term id as the primary key to avoid hashing long IRI strings.
|
|
5544
5657
|
const pk = tr.p.__tid;
|
|
@@ -5550,7 +5663,6 @@ function indexFact(facts, tr, idx) {
|
|
|
5550
5663
|
}
|
|
5551
5664
|
pb.push(idx);
|
|
5552
5665
|
|
|
5553
|
-
const sk = termFastKey(tr.s);
|
|
5554
5666
|
if (sk !== null) {
|
|
5555
5667
|
let ps = facts.__byPS.get(pk);
|
|
5556
5668
|
if (!ps) {
|
|
@@ -5565,7 +5677,6 @@ function indexFact(facts, tr, idx) {
|
|
|
5565
5677
|
psb.push(idx);
|
|
5566
5678
|
}
|
|
5567
5679
|
|
|
5568
|
-
const ok = termFastKey(tr.o);
|
|
5569
5680
|
if (ok !== null) {
|
|
5570
5681
|
let po = facts.__byPO.get(pk);
|
|
5571
5682
|
if (!po) {
|
|
@@ -5579,6 +5690,26 @@ function indexFact(facts, tr, idx) {
|
|
|
5579
5690
|
}
|
|
5580
5691
|
pob.push(idx);
|
|
5581
5692
|
}
|
|
5693
|
+
} else {
|
|
5694
|
+
facts.__wildPred.push(idx);
|
|
5695
|
+
|
|
5696
|
+
if (sk !== null) {
|
|
5697
|
+
let psb = facts.__wildPS.get(sk);
|
|
5698
|
+
if (!psb) {
|
|
5699
|
+
psb = [];
|
|
5700
|
+
facts.__wildPS.set(sk, psb);
|
|
5701
|
+
}
|
|
5702
|
+
psb.push(idx);
|
|
5703
|
+
}
|
|
5704
|
+
|
|
5705
|
+
if (ok !== null) {
|
|
5706
|
+
let pob = facts.__wildPO.get(ok);
|
|
5707
|
+
if (!pob) {
|
|
5708
|
+
pob = [];
|
|
5709
|
+
facts.__wildPO.set(ok, pob);
|
|
5710
|
+
}
|
|
5711
|
+
pob.push(idx);
|
|
5712
|
+
}
|
|
5582
5713
|
}
|
|
5583
5714
|
|
|
5584
5715
|
const key = tripleFastKey(tr);
|
|
@@ -5608,11 +5739,30 @@ function candidateFacts(facts, goal) {
|
|
|
5608
5739
|
if (po) byPO = po.get(ok) || null;
|
|
5609
5740
|
}
|
|
5610
5741
|
|
|
5611
|
-
|
|
5612
|
-
if (byPS)
|
|
5613
|
-
if (
|
|
5742
|
+
let exact = null;
|
|
5743
|
+
if (byPS && byPO) exact = byPS.length <= byPO.length ? byPS : byPO;
|
|
5744
|
+
else if (byPS) exact = byPS;
|
|
5745
|
+
else if (byPO) exact = byPO;
|
|
5746
|
+
else exact = facts.__byPred.get(pk) || null;
|
|
5614
5747
|
|
|
5615
|
-
|
|
5748
|
+
/** @type {number[] | null} */
|
|
5749
|
+
let wildPS = null;
|
|
5750
|
+
if (sk !== null) wildPS = facts.__wildPS.get(sk) || null;
|
|
5751
|
+
|
|
5752
|
+
/** @type {number[] | null} */
|
|
5753
|
+
let wildPO = null;
|
|
5754
|
+
if (ok !== null) wildPO = facts.__wildPO.get(ok) || null;
|
|
5755
|
+
|
|
5756
|
+
let wild = null;
|
|
5757
|
+
if (wildPS && wildPO) wild = wildPS.length <= wildPO.length ? wildPS : wildPO;
|
|
5758
|
+
else if (wildPS) wild = wildPS;
|
|
5759
|
+
else if (wildPO) wild = wildPO;
|
|
5760
|
+
else wild = facts.__wildPred.length ? facts.__wildPred : null;
|
|
5761
|
+
|
|
5762
|
+
if (exact && wild) return exact.concat(wild);
|
|
5763
|
+
if (exact) return exact;
|
|
5764
|
+
if (wild) return wild;
|
|
5765
|
+
return [];
|
|
5616
5766
|
}
|
|
5617
5767
|
|
|
5618
5768
|
return null;
|
|
@@ -5767,7 +5917,13 @@ function __goalMemoKey(goals, subst, facts, opts) {
|
|
|
5767
5917
|
const mode = opts && opts.deferBuiltins ? 'D1' : 'D0';
|
|
5768
5918
|
const scopedLevel = facts && typeof facts.__scopedClosureLevel === 'number' ? facts.__scopedClosureLevel : 0;
|
|
5769
5919
|
const scopedTag = facts && facts.__scopedSnapshot ? 'S' : 'N';
|
|
5770
|
-
|
|
5920
|
+
let keepVarsTag = '';
|
|
5921
|
+
if (opts && opts.keepVars) {
|
|
5922
|
+
const keepVars = Array.isArray(opts.keepVars) ? opts.keepVars.slice() : Array.from(opts.keepVars);
|
|
5923
|
+
keepVars.sort();
|
|
5924
|
+
keepVarsTag = `|K:${keepVars.join(',')}`;
|
|
5925
|
+
}
|
|
5926
|
+
return `${mode}|${scopedTag}|${scopedLevel}${keepVarsTag}|${parts.join('\n')}`;
|
|
5771
5927
|
}
|
|
5772
5928
|
|
|
5773
5929
|
function __cloneGoalSolutions(solutions) {
|
|
@@ -6149,7 +6305,17 @@ function unifyTermWithOptions(a, b, subst, opts) {
|
|
|
6149
6305
|
|
|
6150
6306
|
// Graphs
|
|
6151
6307
|
if (a instanceof GraphTerm && b instanceof GraphTerm) {
|
|
6152
|
-
|
|
6308
|
+
const protectedNames = collectProtectedNamesFromSubst(subst);
|
|
6309
|
+
if (
|
|
6310
|
+
alphaEqGraphTriples(a.triples, b.triples, {
|
|
6311
|
+
protectedVarsA: protectedNames.protectedVars,
|
|
6312
|
+
protectedVarsB: protectedNames.protectedVars,
|
|
6313
|
+
protectedBlanksA: protectedNames.protectedBlanks,
|
|
6314
|
+
protectedBlanksB: protectedNames.protectedBlanks,
|
|
6315
|
+
})
|
|
6316
|
+
) {
|
|
6317
|
+
return subst;
|
|
6318
|
+
}
|
|
6153
6319
|
return unifyGraphTriples(a.triples, b.triples, subst);
|
|
6154
6320
|
}
|
|
6155
6321
|
|
|
@@ -6299,6 +6465,9 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxR
|
|
|
6299
6465
|
// Variables from the original goal list (needed by the caller to instantiate conclusions)
|
|
6300
6466
|
const answerVars = new Set();
|
|
6301
6467
|
gcCollectVarsInGoals(initialGoals, answerVars);
|
|
6468
|
+
if (opts && opts.keepVars) {
|
|
6469
|
+
for (const v of opts.keepVars) answerVars.add(v);
|
|
6470
|
+
}
|
|
6302
6471
|
|
|
6303
6472
|
if (!initialGoals.length) {
|
|
6304
6473
|
results.push(gcCompactForGoals(substMut, [], answerVars));
|
|
@@ -6521,7 +6690,17 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxR
|
|
|
6521
6690
|
|
|
6522
6691
|
// Graphs
|
|
6523
6692
|
if (a instanceof GraphTerm && b instanceof GraphTerm) {
|
|
6524
|
-
|
|
6693
|
+
const protectedNames = collectProtectedNamesFromSubst(substMut);
|
|
6694
|
+
if (
|
|
6695
|
+
alphaEqGraphTriples(a.triples, b.triples, {
|
|
6696
|
+
protectedVarsA: protectedNames.protectedVars,
|
|
6697
|
+
protectedVarsB: protectedNames.protectedVars,
|
|
6698
|
+
protectedBlanksA: protectedNames.protectedBlanks,
|
|
6699
|
+
protectedBlanksB: protectedNames.protectedBlanks,
|
|
6700
|
+
})
|
|
6701
|
+
) {
|
|
6702
|
+
return true;
|
|
6703
|
+
}
|
|
6525
6704
|
// Fallback: reuse allocation-heavy graph unifier rarely hit in typical workloads.
|
|
6526
6705
|
const delta = unifyGraphTriples(a.triples, b.triples, {});
|
|
6527
6706
|
if (delta === null) return false;
|
package/lib/builtins.js
CHANGED
|
@@ -98,6 +98,35 @@ function makeBuiltins(deps) {
|
|
|
98
98
|
return { evalBuiltin, isBuiltinPred };
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
function __builtinCollectVarsInTerm(t, out) {
|
|
102
|
+
if (t instanceof Var) {
|
|
103
|
+
out.add(t.name);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (t instanceof ListTerm) {
|
|
107
|
+
for (const e of t.elems) __builtinCollectVarsInTerm(e, out);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (t instanceof OpenListTerm) {
|
|
111
|
+
for (const e of t.prefix) __builtinCollectVarsInTerm(e, out);
|
|
112
|
+
out.add(t.tailVar);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (t instanceof GraphTerm) {
|
|
116
|
+
for (const tr of t.triples) __builtinCollectVarsInTriple(tr, out);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function __builtinCollectVarsInTriple(tr, out) {
|
|
121
|
+
__builtinCollectVarsInTerm(tr.s, out);
|
|
122
|
+
__builtinCollectVarsInTerm(tr.p, out);
|
|
123
|
+
__builtinCollectVarsInTerm(tr.o, out);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function __builtinCollectVarsInTriples(triples, out) {
|
|
127
|
+
for (const tr of triples) __builtinCollectVarsInTriple(tr, out);
|
|
128
|
+
}
|
|
129
|
+
|
|
101
130
|
function literalHasLangTag(lit) {
|
|
102
131
|
// True iff the literal is a quoted string literal with a language tag suffix,
|
|
103
132
|
// e.g. "hello"@en or """hello"""@en.
|
|
@@ -3028,6 +3057,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
3028
3057
|
if (!(g.o instanceof GraphTerm)) return [];
|
|
3029
3058
|
|
|
3030
3059
|
const visited2 = [];
|
|
3060
|
+
const keepVars = new Set();
|
|
3061
|
+
if (g.s instanceof GraphTerm) __builtinCollectVarsInTriples(g.s.triples, keepVars);
|
|
3031
3062
|
// Start from the incoming substitution so bindings flow outward.
|
|
3032
3063
|
return proveGoals(
|
|
3033
3064
|
Array.from(g.o.triples),
|
|
@@ -3038,6 +3069,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
3038
3069
|
visited2,
|
|
3039
3070
|
varGen,
|
|
3040
3071
|
maxResults,
|
|
3072
|
+
keepVars.size ? { keepVars } : undefined,
|
|
3041
3073
|
);
|
|
3042
3074
|
}
|
|
3043
3075
|
|
package/lib/engine.js
CHANGED
|
@@ -736,27 +736,80 @@ function triplesListEqual(xs, ys) {
|
|
|
736
736
|
return true;
|
|
737
737
|
}
|
|
738
738
|
|
|
739
|
-
|
|
739
|
+
function collectProtectedNamesInTerm(t, protectedVars, protectedBlanks) {
|
|
740
|
+
if (t instanceof Var) {
|
|
741
|
+
protectedVars.add(t.name);
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
if (t instanceof Blank) {
|
|
745
|
+
protectedBlanks.add(t.label);
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
if (t instanceof ListTerm) {
|
|
749
|
+
for (const e of t.elems) collectProtectedNamesInTerm(e, protectedVars, protectedBlanks);
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
if (t instanceof OpenListTerm) {
|
|
753
|
+
for (const e of t.prefix) collectProtectedNamesInTerm(e, protectedVars, protectedBlanks);
|
|
754
|
+
protectedVars.add(t.tailVar);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
if (t instanceof GraphTerm) {
|
|
758
|
+
for (const tr of t.triples) {
|
|
759
|
+
collectProtectedNamesInTerm(tr.s, protectedVars, protectedBlanks);
|
|
760
|
+
collectProtectedNamesInTerm(tr.p, protectedVars, protectedBlanks);
|
|
761
|
+
collectProtectedNamesInTerm(tr.o, protectedVars, protectedBlanks);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
function collectProtectedNamesFromSubst(subst) {
|
|
767
|
+
const protectedVars = new Set();
|
|
768
|
+
const protectedBlanks = new Set();
|
|
769
|
+
if (!subst) return { protectedVars, protectedBlanks };
|
|
770
|
+
for (const k in subst) {
|
|
771
|
+
if (!Object.prototype.hasOwnProperty.call(subst, k)) continue;
|
|
772
|
+
collectProtectedNamesInTerm(subst[k], protectedVars, protectedBlanks);
|
|
773
|
+
}
|
|
774
|
+
return { protectedVars, protectedBlanks };
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// Alpha-equivalence for quoted formulas, up to *local* variable and blank-node renaming.
|
|
778
|
+
// Terms that originate from the surrounding substitution are treated as fixed and are
|
|
779
|
+
// therefore not alpha-renamable inside the quoted formula.
|
|
740
780
|
// Treats a formula as an unordered set of triples (order-insensitive match).
|
|
741
|
-
function alphaEqVarName(x, y, vmap) {
|
|
781
|
+
function alphaEqVarName(x, y, vmap, protectedVarsA, protectedVarsB) {
|
|
782
|
+
const xProtected = protectedVarsA && protectedVarsA.has(x);
|
|
783
|
+
const yProtected = protectedVarsB && protectedVarsB.has(y);
|
|
784
|
+
if (xProtected || yProtected) return xProtected && yProtected && x === y;
|
|
742
785
|
if (Object.prototype.hasOwnProperty.call(vmap, x)) return vmap[x] === y;
|
|
743
786
|
vmap[x] = y;
|
|
744
787
|
return true;
|
|
745
788
|
}
|
|
746
789
|
|
|
747
|
-
function
|
|
748
|
-
|
|
790
|
+
function alphaEqBlankLabel(x, y, bmap, protectedBlanksA, protectedBlanksB) {
|
|
791
|
+
const xProtected = protectedBlanksA && protectedBlanksA.has(x);
|
|
792
|
+
const yProtected = protectedBlanksB && protectedBlanksB.has(y);
|
|
793
|
+
if (xProtected || yProtected) return xProtected && yProtected && x === y;
|
|
794
|
+
if (Object.prototype.hasOwnProperty.call(bmap, x)) return bmap[x] === y;
|
|
795
|
+
bmap[x] = y;
|
|
796
|
+
return true;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function alphaEqTermInGraph(a, b, vmap, bmap, opts) {
|
|
800
|
+
const protectedVarsA = opts && opts.protectedVarsA;
|
|
801
|
+
const protectedVarsB = opts && opts.protectedVarsB;
|
|
802
|
+
const protectedBlanksA = opts && opts.protectedBlanksA;
|
|
803
|
+
const protectedBlanksB = opts && opts.protectedBlanksB;
|
|
804
|
+
|
|
805
|
+
// Blank nodes: renamable only when they are local to the formula.
|
|
749
806
|
if (a instanceof Blank && b instanceof Blank) {
|
|
750
|
-
|
|
751
|
-
const y = b.label;
|
|
752
|
-
if (Object.prototype.hasOwnProperty.call(bmap, x)) return bmap[x] === y;
|
|
753
|
-
bmap[x] = y;
|
|
754
|
-
return true;
|
|
807
|
+
return alphaEqBlankLabel(a.label, b.label, bmap, protectedBlanksA, protectedBlanksB);
|
|
755
808
|
}
|
|
756
809
|
|
|
757
|
-
// Variables: renamable
|
|
810
|
+
// Variables: renamable only when they are local to the formula.
|
|
758
811
|
if (a instanceof Var && b instanceof Var) {
|
|
759
|
-
return alphaEqVarName(a.name, b.name, vmap);
|
|
812
|
+
return alphaEqVarName(a.name, b.name, vmap, protectedVarsA, protectedVarsB);
|
|
760
813
|
}
|
|
761
814
|
|
|
762
815
|
if (a instanceof Iri && b instanceof Iri) return a.value === b.value;
|
|
@@ -765,7 +818,7 @@ function alphaEqTermInGraph(a, b, vmap, bmap) {
|
|
|
765
818
|
if (a instanceof ListTerm && b instanceof ListTerm) {
|
|
766
819
|
if (a.elems.length !== b.elems.length) return false;
|
|
767
820
|
for (let i = 0; i < a.elems.length; i++) {
|
|
768
|
-
if (!alphaEqTermInGraph(a.elems[i], b.elems[i], vmap, bmap)) return false;
|
|
821
|
+
if (!alphaEqTermInGraph(a.elems[i], b.elems[i], vmap, bmap, opts)) return false;
|
|
769
822
|
}
|
|
770
823
|
return true;
|
|
771
824
|
}
|
|
@@ -773,29 +826,30 @@ function alphaEqTermInGraph(a, b, vmap, bmap) {
|
|
|
773
826
|
if (a instanceof OpenListTerm && b instanceof OpenListTerm) {
|
|
774
827
|
if (a.prefix.length !== b.prefix.length) return false;
|
|
775
828
|
for (let i = 0; i < a.prefix.length; i++) {
|
|
776
|
-
if (!alphaEqTermInGraph(a.prefix[i], b.prefix[i], vmap, bmap)) return false;
|
|
829
|
+
if (!alphaEqTermInGraph(a.prefix[i], b.prefix[i], vmap, bmap, opts)) return false;
|
|
777
830
|
}
|
|
778
|
-
// tailVar is a var-name string, so treat it as renamable too
|
|
779
|
-
return alphaEqVarName(a.tailVar, b.tailVar, vmap);
|
|
831
|
+
// tailVar is a var-name string, so treat it as renamable too when local.
|
|
832
|
+
return alphaEqVarName(a.tailVar, b.tailVar, vmap, protectedVarsA, protectedVarsB);
|
|
780
833
|
}
|
|
781
834
|
|
|
782
|
-
// Nested formulas: compare with fresh maps (separate scope)
|
|
835
|
+
// Nested formulas: compare with fresh maps (separate scope), but keep the same
|
|
836
|
+
// protected outer names so already-substituted terms stay fixed everywhere.
|
|
783
837
|
if (a instanceof GraphTerm && b instanceof GraphTerm) {
|
|
784
|
-
return alphaEqGraphTriples(a.triples, b.triples);
|
|
838
|
+
return alphaEqGraphTriples(a.triples, b.triples, opts);
|
|
785
839
|
}
|
|
786
840
|
|
|
787
841
|
return false;
|
|
788
842
|
}
|
|
789
843
|
|
|
790
|
-
function alphaEqTripleInGraph(a, b, vmap, bmap) {
|
|
844
|
+
function alphaEqTripleInGraph(a, b, vmap, bmap, opts) {
|
|
791
845
|
return (
|
|
792
|
-
alphaEqTermInGraph(a.s, b.s, vmap, bmap) &&
|
|
793
|
-
alphaEqTermInGraph(a.p, b.p, vmap, bmap) &&
|
|
794
|
-
alphaEqTermInGraph(a.o, b.o, vmap, bmap)
|
|
846
|
+
alphaEqTermInGraph(a.s, b.s, vmap, bmap, opts) &&
|
|
847
|
+
alphaEqTermInGraph(a.p, b.p, vmap, bmap, opts) &&
|
|
848
|
+
alphaEqTermInGraph(a.o, b.o, vmap, bmap, opts)
|
|
795
849
|
);
|
|
796
850
|
}
|
|
797
851
|
|
|
798
|
-
function alphaEqGraphTriples(xs, ys) {
|
|
852
|
+
function alphaEqGraphTriples(xs, ys, opts) {
|
|
799
853
|
if (xs.length !== ys.length) return false;
|
|
800
854
|
// Fast path: exact same sequence.
|
|
801
855
|
if (triplesListEqual(xs, ys)) return true;
|
|
@@ -814,7 +868,7 @@ function alphaEqGraphTriples(xs, ys) {
|
|
|
814
868
|
|
|
815
869
|
const v2 = { ...vmap };
|
|
816
870
|
const b2 = { ...bmap };
|
|
817
|
-
if (!alphaEqTripleInGraph(x, y, v2, b2)) continue;
|
|
871
|
+
if (!alphaEqTripleInGraph(x, y, v2, b2, opts)) continue;
|
|
818
872
|
|
|
819
873
|
used[j] = true;
|
|
820
874
|
if (step(i + 1, v2, b2)) return true;
|
|
@@ -896,7 +950,16 @@ function tripleFastKey(tr) {
|
|
|
896
950
|
}
|
|
897
951
|
|
|
898
952
|
function ensureFactIndexes(facts) {
|
|
899
|
-
if (
|
|
953
|
+
if (
|
|
954
|
+
facts.__byPred &&
|
|
955
|
+
facts.__byPS &&
|
|
956
|
+
facts.__byPO &&
|
|
957
|
+
facts.__wildPred &&
|
|
958
|
+
facts.__wildPS &&
|
|
959
|
+
facts.__wildPO &&
|
|
960
|
+
facts.__keySet
|
|
961
|
+
)
|
|
962
|
+
return;
|
|
900
963
|
|
|
901
964
|
Object.defineProperty(facts, '__byPred', {
|
|
902
965
|
value: new Map(),
|
|
@@ -913,6 +976,21 @@ function ensureFactIndexes(facts) {
|
|
|
913
976
|
enumerable: false,
|
|
914
977
|
writable: true,
|
|
915
978
|
});
|
|
979
|
+
Object.defineProperty(facts, '__wildPred', {
|
|
980
|
+
value: [],
|
|
981
|
+
enumerable: false,
|
|
982
|
+
writable: true,
|
|
983
|
+
});
|
|
984
|
+
Object.defineProperty(facts, '__wildPS', {
|
|
985
|
+
value: new Map(),
|
|
986
|
+
enumerable: false,
|
|
987
|
+
writable: true,
|
|
988
|
+
});
|
|
989
|
+
Object.defineProperty(facts, '__wildPO', {
|
|
990
|
+
value: new Map(),
|
|
991
|
+
enumerable: false,
|
|
992
|
+
writable: true,
|
|
993
|
+
});
|
|
916
994
|
Object.defineProperty(facts, '__keySet', {
|
|
917
995
|
value: new Set(),
|
|
918
996
|
enumerable: false,
|
|
@@ -923,6 +1001,9 @@ function ensureFactIndexes(facts) {
|
|
|
923
1001
|
}
|
|
924
1002
|
|
|
925
1003
|
function indexFact(facts, tr, idx) {
|
|
1004
|
+
const sk = termFastKey(tr.s);
|
|
1005
|
+
const ok = termFastKey(tr.o);
|
|
1006
|
+
|
|
926
1007
|
if (tr.p instanceof Iri) {
|
|
927
1008
|
// Use predicate term id as the primary key to avoid hashing long IRI strings.
|
|
928
1009
|
const pk = tr.p.__tid;
|
|
@@ -934,7 +1015,6 @@ function indexFact(facts, tr, idx) {
|
|
|
934
1015
|
}
|
|
935
1016
|
pb.push(idx);
|
|
936
1017
|
|
|
937
|
-
const sk = termFastKey(tr.s);
|
|
938
1018
|
if (sk !== null) {
|
|
939
1019
|
let ps = facts.__byPS.get(pk);
|
|
940
1020
|
if (!ps) {
|
|
@@ -949,7 +1029,6 @@ function indexFact(facts, tr, idx) {
|
|
|
949
1029
|
psb.push(idx);
|
|
950
1030
|
}
|
|
951
1031
|
|
|
952
|
-
const ok = termFastKey(tr.o);
|
|
953
1032
|
if (ok !== null) {
|
|
954
1033
|
let po = facts.__byPO.get(pk);
|
|
955
1034
|
if (!po) {
|
|
@@ -963,6 +1042,26 @@ function indexFact(facts, tr, idx) {
|
|
|
963
1042
|
}
|
|
964
1043
|
pob.push(idx);
|
|
965
1044
|
}
|
|
1045
|
+
} else {
|
|
1046
|
+
facts.__wildPred.push(idx);
|
|
1047
|
+
|
|
1048
|
+
if (sk !== null) {
|
|
1049
|
+
let psb = facts.__wildPS.get(sk);
|
|
1050
|
+
if (!psb) {
|
|
1051
|
+
psb = [];
|
|
1052
|
+
facts.__wildPS.set(sk, psb);
|
|
1053
|
+
}
|
|
1054
|
+
psb.push(idx);
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
if (ok !== null) {
|
|
1058
|
+
let pob = facts.__wildPO.get(ok);
|
|
1059
|
+
if (!pob) {
|
|
1060
|
+
pob = [];
|
|
1061
|
+
facts.__wildPO.set(ok, pob);
|
|
1062
|
+
}
|
|
1063
|
+
pob.push(idx);
|
|
1064
|
+
}
|
|
966
1065
|
}
|
|
967
1066
|
|
|
968
1067
|
const key = tripleFastKey(tr);
|
|
@@ -992,11 +1091,30 @@ function candidateFacts(facts, goal) {
|
|
|
992
1091
|
if (po) byPO = po.get(ok) || null;
|
|
993
1092
|
}
|
|
994
1093
|
|
|
995
|
-
|
|
996
|
-
if (byPS)
|
|
997
|
-
if (
|
|
1094
|
+
let exact = null;
|
|
1095
|
+
if (byPS && byPO) exact = byPS.length <= byPO.length ? byPS : byPO;
|
|
1096
|
+
else if (byPS) exact = byPS;
|
|
1097
|
+
else if (byPO) exact = byPO;
|
|
1098
|
+
else exact = facts.__byPred.get(pk) || null;
|
|
1099
|
+
|
|
1100
|
+
/** @type {number[] | null} */
|
|
1101
|
+
let wildPS = null;
|
|
1102
|
+
if (sk !== null) wildPS = facts.__wildPS.get(sk) || null;
|
|
1103
|
+
|
|
1104
|
+
/** @type {number[] | null} */
|
|
1105
|
+
let wildPO = null;
|
|
1106
|
+
if (ok !== null) wildPO = facts.__wildPO.get(ok) || null;
|
|
1107
|
+
|
|
1108
|
+
let wild = null;
|
|
1109
|
+
if (wildPS && wildPO) wild = wildPS.length <= wildPO.length ? wildPS : wildPO;
|
|
1110
|
+
else if (wildPS) wild = wildPS;
|
|
1111
|
+
else if (wildPO) wild = wildPO;
|
|
1112
|
+
else wild = facts.__wildPred.length ? facts.__wildPred : null;
|
|
998
1113
|
|
|
999
|
-
return
|
|
1114
|
+
if (exact && wild) return exact.concat(wild);
|
|
1115
|
+
if (exact) return exact;
|
|
1116
|
+
if (wild) return wild;
|
|
1117
|
+
return [];
|
|
1000
1118
|
}
|
|
1001
1119
|
|
|
1002
1120
|
return null;
|
|
@@ -1151,7 +1269,13 @@ function __goalMemoKey(goals, subst, facts, opts) {
|
|
|
1151
1269
|
const mode = opts && opts.deferBuiltins ? 'D1' : 'D0';
|
|
1152
1270
|
const scopedLevel = facts && typeof facts.__scopedClosureLevel === 'number' ? facts.__scopedClosureLevel : 0;
|
|
1153
1271
|
const scopedTag = facts && facts.__scopedSnapshot ? 'S' : 'N';
|
|
1154
|
-
|
|
1272
|
+
let keepVarsTag = '';
|
|
1273
|
+
if (opts && opts.keepVars) {
|
|
1274
|
+
const keepVars = Array.isArray(opts.keepVars) ? opts.keepVars.slice() : Array.from(opts.keepVars);
|
|
1275
|
+
keepVars.sort();
|
|
1276
|
+
keepVarsTag = `|K:${keepVars.join(',')}`;
|
|
1277
|
+
}
|
|
1278
|
+
return `${mode}|${scopedTag}|${scopedLevel}${keepVarsTag}|${parts.join('\n')}`;
|
|
1155
1279
|
}
|
|
1156
1280
|
|
|
1157
1281
|
function __cloneGoalSolutions(solutions) {
|
|
@@ -1533,7 +1657,17 @@ function unifyTermWithOptions(a, b, subst, opts) {
|
|
|
1533
1657
|
|
|
1534
1658
|
// Graphs
|
|
1535
1659
|
if (a instanceof GraphTerm && b instanceof GraphTerm) {
|
|
1536
|
-
|
|
1660
|
+
const protectedNames = collectProtectedNamesFromSubst(subst);
|
|
1661
|
+
if (
|
|
1662
|
+
alphaEqGraphTriples(a.triples, b.triples, {
|
|
1663
|
+
protectedVarsA: protectedNames.protectedVars,
|
|
1664
|
+
protectedVarsB: protectedNames.protectedVars,
|
|
1665
|
+
protectedBlanksA: protectedNames.protectedBlanks,
|
|
1666
|
+
protectedBlanksB: protectedNames.protectedBlanks,
|
|
1667
|
+
})
|
|
1668
|
+
) {
|
|
1669
|
+
return subst;
|
|
1670
|
+
}
|
|
1537
1671
|
return unifyGraphTriples(a.triples, b.triples, subst);
|
|
1538
1672
|
}
|
|
1539
1673
|
|
|
@@ -1683,6 +1817,9 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxR
|
|
|
1683
1817
|
// Variables from the original goal list (needed by the caller to instantiate conclusions)
|
|
1684
1818
|
const answerVars = new Set();
|
|
1685
1819
|
gcCollectVarsInGoals(initialGoals, answerVars);
|
|
1820
|
+
if (opts && opts.keepVars) {
|
|
1821
|
+
for (const v of opts.keepVars) answerVars.add(v);
|
|
1822
|
+
}
|
|
1686
1823
|
|
|
1687
1824
|
if (!initialGoals.length) {
|
|
1688
1825
|
results.push(gcCompactForGoals(substMut, [], answerVars));
|
|
@@ -1905,7 +2042,17 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxR
|
|
|
1905
2042
|
|
|
1906
2043
|
// Graphs
|
|
1907
2044
|
if (a instanceof GraphTerm && b instanceof GraphTerm) {
|
|
1908
|
-
|
|
2045
|
+
const protectedNames = collectProtectedNamesFromSubst(substMut);
|
|
2046
|
+
if (
|
|
2047
|
+
alphaEqGraphTriples(a.triples, b.triples, {
|
|
2048
|
+
protectedVarsA: protectedNames.protectedVars,
|
|
2049
|
+
protectedVarsB: protectedNames.protectedVars,
|
|
2050
|
+
protectedBlanksA: protectedNames.protectedBlanks,
|
|
2051
|
+
protectedBlanksB: protectedNames.protectedBlanks,
|
|
2052
|
+
})
|
|
2053
|
+
) {
|
|
2054
|
+
return true;
|
|
2055
|
+
}
|
|
1909
2056
|
// Fallback: reuse allocation-heavy graph unifier rarely hit in typical workloads.
|
|
1910
2057
|
const delta = unifyGraphTriples(a.triples, b.triples, {});
|
|
1911
2058
|
if (delta === null) return false;
|
package/package.json
CHANGED
package/test/api.test.js
CHANGED
|
@@ -1432,6 +1432,73 @@ ex:w a ex:Woman .
|
|
|
1432
1432
|
`,
|
|
1433
1433
|
expect: [/:(?:test)\s+:(?:is)\s+true\s*\./],
|
|
1434
1434
|
},
|
|
1435
|
+
|
|
1436
|
+
{
|
|
1437
|
+
name: '59 regression: quoted-formula alpha-equivalence must not rename blanks introduced by outer substitution',
|
|
1438
|
+
opt: { proofComments: false },
|
|
1439
|
+
input: `@prefix : <http://example.org/> .
|
|
1440
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
1441
|
+
@prefix math: <http://www.w3.org/2000/10/swap/math#> .
|
|
1442
|
+
|
|
1443
|
+
_:x :hates { _:foo :making :mess }.
|
|
1444
|
+
|
|
1445
|
+
{
|
|
1446
|
+
?A :hates { ?A :making :mess }.
|
|
1447
|
+
}
|
|
1448
|
+
=>
|
|
1449
|
+
{
|
|
1450
|
+
?A :hates :Himself.
|
|
1451
|
+
}.
|
|
1452
|
+
|
|
1453
|
+
{
|
|
1454
|
+
?A :hates :Himself.
|
|
1455
|
+
}
|
|
1456
|
+
=>
|
|
1457
|
+
{
|
|
1458
|
+
:test :is false.
|
|
1459
|
+
}.
|
|
1460
|
+
`,
|
|
1461
|
+
notExpect: [/:(?:test)\s+:(?:is)\s+false\s*\./],
|
|
1462
|
+
},
|
|
1463
|
+
|
|
1464
|
+
{
|
|
1465
|
+
name: '60 regression: log:includes must match quoted triples with variable predicates',
|
|
1466
|
+
opt: { proofComments: false },
|
|
1467
|
+
input: `@prefix : <http://example.org/> .
|
|
1468
|
+
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
1469
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
1470
|
+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
|
1471
|
+
@base <http://example.org/>.
|
|
1472
|
+
|
|
1473
|
+
{
|
|
1474
|
+
{ ?X ?Y ?Z. } log:includes { :a :b :c. }.
|
|
1475
|
+
}
|
|
1476
|
+
=>
|
|
1477
|
+
{
|
|
1478
|
+
?X ?Y ?Z.
|
|
1479
|
+
{
|
|
1480
|
+
:a :b :c.
|
|
1481
|
+
}
|
|
1482
|
+
=>
|
|
1483
|
+
{
|
|
1484
|
+
:result :has :success-literal-3.
|
|
1485
|
+
}.
|
|
1486
|
+
}.
|
|
1487
|
+
|
|
1488
|
+
{ } => {
|
|
1489
|
+
:test :contains :success-literal-3.
|
|
1490
|
+
}.
|
|
1491
|
+
|
|
1492
|
+
{
|
|
1493
|
+
:result :has :success-literal-3.
|
|
1494
|
+
}
|
|
1495
|
+
=>
|
|
1496
|
+
{
|
|
1497
|
+
:test :is true.
|
|
1498
|
+
}.
|
|
1499
|
+
`,
|
|
1500
|
+
expect: [/:(?:test)\s+:(?:contains)\s+:(?:success-literal-3)\s*\./, /:(?:test)\s+:(?:is)\s+true\s*\./],
|
|
1501
|
+
},
|
|
1435
1502
|
];
|
|
1436
1503
|
|
|
1437
1504
|
let passed = 0;
|