eyeling 1.5.27 → 1.5.29

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
@@ -235,6 +235,12 @@ function lex(inputText) {
235
235
  i += 2;
236
236
  continue;
237
237
  }
238
+ // N3 predicate inversion: "<-" (swap subject/object for this predicate)
239
+ if (peek(1) === "-") {
240
+ tokens.push(new Token("OpPredInvert"));
241
+ i += 2;
242
+ continue;
243
+ }
238
244
  // Otherwise IRIREF <...>
239
245
  i++; // skip '<'
240
246
  const iriChars = [];
@@ -251,15 +257,21 @@ function lex(inputText) {
251
257
  continue;
252
258
  }
253
259
 
254
- // 4) Datatype operator ^^
260
+ // 4) Path + datatype operators: !, ^, ^^
261
+ if (c === "!") {
262
+ tokens.push(new Token("OpPathFwd"));
263
+ i += 1;
264
+ continue;
265
+ }
255
266
  if (c === "^") {
256
267
  if (peek(1) === "^") {
257
268
  tokens.push(new Token("HatHat"));
258
269
  i += 2;
259
270
  continue;
260
- } else {
261
- throw new Error("Unexpected '^' (did you mean ^^?)");
262
271
  }
272
+ tokens.push(new Token("OpPathRev"));
273
+ i += 1;
274
+ continue;
263
275
  }
264
276
 
265
277
  // 5) Single-character punctuation
@@ -728,6 +740,28 @@ class Parser {
728
740
  }
729
741
 
730
742
  parseTerm() {
743
+ let t = this.parsePathItem();
744
+
745
+ while (this.peek().typ === "OpPathFwd" || this.peek().typ === "OpPathRev") {
746
+ const dir = this.next().typ; // OpPathFwd | OpPathRev
747
+ const pred = this.parsePathItem();
748
+
749
+ this.blankCounter += 1;
750
+ const bn = new Blank(`_:b${this.blankCounter}`);
751
+
752
+ this.pendingTriples.push(
753
+ dir === "OpPathFwd"
754
+ ? new Triple(t, pred, bn)
755
+ : new Triple(bn, pred, t)
756
+ );
757
+
758
+ t = bn;
759
+ }
760
+
761
+ return t;
762
+ }
763
+
764
+ parsePathItem() {
731
765
  const tok = this.next();
732
766
  const typ = tok.typ;
733
767
  const val = tok.value;
@@ -823,9 +857,14 @@ class Parser {
823
857
  while (true) {
824
858
  // Verb (can also be 'a')
825
859
  let pred;
860
+ let invert = false;
826
861
  if (this.peek().typ === "Ident" && (this.peek().value || "") === "a") {
827
862
  this.next();
828
863
  pred = new Iri(RDF_NS + "type");
864
+ } else if (this.peek().typ === "OpPredInvert") {
865
+ this.next(); // consume "<-"
866
+ pred = this.parseTerm();
867
+ invert = true;
829
868
  } else {
830
869
  pred = this.parseTerm();
831
870
  }
@@ -838,7 +877,8 @@ class Parser {
838
877
  }
839
878
 
840
879
  for (const o of objs) {
841
- this.pendingTriples.push(new Triple(subj, pred, o));
880
+ this.pendingTriples.push(invert ? new Triple(o, pred, subj)
881
+ : new Triple(subj, pred, o));
842
882
  }
843
883
 
844
884
  if (this.peek().typ === "Semicolon") {
@@ -904,16 +944,40 @@ class Parser {
904
944
 
905
945
  parsePredicateObjectList(subject) {
906
946
  const out = [];
947
+
948
+ // If the SUBJECT was a path, emit its helper triples first
949
+ if (this.pendingTriples.length > 0) {
950
+ out.push(...this.pendingTriples);
951
+ this.pendingTriples = [];
952
+ }
953
+
907
954
  while (true) {
908
955
  let verb;
956
+ let invert = false;
957
+
909
958
  if (this.peek().typ === "Ident" && (this.peek().value || "") === "a") {
910
959
  this.next();
911
960
  verb = new Iri(RDF_NS + "type");
961
+ } else if (this.peek().typ === "OpPredInvert") {
962
+ this.next(); // "<-"
963
+ verb = this.parseTerm();
964
+ invert = true;
912
965
  } else {
913
966
  verb = this.parseTerm();
914
967
  }
968
+
915
969
  const objects = this.parseObjectList();
916
- for (const o of objects) out.push(new Triple(subject, verb, o));
970
+
971
+ // If VERB or OBJECTS contained paths, their helper triples must come
972
+ // before the triples that consume the path results (Easter depends on this).
973
+ if (this.pendingTriples.length > 0) {
974
+ out.push(...this.pendingTriples);
975
+ this.pendingTriples = [];
976
+ }
977
+
978
+ for (const o of objects) {
979
+ out.push(new Triple(invert ? o : subject, verb, invert ? subject : o));
980
+ }
917
981
 
918
982
  if (this.peek().typ === "Semicolon") {
919
983
  this.next();
@@ -923,11 +987,6 @@ class Parser {
923
987
  break;
924
988
  }
925
989
 
926
- if (this.pendingTriples.length > 0) {
927
- out.push(...this.pendingTriples);
928
- this.pendingTriples = [];
929
- }
930
-
931
990
  return out;
932
991
  }
933
992
 
@@ -2309,6 +2368,47 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2309
2368
  }
2310
2369
  }
2311
2370
 
2371
+ // math:integerQuotient
2372
+ // Schema: ( $a $b ) math:integerQuotient $q
2373
+ // Cwm: divide first integer by second integer, ignoring remainder. :contentReference[oaicite:1]{index=1}
2374
+ if (g.p instanceof Iri && g.p.value === MATH_NS + "integerQuotient") {
2375
+ if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
2376
+ const [a0, b0] = g.s.elems;
2377
+
2378
+ // Prefer exact integer division using BigInt when possible
2379
+ const ai = parseIntLiteral(a0);
2380
+ const bi = parseIntLiteral(b0);
2381
+ if (ai !== null && bi !== null) {
2382
+ if (bi === 0n) return [];
2383
+ const q = ai / bi; // BigInt division truncates toward zero
2384
+ const lit = new Literal(q.toString());
2385
+ if (g.o instanceof Var) {
2386
+ const s2 = { ...subst };
2387
+ s2[g.o.name] = lit;
2388
+ return [s2];
2389
+ }
2390
+ const s2 = unifyTerm(g.o, lit, subst);
2391
+ return s2 !== null ? [s2] : [];
2392
+ }
2393
+
2394
+ // Fallback: allow Number literals that still represent integers
2395
+ const a = parseNum(a0);
2396
+ const b = parseNum(b0);
2397
+ if (a === null || b === null) return [];
2398
+ if (!Number.isFinite(a) || !Number.isFinite(b) || b === 0) return [];
2399
+ if (!Number.isInteger(a) || !Number.isInteger(b)) return [];
2400
+
2401
+ const q = Math.trunc(a / b);
2402
+ const lit = new Literal(String(q));
2403
+ if (g.o instanceof Var) {
2404
+ const s2 = { ...subst };
2405
+ s2[g.o.name] = lit;
2406
+ return [s2];
2407
+ }
2408
+ const s2 = unifyTerm(g.o, lit, subst);
2409
+ return s2 !== null ? [s2] : [];
2410
+ }
2411
+
2312
2412
  // math:exponentiation
2313
2413
  if (g.p instanceof Iri && g.p.value === MATH_NS + "exponentiation") {
2314
2414
  if (g.s instanceof ListTerm && g.s.elems.length === 2) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.5.27",
3
+ "version": "1.5.29",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [
package/test/api.test.js CHANGED
@@ -636,6 +636,42 @@ ${U('s')} ${U('p')} ${U('o')}. # another trailing comment
636
636
  world"""@en.`,
637
637
  expect: [new RegExp(`${EX}s>\\s+<${EX}q>\\s+(?:"""Hello[\\s\\S]*?world"""@en|"Hello\\\\nworld"@en)\\s*\\.`)],
638
638
  },
639
+
640
+ { name: '44 syntax: "<-" in predicate position swaps subject and object',
641
+ opt: { proofComments: false },
642
+ input: ` { ?s ${U('p')} ?o } => { ?s ${U('q')} ?o }.
643
+ ${U('a')} <-${U('p')} ${U('b')}.`,
644
+ expect: [new RegExp(`${EX}b>\\s+<${EX}q>\\s+<${EX}a>\\s*\\.`)],
645
+ },
646
+
647
+ { name: '45 syntax: "<-" works inside blank node property lists ([ ... ])',
648
+ opt: { proofComments: false },
649
+ input: ` ${U('s')} ${U('p')} [ <-${U('r')} ${U('o')} ].
650
+ { ${U('o')} ${U('r')} ?x } => { ?x ${U('q')} ${U('k')} }.`,
651
+ expect: [new RegExp(`_:b1\\s+<${EX}q>\\s+<${EX}k>\\s*\\.`)],
652
+ },
653
+
654
+ { name: '46 syntax: N3 resource paths (! / ^) expand to blank-node triples (forward chain)',
655
+ opt: { proofComments: false },
656
+ input: ` ${U('joe')}!${U('hasAddress')}!${U('hasCity')} ${U('name')} "Metropolis".
657
+ { ${U('joe')} ${U('hasAddress')} ?a } => { ?a ${U('q')} "addr" }.
658
+ { ?a ${U('hasCity')} ?c } => { ?c ${U('q')} "city" }.
659
+ `,
660
+ expect: [
661
+ new RegExp(`_:b1\\s+<${EX}q>\\s+"addr"\\s*\\.`),
662
+ new RegExp(`_:b2\\s+<${EX}q>\\s+"city"\\s*\\.`),
663
+ ],
664
+ },
665
+
666
+ { name: '47 syntax: N3 resource paths support reverse steps (^) in the chain',
667
+ opt: { proofComments: false },
668
+ input: ` ${U('joe')}!${U('hasMother')}^${U('hasMother')} ${U('knows')} ${U('someone')}.
669
+ { ?sib ${U('hasMother')} ?mom. ${U('joe')} ${U('hasMother')} ?mom } => { ?sib ${U('q')} ${U('joe')} }.
670
+ `,
671
+ expect: [
672
+ new RegExp(`_:b2\\s+<${EX}q>\\s+<${EX}joe>\\s*\\.`),
673
+ ],
674
+ },
639
675
  ];
640
676
 
641
677
  let passed = 0;