eyeling 1.5.38 → 1.5.39

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/eyeling.js CHANGED
@@ -16,7 +16,7 @@
16
16
  * 5) Print only newly derived forward facts with explanations.
17
17
  */
18
18
 
19
- const { version } = require('./package.json');
19
+ const { version } = require("./package.json");
20
20
  const nodeCrypto = require("crypto");
21
21
 
22
22
  // ============================================================================
@@ -52,9 +52,9 @@ function termToJsonText(t) {
52
52
  function makeRdfJsonLiteral(jsonText) {
53
53
  // Prefer a readable long literal when safe; fall back to short if needed.
54
54
  if (!jsonText.includes('"""')) {
55
- return new Literal('"""' + jsonText + '"""^^<' + RDF_JSON_DT + '>');
55
+ return new Literal('"""' + jsonText + '"""^^<' + RDF_JSON_DT + ">");
56
56
  }
57
- return new Literal(JSON.stringify(jsonText) + '^^<' + RDF_JSON_DT + '>');
57
+ return new Literal(JSON.stringify(jsonText) + "^^<" + RDF_JSON_DT + ">");
58
58
  }
59
59
 
60
60
  // For a single reasoning run, this maps a canonical representation
@@ -90,7 +90,9 @@ function normalizeDateTimeLex(s) {
90
90
  function utcIsoDateTimeStringFromEpochSeconds(sec) {
91
91
  const ms = sec * 1000;
92
92
  const d = new Date(ms);
93
- function pad(n, w = 2) { return String(n).padStart(w, "0"); }
93
+ function pad(n, w = 2) {
94
+ return String(n).padStart(w, "0");
95
+ }
94
96
  const year = d.getUTCFullYear();
95
97
  const month = d.getUTCMonth() + 1;
96
98
  const day = d.getUTCDate();
@@ -100,8 +102,18 @@ function utcIsoDateTimeStringFromEpochSeconds(sec) {
100
102
  const ms2 = d.getUTCMilliseconds();
101
103
  const msPart = ms2 ? "." + String(ms2).padStart(3, "0") : "";
102
104
  return (
103
- pad(year, 4) + "-" + pad(month) + "-" + pad(day) + "T" +
104
- pad(hour) + ":" + pad(min) + ":" + pad(s2) + msPart +
105
+ pad(year, 4) +
106
+ "-" +
107
+ pad(month) +
108
+ "-" +
109
+ pad(day) +
110
+ "T" +
111
+ pad(hour) +
112
+ ":" +
113
+ pad(min) +
114
+ ":" +
115
+ pad(s2) +
116
+ msPart +
105
117
  "+00:00"
106
118
  );
107
119
  }
@@ -139,15 +151,19 @@ function deterministicSkolemIdFromKey(key) {
139
151
  }
140
152
 
141
153
  const hex = [h1, h2, h3, h4]
142
- .map(h => h.toString(16).padStart(8, "0"))
154
+ .map((h) => h.toString(16).padStart(8, "0"))
143
155
  .join(""); // 32 hex chars
144
156
 
145
157
  // Format like a UUID: 8-4-4-4-12
146
158
  return (
147
- hex.slice(0, 8) + "-" +
148
- hex.slice(8, 12) + "-" +
149
- hex.slice(12, 16) + "-" +
150
- hex.slice(16, 20) + "-" +
159
+ hex.slice(0, 8) +
160
+ "-" +
161
+ hex.slice(8, 12) +
162
+ "-" +
163
+ hex.slice(12, 16) +
164
+ "-" +
165
+ hex.slice(16, 20) +
166
+ "-" +
151
167
  hex.slice(20)
152
168
  );
153
169
  }
@@ -220,10 +236,10 @@ class Triple {
220
236
 
221
237
  class Rule {
222
238
  constructor(premise, conclusion, isForward, isFuse, headBlankLabels) {
223
- this.premise = premise; // Triple[]
239
+ this.premise = premise; // Triple[]
224
240
  this.conclusion = conclusion; // Triple[]
225
- this.isForward = isForward; // boolean
226
- this.isFuse = isFuse; // boolean
241
+ this.isForward = isForward; // boolean
242
+ this.isFuse = isFuse; // boolean
227
243
  // Set<string> of blank-node labels that occur explicitly in the rule head
228
244
  this.headBlankLabels = headBlankLabels || new Set();
229
245
  }
@@ -231,10 +247,10 @@ class Rule {
231
247
 
232
248
  class DerivedFact {
233
249
  constructor(fact, rule, premises, subst) {
234
- this.fact = fact; // Triple
235
- this.rule = rule; // Rule
236
- this.premises = premises; // Triple[]
237
- this.subst = subst; // { varName: Term }
250
+ this.fact = fact; // Triple
251
+ this.rule = rule; // Rule
252
+ this.premises = premises; // Triple[]
253
+ this.subst = subst; // { varName: Term }
238
254
  }
239
255
  }
240
256
 
@@ -393,7 +409,8 @@ function lex(inputText) {
393
409
  }
394
410
  sChars.push(cc);
395
411
  }
396
- if (!closed) throw new Error('Unterminated long string literal """..."""');
412
+ if (!closed)
413
+ throw new Error('Unterminated long string literal """..."""');
397
414
  const s = '"""' + sChars.join("") + '"""';
398
415
  tokens.push(new Token("Literal", s));
399
416
  continue;
@@ -468,7 +485,9 @@ function lex(inputText) {
468
485
  i++;
469
486
  }
470
487
  if (!segChars.length) {
471
- throw new Error("Invalid language tag (expected [A-Za-z0-9]+ after '-')");
488
+ throw new Error(
489
+ "Invalid language tag (expected [A-Za-z0-9]+ after '-')",
490
+ );
472
491
  }
473
492
  tagChars.push(...segChars);
474
493
  }
@@ -491,7 +510,10 @@ function lex(inputText) {
491
510
  }
492
511
 
493
512
  // 6) Numeric literal (integer or float)
494
- if (/[0-9]/.test(c) || (c === "-" && peek(1) !== null && /[0-9]/.test(peek(1)))) {
513
+ if (
514
+ /[0-9]/.test(c) ||
515
+ (c === "-" && peek(1) !== null && /[0-9]/.test(peek(1)))
516
+ ) {
495
517
  const numChars = [c];
496
518
  i++;
497
519
  while (i < n) {
@@ -529,7 +551,7 @@ function lex(inputText) {
529
551
  const word = wordChars.join("");
530
552
  if (word === "true" || word === "false") {
531
553
  tokens.push(new Token("Literal", word));
532
- } else if ([...word].every(ch => /[0-9.\-]/.test(ch))) {
554
+ } else if ([...word].every((ch) => /[0-9.\-]/.test(ch))) {
533
555
  tokens.push(new Token("Literal", word));
534
556
  } else {
535
557
  tokens.push(new Token("Ident", word));
@@ -758,12 +780,18 @@ class Parser {
758
780
  if (this.peek().typ === "Dot") {
759
781
  // Allow a bare blank-node property list statement, e.g. `[ a :Statement ].`
760
782
  const lastTok = this.toks[this.pos - 1];
761
- if (this.pendingTriples.length > 0 && lastTok && lastTok.typ === "RBracket") {
783
+ if (
784
+ this.pendingTriples.length > 0 &&
785
+ lastTok &&
786
+ lastTok.typ === "RBracket"
787
+ ) {
762
788
  more = this.pendingTriples;
763
789
  this.pendingTriples = [];
764
790
  this.next(); // consume '.'
765
791
  } else {
766
- throw new Error(`Unexpected '.' after term; missing predicate/object list`);
792
+ throw new Error(
793
+ `Unexpected '.' after term; missing predicate/object list`,
794
+ );
767
795
  }
768
796
  } else {
769
797
  more = this.parsePredicateObjectList(first);
@@ -772,9 +800,17 @@ class Parser {
772
800
 
773
801
  // normalize explicit log:implies / log:impliedBy at top-level
774
802
  for (const tr of more) {
775
- if (isLogImplies(tr.p) && tr.s instanceof FormulaTerm && tr.o instanceof FormulaTerm) {
803
+ if (
804
+ isLogImplies(tr.p) &&
805
+ tr.s instanceof FormulaTerm &&
806
+ tr.o instanceof FormulaTerm
807
+ ) {
776
808
  forwardRules.push(this.makeRule(tr.s, tr.o, true));
777
- } else if (isLogImpliedBy(tr.p) && tr.s instanceof FormulaTerm && tr.o instanceof FormulaTerm) {
809
+ } else if (
810
+ isLogImpliedBy(tr.p) &&
811
+ tr.s instanceof FormulaTerm &&
812
+ tr.o instanceof FormulaTerm
813
+ ) {
778
814
  backwardRules.push(this.makeRule(tr.s, tr.o, false));
779
815
  } else {
780
816
  triples.push(tr);
@@ -834,16 +870,14 @@ class Parser {
834
870
  let t = this.parsePathItem();
835
871
 
836
872
  while (this.peek().typ === "OpPathFwd" || this.peek().typ === "OpPathRev") {
837
- const dir = this.next().typ; // OpPathFwd | OpPathRev
873
+ const dir = this.next().typ; // OpPathFwd | OpPathRev
838
874
  const pred = this.parsePathItem();
839
875
 
840
876
  this.blankCounter += 1;
841
877
  const bn = new Blank(`_:b${this.blankCounter}`);
842
878
 
843
879
  this.pendingTriples.push(
844
- dir === "OpPathFwd"
845
- ? new Triple(t, pred, bn)
846
- : new Triple(bn, pred, t)
880
+ dir === "OpPathFwd" ? new Triple(t, pred, bn) : new Triple(bn, pred, t),
847
881
  );
848
882
 
849
883
  t = bn;
@@ -885,7 +919,9 @@ class Parser {
885
919
  if (this.peek().typ === "LangTag") {
886
920
  // Only quoted string literals can carry a language tag.
887
921
  if (!(s.startsWith('"') && s.endsWith('"'))) {
888
- throw new Error("Language tag is only allowed on quoted string literals");
922
+ throw new Error(
923
+ "Language tag is only allowed on quoted string literals",
924
+ );
889
925
  }
890
926
  const langTok = this.next();
891
927
  const lang = langTok.value || "";
@@ -893,7 +929,9 @@ class Parser {
893
929
 
894
930
  // N3/Turtle: language tags and datatypes are mutually exclusive.
895
931
  if (this.peek().typ === "HatHat") {
896
- throw new Error("A literal cannot have both a language tag (@...) and a datatype (^^...)");
932
+ throw new Error(
933
+ "A literal cannot have both a language tag (@...) and a datatype (^^...)",
934
+ );
897
935
  }
898
936
  }
899
937
 
@@ -908,7 +946,9 @@ class Parser {
908
946
  if (qn.includes(":")) dtIri = this.prefixes.expandQName(qn);
909
947
  else dtIri = qn;
910
948
  } else {
911
- throw new Error(`Expected datatype after ^^, got ${dtTok.toString()}`);
949
+ throw new Error(
950
+ `Expected datatype after ^^, got ${dtTok.toString()}`,
951
+ );
912
952
  }
913
953
  s = `${s}^^<${dtIri}>`;
914
954
  }
@@ -968,8 +1008,9 @@ class Parser {
968
1008
  }
969
1009
 
970
1010
  for (const o of objs) {
971
- this.pendingTriples.push(invert ? new Triple(o, pred, subj)
972
- : new Triple(subj, pred, o));
1011
+ this.pendingTriples.push(
1012
+ invert ? new Triple(o, pred, subj) : new Triple(subj, pred, o),
1013
+ );
973
1014
  }
974
1015
 
975
1016
  if (this.peek().typ === "Semicolon") {
@@ -985,8 +1026,8 @@ class Parser {
985
1026
  } else {
986
1027
  throw new Error(
987
1028
  `Expected ']' at end of blank node property list, got ${JSON.stringify(
988
- this.peek()
989
- )}`
1029
+ this.peek(),
1030
+ )}`,
990
1031
  );
991
1032
  }
992
1033
 
@@ -1023,7 +1064,11 @@ class Parser {
1023
1064
  // Allow a bare blank-node property list statement inside a formula, e.g. `{ [ a :X ]. }`
1024
1065
  if (this.peek().typ === "Dot" || this.peek().typ === "RBrace") {
1025
1066
  const lastTok = this.toks[this.pos - 1];
1026
- if (this.pendingTriples.length > 0 && lastTok && lastTok.typ === "RBracket") {
1067
+ if (
1068
+ this.pendingTriples.length > 0 &&
1069
+ lastTok &&
1070
+ lastTok.typ === "RBracket"
1071
+ ) {
1027
1072
  triples.push(...this.pendingTriples);
1028
1073
  this.pendingTriples = [];
1029
1074
  if (this.peek().typ === "Dot") this.next();
@@ -1143,7 +1188,9 @@ class Parser {
1143
1188
  const [premise0, conclusion] = liftBlankRuleVars(rawPremise, rawConclusion);
1144
1189
 
1145
1190
  // Reorder constraints for *forward* rules.
1146
- const premise = isForward ? reorderPremiseForConstraints(premise0) : premise0;
1191
+ const premise = isForward
1192
+ ? reorderPremiseForConstraints(premise0)
1193
+ : premise0;
1147
1194
 
1148
1195
  return new Rule(premise, conclusion, isForward, isFuse, headBlankLabels);
1149
1196
  }
@@ -1164,21 +1211,22 @@ function liftBlankRuleVars(premise, conclusion) {
1164
1211
  return new Var(mapping[label]);
1165
1212
  }
1166
1213
  if (t instanceof ListTerm) {
1167
- return new ListTerm(t.elems.map(e => convertTerm(e, mapping, counter)));
1214
+ return new ListTerm(t.elems.map((e) => convertTerm(e, mapping, counter)));
1168
1215
  }
1169
1216
  if (t instanceof OpenListTerm) {
1170
1217
  return new OpenListTerm(
1171
- t.prefix.map(e => convertTerm(e, mapping, counter)),
1172
- t.tailVar
1218
+ t.prefix.map((e) => convertTerm(e, mapping, counter)),
1219
+ t.tailVar,
1173
1220
  );
1174
1221
  }
1175
1222
  if (t instanceof FormulaTerm) {
1176
- const triples = t.triples.map(tr =>
1177
- new Triple(
1178
- convertTerm(tr.s, mapping, counter),
1179
- convertTerm(tr.p, mapping, counter),
1180
- convertTerm(tr.o, mapping, counter)
1181
- )
1223
+ const triples = t.triples.map(
1224
+ (tr) =>
1225
+ new Triple(
1226
+ convertTerm(tr.s, mapping, counter),
1227
+ convertTerm(tr.p, mapping, counter),
1228
+ convertTerm(tr.o, mapping, counter),
1229
+ ),
1182
1230
  );
1183
1231
  return new FormulaTerm(triples);
1184
1232
  }
@@ -1189,13 +1237,13 @@ function liftBlankRuleVars(premise, conclusion) {
1189
1237
  return new Triple(
1190
1238
  convertTerm(tr.s, mapping, counter),
1191
1239
  convertTerm(tr.p, mapping, counter),
1192
- convertTerm(tr.o, mapping, counter)
1240
+ convertTerm(tr.o, mapping, counter),
1193
1241
  );
1194
1242
  }
1195
1243
 
1196
1244
  const mapping = {};
1197
1245
  const counter = [0];
1198
- const newPremise = premise.map(tr => convertTriple(tr, mapping, counter));
1246
+ const newPremise = premise.map((tr) => convertTriple(tr, mapping, counter));
1199
1247
  return [newPremise, conclusion];
1200
1248
  }
1201
1249
 
@@ -1216,22 +1264,26 @@ function skolemizeTermForHeadBlanks(t, headBlankLabels, mapping, skCounter) {
1216
1264
 
1217
1265
  if (t instanceof ListTerm) {
1218
1266
  return new ListTerm(
1219
- t.elems.map(e => skolemizeTermForHeadBlanks(e, headBlankLabels, mapping, skCounter))
1267
+ t.elems.map((e) =>
1268
+ skolemizeTermForHeadBlanks(e, headBlankLabels, mapping, skCounter),
1269
+ ),
1220
1270
  );
1221
1271
  }
1222
1272
 
1223
1273
  if (t instanceof OpenListTerm) {
1224
1274
  return new OpenListTerm(
1225
- t.prefix.map(e => skolemizeTermForHeadBlanks(e, headBlankLabels, mapping, skCounter)),
1226
- t.tailVar
1275
+ t.prefix.map((e) =>
1276
+ skolemizeTermForHeadBlanks(e, headBlankLabels, mapping, skCounter),
1277
+ ),
1278
+ t.tailVar,
1227
1279
  );
1228
1280
  }
1229
1281
 
1230
1282
  if (t instanceof FormulaTerm) {
1231
1283
  return new FormulaTerm(
1232
- t.triples.map(tr =>
1233
- skolemizeTripleForHeadBlanks(tr, headBlankLabels, mapping, skCounter)
1234
- )
1284
+ t.triples.map((tr) =>
1285
+ skolemizeTripleForHeadBlanks(tr, headBlankLabels, mapping, skCounter),
1286
+ ),
1235
1287
  );
1236
1288
  }
1237
1289
 
@@ -1242,7 +1294,7 @@ function skolemizeTripleForHeadBlanks(tr, headBlankLabels, mapping, skCounter) {
1242
1294
  return new Triple(
1243
1295
  skolemizeTermForHeadBlanks(tr.s, headBlankLabels, mapping, skCounter),
1244
1296
  skolemizeTermForHeadBlanks(tr.p, headBlankLabels, mapping, skCounter),
1245
- skolemizeTermForHeadBlanks(tr.o, headBlankLabels, mapping, skCounter)
1297
+ skolemizeTermForHeadBlanks(tr.o, headBlankLabels, mapping, skCounter),
1246
1298
  );
1247
1299
  }
1248
1300
 
@@ -1280,9 +1332,7 @@ function termsEqual(a, b) {
1280
1332
  }
1281
1333
 
1282
1334
  function triplesEqual(a, b) {
1283
- return (
1284
- termsEqual(a.s, b.s) && termsEqual(a.p, b.p) && termsEqual(a.o, b.o)
1285
- );
1335
+ return termsEqual(a.s, b.s) && termsEqual(a.p, b.p) && termsEqual(a.o, b.o);
1286
1336
  }
1287
1337
 
1288
1338
  function triplesListEqual(xs, ys) {
@@ -1322,7 +1372,8 @@ function alphaEqTermInFormula(a, b, vmap, bmap) {
1322
1372
  if (a instanceof ListTerm && b instanceof ListTerm) {
1323
1373
  if (a.elems.length !== b.elems.length) return false;
1324
1374
  for (let i = 0; i < a.elems.length; i++) {
1325
- if (!alphaEqTermInFormula(a.elems[i], b.elems[i], vmap, bmap)) return false;
1375
+ if (!alphaEqTermInFormula(a.elems[i], b.elems[i], vmap, bmap))
1376
+ return false;
1326
1377
  }
1327
1378
  return true;
1328
1379
  }
@@ -1330,7 +1381,8 @@ function alphaEqTermInFormula(a, b, vmap, bmap) {
1330
1381
  if (a instanceof OpenListTerm && b instanceof OpenListTerm) {
1331
1382
  if (a.prefix.length !== b.prefix.length) return false;
1332
1383
  for (let i = 0; i < a.prefix.length; i++) {
1333
- if (!alphaEqTermInFormula(a.prefix[i], b.prefix[i], vmap, bmap)) return false;
1384
+ if (!alphaEqTermInFormula(a.prefix[i], b.prefix[i], vmap, bmap))
1385
+ return false;
1334
1386
  }
1335
1387
  // tailVar is a var-name string, so treat it as renamable too
1336
1388
  return alphaEqVarName(a.tailVar, b.tailVar, vmap);
@@ -1367,7 +1419,8 @@ function alphaEqFormulaTriples(xs, ys) {
1367
1419
  if (used[j]) continue;
1368
1420
  const y = ys[j];
1369
1421
  // Cheap pruning when both predicates are IRIs.
1370
- if (x.p instanceof Iri && y.p instanceof Iri && x.p.value !== y.p.value) continue;
1422
+ if (x.p instanceof Iri && y.p instanceof Iri && x.p.value !== y.p.value)
1423
+ continue;
1371
1424
 
1372
1425
  const v2 = { ...vmap };
1373
1426
  const b2 = { ...bmap };
@@ -1429,7 +1482,7 @@ function alphaEqTriple(a, b) {
1429
1482
  }
1430
1483
 
1431
1484
  function hasAlphaEquiv(triples, tr) {
1432
- return triples.some(t => alphaEqTriple(t, tr));
1485
+ return triples.some((t) => alphaEqTriple(t, tr));
1433
1486
  }
1434
1487
 
1435
1488
  // ============================================================================
@@ -1462,9 +1515,21 @@ function tripleFastKey(tr) {
1462
1515
  function ensureFactIndexes(facts) {
1463
1516
  if (facts.__byPred && facts.__byPO && facts.__keySet) return;
1464
1517
 
1465
- Object.defineProperty(facts, "__byPred", { value: new Map(), enumerable: false, writable: true });
1466
- Object.defineProperty(facts, "__byPO", { value: new Map(), enumerable: false, writable: true });
1467
- Object.defineProperty(facts, "__keySet", { value: new Set(), enumerable: false, writable: true });
1518
+ Object.defineProperty(facts, "__byPred", {
1519
+ value: new Map(),
1520
+ enumerable: false,
1521
+ writable: true,
1522
+ });
1523
+ Object.defineProperty(facts, "__byPO", {
1524
+ value: new Map(),
1525
+ enumerable: false,
1526
+ writable: true,
1527
+ });
1528
+ Object.defineProperty(facts, "__keySet", {
1529
+ value: new Set(),
1530
+ enumerable: false,
1531
+ writable: true,
1532
+ });
1468
1533
 
1469
1534
  for (const f of facts) indexFact(facts, f);
1470
1535
  }
@@ -1474,15 +1539,24 @@ function indexFact(facts, tr) {
1474
1539
  const pk = tr.p.value;
1475
1540
 
1476
1541
  let pb = facts.__byPred.get(pk);
1477
- if (!pb) { pb = []; facts.__byPred.set(pk, pb); }
1542
+ if (!pb) {
1543
+ pb = [];
1544
+ facts.__byPred.set(pk, pb);
1545
+ }
1478
1546
  pb.push(tr);
1479
1547
 
1480
1548
  const ok = termFastKey(tr.o);
1481
1549
  if (ok !== null) {
1482
1550
  let po = facts.__byPO.get(pk);
1483
- if (!po) { po = new Map(); facts.__byPO.set(pk, po); }
1551
+ if (!po) {
1552
+ po = new Map();
1553
+ facts.__byPO.set(pk, po);
1554
+ }
1484
1555
  let pob = po.get(ok);
1485
- if (!pob) { pob = []; po.set(ok, pob); }
1556
+ if (!pob) {
1557
+ pob = [];
1558
+ po.set(ok, pob);
1559
+ }
1486
1560
  pob.push(tr);
1487
1561
  }
1488
1562
  }
@@ -1526,12 +1600,12 @@ function hasFactIndexed(facts, tr) {
1526
1600
  const po = facts.__byPO.get(pk);
1527
1601
  if (po) {
1528
1602
  const pob = po.get(ok) || [];
1529
- return pob.some(t => alphaEqTriple(t, tr));
1603
+ return pob.some((t) => alphaEqTriple(t, tr));
1530
1604
  }
1531
1605
  }
1532
1606
 
1533
1607
  const pb = facts.__byPred.get(pk) || [];
1534
- return pb.some(t => alphaEqTriple(t, tr));
1608
+ return pb.some((t) => alphaEqTriple(t, tr));
1535
1609
  }
1536
1610
 
1537
1611
  return hasAlphaEquiv(facts, tr);
@@ -1546,8 +1620,16 @@ function pushFactIndexed(facts, tr) {
1546
1620
  function ensureBackRuleIndexes(backRules) {
1547
1621
  if (backRules.__byHeadPred && backRules.__wildHeadPred) return;
1548
1622
 
1549
- Object.defineProperty(backRules, "__byHeadPred", { value: new Map(), enumerable: false, writable: true });
1550
- Object.defineProperty(backRules, "__wildHeadPred", { value: [], enumerable: false, writable: true });
1623
+ Object.defineProperty(backRules, "__byHeadPred", {
1624
+ value: new Map(),
1625
+ enumerable: false,
1626
+ writable: true,
1627
+ });
1628
+ Object.defineProperty(backRules, "__wildHeadPred", {
1629
+ value: [],
1630
+ enumerable: false,
1631
+ writable: true,
1632
+ });
1551
1633
 
1552
1634
  for (const r of backRules) indexBackRule(backRules, r);
1553
1635
  }
@@ -1558,7 +1640,10 @@ function indexBackRule(backRules, r) {
1558
1640
  if (head && head.p instanceof Iri) {
1559
1641
  const k = head.p.value;
1560
1642
  let bucket = backRules.__byHeadPred.get(k);
1561
- if (!bucket) { bucket = []; backRules.__byHeadPred.set(k, bucket); }
1643
+ if (!bucket) {
1644
+ bucket = [];
1645
+ backRules.__byHeadPred.set(k, bucket);
1646
+ }
1562
1647
  bucket.push(r);
1563
1648
  } else {
1564
1649
  backRules.__wildHeadPred.push(r);
@@ -1574,7 +1659,7 @@ function isRdfTypePred(p) {
1574
1659
  }
1575
1660
 
1576
1661
  function isOwlSameAsPred(t) {
1577
- return t instanceof Iri && t.value === (OWL_NS + "sameAs");
1662
+ return t instanceof Iri && t.value === OWL_NS + "sameAs";
1578
1663
  }
1579
1664
 
1580
1665
  function isLogImplies(p) {
@@ -1595,11 +1680,11 @@ function isConstraintBuiltin(tr) {
1595
1680
 
1596
1681
  // math: numeric comparisons (no new bindings, just tests)
1597
1682
  if (
1598
- v === MATH_NS + "equalTo" ||
1599
- v === MATH_NS + "greaterThan" ||
1600
- v === MATH_NS + "lessThan" ||
1601
- v === MATH_NS + "notEqualTo" ||
1602
- v === MATH_NS + "notGreaterThan" ||
1683
+ v === MATH_NS + "equalTo" ||
1684
+ v === MATH_NS + "greaterThan" ||
1685
+ v === MATH_NS + "lessThan" ||
1686
+ v === MATH_NS + "notEqualTo" ||
1687
+ v === MATH_NS + "notGreaterThan" ||
1603
1688
  v === MATH_NS + "notLessThan"
1604
1689
  ) {
1605
1690
  return true;
@@ -1612,8 +1697,8 @@ function isConstraintBuiltin(tr) {
1612
1697
 
1613
1698
  // log: tests that are purely constraints (no new bindings)
1614
1699
  if (
1615
- v === LOG_NS + "forAllIn" ||
1616
- v === LOG_NS + "notEqualTo" ||
1700
+ v === LOG_NS + "forAllIn" ||
1701
+ v === LOG_NS + "notEqualTo" ||
1617
1702
  v === LOG_NS + "notIncludes"
1618
1703
  ) {
1619
1704
  return true;
@@ -1621,17 +1706,17 @@ function isConstraintBuiltin(tr) {
1621
1706
 
1622
1707
  // string: relational / membership style tests (no bindings)
1623
1708
  if (
1624
- v === STRING_NS + "contains" ||
1709
+ v === STRING_NS + "contains" ||
1625
1710
  v === STRING_NS + "containsIgnoringCase" ||
1626
- v === STRING_NS + "endsWith" ||
1627
- v === STRING_NS + "equalIgnoringCase" ||
1628
- v === STRING_NS + "greaterThan" ||
1629
- v === STRING_NS + "lessThan" ||
1630
- v === STRING_NS + "matches" ||
1711
+ v === STRING_NS + "endsWith" ||
1712
+ v === STRING_NS + "equalIgnoringCase" ||
1713
+ v === STRING_NS + "greaterThan" ||
1714
+ v === STRING_NS + "lessThan" ||
1715
+ v === STRING_NS + "matches" ||
1631
1716
  v === STRING_NS + "notEqualIgnoringCase" ||
1632
- v === STRING_NS + "notGreaterThan" ||
1633
- v === STRING_NS + "notLessThan" ||
1634
- v === STRING_NS + "notMatches" ||
1717
+ v === STRING_NS + "notGreaterThan" ||
1718
+ v === STRING_NS + "notLessThan" ||
1719
+ v === STRING_NS + "notMatches" ||
1635
1720
  v === STRING_NS + "startsWith"
1636
1721
  ) {
1637
1722
  return true;
@@ -1665,17 +1750,15 @@ function reorderPremiseForConstraints(premise) {
1665
1750
 
1666
1751
  function containsVarTerm(t, v) {
1667
1752
  if (t instanceof Var) return t.name === v;
1668
- if (t instanceof ListTerm) return t.elems.some(e => containsVarTerm(e, v));
1753
+ if (t instanceof ListTerm) return t.elems.some((e) => containsVarTerm(e, v));
1669
1754
  if (t instanceof OpenListTerm)
1670
- return (
1671
- t.prefix.some(e => containsVarTerm(e, v)) || t.tailVar === v
1672
- );
1755
+ return t.prefix.some((e) => containsVarTerm(e, v)) || t.tailVar === v;
1673
1756
  if (t instanceof FormulaTerm)
1674
1757
  return t.triples.some(
1675
- tr =>
1758
+ (tr) =>
1676
1759
  containsVarTerm(tr.s, v) ||
1677
1760
  containsVarTerm(tr.p, v) ||
1678
- containsVarTerm(tr.o, v)
1761
+ containsVarTerm(tr.o, v),
1679
1762
  );
1680
1763
  return false;
1681
1764
  }
@@ -1684,8 +1767,10 @@ function isGroundTermInFormula(t) {
1684
1767
  // EYE-style: variables inside formula terms are treated as local placeholders,
1685
1768
  // so they don't make the *surrounding triple* non-ground.
1686
1769
  if (t instanceof OpenListTerm) return false;
1687
- if (t instanceof ListTerm) return t.elems.every(e => isGroundTermInFormula(e));
1688
- if (t instanceof FormulaTerm) return t.triples.every(tr => isGroundTripleInFormula(tr));
1770
+ if (t instanceof ListTerm)
1771
+ return t.elems.every((e) => isGroundTermInFormula(e));
1772
+ if (t instanceof FormulaTerm)
1773
+ return t.triples.every((tr) => isGroundTripleInFormula(tr));
1689
1774
  // Iri/Literal/Blank/Var are all OK inside formulas
1690
1775
  return true;
1691
1776
  }
@@ -1700,9 +1785,10 @@ function isGroundTripleInFormula(tr) {
1700
1785
 
1701
1786
  function isGroundTerm(t) {
1702
1787
  if (t instanceof Var) return false;
1703
- if (t instanceof ListTerm) return t.elems.every(e => isGroundTerm(e));
1788
+ if (t instanceof ListTerm) return t.elems.every((e) => isGroundTerm(e));
1704
1789
  if (t instanceof OpenListTerm) return false;
1705
- if (t instanceof FormulaTerm) return t.triples.every(tr => isGroundTripleInFormula(tr));
1790
+ if (t instanceof FormulaTerm)
1791
+ return t.triples.every((tr) => isGroundTripleInFormula(tr));
1706
1792
  return true;
1707
1793
  }
1708
1794
 
@@ -1715,21 +1801,17 @@ function isGroundTriple(tr) {
1715
1801
  // robust to seeing vars/open lists anyway.
1716
1802
  function skolemKeyFromTerm(t) {
1717
1803
  function enc(u) {
1718
- if (u instanceof Iri) return ["I", u.value];
1719
- if (u instanceof Literal) return ["L", u.value];
1720
- if (u instanceof Blank) return ["B", u.label];
1721
- if (u instanceof Var) return ["V", u.name];
1804
+ if (u instanceof Iri) return ["I", u.value];
1805
+ if (u instanceof Literal) return ["L", u.value];
1806
+ if (u instanceof Blank) return ["B", u.label];
1807
+ if (u instanceof Var) return ["V", u.name];
1722
1808
  if (u instanceof ListTerm) return ["List", u.elems.map(enc)];
1723
1809
  if (u instanceof OpenListTerm)
1724
1810
  return ["OpenList", u.prefix.map(enc), u.tailVar];
1725
1811
  if (u instanceof FormulaTerm)
1726
1812
  return [
1727
1813
  "Formula",
1728
- u.triples.map(tr => [
1729
- enc(tr.s),
1730
- enc(tr.p),
1731
- enc(tr.o)
1732
- ])
1814
+ u.triples.map((tr) => [enc(tr.s), enc(tr.p), enc(tr.o)]),
1733
1815
  ];
1734
1816
  return ["Other", String(u)];
1735
1817
  }
@@ -1768,11 +1850,11 @@ function applySubstTerm(t, s) {
1768
1850
 
1769
1851
  // Non-variable terms
1770
1852
  if (t instanceof ListTerm) {
1771
- return new ListTerm(t.elems.map(e => applySubstTerm(e, s)));
1853
+ return new ListTerm(t.elems.map((e) => applySubstTerm(e, s)));
1772
1854
  }
1773
1855
 
1774
1856
  if (t instanceof OpenListTerm) {
1775
- const newPrefix = t.prefix.map(e => applySubstTerm(e, s));
1857
+ const newPrefix = t.prefix.map((e) => applySubstTerm(e, s));
1776
1858
  const tailTerm = s[t.tailVar];
1777
1859
  if (tailTerm !== undefined) {
1778
1860
  const tailApplied = applySubstTerm(tailTerm, s);
@@ -1781,7 +1863,7 @@ function applySubstTerm(t, s) {
1781
1863
  } else if (tailApplied instanceof OpenListTerm) {
1782
1864
  return new OpenListTerm(
1783
1865
  newPrefix.concat(tailApplied.prefix),
1784
- tailApplied.tailVar
1866
+ tailApplied.tailVar,
1785
1867
  );
1786
1868
  } else {
1787
1869
  return new OpenListTerm(newPrefix, t.tailVar);
@@ -1792,7 +1874,7 @@ function applySubstTerm(t, s) {
1792
1874
  }
1793
1875
 
1794
1876
  if (t instanceof FormulaTerm) {
1795
- return new FormulaTerm(t.triples.map(tr => applySubstTriple(tr, s)));
1877
+ return new FormulaTerm(t.triples.map((tr) => applySubstTriple(tr, s)));
1796
1878
  }
1797
1879
 
1798
1880
  return t;
@@ -1802,7 +1884,7 @@ function applySubstTriple(tr, s) {
1802
1884
  return new Triple(
1803
1885
  applySubstTerm(tr.s, s),
1804
1886
  applySubstTerm(tr.p, s),
1805
- applySubstTerm(tr.o, s)
1887
+ applySubstTerm(tr.o, s),
1806
1888
  );
1807
1889
  }
1808
1890
 
@@ -1837,9 +1919,10 @@ function unifyFormulaTriples(xs, ys, subst) {
1837
1919
  const y = ys[j];
1838
1920
 
1839
1921
  // Cheap pruning when both predicates are IRIs.
1840
- if (x.p instanceof Iri && y.p instanceof Iri && x.p.value !== y.p.value) continue;
1922
+ if (x.p instanceof Iri && y.p instanceof Iri && x.p.value !== y.p.value)
1923
+ continue;
1841
1924
 
1842
- const s2 = unifyTriple(x, y, s); // IMPORTANT: use `s`, not {}
1925
+ const s2 = unifyTriple(x, y, s); // IMPORTANT: use `s`, not {}
1843
1926
  if (s2 === null) continue;
1844
1927
 
1845
1928
  used[j] = true;
@@ -1972,7 +2055,11 @@ function literalParts(lit) {
1972
2055
  // Strip LANGTAG from the lexical form when present.
1973
2056
  if (lex.length >= 2 && lex[0] === '"') {
1974
2057
  const lastQuote = lex.lastIndexOf('"');
1975
- if (lastQuote > 0 && lastQuote < lex.length - 1 && lex[lastQuote + 1] === "@") {
2058
+ if (
2059
+ lastQuote > 0 &&
2060
+ lastQuote < lex.length - 1 &&
2061
+ lex[lastQuote + 1] === "@"
2062
+ ) {
1976
2063
  const lang = lex.slice(lastQuote + 2);
1977
2064
  if (/^[A-Za-z]+(?:-[A-Za-z0-9]+)*$/.test(lang)) {
1978
2065
  lex = lex.slice(0, lastQuote + 1);
@@ -2019,7 +2106,11 @@ function termToJsStringDecoded(t) {
2019
2106
 
2020
2107
  // Short strings: try to decode escapes (this makes "{\"a\":1}" usable too).
2021
2108
  if (lex.length >= 2 && lex[0] === '"' && lex[lex.length - 1] === '"') {
2022
- try { return JSON.parse(lex); } catch (e) { /* fall through */ }
2109
+ try {
2110
+ return JSON.parse(lex);
2111
+ } catch (e) {
2112
+ /* fall through */
2113
+ }
2023
2114
  return stripQuotes(lex);
2024
2115
  }
2025
2116
 
@@ -2031,7 +2122,10 @@ function jsonPointerUnescape(seg) {
2031
2122
  let out = "";
2032
2123
  for (let i = 0; i < seg.length; i++) {
2033
2124
  const c = seg[i];
2034
- if (c !== "~") { out += c; continue; }
2125
+ if (c !== "~") {
2126
+ out += c;
2127
+ continue;
2128
+ }
2035
2129
  if (i + 1 >= seg.length) return null;
2036
2130
  const n = seg[i + 1];
2037
2131
  if (n === "0") out += "~";
@@ -2060,13 +2154,21 @@ function jsonPointerLookup(jsonText, pointer) {
2060
2154
 
2061
2155
  // Support URI fragment form "#/a/b"
2062
2156
  if (ptr.startsWith("#")) {
2063
- try { ptr = decodeURIComponent(ptr.slice(1)); } catch { return null; }
2157
+ try {
2158
+ ptr = decodeURIComponent(ptr.slice(1));
2159
+ } catch {
2160
+ return null;
2161
+ }
2064
2162
  }
2065
2163
 
2066
2164
  let entry = jsonPointerCache.get(jsonText);
2067
2165
  if (!entry) {
2068
2166
  let parsed = null;
2069
- try { parsed = JSON.parse(jsonText); } catch { parsed = null; }
2167
+ try {
2168
+ parsed = JSON.parse(jsonText);
2169
+ } catch {
2170
+ parsed = null;
2171
+ }
2070
2172
  entry = { parsed, ptrCache: new Map() };
2071
2173
  jsonPointerCache.set(jsonText, entry);
2072
2174
  }
@@ -2081,20 +2183,35 @@ function jsonPointerLookup(jsonText, pointer) {
2081
2183
  entry.ptrCache.set(ptr, t);
2082
2184
  return t;
2083
2185
  }
2084
- if (!ptr.startsWith("/")) { entry.ptrCache.set(ptr, null); return null; }
2186
+ if (!ptr.startsWith("/")) {
2187
+ entry.ptrCache.set(ptr, null);
2188
+ return null;
2189
+ }
2085
2190
 
2086
2191
  const parts = ptr.split("/").slice(1);
2087
2192
  for (const raw of parts) {
2088
2193
  const seg = jsonPointerUnescape(raw);
2089
- if (seg === null) { entry.ptrCache.set(ptr, null); return null; }
2194
+ if (seg === null) {
2195
+ entry.ptrCache.set(ptr, null);
2196
+ return null;
2197
+ }
2090
2198
 
2091
2199
  if (Array.isArray(cur)) {
2092
- if (!/^(0|[1-9]\d*)$/.test(seg)) { entry.ptrCache.set(ptr, null); return null; }
2200
+ if (!/^(0|[1-9]\d*)$/.test(seg)) {
2201
+ entry.ptrCache.set(ptr, null);
2202
+ return null;
2203
+ }
2093
2204
  const idx = Number(seg);
2094
- if (idx < 0 || idx >= cur.length) { entry.ptrCache.set(ptr, null); return null; }
2205
+ if (idx < 0 || idx >= cur.length) {
2206
+ entry.ptrCache.set(ptr, null);
2207
+ return null;
2208
+ }
2095
2209
  cur = cur[idx];
2096
2210
  } else if (cur !== null && typeof cur === "object") {
2097
- if (!Object.prototype.hasOwnProperty.call(cur, seg)) { entry.ptrCache.set(ptr, null); return null; }
2211
+ if (!Object.prototype.hasOwnProperty.call(cur, seg)) {
2212
+ entry.ptrCache.set(ptr, null);
2213
+ return null;
2214
+ }
2098
2215
  cur = cur[seg];
2099
2216
  } else {
2100
2217
  entry.ptrCache.set(ptr, null);
@@ -2140,33 +2257,124 @@ function simpleStringFormat(fmt, args) {
2140
2257
  return out;
2141
2258
  }
2142
2259
 
2260
+ // -----------------------------------------------------------------------------
2261
+ // Strict numeric literal parsing for math: builtins
2262
+ // -----------------------------------------------------------------------------
2263
+ const XSD_DECIMAL_DT = XSD_NS + "decimal";
2264
+ const XSD_DOUBLE_DT = XSD_NS + "double";
2265
+ const XSD_FLOAT_DT = XSD_NS + "float";
2266
+ const XSD_INTEGER_DT = XSD_NS + "integer";
2267
+
2268
+ // Integer-derived datatypes from XML Schema Part 2 (and commonly used ones).
2269
+ const XSD_INTEGER_DERIVED_DTS = new Set([
2270
+ XSD_INTEGER_DT,
2271
+ XSD_NS + "nonPositiveInteger",
2272
+ XSD_NS + "negativeInteger",
2273
+ XSD_NS + "long",
2274
+ XSD_NS + "int",
2275
+ XSD_NS + "short",
2276
+ XSD_NS + "byte",
2277
+ XSD_NS + "nonNegativeInteger",
2278
+ XSD_NS + "unsignedLong",
2279
+ XSD_NS + "unsignedInt",
2280
+ XSD_NS + "unsignedShort",
2281
+ XSD_NS + "unsignedByte",
2282
+ XSD_NS + "positiveInteger",
2283
+ ]);
2284
+
2285
+ function isQuotedLexical(lex) {
2286
+ // Note: the lexer stores long strings with literal delimiters: """..."""
2287
+ return (
2288
+ (lex.length >= 2 && lex[0] === '"' && lex[lex.length - 1] === '"') ||
2289
+ (lex.length >= 6 && lex.startsWith('"""') && lex.endsWith('"""'))
2290
+ );
2291
+ }
2292
+
2293
+ function isXsdNumericDatatype(dt) {
2294
+ if (dt === null) return false;
2295
+ return (
2296
+ dt === XSD_DECIMAL_DT ||
2297
+ dt === XSD_DOUBLE_DT ||
2298
+ dt === XSD_FLOAT_DT ||
2299
+ XSD_INTEGER_DERIVED_DTS.has(dt)
2300
+ );
2301
+ }
2302
+
2303
+ function isXsdIntegerDatatype(dt) {
2304
+ if (dt === null) return false;
2305
+ return XSD_INTEGER_DERIVED_DTS.has(dt);
2306
+ }
2307
+
2308
+ function looksLikeUntypedNumericTokenLex(lex) {
2309
+ // We only treat *unquoted* tokens as "untyped numeric" (Turtle/N3 numeric literal).
2310
+ // Quoted literals without datatype are strings, never numbers.
2311
+ if (isQuotedLexical(lex)) return false;
2312
+
2313
+ // integer
2314
+ if (/^[+-]?\d+$/.test(lex)) return true;
2315
+
2316
+ // decimal (no exponent)
2317
+ if (/^[+-]?(?:\d+\.\d*|\.\d+)$/.test(lex)) return true;
2318
+
2319
+ // double (with exponent)
2320
+ if (/^[+-]?(?:\d+\.\d*|\.\d+|\d+)(?:[eE][+-]?\d+)$/.test(lex)) return true;
2321
+
2322
+ return false;
2323
+ }
2324
+
2143
2325
  function parseNum(t) {
2144
- // Parse as JS Number (for floats, dates-as-seconds, etc.)
2326
+ // Parse as JS Number, but ONLY for xsd numeric datatypes or untyped numeric tokens.
2327
+ // Rejects values such as "1"^^<...non-numeric...> or "1" (a string literal).
2145
2328
  if (!(t instanceof Literal)) return null;
2146
- let s = t.value;
2147
- let [lex, _dt] = literalParts(s);
2148
- const val = stripQuotes(lex);
2149
- const n = Number(val);
2150
- if (!Number.isNaN(n)) return n;
2151
- return null;
2329
+
2330
+ const [lex, dt] = literalParts(t.value);
2331
+
2332
+ // Typed literals: must be xsd numeric.
2333
+ if (dt !== null) {
2334
+ if (!isXsdNumericDatatype(dt)) return null;
2335
+ const val = stripQuotes(lex);
2336
+ const n = Number(val);
2337
+ if (!Number.isFinite(n)) return null;
2338
+ return n;
2339
+ }
2340
+
2341
+ // Untyped literals: accept only unquoted numeric tokens.
2342
+ if (!looksLikeUntypedNumericTokenLex(lex)) return null;
2343
+ const n = Number(lex);
2344
+ if (!Number.isFinite(n)) return null;
2345
+ return n;
2152
2346
  }
2153
2347
 
2154
2348
  function parseIntLiteral(t) {
2155
- // Parse as BigInt if the lexical form is an integer
2349
+ // Parse as BigInt if (and only if) it is an integer literal in an integer datatype,
2350
+ // or an untyped integer token.
2156
2351
  if (!(t instanceof Literal)) return null;
2157
- let s = t.value;
2158
- let [lex, _dt] = literalParts(s);
2159
- const val = stripQuotes(lex);
2160
- if (!/^[+-]?\d+$/.test(val)) return null;
2352
+
2353
+ const [lex, dt] = literalParts(t.value);
2354
+
2355
+ if (dt !== null) {
2356
+ if (!isXsdIntegerDatatype(dt)) return null;
2357
+ const val = stripQuotes(lex);
2358
+ if (!/^[+-]?\d+$/.test(val)) return null;
2359
+ try {
2360
+ return BigInt(val);
2361
+ } catch {
2362
+ return null;
2363
+ }
2364
+ }
2365
+
2366
+ // Untyped: only accept unquoted integer tokens.
2367
+ if (isQuotedLexical(lex)) return null;
2368
+ if (!/^[+-]?\d+$/.test(lex)) return null;
2161
2369
  try {
2162
- return BigInt(val);
2163
- } catch (e) {
2370
+ return BigInt(lex);
2371
+ } catch {
2164
2372
  return null;
2165
2373
  }
2166
2374
  }
2167
2375
 
2168
2376
  function parseNumberLiteral(t) {
2169
- // Prefer BigInt for integers, fall back to Number for non-integers
2377
+ // Prefer BigInt for integers, fall back to Number for other numeric literals.
2170
2378
  const bi = parseIntLiteral(t);
2171
2379
  if (bi !== null) return bi;
2172
2380
  const n = parseNum(t);
@@ -2180,28 +2388,22 @@ function formatNum(n) {
2180
2388
 
2181
2389
  function parseXsdDateTerm(t) {
2182
2390
  if (!(t instanceof Literal)) return null;
2183
- const s = t.value;
2184
- let [lex, dt] = literalParts(s);
2391
+ const [lex, dt] = literalParts(t.value);
2392
+ if (dt !== XSD_NS + "date") return null;
2185
2393
  const val = stripQuotes(lex);
2186
- if (dt === XSD_NS + "date" || val.length === 10) {
2187
- const d = new Date(val + "T00:00:00Z");
2188
- if (Number.isNaN(d.getTime())) return null;
2189
- return d;
2190
- }
2191
- return null;
2394
+ const d = new Date(val + "T00:00:00Z");
2395
+ if (Number.isNaN(d.getTime())) return null;
2396
+ return d;
2192
2397
  }
2193
2398
 
2194
2399
  function parseXsdDatetimeTerm(t) {
2195
2400
  if (!(t instanceof Literal)) return null;
2196
- const s = t.value;
2197
- let [lex, dt] = literalParts(s);
2401
+ const [lex, dt] = literalParts(t.value);
2402
+ if (dt !== XSD_NS + "dateTime") return null;
2198
2403
  const val = stripQuotes(lex);
2199
- if (dt === XSD_NS + "dateTime" || val.includes("T")) {
2200
- const d = new Date(val);
2201
- if (Number.isNaN(d.getTime())) return null;
2202
- return d; // Date in local/UTC, we only use timestamp
2203
- }
2204
- return null;
2404
+ const d = new Date(val);
2405
+ if (Number.isNaN(d.getTime())) return null;
2406
+ return d; // Date in local/UTC, we only use timestamp
2205
2407
  }
2206
2408
 
2207
2409
  function parseDatetimeLike(t) {
@@ -2260,24 +2462,13 @@ function parseIso8601DurationToSeconds(s) {
2260
2462
  }
2261
2463
 
2262
2464
  function parseNumericForCompareTerm(t) {
2263
- // Try integer BigInt first
2264
- if (t instanceof Literal) {
2265
- let [lex, dt] = literalParts(t.value);
2266
- const val = stripQuotes(lex);
2267
- if (/^[+-]?\d+$/.test(val)) {
2268
- try {
2269
- return { kind: "bigint", value: BigInt(val) };
2270
- } catch (e) {
2271
- // fall through
2272
- }
2273
- }
2274
- // durations / dateTimes / floats -> Number (seconds or numeric)
2275
- const nDur = parseNumOrDuration(t);
2276
- if (nDur !== null) return { kind: "number", value: nDur };
2277
- return null;
2278
- }
2279
- const n = parseNumOrDuration(t);
2280
- if (n !== null) return { kind: "number", value: n };
2465
+ // Strict: only accept xsd numeric literals, xsd:duration, xsd:date, xsd:dateTime
2466
+ // (or untyped numeric tokens).
2467
+ const bi = parseIntLiteral(t);
2468
+ if (bi !== null) return { kind: "bigint", value: bi };
2469
+
2470
+ const nDur = parseNumOrDuration(t);
2471
+ if (nDur !== null) return { kind: "number", value: nDur };
2281
2472
  return null;
2282
2473
  }
2283
2474
 
@@ -2286,8 +2477,8 @@ function cmpNumericInfo(aInfo, bInfo, op) {
2286
2477
  if (!aInfo || !bInfo) return false;
2287
2478
 
2288
2479
  if (aInfo.kind === "bigint" && bInfo.kind === "bigint") {
2289
- if (op === ">") return aInfo.value > bInfo.value;
2290
- if (op === "<") return aInfo.value < bInfo.value;
2480
+ if (op === ">") return aInfo.value > bInfo.value;
2481
+ if (op === "<") return aInfo.value < bInfo.value;
2291
2482
  if (op === ">=") return aInfo.value >= bInfo.value;
2292
2483
  if (op === "<=") return aInfo.value <= bInfo.value;
2293
2484
  if (op === "==") return aInfo.value == bInfo.value;
@@ -2298,8 +2489,8 @@ function cmpNumericInfo(aInfo, bInfo, op) {
2298
2489
  const a = typeof aInfo.value === "bigint" ? Number(aInfo.value) : aInfo.value;
2299
2490
  const b = typeof bInfo.value === "bigint" ? Number(bInfo.value) : bInfo.value;
2300
2491
 
2301
- if (op === ">") return a > b;
2302
- if (op === "<") return a < b;
2492
+ if (op === ">") return a > b;
2493
+ if (op === "<") return a < b;
2303
2494
  if (op === ">=") return a >= b;
2304
2495
  if (op === "<=") return a <= b;
2305
2496
  if (op === "==") return a == b;
@@ -2310,22 +2501,22 @@ function cmpNumericInfo(aInfo, bInfo, op) {
2310
2501
  function parseNumOrDuration(t) {
2311
2502
  const n = parseNum(t);
2312
2503
  if (n !== null) return n;
2504
+
2505
+ // xsd:duration
2313
2506
  if (t instanceof Literal) {
2314
- let s = t.value;
2315
- let [lex, dt] = literalParts(s);
2316
- const val = stripQuotes(lex);
2317
- if (
2318
- dt === XSD_NS + "duration" ||
2319
- val.startsWith("P") ||
2320
- val.startsWith("-P")
2321
- ) {
2507
+ const [lex, dt] = literalParts(t.value);
2508
+ if (dt === XSD_NS + "duration") {
2509
+ const val = stripQuotes(lex);
2322
2510
  const negative = val.startsWith("-");
2323
2511
  const core = negative ? val.slice(1) : val;
2512
+ if (!core.startsWith("P")) return null;
2324
2513
  const secs = parseIso8601DurationToSeconds(core);
2325
2514
  if (secs === null) return null;
2326
2515
  return negative ? -secs : secs;
2327
2516
  }
2328
2517
  }
2518
+
2519
+ // xsd:date / xsd:dateTime
2329
2520
  const dtval = parseDatetimeLike(t);
2330
2521
  if (dtval !== null) {
2331
2522
  return dtval.getTime() / 1000.0;
@@ -2453,7 +2644,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2453
2644
  if (g.p instanceof Iri && g.p.value === MATH_NS + "greaterThan") {
2454
2645
  const aInfo = parseNumericForCompareTerm(g.s);
2455
2646
  const bInfo = parseNumericForCompareTerm(g.o);
2456
- if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, ">")) return [{ ...subst }];
2647
+ if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, ">"))
2648
+ return [{ ...subst }];
2457
2649
 
2458
2650
  if (g.s instanceof ListTerm && g.s.elems.length === 2) {
2459
2651
  const a2 = parseNumericForCompareTerm(g.s.elems[0]);
@@ -2467,7 +2659,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2467
2659
  if (g.p instanceof Iri && g.p.value === MATH_NS + "lessThan") {
2468
2660
  const aInfo = parseNumericForCompareTerm(g.s);
2469
2661
  const bInfo = parseNumericForCompareTerm(g.o);
2470
- if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, "<")) return [{ ...subst }];
2662
+ if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, "<"))
2663
+ return [{ ...subst }];
2471
2664
 
2472
2665
  if (g.s instanceof ListTerm && g.s.elems.length === 2) {
2473
2666
  const a2 = parseNumericForCompareTerm(g.s.elems[0]);
@@ -2481,7 +2674,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2481
2674
  if (g.p instanceof Iri && g.p.value === MATH_NS + "notLessThan") {
2482
2675
  const aInfo = parseNumericForCompareTerm(g.s);
2483
2676
  const bInfo = parseNumericForCompareTerm(g.o);
2484
- if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, ">=")) return [{ ...subst }];
2677
+ if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, ">="))
2678
+ return [{ ...subst }];
2485
2679
 
2486
2680
  if (g.s instanceof ListTerm && g.s.elems.length === 2) {
2487
2681
  const a2 = parseNumericForCompareTerm(g.s.elems[0]);
@@ -2495,7 +2689,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2495
2689
  if (g.p instanceof Iri && g.p.value === MATH_NS + "notGreaterThan") {
2496
2690
  const aInfo = parseNumericForCompareTerm(g.s);
2497
2691
  const bInfo = parseNumericForCompareTerm(g.o);
2498
- if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, "<=")) return [{ ...subst }];
2692
+ if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, "<="))
2693
+ return [{ ...subst }];
2499
2694
 
2500
2695
  if (g.s instanceof ListTerm && g.s.elems.length === 2) {
2501
2696
  const a2 = parseNumericForCompareTerm(g.s.elems[0]);
@@ -2509,7 +2704,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2509
2704
  if (g.p instanceof Iri && g.p.value === MATH_NS + "equalTo") {
2510
2705
  const aInfo = parseNumericForCompareTerm(g.s);
2511
2706
  const bInfo = parseNumericForCompareTerm(g.o);
2512
- if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, "==")) return [{ ...subst }];
2707
+ if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, "=="))
2708
+ return [{ ...subst }];
2513
2709
 
2514
2710
  if (g.s instanceof ListTerm && g.s.elems.length === 2) {
2515
2711
  const a2 = parseNumericForCompareTerm(g.s.elems[0]);
@@ -2523,7 +2719,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2523
2719
  if (g.p instanceof Iri && g.p.value === MATH_NS + "notEqualTo") {
2524
2720
  const aInfo = parseNumericForCompareTerm(g.s);
2525
2721
  const bInfo = parseNumericForCompareTerm(g.o);
2526
- if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, "!=")) return [{ ...subst }];
2722
+ if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, "!="))
2723
+ return [{ ...subst }];
2527
2724
 
2528
2725
  if (g.s instanceof ListTerm && g.s.elems.length === 2) {
2529
2726
  const a2 = parseNumericForCompareTerm(g.s.elems[0]);
@@ -2545,7 +2742,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2545
2742
  }
2546
2743
 
2547
2744
  let lit;
2548
- const allBig = values.every(v => typeof v === "bigint");
2745
+ const allBig = values.every((v) => typeof v === "bigint");
2549
2746
  if (allBig) {
2550
2747
  let total = 0n;
2551
2748
  for (const v of values) total += v;
@@ -2581,7 +2778,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2581
2778
  }
2582
2779
 
2583
2780
  let lit;
2584
- const allBig = values.every(v => typeof v === "bigint");
2781
+ const allBig = values.every((v) => typeof v === "bigint");
2585
2782
  if (allBig) {
2586
2783
  let prod = 1n;
2587
2784
  for (const v of values) prod *= v;
@@ -3321,7 +3518,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3321
3518
  else if (g.o instanceof ListTerm) inputList = g.o.elems;
3322
3519
  else return [];
3323
3520
 
3324
- if (!inputList.every(e => isGroundTerm(e))) return [];
3521
+ if (!inputList.every((e) => isGroundTerm(e))) return [];
3325
3522
 
3326
3523
  const sortedList = [...inputList].sort(cmpTermForSort);
3327
3524
  const sortedTerm = new ListTerm(sortedList);
@@ -3345,13 +3542,20 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3345
3542
  if (!(predTerm instanceof Iri)) return [];
3346
3543
  const pred = new Iri(predTerm.value);
3347
3544
  if (!isBuiltinPred(pred)) return [];
3348
- if (!inputList.every(e => isGroundTerm(e))) return [];
3545
+ if (!inputList.every((e) => isGroundTerm(e))) return [];
3349
3546
 
3350
3547
  const results = [];
3351
3548
  for (const el of inputList) {
3352
3549
  const yvar = new Var("_mapY");
3353
3550
  const goal2 = new Triple(el, pred, yvar);
3354
- const sols = evalBuiltin(goal2, subst, facts, backRules, depth + 1, varGen);
3551
+ const sols = evalBuiltin(
3552
+ goal2,
3553
+ subst,
3554
+ facts,
3555
+ backRules,
3556
+ depth + 1,
3557
+ varGen,
3558
+ );
3355
3559
  if (!sols.length) return [];
3356
3560
  const yval = applySubstTerm(yvar, sols[0]);
3357
3561
  if (yval instanceof Var) return [];
@@ -3484,7 +3688,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3484
3688
  backRules,
3485
3689
  depth + 1,
3486
3690
  visited2,
3487
- varGen
3691
+ varGen,
3488
3692
  );
3489
3693
  if (!sols.length) return [{ ...subst }];
3490
3694
  return [];
@@ -3504,7 +3708,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3504
3708
  backRules,
3505
3709
  depth + 1,
3506
3710
  visited2,
3507
- varGen
3711
+ varGen,
3508
3712
  );
3509
3713
 
3510
3714
  // Collect one value per *solution*, duplicates allowed
@@ -3536,7 +3740,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3536
3740
  backRules,
3537
3741
  depth + 1,
3538
3742
  visited1,
3539
- varGen
3743
+ varGen,
3540
3744
  );
3541
3745
 
3542
3746
  // 2. For every such substitution, check that the second clause holds too.
@@ -3550,7 +3754,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3550
3754
  backRules,
3551
3755
  depth + 1,
3552
3756
  visited2,
3553
- varGen
3757
+ varGen,
3554
3758
  );
3555
3759
  // Found a counterexample: whereClause holds but thenClause does not
3556
3760
  if (!sols2.length) return [];
@@ -3581,7 +3785,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3581
3785
  if (g.p instanceof Iri && g.p.value === LOG_NS + "uri") {
3582
3786
  // Direction 1: subject is an IRI -> object is its string representation
3583
3787
  if (g.s instanceof Iri) {
3584
- const uriStr = g.s.value; // raw IRI string, e.g. "https://www.w3.org"
3788
+ const uriStr = g.s.value; // raw IRI string, e.g. "https://www.w3.org"
3585
3789
  const lit = makeStringLiteral(uriStr); // "https://www.w3.org"
3586
3790
  const s2 = unifyTerm(goal.o, lit, subst);
3587
3791
  return s2 !== null ? [s2] : [];
@@ -3656,9 +3860,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3656
3860
  const sStr = termToJsString(g.s);
3657
3861
  const oStr = termToJsString(g.o);
3658
3862
  if (sStr === null || oStr === null) return [];
3659
- return sStr.toLowerCase() === oStr.toLowerCase()
3660
- ? [{ ...subst }]
3661
- : [];
3863
+ return sStr.toLowerCase() === oStr.toLowerCase() ? [{ ...subst }] : [];
3662
3864
  }
3663
3865
 
3664
3866
  // string:format
@@ -3693,8 +3895,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3693
3895
  if (g.p instanceof Iri && g.p.value === STRING_NS + "jsonPointer") {
3694
3896
  if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
3695
3897
 
3696
- const jsonText = termToJsonText(g.s.elems[0]); // <-- changed
3697
- const ptr = termToJsStringDecoded(g.s.elems[1]);
3898
+ const jsonText = termToJsonText(g.s.elems[0]); // <-- changed
3899
+ const ptr = termToJsStringDecoded(g.s.elems[1]);
3698
3900
  if (jsonText === null || ptr === null) return [];
3699
3901
 
3700
3902
  const valTerm = jsonPointerLookup(jsonText, ptr);
@@ -3740,9 +3942,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3740
3942
  const sStr = termToJsString(g.s);
3741
3943
  const oStr = termToJsString(g.o);
3742
3944
  if (sStr === null || oStr === null) return [];
3743
- return sStr.toLowerCase() !== oStr.toLowerCase()
3744
- ? [{ ...subst }]
3745
- : [];
3945
+ return sStr.toLowerCase() !== oStr.toLowerCase() ? [{ ...subst }] : [];
3746
3946
  }
3747
3947
 
3748
3948
  // string:notGreaterThan (≤ in Unicode code order)
@@ -3778,9 +3978,9 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3778
3978
  // string:replace
3779
3979
  if (g.p instanceof Iri && g.p.value === STRING_NS + "replace") {
3780
3980
  if (!(g.s instanceof ListTerm) || g.s.elems.length !== 3) return [];
3781
- const dataStr = termToJsString(g.s.elems[0]);
3981
+ const dataStr = termToJsString(g.s.elems[0]);
3782
3982
  const searchStr = termToJsString(g.s.elems[1]);
3783
- const replStr = termToJsString(g.s.elems[2]);
3983
+ const replStr = termToJsString(g.s.elems[2]);
3784
3984
  if (dataStr === null || searchStr === null || replStr === null) return [];
3785
3985
 
3786
3986
  let re;
@@ -3855,10 +4055,10 @@ function isBuiltinPred(p) {
3855
4055
 
3856
4056
  return (
3857
4057
  v.startsWith(CRYPTO_NS) ||
3858
- v.startsWith(MATH_NS) ||
3859
- v.startsWith(LOG_NS) ||
4058
+ v.startsWith(MATH_NS) ||
4059
+ v.startsWith(LOG_NS) ||
3860
4060
  v.startsWith(STRING_NS) ||
3861
- v.startsWith(TIME_NS) ||
4061
+ v.startsWith(TIME_NS) ||
3862
4062
  v.startsWith(LIST_NS)
3863
4063
  );
3864
4064
  }
@@ -3878,10 +4078,10 @@ function standardizeRule(rule, gen) {
3878
4078
  return new Var(vmap[t.name]);
3879
4079
  }
3880
4080
  if (t instanceof ListTerm) {
3881
- return new ListTerm(t.elems.map(e => renameTerm(e, vmap, genArr)));
4081
+ return new ListTerm(t.elems.map((e) => renameTerm(e, vmap, genArr)));
3882
4082
  }
3883
4083
  if (t instanceof OpenListTerm) {
3884
- const newXs = t.prefix.map(e => renameTerm(e, vmap, genArr));
4084
+ const newXs = t.prefix.map((e) => renameTerm(e, vmap, genArr));
3885
4085
  if (!vmap.hasOwnProperty(t.tailVar)) {
3886
4086
  const name = `${t.tailVar}__${genArr[0]}`;
3887
4087
  genArr[0] += 1;
@@ -3892,13 +4092,14 @@ function standardizeRule(rule, gen) {
3892
4092
  }
3893
4093
  if (t instanceof FormulaTerm) {
3894
4094
  return new FormulaTerm(
3895
- t.triples.map(tr =>
3896
- new Triple(
3897
- renameTerm(tr.s, vmap, genArr),
3898
- renameTerm(tr.p, vmap, genArr),
3899
- renameTerm(tr.o, vmap, genArr)
3900
- )
3901
- )
4095
+ t.triples.map(
4096
+ (tr) =>
4097
+ new Triple(
4098
+ renameTerm(tr.s, vmap, genArr),
4099
+ renameTerm(tr.p, vmap, genArr),
4100
+ renameTerm(tr.o, vmap, genArr),
4101
+ ),
4102
+ ),
3902
4103
  );
3903
4104
  }
3904
4105
  return t;
@@ -3906,32 +4107,32 @@ function standardizeRule(rule, gen) {
3906
4107
 
3907
4108
  const vmap2 = {};
3908
4109
  const premise = rule.premise.map(
3909
- tr =>
4110
+ (tr) =>
3910
4111
  new Triple(
3911
4112
  renameTerm(tr.s, vmap2, gen),
3912
4113
  renameTerm(tr.p, vmap2, gen),
3913
- renameTerm(tr.o, vmap2, gen)
3914
- )
4114
+ renameTerm(tr.o, vmap2, gen),
4115
+ ),
3915
4116
  );
3916
4117
  const conclusion = rule.conclusion.map(
3917
- tr =>
4118
+ (tr) =>
3918
4119
  new Triple(
3919
4120
  renameTerm(tr.s, vmap2, gen),
3920
4121
  renameTerm(tr.p, vmap2, gen),
3921
- renameTerm(tr.o, vmap2, gen)
3922
- )
4122
+ renameTerm(tr.o, vmap2, gen),
4123
+ ),
3923
4124
  );
3924
4125
  return new Rule(
3925
4126
  premise,
3926
4127
  conclusion,
3927
4128
  rule.isForward,
3928
4129
  rule.isFuse,
3929
- rule.headBlankLabels
4130
+ rule.headBlankLabels,
3930
4131
  );
3931
4132
  }
3932
4133
 
3933
4134
  function listHasTriple(list, tr) {
3934
- return list.some(t => triplesEqual(t, tr));
4135
+ return list.some((t) => triplesEqual(t, tr));
3935
4136
  }
3936
4137
 
3937
4138
  // ============================================================================
@@ -3951,14 +4152,23 @@ function listHasTriple(list, tr) {
3951
4152
  // This is semantics-preserving for the ongoing proof state.
3952
4153
 
3953
4154
  function gcCollectVarsInTerm(t, out) {
3954
- if (t instanceof Var) { out.add(t.name); return; }
3955
- if (t instanceof ListTerm) { for (const e of t.elems) gcCollectVarsInTerm(e, out); return; }
4155
+ if (t instanceof Var) {
4156
+ out.add(t.name);
4157
+ return;
4158
+ }
4159
+ if (t instanceof ListTerm) {
4160
+ for (const e of t.elems) gcCollectVarsInTerm(e, out);
4161
+ return;
4162
+ }
3956
4163
  if (t instanceof OpenListTerm) {
3957
4164
  for (const e of t.prefix) gcCollectVarsInTerm(e, out);
3958
4165
  out.add(t.tailVar);
3959
4166
  return;
3960
4167
  }
3961
- if (t instanceof FormulaTerm) { for (const tr of t.triples) gcCollectVarsInTriple(tr, out); return; }
4168
+ if (t instanceof FormulaTerm) {
4169
+ for (const tr of t.triples) gcCollectVarsInTriple(tr, out);
4170
+ return;
4171
+ }
3962
4172
  }
3963
4173
 
3964
4174
  function gcCollectVarsInTriple(tr, out) {
@@ -4018,8 +4228,7 @@ function maybeCompactSubst(subst, goals, answerVars, depth) {
4018
4228
  return gcCompactForGoals(subst, goals, answerVars);
4019
4229
  }
4020
4230
 
4021
-
4022
- function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
4231
+ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen) {
4023
4232
  // Iterative DFS over proof states using an explicit stack.
4024
4233
  // Each state carries its own substitution and remaining goals.
4025
4234
  const results = [];
@@ -4028,7 +4237,6 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
4028
4237
  const initialSubst = subst ? { ...subst } : {};
4029
4238
  const initialVisited = visited ? visited.slice() : [];
4030
4239
 
4031
-
4032
4240
  // Variables from the original goal list (needed by the caller to instantiate conclusions)
4033
4241
  const answerVars = new Set();
4034
4242
  gcCollectVarsInGoals(initialGoals, answerVars);
@@ -4038,7 +4246,12 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
4038
4246
  }
4039
4247
 
4040
4248
  const stack = [
4041
- { goals: initialGoals, subst: initialSubst, depth: depth || 0, visited: initialVisited }
4249
+ {
4250
+ goals: initialGoals,
4251
+ subst: initialSubst,
4252
+ depth: depth || 0,
4253
+ visited: initialVisited,
4254
+ },
4042
4255
  ];
4043
4256
 
4044
4257
  while (stack.length) {
@@ -4055,7 +4268,14 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
4055
4268
 
4056
4269
  // 1) Builtins
4057
4270
  if (isBuiltinPred(goal0.p)) {
4058
- const deltas = evalBuiltin(goal0, {}, facts, backRules, state.depth, varGen);
4271
+ const deltas = evalBuiltin(
4272
+ goal0,
4273
+ {},
4274
+ facts,
4275
+ backRules,
4276
+ state.depth,
4277
+ varGen,
4278
+ );
4059
4279
  for (const delta of deltas) {
4060
4280
  const composed = composeSubst(state.subst, delta);
4061
4281
  if (composed === null) continue;
@@ -4063,12 +4283,17 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
4063
4283
  if (!restGoals.length) {
4064
4284
  results.push(gcCompactForGoals(composed, [], answerVars));
4065
4285
  } else {
4066
- const nextSubst = maybeCompactSubst(composed, restGoals, answerVars, state.depth + 1);
4286
+ const nextSubst = maybeCompactSubst(
4287
+ composed,
4288
+ restGoals,
4289
+ answerVars,
4290
+ state.depth + 1,
4291
+ );
4067
4292
  stack.push({
4068
4293
  goals: restGoals,
4069
4294
  subst: nextSubst,
4070
4295
  depth: state.depth + 1,
4071
- visited: state.visited
4296
+ visited: state.visited,
4072
4297
  });
4073
4298
  }
4074
4299
  }
@@ -4092,12 +4317,17 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
4092
4317
  if (!restGoals.length) {
4093
4318
  results.push(gcCompactForGoals(composed, [], answerVars));
4094
4319
  } else {
4095
- const nextSubst = maybeCompactSubst(composed, restGoals, answerVars, state.depth + 1);
4320
+ const nextSubst = maybeCompactSubst(
4321
+ composed,
4322
+ restGoals,
4323
+ answerVars,
4324
+ state.depth + 1,
4325
+ );
4096
4326
  stack.push({
4097
4327
  goals: restGoals,
4098
4328
  subst: nextSubst,
4099
4329
  depth: state.depth + 1,
4100
- visited: state.visited
4330
+ visited: state.visited,
4101
4331
  });
4102
4332
  }
4103
4333
  }
@@ -4113,12 +4343,17 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
4113
4343
  if (!restGoals.length) {
4114
4344
  results.push(gcCompactForGoals(composed, [], answerVars));
4115
4345
  } else {
4116
- const nextSubst = maybeCompactSubst(composed, restGoals, answerVars, state.depth + 1);
4346
+ const nextSubst = maybeCompactSubst(
4347
+ composed,
4348
+ restGoals,
4349
+ answerVars,
4350
+ state.depth + 1,
4351
+ );
4117
4352
  stack.push({
4118
4353
  goals: restGoals,
4119
4354
  subst: nextSubst,
4120
4355
  depth: state.depth + 1,
4121
- visited: state.visited
4356
+ visited: state.visited,
4122
4357
  });
4123
4358
  }
4124
4359
  }
@@ -4127,31 +4362,38 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
4127
4362
  // 4) Backward rules (indexed by head predicate)
4128
4363
  if (goal0.p instanceof Iri) {
4129
4364
  ensureBackRuleIndexes(backRules);
4130
- const candRules =
4131
- (backRules.__byHeadPred.get(goal0.p.value) || []).concat(backRules.__wildHeadPred);
4365
+ const candRules = (
4366
+ backRules.__byHeadPred.get(goal0.p.value) || []
4367
+ ).concat(backRules.__wildHeadPred);
4132
4368
 
4133
4369
  for (const r of candRules) {
4134
4370
  if (r.conclusion.length !== 1) continue;
4135
4371
 
4136
4372
  const rawHead = r.conclusion[0];
4137
- if (rawHead.p instanceof Iri && rawHead.p.value !== goal0.p.value) continue;
4373
+ if (rawHead.p instanceof Iri && rawHead.p.value !== goal0.p.value)
4374
+ continue;
4138
4375
 
4139
4376
  const rStd = standardizeRule(r, varGen);
4140
4377
  const head = rStd.conclusion[0];
4141
4378
  const deltaHead = unifyTriple(head, goal0, {});
4142
4379
  if (deltaHead === null) continue;
4143
4380
 
4144
- const body = rStd.premise.map(b => applySubstTriple(b, deltaHead));
4381
+ const body = rStd.premise.map((b) => applySubstTriple(b, deltaHead));
4145
4382
  const composed = composeSubst(state.subst, deltaHead);
4146
4383
  if (composed === null) continue;
4147
4384
 
4148
4385
  const newGoals = body.concat(restGoals);
4149
- const nextSubst = maybeCompactSubst(composed, newGoals, answerVars, state.depth + 1);
4386
+ const nextSubst = maybeCompactSubst(
4387
+ composed,
4388
+ newGoals,
4389
+ answerVars,
4390
+ state.depth + 1,
4391
+ );
4150
4392
  stack.push({
4151
4393
  goals: newGoals,
4152
4394
  subst: nextSubst,
4153
4395
  depth: state.depth + 1,
4154
- visited: visitedForRules
4396
+ visited: visitedForRules,
4155
4397
  });
4156
4398
  }
4157
4399
  }
@@ -4185,54 +4427,82 @@ function forwardChain(facts, forwardRules, backRules) {
4185
4427
  const empty = {};
4186
4428
  const visited = [];
4187
4429
 
4188
- const sols = proveGoals(r.premise.slice(), empty, facts, backRules, 0, visited, varGen);
4430
+ const sols = proveGoals(
4431
+ r.premise.slice(),
4432
+ empty,
4433
+ facts,
4434
+ backRules,
4435
+ 0,
4436
+ visited,
4437
+ varGen,
4438
+ );
4189
4439
 
4190
4440
  // Inference fuse
4191
4441
  if (r.isFuse && sols.length) {
4192
- console.log("# Inference fuse triggered: a { ... } => false. rule fired.");
4442
+ console.log(
4443
+ "# Inference fuse triggered: a { ... } => false. rule fired.",
4444
+ );
4193
4445
  process.exit(2);
4194
4446
  }
4195
4447
 
4196
4448
  for (const s of sols) {
4197
- const instantiatedPremises = r.premise.map(b => applySubstTriple(b, s));
4449
+ const instantiatedPremises = r.premise.map((b) =>
4450
+ applySubstTriple(b, s),
4451
+ );
4198
4452
 
4199
4453
  for (const cpat of r.conclusion) {
4200
4454
  const instantiated = applySubstTriple(cpat, s);
4201
4455
 
4202
4456
  const isFwRuleTriple =
4203
4457
  isLogImplies(instantiated.p) &&
4204
- (
4205
- (instantiated.s instanceof FormulaTerm && instantiated.o instanceof FormulaTerm) ||
4206
- (instantiated.s instanceof Literal && instantiated.s.value === "true" && instantiated.o instanceof FormulaTerm) ||
4207
- (instantiated.s instanceof FormulaTerm && instantiated.o instanceof Literal && instantiated.o.value === "true")
4208
- );
4458
+ ((instantiated.s instanceof FormulaTerm &&
4459
+ instantiated.o instanceof FormulaTerm) ||
4460
+ (instantiated.s instanceof Literal &&
4461
+ instantiated.s.value === "true" &&
4462
+ instantiated.o instanceof FormulaTerm) ||
4463
+ (instantiated.s instanceof FormulaTerm &&
4464
+ instantiated.o instanceof Literal &&
4465
+ instantiated.o.value === "true"));
4209
4466
 
4210
4467
  const isBwRuleTriple =
4211
4468
  isLogImpliedBy(instantiated.p) &&
4212
- (
4213
- (instantiated.s instanceof FormulaTerm && instantiated.o instanceof FormulaTerm) ||
4214
- (instantiated.s instanceof FormulaTerm && instantiated.o instanceof Literal && instantiated.o.value === "true") ||
4215
- (instantiated.s instanceof Literal && instantiated.s.value === "true" && instantiated.o instanceof FormulaTerm)
4216
- );
4469
+ ((instantiated.s instanceof FormulaTerm &&
4470
+ instantiated.o instanceof FormulaTerm) ||
4471
+ (instantiated.s instanceof FormulaTerm &&
4472
+ instantiated.o instanceof Literal &&
4473
+ instantiated.o.value === "true") ||
4474
+ (instantiated.s instanceof Literal &&
4475
+ instantiated.s.value === "true" &&
4476
+ instantiated.o instanceof FormulaTerm));
4217
4477
 
4218
4478
  if (isFwRuleTriple || isBwRuleTriple) {
4219
4479
  if (!hasFactIndexed(facts, instantiated)) {
4220
4480
  factList.push(instantiated);
4221
4481
  pushFactIndexed(facts, instantiated);
4222
- derivedForward.push(new DerivedFact(instantiated, r, instantiatedPremises.slice(), { ...s }));
4482
+ derivedForward.push(
4483
+ new DerivedFact(instantiated, r, instantiatedPremises.slice(), {
4484
+ ...s,
4485
+ }),
4486
+ );
4223
4487
  changed = true;
4224
4488
  }
4225
4489
 
4226
4490
  // Promote rule-producing triples to live rules, treating literal true as {}.
4227
4491
  const left =
4228
- instantiated.s instanceof FormulaTerm ? instantiated.s.triples :
4229
- (instantiated.s instanceof Literal && instantiated.s.value === "true") ? [] :
4230
- null;
4492
+ instantiated.s instanceof FormulaTerm
4493
+ ? instantiated.s.triples
4494
+ : instantiated.s instanceof Literal &&
4495
+ instantiated.s.value === "true"
4496
+ ? []
4497
+ : null;
4231
4498
 
4232
4499
  const right =
4233
- instantiated.o instanceof FormulaTerm ? instantiated.o.triples :
4234
- (instantiated.o instanceof Literal && instantiated.o.value === "true") ? [] :
4235
- null;
4500
+ instantiated.o instanceof FormulaTerm
4501
+ ? instantiated.o.triples
4502
+ : instantiated.o instanceof Literal &&
4503
+ instantiated.o.value === "true"
4504
+ ? []
4505
+ : null;
4236
4506
 
4237
4507
  if (left !== null && right !== null) {
4238
4508
  if (isFwRuleTriple) {
@@ -4240,28 +4510,40 @@ function forwardChain(facts, forwardRules, backRules) {
4240
4510
  const premise = reorderPremiseForConstraints(premise0);
4241
4511
 
4242
4512
  const headBlankLabels = collectBlankLabelsInTriples(conclusion);
4243
- const newRule = new Rule(premise, conclusion, true, false, headBlankLabels);
4513
+ const newRule = new Rule(
4514
+ premise,
4515
+ conclusion,
4516
+ true,
4517
+ false,
4518
+ headBlankLabels,
4519
+ );
4244
4520
 
4245
4521
  const already = forwardRules.some(
4246
- rr =>
4522
+ (rr) =>
4247
4523
  rr.isForward === newRule.isForward &&
4248
4524
  rr.isFuse === newRule.isFuse &&
4249
4525
  triplesListEqual(rr.premise, newRule.premise) &&
4250
- triplesListEqual(rr.conclusion, newRule.conclusion)
4526
+ triplesListEqual(rr.conclusion, newRule.conclusion),
4251
4527
  );
4252
4528
  if (!already) forwardRules.push(newRule);
4253
4529
  } else if (isBwRuleTriple) {
4254
4530
  const [premise, conclusion] = liftBlankRuleVars(right, left);
4255
4531
 
4256
4532
  const headBlankLabels = collectBlankLabelsInTriples(conclusion);
4257
- const newRule = new Rule(premise, conclusion, false, false, headBlankLabels);
4533
+ const newRule = new Rule(
4534
+ premise,
4535
+ conclusion,
4536
+ false,
4537
+ false,
4538
+ headBlankLabels,
4539
+ );
4258
4540
 
4259
4541
  const already = backRules.some(
4260
- rr =>
4542
+ (rr) =>
4261
4543
  rr.isForward === newRule.isForward &&
4262
4544
  rr.isFuse === newRule.isFuse &&
4263
4545
  triplesListEqual(rr.premise, newRule.premise) &&
4264
- triplesListEqual(rr.conclusion, newRule.conclusion)
4546
+ triplesListEqual(rr.conclusion, newRule.conclusion),
4265
4547
  );
4266
4548
  if (!already) {
4267
4549
  backRules.push(newRule);
@@ -4275,7 +4557,12 @@ function forwardChain(facts, forwardRules, backRules) {
4275
4557
 
4276
4558
  // Only skolemize blank nodes that occur explicitly in the rule head
4277
4559
  const skMap = {};
4278
- const inst = skolemizeTripleForHeadBlanks(instantiated, r.headBlankLabels, skMap, skCounter);
4560
+ const inst = skolemizeTripleForHeadBlanks(
4561
+ instantiated,
4562
+ r.headBlankLabels,
4563
+ skMap,
4564
+ skCounter,
4565
+ );
4279
4566
 
4280
4567
  if (!isGroundTriple(inst)) continue;
4281
4568
  if (hasFactIndexed(facts, inst)) continue;
@@ -4283,7 +4570,9 @@ function forwardChain(facts, forwardRules, backRules) {
4283
4570
  factList.push(inst);
4284
4571
  pushFactIndexed(facts, inst);
4285
4572
 
4286
- derivedForward.push(new DerivedFact(inst, r, instantiatedPremises.slice(), { ...s }));
4573
+ derivedForward.push(
4574
+ new DerivedFact(inst, r, instantiatedPremises.slice(), { ...s }),
4575
+ );
4287
4576
  changed = true;
4288
4577
  }
4289
4578
  }
@@ -4307,31 +4596,31 @@ function termToN3(t, pref) {
4307
4596
  if (i.startsWith("_:")) return i;
4308
4597
  return `<${i}>`;
4309
4598
  }
4310
- if (t instanceof Literal) {
4311
- const [lex, dt] = literalParts(t.value);
4312
-
4313
- // Pretty-print xsd:boolean as bare true/false
4314
- if (dt === XSD_NS + "boolean") {
4315
- const v = stripQuotes(lex);
4316
- if (v === "true" || v === "false") return v;
4317
- // optional: normalize 1/0 too
4318
- if (v === "1") return "true";
4319
- if (v === "0") return "false";
4320
- }
4321
-
4322
- if (!dt) return t.value; // keep numbers, booleans, lang-tagged strings, etc.
4323
- const qdt = pref.shrinkIri(dt);
4324
- if (qdt !== null) return `${lex}^^${qdt}`; // e.g. ^^rdf:JSON
4325
- return `${lex}^^<${dt}>`; // fallback
4326
- }
4599
+ if (t instanceof Literal) {
4600
+ const [lex, dt] = literalParts(t.value);
4601
+
4602
+ // Pretty-print xsd:boolean as bare true/false
4603
+ if (dt === XSD_NS + "boolean") {
4604
+ const v = stripQuotes(lex);
4605
+ if (v === "true" || v === "false") return v;
4606
+ // optional: normalize 1/0 too
4607
+ if (v === "1") return "true";
4608
+ if (v === "0") return "false";
4609
+ }
4610
+
4611
+ if (!dt) return t.value; // keep numbers, booleans, lang-tagged strings, etc.
4612
+ const qdt = pref.shrinkIri(dt);
4613
+ if (qdt !== null) return `${lex}^^${qdt}`; // e.g. ^^rdf:JSON
4614
+ return `${lex}^^<${dt}>`; // fallback
4615
+ }
4327
4616
  if (t instanceof Var) return `?${t.name}`;
4328
4617
  if (t instanceof Blank) return t.label;
4329
4618
  if (t instanceof ListTerm) {
4330
- const inside = t.elems.map(e => termToN3(e, pref));
4619
+ const inside = t.elems.map((e) => termToN3(e, pref));
4331
4620
  return "(" + inside.join(" ") + ")";
4332
4621
  }
4333
4622
  if (t instanceof OpenListTerm) {
4334
- const inside = t.prefix.map(e => termToN3(e, pref));
4623
+ const inside = t.prefix.map((e) => termToN3(e, pref));
4335
4624
  inside.push("?" + t.tailVar);
4336
4625
  return "(" + inside.join(" ") + ")";
4337
4626
  }
@@ -4362,10 +4651,11 @@ function tripleToN3(tr, prefixes) {
4362
4651
  }
4363
4652
 
4364
4653
  const s = termToN3(tr.s, prefixes);
4365
- const p =
4366
- isRdfTypePred(tr.p) ? "a"
4367
- : isOwlSameAsPred(tr.p) ? "="
4368
- : termToN3(tr.p, prefixes);
4654
+ const p = isRdfTypePred(tr.p)
4655
+ ? "a"
4656
+ : isOwlSameAsPred(tr.p)
4657
+ ? "="
4658
+ : termToN3(tr.p, prefixes);
4369
4659
  const o = termToN3(tr.o, prefixes);
4370
4660
 
4371
4661
  return `${s} ${p} ${o} .`;
@@ -4373,7 +4663,7 @@ function tripleToN3(tr, prefixes) {
4373
4663
 
4374
4664
  function printExplanation(df, prefixes) {
4375
4665
  console.log(
4376
- "# ----------------------------------------------------------------------"
4666
+ "# ----------------------------------------------------------------------",
4377
4667
  );
4378
4668
  console.log("# Proof for derived triple:");
4379
4669
 
@@ -4387,14 +4677,14 @@ function printExplanation(df, prefixes) {
4387
4677
 
4388
4678
  if (!df.premises.length) {
4389
4679
  console.log(
4390
- "# This triple is the head of a forward rule with an empty premise,"
4680
+ "# This triple is the head of a forward rule with an empty premise,",
4391
4681
  );
4392
4682
  console.log(
4393
- "# so it holds unconditionally whenever the program is loaded."
4683
+ "# so it holds unconditionally whenever the program is loaded.",
4394
4684
  );
4395
4685
  } else {
4396
4686
  console.log(
4397
- "# It holds because the following instance of the rule body is provable:"
4687
+ "# It holds because the following instance of the rule body is provable:",
4398
4688
  );
4399
4689
 
4400
4690
  // Premises, also indented 2 spaces after '# '
@@ -4434,7 +4724,7 @@ function printExplanation(df, prefixes) {
4434
4724
  // Substitution block
4435
4725
  const ruleVars = varsInRule(df.rule);
4436
4726
  const visibleNames = Object.keys(df.subst)
4437
- .filter(name => ruleVars.has(name))
4727
+ .filter((name) => ruleVars.has(name))
4438
4728
  .sort();
4439
4729
 
4440
4730
  if (visibleNames.length) {
@@ -4472,10 +4762,10 @@ function printExplanation(df, prefixes) {
4472
4762
  }
4473
4763
 
4474
4764
  console.log(
4475
- "# Therefore the derived triple above is entailed by the rules and facts."
4765
+ "# Therefore the derived triple above is entailed by the rules and facts.",
4476
4766
  );
4477
4767
  console.log(
4478
- "# ----------------------------------------------------------------------\n"
4768
+ "# ----------------------------------------------------------------------\n",
4479
4769
  );
4480
4770
  }
4481
4771
 
@@ -4487,8 +4777,8 @@ function printExplanation(df, prefixes) {
4487
4777
  // This mutates triples/rules in-place so list:* builtins work on RDF-serialized lists too.
4488
4778
  function materializeRdfLists(triples, forwardRules, backwardRules) {
4489
4779
  const RDF_FIRST = RDF_NS + "first";
4490
- const RDF_REST = RDF_NS + "rest";
4491
- const RDF_NIL = RDF_NS + "nil";
4780
+ const RDF_REST = RDF_NS + "rest";
4781
+ const RDF_NIL = RDF_NS + "nil";
4492
4782
 
4493
4783
  function nodeKey(t) {
4494
4784
  if (t instanceof Blank) return "B:" + t.label;
@@ -4498,7 +4788,7 @@ function materializeRdfLists(triples, forwardRules, backwardRules) {
4498
4788
 
4499
4789
  // Collect first/rest arcs from *input triples*
4500
4790
  const firstMap = new Map(); // key(subject) -> Term (object)
4501
- const restMap = new Map(); // key(subject) -> Term (object)
4791
+ const restMap = new Map(); // key(subject) -> Term (object)
4502
4792
  for (const tr of triples) {
4503
4793
  if (!(tr.p instanceof Iri)) continue;
4504
4794
  const k = nodeKey(tr.s);
@@ -4508,8 +4798,8 @@ function materializeRdfLists(triples, forwardRules, backwardRules) {
4508
4798
  }
4509
4799
  if (!firstMap.size && !restMap.size) return;
4510
4800
 
4511
- const cache = new Map(); // key(node) -> ListTerm
4512
- const visiting = new Set(); // cycle guard
4801
+ const cache = new Map(); // key(node) -> ListTerm
4802
+ const visiting = new Set(); // cycle guard
4513
4803
 
4514
4804
  function buildListForKey(k) {
4515
4805
  if (cache.has(k)) return cache.get(k);
@@ -4568,7 +4858,7 @@ function materializeRdfLists(triples, forwardRules, backwardRules) {
4568
4858
  }
4569
4859
  if (t instanceof ListTerm) {
4570
4860
  let changed = false;
4571
- const elems = t.elems.map(e => {
4861
+ const elems = t.elems.map((e) => {
4572
4862
  const r = rewriteTerm(e);
4573
4863
  if (r !== e) changed = true;
4574
4864
  return r;
@@ -4577,7 +4867,7 @@ function materializeRdfLists(triples, forwardRules, backwardRules) {
4577
4867
  }
4578
4868
  if (t instanceof OpenListTerm) {
4579
4869
  let changed = false;
4580
- const prefix = t.prefix.map(e => {
4870
+ const prefix = t.prefix.map((e) => {
4581
4871
  const r = rewriteTerm(e);
4582
4872
  if (r !== e) changed = true;
4583
4873
  return r;
@@ -4675,11 +4965,11 @@ function main() {
4675
4965
  // --------------------------------------------------------------------------
4676
4966
  // Positional args (the N3 file)
4677
4967
  // --------------------------------------------------------------------------
4678
- const positional = argv.filter(a => !a.startsWith("-"));
4968
+ const positional = argv.filter((a) => !a.startsWith("-"));
4679
4969
 
4680
4970
  if (positional.length !== 1) {
4681
4971
  console.error(
4682
- "Usage: eyeling.js [--version|-v] [--no-proof-comments|-n] <file.n3>"
4972
+ "Usage: eyeling.js [--version|-v] [--no-proof-comments|-n] <file.n3>",
4683
4973
  );
4684
4974
  process.exit(1);
4685
4975
  }
@@ -4702,10 +4992,10 @@ function main() {
4702
4992
  // Build internal ListTerm values from rdf:first/rdf:rest (+ rdf:nil) input triples
4703
4993
  materializeRdfLists(triples, frules, brules);
4704
4994
 
4705
- const facts = triples.filter(tr => isGroundTriple(tr));
4995
+ const facts = triples.filter((tr) => isGroundTriple(tr));
4706
4996
  const derived = forwardChain(facts, frules, brules);
4707
4997
 
4708
- const derivedTriples = derived.map(df => df.fact);
4998
+ const derivedTriples = derived.map((df) => df.fact);
4709
4999
  const usedPrefixes = prefixes.prefixesUsedForOutput(derivedTriples);
4710
5000
 
4711
5001
  for (const [pfx, base] of usedPrefixes) {
@@ -4728,4 +5018,3 @@ function main() {
4728
5018
  if (require.main === module) {
4729
5019
  main();
4730
5020
  }
4731
-