eyeling 1.5.38 → 1.5.40

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
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- "use strict";
2
+ 'use strict';
3
3
 
4
4
  /*
5
5
  * eyeling.js — a minimal Notation3 (N3) reasoner in JavaScript
@@ -17,28 +17,28 @@
17
17
  */
18
18
 
19
19
  const { version } = require('./package.json');
20
- const nodeCrypto = require("crypto");
20
+ const nodeCrypto = require('crypto');
21
21
 
22
22
  // ============================================================================
23
23
  // Namespace constants
24
24
  // ============================================================================
25
25
 
26
- const RDF_NS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
27
- const RDFS_NS = "http://www.w3.org/2000/01/rdf-schema#";
28
- const OWL_NS = "http://www.w3.org/2002/07/owl#";
29
- const XSD_NS = "http://www.w3.org/2001/XMLSchema#";
30
- const CRYPTO_NS = "http://www.w3.org/2000/10/swap/crypto#";
31
- const MATH_NS = "http://www.w3.org/2000/10/swap/math#";
32
- const TIME_NS = "http://www.w3.org/2000/10/swap/time#";
33
- const LIST_NS = "http://www.w3.org/2000/10/swap/list#";
34
- const LOG_NS = "http://www.w3.org/2000/10/swap/log#";
35
- const STRING_NS = "http://www.w3.org/2000/10/swap/string#";
36
- const SKOLEM_NS = "https://eyereasoner.github.io/.well-known/genid/";
37
- const RDF_JSON_DT = RDF_NS + "JSON";
26
+ const RDF_NS = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
27
+ const RDFS_NS = 'http://www.w3.org/2000/01/rdf-schema#';
28
+ const OWL_NS = 'http://www.w3.org/2002/07/owl#';
29
+ const XSD_NS = 'http://www.w3.org/2001/XMLSchema#';
30
+ const CRYPTO_NS = 'http://www.w3.org/2000/10/swap/crypto#';
31
+ const MATH_NS = 'http://www.w3.org/2000/10/swap/math#';
32
+ const TIME_NS = 'http://www.w3.org/2000/10/swap/time#';
33
+ const LIST_NS = 'http://www.w3.org/2000/10/swap/list#';
34
+ const LOG_NS = 'http://www.w3.org/2000/10/swap/log#';
35
+ const STRING_NS = 'http://www.w3.org/2000/10/swap/string#';
36
+ const SKOLEM_NS = 'https://eyereasoner.github.io/.well-known/genid/';
37
+ const RDF_JSON_DT = RDF_NS + 'JSON';
38
38
 
39
39
  function isRdfJsonDatatype(dt) {
40
40
  // dt comes from literalParts() and may be expanded or prefixed depending on parsing/printing.
41
- return dt === null || dt === RDF_JSON_DT || dt === "rdf:JSON";
41
+ return dt === null || dt === RDF_JSON_DT || dt === 'rdf:JSON';
42
42
  }
43
43
 
44
44
  function termToJsonText(t) {
@@ -81,7 +81,7 @@ function normalizeDateTimeLex(s) {
81
81
  // Accept: 2025-... , "2025-..." , "2025-..."^^xsd:dateTime , "..."^^<...>
82
82
  if (s == null) return null;
83
83
  let t = String(s).trim();
84
- const caret = t.indexOf("^^");
84
+ const caret = t.indexOf('^^');
85
85
  if (caret >= 0) t = t.slice(0, caret).trim();
86
86
  if (t.startsWith('"') && t.endsWith('"') && t.length >= 2) t = t.slice(1, -1);
87
87
  return t.trim();
@@ -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();
@@ -98,12 +100,8 @@ function utcIsoDateTimeStringFromEpochSeconds(sec) {
98
100
  const min = d.getUTCMinutes();
99
101
  const s2 = d.getUTCSeconds();
100
102
  const ms2 = d.getUTCMilliseconds();
101
- const msPart = ms2 ? "." + String(ms2).padStart(3, "0") : "";
102
- return (
103
- pad(year, 4) + "-" + pad(month) + "-" + pad(day) + "T" +
104
- pad(hour) + ":" + pad(min) + ":" + pad(s2) + msPart +
105
- "+00:00"
106
- );
103
+ const msPart = ms2 ? '.' + String(ms2).padStart(3, '0') : '';
104
+ return pad(year, 4) + '-' + pad(month) + '-' + pad(day) + 'T' + pad(hour) + ':' + pad(min) + ':' + pad(s2) + msPart + '+00:00';
107
105
  }
108
106
 
109
107
  function getNowLex() {
@@ -138,18 +136,10 @@ function deterministicSkolemIdFromKey(key) {
138
136
  h4 = (h4 * 0x01000193) >>> 0;
139
137
  }
140
138
 
141
- const hex = [h1, h2, h3, h4]
142
- .map(h => h.toString(16).padStart(8, "0"))
143
- .join(""); // 32 hex chars
139
+ const hex = [h1, h2, h3, h4].map((h) => h.toString(16).padStart(8, '0')).join(''); // 32 hex chars
144
140
 
145
141
  // Format like a UUID: 8-4-4-4-12
146
- return (
147
- hex.slice(0, 8) + "-" +
148
- hex.slice(8, 12) + "-" +
149
- hex.slice(12, 16) + "-" +
150
- hex.slice(16, 20) + "-" +
151
- hex.slice(20)
152
- );
142
+ return hex.slice(0, 8) + '-' + hex.slice(8, 12) + '-' + hex.slice(12, 16) + '-' + hex.slice(16, 20) + '-' + hex.slice(20);
153
143
  }
154
144
 
155
145
  let runLocalTimeCache = null;
@@ -220,10 +210,10 @@ class Triple {
220
210
 
221
211
  class Rule {
222
212
  constructor(premise, conclusion, isForward, isFuse, headBlankLabels) {
223
- this.premise = premise; // Triple[]
213
+ this.premise = premise; // Triple[]
224
214
  this.conclusion = conclusion; // Triple[]
225
- this.isForward = isForward; // boolean
226
- this.isFuse = isFuse; // boolean
215
+ this.isForward = isForward; // boolean
216
+ this.isFuse = isFuse; // boolean
227
217
  // Set<string> of blank-node labels that occur explicitly in the rule head
228
218
  this.headBlankLabels = headBlankLabels || new Set();
229
219
  }
@@ -231,10 +221,10 @@ class Rule {
231
221
 
232
222
  class DerivedFact {
233
223
  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 }
224
+ this.fact = fact; // Triple
225
+ this.rule = rule; // Rule
226
+ this.premises = premises; // Triple[]
227
+ this.subst = subst; // { varName: Term }
238
228
  }
239
229
  }
240
230
 
@@ -283,82 +273,82 @@ function lex(inputText) {
283
273
  }
284
274
 
285
275
  // 2) Comments starting with '#'
286
- if (c === "#") {
287
- while (i < n && chars[i] !== "\n" && chars[i] !== "\r") i++;
276
+ if (c === '#') {
277
+ while (i < n && chars[i] !== '\n' && chars[i] !== '\r') i++;
288
278
  continue;
289
279
  }
290
280
 
291
281
  // 3) Two-character operators: => and <=
292
- if (c === "=") {
293
- if (peek(1) === ">") {
294
- tokens.push(new Token("OpImplies"));
282
+ if (c === '=') {
283
+ if (peek(1) === '>') {
284
+ tokens.push(new Token('OpImplies'));
295
285
  i += 2;
296
286
  continue;
297
287
  } else {
298
288
  // N3 syntactic sugar: '=' means owl:sameAs
299
- tokens.push(new Token("Equals"));
289
+ tokens.push(new Token('Equals'));
300
290
  i += 1;
301
291
  continue;
302
292
  }
303
293
  }
304
294
 
305
- if (c === "<") {
306
- if (peek(1) === "=") {
307
- tokens.push(new Token("OpImpliedBy"));
295
+ if (c === '<') {
296
+ if (peek(1) === '=') {
297
+ tokens.push(new Token('OpImpliedBy'));
308
298
  i += 2;
309
299
  continue;
310
300
  }
311
301
  // N3 predicate inversion: "<-" (swap subject/object for this predicate)
312
- if (peek(1) === "-") {
313
- tokens.push(new Token("OpPredInvert"));
302
+ if (peek(1) === '-') {
303
+ tokens.push(new Token('OpPredInvert'));
314
304
  i += 2;
315
305
  continue;
316
306
  }
317
307
  // Otherwise IRIREF <...>
318
308
  i++; // skip '<'
319
309
  const iriChars = [];
320
- while (i < n && chars[i] !== ">") {
310
+ while (i < n && chars[i] !== '>') {
321
311
  iriChars.push(chars[i]);
322
312
  i++;
323
313
  }
324
- if (i >= n || chars[i] !== ">") {
325
- throw new Error("Unterminated IRI <...>");
314
+ if (i >= n || chars[i] !== '>') {
315
+ throw new Error('Unterminated IRI <...>');
326
316
  }
327
317
  i++; // skip '>'
328
- const iri = iriChars.join("");
329
- tokens.push(new Token("IriRef", iri));
318
+ const iri = iriChars.join('');
319
+ tokens.push(new Token('IriRef', iri));
330
320
  continue;
331
321
  }
332
322
 
333
323
  // 4) Path + datatype operators: !, ^, ^^
334
- if (c === "!") {
335
- tokens.push(new Token("OpPathFwd"));
324
+ if (c === '!') {
325
+ tokens.push(new Token('OpPathFwd'));
336
326
  i += 1;
337
327
  continue;
338
328
  }
339
- if (c === "^") {
340
- if (peek(1) === "^") {
341
- tokens.push(new Token("HatHat"));
329
+ if (c === '^') {
330
+ if (peek(1) === '^') {
331
+ tokens.push(new Token('HatHat'));
342
332
  i += 2;
343
333
  continue;
344
334
  }
345
- tokens.push(new Token("OpPathRev"));
335
+ tokens.push(new Token('OpPathRev'));
346
336
  i += 1;
347
337
  continue;
348
338
  }
349
339
 
350
340
  // 5) Single-character punctuation
351
- if ("{}()[];,.".includes(c)) {
341
+ if ('{}()[];,.'.includes(c)) {
352
342
  const mapping = {
353
- "{": "LBrace",
354
- "}": "RBrace",
355
- "(": "LParen",
356
- ")": "RParen",
357
- "[": "LBracket",
358
- "]": "RBracket",
359
- ";": "Semicolon",
360
- ",": "Comma",
361
- ".": "Dot",
343
+ '{': 'LBrace',
344
+ '}': 'RBrace',
345
+ '(': 'LParen',
346
+ ')': 'RParen',
347
+ '[': 'LBracket',
348
+ ']': 'RBracket',
349
+ ';': 'Semicolon',
350
+ ',': 'Comma',
351
+ '.': 'Dot',
362
352
  };
363
353
  tokens.push(new Token(mapping[c]));
364
354
  i++;
@@ -381,12 +371,12 @@ function lex(inputText) {
381
371
  }
382
372
  let cc = chars[i];
383
373
  i++;
384
- if (cc === "\\") {
374
+ if (cc === '\\') {
385
375
  // Preserve escapes verbatim (same behavior as short strings)
386
376
  if (i < n) {
387
377
  const esc = chars[i];
388
378
  i++;
389
- sChars.push("\\");
379
+ sChars.push('\\');
390
380
  sChars.push(esc);
391
381
  }
392
382
  continue;
@@ -394,8 +384,8 @@ function lex(inputText) {
394
384
  sChars.push(cc);
395
385
  }
396
386
  if (!closed) throw new Error('Unterminated long string literal """..."""');
397
- const s = '"""' + sChars.join("") + '"""';
398
- tokens.push(new Token("Literal", s));
387
+ const s = '"""' + sChars.join('') + '"""';
388
+ tokens.push(new Token('Literal', s));
399
389
  continue;
400
390
  }
401
391
 
@@ -405,11 +395,11 @@ function lex(inputText) {
405
395
  while (i < n) {
406
396
  let cc = chars[i];
407
397
  i++;
408
- if (cc === "\\") {
398
+ if (cc === '\\') {
409
399
  if (i < n) {
410
400
  const esc = chars[i];
411
401
  i++;
412
- sChars.push("\\");
402
+ sChars.push('\\');
413
403
  sChars.push(esc);
414
404
  }
415
405
  continue;
@@ -417,13 +407,13 @@ function lex(inputText) {
417
407
  if (cc === '"') break;
418
408
  sChars.push(cc);
419
409
  }
420
- const s = '"' + sChars.join("") + '"';
421
- tokens.push(new Token("Literal", s));
410
+ const s = '"' + sChars.join('') + '"';
411
+ tokens.push(new Token('Literal', s));
422
412
  continue;
423
413
  }
424
414
 
425
415
  // Variable ?name
426
- if (c === "?") {
416
+ if (c === '?') {
427
417
  i++;
428
418
  const nameChars = [];
429
419
  let cc;
@@ -431,19 +421,16 @@ function lex(inputText) {
431
421
  nameChars.push(cc);
432
422
  i++;
433
423
  }
434
- const name = nameChars.join("");
435
- tokens.push(new Token("Var", name));
424
+ const name = nameChars.join('');
425
+ tokens.push(new Token('Var', name));
436
426
  continue;
437
427
  }
438
428
 
439
429
  // Directives: @prefix, @base (and language tags after string literals)
440
- if (c === "@") {
430
+ if (c === '@') {
441
431
  const prevTok = tokens.length ? tokens[tokens.length - 1] : null;
442
432
  const prevWasQuotedLiteral =
443
- prevTok &&
444
- prevTok.typ === "Literal" &&
445
- typeof prevTok.value === "string" &&
446
- prevTok.value.startsWith('"');
433
+ prevTok && prevTok.typ === 'Literal' && typeof prevTok.value === 'string' && prevTok.value.startsWith('"');
447
434
 
448
435
  i++; // consume '@'
449
436
 
@@ -459,8 +446,8 @@ function lex(inputText) {
459
446
  tagChars.push(cc);
460
447
  i++;
461
448
  }
462
- while (peek() === "-") {
463
- tagChars.push("-");
449
+ while (peek() === '-') {
450
+ tagChars.push('-');
464
451
  i++; // consume '-'
465
452
  const segChars = [];
466
453
  while ((cc = peek()) !== null && /[A-Za-z0-9]/.test(cc)) {
@@ -472,7 +459,7 @@ function lex(inputText) {
472
459
  }
473
460
  tagChars.push(...segChars);
474
461
  }
475
- tokens.push(new Token("LangTag", tagChars.join("")));
462
+ tokens.push(new Token('LangTag', tagChars.join('')));
476
463
  continue;
477
464
  }
478
465
 
@@ -483,15 +470,15 @@ function lex(inputText) {
483
470
  wordChars.push(cc);
484
471
  i++;
485
472
  }
486
- const word = wordChars.join("");
487
- if (word === "prefix") tokens.push(new Token("AtPrefix"));
488
- else if (word === "base") tokens.push(new Token("AtBase"));
473
+ const word = wordChars.join('');
474
+ if (word === 'prefix') tokens.push(new Token('AtPrefix'));
475
+ else if (word === 'base') tokens.push(new Token('AtBase'));
489
476
  else throw new Error(`Unknown directive @${word}`);
490
477
  continue;
491
478
  }
492
479
 
493
480
  // 6) Numeric literal (integer or float)
494
- if (/[0-9]/.test(c) || (c === "-" && peek(1) !== null && /[0-9]/.test(peek(1)))) {
481
+ if (/[0-9]/.test(c) || (c === '-' && peek(1) !== null && /[0-9]/.test(peek(1)))) {
495
482
  const numChars = [c];
496
483
  i++;
497
484
  while (i < n) {
@@ -501,9 +488,9 @@ function lex(inputText) {
501
488
  i++;
502
489
  continue;
503
490
  }
504
- if (cc === ".") {
491
+ if (cc === '.') {
505
492
  if (i + 1 < n && /[0-9]/.test(chars[i + 1])) {
506
- numChars.push(".");
493
+ numChars.push('.');
507
494
  i++;
508
495
  continue;
509
496
  } else {
@@ -512,7 +499,7 @@ function lex(inputText) {
512
499
  }
513
500
  break;
514
501
  }
515
- tokens.push(new Token("Literal", numChars.join("")));
502
+ tokens.push(new Token('Literal', numChars.join('')));
516
503
  continue;
517
504
  }
518
505
 
@@ -526,17 +513,17 @@ function lex(inputText) {
526
513
  if (!wordChars.length) {
527
514
  throw new Error(`Unexpected char: ${JSON.stringify(c)}`);
528
515
  }
529
- const word = wordChars.join("");
530
- if (word === "true" || word === "false") {
531
- tokens.push(new Token("Literal", word));
532
- } else if ([...word].every(ch => /[0-9.\-]/.test(ch))) {
533
- tokens.push(new Token("Literal", word));
516
+ const word = wordChars.join('');
517
+ if (word === 'true' || word === 'false') {
518
+ tokens.push(new Token('Literal', word));
519
+ } else if ([...word].every((ch) => /[0-9.\-]/.test(ch))) {
520
+ tokens.push(new Token('Literal', word));
534
521
  } else {
535
- tokens.push(new Token("Ident", word));
522
+ tokens.push(new Token('Ident', word));
536
523
  }
537
524
  }
538
525
 
539
- tokens.push(new Token("EOF"));
526
+ tokens.push(new Token('EOF'));
540
527
  return tokens;
541
528
  }
542
529
 
@@ -551,16 +538,16 @@ class PrefixEnv {
551
538
 
552
539
  static newDefault() {
553
540
  const m = {};
554
- m["rdf"] = RDF_NS;
555
- m["rdfs"] = RDFS_NS;
556
- m["xsd"] = XSD_NS;
557
- m["log"] = LOG_NS;
558
- m["math"] = MATH_NS;
559
- m["string"] = STRING_NS;
560
- m["list"] = LIST_NS;
561
- m["time"] = TIME_NS;
562
- m["genid"] = SKOLEM_NS;
563
- m[""] = "";
541
+ m['rdf'] = RDF_NS;
542
+ m['rdfs'] = RDFS_NS;
543
+ m['xsd'] = XSD_NS;
544
+ m['log'] = LOG_NS;
545
+ m['math'] = MATH_NS;
546
+ m['string'] = STRING_NS;
547
+ m['list'] = LIST_NS;
548
+ m['time'] = TIME_NS;
549
+ m['genid'] = SKOLEM_NS;
550
+ m[''] = '';
564
551
  return new PrefixEnv(m);
565
552
  }
566
553
 
@@ -569,9 +556,9 @@ class PrefixEnv {
569
556
  }
570
557
 
571
558
  expandQName(q) {
572
- if (q.includes(":")) {
573
- const [p, local] = q.split(":", 2);
574
- const base = this.map[p] || "";
559
+ if (q.includes(':')) {
560
+ const [p, local] = q.split(':', 2);
561
+ const base = this.map[p] || '';
575
562
  if (base) return base + local;
576
563
  return q;
577
564
  }
@@ -591,7 +578,7 @@ class PrefixEnv {
591
578
  }
592
579
  if (best === null) return null;
593
580
  const [p, local] = best;
594
- if (p === "") return `:${local}`;
581
+ if (p === '') return `:${local}`;
595
582
  return `${p}:${local}`;
596
583
  }
597
584
 
@@ -723,7 +710,7 @@ class Parser {
723
710
 
724
711
  expectDot() {
725
712
  const tok = this.next();
726
- if (tok.typ !== "Dot") {
713
+ if (tok.typ !== 'Dot') {
727
714
  throw new Error(`Expected '.', got ${tok.toString()}`);
728
715
  }
729
716
  }
@@ -733,21 +720,21 @@ class Parser {
733
720
  const forwardRules = [];
734
721
  const backwardRules = [];
735
722
 
736
- while (this.peek().typ !== "EOF") {
737
- if (this.peek().typ === "AtPrefix") {
723
+ while (this.peek().typ !== 'EOF') {
724
+ if (this.peek().typ === 'AtPrefix') {
738
725
  this.next();
739
726
  this.parsePrefixDirective();
740
- } else if (this.peek().typ === "AtBase") {
727
+ } else if (this.peek().typ === 'AtBase') {
741
728
  this.next();
742
729
  this.parseBaseDirective();
743
730
  } else {
744
731
  const first = this.parseTerm();
745
- if (this.peek().typ === "OpImplies") {
732
+ if (this.peek().typ === 'OpImplies') {
746
733
  this.next();
747
734
  const second = this.parseTerm();
748
735
  this.expectDot();
749
736
  forwardRules.push(this.makeRule(first, second, true));
750
- } else if (this.peek().typ === "OpImpliedBy") {
737
+ } else if (this.peek().typ === 'OpImpliedBy') {
751
738
  this.next();
752
739
  const second = this.parseTerm();
753
740
  this.expectDot();
@@ -755,10 +742,10 @@ class Parser {
755
742
  } else {
756
743
  let more;
757
744
 
758
- if (this.peek().typ === "Dot") {
745
+ if (this.peek().typ === 'Dot') {
759
746
  // Allow a bare blank-node property list statement, e.g. `[ a :Statement ].`
760
747
  const lastTok = this.toks[this.pos - 1];
761
- if (this.pendingTriples.length > 0 && lastTok && lastTok.typ === "RBracket") {
748
+ if (this.pendingTriples.length > 0 && lastTok && lastTok.typ === 'RBracket') {
762
749
  more = this.pendingTriples;
763
750
  this.pendingTriples = [];
764
751
  this.next(); // consume '.'
@@ -789,26 +776,26 @@ class Parser {
789
776
 
790
777
  parsePrefixDirective() {
791
778
  const tok = this.next();
792
- if (tok.typ !== "Ident") {
779
+ if (tok.typ !== 'Ident') {
793
780
  throw new Error(`Expected prefix name, got ${tok.toString()}`);
794
781
  }
795
- const pref = tok.value || "";
796
- const prefName = pref.endsWith(":") ? pref.slice(0, -1) : pref;
782
+ const pref = tok.value || '';
783
+ const prefName = pref.endsWith(':') ? pref.slice(0, -1) : pref;
797
784
 
798
- if (this.peek().typ === "Dot") {
785
+ if (this.peek().typ === 'Dot') {
799
786
  this.next();
800
787
  if (!this.prefixes.map.hasOwnProperty(prefName)) {
801
- this.prefixes.set(prefName, "");
788
+ this.prefixes.set(prefName, '');
802
789
  }
803
790
  return;
804
791
  }
805
792
 
806
793
  const tok2 = this.next();
807
794
  let iri;
808
- if (tok2.typ === "IriRef") {
809
- iri = tok2.value || "";
810
- } else if (tok2.typ === "Ident") {
811
- iri = this.prefixes.expandQName(tok2.value || "");
795
+ if (tok2.typ === 'IriRef') {
796
+ iri = tok2.value || '';
797
+ } else if (tok2.typ === 'Ident') {
798
+ iri = this.prefixes.expandQName(tok2.value || '');
812
799
  } else {
813
800
  throw new Error(`Expected IRI after @prefix, got ${tok2.toString()}`);
814
801
  }
@@ -819,32 +806,28 @@ class Parser {
819
806
  parseBaseDirective() {
820
807
  const tok = this.next();
821
808
  let iri;
822
- if (tok.typ === "IriRef") {
823
- iri = tok.value || "";
824
- } else if (tok.typ === "Ident") {
825
- iri = tok.value || "";
809
+ if (tok.typ === 'IriRef') {
810
+ iri = tok.value || '';
811
+ } else if (tok.typ === 'Ident') {
812
+ iri = tok.value || '';
826
813
  } else {
827
814
  throw new Error(`Expected IRI after @base, got ${tok.toString()}`);
828
815
  }
829
816
  this.expectDot();
830
- this.prefixes.set("", iri);
817
+ this.prefixes.set('', iri);
831
818
  }
832
819
 
833
820
  parseTerm() {
834
821
  let t = this.parsePathItem();
835
822
 
836
- while (this.peek().typ === "OpPathFwd" || this.peek().typ === "OpPathRev") {
837
- const dir = this.next().typ; // OpPathFwd | OpPathRev
823
+ while (this.peek().typ === 'OpPathFwd' || this.peek().typ === 'OpPathRev') {
824
+ const dir = this.next().typ; // OpPathFwd | OpPathRev
838
825
  const pred = this.parsePathItem();
839
826
 
840
827
  this.blankCounter += 1;
841
828
  const bn = new Blank(`_:b${this.blankCounter}`);
842
829
 
843
- this.pendingTriples.push(
844
- dir === "OpPathFwd"
845
- ? new Triple(t, pred, bn)
846
- : new Triple(bn, pred, t)
847
- );
830
+ this.pendingTriples.push(dir === 'OpPathFwd' ? new Triple(t, pred, bn) : new Triple(bn, pred, t));
848
831
 
849
832
  t = bn;
850
833
  }
@@ -857,55 +840,55 @@ class Parser {
857
840
  const typ = tok.typ;
858
841
  const val = tok.value;
859
842
 
860
- if (typ === "Equals") {
861
- return new Iri(OWL_NS + "sameAs");
843
+ if (typ === 'Equals') {
844
+ return new Iri(OWL_NS + 'sameAs');
862
845
  }
863
846
 
864
- if (typ === "IriRef") {
865
- return new Iri(val || "");
847
+ if (typ === 'IriRef') {
848
+ return new Iri(val || '');
866
849
  }
867
850
 
868
- if (typ === "Ident") {
869
- const name = val || "";
870
- if (name === "a") {
871
- return new Iri(RDF_NS + "type");
872
- } else if (name.startsWith("_:")) {
851
+ if (typ === 'Ident') {
852
+ const name = val || '';
853
+ if (name === 'a') {
854
+ return new Iri(RDF_NS + 'type');
855
+ } else if (name.startsWith('_:')) {
873
856
  return new Blank(name);
874
- } else if (name.includes(":")) {
857
+ } else if (name.includes(':')) {
875
858
  return new Iri(this.prefixes.expandQName(name));
876
859
  } else {
877
860
  return new Iri(name);
878
861
  }
879
862
  }
880
863
 
881
- if (typ === "Literal") {
882
- let s = val || "";
864
+ if (typ === 'Literal') {
865
+ let s = val || '';
883
866
 
884
867
  // Optional language tag: "..."@en, per N3 LANGTAG production.
885
- if (this.peek().typ === "LangTag") {
868
+ if (this.peek().typ === 'LangTag') {
886
869
  // Only quoted string literals can carry a language tag.
887
870
  if (!(s.startsWith('"') && s.endsWith('"'))) {
888
- throw new Error("Language tag is only allowed on quoted string literals");
871
+ throw new Error('Language tag is only allowed on quoted string literals');
889
872
  }
890
873
  const langTok = this.next();
891
- const lang = langTok.value || "";
874
+ const lang = langTok.value || '';
892
875
  s = `${s}@${lang}`;
893
876
 
894
877
  // N3/Turtle: language tags and datatypes are mutually exclusive.
895
- if (this.peek().typ === "HatHat") {
896
- throw new Error("A literal cannot have both a language tag (@...) and a datatype (^^...)");
878
+ if (this.peek().typ === 'HatHat') {
879
+ throw new Error('A literal cannot have both a language tag (@...) and a datatype (^^...)');
897
880
  }
898
881
  }
899
882
 
900
- if (this.peek().typ === "HatHat") {
883
+ if (this.peek().typ === 'HatHat') {
901
884
  this.next();
902
885
  const dtTok = this.next();
903
886
  let dtIri;
904
- if (dtTok.typ === "IriRef") {
905
- dtIri = dtTok.value || "";
906
- } else if (dtTok.typ === "Ident") {
907
- const qn = dtTok.value || "";
908
- if (qn.includes(":")) dtIri = this.prefixes.expandQName(qn);
887
+ if (dtTok.typ === 'IriRef') {
888
+ dtIri = dtTok.value || '';
889
+ } else if (dtTok.typ === 'Ident') {
890
+ const qn = dtTok.value || '';
891
+ if (qn.includes(':')) dtIri = this.prefixes.expandQName(qn);
909
892
  else dtIri = qn;
910
893
  } else {
911
894
  throw new Error(`Expected datatype after ^^, got ${dtTok.toString()}`);
@@ -915,17 +898,17 @@ class Parser {
915
898
  return new Literal(s);
916
899
  }
917
900
 
918
- if (typ === "Var") return new Var(val || "");
919
- if (typ === "LParen") return this.parseList();
920
- if (typ === "LBracket") return this.parseBlank();
921
- if (typ === "LBrace") return this.parseFormula();
901
+ if (typ === 'Var') return new Var(val || '');
902
+ if (typ === 'LParen') return this.parseList();
903
+ if (typ === 'LBracket') return this.parseBlank();
904
+ if (typ === 'LBrace') return this.parseFormula();
922
905
 
923
906
  throw new Error(`Unexpected term token: ${tok.toString()}`);
924
907
  }
925
908
 
926
909
  parseList() {
927
910
  const elems = [];
928
- while (this.peek().typ !== "RParen") {
911
+ while (this.peek().typ !== 'RParen') {
929
912
  elems.push(this.parseTerm());
930
913
  }
931
914
  this.next(); // consume ')'
@@ -934,7 +917,7 @@ class Parser {
934
917
 
935
918
  parseBlank() {
936
919
  // [] or [ ... ] property list
937
- if (this.peek().typ === "RBracket") {
920
+ if (this.peek().typ === 'RBracket') {
938
921
  this.next();
939
922
  this.blankCounter += 1;
940
923
  return new Blank(`_:b${this.blankCounter}`);
@@ -949,10 +932,10 @@ class Parser {
949
932
  // Verb (can also be 'a')
950
933
  let pred;
951
934
  let invert = false;
952
- if (this.peek().typ === "Ident" && (this.peek().value || "") === "a") {
935
+ if (this.peek().typ === 'Ident' && (this.peek().value || '') === 'a') {
953
936
  this.next();
954
- pred = new Iri(RDF_NS + "type");
955
- } else if (this.peek().typ === "OpPredInvert") {
937
+ pred = new Iri(RDF_NS + 'type');
938
+ } else if (this.peek().typ === 'OpPredInvert') {
956
939
  this.next(); // consume "<-"
957
940
  pred = this.parseTerm();
958
941
  invert = true;
@@ -962,32 +945,27 @@ class Parser {
962
945
 
963
946
  // Object list: o1, o2, ...
964
947
  const objs = [this.parseTerm()];
965
- while (this.peek().typ === "Comma") {
948
+ while (this.peek().typ === 'Comma') {
966
949
  this.next();
967
950
  objs.push(this.parseTerm());
968
951
  }
969
952
 
970
953
  for (const o of objs) {
971
- this.pendingTriples.push(invert ? new Triple(o, pred, subj)
972
- : new Triple(subj, pred, o));
954
+ this.pendingTriples.push(invert ? new Triple(o, pred, subj) : new Triple(subj, pred, o));
973
955
  }
974
956
 
975
- if (this.peek().typ === "Semicolon") {
957
+ if (this.peek().typ === 'Semicolon') {
976
958
  this.next();
977
- if (this.peek().typ === "RBracket") break;
959
+ if (this.peek().typ === 'RBracket') break;
978
960
  continue;
979
961
  }
980
962
  break;
981
963
  }
982
964
 
983
- if (this.peek().typ === "RBracket") {
965
+ if (this.peek().typ === 'RBracket') {
984
966
  this.next();
985
967
  } else {
986
- throw new Error(
987
- `Expected ']' at end of blank node property list, got ${JSON.stringify(
988
- this.peek()
989
- )}`
990
- );
968
+ throw new Error(`Expected ']' at end of blank node property list, got ${JSON.stringify(this.peek())}`);
991
969
  }
992
970
 
993
971
  return new Blank(id);
@@ -995,45 +973,45 @@ class Parser {
995
973
 
996
974
  parseFormula() {
997
975
  const triples = [];
998
- while (this.peek().typ !== "RBrace") {
976
+ while (this.peek().typ !== 'RBrace') {
999
977
  const left = this.parseTerm();
1000
- if (this.peek().typ === "OpImplies") {
978
+ if (this.peek().typ === 'OpImplies') {
1001
979
  this.next();
1002
980
  const right = this.parseTerm();
1003
- const pred = new Iri(LOG_NS + "implies");
981
+ const pred = new Iri(LOG_NS + 'implies');
1004
982
  triples.push(new Triple(left, pred, right));
1005
- if (this.peek().typ === "Dot") this.next();
1006
- else if (this.peek().typ === "RBrace") {
983
+ if (this.peek().typ === 'Dot') this.next();
984
+ else if (this.peek().typ === 'RBrace') {
1007
985
  // ok
1008
986
  } else {
1009
987
  throw new Error(`Expected '.' or '}', got ${this.peek().toString()}`);
1010
988
  }
1011
- } else if (this.peek().typ === "OpImpliedBy") {
989
+ } else if (this.peek().typ === 'OpImpliedBy') {
1012
990
  this.next();
1013
991
  const right = this.parseTerm();
1014
- const pred = new Iri(LOG_NS + "impliedBy");
992
+ const pred = new Iri(LOG_NS + 'impliedBy');
1015
993
  triples.push(new Triple(left, pred, right));
1016
- if (this.peek().typ === "Dot") this.next();
1017
- else if (this.peek().typ === "RBrace") {
994
+ if (this.peek().typ === 'Dot') this.next();
995
+ else if (this.peek().typ === 'RBrace') {
1018
996
  // ok
1019
997
  } else {
1020
998
  throw new Error(`Expected '.' or '}', got ${this.peek().toString()}`);
1021
999
  }
1022
1000
  } else {
1023
1001
  // Allow a bare blank-node property list statement inside a formula, e.g. `{ [ a :X ]. }`
1024
- if (this.peek().typ === "Dot" || this.peek().typ === "RBrace") {
1002
+ if (this.peek().typ === 'Dot' || this.peek().typ === 'RBrace') {
1025
1003
  const lastTok = this.toks[this.pos - 1];
1026
- if (this.pendingTriples.length > 0 && lastTok && lastTok.typ === "RBracket") {
1004
+ if (this.pendingTriples.length > 0 && lastTok && lastTok.typ === 'RBracket') {
1027
1005
  triples.push(...this.pendingTriples);
1028
1006
  this.pendingTriples = [];
1029
- if (this.peek().typ === "Dot") this.next();
1007
+ if (this.peek().typ === 'Dot') this.next();
1030
1008
  continue;
1031
1009
  }
1032
1010
  }
1033
1011
 
1034
1012
  triples.push(...this.parsePredicateObjectList(left));
1035
- if (this.peek().typ === "Dot") this.next();
1036
- else if (this.peek().typ === "RBrace") {
1013
+ if (this.peek().typ === 'Dot') this.next();
1014
+ else if (this.peek().typ === 'RBrace') {
1037
1015
  // ok
1038
1016
  } else {
1039
1017
  throw new Error(`Expected '.' or '}', got ${this.peek().toString()}`);
@@ -1057,10 +1035,10 @@ class Parser {
1057
1035
  let verb;
1058
1036
  let invert = false;
1059
1037
 
1060
- if (this.peek().typ === "Ident" && (this.peek().value || "") === "a") {
1038
+ if (this.peek().typ === 'Ident' && (this.peek().value || '') === 'a') {
1061
1039
  this.next();
1062
- verb = new Iri(RDF_NS + "type");
1063
- } else if (this.peek().typ === "OpPredInvert") {
1040
+ verb = new Iri(RDF_NS + 'type');
1041
+ } else if (this.peek().typ === 'OpPredInvert') {
1064
1042
  this.next(); // "<-"
1065
1043
  verb = this.parseTerm();
1066
1044
  invert = true;
@@ -1081,9 +1059,9 @@ class Parser {
1081
1059
  out.push(new Triple(invert ? o : subject, verb, invert ? subject : o));
1082
1060
  }
1083
1061
 
1084
- if (this.peek().typ === "Semicolon") {
1062
+ if (this.peek().typ === 'Semicolon') {
1085
1063
  this.next();
1086
- if (this.peek().typ === "Dot") break;
1064
+ if (this.peek().typ === 'Dot') break;
1087
1065
  continue;
1088
1066
  }
1089
1067
  break;
@@ -1094,7 +1072,7 @@ class Parser {
1094
1072
 
1095
1073
  parseObjectList() {
1096
1074
  const objs = [this.parseTerm()];
1097
- while (this.peek().typ === "Comma") {
1075
+ while (this.peek().typ === 'Comma') {
1098
1076
  this.next();
1099
1077
  objs.push(this.parseTerm());
1100
1078
  }
@@ -1114,7 +1092,7 @@ class Parser {
1114
1092
 
1115
1093
  let isFuse = false;
1116
1094
  if (isForward) {
1117
- if (conclTerm instanceof Literal && conclTerm.value === "false") {
1095
+ if (conclTerm instanceof Literal && conclTerm.value === 'false') {
1118
1096
  isFuse = true;
1119
1097
  }
1120
1098
  }
@@ -1122,7 +1100,7 @@ class Parser {
1122
1100
  let rawPremise;
1123
1101
  if (premiseTerm instanceof FormulaTerm) {
1124
1102
  rawPremise = premiseTerm.triples;
1125
- } else if (premiseTerm instanceof Literal && premiseTerm.value === "true") {
1103
+ } else if (premiseTerm instanceof Literal && premiseTerm.value === 'true') {
1126
1104
  rawPremise = [];
1127
1105
  } else {
1128
1106
  rawPremise = [];
@@ -1131,7 +1109,7 @@ class Parser {
1131
1109
  let rawConclusion;
1132
1110
  if (conclTerm instanceof FormulaTerm) {
1133
1111
  rawConclusion = conclTerm.triples;
1134
- } else if (conclTerm instanceof Literal && conclTerm.value === "false") {
1112
+ } else if (conclTerm instanceof Literal && conclTerm.value === 'false') {
1135
1113
  rawConclusion = [];
1136
1114
  } else {
1137
1115
  rawConclusion = [];
@@ -1164,21 +1142,17 @@ function liftBlankRuleVars(premise, conclusion) {
1164
1142
  return new Var(mapping[label]);
1165
1143
  }
1166
1144
  if (t instanceof ListTerm) {
1167
- return new ListTerm(t.elems.map(e => convertTerm(e, mapping, counter)));
1145
+ return new ListTerm(t.elems.map((e) => convertTerm(e, mapping, counter)));
1168
1146
  }
1169
1147
  if (t instanceof OpenListTerm) {
1170
1148
  return new OpenListTerm(
1171
- t.prefix.map(e => convertTerm(e, mapping, counter)),
1172
- t.tailVar
1149
+ t.prefix.map((e) => convertTerm(e, mapping, counter)),
1150
+ t.tailVar,
1173
1151
  );
1174
1152
  }
1175
1153
  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
- )
1154
+ const triples = t.triples.map(
1155
+ (tr) => new Triple(convertTerm(tr.s, mapping, counter), convertTerm(tr.p, mapping, counter), convertTerm(tr.o, mapping, counter)),
1182
1156
  );
1183
1157
  return new FormulaTerm(triples);
1184
1158
  }
@@ -1186,16 +1160,12 @@ function liftBlankRuleVars(premise, conclusion) {
1186
1160
  }
1187
1161
 
1188
1162
  function convertTriple(tr, mapping, counter) {
1189
- return new Triple(
1190
- convertTerm(tr.s, mapping, counter),
1191
- convertTerm(tr.p, mapping, counter),
1192
- convertTerm(tr.o, mapping, counter)
1193
- );
1163
+ return new Triple(convertTerm(tr.s, mapping, counter), convertTerm(tr.p, mapping, counter), convertTerm(tr.o, mapping, counter));
1194
1164
  }
1195
1165
 
1196
1166
  const mapping = {};
1197
1167
  const counter = [0];
1198
- const newPremise = premise.map(tr => convertTriple(tr, mapping, counter));
1168
+ const newPremise = premise.map((tr) => convertTriple(tr, mapping, counter));
1199
1169
  return [newPremise, conclusion];
1200
1170
  }
1201
1171
 
@@ -1215,24 +1185,18 @@ function skolemizeTermForHeadBlanks(t, headBlankLabels, mapping, skCounter) {
1215
1185
  }
1216
1186
 
1217
1187
  if (t instanceof ListTerm) {
1218
- return new ListTerm(
1219
- t.elems.map(e => skolemizeTermForHeadBlanks(e, headBlankLabels, mapping, skCounter))
1220
- );
1188
+ return new ListTerm(t.elems.map((e) => skolemizeTermForHeadBlanks(e, headBlankLabels, mapping, skCounter)));
1221
1189
  }
1222
1190
 
1223
1191
  if (t instanceof OpenListTerm) {
1224
1192
  return new OpenListTerm(
1225
- t.prefix.map(e => skolemizeTermForHeadBlanks(e, headBlankLabels, mapping, skCounter)),
1226
- t.tailVar
1193
+ t.prefix.map((e) => skolemizeTermForHeadBlanks(e, headBlankLabels, mapping, skCounter)),
1194
+ t.tailVar,
1227
1195
  );
1228
1196
  }
1229
1197
 
1230
1198
  if (t instanceof FormulaTerm) {
1231
- return new FormulaTerm(
1232
- t.triples.map(tr =>
1233
- skolemizeTripleForHeadBlanks(tr, headBlankLabels, mapping, skCounter)
1234
- )
1235
- );
1199
+ return new FormulaTerm(t.triples.map((tr) => skolemizeTripleForHeadBlanks(tr, headBlankLabels, mapping, skCounter)));
1236
1200
  }
1237
1201
 
1238
1202
  return t;
@@ -1242,7 +1206,7 @@ function skolemizeTripleForHeadBlanks(tr, headBlankLabels, mapping, skCounter) {
1242
1206
  return new Triple(
1243
1207
  skolemizeTermForHeadBlanks(tr.s, headBlankLabels, mapping, skCounter),
1244
1208
  skolemizeTermForHeadBlanks(tr.p, headBlankLabels, mapping, skCounter),
1245
- skolemizeTermForHeadBlanks(tr.o, headBlankLabels, mapping, skCounter)
1209
+ skolemizeTermForHeadBlanks(tr.o, headBlankLabels, mapping, skCounter),
1246
1210
  );
1247
1211
  }
1248
1212
 
@@ -1280,9 +1244,7 @@ function termsEqual(a, b) {
1280
1244
  }
1281
1245
 
1282
1246
  function triplesEqual(a, b) {
1283
- return (
1284
- termsEqual(a.s, b.s) && termsEqual(a.p, b.p) && termsEqual(a.o, b.o)
1285
- );
1247
+ return termsEqual(a.s, b.s) && termsEqual(a.p, b.p) && termsEqual(a.o, b.o);
1286
1248
  }
1287
1249
 
1288
1250
  function triplesListEqual(xs, ys) {
@@ -1346,9 +1308,7 @@ function alphaEqTermInFormula(a, b, vmap, bmap) {
1346
1308
 
1347
1309
  function alphaEqTripleInFormula(a, b, vmap, bmap) {
1348
1310
  return (
1349
- alphaEqTermInFormula(a.s, b.s, vmap, bmap) &&
1350
- alphaEqTermInFormula(a.p, b.p, vmap, bmap) &&
1351
- alphaEqTermInFormula(a.o, b.o, vmap, bmap)
1311
+ alphaEqTermInFormula(a.s, b.s, vmap, bmap) && alphaEqTermInFormula(a.p, b.p, vmap, bmap) && alphaEqTermInFormula(a.o, b.o, vmap, bmap)
1352
1312
  );
1353
1313
  }
1354
1314
 
@@ -1405,8 +1365,7 @@ function alphaEqTerm(a, b, bmap) {
1405
1365
  return true;
1406
1366
  }
1407
1367
  if (a instanceof OpenListTerm && b instanceof OpenListTerm) {
1408
- if (a.tailVar !== b.tailVar || a.prefix.length !== b.prefix.length)
1409
- return false;
1368
+ if (a.tailVar !== b.tailVar || a.prefix.length !== b.prefix.length) return false;
1410
1369
  for (let i = 0; i < a.prefix.length; i++) {
1411
1370
  if (!alphaEqTerm(a.prefix[i], b.prefix[i], bmap)) return false;
1412
1371
  }
@@ -1421,15 +1380,11 @@ function alphaEqTerm(a, b, bmap) {
1421
1380
 
1422
1381
  function alphaEqTriple(a, b) {
1423
1382
  const bmap = {};
1424
- return (
1425
- alphaEqTerm(a.s, b.s, bmap) &&
1426
- alphaEqTerm(a.p, b.p, bmap) &&
1427
- alphaEqTerm(a.o, b.o, bmap)
1428
- );
1383
+ return alphaEqTerm(a.s, b.s, bmap) && alphaEqTerm(a.p, b.p, bmap) && alphaEqTerm(a.o, b.o, bmap);
1429
1384
  }
1430
1385
 
1431
1386
  function hasAlphaEquiv(triples, tr) {
1432
- return triples.some(t => alphaEqTriple(t, tr));
1387
+ return triples.some((t) => alphaEqTriple(t, tr));
1433
1388
  }
1434
1389
 
1435
1390
  // ============================================================================
@@ -1446,8 +1401,8 @@ function hasAlphaEquiv(triples, tr) {
1446
1401
  // - __wildHeadPred: Rule[] (non-IRI head predicate)
1447
1402
 
1448
1403
  function termFastKey(t) {
1449
- if (t instanceof Iri) return "I:" + t.value;
1450
- if (t instanceof Literal) return "L:" + t.value;
1404
+ if (t instanceof Iri) return 'I:' + t.value;
1405
+ if (t instanceof Literal) return 'L:' + t.value;
1451
1406
  return null;
1452
1407
  }
1453
1408
 
@@ -1456,15 +1411,27 @@ function tripleFastKey(tr) {
1456
1411
  const kp = termFastKey(tr.p);
1457
1412
  const ko = termFastKey(tr.o);
1458
1413
  if (ks === null || kp === null || ko === null) return null;
1459
- return ks + "\t" + kp + "\t" + ko;
1414
+ return ks + '\t' + kp + '\t' + ko;
1460
1415
  }
1461
1416
 
1462
1417
  function ensureFactIndexes(facts) {
1463
1418
  if (facts.__byPred && facts.__byPO && facts.__keySet) return;
1464
1419
 
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 });
1420
+ Object.defineProperty(facts, '__byPred', {
1421
+ value: new Map(),
1422
+ enumerable: false,
1423
+ writable: true,
1424
+ });
1425
+ Object.defineProperty(facts, '__byPO', {
1426
+ value: new Map(),
1427
+ enumerable: false,
1428
+ writable: true,
1429
+ });
1430
+ Object.defineProperty(facts, '__keySet', {
1431
+ value: new Set(),
1432
+ enumerable: false,
1433
+ writable: true,
1434
+ });
1468
1435
 
1469
1436
  for (const f of facts) indexFact(facts, f);
1470
1437
  }
@@ -1474,15 +1441,24 @@ function indexFact(facts, tr) {
1474
1441
  const pk = tr.p.value;
1475
1442
 
1476
1443
  let pb = facts.__byPred.get(pk);
1477
- if (!pb) { pb = []; facts.__byPred.set(pk, pb); }
1444
+ if (!pb) {
1445
+ pb = [];
1446
+ facts.__byPred.set(pk, pb);
1447
+ }
1478
1448
  pb.push(tr);
1479
1449
 
1480
1450
  const ok = termFastKey(tr.o);
1481
1451
  if (ok !== null) {
1482
1452
  let po = facts.__byPO.get(pk);
1483
- if (!po) { po = new Map(); facts.__byPO.set(pk, po); }
1453
+ if (!po) {
1454
+ po = new Map();
1455
+ facts.__byPO.set(pk, po);
1456
+ }
1484
1457
  let pob = po.get(ok);
1485
- if (!pob) { pob = []; po.set(ok, pob); }
1458
+ if (!pob) {
1459
+ pob = [];
1460
+ po.set(ok, pob);
1461
+ }
1486
1462
  pob.push(tr);
1487
1463
  }
1488
1464
  }
@@ -1526,12 +1502,12 @@ function hasFactIndexed(facts, tr) {
1526
1502
  const po = facts.__byPO.get(pk);
1527
1503
  if (po) {
1528
1504
  const pob = po.get(ok) || [];
1529
- return pob.some(t => alphaEqTriple(t, tr));
1505
+ return pob.some((t) => alphaEqTriple(t, tr));
1530
1506
  }
1531
1507
  }
1532
1508
 
1533
1509
  const pb = facts.__byPred.get(pk) || [];
1534
- return pb.some(t => alphaEqTriple(t, tr));
1510
+ return pb.some((t) => alphaEqTriple(t, tr));
1535
1511
  }
1536
1512
 
1537
1513
  return hasAlphaEquiv(facts, tr);
@@ -1546,8 +1522,16 @@ function pushFactIndexed(facts, tr) {
1546
1522
  function ensureBackRuleIndexes(backRules) {
1547
1523
  if (backRules.__byHeadPred && backRules.__wildHeadPred) return;
1548
1524
 
1549
- Object.defineProperty(backRules, "__byHeadPred", { value: new Map(), enumerable: false, writable: true });
1550
- Object.defineProperty(backRules, "__wildHeadPred", { value: [], enumerable: false, writable: true });
1525
+ Object.defineProperty(backRules, '__byHeadPred', {
1526
+ value: new Map(),
1527
+ enumerable: false,
1528
+ writable: true,
1529
+ });
1530
+ Object.defineProperty(backRules, '__wildHeadPred', {
1531
+ value: [],
1532
+ enumerable: false,
1533
+ writable: true,
1534
+ });
1551
1535
 
1552
1536
  for (const r of backRules) indexBackRule(backRules, r);
1553
1537
  }
@@ -1558,7 +1542,10 @@ function indexBackRule(backRules, r) {
1558
1542
  if (head && head.p instanceof Iri) {
1559
1543
  const k = head.p.value;
1560
1544
  let bucket = backRules.__byHeadPred.get(k);
1561
- if (!bucket) { bucket = []; backRules.__byHeadPred.set(k, bucket); }
1545
+ if (!bucket) {
1546
+ bucket = [];
1547
+ backRules.__byHeadPred.set(k, bucket);
1548
+ }
1562
1549
  bucket.push(r);
1563
1550
  } else {
1564
1551
  backRules.__wildHeadPred.push(r);
@@ -1570,19 +1557,19 @@ function indexBackRule(backRules, r) {
1570
1557
  // ============================================================================
1571
1558
 
1572
1559
  function isRdfTypePred(p) {
1573
- return p instanceof Iri && p.value === RDF_NS + "type";
1560
+ return p instanceof Iri && p.value === RDF_NS + 'type';
1574
1561
  }
1575
1562
 
1576
1563
  function isOwlSameAsPred(t) {
1577
- return t instanceof Iri && t.value === (OWL_NS + "sameAs");
1564
+ return t instanceof Iri && t.value === OWL_NS + 'sameAs';
1578
1565
  }
1579
1566
 
1580
1567
  function isLogImplies(p) {
1581
- return p instanceof Iri && p.value === LOG_NS + "implies";
1568
+ return p instanceof Iri && p.value === LOG_NS + 'implies';
1582
1569
  }
1583
1570
 
1584
1571
  function isLogImpliedBy(p) {
1585
- return p instanceof Iri && p.value === LOG_NS + "impliedBy";
1572
+ return p instanceof Iri && p.value === LOG_NS + 'impliedBy';
1586
1573
  }
1587
1574
 
1588
1575
  // ============================================================================
@@ -1595,44 +1582,40 @@ function isConstraintBuiltin(tr) {
1595
1582
 
1596
1583
  // math: numeric comparisons (no new bindings, just tests)
1597
1584
  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" ||
1603
- v === MATH_NS + "notLessThan"
1585
+ v === MATH_NS + 'equalTo' ||
1586
+ v === MATH_NS + 'greaterThan' ||
1587
+ v === MATH_NS + 'lessThan' ||
1588
+ v === MATH_NS + 'notEqualTo' ||
1589
+ v === MATH_NS + 'notGreaterThan' ||
1590
+ v === MATH_NS + 'notLessThan'
1604
1591
  ) {
1605
1592
  return true;
1606
1593
  }
1607
1594
 
1608
1595
  // list: membership test with no bindings
1609
- if (v === LIST_NS + "notMember") {
1596
+ if (v === LIST_NS + 'notMember') {
1610
1597
  return true;
1611
1598
  }
1612
1599
 
1613
1600
  // log: tests that are purely constraints (no new bindings)
1614
- if (
1615
- v === LOG_NS + "forAllIn" ||
1616
- v === LOG_NS + "notEqualTo" ||
1617
- v === LOG_NS + "notIncludes"
1618
- ) {
1601
+ if (v === LOG_NS + 'forAllIn' || v === LOG_NS + 'notEqualTo' || v === LOG_NS + 'notIncludes') {
1619
1602
  return true;
1620
1603
  }
1621
1604
 
1622
1605
  // string: relational / membership style tests (no bindings)
1623
1606
  if (
1624
- v === STRING_NS + "contains" ||
1625
- 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" ||
1631
- v === STRING_NS + "notEqualIgnoringCase" ||
1632
- v === STRING_NS + "notGreaterThan" ||
1633
- v === STRING_NS + "notLessThan" ||
1634
- v === STRING_NS + "notMatches" ||
1635
- v === STRING_NS + "startsWith"
1607
+ v === STRING_NS + 'contains' ||
1608
+ v === STRING_NS + 'containsIgnoringCase' ||
1609
+ v === STRING_NS + 'endsWith' ||
1610
+ v === STRING_NS + 'equalIgnoringCase' ||
1611
+ v === STRING_NS + 'greaterThan' ||
1612
+ v === STRING_NS + 'lessThan' ||
1613
+ v === STRING_NS + 'matches' ||
1614
+ v === STRING_NS + 'notEqualIgnoringCase' ||
1615
+ v === STRING_NS + 'notGreaterThan' ||
1616
+ v === STRING_NS + 'notLessThan' ||
1617
+ v === STRING_NS + 'notMatches' ||
1618
+ v === STRING_NS + 'startsWith'
1636
1619
  ) {
1637
1620
  return true;
1638
1621
  }
@@ -1665,18 +1648,10 @@ function reorderPremiseForConstraints(premise) {
1665
1648
 
1666
1649
  function containsVarTerm(t, v) {
1667
1650
  if (t instanceof Var) return t.name === v;
1668
- if (t instanceof ListTerm) return t.elems.some(e => containsVarTerm(e, v));
1669
- if (t instanceof OpenListTerm)
1670
- return (
1671
- t.prefix.some(e => containsVarTerm(e, v)) || t.tailVar === v
1672
- );
1651
+ if (t instanceof ListTerm) return t.elems.some((e) => containsVarTerm(e, v));
1652
+ if (t instanceof OpenListTerm) return t.prefix.some((e) => containsVarTerm(e, v)) || t.tailVar === v;
1673
1653
  if (t instanceof FormulaTerm)
1674
- return t.triples.some(
1675
- tr =>
1676
- containsVarTerm(tr.s, v) ||
1677
- containsVarTerm(tr.p, v) ||
1678
- containsVarTerm(tr.o, v)
1679
- );
1654
+ return t.triples.some((tr) => containsVarTerm(tr.s, v) || containsVarTerm(tr.p, v) || containsVarTerm(tr.o, v));
1680
1655
  return false;
1681
1656
  }
1682
1657
 
@@ -1684,25 +1659,21 @@ function isGroundTermInFormula(t) {
1684
1659
  // EYE-style: variables inside formula terms are treated as local placeholders,
1685
1660
  // so they don't make the *surrounding triple* non-ground.
1686
1661
  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));
1662
+ if (t instanceof ListTerm) return t.elems.every((e) => isGroundTermInFormula(e));
1663
+ if (t instanceof FormulaTerm) return t.triples.every((tr) => isGroundTripleInFormula(tr));
1689
1664
  // Iri/Literal/Blank/Var are all OK inside formulas
1690
1665
  return true;
1691
1666
  }
1692
1667
 
1693
1668
  function isGroundTripleInFormula(tr) {
1694
- return (
1695
- isGroundTermInFormula(tr.s) &&
1696
- isGroundTermInFormula(tr.p) &&
1697
- isGroundTermInFormula(tr.o)
1698
- );
1669
+ return isGroundTermInFormula(tr.s) && isGroundTermInFormula(tr.p) && isGroundTermInFormula(tr.o);
1699
1670
  }
1700
1671
 
1701
1672
  function isGroundTerm(t) {
1702
1673
  if (t instanceof Var) return false;
1703
- if (t instanceof ListTerm) return t.elems.every(e => isGroundTerm(e));
1674
+ if (t instanceof ListTerm) return t.elems.every((e) => isGroundTerm(e));
1704
1675
  if (t instanceof OpenListTerm) return false;
1705
- if (t instanceof FormulaTerm) return t.triples.every(tr => isGroundTripleInFormula(tr));
1676
+ if (t instanceof FormulaTerm) return t.triples.every((tr) => isGroundTripleInFormula(tr));
1706
1677
  return true;
1707
1678
  }
1708
1679
 
@@ -1715,23 +1686,14 @@ function isGroundTriple(tr) {
1715
1686
  // robust to seeing vars/open lists anyway.
1716
1687
  function skolemKeyFromTerm(t) {
1717
1688
  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];
1722
- if (u instanceof ListTerm) return ["List", u.elems.map(enc)];
1723
- if (u instanceof OpenListTerm)
1724
- return ["OpenList", u.prefix.map(enc), u.tailVar];
1725
- if (u instanceof FormulaTerm)
1726
- return [
1727
- "Formula",
1728
- u.triples.map(tr => [
1729
- enc(tr.s),
1730
- enc(tr.p),
1731
- enc(tr.o)
1732
- ])
1733
- ];
1734
- return ["Other", String(u)];
1689
+ if (u instanceof Iri) return ['I', u.value];
1690
+ if (u instanceof Literal) return ['L', u.value];
1691
+ if (u instanceof Blank) return ['B', u.label];
1692
+ if (u instanceof Var) return ['V', u.name];
1693
+ if (u instanceof ListTerm) return ['List', u.elems.map(enc)];
1694
+ if (u instanceof OpenListTerm) return ['OpenList', u.prefix.map(enc), u.tailVar];
1695
+ if (u instanceof FormulaTerm) return ['Formula', u.triples.map((tr) => [enc(tr.s), enc(tr.p), enc(tr.o)])];
1696
+ return ['Other', String(u)];
1735
1697
  }
1736
1698
  return JSON.stringify(enc(t));
1737
1699
  }
@@ -1768,21 +1730,18 @@ function applySubstTerm(t, s) {
1768
1730
 
1769
1731
  // Non-variable terms
1770
1732
  if (t instanceof ListTerm) {
1771
- return new ListTerm(t.elems.map(e => applySubstTerm(e, s)));
1733
+ return new ListTerm(t.elems.map((e) => applySubstTerm(e, s)));
1772
1734
  }
1773
1735
 
1774
1736
  if (t instanceof OpenListTerm) {
1775
- const newPrefix = t.prefix.map(e => applySubstTerm(e, s));
1737
+ const newPrefix = t.prefix.map((e) => applySubstTerm(e, s));
1776
1738
  const tailTerm = s[t.tailVar];
1777
1739
  if (tailTerm !== undefined) {
1778
1740
  const tailApplied = applySubstTerm(tailTerm, s);
1779
1741
  if (tailApplied instanceof ListTerm) {
1780
1742
  return new ListTerm(newPrefix.concat(tailApplied.elems));
1781
1743
  } else if (tailApplied instanceof OpenListTerm) {
1782
- return new OpenListTerm(
1783
- newPrefix.concat(tailApplied.prefix),
1784
- tailApplied.tailVar
1785
- );
1744
+ return new OpenListTerm(newPrefix.concat(tailApplied.prefix), tailApplied.tailVar);
1786
1745
  } else {
1787
1746
  return new OpenListTerm(newPrefix, t.tailVar);
1788
1747
  }
@@ -1792,18 +1751,14 @@ function applySubstTerm(t, s) {
1792
1751
  }
1793
1752
 
1794
1753
  if (t instanceof FormulaTerm) {
1795
- return new FormulaTerm(t.triples.map(tr => applySubstTriple(tr, s)));
1754
+ return new FormulaTerm(t.triples.map((tr) => applySubstTriple(tr, s)));
1796
1755
  }
1797
1756
 
1798
1757
  return t;
1799
1758
  }
1800
1759
 
1801
1760
  function applySubstTriple(tr, s) {
1802
- return new Triple(
1803
- applySubstTerm(tr.s, s),
1804
- applySubstTerm(tr.p, s),
1805
- applySubstTerm(tr.o, s)
1806
- );
1761
+ return new Triple(applySubstTerm(tr.s, s), applySubstTerm(tr.p, s), applySubstTerm(tr.o, s));
1807
1762
  }
1808
1763
 
1809
1764
  function unifyOpenWithList(prefix, tailv, ys, subst) {
@@ -1839,7 +1794,7 @@ function unifyFormulaTriples(xs, ys, subst) {
1839
1794
  // Cheap pruning when both predicates are IRIs.
1840
1795
  if (x.p instanceof Iri && y.p instanceof Iri && x.p.value !== y.p.value) continue;
1841
1796
 
1842
- const s2 = unifyTriple(x, y, s); // IMPORTANT: use `s`, not {}
1797
+ const s2 = unifyTriple(x, y, s); // IMPORTANT: use `s`, not {}
1843
1798
  if (s2 === null) continue;
1844
1799
 
1845
1800
  used[j] = true;
@@ -1873,12 +1828,9 @@ function unifyTerm(a, b, subst) {
1873
1828
  }
1874
1829
 
1875
1830
  // Exact matches
1876
- if (a instanceof Iri && b instanceof Iri && a.value === b.value)
1877
- return { ...subst };
1878
- if (a instanceof Literal && b instanceof Literal && a.value === b.value)
1879
- return { ...subst };
1880
- if (a instanceof Blank && b instanceof Blank && a.label === b.label)
1881
- return { ...subst };
1831
+ if (a instanceof Iri && b instanceof Iri && a.value === b.value) return { ...subst };
1832
+ if (a instanceof Literal && b instanceof Literal && a.value === b.value) return { ...subst };
1833
+ if (a instanceof Blank && b instanceof Blank && a.label === b.label) return { ...subst };
1882
1834
 
1883
1835
  // Open list vs concrete list
1884
1836
  if (a instanceof OpenListTerm && b instanceof ListTerm) {
@@ -1890,8 +1842,7 @@ function unifyTerm(a, b, subst) {
1890
1842
 
1891
1843
  // Open list vs open list (same tail var)
1892
1844
  if (a instanceof OpenListTerm && b instanceof OpenListTerm) {
1893
- if (a.tailVar !== b.tailVar || a.prefix.length !== b.prefix.length)
1894
- return null;
1845
+ if (a.tailVar !== b.tailVar || a.prefix.length !== b.prefix.length) return null;
1895
1846
  let s2 = { ...subst };
1896
1847
  for (let i = 0; i < a.prefix.length; i++) {
1897
1848
  s2 = unifyTerm(a.prefix[i], b.prefix[i], s2);
@@ -1957,14 +1908,14 @@ function literalParts(lit) {
1957
1908
  // Also strip an optional language tag from the lexical form:
1958
1909
  // "\"hello\"@en" -> "\"hello\""
1959
1910
  // "\"hello\"@en^^<...>" is rejected earlier in the parser.
1960
- const idx = lit.indexOf("^^");
1911
+ const idx = lit.indexOf('^^');
1961
1912
  let lex = lit;
1962
1913
  let dt = null;
1963
1914
 
1964
1915
  if (idx >= 0) {
1965
1916
  lex = lit.slice(0, idx);
1966
1917
  dt = lit.slice(idx + 2).trim();
1967
- if (dt.startsWith("<") && dt.endsWith(">")) {
1918
+ if (dt.startsWith('<') && dt.endsWith('>')) {
1968
1919
  dt = dt.slice(1, -1);
1969
1920
  }
1970
1921
  }
@@ -1972,7 +1923,7 @@ function literalParts(lit) {
1972
1923
  // Strip LANGTAG from the lexical form when present.
1973
1924
  if (lex.length >= 2 && lex[0] === '"') {
1974
1925
  const lastQuote = lex.lastIndexOf('"');
1975
- if (lastQuote > 0 && lastQuote < lex.length - 1 && lex[lastQuote + 1] === "@") {
1926
+ if (lastQuote > 0 && lastQuote < lex.length - 1 && lex[lastQuote + 1] === '@') {
1976
1927
  const lang = lex.slice(lastQuote + 2);
1977
1928
  if (/^[A-Za-z]+(?:-[A-Za-z0-9]+)*$/.test(lang)) {
1978
1929
  lex = lex.slice(0, lastQuote + 1);
@@ -2019,7 +1970,11 @@ function termToJsStringDecoded(t) {
2019
1970
 
2020
1971
  // Short strings: try to decode escapes (this makes "{\"a\":1}" usable too).
2021
1972
  if (lex.length >= 2 && lex[0] === '"' && lex[lex.length - 1] === '"') {
2022
- try { return JSON.parse(lex); } catch (e) { /* fall through */ }
1973
+ try {
1974
+ return JSON.parse(lex);
1975
+ } catch (e) {
1976
+ /* fall through */
1977
+ }
2023
1978
  return stripQuotes(lex);
2024
1979
  }
2025
1980
 
@@ -2028,14 +1983,17 @@ function termToJsStringDecoded(t) {
2028
1983
 
2029
1984
  function jsonPointerUnescape(seg) {
2030
1985
  // RFC6901: ~1 -> '/', ~0 -> '~'
2031
- let out = "";
1986
+ let out = '';
2032
1987
  for (let i = 0; i < seg.length; i++) {
2033
1988
  const c = seg[i];
2034
- if (c !== "~") { out += c; continue; }
1989
+ if (c !== '~') {
1990
+ out += c;
1991
+ continue;
1992
+ }
2035
1993
  if (i + 1 >= seg.length) return null;
2036
1994
  const n = seg[i + 1];
2037
- if (n === "0") out += "~";
2038
- else if (n === "1") out += "/";
1995
+ if (n === '0') out += '~';
1996
+ else if (n === '1') out += '/';
2039
1997
  else return null;
2040
1998
  i++;
2041
1999
  }
@@ -2043,13 +2001,13 @@ function jsonPointerUnescape(seg) {
2043
2001
  }
2044
2002
 
2045
2003
  function jsonToTerm(v) {
2046
- if (v === null) return makeStringLiteral("null");
2047
- if (typeof v === "string") return makeStringLiteral(v);
2048
- if (typeof v === "number") return new Literal(String(v));
2049
- if (typeof v === "boolean") return new Literal(v ? "true" : "false");
2004
+ if (v === null) return makeStringLiteral('null');
2005
+ if (typeof v === 'string') return makeStringLiteral(v);
2006
+ if (typeof v === 'number') return new Literal(String(v));
2007
+ if (typeof v === 'boolean') return new Literal(v ? 'true' : 'false');
2050
2008
  if (Array.isArray(v)) return new ListTerm(v.map(jsonToTerm));
2051
2009
 
2052
- if (v && typeof v === "object") {
2010
+ if (v && typeof v === 'object') {
2053
2011
  return makeRdfJsonLiteral(JSON.stringify(v));
2054
2012
  }
2055
2013
  return null;
@@ -2059,14 +2017,22 @@ function jsonPointerLookup(jsonText, pointer) {
2059
2017
  let ptr = pointer;
2060
2018
 
2061
2019
  // Support URI fragment form "#/a/b"
2062
- if (ptr.startsWith("#")) {
2063
- try { ptr = decodeURIComponent(ptr.slice(1)); } catch { return null; }
2020
+ if (ptr.startsWith('#')) {
2021
+ try {
2022
+ ptr = decodeURIComponent(ptr.slice(1));
2023
+ } catch {
2024
+ return null;
2025
+ }
2064
2026
  }
2065
2027
 
2066
2028
  let entry = jsonPointerCache.get(jsonText);
2067
2029
  if (!entry) {
2068
2030
  let parsed = null;
2069
- try { parsed = JSON.parse(jsonText); } catch { parsed = null; }
2031
+ try {
2032
+ parsed = JSON.parse(jsonText);
2033
+ } catch {
2034
+ parsed = null;
2035
+ }
2070
2036
  entry = { parsed, ptrCache: new Map() };
2071
2037
  jsonPointerCache.set(jsonText, entry);
2072
2038
  }
@@ -2076,25 +2042,40 @@ function jsonPointerLookup(jsonText, pointer) {
2076
2042
 
2077
2043
  let cur = entry.parsed;
2078
2044
 
2079
- if (ptr === "") {
2045
+ if (ptr === '') {
2080
2046
  const t = jsonToTerm(cur);
2081
2047
  entry.ptrCache.set(ptr, t);
2082
2048
  return t;
2083
2049
  }
2084
- if (!ptr.startsWith("/")) { entry.ptrCache.set(ptr, null); return null; }
2050
+ if (!ptr.startsWith('/')) {
2051
+ entry.ptrCache.set(ptr, null);
2052
+ return null;
2053
+ }
2085
2054
 
2086
- const parts = ptr.split("/").slice(1);
2055
+ const parts = ptr.split('/').slice(1);
2087
2056
  for (const raw of parts) {
2088
2057
  const seg = jsonPointerUnescape(raw);
2089
- if (seg === null) { entry.ptrCache.set(ptr, null); return null; }
2058
+ if (seg === null) {
2059
+ entry.ptrCache.set(ptr, null);
2060
+ return null;
2061
+ }
2090
2062
 
2091
2063
  if (Array.isArray(cur)) {
2092
- if (!/^(0|[1-9]\d*)$/.test(seg)) { entry.ptrCache.set(ptr, null); return null; }
2064
+ if (!/^(0|[1-9]\d*)$/.test(seg)) {
2065
+ entry.ptrCache.set(ptr, null);
2066
+ return null;
2067
+ }
2093
2068
  const idx = Number(seg);
2094
- if (idx < 0 || idx >= cur.length) { entry.ptrCache.set(ptr, null); return null; }
2069
+ if (idx < 0 || idx >= cur.length) {
2070
+ entry.ptrCache.set(ptr, null);
2071
+ return null;
2072
+ }
2095
2073
  cur = cur[idx];
2096
- } else if (cur !== null && typeof cur === "object") {
2097
- if (!Object.prototype.hasOwnProperty.call(cur, seg)) { entry.ptrCache.set(ptr, null); return null; }
2074
+ } else if (cur !== null && typeof cur === 'object') {
2075
+ if (!Object.prototype.hasOwnProperty.call(cur, seg)) {
2076
+ entry.ptrCache.set(ptr, null);
2077
+ return null;
2078
+ }
2098
2079
  cur = cur[seg];
2099
2080
  } else {
2100
2081
  entry.ptrCache.set(ptr, null);
@@ -2110,23 +2091,23 @@ function jsonPointerLookup(jsonText, pointer) {
2110
2091
  // Tiny subset of sprintf: supports only %s and %%.
2111
2092
  // Good enough for most N3 string:format use cases that just splice strings.
2112
2093
  function simpleStringFormat(fmt, args) {
2113
- let out = "";
2094
+ let out = '';
2114
2095
  let argIndex = 0;
2115
2096
 
2116
2097
  for (let i = 0; i < fmt.length; i++) {
2117
2098
  const ch = fmt[i];
2118
- if (ch === "%" && i + 1 < fmt.length) {
2099
+ if (ch === '%' && i + 1 < fmt.length) {
2119
2100
  const spec = fmt[i + 1];
2120
2101
 
2121
- if (spec === "s") {
2122
- const arg = argIndex < args.length ? args[argIndex++] : "";
2102
+ if (spec === 's') {
2103
+ const arg = argIndex < args.length ? args[argIndex++] : '';
2123
2104
  out += arg;
2124
2105
  i++;
2125
2106
  continue;
2126
2107
  }
2127
2108
 
2128
- if (spec === "%") {
2129
- out += "%";
2109
+ if (spec === '%') {
2110
+ out += '%';
2130
2111
  i++;
2131
2112
  continue;
2132
2113
  }
@@ -2140,33 +2121,118 @@ function simpleStringFormat(fmt, args) {
2140
2121
  return out;
2141
2122
  }
2142
2123
 
2124
+ // -----------------------------------------------------------------------------
2125
+ // Strict numeric literal parsing for math: builtins
2126
+ // -----------------------------------------------------------------------------
2127
+ const XSD_DECIMAL_DT = XSD_NS + 'decimal';
2128
+ const XSD_DOUBLE_DT = XSD_NS + 'double';
2129
+ const XSD_FLOAT_DT = XSD_NS + 'float';
2130
+ const XSD_INTEGER_DT = XSD_NS + 'integer';
2131
+
2132
+ // Integer-derived datatypes from XML Schema Part 2 (and commonly used ones).
2133
+ const XSD_INTEGER_DERIVED_DTS = new Set([
2134
+ XSD_INTEGER_DT,
2135
+ XSD_NS + 'nonPositiveInteger',
2136
+ XSD_NS + 'negativeInteger',
2137
+ XSD_NS + 'long',
2138
+ XSD_NS + 'int',
2139
+ XSD_NS + 'short',
2140
+ XSD_NS + 'byte',
2141
+ XSD_NS + 'nonNegativeInteger',
2142
+ XSD_NS + 'unsignedLong',
2143
+ XSD_NS + 'unsignedInt',
2144
+ XSD_NS + 'unsignedShort',
2145
+ XSD_NS + 'unsignedByte',
2146
+ XSD_NS + 'positiveInteger',
2147
+ ]);
2148
+
2149
+ function isQuotedLexical(lex) {
2150
+ // Note: the lexer stores long strings with literal delimiters: """..."""
2151
+ return (
2152
+ (lex.length >= 2 && lex[0] === '"' && lex[lex.length - 1] === '"') || (lex.length >= 6 && lex.startsWith('"""') && lex.endsWith('"""'))
2153
+ );
2154
+ }
2155
+
2156
+ function isXsdNumericDatatype(dt) {
2157
+ if (dt === null) return false;
2158
+ return dt === XSD_DECIMAL_DT || dt === XSD_DOUBLE_DT || dt === XSD_FLOAT_DT || XSD_INTEGER_DERIVED_DTS.has(dt);
2159
+ }
2160
+
2161
+ function isXsdIntegerDatatype(dt) {
2162
+ if (dt === null) return false;
2163
+ return XSD_INTEGER_DERIVED_DTS.has(dt);
2164
+ }
2165
+
2166
+ function looksLikeUntypedNumericTokenLex(lex) {
2167
+ // We only treat *unquoted* tokens as "untyped numeric" (Turtle/N3 numeric literal).
2168
+ // Quoted literals without datatype are strings, never numbers.
2169
+ if (isQuotedLexical(lex)) return false;
2170
+
2171
+ // integer
2172
+ if (/^[+-]?\d+$/.test(lex)) return true;
2173
+
2174
+ // decimal (no exponent)
2175
+ if (/^[+-]?(?:\d+\.\d*|\.\d+)$/.test(lex)) return true;
2176
+
2177
+ // double (with exponent)
2178
+ if (/^[+-]?(?:\d+\.\d*|\.\d+|\d+)(?:[eE][+-]?\d+)$/.test(lex)) return true;
2179
+
2180
+ return false;
2181
+ }
2182
+
2143
2183
  function parseNum(t) {
2144
- // Parse as JS Number (for floats, dates-as-seconds, etc.)
2184
+ // Parse as JS Number, but ONLY for xsd numeric datatypes or untyped numeric tokens.
2185
+ // Rejects values such as "1"^^<...non-numeric...> or "1" (a string literal).
2145
2186
  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;
2187
+
2188
+ const [lex, dt] = literalParts(t.value);
2189
+
2190
+ // Typed literals: must be xsd numeric.
2191
+ if (dt !== null) {
2192
+ if (!isXsdNumericDatatype(dt)) return null;
2193
+ const val = stripQuotes(lex);
2194
+ const n = Number(val);
2195
+ if (!Number.isFinite(n)) return null;
2196
+ return n;
2197
+ }
2198
+
2199
+ // Untyped literals: accept only unquoted numeric tokens.
2200
+ if (!looksLikeUntypedNumericTokenLex(lex)) return null;
2201
+ const n = Number(lex);
2202
+ if (!Number.isFinite(n)) return null;
2203
+ return n;
2152
2204
  }
2153
2205
 
2154
2206
  function parseIntLiteral(t) {
2155
- // Parse as BigInt if the lexical form is an integer
2207
+ // Parse as BigInt if (and only if) it is an integer literal in an integer datatype,
2208
+ // or an untyped integer token.
2156
2209
  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;
2210
+
2211
+ const [lex, dt] = literalParts(t.value);
2212
+
2213
+ if (dt !== null) {
2214
+ if (!isXsdIntegerDatatype(dt)) return null;
2215
+ const val = stripQuotes(lex);
2216
+ if (!/^[+-]?\d+$/.test(val)) return null;
2217
+ try {
2218
+ return BigInt(val);
2219
+ } catch {
2220
+ return null;
2221
+ }
2222
+ }
2223
+
2224
+ // Untyped: only accept unquoted integer tokens.
2225
+ if (isQuotedLexical(lex)) return null;
2226
+ if (!/^[+-]?\d+$/.test(lex)) return null;
2161
2227
  try {
2162
- return BigInt(val);
2163
- } catch (e) {
2228
+ return BigInt(lex);
2229
+ } catch {
2164
2230
  return null;
2165
2231
  }
2166
2232
  }
2167
2233
 
2168
2234
  function parseNumberLiteral(t) {
2169
- // Prefer BigInt for integers, fall back to Number for non-integers
2235
+ // Prefer BigInt for integers, fall back to Number for other numeric literals.
2170
2236
  const bi = parseIntLiteral(t);
2171
2237
  if (bi !== null) return bi;
2172
2238
  const n = parseNum(t);
@@ -2180,28 +2246,22 @@ function formatNum(n) {
2180
2246
 
2181
2247
  function parseXsdDateTerm(t) {
2182
2248
  if (!(t instanceof Literal)) return null;
2183
- const s = t.value;
2184
- let [lex, dt] = literalParts(s);
2249
+ const [lex, dt] = literalParts(t.value);
2250
+ if (dt !== XSD_NS + 'date') return null;
2185
2251
  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;
2252
+ const d = new Date(val + 'T00:00:00Z');
2253
+ if (Number.isNaN(d.getTime())) return null;
2254
+ return d;
2192
2255
  }
2193
2256
 
2194
2257
  function parseXsdDatetimeTerm(t) {
2195
2258
  if (!(t instanceof Literal)) return null;
2196
- const s = t.value;
2197
- let [lex, dt] = literalParts(s);
2259
+ const [lex, dt] = literalParts(t.value);
2260
+ if (dt !== XSD_NS + 'dateTime') return null;
2198
2261
  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;
2262
+ const d = new Date(val);
2263
+ if (Number.isNaN(d.getTime())) return null;
2264
+ return d; // Date in local/UTC, we only use timestamp
2205
2265
  }
2206
2266
 
2207
2267
  function parseDatetimeLike(t) {
@@ -2212,9 +2272,9 @@ function parseDatetimeLike(t) {
2212
2272
 
2213
2273
  function parseIso8601DurationToSeconds(s) {
2214
2274
  if (!s) return null;
2215
- if (s[0] !== "P") return null;
2275
+ if (s[0] !== 'P') return null;
2216
2276
  const it = s.slice(1);
2217
- let num = "";
2277
+ let num = '';
2218
2278
  let inTime = false;
2219
2279
  let years = 0,
2220
2280
  months = 0,
@@ -2225,7 +2285,7 @@ function parseIso8601DurationToSeconds(s) {
2225
2285
  seconds = 0;
2226
2286
 
2227
2287
  for (const c of it) {
2228
- if (c === "T") {
2288
+ if (c === 'T') {
2229
2289
  inTime = true;
2230
2290
  continue;
2231
2291
  }
@@ -2236,48 +2296,31 @@ function parseIso8601DurationToSeconds(s) {
2236
2296
  if (!num) return null;
2237
2297
  const val = Number(num);
2238
2298
  if (Number.isNaN(val)) return null;
2239
- num = "";
2240
- if (!inTime && c === "Y") years += val;
2241
- else if (!inTime && c === "M") months += val;
2242
- else if (!inTime && c === "W") weeks += val;
2243
- else if (!inTime && c === "D") days += val;
2244
- else if (inTime && c === "H") hours += val;
2245
- else if (inTime && c === "M") minutes += val;
2246
- else if (inTime && c === "S") seconds += val;
2299
+ num = '';
2300
+ if (!inTime && c === 'Y') years += val;
2301
+ else if (!inTime && c === 'M') months += val;
2302
+ else if (!inTime && c === 'W') weeks += val;
2303
+ else if (!inTime && c === 'D') days += val;
2304
+ else if (inTime && c === 'H') hours += val;
2305
+ else if (inTime && c === 'M') minutes += val;
2306
+ else if (inTime && c === 'S') seconds += val;
2247
2307
  else return null;
2248
2308
  }
2249
2309
 
2250
2310
  const totalDays =
2251
- years * 365.2425 +
2252
- months * 30.436875 +
2253
- weeks * 7.0 +
2254
- days +
2255
- hours / 24.0 +
2256
- minutes / (24.0 * 60.0) +
2257
- seconds / (24.0 * 3600.0);
2311
+ years * 365.2425 + months * 30.436875 + weeks * 7.0 + days + hours / 24.0 + minutes / (24.0 * 60.0) + seconds / (24.0 * 3600.0);
2258
2312
 
2259
2313
  return totalDays * 86400.0;
2260
2314
  }
2261
2315
 
2262
2316
  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 };
2317
+ // Strict: only accept xsd numeric literals, xsd:duration, xsd:date, xsd:dateTime
2318
+ // (or untyped numeric tokens).
2319
+ const bi = parseIntLiteral(t);
2320
+ if (bi !== null) return { kind: 'bigint', value: bi };
2321
+
2322
+ const nDur = parseNumOrDuration(t);
2323
+ if (nDur !== null) return { kind: 'number', value: nDur };
2281
2324
  return null;
2282
2325
  }
2283
2326
 
@@ -2285,47 +2328,47 @@ function cmpNumericInfo(aInfo, bInfo, op) {
2285
2328
  // op is one of ">", "<", ">=", "<="
2286
2329
  if (!aInfo || !bInfo) return false;
2287
2330
 
2288
- if (aInfo.kind === "bigint" && bInfo.kind === "bigint") {
2289
- if (op === ">") return aInfo.value > bInfo.value;
2290
- if (op === "<") return aInfo.value < bInfo.value;
2291
- if (op === ">=") return aInfo.value >= bInfo.value;
2292
- if (op === "<=") return aInfo.value <= bInfo.value;
2293
- if (op === "==") return aInfo.value == bInfo.value;
2294
- if (op === "!=") return aInfo.value != bInfo.value;
2331
+ if (aInfo.kind === 'bigint' && bInfo.kind === 'bigint') {
2332
+ if (op === '>') return aInfo.value > bInfo.value;
2333
+ if (op === '<') return aInfo.value < bInfo.value;
2334
+ if (op === '>=') return aInfo.value >= bInfo.value;
2335
+ if (op === '<=') return aInfo.value <= bInfo.value;
2336
+ if (op === '==') return aInfo.value == bInfo.value;
2337
+ if (op === '!=') return aInfo.value != bInfo.value;
2295
2338
  return false;
2296
2339
  }
2297
2340
 
2298
- const a = typeof aInfo.value === "bigint" ? Number(aInfo.value) : aInfo.value;
2299
- const b = typeof bInfo.value === "bigint" ? Number(bInfo.value) : bInfo.value;
2341
+ const a = typeof aInfo.value === 'bigint' ? Number(aInfo.value) : aInfo.value;
2342
+ const b = typeof bInfo.value === 'bigint' ? Number(bInfo.value) : bInfo.value;
2300
2343
 
2301
- if (op === ">") return a > b;
2302
- if (op === "<") return a < b;
2303
- if (op === ">=") return a >= b;
2304
- if (op === "<=") return a <= b;
2305
- if (op === "==") return a == b;
2306
- if (op === "!=") return a != b;
2344
+ if (op === '>') return a > b;
2345
+ if (op === '<') return a < b;
2346
+ if (op === '>=') return a >= b;
2347
+ if (op === '<=') return a <= b;
2348
+ if (op === '==') return a == b;
2349
+ if (op === '!=') return a != b;
2307
2350
  return false;
2308
2351
  }
2309
2352
 
2310
2353
  function parseNumOrDuration(t) {
2311
2354
  const n = parseNum(t);
2312
2355
  if (n !== null) return n;
2356
+
2357
+ // xsd:duration
2313
2358
  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
- ) {
2322
- const negative = val.startsWith("-");
2359
+ const [lex, dt] = literalParts(t.value);
2360
+ if (dt === XSD_NS + 'duration') {
2361
+ const val = stripQuotes(lex);
2362
+ const negative = val.startsWith('-');
2323
2363
  const core = negative ? val.slice(1) : val;
2364
+ if (!core.startsWith('P')) return null;
2324
2365
  const secs = parseIso8601DurationToSeconds(core);
2325
2366
  if (secs === null) return null;
2326
2367
  return negative ? -secs : secs;
2327
2368
  }
2328
2369
  }
2370
+
2371
+ // xsd:date / xsd:dateTime
2329
2372
  const dtval = parseDatetimeLike(t);
2330
2373
  if (dtval !== null) {
2331
2374
  return dtval.getTime() / 1000.0;
@@ -2377,10 +2420,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2377
2420
  const [lex, _dt] = literalParts(t.value);
2378
2421
  const input = stripQuotes(lex);
2379
2422
  try {
2380
- const digest = nodeCrypto
2381
- .createHash(algo)
2382
- .update(input, "utf8")
2383
- .digest("hex");
2423
+ const digest = nodeCrypto.createHash(algo).update(input, 'utf8').digest('hex');
2384
2424
  // plain string literal with the hex digest
2385
2425
  return new Literal(JSON.stringify(digest));
2386
2426
  } catch (e) {
@@ -2394,8 +2434,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2394
2434
 
2395
2435
  // crypto:sha
2396
2436
  // true iff ?o is the SHA-1 hash of the subject string.
2397
- if (g.p instanceof Iri && g.p.value === CRYPTO_NS + "sha") {
2398
- const lit = hashLiteral(g.s, "sha1");
2437
+ if (g.p instanceof Iri && g.p.value === CRYPTO_NS + 'sha') {
2438
+ const lit = hashLiteral(g.s, 'sha1');
2399
2439
  if (!lit) return [];
2400
2440
  if (g.o instanceof Var) {
2401
2441
  const s2 = { ...subst };
@@ -2407,8 +2447,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2407
2447
  }
2408
2448
 
2409
2449
  // crypto:md5
2410
- if (g.p instanceof Iri && g.p.value === CRYPTO_NS + "md5") {
2411
- const lit = hashLiteral(g.s, "md5");
2450
+ if (g.p instanceof Iri && g.p.value === CRYPTO_NS + 'md5') {
2451
+ const lit = hashLiteral(g.s, 'md5');
2412
2452
  if (!lit) return [];
2413
2453
  if (g.o instanceof Var) {
2414
2454
  const s2 = { ...subst };
@@ -2420,8 +2460,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2420
2460
  }
2421
2461
 
2422
2462
  // crypto:sha256
2423
- if (g.p instanceof Iri && g.p.value === CRYPTO_NS + "sha256") {
2424
- const lit = hashLiteral(g.s, "sha256");
2463
+ if (g.p instanceof Iri && g.p.value === CRYPTO_NS + 'sha256') {
2464
+ const lit = hashLiteral(g.s, 'sha256');
2425
2465
  if (!lit) return [];
2426
2466
  if (g.o instanceof Var) {
2427
2467
  const s2 = { ...subst };
@@ -2433,8 +2473,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2433
2473
  }
2434
2474
 
2435
2475
  // crypto:sha512
2436
- if (g.p instanceof Iri && g.p.value === CRYPTO_NS + "sha512") {
2437
- const lit = hashLiteral(g.s, "sha512");
2476
+ if (g.p instanceof Iri && g.p.value === CRYPTO_NS + 'sha512') {
2477
+ const lit = hashLiteral(g.s, 'sha512');
2438
2478
  if (!lit) return [];
2439
2479
  if (g.o instanceof Var) {
2440
2480
  const s2 = { ...subst };
@@ -2450,91 +2490,91 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2450
2490
  // -----------------------------------------------------------------
2451
2491
 
2452
2492
  // math:greaterThan
2453
- if (g.p instanceof Iri && g.p.value === MATH_NS + "greaterThan") {
2493
+ if (g.p instanceof Iri && g.p.value === MATH_NS + 'greaterThan') {
2454
2494
  const aInfo = parseNumericForCompareTerm(g.s);
2455
2495
  const bInfo = parseNumericForCompareTerm(g.o);
2456
- if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, ">")) return [{ ...subst }];
2496
+ if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, '>')) return [{ ...subst }];
2457
2497
 
2458
2498
  if (g.s instanceof ListTerm && g.s.elems.length === 2) {
2459
2499
  const a2 = parseNumericForCompareTerm(g.s.elems[0]);
2460
2500
  const b2 = parseNumericForCompareTerm(g.s.elems[1]);
2461
- if (a2 && b2 && cmpNumericInfo(a2, b2, ">")) return [{ ...subst }];
2501
+ if (a2 && b2 && cmpNumericInfo(a2, b2, '>')) return [{ ...subst }];
2462
2502
  }
2463
2503
  return [];
2464
2504
  }
2465
2505
 
2466
2506
  // math:lessThan
2467
- if (g.p instanceof Iri && g.p.value === MATH_NS + "lessThan") {
2507
+ if (g.p instanceof Iri && g.p.value === MATH_NS + 'lessThan') {
2468
2508
  const aInfo = parseNumericForCompareTerm(g.s);
2469
2509
  const bInfo = parseNumericForCompareTerm(g.o);
2470
- if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, "<")) return [{ ...subst }];
2510
+ if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, '<')) return [{ ...subst }];
2471
2511
 
2472
2512
  if (g.s instanceof ListTerm && g.s.elems.length === 2) {
2473
2513
  const a2 = parseNumericForCompareTerm(g.s.elems[0]);
2474
2514
  const b2 = parseNumericForCompareTerm(g.s.elems[1]);
2475
- if (a2 && b2 && cmpNumericInfo(a2, b2, "<")) return [{ ...subst }];
2515
+ if (a2 && b2 && cmpNumericInfo(a2, b2, '<')) return [{ ...subst }];
2476
2516
  }
2477
2517
  return [];
2478
2518
  }
2479
2519
 
2480
2520
  // math:notLessThan
2481
- if (g.p instanceof Iri && g.p.value === MATH_NS + "notLessThan") {
2521
+ if (g.p instanceof Iri && g.p.value === MATH_NS + 'notLessThan') {
2482
2522
  const aInfo = parseNumericForCompareTerm(g.s);
2483
2523
  const bInfo = parseNumericForCompareTerm(g.o);
2484
- if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, ">=")) return [{ ...subst }];
2524
+ if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, '>=')) return [{ ...subst }];
2485
2525
 
2486
2526
  if (g.s instanceof ListTerm && g.s.elems.length === 2) {
2487
2527
  const a2 = parseNumericForCompareTerm(g.s.elems[0]);
2488
2528
  const b2 = parseNumericForCompareTerm(g.s.elems[1]);
2489
- if (a2 && b2 && cmpNumericInfo(a2, b2, ">=")) return [{ ...subst }];
2529
+ if (a2 && b2 && cmpNumericInfo(a2, b2, '>=')) return [{ ...subst }];
2490
2530
  }
2491
2531
  return [];
2492
2532
  }
2493
2533
 
2494
2534
  // math:notGreaterThan
2495
- if (g.p instanceof Iri && g.p.value === MATH_NS + "notGreaterThan") {
2535
+ if (g.p instanceof Iri && g.p.value === MATH_NS + 'notGreaterThan') {
2496
2536
  const aInfo = parseNumericForCompareTerm(g.s);
2497
2537
  const bInfo = parseNumericForCompareTerm(g.o);
2498
- if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, "<=")) return [{ ...subst }];
2538
+ if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, '<=')) return [{ ...subst }];
2499
2539
 
2500
2540
  if (g.s instanceof ListTerm && g.s.elems.length === 2) {
2501
2541
  const a2 = parseNumericForCompareTerm(g.s.elems[0]);
2502
2542
  const b2 = parseNumericForCompareTerm(g.s.elems[1]);
2503
- if (a2 && b2 && cmpNumericInfo(a2, b2, "<=")) return [{ ...subst }];
2543
+ if (a2 && b2 && cmpNumericInfo(a2, b2, '<=')) return [{ ...subst }];
2504
2544
  }
2505
2545
  return [];
2506
2546
  }
2507
2547
 
2508
2548
  // math:equalTo
2509
- if (g.p instanceof Iri && g.p.value === MATH_NS + "equalTo") {
2549
+ if (g.p instanceof Iri && g.p.value === MATH_NS + 'equalTo') {
2510
2550
  const aInfo = parseNumericForCompareTerm(g.s);
2511
2551
  const bInfo = parseNumericForCompareTerm(g.o);
2512
- if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, "==")) return [{ ...subst }];
2552
+ if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, '==')) return [{ ...subst }];
2513
2553
 
2514
2554
  if (g.s instanceof ListTerm && g.s.elems.length === 2) {
2515
2555
  const a2 = parseNumericForCompareTerm(g.s.elems[0]);
2516
2556
  const b2 = parseNumericForCompareTerm(g.s.elems[1]);
2517
- if (a2 && b2 && cmpNumericInfo(a2, b2, "==")) return [{ ...subst }];
2557
+ if (a2 && b2 && cmpNumericInfo(a2, b2, '==')) return [{ ...subst }];
2518
2558
  }
2519
2559
  return [];
2520
2560
  }
2521
2561
 
2522
2562
  // math:notEqualTo
2523
- if (g.p instanceof Iri && g.p.value === MATH_NS + "notEqualTo") {
2563
+ if (g.p instanceof Iri && g.p.value === MATH_NS + 'notEqualTo') {
2524
2564
  const aInfo = parseNumericForCompareTerm(g.s);
2525
2565
  const bInfo = parseNumericForCompareTerm(g.o);
2526
- if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, "!=")) return [{ ...subst }];
2566
+ if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, '!=')) return [{ ...subst }];
2527
2567
 
2528
2568
  if (g.s instanceof ListTerm && g.s.elems.length === 2) {
2529
2569
  const a2 = parseNumericForCompareTerm(g.s.elems[0]);
2530
2570
  const b2 = parseNumericForCompareTerm(g.s.elems[1]);
2531
- if (a2 && b2 && cmpNumericInfo(a2, b2, "!=")) return [{ ...subst }];
2571
+ if (a2 && b2 && cmpNumericInfo(a2, b2, '!=')) return [{ ...subst }];
2532
2572
  }
2533
2573
  return [];
2534
2574
  }
2535
2575
 
2536
2576
  // math:sum
2537
- if (g.p instanceof Iri && g.p.value === MATH_NS + "sum") {
2577
+ if (g.p instanceof Iri && g.p.value === MATH_NS + 'sum') {
2538
2578
  if (g.s instanceof ListTerm && g.s.elems.length >= 2) {
2539
2579
  const xs = g.s.elems;
2540
2580
  const values = [];
@@ -2545,7 +2585,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2545
2585
  }
2546
2586
 
2547
2587
  let lit;
2548
- const allBig = values.every(v => typeof v === "bigint");
2588
+ const allBig = values.every((v) => typeof v === 'bigint');
2549
2589
  if (allBig) {
2550
2590
  let total = 0n;
2551
2591
  for (const v of values) total += v;
@@ -2553,7 +2593,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2553
2593
  } else {
2554
2594
  let total = 0.0;
2555
2595
  for (const v of values) {
2556
- total += typeof v === "bigint" ? Number(v) : v;
2596
+ total += typeof v === 'bigint' ? Number(v) : v;
2557
2597
  }
2558
2598
  lit = new Literal(formatNum(total));
2559
2599
  }
@@ -2570,7 +2610,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2570
2610
  }
2571
2611
 
2572
2612
  // math:product
2573
- if (g.p instanceof Iri && g.p.value === MATH_NS + "product") {
2613
+ if (g.p instanceof Iri && g.p.value === MATH_NS + 'product') {
2574
2614
  if (g.s instanceof ListTerm && g.s.elems.length >= 2) {
2575
2615
  const xs = g.s.elems;
2576
2616
  const values = [];
@@ -2581,7 +2621,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2581
2621
  }
2582
2622
 
2583
2623
  let lit;
2584
- const allBig = values.every(v => typeof v === "bigint");
2624
+ const allBig = values.every((v) => typeof v === 'bigint');
2585
2625
  if (allBig) {
2586
2626
  let prod = 1n;
2587
2627
  for (const v of values) prod *= v;
@@ -2589,7 +2629,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2589
2629
  } else {
2590
2630
  let prod = 1.0;
2591
2631
  for (const v of values) {
2592
- prod *= typeof v === "bigint" ? Number(v) : v;
2632
+ prod *= typeof v === 'bigint' ? Number(v) : v;
2593
2633
  }
2594
2634
  lit = new Literal(formatNum(prod));
2595
2635
  }
@@ -2607,7 +2647,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2607
2647
  }
2608
2648
 
2609
2649
  // math:difference
2610
- if (g.p instanceof Iri && g.p.value === MATH_NS + "difference") {
2650
+ if (g.p instanceof Iri && g.p.value === MATH_NS + 'difference') {
2611
2651
  if (g.s instanceof ListTerm && g.s.elems.length === 2) {
2612
2652
  const [a0, b0] = g.s.elems;
2613
2653
 
@@ -2662,7 +2702,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2662
2702
  }
2663
2703
 
2664
2704
  // math:quotient
2665
- if (g.p instanceof Iri && g.p.value === MATH_NS + "quotient") {
2705
+ if (g.p instanceof Iri && g.p.value === MATH_NS + 'quotient') {
2666
2706
  if (g.s instanceof ListTerm && g.s.elems.length === 2) {
2667
2707
  const a = parseNum(g.s.elems[0]);
2668
2708
  const b = parseNum(g.s.elems[1]);
@@ -2684,7 +2724,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2684
2724
  // math:integerQuotient
2685
2725
  // Schema: ( $a $b ) math:integerQuotient $q
2686
2726
  // Cwm: divide first integer by second integer, ignoring remainder. :contentReference[oaicite:1]{index=1}
2687
- if (g.p instanceof Iri && g.p.value === MATH_NS + "integerQuotient") {
2727
+ if (g.p instanceof Iri && g.p.value === MATH_NS + 'integerQuotient') {
2688
2728
  if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
2689
2729
  const [a0, b0] = g.s.elems;
2690
2730
 
@@ -2723,7 +2763,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2723
2763
  }
2724
2764
 
2725
2765
  // math:exponentiation
2726
- if (g.p instanceof Iri && g.p.value === MATH_NS + "exponentiation") {
2766
+ if (g.p instanceof Iri && g.p.value === MATH_NS + 'exponentiation') {
2727
2767
  if (g.s instanceof ListTerm && g.s.elems.length === 2) {
2728
2768
  const a = parseNum(g.s.elems[0]);
2729
2769
  const b0 = g.s.elems[1];
@@ -2757,7 +2797,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2757
2797
  }
2758
2798
 
2759
2799
  // math:negation
2760
- if (g.p instanceof Iri && g.p.value === MATH_NS + "negation") {
2800
+ if (g.p instanceof Iri && g.p.value === MATH_NS + 'negation') {
2761
2801
  const a = parseNum(g.s);
2762
2802
  if (a !== null && g.o instanceof Var) {
2763
2803
  const s2 = { ...subst };
@@ -2779,7 +2819,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2779
2819
  }
2780
2820
 
2781
2821
  // math:absoluteValue
2782
- if (g.p instanceof Iri && g.p.value === MATH_NS + "absoluteValue") {
2822
+ if (g.p instanceof Iri && g.p.value === MATH_NS + 'absoluteValue') {
2783
2823
  const a = parseNum(g.s);
2784
2824
  if (a !== null && g.o instanceof Var) {
2785
2825
  const s2 = { ...subst };
@@ -2794,7 +2834,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2794
2834
  }
2795
2835
 
2796
2836
  // math:cos
2797
- if (g.p instanceof Iri && g.p.value === MATH_NS + "cos") {
2837
+ if (g.p instanceof Iri && g.p.value === MATH_NS + 'cos') {
2798
2838
  const a = parseNum(g.s);
2799
2839
  if (a !== null) {
2800
2840
  const cVal = Math.cos(a);
@@ -2811,7 +2851,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2811
2851
  }
2812
2852
 
2813
2853
  // math:sin
2814
- if (g.p instanceof Iri && g.p.value === MATH_NS + "sin") {
2854
+ if (g.p instanceof Iri && g.p.value === MATH_NS + 'sin') {
2815
2855
  const a = parseNum(g.s);
2816
2856
  if (a !== null) {
2817
2857
  const cVal = Math.sin(a);
@@ -2828,7 +2868,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2828
2868
  }
2829
2869
 
2830
2870
  // math:acos
2831
- if (g.p instanceof Iri && g.p.value === MATH_NS + "acos") {
2871
+ if (g.p instanceof Iri && g.p.value === MATH_NS + 'acos') {
2832
2872
  const a = parseNum(g.s);
2833
2873
  if (a !== null) {
2834
2874
  const cVal = Math.acos(a);
@@ -2847,7 +2887,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2847
2887
  }
2848
2888
 
2849
2889
  // math:asin
2850
- if (g.p instanceof Iri && g.p.value === MATH_NS + "asin") {
2890
+ if (g.p instanceof Iri && g.p.value === MATH_NS + 'asin') {
2851
2891
  const a = parseNum(g.s);
2852
2892
  if (a !== null) {
2853
2893
  const cVal = Math.asin(a);
@@ -2866,7 +2906,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2866
2906
  }
2867
2907
 
2868
2908
  // math:atan
2869
- if (g.p instanceof Iri && g.p.value === MATH_NS + "atan") {
2909
+ if (g.p instanceof Iri && g.p.value === MATH_NS + 'atan') {
2870
2910
  const a = parseNum(g.s);
2871
2911
  if (a !== null) {
2872
2912
  const cVal = Math.atan(a);
@@ -2886,9 +2926,9 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2886
2926
 
2887
2927
  // math:cosh
2888
2928
  // Hyperbolic cosine
2889
- if (g.p instanceof Iri && g.p.value === MATH_NS + "cosh") {
2929
+ if (g.p instanceof Iri && g.p.value === MATH_NS + 'cosh') {
2890
2930
  const a = parseNum(g.s);
2891
- if (a !== null && typeof Math.cosh === "function") {
2931
+ if (a !== null && typeof Math.cosh === 'function') {
2892
2932
  const cVal = Math.cosh(a);
2893
2933
  if (Number.isFinite(cVal)) {
2894
2934
  if (g.o instanceof Var) {
@@ -2906,7 +2946,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2906
2946
 
2907
2947
  // math:degrees
2908
2948
  // Convert radians -> degrees
2909
- if (g.p instanceof Iri && g.p.value === MATH_NS + "degrees") {
2949
+ if (g.p instanceof Iri && g.p.value === MATH_NS + 'degrees') {
2910
2950
  const a = parseNum(g.s);
2911
2951
  if (a !== null) {
2912
2952
  const cVal = (a * 180.0) / Math.PI;
@@ -2927,7 +2967,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2927
2967
  // math:remainder
2928
2968
  // Subject is a list (dividend divisor); object is the remainder.
2929
2969
  // Schema: ( $a $b ) math:remainder $r
2930
- if (g.p instanceof Iri && g.p.value === MATH_NS + "remainder") {
2970
+ if (g.p instanceof Iri && g.p.value === MATH_NS + 'remainder') {
2931
2971
  if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
2932
2972
  const a = parseNum(g.s.elems[0]);
2933
2973
  const b = parseNum(g.s.elems[1]);
@@ -2949,7 +2989,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2949
2989
  // Round to nearest integer.
2950
2990
  // If there are two such numbers, then the one closest to positive infinity is returned.
2951
2991
  // Schema: $s math:rounded $o
2952
- if (g.p instanceof Iri && g.p.value === MATH_NS + "rounded") {
2992
+ if (g.p instanceof Iri && g.p.value === MATH_NS + 'rounded') {
2953
2993
  const a = parseNum(g.s);
2954
2994
  if (a === null) return [];
2955
2995
  const rVal = Math.round(a);
@@ -2965,9 +3005,9 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2965
3005
  }
2966
3006
 
2967
3007
  // math:sinh
2968
- if (g.p instanceof Iri && g.p.value === MATH_NS + "sinh") {
3008
+ if (g.p instanceof Iri && g.p.value === MATH_NS + 'sinh') {
2969
3009
  const a = parseNum(g.s);
2970
- if (a !== null && typeof Math.sinh === "function") {
3010
+ if (a !== null && typeof Math.sinh === 'function') {
2971
3011
  const cVal = Math.sinh(a);
2972
3012
  if (Number.isFinite(cVal)) {
2973
3013
  if (g.o instanceof Var) {
@@ -2984,7 +3024,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2984
3024
  }
2985
3025
 
2986
3026
  // math:tan
2987
- if (g.p instanceof Iri && g.p.value === MATH_NS + "tan") {
3027
+ if (g.p instanceof Iri && g.p.value === MATH_NS + 'tan') {
2988
3028
  const a = parseNum(g.s);
2989
3029
  if (a !== null) {
2990
3030
  const cVal = Math.tan(a);
@@ -3003,9 +3043,9 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3003
3043
  }
3004
3044
 
3005
3045
  // math:tanh
3006
- if (g.p instanceof Iri && g.p.value === MATH_NS + "tanh") {
3046
+ if (g.p instanceof Iri && g.p.value === MATH_NS + 'tanh') {
3007
3047
  const a = parseNum(g.s);
3008
- if (a !== null && typeof Math.tanh === "function") {
3048
+ if (a !== null && typeof Math.tanh === 'function') {
3009
3049
  const cVal = Math.tanh(a);
3010
3050
  if (Number.isFinite(cVal)) {
3011
3051
  if (g.o instanceof Var) {
@@ -3027,7 +3067,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3027
3067
 
3028
3068
  // time:localTime
3029
3069
  // "" time:localTime ?D. binds ?D to “now” as xsd:dateTime.
3030
- if (g.p instanceof Iri && g.p.value === TIME_NS + "localTime") {
3070
+ if (g.p instanceof Iri && g.p.value === TIME_NS + 'localTime') {
3031
3071
  const now = getNowLex();
3032
3072
 
3033
3073
  if (g.o instanceof Var) {
@@ -3049,7 +3089,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3049
3089
  // list:append
3050
3090
  // true if and only if $o is the concatenation of all lists $s.i.
3051
3091
  // Schema: ( $s.i?[*] )+ list:append $o?
3052
- if (g.p instanceof Iri && g.p.value === LIST_NS + "append") {
3092
+ if (g.p instanceof Iri && g.p.value === LIST_NS + 'append') {
3053
3093
  if (!(g.s instanceof ListTerm)) return [];
3054
3094
  const parts = g.s.elems;
3055
3095
  if (g.o instanceof ListTerm) {
@@ -3073,7 +3113,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3073
3113
  // list:first
3074
3114
  // true iff $s is a list and $o is the first member of that list.
3075
3115
  // Schema: $s+ list:first $o-
3076
- if (g.p instanceof Iri && g.p.value === LIST_NS + "first") {
3116
+ if (g.p instanceof Iri && g.p.value === LIST_NS + 'first') {
3077
3117
  if (!(g.s instanceof ListTerm)) return [];
3078
3118
  if (!g.s.elems.length) return [];
3079
3119
  const first = g.s.elems[0];
@@ -3084,7 +3124,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3084
3124
  // list:rest
3085
3125
  // true iff $s is a (non-empty) list and $o is the rest (tail) of that list.
3086
3126
  // Schema: $s+ list:rest $o-
3087
- if (g.p instanceof Iri && g.p.value === LIST_NS + "rest") {
3127
+ if (g.p instanceof Iri && g.p.value === LIST_NS + 'rest') {
3088
3128
  // Closed list: (a b c) -> (b c)
3089
3129
  if (g.s instanceof ListTerm) {
3090
3130
  if (!g.s.elems.length) return [];
@@ -3113,7 +3153,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3113
3153
 
3114
3154
  // rdf:first (alias of list:first)
3115
3155
  // Schema: $s+ rdf:first $o-
3116
- if (g.p instanceof Iri && g.p.value === RDF_NS + "first") {
3156
+ if (g.p instanceof Iri && g.p.value === RDF_NS + 'first') {
3117
3157
  if (!(g.s instanceof ListTerm)) return [];
3118
3158
  if (!g.s.elems.length) return [];
3119
3159
  const first = g.s.elems[0];
@@ -3123,7 +3163,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3123
3163
 
3124
3164
  // rdf:rest (alias of list:rest)
3125
3165
  // Schema: $s+ rdf:rest $o-
3126
- if (g.p instanceof Iri && g.p.value === RDF_NS + "rest") {
3166
+ if (g.p instanceof Iri && g.p.value === RDF_NS + 'rest') {
3127
3167
  // Closed list: (a b c) -> (b c)
3128
3168
  if (g.s instanceof ListTerm) {
3129
3169
  if (!g.s.elems.length) return [];
@@ -3149,7 +3189,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3149
3189
  // Multi-solution builtin:
3150
3190
  // For a list subject $s, generate solutions by unifying $o with (index value).
3151
3191
  // This allows $o to be a variable (e.g., ?Y) or a pattern (e.g., (?i "Dewey")).
3152
- if (g.p instanceof Iri && g.p.value === LIST_NS + "iterate") {
3192
+ if (g.p instanceof Iri && g.p.value === LIST_NS + 'iterate') {
3153
3193
  if (!(g.s instanceof ListTerm)) return [];
3154
3194
  const xs = g.s.elems;
3155
3195
  const outs = [];
@@ -3165,7 +3205,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3165
3205
  // list:last
3166
3206
  // true iff $s is a list and $o is the last member of that list.
3167
3207
  // Schema: $s+ list:last $o-
3168
- if (g.p instanceof Iri && g.p.value === LIST_NS + "last") {
3208
+ if (g.p instanceof Iri && g.p.value === LIST_NS + 'last') {
3169
3209
  if (!(g.s instanceof ListTerm)) return [];
3170
3210
  const xs = g.s.elems;
3171
3211
  if (!xs.length) return [];
@@ -3177,7 +3217,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3177
3217
  // list:memberAt
3178
3218
  // true iff $s.1 is a list, $s.2 is a valid index, and $o is the member at that index.
3179
3219
  // Schema: ( $s.1+ $s.2?[*] )+ list:memberAt $o?[*]
3180
- if (g.p instanceof Iri && g.p.value === LIST_NS + "memberAt") {
3220
+ if (g.p instanceof Iri && g.p.value === LIST_NS + 'memberAt') {
3181
3221
  if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
3182
3222
  const [listTerm, indexTerm] = g.s.elems;
3183
3223
  if (!(listTerm instanceof ListTerm)) return [];
@@ -3198,7 +3238,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3198
3238
  // list:remove
3199
3239
  // true iff $s.1 is a list and $o is that list with all occurrences of $s.2 removed.
3200
3240
  // Schema: ( $s.1+ $s.2+ )+ list:remove $o-
3201
- if (g.p instanceof Iri && g.p.value === LIST_NS + "remove") {
3241
+ if (g.p instanceof Iri && g.p.value === LIST_NS + 'remove') {
3202
3242
  if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
3203
3243
  const [listTerm, itemTerm] = g.s.elems;
3204
3244
  if (!(listTerm instanceof ListTerm)) return [];
@@ -3213,7 +3253,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3213
3253
  }
3214
3254
 
3215
3255
  // list:member
3216
- if (g.p instanceof Iri && g.p.value === LIST_NS + "member") {
3256
+ if (g.p instanceof Iri && g.p.value === LIST_NS + 'member') {
3217
3257
  if (!(g.s instanceof ListTerm)) return [];
3218
3258
  const outs = [];
3219
3259
  for (const x of g.s.elems) {
@@ -3224,7 +3264,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3224
3264
  }
3225
3265
 
3226
3266
  // list:in
3227
- if (g.p instanceof Iri && g.p.value === LIST_NS + "in") {
3267
+ if (g.p instanceof Iri && g.p.value === LIST_NS + 'in') {
3228
3268
  if (!(g.o instanceof ListTerm)) return [];
3229
3269
  const outs = [];
3230
3270
  for (const x of g.o.elems) {
@@ -3235,7 +3275,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3235
3275
  }
3236
3276
 
3237
3277
  // list:length
3238
- if (g.p instanceof Iri && g.p.value === LIST_NS + "length") {
3278
+ if (g.p instanceof Iri && g.p.value === LIST_NS + 'length') {
3239
3279
  if (!(g.s instanceof ListTerm)) return [];
3240
3280
  const nTerm = new Literal(String(g.s.elems.length));
3241
3281
  const s2 = unifyTerm(g.o, nTerm, subst);
@@ -3243,7 +3283,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3243
3283
  }
3244
3284
 
3245
3285
  // list:notMember
3246
- if (g.p instanceof Iri && g.p.value === LIST_NS + "notMember") {
3286
+ if (g.p instanceof Iri && g.p.value === LIST_NS + 'notMember') {
3247
3287
  if (!(g.s instanceof ListTerm)) return [];
3248
3288
  for (const el of g.s.elems) {
3249
3289
  if (unifyTerm(g.o, el, subst) !== null) return [];
@@ -3252,7 +3292,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3252
3292
  }
3253
3293
 
3254
3294
  // list:reverse
3255
- if (g.p instanceof Iri && g.p.value === LIST_NS + "reverse") {
3295
+ if (g.p instanceof Iri && g.p.value === LIST_NS + 'reverse') {
3256
3296
  if (g.s instanceof ListTerm) {
3257
3297
  const rev = [...g.s.elems].reverse();
3258
3298
  const rterm = new ListTerm(rev);
@@ -3269,7 +3309,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3269
3309
  }
3270
3310
 
3271
3311
  // list:sort
3272
- if (g.p instanceof Iri && g.p.value === LIST_NS + "sort") {
3312
+ if (g.p instanceof Iri && g.p.value === LIST_NS + 'sort') {
3273
3313
  function cmpTermForSort(a, b) {
3274
3314
  if (a instanceof Literal && b instanceof Literal) {
3275
3315
  const [lexA] = literalParts(a.value);
@@ -3321,7 +3361,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3321
3361
  else if (g.o instanceof ListTerm) inputList = g.o.elems;
3322
3362
  else return [];
3323
3363
 
3324
- if (!inputList.every(e => isGroundTerm(e))) return [];
3364
+ if (!inputList.every((e) => isGroundTerm(e))) return [];
3325
3365
 
3326
3366
  const sortedList = [...inputList].sort(cmpTermForSort);
3327
3367
  const sortedTerm = new ListTerm(sortedList);
@@ -3337,7 +3377,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3337
3377
  }
3338
3378
 
3339
3379
  // list:map
3340
- if (g.p instanceof Iri && g.p.value === LIST_NS + "map") {
3380
+ if (g.p instanceof Iri && g.p.value === LIST_NS + 'map') {
3341
3381
  if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
3342
3382
  const [inputTerm, predTerm] = g.s.elems;
3343
3383
  if (!(inputTerm instanceof ListTerm)) return [];
@@ -3345,11 +3385,11 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3345
3385
  if (!(predTerm instanceof Iri)) return [];
3346
3386
  const pred = new Iri(predTerm.value);
3347
3387
  if (!isBuiltinPred(pred)) return [];
3348
- if (!inputList.every(e => isGroundTerm(e))) return [];
3388
+ if (!inputList.every((e) => isGroundTerm(e))) return [];
3349
3389
 
3350
3390
  const results = [];
3351
3391
  for (const el of inputList) {
3352
- const yvar = new Var("_mapY");
3392
+ const yvar = new Var('_mapY');
3353
3393
  const goal2 = new Triple(el, pred, yvar);
3354
3394
  const sols = evalBuiltin(goal2, subst, facts, backRules, depth + 1, varGen);
3355
3395
  if (!sols.length) return [];
@@ -3363,7 +3403,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3363
3403
  }
3364
3404
 
3365
3405
  // list:firstRest
3366
- if (g.p instanceof Iri && g.p.value === LIST_NS + "firstRest") {
3406
+ if (g.p instanceof Iri && g.p.value === LIST_NS + 'firstRest') {
3367
3407
  if (g.s instanceof ListTerm) {
3368
3408
  if (!g.s.elems.length) return [];
3369
3409
  const first = g.s.elems[0];
@@ -3401,20 +3441,20 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3401
3441
  // -----------------------------------------------------------------
3402
3442
 
3403
3443
  // log:equalTo
3404
- if (g.p instanceof Iri && g.p.value === LOG_NS + "equalTo") {
3444
+ if (g.p instanceof Iri && g.p.value === LOG_NS + 'equalTo') {
3405
3445
  const s2 = unifyTerm(goal.s, goal.o, subst);
3406
3446
  return s2 !== null ? [s2] : [];
3407
3447
  }
3408
3448
 
3409
3449
  // log:notEqualTo
3410
- if (g.p instanceof Iri && g.p.value === LOG_NS + "notEqualTo") {
3450
+ if (g.p instanceof Iri && g.p.value === LOG_NS + 'notEqualTo') {
3411
3451
  const s2 = unifyTerm(goal.s, goal.o, subst);
3412
3452
  if (s2 !== null) return [];
3413
3453
  return [{ ...subst }];
3414
3454
  }
3415
3455
 
3416
3456
  // log:implies — expose internal forward rules as data
3417
- if (g.p instanceof Iri && g.p.value === LOG_NS + "implies") {
3457
+ if (g.p instanceof Iri && g.p.value === LOG_NS + 'implies') {
3418
3458
  const allFw = backRules.__allForwardRules || [];
3419
3459
  const results = [];
3420
3460
 
@@ -3425,9 +3465,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3425
3465
  const r = standardizeRule(r0, varGen);
3426
3466
 
3427
3467
  const premF = new FormulaTerm(r.premise);
3428
- const concTerm = r0.isFuse
3429
- ? new Literal("false")
3430
- : new FormulaTerm(r.conclusion);
3468
+ const concTerm = r0.isFuse ? new Literal('false') : new FormulaTerm(r.conclusion);
3431
3469
 
3432
3470
  // unify subject with the premise formula
3433
3471
  let s2 = unifyTerm(goal.s, premF, subst);
@@ -3444,7 +3482,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3444
3482
  }
3445
3483
 
3446
3484
  // log:impliedBy — expose internal backward rules as data
3447
- if (g.p instanceof Iri && g.p.value === LOG_NS + "impliedBy") {
3485
+ if (g.p instanceof Iri && g.p.value === LOG_NS + 'impliedBy') {
3448
3486
  const allBw = backRules.__allBackwardRules || backRules;
3449
3487
  const results = [];
3450
3488
 
@@ -3473,39 +3511,23 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3473
3511
  }
3474
3512
 
3475
3513
  // log:notIncludes
3476
- if (g.p instanceof Iri && g.p.value === LOG_NS + "notIncludes") {
3514
+ if (g.p instanceof Iri && g.p.value === LOG_NS + 'notIncludes') {
3477
3515
  if (!(g.o instanceof FormulaTerm)) return [];
3478
3516
  const body = g.o.triples;
3479
3517
  const visited2 = [];
3480
- const sols = proveGoals(
3481
- Array.from(body),
3482
- {},
3483
- facts,
3484
- backRules,
3485
- depth + 1,
3486
- visited2,
3487
- varGen
3488
- );
3518
+ const sols = proveGoals(Array.from(body), {}, facts, backRules, depth + 1, visited2, varGen);
3489
3519
  if (!sols.length) return [{ ...subst }];
3490
3520
  return [];
3491
3521
  }
3492
3522
 
3493
3523
  // log:collectAllIn
3494
- if (g.p instanceof Iri && g.p.value === LOG_NS + "collectAllIn") {
3524
+ if (g.p instanceof Iri && g.p.value === LOG_NS + 'collectAllIn') {
3495
3525
  if (!(g.s instanceof ListTerm) || g.s.elems.length !== 3) return [];
3496
3526
  const [valueTempl, clauseTerm, listTerm] = g.s.elems;
3497
3527
  if (!(clauseTerm instanceof FormulaTerm)) return [];
3498
3528
  const body = clauseTerm.triples;
3499
3529
  const visited2 = [];
3500
- const sols = proveGoals(
3501
- Array.from(body),
3502
- {},
3503
- facts,
3504
- backRules,
3505
- depth + 1,
3506
- visited2,
3507
- varGen
3508
- );
3530
+ const sols = proveGoals(Array.from(body), {}, facts, backRules, depth + 1, visited2, varGen);
3509
3531
 
3510
3532
  // Collect one value per *solution*, duplicates allowed
3511
3533
  const collected = [];
@@ -3520,7 +3542,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3520
3542
  }
3521
3543
 
3522
3544
  // log:forAllIn
3523
- if (g.p instanceof Iri && g.p.value === LOG_NS + "forAllIn") {
3545
+ if (g.p instanceof Iri && g.p.value === LOG_NS + 'forAllIn') {
3524
3546
  // Subject: list with two clauses (where-clause, then-clause)
3525
3547
  if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
3526
3548
  const [whereClause, thenClause] = g.s.elems;
@@ -3529,29 +3551,13 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3529
3551
 
3530
3552
  // 1. Find all substitutions that make the first clause true
3531
3553
  const visited1 = [];
3532
- const sols1 = proveGoals(
3533
- Array.from(whereClause.triples),
3534
- {},
3535
- facts,
3536
- backRules,
3537
- depth + 1,
3538
- visited1,
3539
- varGen
3540
- );
3554
+ const sols1 = proveGoals(Array.from(whereClause.triples), {}, facts, backRules, depth + 1, visited1, varGen);
3541
3555
 
3542
3556
  // 2. For every such substitution, check that the second clause holds too.
3543
3557
  // If there are no matches for the first clause, this is vacuously true.
3544
3558
  for (const s1 of sols1) {
3545
3559
  const visited2 = [];
3546
- const sols2 = proveGoals(
3547
- Array.from(thenClause.triples),
3548
- s1,
3549
- facts,
3550
- backRules,
3551
- depth + 1,
3552
- visited2,
3553
- varGen
3554
- );
3560
+ const sols2 = proveGoals(Array.from(thenClause.triples), s1, facts, backRules, depth + 1, visited2, varGen);
3555
3561
  // Found a counterexample: whereClause holds but thenClause does not
3556
3562
  if (!sols2.length) return [];
3557
3563
  }
@@ -3561,7 +3567,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3561
3567
  }
3562
3568
 
3563
3569
  // log:skolem
3564
- if (g.p instanceof Iri && g.p.value === LOG_NS + "skolem") {
3570
+ if (g.p instanceof Iri && g.p.value === LOG_NS + 'skolem') {
3565
3571
  // Subject must be ground; commonly a list, but we allow any ground term.
3566
3572
  if (!isGroundTerm(g.s)) return [];
3567
3573
 
@@ -3578,10 +3584,10 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3578
3584
  }
3579
3585
 
3580
3586
  // log:uri
3581
- if (g.p instanceof Iri && g.p.value === LOG_NS + "uri") {
3587
+ if (g.p instanceof Iri && g.p.value === LOG_NS + 'uri') {
3582
3588
  // Direction 1: subject is an IRI -> object is its string representation
3583
3589
  if (g.s instanceof Iri) {
3584
- const uriStr = g.s.value; // raw IRI string, e.g. "https://www.w3.org"
3590
+ const uriStr = g.s.value; // raw IRI string, e.g. "https://www.w3.org"
3585
3591
  const lit = makeStringLiteral(uriStr); // "https://www.w3.org"
3586
3592
  const s2 = unifyTerm(goal.o, lit, subst);
3587
3593
  return s2 !== null ? [s2] : [];
@@ -3606,7 +3612,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3606
3612
  // -----------------------------------------------------------------
3607
3613
 
3608
3614
  // string:concatenation
3609
- if (g.p instanceof Iri && g.p.value === STRING_NS + "concatenation") {
3615
+ if (g.p instanceof Iri && g.p.value === STRING_NS + 'concatenation') {
3610
3616
  if (!(g.s instanceof ListTerm)) return [];
3611
3617
  const parts = [];
3612
3618
  for (const t of g.s.elems) {
@@ -3614,7 +3620,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3614
3620
  if (sStr === null) return [];
3615
3621
  parts.push(sStr);
3616
3622
  }
3617
- const lit = makeStringLiteral(parts.join(""));
3623
+ const lit = makeStringLiteral(parts.join(''));
3618
3624
 
3619
3625
  if (g.o instanceof Var) {
3620
3626
  const s2 = { ...subst };
@@ -3626,7 +3632,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3626
3632
  }
3627
3633
 
3628
3634
  // string:contains
3629
- if (g.p instanceof Iri && g.p.value === STRING_NS + "contains") {
3635
+ if (g.p instanceof Iri && g.p.value === STRING_NS + 'contains') {
3630
3636
  const sStr = termToJsString(g.s);
3631
3637
  const oStr = termToJsString(g.o);
3632
3638
  if (sStr === null || oStr === null) return [];
@@ -3634,17 +3640,15 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3634
3640
  }
3635
3641
 
3636
3642
  // string:containsIgnoringCase
3637
- if (g.p instanceof Iri && g.p.value === STRING_NS + "containsIgnoringCase") {
3643
+ if (g.p instanceof Iri && g.p.value === STRING_NS + 'containsIgnoringCase') {
3638
3644
  const sStr = termToJsString(g.s);
3639
3645
  const oStr = termToJsString(g.o);
3640
3646
  if (sStr === null || oStr === null) return [];
3641
- return sStr.toLowerCase().includes(oStr.toLowerCase())
3642
- ? [{ ...subst }]
3643
- : [];
3647
+ return sStr.toLowerCase().includes(oStr.toLowerCase()) ? [{ ...subst }] : [];
3644
3648
  }
3645
3649
 
3646
3650
  // string:endsWith
3647
- if (g.p instanceof Iri && g.p.value === STRING_NS + "endsWith") {
3651
+ if (g.p instanceof Iri && g.p.value === STRING_NS + 'endsWith') {
3648
3652
  const sStr = termToJsString(g.s);
3649
3653
  const oStr = termToJsString(g.o);
3650
3654
  if (sStr === null || oStr === null) return [];
@@ -3652,18 +3656,16 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3652
3656
  }
3653
3657
 
3654
3658
  // string:equalIgnoringCase
3655
- if (g.p instanceof Iri && g.p.value === STRING_NS + "equalIgnoringCase") {
3659
+ if (g.p instanceof Iri && g.p.value === STRING_NS + 'equalIgnoringCase') {
3656
3660
  const sStr = termToJsString(g.s);
3657
3661
  const oStr = termToJsString(g.o);
3658
3662
  if (sStr === null || oStr === null) return [];
3659
- return sStr.toLowerCase() === oStr.toLowerCase()
3660
- ? [{ ...subst }]
3661
- : [];
3663
+ return sStr.toLowerCase() === oStr.toLowerCase() ? [{ ...subst }] : [];
3662
3664
  }
3663
3665
 
3664
3666
  // string:format
3665
3667
  // (limited: only %s and %% are supported, anything else ⇒ builtin fails)
3666
- if (g.p instanceof Iri && g.p.value === STRING_NS + "format") {
3668
+ if (g.p instanceof Iri && g.p.value === STRING_NS + 'format') {
3667
3669
  if (!(g.s instanceof ListTerm) || g.s.elems.length < 1) return [];
3668
3670
  const fmtStr = termToJsString(g.s.elems[0]);
3669
3671
  if (fmtStr === null) return [];
@@ -3690,11 +3692,11 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3690
3692
 
3691
3693
  // string:jsonPointer
3692
3694
  // Schema: ( $jsonText $pointer ) string:jsonPointer $value
3693
- if (g.p instanceof Iri && g.p.value === STRING_NS + "jsonPointer") {
3695
+ if (g.p instanceof Iri && g.p.value === STRING_NS + 'jsonPointer') {
3694
3696
  if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
3695
3697
 
3696
- const jsonText = termToJsonText(g.s.elems[0]); // <-- changed
3697
- const ptr = termToJsStringDecoded(g.s.elems[1]);
3698
+ const jsonText = termToJsonText(g.s.elems[0]); // <-- changed
3699
+ const ptr = termToJsStringDecoded(g.s.elems[1]);
3698
3700
  if (jsonText === null || ptr === null) return [];
3699
3701
 
3700
3702
  const valTerm = jsonPointerLookup(jsonText, ptr);
@@ -3705,7 +3707,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3705
3707
  }
3706
3708
 
3707
3709
  // string:greaterThan
3708
- if (g.p instanceof Iri && g.p.value === STRING_NS + "greaterThan") {
3710
+ if (g.p instanceof Iri && g.p.value === STRING_NS + 'greaterThan') {
3709
3711
  const sStr = termToJsString(g.s);
3710
3712
  const oStr = termToJsString(g.o);
3711
3713
  if (sStr === null || oStr === null) return [];
@@ -3713,7 +3715,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3713
3715
  }
3714
3716
 
3715
3717
  // string:lessThan
3716
- if (g.p instanceof Iri && g.p.value === STRING_NS + "lessThan") {
3718
+ if (g.p instanceof Iri && g.p.value === STRING_NS + 'lessThan') {
3717
3719
  const sStr = termToJsString(g.s);
3718
3720
  const oStr = termToJsString(g.o);
3719
3721
  if (sStr === null || oStr === null) return [];
@@ -3721,7 +3723,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3721
3723
  }
3722
3724
 
3723
3725
  // string:matches
3724
- if (g.p instanceof Iri && g.p.value === STRING_NS + "matches") {
3726
+ if (g.p instanceof Iri && g.p.value === STRING_NS + 'matches') {
3725
3727
  const sStr = termToJsString(g.s);
3726
3728
  const pattern = termToJsString(g.o);
3727
3729
  if (sStr === null || pattern === null) return [];
@@ -3736,17 +3738,15 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3736
3738
  }
3737
3739
 
3738
3740
  // string:notEqualIgnoringCase
3739
- if (g.p instanceof Iri && g.p.value === STRING_NS + "notEqualIgnoringCase") {
3741
+ if (g.p instanceof Iri && g.p.value === STRING_NS + 'notEqualIgnoringCase') {
3740
3742
  const sStr = termToJsString(g.s);
3741
3743
  const oStr = termToJsString(g.o);
3742
3744
  if (sStr === null || oStr === null) return [];
3743
- return sStr.toLowerCase() !== oStr.toLowerCase()
3744
- ? [{ ...subst }]
3745
- : [];
3745
+ return sStr.toLowerCase() !== oStr.toLowerCase() ? [{ ...subst }] : [];
3746
3746
  }
3747
3747
 
3748
3748
  // string:notGreaterThan (≤ in Unicode code order)
3749
- if (g.p instanceof Iri && g.p.value === STRING_NS + "notGreaterThan") {
3749
+ if (g.p instanceof Iri && g.p.value === STRING_NS + 'notGreaterThan') {
3750
3750
  const sStr = termToJsString(g.s);
3751
3751
  const oStr = termToJsString(g.o);
3752
3752
  if (sStr === null || oStr === null) return [];
@@ -3754,7 +3754,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3754
3754
  }
3755
3755
 
3756
3756
  // string:notLessThan (≥ in Unicode code order)
3757
- if (g.p instanceof Iri && g.p.value === STRING_NS + "notLessThan") {
3757
+ if (g.p instanceof Iri && g.p.value === STRING_NS + 'notLessThan') {
3758
3758
  const sStr = termToJsString(g.s);
3759
3759
  const oStr = termToJsString(g.o);
3760
3760
  if (sStr === null || oStr === null) return [];
@@ -3762,7 +3762,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3762
3762
  }
3763
3763
 
3764
3764
  // string:notMatches
3765
- if (g.p instanceof Iri && g.p.value === STRING_NS + "notMatches") {
3765
+ if (g.p instanceof Iri && g.p.value === STRING_NS + 'notMatches') {
3766
3766
  const sStr = termToJsString(g.s);
3767
3767
  const pattern = termToJsString(g.o);
3768
3768
  if (sStr === null || pattern === null) return [];
@@ -3776,17 +3776,17 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3776
3776
  }
3777
3777
 
3778
3778
  // string:replace
3779
- if (g.p instanceof Iri && g.p.value === STRING_NS + "replace") {
3779
+ if (g.p instanceof Iri && g.p.value === STRING_NS + 'replace') {
3780
3780
  if (!(g.s instanceof ListTerm) || g.s.elems.length !== 3) return [];
3781
- const dataStr = termToJsString(g.s.elems[0]);
3781
+ const dataStr = termToJsString(g.s.elems[0]);
3782
3782
  const searchStr = termToJsString(g.s.elems[1]);
3783
- const replStr = termToJsString(g.s.elems[2]);
3783
+ const replStr = termToJsString(g.s.elems[2]);
3784
3784
  if (dataStr === null || searchStr === null || replStr === null) return [];
3785
3785
 
3786
3786
  let re;
3787
3787
  try {
3788
3788
  // Global replacement
3789
- re = new RegExp(searchStr, "g");
3789
+ re = new RegExp(searchStr, 'g');
3790
3790
  } catch (e) {
3791
3791
  return [];
3792
3792
  }
@@ -3804,7 +3804,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3804
3804
  }
3805
3805
 
3806
3806
  // string:scrape
3807
- if (g.p instanceof Iri && g.p.value === STRING_NS + "scrape") {
3807
+ if (g.p instanceof Iri && g.p.value === STRING_NS + 'scrape') {
3808
3808
  if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
3809
3809
  const dataStr = termToJsString(g.s.elems[0]);
3810
3810
  const pattern = termToJsString(g.s.elems[1]);
@@ -3833,7 +3833,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3833
3833
  }
3834
3834
 
3835
3835
  // string:startsWith
3836
- if (g.p instanceof Iri && g.p.value === STRING_NS + "startsWith") {
3836
+ if (g.p instanceof Iri && g.p.value === STRING_NS + 'startsWith') {
3837
3837
  const sStr = termToJsString(g.s);
3838
3838
  const oStr = termToJsString(g.o);
3839
3839
  if (sStr === null || oStr === null) return [];
@@ -3849,16 +3849,16 @@ function isBuiltinPred(p) {
3849
3849
  const v = p.value;
3850
3850
 
3851
3851
  // Treat RDF Collections as list-term builtins too.
3852
- if (v === RDF_NS + "first" || v === RDF_NS + "rest") {
3852
+ if (v === RDF_NS + 'first' || v === RDF_NS + 'rest') {
3853
3853
  return true;
3854
3854
  }
3855
3855
 
3856
3856
  return (
3857
3857
  v.startsWith(CRYPTO_NS) ||
3858
- v.startsWith(MATH_NS) ||
3859
- v.startsWith(LOG_NS) ||
3858
+ v.startsWith(MATH_NS) ||
3859
+ v.startsWith(LOG_NS) ||
3860
3860
  v.startsWith(STRING_NS) ||
3861
- v.startsWith(TIME_NS) ||
3861
+ v.startsWith(TIME_NS) ||
3862
3862
  v.startsWith(LIST_NS)
3863
3863
  );
3864
3864
  }
@@ -3878,10 +3878,10 @@ function standardizeRule(rule, gen) {
3878
3878
  return new Var(vmap[t.name]);
3879
3879
  }
3880
3880
  if (t instanceof ListTerm) {
3881
- return new ListTerm(t.elems.map(e => renameTerm(e, vmap, genArr)));
3881
+ return new ListTerm(t.elems.map((e) => renameTerm(e, vmap, genArr)));
3882
3882
  }
3883
3883
  if (t instanceof OpenListTerm) {
3884
- const newXs = t.prefix.map(e => renameTerm(e, vmap, genArr));
3884
+ const newXs = t.prefix.map((e) => renameTerm(e, vmap, genArr));
3885
3885
  if (!vmap.hasOwnProperty(t.tailVar)) {
3886
3886
  const name = `${t.tailVar}__${genArr[0]}`;
3887
3887
  genArr[0] += 1;
@@ -3892,13 +3892,7 @@ function standardizeRule(rule, gen) {
3892
3892
  }
3893
3893
  if (t instanceof FormulaTerm) {
3894
3894
  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
- )
3895
+ t.triples.map((tr) => new Triple(renameTerm(tr.s, vmap, genArr), renameTerm(tr.p, vmap, genArr), renameTerm(tr.o, vmap, genArr))),
3902
3896
  );
3903
3897
  }
3904
3898
  return t;
@@ -3906,32 +3900,16 @@ function standardizeRule(rule, gen) {
3906
3900
 
3907
3901
  const vmap2 = {};
3908
3902
  const premise = rule.premise.map(
3909
- tr =>
3910
- new Triple(
3911
- renameTerm(tr.s, vmap2, gen),
3912
- renameTerm(tr.p, vmap2, gen),
3913
- renameTerm(tr.o, vmap2, gen)
3914
- )
3903
+ (tr) => new Triple(renameTerm(tr.s, vmap2, gen), renameTerm(tr.p, vmap2, gen), renameTerm(tr.o, vmap2, gen)),
3915
3904
  );
3916
3905
  const conclusion = rule.conclusion.map(
3917
- tr =>
3918
- new Triple(
3919
- renameTerm(tr.s, vmap2, gen),
3920
- renameTerm(tr.p, vmap2, gen),
3921
- renameTerm(tr.o, vmap2, gen)
3922
- )
3923
- );
3924
- return new Rule(
3925
- premise,
3926
- conclusion,
3927
- rule.isForward,
3928
- rule.isFuse,
3929
- rule.headBlankLabels
3906
+ (tr) => new Triple(renameTerm(tr.s, vmap2, gen), renameTerm(tr.p, vmap2, gen), renameTerm(tr.o, vmap2, gen)),
3930
3907
  );
3908
+ return new Rule(premise, conclusion, rule.isForward, rule.isFuse, rule.headBlankLabels);
3931
3909
  }
3932
3910
 
3933
3911
  function listHasTriple(list, tr) {
3934
- return list.some(t => triplesEqual(t, tr));
3912
+ return list.some((t) => triplesEqual(t, tr));
3935
3913
  }
3936
3914
 
3937
3915
  // ============================================================================
@@ -3951,14 +3929,23 @@ function listHasTriple(list, tr) {
3951
3929
  // This is semantics-preserving for the ongoing proof state.
3952
3930
 
3953
3931
  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; }
3932
+ if (t instanceof Var) {
3933
+ out.add(t.name);
3934
+ return;
3935
+ }
3936
+ if (t instanceof ListTerm) {
3937
+ for (const e of t.elems) gcCollectVarsInTerm(e, out);
3938
+ return;
3939
+ }
3956
3940
  if (t instanceof OpenListTerm) {
3957
3941
  for (const e of t.prefix) gcCollectVarsInTerm(e, out);
3958
3942
  out.add(t.tailVar);
3959
3943
  return;
3960
3944
  }
3961
- if (t instanceof FormulaTerm) { for (const tr of t.triples) gcCollectVarsInTriple(tr, out); return; }
3945
+ if (t instanceof FormulaTerm) {
3946
+ for (const tr of t.triples) gcCollectVarsInTriple(tr, out);
3947
+ return;
3948
+ }
3962
3949
  }
3963
3950
 
3964
3951
  function gcCollectVarsInTriple(tr, out) {
@@ -4018,8 +4005,7 @@ function maybeCompactSubst(subst, goals, answerVars, depth) {
4018
4005
  return gcCompactForGoals(subst, goals, answerVars);
4019
4006
  }
4020
4007
 
4021
-
4022
- function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
4008
+ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen) {
4023
4009
  // Iterative DFS over proof states using an explicit stack.
4024
4010
  // Each state carries its own substitution and remaining goals.
4025
4011
  const results = [];
@@ -4028,7 +4014,6 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
4028
4014
  const initialSubst = subst ? { ...subst } : {};
4029
4015
  const initialVisited = visited ? visited.slice() : [];
4030
4016
 
4031
-
4032
4017
  // Variables from the original goal list (needed by the caller to instantiate conclusions)
4033
4018
  const answerVars = new Set();
4034
4019
  gcCollectVarsInGoals(initialGoals, answerVars);
@@ -4038,7 +4023,12 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
4038
4023
  }
4039
4024
 
4040
4025
  const stack = [
4041
- { goals: initialGoals, subst: initialSubst, depth: depth || 0, visited: initialVisited }
4026
+ {
4027
+ goals: initialGoals,
4028
+ subst: initialSubst,
4029
+ depth: depth || 0,
4030
+ visited: initialVisited,
4031
+ },
4042
4032
  ];
4043
4033
 
4044
4034
  while (stack.length) {
@@ -4068,7 +4058,7 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
4068
4058
  goals: restGoals,
4069
4059
  subst: nextSubst,
4070
4060
  depth: state.depth + 1,
4071
- visited: state.visited
4061
+ visited: state.visited,
4072
4062
  });
4073
4063
  }
4074
4064
  }
@@ -4097,7 +4087,7 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
4097
4087
  goals: restGoals,
4098
4088
  subst: nextSubst,
4099
4089
  depth: state.depth + 1,
4100
- visited: state.visited
4090
+ visited: state.visited,
4101
4091
  });
4102
4092
  }
4103
4093
  }
@@ -4118,7 +4108,7 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
4118
4108
  goals: restGoals,
4119
4109
  subst: nextSubst,
4120
4110
  depth: state.depth + 1,
4121
- visited: state.visited
4111
+ visited: state.visited,
4122
4112
  });
4123
4113
  }
4124
4114
  }
@@ -4127,8 +4117,7 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
4127
4117
  // 4) Backward rules (indexed by head predicate)
4128
4118
  if (goal0.p instanceof Iri) {
4129
4119
  ensureBackRuleIndexes(backRules);
4130
- const candRules =
4131
- (backRules.__byHeadPred.get(goal0.p.value) || []).concat(backRules.__wildHeadPred);
4120
+ const candRules = (backRules.__byHeadPred.get(goal0.p.value) || []).concat(backRules.__wildHeadPred);
4132
4121
 
4133
4122
  for (const r of candRules) {
4134
4123
  if (r.conclusion.length !== 1) continue;
@@ -4141,7 +4130,7 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
4141
4130
  const deltaHead = unifyTriple(head, goal0, {});
4142
4131
  if (deltaHead === null) continue;
4143
4132
 
4144
- const body = rStd.premise.map(b => applySubstTriple(b, deltaHead));
4133
+ const body = rStd.premise.map((b) => applySubstTriple(b, deltaHead));
4145
4134
  const composed = composeSubst(state.subst, deltaHead);
4146
4135
  if (composed === null) continue;
4147
4136
 
@@ -4151,7 +4140,7 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
4151
4140
  goals: newGoals,
4152
4141
  subst: nextSubst,
4153
4142
  depth: state.depth + 1,
4154
- visited: visitedForRules
4143
+ visited: visitedForRules,
4155
4144
  });
4156
4145
  }
4157
4146
  }
@@ -4189,50 +4178,54 @@ function forwardChain(facts, forwardRules, backRules) {
4189
4178
 
4190
4179
  // Inference fuse
4191
4180
  if (r.isFuse && sols.length) {
4192
- console.log("# Inference fuse triggered: a { ... } => false. rule fired.");
4181
+ console.log('# Inference fuse triggered: a { ... } => false. rule fired.');
4193
4182
  process.exit(2);
4194
4183
  }
4195
4184
 
4196
4185
  for (const s of sols) {
4197
- const instantiatedPremises = r.premise.map(b => applySubstTriple(b, s));
4186
+ const instantiatedPremises = r.premise.map((b) => applySubstTriple(b, s));
4198
4187
 
4199
4188
  for (const cpat of r.conclusion) {
4200
4189
  const instantiated = applySubstTriple(cpat, s);
4201
4190
 
4202
4191
  const isFwRuleTriple =
4203
4192
  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
- );
4193
+ ((instantiated.s instanceof FormulaTerm && instantiated.o instanceof FormulaTerm) ||
4194
+ (instantiated.s instanceof Literal && instantiated.s.value === 'true' && instantiated.o instanceof FormulaTerm) ||
4195
+ (instantiated.s instanceof FormulaTerm && instantiated.o instanceof Literal && instantiated.o.value === 'true'));
4209
4196
 
4210
4197
  const isBwRuleTriple =
4211
4198
  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
- );
4199
+ ((instantiated.s instanceof FormulaTerm && instantiated.o instanceof FormulaTerm) ||
4200
+ (instantiated.s instanceof FormulaTerm && instantiated.o instanceof Literal && instantiated.o.value === 'true') ||
4201
+ (instantiated.s instanceof Literal && instantiated.s.value === 'true' && instantiated.o instanceof FormulaTerm));
4217
4202
 
4218
4203
  if (isFwRuleTriple || isBwRuleTriple) {
4219
4204
  if (!hasFactIndexed(facts, instantiated)) {
4220
4205
  factList.push(instantiated);
4221
4206
  pushFactIndexed(facts, instantiated);
4222
- derivedForward.push(new DerivedFact(instantiated, r, instantiatedPremises.slice(), { ...s }));
4207
+ derivedForward.push(
4208
+ new DerivedFact(instantiated, r, instantiatedPremises.slice(), {
4209
+ ...s,
4210
+ }),
4211
+ );
4223
4212
  changed = true;
4224
4213
  }
4225
4214
 
4226
4215
  // Promote rule-producing triples to live rules, treating literal true as {}.
4227
4216
  const left =
4228
- instantiated.s instanceof FormulaTerm ? instantiated.s.triples :
4229
- (instantiated.s instanceof Literal && instantiated.s.value === "true") ? [] :
4230
- null;
4217
+ instantiated.s instanceof FormulaTerm
4218
+ ? instantiated.s.triples
4219
+ : instantiated.s instanceof Literal && instantiated.s.value === 'true'
4220
+ ? []
4221
+ : null;
4231
4222
 
4232
4223
  const right =
4233
- instantiated.o instanceof FormulaTerm ? instantiated.o.triples :
4234
- (instantiated.o instanceof Literal && instantiated.o.value === "true") ? [] :
4235
- null;
4224
+ instantiated.o instanceof FormulaTerm
4225
+ ? instantiated.o.triples
4226
+ : instantiated.o instanceof Literal && instantiated.o.value === 'true'
4227
+ ? []
4228
+ : null;
4236
4229
 
4237
4230
  if (left !== null && right !== null) {
4238
4231
  if (isFwRuleTriple) {
@@ -4243,11 +4236,11 @@ function forwardChain(facts, forwardRules, backRules) {
4243
4236
  const newRule = new Rule(premise, conclusion, true, false, headBlankLabels);
4244
4237
 
4245
4238
  const already = forwardRules.some(
4246
- rr =>
4239
+ (rr) =>
4247
4240
  rr.isForward === newRule.isForward &&
4248
4241
  rr.isFuse === newRule.isFuse &&
4249
4242
  triplesListEqual(rr.premise, newRule.premise) &&
4250
- triplesListEqual(rr.conclusion, newRule.conclusion)
4243
+ triplesListEqual(rr.conclusion, newRule.conclusion),
4251
4244
  );
4252
4245
  if (!already) forwardRules.push(newRule);
4253
4246
  } else if (isBwRuleTriple) {
@@ -4257,11 +4250,11 @@ function forwardChain(facts, forwardRules, backRules) {
4257
4250
  const newRule = new Rule(premise, conclusion, false, false, headBlankLabels);
4258
4251
 
4259
4252
  const already = backRules.some(
4260
- rr =>
4253
+ (rr) =>
4261
4254
  rr.isForward === newRule.isForward &&
4262
4255
  rr.isFuse === newRule.isFuse &&
4263
4256
  triplesListEqual(rr.premise, newRule.premise) &&
4264
- triplesListEqual(rr.conclusion, newRule.conclusion)
4257
+ triplesListEqual(rr.conclusion, newRule.conclusion),
4265
4258
  );
4266
4259
  if (!already) {
4267
4260
  backRules.push(newRule);
@@ -4304,44 +4297,44 @@ function termToN3(t, pref) {
4304
4297
  const i = t.value;
4305
4298
  const q = pref.shrinkIri(i);
4306
4299
  if (q !== null) return q;
4307
- if (i.startsWith("_:")) return i;
4300
+ if (i.startsWith('_:')) return i;
4308
4301
  return `<${i}>`;
4309
4302
  }
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
- }
4303
+ if (t instanceof Literal) {
4304
+ const [lex, dt] = literalParts(t.value);
4305
+
4306
+ // Pretty-print xsd:boolean as bare true/false
4307
+ if (dt === XSD_NS + 'boolean') {
4308
+ const v = stripQuotes(lex);
4309
+ if (v === 'true' || v === 'false') return v;
4310
+ // optional: normalize 1/0 too
4311
+ if (v === '1') return 'true';
4312
+ if (v === '0') return 'false';
4313
+ }
4314
+
4315
+ if (!dt) return t.value; // keep numbers, booleans, lang-tagged strings, etc.
4316
+ const qdt = pref.shrinkIri(dt);
4317
+ if (qdt !== null) return `${lex}^^${qdt}`; // e.g. ^^rdf:JSON
4318
+ return `${lex}^^<${dt}>`; // fallback
4319
+ }
4327
4320
  if (t instanceof Var) return `?${t.name}`;
4328
4321
  if (t instanceof Blank) return t.label;
4329
4322
  if (t instanceof ListTerm) {
4330
- const inside = t.elems.map(e => termToN3(e, pref));
4331
- return "(" + inside.join(" ") + ")";
4323
+ const inside = t.elems.map((e) => termToN3(e, pref));
4324
+ return '(' + inside.join(' ') + ')';
4332
4325
  }
4333
4326
  if (t instanceof OpenListTerm) {
4334
- const inside = t.prefix.map(e => termToN3(e, pref));
4335
- inside.push("?" + t.tailVar);
4336
- return "(" + inside.join(" ") + ")";
4327
+ const inside = t.prefix.map((e) => termToN3(e, pref));
4328
+ inside.push('?' + t.tailVar);
4329
+ return '(' + inside.join(' ') + ')';
4337
4330
  }
4338
4331
  if (t instanceof FormulaTerm) {
4339
- let s = "{\n";
4332
+ let s = '{\n';
4340
4333
  for (const tr of t.triples) {
4341
4334
  let line = tripleToN3(tr, pref).trimEnd();
4342
- if (line) s += " " + line + "\n";
4335
+ if (line) s += ' ' + line + '\n';
4343
4336
  }
4344
- s += "}";
4337
+ s += '}';
4345
4338
  return s;
4346
4339
  }
4347
4340
  return JSON.stringify(t);
@@ -4362,83 +4355,72 @@ function tripleToN3(tr, prefixes) {
4362
4355
  }
4363
4356
 
4364
4357
  const s = termToN3(tr.s, prefixes);
4365
- const p =
4366
- isRdfTypePred(tr.p) ? "a"
4367
- : isOwlSameAsPred(tr.p) ? "="
4368
- : termToN3(tr.p, prefixes);
4358
+ const p = isRdfTypePred(tr.p) ? 'a' : isOwlSameAsPred(tr.p) ? '=' : termToN3(tr.p, prefixes);
4369
4359
  const o = termToN3(tr.o, prefixes);
4370
4360
 
4371
4361
  return `${s} ${p} ${o} .`;
4372
4362
  }
4373
4363
 
4374
4364
  function printExplanation(df, prefixes) {
4375
- console.log(
4376
- "# ----------------------------------------------------------------------"
4377
- );
4378
- console.log("# Proof for derived triple:");
4365
+ console.log('# ----------------------------------------------------------------------');
4366
+ console.log('# Proof for derived triple:');
4379
4367
 
4380
4368
  // Fact line(s), indented 2 spaces after '# '
4381
4369
  for (const line of tripleToN3(df.fact, prefixes).split(/\r?\n/)) {
4382
- const stripped = line.replace(/\s+$/, "");
4370
+ const stripped = line.replace(/\s+$/, '');
4383
4371
  if (stripped) {
4384
- console.log("# " + stripped);
4372
+ console.log('# ' + stripped);
4385
4373
  }
4386
4374
  }
4387
4375
 
4388
4376
  if (!df.premises.length) {
4389
- console.log(
4390
- "# This triple is the head of a forward rule with an empty premise,"
4391
- );
4392
- console.log(
4393
- "# so it holds unconditionally whenever the program is loaded."
4394
- );
4377
+ console.log('# This triple is the head of a forward rule with an empty premise,');
4378
+ console.log('# so it holds unconditionally whenever the program is loaded.');
4395
4379
  } else {
4396
- console.log(
4397
- "# It holds because the following instance of the rule body is provable:"
4398
- );
4380
+ console.log('# It holds because the following instance of the rule body is provable:');
4399
4381
 
4400
4382
  // Premises, also indented 2 spaces after '# '
4401
4383
  for (const prem of df.premises) {
4402
4384
  for (const line of tripleToN3(prem, prefixes).split(/\r?\n/)) {
4403
- const stripped = line.replace(/\s+$/, "");
4385
+ const stripped = line.replace(/\s+$/, '');
4404
4386
  if (stripped) {
4405
- console.log("# " + stripped);
4387
+ console.log('# ' + stripped);
4406
4388
  }
4407
4389
  }
4408
4390
  }
4409
4391
 
4410
- console.log("# via the schematic forward rule:");
4392
+ console.log('# via the schematic forward rule:');
4411
4393
 
4412
4394
  // Rule pretty-printed
4413
- console.log("# {");
4395
+ console.log('# {');
4414
4396
  for (const tr of df.rule.premise) {
4415
4397
  for (const line of tripleToN3(tr, prefixes).split(/\r?\n/)) {
4416
- const stripped = line.replace(/\s+$/, "");
4398
+ const stripped = line.replace(/\s+$/, '');
4417
4399
  if (stripped) {
4418
- console.log("# " + stripped);
4400
+ console.log('# ' + stripped);
4419
4401
  }
4420
4402
  }
4421
4403
  }
4422
- console.log("# } => {");
4404
+ console.log('# } => {');
4423
4405
  for (const tr of df.rule.conclusion) {
4424
4406
  for (const line of tripleToN3(tr, prefixes).split(/\r?\n/)) {
4425
- const stripped = line.replace(/\s+$/, "");
4407
+ const stripped = line.replace(/\s+$/, '');
4426
4408
  if (stripped) {
4427
- console.log("# " + stripped);
4409
+ console.log('# ' + stripped);
4428
4410
  }
4429
4411
  }
4430
4412
  }
4431
- console.log("# } .");
4413
+ console.log('# } .');
4432
4414
  }
4433
4415
 
4434
4416
  // Substitution block
4435
4417
  const ruleVars = varsInRule(df.rule);
4436
4418
  const visibleNames = Object.keys(df.subst)
4437
- .filter(name => ruleVars.has(name))
4419
+ .filter((name) => ruleVars.has(name))
4438
4420
  .sort();
4439
4421
 
4440
4422
  if (visibleNames.length) {
4441
- console.log("# with substitution (on rule variables):");
4423
+ console.log('# with substitution (on rule variables):');
4442
4424
  for (const v of visibleNames) {
4443
4425
  const fullTerm = applySubstTerm(new Var(v), df.subst);
4444
4426
  const rendered = termToN3(fullTerm, prefixes);
@@ -4446,37 +4428,33 @@ function printExplanation(df, prefixes) {
4446
4428
 
4447
4429
  if (lines.length === 1) {
4448
4430
  // single-line term
4449
- const stripped = lines[0].replace(/\s+$/, "");
4431
+ const stripped = lines[0].replace(/\s+$/, '');
4450
4432
  if (stripped) {
4451
- console.log("# ?" + v + " = " + stripped);
4433
+ console.log('# ?' + v + ' = ' + stripped);
4452
4434
  }
4453
4435
  } else {
4454
4436
  // multi-line term (e.g. a formula)
4455
4437
  const first = lines[0].trimEnd(); // usually "{"
4456
4438
  if (first) {
4457
- console.log("# ?" + v + " = " + first);
4439
+ console.log('# ?' + v + ' = ' + first);
4458
4440
  }
4459
4441
  for (let i = 1; i < lines.length; i++) {
4460
4442
  const stripped = lines[i].trim();
4461
4443
  if (!stripped) continue;
4462
4444
  if (i === lines.length - 1) {
4463
4445
  // closing brace
4464
- console.log("# " + stripped);
4446
+ console.log('# ' + stripped);
4465
4447
  } else {
4466
4448
  // inner triple lines
4467
- console.log("# " + stripped);
4449
+ console.log('# ' + stripped);
4468
4450
  }
4469
4451
  }
4470
4452
  }
4471
4453
  }
4472
4454
  }
4473
4455
 
4474
- console.log(
4475
- "# Therefore the derived triple above is entailed by the rules and facts."
4476
- );
4477
- console.log(
4478
- "# ----------------------------------------------------------------------\n"
4479
- );
4456
+ console.log('# Therefore the derived triple above is entailed by the rules and facts.');
4457
+ console.log('# ----------------------------------------------------------------------\n');
4480
4458
  }
4481
4459
 
4482
4460
  // ============================================================================
@@ -4486,19 +4464,19 @@ function printExplanation(df, prefixes) {
4486
4464
  // Turn RDF Collections described with rdf:first/rdf:rest (+ rdf:nil) into ListTerm terms.
4487
4465
  // This mutates triples/rules in-place so list:* builtins work on RDF-serialized lists too.
4488
4466
  function materializeRdfLists(triples, forwardRules, backwardRules) {
4489
- const RDF_FIRST = RDF_NS + "first";
4490
- const RDF_REST = RDF_NS + "rest";
4491
- const RDF_NIL = RDF_NS + "nil";
4467
+ const RDF_FIRST = RDF_NS + 'first';
4468
+ const RDF_REST = RDF_NS + 'rest';
4469
+ const RDF_NIL = RDF_NS + 'nil';
4492
4470
 
4493
4471
  function nodeKey(t) {
4494
- if (t instanceof Blank) return "B:" + t.label;
4495
- if (t instanceof Iri) return "I:" + t.value;
4472
+ if (t instanceof Blank) return 'B:' + t.label;
4473
+ if (t instanceof Iri) return 'I:' + t.value;
4496
4474
  return null;
4497
4475
  }
4498
4476
 
4499
4477
  // Collect first/rest arcs from *input triples*
4500
4478
  const firstMap = new Map(); // key(subject) -> Term (object)
4501
- const restMap = new Map(); // key(subject) -> Term (object)
4479
+ const restMap = new Map(); // key(subject) -> Term (object)
4502
4480
  for (const tr of triples) {
4503
4481
  if (!(tr.p instanceof Iri)) continue;
4504
4482
  const k = nodeKey(tr.s);
@@ -4508,8 +4486,8 @@ function materializeRdfLists(triples, forwardRules, backwardRules) {
4508
4486
  }
4509
4487
  if (!firstMap.size && !restMap.size) return;
4510
4488
 
4511
- const cache = new Map(); // key(node) -> ListTerm
4512
- const visiting = new Set(); // cycle guard
4489
+ const cache = new Map(); // key(node) -> ListTerm
4490
+ const visiting = new Set(); // cycle guard
4513
4491
 
4514
4492
  function buildListForKey(k) {
4515
4493
  if (cache.has(k)) return cache.get(k);
@@ -4517,7 +4495,7 @@ function materializeRdfLists(triples, forwardRules, backwardRules) {
4517
4495
  visiting.add(k);
4518
4496
 
4519
4497
  // rdf:nil => ()
4520
- if (k === "I:" + RDF_NIL) {
4498
+ if (k === 'I:' + RDF_NIL) {
4521
4499
  const empty = new ListTerm([]);
4522
4500
  cache.set(k, empty);
4523
4501
  visiting.delete(k);
@@ -4568,7 +4546,7 @@ function materializeRdfLists(triples, forwardRules, backwardRules) {
4568
4546
  }
4569
4547
  if (t instanceof ListTerm) {
4570
4548
  let changed = false;
4571
- const elems = t.elems.map(e => {
4549
+ const elems = t.elems.map((e) => {
4572
4550
  const r = rewriteTerm(e);
4573
4551
  if (r !== e) changed = true;
4574
4552
  return r;
@@ -4577,7 +4555,7 @@ function materializeRdfLists(triples, forwardRules, backwardRules) {
4577
4555
  }
4578
4556
  if (t instanceof OpenListTerm) {
4579
4557
  let changed = false;
4580
- const prefix = t.prefix.map(e => {
4558
+ const prefix = t.prefix.map((e) => {
4581
4559
  const r = rewriteTerm(e);
4582
4560
  if (r !== e) changed = true;
4583
4561
  return r;
@@ -4614,7 +4592,7 @@ function materializeRdfLists(triples, forwardRules, backwardRules) {
4614
4592
 
4615
4593
  function localIsoDateTimeString(d) {
4616
4594
  function pad(n, width = 2) {
4617
- return String(n).padStart(width, "0");
4595
+ return String(n).padStart(width, '0');
4618
4596
  }
4619
4597
  const year = d.getFullYear();
4620
4598
  const month = d.getMonth() + 1;
@@ -4624,27 +4602,27 @@ function localIsoDateTimeString(d) {
4624
4602
  const sec = d.getSeconds();
4625
4603
  const ms = d.getMilliseconds();
4626
4604
  const offsetMin = -d.getTimezoneOffset(); // minutes east of UTC
4627
- const sign = offsetMin >= 0 ? "+" : "-";
4605
+ const sign = offsetMin >= 0 ? '+' : '-';
4628
4606
  const abs = Math.abs(offsetMin);
4629
4607
  const oh = Math.floor(abs / 60);
4630
4608
  const om = abs % 60;
4631
- const msPart = ms ? "." + String(ms).padStart(3, "0") : "";
4609
+ const msPart = ms ? '.' + String(ms).padStart(3, '0') : '';
4632
4610
  return (
4633
4611
  pad(year, 4) +
4634
- "-" +
4612
+ '-' +
4635
4613
  pad(month) +
4636
- "-" +
4614
+ '-' +
4637
4615
  pad(day) +
4638
- "T" +
4616
+ 'T' +
4639
4617
  pad(hour) +
4640
- ":" +
4618
+ ':' +
4641
4619
  pad(min) +
4642
- ":" +
4620
+ ':' +
4643
4621
  pad(sec) +
4644
4622
  msPart +
4645
4623
  sign +
4646
4624
  pad(oh) +
4647
- ":" +
4625
+ ':' +
4648
4626
  pad(om)
4649
4627
  );
4650
4628
  }
@@ -4662,33 +4640,31 @@ function main() {
4662
4640
  // --------------------------------------------------------------------------
4663
4641
 
4664
4642
  // --version / -v: print version and exit
4665
- if (argv.includes("--version") || argv.includes("-v")) {
4643
+ if (argv.includes('--version') || argv.includes('-v')) {
4666
4644
  console.log(`eyeling v${version}`);
4667
4645
  process.exit(0);
4668
4646
  }
4669
4647
 
4670
4648
  // --no-proof-comments / -n: disable proof explanations
4671
- if (argv.includes("--no-proof-comments") || argv.includes("-n")) {
4649
+ if (argv.includes('--no-proof-comments') || argv.includes('-n')) {
4672
4650
  proofCommentsEnabled = false;
4673
4651
  }
4674
4652
 
4675
4653
  // --------------------------------------------------------------------------
4676
4654
  // Positional args (the N3 file)
4677
4655
  // --------------------------------------------------------------------------
4678
- const positional = argv.filter(a => !a.startsWith("-"));
4656
+ const positional = argv.filter((a) => !a.startsWith('-'));
4679
4657
 
4680
4658
  if (positional.length !== 1) {
4681
- console.error(
4682
- "Usage: eyeling.js [--version|-v] [--no-proof-comments|-n] <file.n3>"
4683
- );
4659
+ console.error('Usage: eyeling.js [--version|-v] [--no-proof-comments|-n] <file.n3>');
4684
4660
  process.exit(1);
4685
4661
  }
4686
4662
 
4687
4663
  const path = positional[0];
4688
4664
  let text;
4689
4665
  try {
4690
- const fs = require("fs");
4691
- text = fs.readFileSync(path, { encoding: "utf8" });
4666
+ const fs = require('fs');
4667
+ text = fs.readFileSync(path, { encoding: 'utf8' });
4692
4668
  } catch (e) {
4693
4669
  console.error(`Error reading file ${JSON.stringify(path)}: ${e.message}`);
4694
4670
  process.exit(1);
@@ -4702,14 +4678,14 @@ function main() {
4702
4678
  // Build internal ListTerm values from rdf:first/rdf:rest (+ rdf:nil) input triples
4703
4679
  materializeRdfLists(triples, frules, brules);
4704
4680
 
4705
- const facts = triples.filter(tr => isGroundTriple(tr));
4681
+ const facts = triples.filter((tr) => isGroundTriple(tr));
4706
4682
  const derived = forwardChain(facts, frules, brules);
4707
4683
 
4708
- const derivedTriples = derived.map(df => df.fact);
4684
+ const derivedTriples = derived.map((df) => df.fact);
4709
4685
  const usedPrefixes = prefixes.prefixesUsedForOutput(derivedTriples);
4710
4686
 
4711
4687
  for (const [pfx, base] of usedPrefixes) {
4712
- if (pfx === "") console.log(`@prefix : <${base}> .`);
4688
+ if (pfx === '') console.log(`@prefix : <${base}> .`);
4713
4689
  else console.log(`@prefix ${pfx}: <${base}> .`);
4714
4690
  }
4715
4691
  if (derived.length && usedPrefixes.length) console.log();
@@ -4728,4 +4704,3 @@ function main() {
4728
4704
  if (require.main === module) {
4729
4705
  main();
4730
4706
  }
4731
-