eyeling 1.5.33 → 1.5.35
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/examples/output/rdf-list.n3 +31 -0
- package/examples/rdf-list.n3 +22 -0
- package/eyeling.js +193 -112
- package/package.json +1 -1
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
@prefix : <http://example.org/ns#> .
|
|
2
|
+
|
|
3
|
+
# ----------------------------------------------------------------------
|
|
4
|
+
# Proof for derived triple:
|
|
5
|
+
# :result :is true .
|
|
6
|
+
# It holds because the following instance of the rule body is provable:
|
|
7
|
+
# :X :val (1 2) .
|
|
8
|
+
# (1 2) :head 1 .
|
|
9
|
+
# (1 2) :tail (2) .
|
|
10
|
+
# 1 log:equalTo 1 .
|
|
11
|
+
# (2) log:equalTo (2) .
|
|
12
|
+
# via the schematic forward rule:
|
|
13
|
+
# {
|
|
14
|
+
# ?X :val ?Y .
|
|
15
|
+
# ?Y :head ?H .
|
|
16
|
+
# ?Y :tail ?T .
|
|
17
|
+
# ?H log:equalTo 1 .
|
|
18
|
+
# ?T log:equalTo (2) .
|
|
19
|
+
# } => {
|
|
20
|
+
# :result :is true .
|
|
21
|
+
# } .
|
|
22
|
+
# with substitution (on rule variables):
|
|
23
|
+
# ?H = 1
|
|
24
|
+
# ?T = (2)
|
|
25
|
+
# ?X = :X
|
|
26
|
+
# ?Y = (1 2)
|
|
27
|
+
# Therefore the derived triple above is entailed by the rules and facts.
|
|
28
|
+
# ----------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
:result :is true .
|
|
31
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
2
|
+
@prefix list: <http://www.w3.org/2000/10/swap/list#> .
|
|
3
|
+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
|
4
|
+
@prefix : <http://example.org/ns#> .
|
|
5
|
+
|
|
6
|
+
:X :val (1 2).
|
|
7
|
+
|
|
8
|
+
{ ?X :head ?Y } <= { ?X list:first ?Y }.
|
|
9
|
+
{ ?X :tail ?Y } <= { ?X list:rest ?Y} .
|
|
10
|
+
|
|
11
|
+
{
|
|
12
|
+
?X :val ?Y.
|
|
13
|
+
?Y :head ?H .
|
|
14
|
+
?Y :tail ?T .
|
|
15
|
+
|
|
16
|
+
?H log:equalTo 1.
|
|
17
|
+
?T log:equalTo (2).
|
|
18
|
+
}
|
|
19
|
+
=>
|
|
20
|
+
{
|
|
21
|
+
:result :is true.
|
|
22
|
+
}.
|
package/eyeling.js
CHANGED
|
@@ -1867,91 +1867,6 @@ function termToJsStringDecoded(t) {
|
|
|
1867
1867
|
return stripQuotes(lex);
|
|
1868
1868
|
}
|
|
1869
1869
|
|
|
1870
|
-
function _jsonPointerUnescape(seg) {
|
|
1871
|
-
// RFC6901: ~1 -> '/', ~0 -> '~'
|
|
1872
|
-
// Any other '~' escape is invalid.
|
|
1873
|
-
let out = "";
|
|
1874
|
-
for (let i = 0; i < seg.length; i++) {
|
|
1875
|
-
const c = seg[i];
|
|
1876
|
-
if (c !== "~") { out += c; continue; }
|
|
1877
|
-
if (i + 1 >= seg.length) return null;
|
|
1878
|
-
const n = seg[i + 1];
|
|
1879
|
-
if (n === "0") out += "~";
|
|
1880
|
-
else if (n === "1") out += "/";
|
|
1881
|
-
else return null;
|
|
1882
|
-
i++;
|
|
1883
|
-
}
|
|
1884
|
-
return out;
|
|
1885
|
-
}
|
|
1886
|
-
|
|
1887
|
-
function _jsonToTerm(v) {
|
|
1888
|
-
if (v === null) return makeStringLiteral("null");
|
|
1889
|
-
if (typeof v === "string") return makeStringLiteral(v);
|
|
1890
|
-
if (typeof v === "number") return new Literal(String(v));
|
|
1891
|
-
if (typeof v === "boolean") return new Literal(v ? "true" : "false");
|
|
1892
|
-
if (Array.isArray(v)) return new ListTerm(v.map(_jsonToTerm));
|
|
1893
|
-
if (typeof v === "object") return makeStringLiteral(JSON.stringify(v));
|
|
1894
|
-
return null;
|
|
1895
|
-
}
|
|
1896
|
-
|
|
1897
|
-
function _jsonPointerLookup(jsonText, pointer) {
|
|
1898
|
-
// Support URI fragment form "#/a/b" (percent-decoded) as well.
|
|
1899
|
-
let ptr = pointer;
|
|
1900
|
-
if (ptr.startsWith("#")) {
|
|
1901
|
-
try { ptr = decodeURIComponent(ptr.slice(1)); } catch (e) { return null; }
|
|
1902
|
-
}
|
|
1903
|
-
|
|
1904
|
-
// Cache per JSON document
|
|
1905
|
-
let entry = jsonPointerCache.get(jsonText);
|
|
1906
|
-
if (!entry) {
|
|
1907
|
-
let parsed = null;
|
|
1908
|
-
try { parsed = JSON.parse(jsonText); } catch (e) { parsed = null; }
|
|
1909
|
-
entry = { parsed, ptrCache: new Map() };
|
|
1910
|
-
jsonPointerCache.set(jsonText, entry);
|
|
1911
|
-
}
|
|
1912
|
-
if (entry.parsed === null) return null;
|
|
1913
|
-
|
|
1914
|
-
// Cache per pointer within this doc
|
|
1915
|
-
if (entry.ptrCache.has(ptr)) return entry.ptrCache.get(ptr);
|
|
1916
|
-
|
|
1917
|
-
let cur = entry.parsed;
|
|
1918
|
-
|
|
1919
|
-
if (ptr === "") {
|
|
1920
|
-
const t = _jsonToTerm(cur);
|
|
1921
|
-
entry.ptrCache.set(ptr, t);
|
|
1922
|
-
return t;
|
|
1923
|
-
}
|
|
1924
|
-
if (!ptr.startsWith("/")) { entry.ptrCache.set(ptr, null); return null; }
|
|
1925
|
-
|
|
1926
|
-
const parts = ptr.split("/").slice(1);
|
|
1927
|
-
for (const raw of parts) {
|
|
1928
|
-
const seg = _jsonPointerUnescape(raw);
|
|
1929
|
-
if (seg === null) { entry.ptrCache.set(ptr, null); return null; }
|
|
1930
|
-
|
|
1931
|
-
if (Array.isArray(cur)) {
|
|
1932
|
-
// JSON Pointer uses array indices as decimal strings
|
|
1933
|
-
if (!/^(0|[1-9]\d*)$/.test(seg)) { entry.ptrCache.set(ptr, null); return null; }
|
|
1934
|
-
const idx = Number(seg);
|
|
1935
|
-
if (!Number.isFinite(idx) || idx < 0 || idx >= cur.length) { entry.ptrCache.set(ptr, null); return null; }
|
|
1936
|
-
cur = cur[idx];
|
|
1937
|
-
continue;
|
|
1938
|
-
}
|
|
1939
|
-
|
|
1940
|
-
if (cur !== null && typeof cur === "object") {
|
|
1941
|
-
if (!Object.prototype.hasOwnProperty.call(cur, seg)) { entry.ptrCache.set(ptr, null); return null; }
|
|
1942
|
-
cur = cur[seg];
|
|
1943
|
-
continue;
|
|
1944
|
-
}
|
|
1945
|
-
|
|
1946
|
-
entry.ptrCache.set(ptr, null);
|
|
1947
|
-
return null;
|
|
1948
|
-
}
|
|
1949
|
-
|
|
1950
|
-
const out = _jsonToTerm(cur);
|
|
1951
|
-
entry.ptrCache.set(ptr, out);
|
|
1952
|
-
return out;
|
|
1953
|
-
}
|
|
1954
|
-
|
|
1955
1870
|
function jsonPointerUnescape(seg) {
|
|
1956
1871
|
// RFC6901: ~1 -> '/', ~0 -> '~'
|
|
1957
1872
|
let out = "";
|
|
@@ -3036,6 +2951,40 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3036
2951
|
return [];
|
|
3037
2952
|
}
|
|
3038
2953
|
|
|
2954
|
+
// rdf:first (alias of list:first)
|
|
2955
|
+
// Schema: $s+ rdf:first $o-
|
|
2956
|
+
if (g.p instanceof Iri && g.p.value === RDF_NS + "first") {
|
|
2957
|
+
if (!(g.s instanceof ListTerm)) return [];
|
|
2958
|
+
if (!g.s.elems.length) return [];
|
|
2959
|
+
const first = g.s.elems[0];
|
|
2960
|
+
const s2 = unifyTerm(g.o, first, subst);
|
|
2961
|
+
return s2 !== null ? [s2] : [];
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2964
|
+
// rdf:rest (alias of list:rest)
|
|
2965
|
+
// Schema: $s+ rdf:rest $o-
|
|
2966
|
+
if (g.p instanceof Iri && g.p.value === RDF_NS + "rest") {
|
|
2967
|
+
// Closed list: (a b c) -> (b c)
|
|
2968
|
+
if (g.s instanceof ListTerm) {
|
|
2969
|
+
if (!g.s.elems.length) return [];
|
|
2970
|
+
const rest = new ListTerm(g.s.elems.slice(1));
|
|
2971
|
+
const s2 = unifyTerm(g.o, rest, subst);
|
|
2972
|
+
return s2 !== null ? [s2] : [];
|
|
2973
|
+
}
|
|
2974
|
+
// Open list: (a b ... ?T) -> (b ... ?T)
|
|
2975
|
+
if (g.s instanceof OpenListTerm) {
|
|
2976
|
+
if (!g.s.prefix.length) return [];
|
|
2977
|
+
if (g.s.prefix.length === 1) {
|
|
2978
|
+
const s2 = unifyTerm(g.o, new Var(g.s.tailVar), subst);
|
|
2979
|
+
return s2 !== null ? [s2] : [];
|
|
2980
|
+
}
|
|
2981
|
+
const rest = new OpenListTerm(g.s.prefix.slice(1), g.s.tailVar);
|
|
2982
|
+
const s2 = unifyTerm(g.o, rest, subst);
|
|
2983
|
+
return s2 !== null ? [s2] : [];
|
|
2984
|
+
}
|
|
2985
|
+
return [];
|
|
2986
|
+
}
|
|
2987
|
+
|
|
3039
2988
|
// list:iterate
|
|
3040
2989
|
// true iff $s is a list and $o is a list (index value),
|
|
3041
2990
|
// where index is a valid 0-based index into $s and value is the element at that index.
|
|
@@ -3840,28 +3789,28 @@ function listHasTriple(list, tr) {
|
|
|
3840
3789
|
//
|
|
3841
3790
|
// This is semantics-preserving for the ongoing proof state.
|
|
3842
3791
|
|
|
3843
|
-
function
|
|
3792
|
+
function gcCollectVarsInTerm(t, out) {
|
|
3844
3793
|
if (t instanceof Var) { out.add(t.name); return; }
|
|
3845
|
-
if (t instanceof ListTerm) { for (const e of t.elems)
|
|
3794
|
+
if (t instanceof ListTerm) { for (const e of t.elems) gcCollectVarsInTerm(e, out); return; }
|
|
3846
3795
|
if (t instanceof OpenListTerm) {
|
|
3847
|
-
for (const e of t.prefix)
|
|
3796
|
+
for (const e of t.prefix) gcCollectVarsInTerm(e, out);
|
|
3848
3797
|
out.add(t.tailVar);
|
|
3849
3798
|
return;
|
|
3850
3799
|
}
|
|
3851
|
-
if (t instanceof FormulaTerm) { for (const tr of t.triples)
|
|
3800
|
+
if (t instanceof FormulaTerm) { for (const tr of t.triples) gcCollectVarsInTriple(tr, out); return; }
|
|
3852
3801
|
}
|
|
3853
3802
|
|
|
3854
|
-
function
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3803
|
+
function gcCollectVarsInTriple(tr, out) {
|
|
3804
|
+
gcCollectVarsInTerm(tr.s, out);
|
|
3805
|
+
gcCollectVarsInTerm(tr.p, out);
|
|
3806
|
+
gcCollectVarsInTerm(tr.o, out);
|
|
3858
3807
|
}
|
|
3859
3808
|
|
|
3860
|
-
function
|
|
3861
|
-
for (const g of goals)
|
|
3809
|
+
function gcCollectVarsInGoals(goals, out) {
|
|
3810
|
+
for (const g of goals) gcCollectVarsInTriple(g, out);
|
|
3862
3811
|
}
|
|
3863
3812
|
|
|
3864
|
-
function
|
|
3813
|
+
function substSizeOver(subst, limit) {
|
|
3865
3814
|
let c = 0;
|
|
3866
3815
|
for (const _k in subst) {
|
|
3867
3816
|
if (++c > limit) return true;
|
|
@@ -3869,9 +3818,9 @@ function _substSizeOver(subst, limit) {
|
|
|
3869
3818
|
return false;
|
|
3870
3819
|
}
|
|
3871
3820
|
|
|
3872
|
-
function
|
|
3821
|
+
function gcCompactForGoals(subst, goals, answerVars) {
|
|
3873
3822
|
const keep = new Set(answerVars);
|
|
3874
|
-
|
|
3823
|
+
gcCollectVarsInGoals(goals, keep);
|
|
3875
3824
|
|
|
3876
3825
|
const expanded = new Set();
|
|
3877
3826
|
const queue = Array.from(keep);
|
|
@@ -3885,7 +3834,7 @@ function _gcCompactForGoals(subst, goals, answerVars) {
|
|
|
3885
3834
|
if (bound === undefined) continue;
|
|
3886
3835
|
|
|
3887
3836
|
const before = keep.size;
|
|
3888
|
-
|
|
3837
|
+
gcCollectVarsInTerm(bound, keep);
|
|
3889
3838
|
if (keep.size !== before) {
|
|
3890
3839
|
for (const nv of keep) {
|
|
3891
3840
|
if (!expanded.has(nv)) queue.push(nv);
|
|
@@ -3900,12 +3849,12 @@ function _gcCompactForGoals(subst, goals, answerVars) {
|
|
|
3900
3849
|
return out;
|
|
3901
3850
|
}
|
|
3902
3851
|
|
|
3903
|
-
function
|
|
3852
|
+
function maybeCompactSubst(subst, goals, answerVars, depth) {
|
|
3904
3853
|
// Keep the fast path fast.
|
|
3905
3854
|
// Only compact when the substitution is clearly getting large, or
|
|
3906
3855
|
// we are in a deep chain (where the quadratic behavior shows up).
|
|
3907
|
-
if (depth < 128 && !
|
|
3908
|
-
return
|
|
3856
|
+
if (depth < 128 && !substSizeOver(subst, 256)) return subst;
|
|
3857
|
+
return gcCompactForGoals(subst, goals, answerVars);
|
|
3909
3858
|
}
|
|
3910
3859
|
|
|
3911
3860
|
|
|
@@ -3921,9 +3870,9 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
|
|
|
3921
3870
|
|
|
3922
3871
|
// Variables from the original goal list (needed by the caller to instantiate conclusions)
|
|
3923
3872
|
const answerVars = new Set();
|
|
3924
|
-
|
|
3873
|
+
gcCollectVarsInGoals(initialGoals, answerVars);
|
|
3925
3874
|
if (!initialGoals.length) {
|
|
3926
|
-
results.push(
|
|
3875
|
+
results.push(gcCompactForGoals(initialSubst, [], answerVars));
|
|
3927
3876
|
return results;
|
|
3928
3877
|
}
|
|
3929
3878
|
|
|
@@ -3935,7 +3884,7 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
|
|
|
3935
3884
|
const state = stack.pop();
|
|
3936
3885
|
|
|
3937
3886
|
if (!state.goals.length) {
|
|
3938
|
-
results.push(
|
|
3887
|
+
results.push(gcCompactForGoals(state.subst, [], answerVars));
|
|
3939
3888
|
continue;
|
|
3940
3889
|
}
|
|
3941
3890
|
|
|
@@ -3951,9 +3900,9 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
|
|
|
3951
3900
|
if (composed === null) continue;
|
|
3952
3901
|
|
|
3953
3902
|
if (!restGoals.length) {
|
|
3954
|
-
results.push(
|
|
3903
|
+
results.push(gcCompactForGoals(composed, [], answerVars));
|
|
3955
3904
|
} else {
|
|
3956
|
-
const nextSubst =
|
|
3905
|
+
const nextSubst = maybeCompactSubst(composed, restGoals, answerVars, state.depth + 1);
|
|
3957
3906
|
stack.push({
|
|
3958
3907
|
goals: restGoals,
|
|
3959
3908
|
subst: nextSubst,
|
|
@@ -3980,9 +3929,9 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
|
|
|
3980
3929
|
if (composed === null) continue;
|
|
3981
3930
|
|
|
3982
3931
|
if (!restGoals.length) {
|
|
3983
|
-
results.push(
|
|
3932
|
+
results.push(gcCompactForGoals(composed, [], answerVars));
|
|
3984
3933
|
} else {
|
|
3985
|
-
const nextSubst =
|
|
3934
|
+
const nextSubst = maybeCompactSubst(composed, restGoals, answerVars, state.depth + 1);
|
|
3986
3935
|
stack.push({
|
|
3987
3936
|
goals: restGoals,
|
|
3988
3937
|
subst: nextSubst,
|
|
@@ -4001,9 +3950,9 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
|
|
|
4001
3950
|
if (composed === null) continue;
|
|
4002
3951
|
|
|
4003
3952
|
if (!restGoals.length) {
|
|
4004
|
-
results.push(
|
|
3953
|
+
results.push(gcCompactForGoals(composed, [], answerVars));
|
|
4005
3954
|
} else {
|
|
4006
|
-
const nextSubst =
|
|
3955
|
+
const nextSubst = maybeCompactSubst(composed, restGoals, answerVars, state.depth + 1);
|
|
4007
3956
|
stack.push({
|
|
4008
3957
|
goals: restGoals,
|
|
4009
3958
|
subst: nextSubst,
|
|
@@ -4036,7 +3985,7 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
|
|
|
4036
3985
|
if (composed === null) continue;
|
|
4037
3986
|
|
|
4038
3987
|
const newGoals = body.concat(restGoals);
|
|
4039
|
-
const nextSubst =
|
|
3988
|
+
const nextSubst = maybeCompactSubst(composed, newGoals, answerVars, state.depth + 1);
|
|
4040
3989
|
stack.push({
|
|
4041
3990
|
goals: newGoals,
|
|
4042
3991
|
subst: nextSubst,
|
|
@@ -4363,6 +4312,135 @@ function printExplanation(df, prefixes) {
|
|
|
4363
4312
|
// Misc helpers
|
|
4364
4313
|
// ============================================================================
|
|
4365
4314
|
|
|
4315
|
+
// Turn RDF Collections described with rdf:first/rdf:rest (+ rdf:nil) into ListTerm terms.
|
|
4316
|
+
// This mutates triples/rules in-place so list:* builtins work on RDF-serialized lists too.
|
|
4317
|
+
function materializeRdfLists(triples, forwardRules, backwardRules) {
|
|
4318
|
+
const RDF_FIRST = RDF_NS + "first";
|
|
4319
|
+
const RDF_REST = RDF_NS + "rest";
|
|
4320
|
+
const RDF_NIL = RDF_NS + "nil";
|
|
4321
|
+
|
|
4322
|
+
function nodeKey(t) {
|
|
4323
|
+
if (t instanceof Blank) return "B:" + t.label;
|
|
4324
|
+
if (t instanceof Iri) return "I:" + t.value;
|
|
4325
|
+
return null;
|
|
4326
|
+
}
|
|
4327
|
+
|
|
4328
|
+
// Collect first/rest arcs from *input triples*
|
|
4329
|
+
const firstMap = new Map(); // key(subject) -> Term (object)
|
|
4330
|
+
const restMap = new Map(); // key(subject) -> Term (object)
|
|
4331
|
+
for (const tr of triples) {
|
|
4332
|
+
if (!(tr.p instanceof Iri)) continue;
|
|
4333
|
+
const k = nodeKey(tr.s);
|
|
4334
|
+
if (!k) continue;
|
|
4335
|
+
if (tr.p.value === RDF_FIRST) firstMap.set(k, tr.o);
|
|
4336
|
+
else if (tr.p.value === RDF_REST) restMap.set(k, tr.o);
|
|
4337
|
+
}
|
|
4338
|
+
if (!firstMap.size && !restMap.size) return;
|
|
4339
|
+
|
|
4340
|
+
const cache = new Map(); // key(node) -> ListTerm
|
|
4341
|
+
const visiting = new Set(); // cycle guard
|
|
4342
|
+
|
|
4343
|
+
function buildListForKey(k) {
|
|
4344
|
+
if (cache.has(k)) return cache.get(k);
|
|
4345
|
+
if (visiting.has(k)) return null; // cycle => not a well-formed list
|
|
4346
|
+
visiting.add(k);
|
|
4347
|
+
|
|
4348
|
+
// rdf:nil => ()
|
|
4349
|
+
if (k === "I:" + RDF_NIL) {
|
|
4350
|
+
const empty = new ListTerm([]);
|
|
4351
|
+
cache.set(k, empty);
|
|
4352
|
+
visiting.delete(k);
|
|
4353
|
+
return empty;
|
|
4354
|
+
}
|
|
4355
|
+
|
|
4356
|
+
const head = firstMap.get(k);
|
|
4357
|
+
const tail = restMap.get(k);
|
|
4358
|
+
if (head === undefined || tail === undefined) {
|
|
4359
|
+
visiting.delete(k);
|
|
4360
|
+
return null; // not a full cons cell
|
|
4361
|
+
}
|
|
4362
|
+
|
|
4363
|
+
const headTerm = rewriteTerm(head);
|
|
4364
|
+
|
|
4365
|
+
let tailListElems = null;
|
|
4366
|
+
if (tail instanceof Iri && tail.value === RDF_NIL) {
|
|
4367
|
+
tailListElems = [];
|
|
4368
|
+
} else {
|
|
4369
|
+
const tk = nodeKey(tail);
|
|
4370
|
+
if (!tk) {
|
|
4371
|
+
visiting.delete(k);
|
|
4372
|
+
return null;
|
|
4373
|
+
}
|
|
4374
|
+
const tailList = buildListForKey(tk);
|
|
4375
|
+
if (!tailList) {
|
|
4376
|
+
visiting.delete(k);
|
|
4377
|
+
return null;
|
|
4378
|
+
}
|
|
4379
|
+
tailListElems = tailList.elems;
|
|
4380
|
+
}
|
|
4381
|
+
|
|
4382
|
+
const out = new ListTerm([headTerm, ...tailListElems]);
|
|
4383
|
+
cache.set(k, out);
|
|
4384
|
+
visiting.delete(k);
|
|
4385
|
+
return out;
|
|
4386
|
+
}
|
|
4387
|
+
|
|
4388
|
+
function rewriteTerm(t) {
|
|
4389
|
+
// Replace list nodes (Blank/Iri) by their constructed ListTerm when possible
|
|
4390
|
+
const k = nodeKey(t);
|
|
4391
|
+
if (k) {
|
|
4392
|
+
const built = buildListForKey(k);
|
|
4393
|
+
if (built) return built;
|
|
4394
|
+
// Also rewrite rdf:nil even if not otherwise referenced
|
|
4395
|
+
if (t instanceof Iri && t.value === RDF_NIL) return new ListTerm([]);
|
|
4396
|
+
return t;
|
|
4397
|
+
}
|
|
4398
|
+
if (t instanceof ListTerm) {
|
|
4399
|
+
let changed = false;
|
|
4400
|
+
const elems = t.elems.map(e => {
|
|
4401
|
+
const r = rewriteTerm(e);
|
|
4402
|
+
if (r !== e) changed = true;
|
|
4403
|
+
return r;
|
|
4404
|
+
});
|
|
4405
|
+
return changed ? new ListTerm(elems) : t;
|
|
4406
|
+
}
|
|
4407
|
+
if (t instanceof OpenListTerm) {
|
|
4408
|
+
let changed = false;
|
|
4409
|
+
const prefix = t.prefix.map(e => {
|
|
4410
|
+
const r = rewriteTerm(e);
|
|
4411
|
+
if (r !== e) changed = true;
|
|
4412
|
+
return r;
|
|
4413
|
+
});
|
|
4414
|
+
return changed ? new OpenListTerm(prefix, t.tailVar) : t;
|
|
4415
|
+
}
|
|
4416
|
+
if (t instanceof FormulaTerm) {
|
|
4417
|
+
for (const tr of t.triples) rewriteTriple(tr);
|
|
4418
|
+
return t;
|
|
4419
|
+
}
|
|
4420
|
+
return t;
|
|
4421
|
+
}
|
|
4422
|
+
|
|
4423
|
+
function rewriteTriple(tr) {
|
|
4424
|
+
tr.s = rewriteTerm(tr.s);
|
|
4425
|
+
tr.p = rewriteTerm(tr.p);
|
|
4426
|
+
tr.o = rewriteTerm(tr.o);
|
|
4427
|
+
}
|
|
4428
|
+
|
|
4429
|
+
// Pre-build all reachable list heads
|
|
4430
|
+
for (const k of firstMap.keys()) buildListForKey(k);
|
|
4431
|
+
|
|
4432
|
+
// Rewrite input triples + rules in-place
|
|
4433
|
+
for (const tr of triples) rewriteTriple(tr);
|
|
4434
|
+
for (const r of forwardRules) {
|
|
4435
|
+
for (const tr of r.premise) rewriteTriple(tr);
|
|
4436
|
+
for (const tr of r.conclusion) rewriteTriple(tr);
|
|
4437
|
+
}
|
|
4438
|
+
for (const r of backwardRules) {
|
|
4439
|
+
for (const tr of r.premise) rewriteTriple(tr);
|
|
4440
|
+
for (const tr of r.conclusion) rewriteTriple(tr);
|
|
4441
|
+
}
|
|
4442
|
+
}
|
|
4443
|
+
|
|
4366
4444
|
function localIsoDateTimeString(d) {
|
|
4367
4445
|
function pad(n, width = 2) {
|
|
4368
4446
|
return String(n).padStart(width, "0");
|
|
@@ -4450,6 +4528,9 @@ function main() {
|
|
|
4450
4528
|
const [prefixes, triples, frules, brules] = parser.parseDocument();
|
|
4451
4529
|
// console.log(JSON.stringify([prefixes, triples, frules, brules], null, 2));
|
|
4452
4530
|
|
|
4531
|
+
// Build internal ListTerm values from rdf:first/rdf:rest (+ rdf:nil) input triples
|
|
4532
|
+
materializeRdfLists(triples, frules, brules);
|
|
4533
|
+
|
|
4453
4534
|
const facts = triples.filter(tr => isGroundTriple(tr));
|
|
4454
4535
|
const derived = forwardChain(facts, frules, brules);
|
|
4455
4536
|
|