eyeling 1.22.6 → 1.22.8

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.
Files changed (38) hide show
  1. package/HANDBOOK.md +245 -0
  2. package/dist/browser/eyeling.browser.js +188 -33
  3. package/examples/act-alarm-bit-interoperability.n3 +180 -0
  4. package/examples/act-barley-seed-lineage.n3 +565 -0
  5. package/examples/act-docking-abort.n3 +285 -0
  6. package/examples/act-gravity-mediator-witness.n3 +235 -0
  7. package/examples/act-isolation-breach.n3 +354 -0
  8. package/examples/act-photosynthetic-exciton-transfer.n3 +245 -0
  9. package/examples/act-sensor-memory-reset.n3 +190 -0
  10. package/examples/act-tunnel-junction-wake-switch.n3 +225 -0
  11. package/examples/act-yeast-self-reproduction.n3 +248 -0
  12. package/examples/complex-matrix-stability.n3 +288 -0
  13. package/examples/deck/act-barley-seed-lineage.md +593 -0
  14. package/examples/fundamental-theorem-arithmetic.n3 +244 -0
  15. package/examples/harborsmr.n3 +233 -0
  16. package/examples/meta-rule-audit.n3 +135 -0
  17. package/examples/output/act-alarm-bit-interoperability.txt +20 -0
  18. package/examples/output/act-barley-seed-lineage.txt +25 -0
  19. package/examples/output/act-docking-abort.txt +22 -0
  20. package/examples/output/act-gravity-mediator-witness.txt +24 -0
  21. package/examples/output/act-isolation-breach.txt +27 -0
  22. package/examples/output/act-photosynthetic-exciton-transfer.txt +20 -0
  23. package/examples/output/act-sensor-memory-reset.txt +20 -0
  24. package/examples/output/act-tunnel-junction-wake-switch.txt +21 -0
  25. package/examples/output/act-yeast-self-reproduction.txt +23 -0
  26. package/examples/output/complex-matrix-stability.txt +14 -0
  27. package/examples/output/fundamental-theorem-arithmetic.txt +15 -0
  28. package/examples/output/get-uuid.n3 +2 -2
  29. package/examples/output/harborsmr.txt +20 -0
  30. package/examples/output/meta-rule-audit.n3 +44 -0
  31. package/examples/output/theory-diff.n3 +22 -0
  32. package/examples/theory-diff.n3 +125 -0
  33. package/eyeling.js +188 -33
  34. package/lib/builtins.js +18 -1
  35. package/lib/cli.js +31 -5
  36. package/lib/engine.js +139 -27
  37. package/package.json +1 -1
  38. package/test/api.test.js +100 -0
package/eyeling.js CHANGED
@@ -2354,6 +2354,23 @@ function __logNaturalPriorityFromTerm(t) {
2354
2354
  // ===========================================================================
2355
2355
  // Backward proof & builtins mutual recursion — declarations first
2356
2356
 
2357
+ function __varCameFromBoundSubstitution(goalTerm, subst) {
2358
+ if (!(goalTerm instanceof Var)) return false;
2359
+ if (!subst || !Object.prototype.hasOwnProperty.call(subst, goalTerm.name)) return false;
2360
+
2361
+ let cur = subst[goalTerm.name];
2362
+ const seen = new Set([goalTerm.name]);
2363
+
2364
+ while (cur instanceof Var) {
2365
+ if (seen.has(cur.name)) return true;
2366
+ seen.add(cur.name);
2367
+ if (!Object.prototype.hasOwnProperty.call(subst, cur.name)) return true;
2368
+ cur = subst[cur.name];
2369
+ }
2370
+
2371
+ return false;
2372
+ }
2373
+
2357
2374
  function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
2358
2375
  const g = applySubstTriple(goal, subst);
2359
2376
  const pv = iriValue(g.p);
@@ -3820,7 +3837,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
3820
3837
  // Schema: $s+ log:rawType $o-
3821
3838
  // Returns one of log:Formula, log:Literal, rdf:List, or log:Other.
3822
3839
  if (pv === LOG_NS + 'rawType') {
3823
- if (g.s instanceof Var) return [];
3840
+ if (g.s instanceof Var && !__varCameFromBoundSubstitution(goal.s, subst)) return [];
3824
3841
 
3825
3842
  let ty;
3826
3843
  if (g.s instanceof GraphTerm) ty = internIri(LOG_NS + 'Formula');
@@ -4870,7 +4887,11 @@ module.exports = {
4870
4887
 
4871
4888
  'use strict';
4872
4889
 
4890
+ const path = require('node:path');
4891
+ const { pathToFileURL } = require('node:url');
4892
+
4873
4893
  const engine = require('./engine');
4894
+ const deref = require('./deref');
4874
4895
  const { PrefixEnv } = require('./prelude');
4875
4896
 
4876
4897
  function offsetToLineCol(text, offset) {
@@ -4913,6 +4934,29 @@ function readTextFromStdinSync() {
4913
4934
  return fs.readFileSync(0, { encoding: 'utf8' });
4914
4935
  }
4915
4936
 
4937
+ function __isNetworkOrFileIri(s) {
4938
+ return typeof s === 'string' && /^(https?:|file:\/\/)/i.test(s);
4939
+ }
4940
+
4941
+ function __sourceLabelToBaseIri(sourceLabel) {
4942
+ if (!sourceLabel || sourceLabel === '<stdin>') return '';
4943
+ if (__isNetworkOrFileIri(sourceLabel)) return deref.stripFragment(sourceLabel);
4944
+ return pathToFileURL(path.resolve(sourceLabel)).toString();
4945
+ }
4946
+
4947
+ function __readInputSourceSync(sourceLabel) {
4948
+ if (sourceLabel === '<stdin>') return readTextFromStdinSync();
4949
+
4950
+ if (__isNetworkOrFileIri(sourceLabel)) {
4951
+ const txt = deref.derefTextSync(sourceLabel);
4952
+ if (typeof txt !== 'string') throw new Error(`Failed to dereference ${sourceLabel}`);
4953
+ return txt;
4954
+ }
4955
+
4956
+ const fs = require('node:fs');
4957
+ return fs.readFileSync(sourceLabel, { encoding: 'utf8' });
4958
+ }
4959
+
4916
4960
  function main() {
4917
4961
  // Drop "node" and script name; keep only user-provided args
4918
4962
  // Expand combined short options: -pt == -p -t
@@ -5026,13 +5070,11 @@ function main() {
5026
5070
  }
5027
5071
 
5028
5072
  const sourceLabel = useImplicitStdin || positional[0] === '-' ? '<stdin>' : positional[0];
5073
+ const baseIri = __sourceLabelToBaseIri(sourceLabel);
5074
+
5029
5075
  let text;
5030
5076
  try {
5031
- if (sourceLabel === '<stdin>') text = readTextFromStdinSync();
5032
- else {
5033
- const fs = require('node:fs');
5034
- text = fs.readFileSync(sourceLabel, { encoding: 'utf8' });
5035
- }
5077
+ text = __readInputSourceSync(sourceLabel);
5036
5078
  } catch (e) {
5037
5079
  if (sourceLabel === '<stdin>') console.error(`Error reading stdin: ${e.message}`);
5038
5080
  else console.error(`Error reading file ${JSON.stringify(sourceLabel)}: ${e.message}`);
@@ -5044,6 +5086,7 @@ function main() {
5044
5086
  try {
5045
5087
  toks = engine.lex(text);
5046
5088
  const parser = new engine.Parser(toks);
5089
+ if (baseIri) parser.prefixes.setBase(baseIri);
5047
5090
  [prefixes, triples, frules, brules, qrules] = parser.parseDocument();
5048
5091
  // Make the parsed prefixes available to log:trace output (CLI path)
5049
5092
  engine.setTracePrefixes(prefixes);
@@ -5951,21 +5994,80 @@ function __isStrictGroundTriple(tr) {
5951
5994
  // -----------------------------------------------------------------------------
5952
5995
  // Used to maintain O(1) membership sets for dynamically promoted rules, and to
5953
5996
  // memoize per-firing head-blank skolemization.
5997
+ //
5998
+ // Important: variables and blank nodes *inside quoted formulas* are local to the
5999
+ // formula. Canonicalize those labels by first occurrence so alpha-equivalent
6000
+ // formulas (for example the repeated results of log:semantics after
6001
+ // standardize-apart) get the same identity key. Keep top-level blank labels
6002
+ // untouched so distinct existential witnesses in the global fact set do not
6003
+ // collapse together.
6004
+ function __keyFromTermForRuleIdentity(term) {
6005
+ const ctx = { quotedVar: new Map(), quotedBlank: new Map() };
6006
+
6007
+ function canonQuotedVar(name) {
6008
+ let out = ctx.quotedVar.get(name);
6009
+ if (!out) {
6010
+ out = `v${ctx.quotedVar.size}`;
6011
+ ctx.quotedVar.set(name, out);
6012
+ }
6013
+ return out;
6014
+ }
6015
+
6016
+ function canonQuotedBlank(label) {
6017
+ let out = ctx.quotedBlank.get(label);
6018
+ if (!out) {
6019
+ out = `b${ctx.quotedBlank.size}`;
6020
+ ctx.quotedBlank.set(label, out);
6021
+ }
6022
+ return out;
6023
+ }
6024
+
6025
+ function enc(u, inQuotedFormula) {
6026
+ if (u instanceof Iri) return ['I', u.value];
6027
+ if (u instanceof Literal) return ['L', u.value];
6028
+ if (u instanceof Blank) return inQuotedFormula ? ['BQ', canonQuotedBlank(u.label)] : ['B', u.label];
6029
+ if (u instanceof Var) return inQuotedFormula ? ['VQ', canonQuotedVar(u.name)] : ['V', u.name];
6030
+ if (u instanceof ListTerm) return ['List', u.elems.map((e) => enc(e, inQuotedFormula))];
6031
+ if (u instanceof OpenListTerm) {
6032
+ return [
6033
+ 'OpenList',
6034
+ u.prefix.map((e) => enc(e, inQuotedFormula)),
6035
+ inQuotedFormula ? ['TailVQ', canonQuotedVar(u.tailVar)] : ['TailV', u.tailVar],
6036
+ ];
6037
+ }
6038
+ if (u instanceof GraphTerm)
6039
+ return ['Graph', u.triples.map((tr) => [enc(tr.s, true), enc(tr.p, true), enc(tr.o, true)])];
6040
+ return ['Other', String(u)];
6041
+ }
6042
+
6043
+ return JSON.stringify(enc(term, false));
6044
+ }
6045
+
5954
6046
  function __ruleKey(isForward, isFuse, premise, conclusion, dynamicConclusionTerm /* optional */) {
5955
6047
  let out = (isForward ? 'F' : 'B') + (isFuse ? '!' : '') + '|P|';
5956
6048
  for (let i = 0; i < premise.length; i++) {
5957
6049
  const tr = premise[i];
5958
6050
  if (i) out += '\n';
5959
- out += skolemKeyFromTerm(tr.s) + '\t' + skolemKeyFromTerm(tr.p) + '\t' + skolemKeyFromTerm(tr.o);
6051
+ out +=
6052
+ __keyFromTermForRuleIdentity(tr.s) +
6053
+ '\t' +
6054
+ __keyFromTermForRuleIdentity(tr.p) +
6055
+ '\t' +
6056
+ __keyFromTermForRuleIdentity(tr.o);
5960
6057
  }
5961
6058
  out += '|C|';
5962
6059
  for (let i = 0; i < conclusion.length; i++) {
5963
6060
  const tr = conclusion[i];
5964
6061
  if (i) out += '\n';
5965
- out += skolemKeyFromTerm(tr.s) + '\t' + skolemKeyFromTerm(tr.p) + '\t' + skolemKeyFromTerm(tr.o);
6062
+ out +=
6063
+ __keyFromTermForRuleIdentity(tr.s) +
6064
+ '\t' +
6065
+ __keyFromTermForRuleIdentity(tr.p) +
6066
+ '\t' +
6067
+ __keyFromTermForRuleIdentity(tr.o);
5966
6068
  }
5967
6069
  if (dynamicConclusionTerm) {
5968
- out += '|T|' + skolemKeyFromTerm(dynamicConclusionTerm);
6070
+ out += '|T|' + __keyFromTermForRuleIdentity(dynamicConclusionTerm);
5969
6071
  }
5970
6072
  return out;
5971
6073
  }
@@ -5976,7 +6078,12 @@ function __firingKey(ruleIndex, instantiatedPremises) {
5976
6078
  for (let i = 0; i < instantiatedPremises.length; i++) {
5977
6079
  const tr = instantiatedPremises[i];
5978
6080
  if (i) out += '\n';
5979
- out += skolemKeyFromTerm(tr.s) + '\t' + skolemKeyFromTerm(tr.p) + '\t' + skolemKeyFromTerm(tr.o);
6081
+ out +=
6082
+ __keyFromTermForRuleIdentity(tr.s) +
6083
+ '\t' +
6084
+ __keyFromTermForRuleIdentity(tr.p) +
6085
+ '\t' +
6086
+ __keyFromTermForRuleIdentity(tr.o);
5980
6087
  }
5981
6088
  return out;
5982
6089
  }
@@ -6534,14 +6641,44 @@ function collectProtectedNamesInTerm(t, protectedVars, protectedBlanks) {
6534
6641
  }
6535
6642
  }
6536
6643
 
6537
- function collectProtectedNamesFromSubst(subst) {
6644
+ function collectProtectedNamesFromTermViaSubst(term, subst, protectedVars, protectedBlanks, seenVarNames) {
6645
+ if (term instanceof Var) {
6646
+ if (!subst || !Object.prototype.hasOwnProperty.call(subst, term.name)) return;
6647
+ if (seenVarNames.has(term.name)) return;
6648
+ seenVarNames.add(term.name);
6649
+ collectProtectedNamesInTerm(subst[term.name], protectedVars, protectedBlanks);
6650
+ return;
6651
+ }
6652
+
6653
+ if (term instanceof ListTerm) {
6654
+ for (const e of term.elems)
6655
+ collectProtectedNamesFromTermViaSubst(e, subst, protectedVars, protectedBlanks, seenVarNames);
6656
+ return;
6657
+ }
6658
+
6659
+ if (term instanceof OpenListTerm) {
6660
+ for (const e of term.prefix)
6661
+ collectProtectedNamesFromTermViaSubst(e, subst, protectedVars, protectedBlanks, seenVarNames);
6662
+ if (subst && Object.prototype.hasOwnProperty.call(subst, term.tailVar) && !seenVarNames.has(term.tailVar)) {
6663
+ seenVarNames.add(term.tailVar);
6664
+ collectProtectedNamesInTerm(subst[term.tailVar], protectedVars, protectedBlanks);
6665
+ }
6666
+ return;
6667
+ }
6668
+
6669
+ if (term instanceof GraphTerm) {
6670
+ for (const tr of term.triples) {
6671
+ collectProtectedNamesFromTermViaSubst(tr.s, subst, protectedVars, protectedBlanks, seenVarNames);
6672
+ collectProtectedNamesFromTermViaSubst(tr.p, subst, protectedVars, protectedBlanks, seenVarNames);
6673
+ collectProtectedNamesFromTermViaSubst(tr.o, subst, protectedVars, protectedBlanks, seenVarNames);
6674
+ }
6675
+ }
6676
+ }
6677
+
6678
+ function collectProtectedNamesForTerm(term, subst) {
6538
6679
  const protectedVars = new Set();
6539
6680
  const protectedBlanks = new Set();
6540
- if (!subst) return { protectedVars, protectedBlanks };
6541
- for (const k in subst) {
6542
- if (!Object.prototype.hasOwnProperty.call(subst, k)) continue;
6543
- collectProtectedNamesInTerm(subst[k], protectedVars, protectedBlanks);
6544
- }
6681
+ collectProtectedNamesFromTermViaSubst(term, subst, protectedVars, protectedBlanks, new Set());
6545
6682
  return { protectedVars, protectedBlanks };
6546
6683
  }
6547
6684
 
@@ -7503,6 +7640,9 @@ function unifyTermListAppend(a, b, subst) {
7503
7640
  }
7504
7641
 
7505
7642
  function unifyTermWithOptions(a, b, subst, opts) {
7643
+ const aRaw = a;
7644
+ const bRaw = b;
7645
+
7506
7646
  a = applySubstTerm(a, subst);
7507
7647
  b = applySubstTerm(b, subst);
7508
7648
 
@@ -7610,13 +7750,14 @@ function unifyTermWithOptions(a, b, subst, opts) {
7610
7750
 
7611
7751
  // Graphs
7612
7752
  if (a instanceof GraphTerm && b instanceof GraphTerm) {
7613
- const protectedNames = collectProtectedNamesFromSubst(subst);
7753
+ const protectedNamesA = collectProtectedNamesForTerm(aRaw, subst);
7754
+ const protectedNamesB = collectProtectedNamesForTerm(bRaw, subst);
7614
7755
  if (
7615
7756
  alphaEqGraphTriples(a.triples, b.triples, {
7616
- protectedVarsA: protectedNames.protectedVars,
7617
- protectedVarsB: protectedNames.protectedVars,
7618
- protectedBlanksA: protectedNames.protectedBlanks,
7619
- protectedBlanksB: protectedNames.protectedBlanks,
7757
+ protectedVarsA: protectedNamesA.protectedVars,
7758
+ protectedVarsB: protectedNamesB.protectedVars,
7759
+ protectedBlanksA: protectedNamesA.protectedBlanks,
7760
+ protectedBlanksB: protectedNamesB.protectedBlanks,
7620
7761
  })
7621
7762
  ) {
7622
7763
  return subst;
@@ -7920,6 +8061,9 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxR
7920
8061
  }
7921
8062
 
7922
8063
  function unifyTermTrail(a, b) {
8064
+ const aRaw = a;
8065
+ const bRaw = b;
8066
+
7923
8067
  a = applySubstTerm(a, substMut);
7924
8068
  b = applySubstTerm(b, substMut);
7925
8069
 
@@ -7997,24 +8141,25 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxR
7997
8141
 
7998
8142
  // Graphs
7999
8143
  if (a instanceof GraphTerm && b instanceof GraphTerm) {
8000
- const protectedNames = collectProtectedNamesFromSubst(substMut);
8144
+ const protectedNamesA = collectProtectedNamesForTerm(aRaw, substMut);
8145
+ const protectedNamesB = collectProtectedNamesForTerm(bRaw, substMut);
8001
8146
  if (
8002
8147
  alphaEqGraphTriples(a.triples, b.triples, {
8003
- protectedVarsA: protectedNames.protectedVars,
8004
- protectedVarsB: protectedNames.protectedVars,
8005
- protectedBlanksA: protectedNames.protectedBlanks,
8006
- protectedBlanksB: protectedNames.protectedBlanks,
8148
+ protectedVarsA: protectedNamesA.protectedVars,
8149
+ protectedVarsB: protectedNamesB.protectedVars,
8150
+ protectedBlanksA: protectedNamesA.protectedBlanks,
8151
+ protectedBlanksB: protectedNamesB.protectedBlanks,
8007
8152
  })
8008
8153
  ) {
8009
8154
  return true;
8010
8155
  }
8011
- // Fallback: reuse allocation-heavy graph unifier rarely hit in typical workloads.
8012
- const delta = unifyGraphTriples(a.triples, b.triples, {});
8013
- if (delta === null) return false;
8156
+ const merged = unifyGraphTriples(a.triples, b.triples, substMut);
8157
+ if (merged === null) return false;
8014
8158
  const mark = trail.length;
8015
- for (const k in delta) {
8016
- if (!Object.prototype.hasOwnProperty.call(delta, k)) continue;
8017
- if (!bindVarTrail(k, delta[k])) {
8159
+ for (const k in merged) {
8160
+ if (!Object.prototype.hasOwnProperty.call(merged, k)) continue;
8161
+ if (Object.prototype.hasOwnProperty.call(substMut, k)) continue;
8162
+ if (!bindVarTrail(k, merged[k])) {
8018
8163
  undoTo(mark);
8019
8164
  return false;
8020
8165
  }
@@ -8216,7 +8361,17 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxR
8216
8361
  if (remaining <= 0) continue;
8217
8362
  const builtinMax = Number.isFinite(remaining) && !restGoals.length ? remaining : undefined;
8218
8363
 
8219
- let deltas = evalBuiltin(goal0, {}, facts, backRules, frame.curDepth, varGen, builtinMax);
8364
+ const builtinGoalForEval = goalPredicateIri === LOG_NS + 'rawType' ? rawGoal : goal0;
8365
+ const builtinSubstForEval = goalPredicateIri === LOG_NS + 'rawType' ? substMut : {};
8366
+ let deltas = evalBuiltin(
8367
+ builtinGoalForEval,
8368
+ builtinSubstForEval,
8369
+ facts,
8370
+ backRules,
8371
+ frame.curDepth,
8372
+ varGen,
8373
+ builtinMax,
8374
+ );
8220
8375
 
8221
8376
  const dc = typeof frame.deferCount === 'number' ? frame.deferCount : 0;
8222
8377
  const builtinDeltasAreVacuous = deltas.length > 0 && deltas.every((d) => Object.keys(d).length === 0);
package/lib/builtins.js CHANGED
@@ -1875,6 +1875,23 @@ function __logNaturalPriorityFromTerm(t) {
1875
1875
  // ===========================================================================
1876
1876
  // Backward proof & builtins mutual recursion — declarations first
1877
1877
 
1878
+ function __varCameFromBoundSubstitution(goalTerm, subst) {
1879
+ if (!(goalTerm instanceof Var)) return false;
1880
+ if (!subst || !Object.prototype.hasOwnProperty.call(subst, goalTerm.name)) return false;
1881
+
1882
+ let cur = subst[goalTerm.name];
1883
+ const seen = new Set([goalTerm.name]);
1884
+
1885
+ while (cur instanceof Var) {
1886
+ if (seen.has(cur.name)) return true;
1887
+ seen.add(cur.name);
1888
+ if (!Object.prototype.hasOwnProperty.call(subst, cur.name)) return true;
1889
+ cur = subst[cur.name];
1890
+ }
1891
+
1892
+ return false;
1893
+ }
1894
+
1878
1895
  function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
1879
1896
  const g = applySubstTriple(goal, subst);
1880
1897
  const pv = iriValue(g.p);
@@ -3341,7 +3358,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
3341
3358
  // Schema: $s+ log:rawType $o-
3342
3359
  // Returns one of log:Formula, log:Literal, rdf:List, or log:Other.
3343
3360
  if (pv === LOG_NS + 'rawType') {
3344
- if (g.s instanceof Var) return [];
3361
+ if (g.s instanceof Var && !__varCameFromBoundSubstitution(goal.s, subst)) return [];
3345
3362
 
3346
3363
  let ty;
3347
3364
  if (g.s instanceof GraphTerm) ty = internIri(LOG_NS + 'Formula');
package/lib/cli.js CHANGED
@@ -7,7 +7,11 @@
7
7
 
8
8
  'use strict';
9
9
 
10
+ const path = require('node:path');
11
+ const { pathToFileURL } = require('node:url');
12
+
10
13
  const engine = require('./engine');
14
+ const deref = require('./deref');
11
15
  const { PrefixEnv } = require('./prelude');
12
16
 
13
17
  function offsetToLineCol(text, offset) {
@@ -50,6 +54,29 @@ function readTextFromStdinSync() {
50
54
  return fs.readFileSync(0, { encoding: 'utf8' });
51
55
  }
52
56
 
57
+ function __isNetworkOrFileIri(s) {
58
+ return typeof s === 'string' && /^(https?:|file:\/\/)/i.test(s);
59
+ }
60
+
61
+ function __sourceLabelToBaseIri(sourceLabel) {
62
+ if (!sourceLabel || sourceLabel === '<stdin>') return '';
63
+ if (__isNetworkOrFileIri(sourceLabel)) return deref.stripFragment(sourceLabel);
64
+ return pathToFileURL(path.resolve(sourceLabel)).toString();
65
+ }
66
+
67
+ function __readInputSourceSync(sourceLabel) {
68
+ if (sourceLabel === '<stdin>') return readTextFromStdinSync();
69
+
70
+ if (__isNetworkOrFileIri(sourceLabel)) {
71
+ const txt = deref.derefTextSync(sourceLabel);
72
+ if (typeof txt !== 'string') throw new Error(`Failed to dereference ${sourceLabel}`);
73
+ return txt;
74
+ }
75
+
76
+ const fs = require('node:fs');
77
+ return fs.readFileSync(sourceLabel, { encoding: 'utf8' });
78
+ }
79
+
53
80
  function main() {
54
81
  // Drop "node" and script name; keep only user-provided args
55
82
  // Expand combined short options: -pt == -p -t
@@ -163,13 +190,11 @@ function main() {
163
190
  }
164
191
 
165
192
  const sourceLabel = useImplicitStdin || positional[0] === '-' ? '<stdin>' : positional[0];
193
+ const baseIri = __sourceLabelToBaseIri(sourceLabel);
194
+
166
195
  let text;
167
196
  try {
168
- if (sourceLabel === '<stdin>') text = readTextFromStdinSync();
169
- else {
170
- const fs = require('node:fs');
171
- text = fs.readFileSync(sourceLabel, { encoding: 'utf8' });
172
- }
197
+ text = __readInputSourceSync(sourceLabel);
173
198
  } catch (e) {
174
199
  if (sourceLabel === '<stdin>') console.error(`Error reading stdin: ${e.message}`);
175
200
  else console.error(`Error reading file ${JSON.stringify(sourceLabel)}: ${e.message}`);
@@ -181,6 +206,7 @@ function main() {
181
206
  try {
182
207
  toks = engine.lex(text);
183
208
  const parser = new engine.Parser(toks);
209
+ if (baseIri) parser.prefixes.setBase(baseIri);
184
210
  [prefixes, triples, frules, brules, qrules] = parser.parseDocument();
185
211
  // Make the parsed prefixes available to log:trace output (CLI path)
186
212
  engine.setTracePrefixes(prefixes);
package/lib/engine.js CHANGED
@@ -199,21 +199,80 @@ function __isStrictGroundTriple(tr) {
199
199
  // -----------------------------------------------------------------------------
200
200
  // Used to maintain O(1) membership sets for dynamically promoted rules, and to
201
201
  // memoize per-firing head-blank skolemization.
202
+ //
203
+ // Important: variables and blank nodes *inside quoted formulas* are local to the
204
+ // formula. Canonicalize those labels by first occurrence so alpha-equivalent
205
+ // formulas (for example the repeated results of log:semantics after
206
+ // standardize-apart) get the same identity key. Keep top-level blank labels
207
+ // untouched so distinct existential witnesses in the global fact set do not
208
+ // collapse together.
209
+ function __keyFromTermForRuleIdentity(term) {
210
+ const ctx = { quotedVar: new Map(), quotedBlank: new Map() };
211
+
212
+ function canonQuotedVar(name) {
213
+ let out = ctx.quotedVar.get(name);
214
+ if (!out) {
215
+ out = `v${ctx.quotedVar.size}`;
216
+ ctx.quotedVar.set(name, out);
217
+ }
218
+ return out;
219
+ }
220
+
221
+ function canonQuotedBlank(label) {
222
+ let out = ctx.quotedBlank.get(label);
223
+ if (!out) {
224
+ out = `b${ctx.quotedBlank.size}`;
225
+ ctx.quotedBlank.set(label, out);
226
+ }
227
+ return out;
228
+ }
229
+
230
+ function enc(u, inQuotedFormula) {
231
+ if (u instanceof Iri) return ['I', u.value];
232
+ if (u instanceof Literal) return ['L', u.value];
233
+ if (u instanceof Blank) return inQuotedFormula ? ['BQ', canonQuotedBlank(u.label)] : ['B', u.label];
234
+ if (u instanceof Var) return inQuotedFormula ? ['VQ', canonQuotedVar(u.name)] : ['V', u.name];
235
+ if (u instanceof ListTerm) return ['List', u.elems.map((e) => enc(e, inQuotedFormula))];
236
+ if (u instanceof OpenListTerm) {
237
+ return [
238
+ 'OpenList',
239
+ u.prefix.map((e) => enc(e, inQuotedFormula)),
240
+ inQuotedFormula ? ['TailVQ', canonQuotedVar(u.tailVar)] : ['TailV', u.tailVar],
241
+ ];
242
+ }
243
+ if (u instanceof GraphTerm)
244
+ return ['Graph', u.triples.map((tr) => [enc(tr.s, true), enc(tr.p, true), enc(tr.o, true)])];
245
+ return ['Other', String(u)];
246
+ }
247
+
248
+ return JSON.stringify(enc(term, false));
249
+ }
250
+
202
251
  function __ruleKey(isForward, isFuse, premise, conclusion, dynamicConclusionTerm /* optional */) {
203
252
  let out = (isForward ? 'F' : 'B') + (isFuse ? '!' : '') + '|P|';
204
253
  for (let i = 0; i < premise.length; i++) {
205
254
  const tr = premise[i];
206
255
  if (i) out += '\n';
207
- out += skolemKeyFromTerm(tr.s) + '\t' + skolemKeyFromTerm(tr.p) + '\t' + skolemKeyFromTerm(tr.o);
256
+ out +=
257
+ __keyFromTermForRuleIdentity(tr.s) +
258
+ '\t' +
259
+ __keyFromTermForRuleIdentity(tr.p) +
260
+ '\t' +
261
+ __keyFromTermForRuleIdentity(tr.o);
208
262
  }
209
263
  out += '|C|';
210
264
  for (let i = 0; i < conclusion.length; i++) {
211
265
  const tr = conclusion[i];
212
266
  if (i) out += '\n';
213
- out += skolemKeyFromTerm(tr.s) + '\t' + skolemKeyFromTerm(tr.p) + '\t' + skolemKeyFromTerm(tr.o);
267
+ out +=
268
+ __keyFromTermForRuleIdentity(tr.s) +
269
+ '\t' +
270
+ __keyFromTermForRuleIdentity(tr.p) +
271
+ '\t' +
272
+ __keyFromTermForRuleIdentity(tr.o);
214
273
  }
215
274
  if (dynamicConclusionTerm) {
216
- out += '|T|' + skolemKeyFromTerm(dynamicConclusionTerm);
275
+ out += '|T|' + __keyFromTermForRuleIdentity(dynamicConclusionTerm);
217
276
  }
218
277
  return out;
219
278
  }
@@ -224,7 +283,12 @@ function __firingKey(ruleIndex, instantiatedPremises) {
224
283
  for (let i = 0; i < instantiatedPremises.length; i++) {
225
284
  const tr = instantiatedPremises[i];
226
285
  if (i) out += '\n';
227
- out += skolemKeyFromTerm(tr.s) + '\t' + skolemKeyFromTerm(tr.p) + '\t' + skolemKeyFromTerm(tr.o);
286
+ out +=
287
+ __keyFromTermForRuleIdentity(tr.s) +
288
+ '\t' +
289
+ __keyFromTermForRuleIdentity(tr.p) +
290
+ '\t' +
291
+ __keyFromTermForRuleIdentity(tr.o);
228
292
  }
229
293
  return out;
230
294
  }
@@ -782,14 +846,44 @@ function collectProtectedNamesInTerm(t, protectedVars, protectedBlanks) {
782
846
  }
783
847
  }
784
848
 
785
- function collectProtectedNamesFromSubst(subst) {
849
+ function collectProtectedNamesFromTermViaSubst(term, subst, protectedVars, protectedBlanks, seenVarNames) {
850
+ if (term instanceof Var) {
851
+ if (!subst || !Object.prototype.hasOwnProperty.call(subst, term.name)) return;
852
+ if (seenVarNames.has(term.name)) return;
853
+ seenVarNames.add(term.name);
854
+ collectProtectedNamesInTerm(subst[term.name], protectedVars, protectedBlanks);
855
+ return;
856
+ }
857
+
858
+ if (term instanceof ListTerm) {
859
+ for (const e of term.elems)
860
+ collectProtectedNamesFromTermViaSubst(e, subst, protectedVars, protectedBlanks, seenVarNames);
861
+ return;
862
+ }
863
+
864
+ if (term instanceof OpenListTerm) {
865
+ for (const e of term.prefix)
866
+ collectProtectedNamesFromTermViaSubst(e, subst, protectedVars, protectedBlanks, seenVarNames);
867
+ if (subst && Object.prototype.hasOwnProperty.call(subst, term.tailVar) && !seenVarNames.has(term.tailVar)) {
868
+ seenVarNames.add(term.tailVar);
869
+ collectProtectedNamesInTerm(subst[term.tailVar], protectedVars, protectedBlanks);
870
+ }
871
+ return;
872
+ }
873
+
874
+ if (term instanceof GraphTerm) {
875
+ for (const tr of term.triples) {
876
+ collectProtectedNamesFromTermViaSubst(tr.s, subst, protectedVars, protectedBlanks, seenVarNames);
877
+ collectProtectedNamesFromTermViaSubst(tr.p, subst, protectedVars, protectedBlanks, seenVarNames);
878
+ collectProtectedNamesFromTermViaSubst(tr.o, subst, protectedVars, protectedBlanks, seenVarNames);
879
+ }
880
+ }
881
+ }
882
+
883
+ function collectProtectedNamesForTerm(term, subst) {
786
884
  const protectedVars = new Set();
787
885
  const protectedBlanks = new Set();
788
- if (!subst) return { protectedVars, protectedBlanks };
789
- for (const k in subst) {
790
- if (!Object.prototype.hasOwnProperty.call(subst, k)) continue;
791
- collectProtectedNamesInTerm(subst[k], protectedVars, protectedBlanks);
792
- }
886
+ collectProtectedNamesFromTermViaSubst(term, subst, protectedVars, protectedBlanks, new Set());
793
887
  return { protectedVars, protectedBlanks };
794
888
  }
795
889
 
@@ -1751,6 +1845,9 @@ function unifyTermListAppend(a, b, subst) {
1751
1845
  }
1752
1846
 
1753
1847
  function unifyTermWithOptions(a, b, subst, opts) {
1848
+ const aRaw = a;
1849
+ const bRaw = b;
1850
+
1754
1851
  a = applySubstTerm(a, subst);
1755
1852
  b = applySubstTerm(b, subst);
1756
1853
 
@@ -1858,13 +1955,14 @@ function unifyTermWithOptions(a, b, subst, opts) {
1858
1955
 
1859
1956
  // Graphs
1860
1957
  if (a instanceof GraphTerm && b instanceof GraphTerm) {
1861
- const protectedNames = collectProtectedNamesFromSubst(subst);
1958
+ const protectedNamesA = collectProtectedNamesForTerm(aRaw, subst);
1959
+ const protectedNamesB = collectProtectedNamesForTerm(bRaw, subst);
1862
1960
  if (
1863
1961
  alphaEqGraphTriples(a.triples, b.triples, {
1864
- protectedVarsA: protectedNames.protectedVars,
1865
- protectedVarsB: protectedNames.protectedVars,
1866
- protectedBlanksA: protectedNames.protectedBlanks,
1867
- protectedBlanksB: protectedNames.protectedBlanks,
1962
+ protectedVarsA: protectedNamesA.protectedVars,
1963
+ protectedVarsB: protectedNamesB.protectedVars,
1964
+ protectedBlanksA: protectedNamesA.protectedBlanks,
1965
+ protectedBlanksB: protectedNamesB.protectedBlanks,
1868
1966
  })
1869
1967
  ) {
1870
1968
  return subst;
@@ -2168,6 +2266,9 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxR
2168
2266
  }
2169
2267
 
2170
2268
  function unifyTermTrail(a, b) {
2269
+ const aRaw = a;
2270
+ const bRaw = b;
2271
+
2171
2272
  a = applySubstTerm(a, substMut);
2172
2273
  b = applySubstTerm(b, substMut);
2173
2274
 
@@ -2245,24 +2346,25 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxR
2245
2346
 
2246
2347
  // Graphs
2247
2348
  if (a instanceof GraphTerm && b instanceof GraphTerm) {
2248
- const protectedNames = collectProtectedNamesFromSubst(substMut);
2349
+ const protectedNamesA = collectProtectedNamesForTerm(aRaw, substMut);
2350
+ const protectedNamesB = collectProtectedNamesForTerm(bRaw, substMut);
2249
2351
  if (
2250
2352
  alphaEqGraphTriples(a.triples, b.triples, {
2251
- protectedVarsA: protectedNames.protectedVars,
2252
- protectedVarsB: protectedNames.protectedVars,
2253
- protectedBlanksA: protectedNames.protectedBlanks,
2254
- protectedBlanksB: protectedNames.protectedBlanks,
2353
+ protectedVarsA: protectedNamesA.protectedVars,
2354
+ protectedVarsB: protectedNamesB.protectedVars,
2355
+ protectedBlanksA: protectedNamesA.protectedBlanks,
2356
+ protectedBlanksB: protectedNamesB.protectedBlanks,
2255
2357
  })
2256
2358
  ) {
2257
2359
  return true;
2258
2360
  }
2259
- // Fallback: reuse allocation-heavy graph unifier rarely hit in typical workloads.
2260
- const delta = unifyGraphTriples(a.triples, b.triples, {});
2261
- if (delta === null) return false;
2361
+ const merged = unifyGraphTriples(a.triples, b.triples, substMut);
2362
+ if (merged === null) return false;
2262
2363
  const mark = trail.length;
2263
- for (const k in delta) {
2264
- if (!Object.prototype.hasOwnProperty.call(delta, k)) continue;
2265
- if (!bindVarTrail(k, delta[k])) {
2364
+ for (const k in merged) {
2365
+ if (!Object.prototype.hasOwnProperty.call(merged, k)) continue;
2366
+ if (Object.prototype.hasOwnProperty.call(substMut, k)) continue;
2367
+ if (!bindVarTrail(k, merged[k])) {
2266
2368
  undoTo(mark);
2267
2369
  return false;
2268
2370
  }
@@ -2464,7 +2566,17 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxR
2464
2566
  if (remaining <= 0) continue;
2465
2567
  const builtinMax = Number.isFinite(remaining) && !restGoals.length ? remaining : undefined;
2466
2568
 
2467
- let deltas = evalBuiltin(goal0, {}, facts, backRules, frame.curDepth, varGen, builtinMax);
2569
+ const builtinGoalForEval = goalPredicateIri === LOG_NS + 'rawType' ? rawGoal : goal0;
2570
+ const builtinSubstForEval = goalPredicateIri === LOG_NS + 'rawType' ? substMut : {};
2571
+ let deltas = evalBuiltin(
2572
+ builtinGoalForEval,
2573
+ builtinSubstForEval,
2574
+ facts,
2575
+ backRules,
2576
+ frame.curDepth,
2577
+ varGen,
2578
+ builtinMax,
2579
+ );
2468
2580
 
2469
2581
  const dc = typeof frame.deferCount === 'number' ? frame.deferCount : 0;
2470
2582
  const builtinDeltasAreVacuous = deltas.length > 0 && deltas.every((d) => Object.keys(d).length === 0);