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/README.md +42 -46
- package/examples/easter.n3 +78 -0
- package/examples/output/easter.n3 +3602 -0
- package/eyeling.js +110 -10
- package/package.json +1 -1
- package/test/api.test.js +36 -0
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)
|
|
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(
|
|
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
|
-
|
|
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
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;
|