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.
@@ -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 _gcCollectVarsInTerm(t, out) {
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) _gcCollectVarsInTerm(e, out); return; }
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) _gcCollectVarsInTerm(e, out);
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) _gcCollectVarsInTriple(tr, out); return; }
3800
+ if (t instanceof FormulaTerm) { for (const tr of t.triples) gcCollectVarsInTriple(tr, out); return; }
3852
3801
  }
3853
3802
 
3854
- function _gcCollectVarsInTriple(tr, out) {
3855
- _gcCollectVarsInTerm(tr.s, out);
3856
- _gcCollectVarsInTerm(tr.p, out);
3857
- _gcCollectVarsInTerm(tr.o, out);
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 _gcCollectVarsInGoals(goals, out) {
3861
- for (const g of goals) _gcCollectVarsInTriple(g, out);
3809
+ function gcCollectVarsInGoals(goals, out) {
3810
+ for (const g of goals) gcCollectVarsInTriple(g, out);
3862
3811
  }
3863
3812
 
3864
- function _substSizeOver(subst, limit) {
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 _gcCompactForGoals(subst, goals, answerVars) {
3821
+ function gcCompactForGoals(subst, goals, answerVars) {
3873
3822
  const keep = new Set(answerVars);
3874
- _gcCollectVarsInGoals(goals, keep);
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
- _gcCollectVarsInTerm(bound, keep);
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 _maybeCompactSubst(subst, goals, answerVars, depth) {
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 && !_substSizeOver(subst, 256)) return subst;
3908
- return _gcCompactForGoals(subst, goals, answerVars);
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
- _gcCollectVarsInGoals(initialGoals, answerVars);
3873
+ gcCollectVarsInGoals(initialGoals, answerVars);
3925
3874
  if (!initialGoals.length) {
3926
- results.push(_gcCompactForGoals(initialSubst, [], answerVars));
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(_gcCompactForGoals(state.subst, [], answerVars));
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(_gcCompactForGoals(composed, [], answerVars));
3903
+ results.push(gcCompactForGoals(composed, [], answerVars));
3955
3904
  } else {
3956
- const nextSubst = _maybeCompactSubst(composed, restGoals, answerVars, state.depth + 1);
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(_gcCompactForGoals(composed, [], answerVars));
3932
+ results.push(gcCompactForGoals(composed, [], answerVars));
3984
3933
  } else {
3985
- const nextSubst = _maybeCompactSubst(composed, restGoals, answerVars, state.depth + 1);
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(_gcCompactForGoals(composed, [], answerVars));
3953
+ results.push(gcCompactForGoals(composed, [], answerVars));
4005
3954
  } else {
4006
- const nextSubst = _maybeCompactSubst(composed, restGoals, answerVars, state.depth + 1);
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 = _maybeCompactSubst(composed, newGoals, answerVars, state.depth + 1);
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.5.33",
3
+ "version": "1.5.35",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [