eyeling 1.5.28 → 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 +91 -10
- package/package.json +1 -1
- package/test/api.test.js +22 -0
package/eyeling.js
CHANGED
|
@@ -257,15 +257,21 @@ function lex(inputText) {
|
|
|
257
257
|
continue;
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
-
// 4)
|
|
260
|
+
// 4) Path + datatype operators: !, ^, ^^
|
|
261
|
+
if (c === "!") {
|
|
262
|
+
tokens.push(new Token("OpPathFwd"));
|
|
263
|
+
i += 1;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
261
266
|
if (c === "^") {
|
|
262
267
|
if (peek(1) === "^") {
|
|
263
268
|
tokens.push(new Token("HatHat"));
|
|
264
269
|
i += 2;
|
|
265
270
|
continue;
|
|
266
|
-
} else {
|
|
267
|
-
throw new Error("Unexpected '^' (did you mean ^^?)");
|
|
268
271
|
}
|
|
272
|
+
tokens.push(new Token("OpPathRev"));
|
|
273
|
+
i += 1;
|
|
274
|
+
continue;
|
|
269
275
|
}
|
|
270
276
|
|
|
271
277
|
// 5) Single-character punctuation
|
|
@@ -734,6 +740,28 @@ class Parser {
|
|
|
734
740
|
}
|
|
735
741
|
|
|
736
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() {
|
|
737
765
|
const tok = this.next();
|
|
738
766
|
const typ = tok.typ;
|
|
739
767
|
const val = tok.value;
|
|
@@ -916,20 +944,37 @@ class Parser {
|
|
|
916
944
|
|
|
917
945
|
parsePredicateObjectList(subject) {
|
|
918
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
|
+
|
|
919
954
|
while (true) {
|
|
920
955
|
let verb;
|
|
921
956
|
let invert = false;
|
|
957
|
+
|
|
922
958
|
if (this.peek().typ === "Ident" && (this.peek().value || "") === "a") {
|
|
923
959
|
this.next();
|
|
924
960
|
verb = new Iri(RDF_NS + "type");
|
|
925
961
|
} else if (this.peek().typ === "OpPredInvert") {
|
|
926
|
-
this.next(); //
|
|
927
|
-
verb = this.parseTerm();
|
|
962
|
+
this.next(); // "<-"
|
|
963
|
+
verb = this.parseTerm();
|
|
928
964
|
invert = true;
|
|
929
965
|
} else {
|
|
930
966
|
verb = this.parseTerm();
|
|
931
967
|
}
|
|
968
|
+
|
|
932
969
|
const objects = this.parseObjectList();
|
|
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
|
+
|
|
933
978
|
for (const o of objects) {
|
|
934
979
|
out.push(new Triple(invert ? o : subject, verb, invert ? subject : o));
|
|
935
980
|
}
|
|
@@ -942,11 +987,6 @@ class Parser {
|
|
|
942
987
|
break;
|
|
943
988
|
}
|
|
944
989
|
|
|
945
|
-
if (this.pendingTriples.length > 0) {
|
|
946
|
-
out.push(...this.pendingTriples);
|
|
947
|
-
this.pendingTriples = [];
|
|
948
|
-
}
|
|
949
|
-
|
|
950
990
|
return out;
|
|
951
991
|
}
|
|
952
992
|
|
|
@@ -2328,6 +2368,47 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
2328
2368
|
}
|
|
2329
2369
|
}
|
|
2330
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
|
+
|
|
2331
2412
|
// math:exponentiation
|
|
2332
2413
|
if (g.p instanceof Iri && g.p.value === MATH_NS + "exponentiation") {
|
|
2333
2414
|
if (g.s instanceof ListTerm && g.s.elems.length === 2) {
|
package/package.json
CHANGED
package/test/api.test.js
CHANGED
|
@@ -650,6 +650,28 @@ ${U('a')} <-${U('p')} ${U('b')}.`,
|
|
|
650
650
|
{ ${U('o')} ${U('r')} ?x } => { ?x ${U('q')} ${U('k')} }.`,
|
|
651
651
|
expect: [new RegExp(`_:b1\\s+<${EX}q>\\s+<${EX}k>\\s*\\.`)],
|
|
652
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
|
+
},
|
|
653
675
|
];
|
|
654
676
|
|
|
655
677
|
let passed = 0;
|