eyeling 1.9.1 → 1.9.2
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 +855 -853
- package/package.json +1 -1
- package/src/eyeling-buitins.ts +3348 -0
- package/src/eyeling-engine.ts +0 -3346
package/eyeling.js
CHANGED
|
@@ -2484,948 +2484,620 @@ function reorderPremiseForConstraints(premise) {
|
|
|
2484
2484
|
// @ts-nocheck
|
|
2485
2485
|
/* eslint-disable */
|
|
2486
2486
|
// ===========================================================================
|
|
2487
|
-
//
|
|
2487
|
+
// BUILTINS
|
|
2488
2488
|
// ===========================================================================
|
|
2489
|
-
function
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2489
|
+
function literalParts(lit) {
|
|
2490
|
+
const cached = __literalPartsCache.get(lit);
|
|
2491
|
+
if (cached)
|
|
2492
|
+
return cached;
|
|
2493
|
+
// Split a literal into lexical form and datatype IRI (if any).
|
|
2494
|
+
// Also strip an optional language tag from the lexical form:
|
|
2495
|
+
// "\"hello\"@en" -> "\"hello\""
|
|
2496
|
+
// "\"hello\"@en^^<...>" is rejected earlier in the parser.
|
|
2497
|
+
const idx = lit.indexOf('^^');
|
|
2498
|
+
let lex = lit;
|
|
2499
|
+
let dt = null;
|
|
2500
|
+
if (idx >= 0) {
|
|
2501
|
+
lex = lit.slice(0, idx);
|
|
2502
|
+
dt = lit.slice(idx + 2).trim();
|
|
2503
|
+
if (dt.startsWith('<') && dt.endsWith('>')) {
|
|
2504
|
+
dt = dt.slice(1, -1);
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
// Strip LANGTAG from the lexical form when present.
|
|
2508
|
+
if (lex.length >= 2 && lex[0] === '"') {
|
|
2509
|
+
const lastQuote = lex.lastIndexOf('"');
|
|
2510
|
+
if (lastQuote > 0 && lastQuote < lex.length - 1 && lex[lastQuote + 1] === '@') {
|
|
2511
|
+
const lang = lex.slice(lastQuote + 2);
|
|
2512
|
+
if (/^[A-Za-z]+(?:-[A-Za-z0-9]+)*$/.test(lang)) {
|
|
2513
|
+
lex = lex.slice(0, lastQuote + 1);
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
const res = [lex, dt];
|
|
2518
|
+
__literalPartsCache.set(lit, res);
|
|
2519
|
+
return res;
|
|
2499
2520
|
}
|
|
2500
|
-
function
|
|
2501
|
-
//
|
|
2502
|
-
//
|
|
2503
|
-
|
|
2521
|
+
function literalHasLangTag(lit) {
|
|
2522
|
+
// True iff the literal is a quoted string literal with a language tag suffix,
|
|
2523
|
+
// e.g. "hello"@en or """hello"""@en.
|
|
2524
|
+
// (The parser rejects language tags combined with datatypes.)
|
|
2525
|
+
if (typeof lit !== 'string')
|
|
2504
2526
|
return false;
|
|
2505
|
-
if (
|
|
2506
|
-
return t.elems.every((e) => isGroundTermInGraph(e));
|
|
2507
|
-
if (t instanceof GraphTerm)
|
|
2508
|
-
return t.triples.every((tr) => isGroundTripleInGraph(tr));
|
|
2509
|
-
// Iri/Literal/Blank/Var are all OK inside formulas
|
|
2510
|
-
return true;
|
|
2511
|
-
}
|
|
2512
|
-
function isGroundTripleInGraph(tr) {
|
|
2513
|
-
return isGroundTermInGraph(tr.s) && isGroundTermInGraph(tr.p) && isGroundTermInGraph(tr.o);
|
|
2514
|
-
}
|
|
2515
|
-
function isGroundTerm(t) {
|
|
2516
|
-
if (t instanceof Var)
|
|
2527
|
+
if (lit.indexOf('^^') >= 0)
|
|
2517
2528
|
return false;
|
|
2518
|
-
if (
|
|
2519
|
-
return t.elems.every((e) => isGroundTerm(e));
|
|
2520
|
-
if (t instanceof OpenListTerm)
|
|
2529
|
+
if (!lit.startsWith('"'))
|
|
2521
2530
|
return false;
|
|
2522
|
-
if (
|
|
2523
|
-
|
|
2524
|
-
|
|
2531
|
+
if (lit.startsWith('"""')) {
|
|
2532
|
+
const end = lit.lastIndexOf('"""');
|
|
2533
|
+
if (end < 0)
|
|
2534
|
+
return false;
|
|
2535
|
+
const after = end + 3;
|
|
2536
|
+
return after < lit.length && lit[after] === '@';
|
|
2537
|
+
}
|
|
2538
|
+
const lastQuote = lit.lastIndexOf('"');
|
|
2539
|
+
if (lastQuote < 0)
|
|
2540
|
+
return false;
|
|
2541
|
+
const after = lastQuote + 1;
|
|
2542
|
+
return after < lit.length && lit[after] === '@';
|
|
2525
2543
|
}
|
|
2526
|
-
function
|
|
2527
|
-
|
|
2544
|
+
function isPlainStringLiteralValue(lit) {
|
|
2545
|
+
// Plain string literal: quoted, no datatype, no lang.
|
|
2546
|
+
if (typeof lit !== 'string')
|
|
2547
|
+
return false;
|
|
2548
|
+
if (lit.indexOf('^^') >= 0)
|
|
2549
|
+
return false;
|
|
2550
|
+
if (!isQuotedLexical(lit))
|
|
2551
|
+
return false;
|
|
2552
|
+
return !literalHasLangTag(lit);
|
|
2528
2553
|
}
|
|
2529
|
-
|
|
2530
|
-
//
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
if (u instanceof ListTerm)
|
|
2543
|
-
return ['List', u.elems.map(enc)];
|
|
2544
|
-
if (u instanceof OpenListTerm)
|
|
2545
|
-
return ['OpenList', u.prefix.map(enc), u.tailVar];
|
|
2546
|
-
if (u instanceof GraphTerm)
|
|
2547
|
-
return ['Graph', u.triples.map((tr) => [enc(tr.s), enc(tr.p), enc(tr.o)])];
|
|
2548
|
-
return ['Other', String(u)];
|
|
2549
|
-
}
|
|
2550
|
-
return JSON.stringify(enc(t));
|
|
2554
|
+
function literalsEquivalentAsXsdString(aLit, bLit) {
|
|
2555
|
+
// Treat "abc" and "abc"^^xsd:string as equal, but do NOT conflate language-tagged strings.
|
|
2556
|
+
if (typeof aLit !== 'string' || typeof bLit !== 'string')
|
|
2557
|
+
return false;
|
|
2558
|
+
const [alex, adt] = literalParts(aLit);
|
|
2559
|
+
const [blex, bdt] = literalParts(bLit);
|
|
2560
|
+
if (alex !== blex)
|
|
2561
|
+
return false;
|
|
2562
|
+
const aPlain = adt === null && isPlainStringLiteralValue(aLit);
|
|
2563
|
+
const bPlain = bdt === null && isPlainStringLiteralValue(bLit);
|
|
2564
|
+
const aXsdStr = adt === XSD_NS + 'string';
|
|
2565
|
+
const bXsdStr = bdt === XSD_NS + 'string';
|
|
2566
|
+
return (aPlain && bXsdStr) || (bPlain && aXsdStr);
|
|
2551
2567
|
}
|
|
2552
|
-
function
|
|
2553
|
-
//
|
|
2554
|
-
if (
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
}
|
|
2560
|
-
// Follow chains X -> Y -> ... until we hit a non-var or a cycle.
|
|
2561
|
-
let cur = first;
|
|
2562
|
-
const seen = new Set([t.name]);
|
|
2563
|
-
while (cur instanceof Var) {
|
|
2564
|
-
const name = cur.name;
|
|
2565
|
-
if (seen.has(name))
|
|
2566
|
-
break; // cycle
|
|
2567
|
-
seen.add(name);
|
|
2568
|
-
const nxt = s[name];
|
|
2569
|
-
if (!nxt)
|
|
2570
|
-
break;
|
|
2571
|
-
cur = nxt;
|
|
2572
|
-
}
|
|
2573
|
-
if (cur instanceof Var) {
|
|
2574
|
-
// Still a var: keep it as is (no need to clone)
|
|
2575
|
-
return cur;
|
|
2576
|
-
}
|
|
2577
|
-
// Bound to a non-var term: apply substitution recursively in case it
|
|
2578
|
-
// contains variables inside.
|
|
2579
|
-
return applySubstTerm(cur, s);
|
|
2568
|
+
function normalizeLiteralForFastKey(lit) {
|
|
2569
|
+
// Canonicalize so that "abc" and "abc"^^xsd:string share the same index/dedup key.
|
|
2570
|
+
if (typeof lit !== 'string')
|
|
2571
|
+
return lit;
|
|
2572
|
+
const [lex, dt] = literalParts(lit);
|
|
2573
|
+
if (dt === XSD_NS + 'string') {
|
|
2574
|
+
return `${lex}^^<${XSD_NS}string>`;
|
|
2580
2575
|
}
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
return new ListTerm(t.elems.map((e) => applySubstTerm(e, s)));
|
|
2576
|
+
if (dt === null && isPlainStringLiteralValue(lit)) {
|
|
2577
|
+
return `${lex}^^<${XSD_NS}string>`;
|
|
2584
2578
|
}
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
else {
|
|
2597
|
-
return new OpenListTerm(newPrefix, t.tailVar);
|
|
2598
|
-
}
|
|
2599
|
-
}
|
|
2600
|
-
else {
|
|
2601
|
-
return new OpenListTerm(newPrefix, t.tailVar);
|
|
2602
|
-
}
|
|
2579
|
+
return lit;
|
|
2580
|
+
}
|
|
2581
|
+
function stripQuotes(lex) {
|
|
2582
|
+
if (typeof lex !== 'string')
|
|
2583
|
+
return lex;
|
|
2584
|
+
// Handle both short ('...' / "...") and long ('''...''' / """...""") forms.
|
|
2585
|
+
if (lex.length >= 6) {
|
|
2586
|
+
if (lex.startsWith('"""') && lex.endsWith('"""'))
|
|
2587
|
+
return lex.slice(3, -3);
|
|
2588
|
+
if (lex.startsWith("'''") && lex.endsWith("'''"))
|
|
2589
|
+
return lex.slice(3, -3);
|
|
2603
2590
|
}
|
|
2604
|
-
if (
|
|
2605
|
-
|
|
2591
|
+
if (lex.length >= 2) {
|
|
2592
|
+
const a = lex[0];
|
|
2593
|
+
const b = lex[lex.length - 1];
|
|
2594
|
+
if ((a === '"' && b === '"') || (a === "'" && b === "'"))
|
|
2595
|
+
return lex.slice(1, -1);
|
|
2606
2596
|
}
|
|
2607
|
-
return
|
|
2608
|
-
}
|
|
2609
|
-
function applySubstTriple(tr, s) {
|
|
2610
|
-
return new Triple(applySubstTerm(tr.s, s), applySubstTerm(tr.p, s), applySubstTerm(tr.o, s));
|
|
2611
|
-
}
|
|
2612
|
-
function iriValue(t) {
|
|
2613
|
-
return t instanceof Iri ? t.value : null;
|
|
2597
|
+
return lex;
|
|
2614
2598
|
}
|
|
2615
|
-
function
|
|
2616
|
-
|
|
2599
|
+
function termToJsXsdStringNoLang(t) {
|
|
2600
|
+
// Strict xsd:string extraction *without* language tags.
|
|
2601
|
+
// Accept:
|
|
2602
|
+
// - plain string literals ("...")
|
|
2603
|
+
// - "..."^^xsd:string
|
|
2604
|
+
// Reject:
|
|
2605
|
+
// - language-tagged strings ("..."@en)
|
|
2606
|
+
// - any other datatype
|
|
2607
|
+
if (!(t instanceof Literal))
|
|
2617
2608
|
return null;
|
|
2618
|
-
|
|
2619
|
-
for (let i = 0; i < prefix.length; i++) {
|
|
2620
|
-
s2 = unifyTerm(prefix[i], ys[i], s2);
|
|
2621
|
-
if (s2 === null)
|
|
2622
|
-
return null;
|
|
2623
|
-
}
|
|
2624
|
-
const rest = new ListTerm(ys.slice(prefix.length));
|
|
2625
|
-
s2 = unifyTerm(new Var(tailv), rest, s2);
|
|
2626
|
-
if (s2 === null)
|
|
2609
|
+
if (literalHasLangTag(t.value))
|
|
2627
2610
|
return null;
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
function unifyGraphTriples(xs, ys, subst) {
|
|
2631
|
-
if (xs.length !== ys.length)
|
|
2611
|
+
const [lex, dt] = literalParts(t.value);
|
|
2612
|
+
if (!isQuotedLexical(lex))
|
|
2632
2613
|
return null;
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
const s3 = step(i + 1, s2);
|
|
2654
|
-
if (s3 !== null)
|
|
2655
|
-
return s3;
|
|
2656
|
-
used[j] = false;
|
|
2657
|
-
}
|
|
2614
|
+
if (dt !== null && dt !== XSD_NS + 'string' && dt !== 'xsd:string')
|
|
2615
|
+
return null;
|
|
2616
|
+
return decodeN3StringEscapes(stripQuotes(lex));
|
|
2617
|
+
}
|
|
2618
|
+
function termToJsString(t) {
|
|
2619
|
+
// Domain is xsd:string for SWAP/N3 string builtins (string:*).
|
|
2620
|
+
//
|
|
2621
|
+
// Per the N3 Builtins spec, when the domain is xsd:string we must be able to
|
|
2622
|
+
// cast *any* IRI or literal value (incl. numeric, boolean, dateTime, anyURI,
|
|
2623
|
+
// rdf:langString, and plain literals) to a string.
|
|
2624
|
+
//
|
|
2625
|
+
// We implement this as:
|
|
2626
|
+
// - IRI -> its IRI string
|
|
2627
|
+
// - Literal:
|
|
2628
|
+
// * quoted lexical form: decode N3/Turtle escapes and strip quotes
|
|
2629
|
+
// * unquoted lexical form: use as-is (e.g., 1234, true, 1971-..., 1.23E4)
|
|
2630
|
+
// - Everything else (blank nodes, lists, formulas, vars) -> fail
|
|
2631
|
+
if (t instanceof Iri)
|
|
2632
|
+
return t.value;
|
|
2633
|
+
if (!(t instanceof Literal))
|
|
2658
2634
|
return null;
|
|
2635
|
+
const [lex, _dt] = literalParts(t.value);
|
|
2636
|
+
if (isQuotedLexical(lex)) {
|
|
2637
|
+
// Interpret N3/Turtle string escapes (\" \n \uXXXX \UXXXXXXXX …)
|
|
2638
|
+
// to obtain the actual string value.
|
|
2639
|
+
return decodeN3StringEscapes(stripQuotes(lex));
|
|
2659
2640
|
}
|
|
2660
|
-
|
|
2641
|
+
// Unquoted lexical (numbers/booleans/dateTimes, etc.)
|
|
2642
|
+
return typeof lex === 'string' ? lex : String(lex);
|
|
2661
2643
|
}
|
|
2662
|
-
function
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
});
|
|
2644
|
+
function makeStringLiteral(str) {
|
|
2645
|
+
// JSON.stringify gives us a valid N3/Turtle-style quoted string
|
|
2646
|
+
// (with proper escaping for quotes, backslashes, newlines, …).
|
|
2647
|
+
return internLiteral(JSON.stringify(str));
|
|
2667
2648
|
}
|
|
2668
|
-
function
|
|
2669
|
-
//
|
|
2670
|
-
//
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2649
|
+
function termToJsStringDecoded(t) {
|
|
2650
|
+
// Like termToJsString, but for short literals it *also* interprets escapes
|
|
2651
|
+
// (\" \n \uXXXX …) by attempting JSON.parse on the quoted lexical form.
|
|
2652
|
+
if (!(t instanceof Literal))
|
|
2653
|
+
return null;
|
|
2654
|
+
const [lex, _dt] = literalParts(t.value);
|
|
2655
|
+
// Long strings: """ ... """ are taken verbatim.
|
|
2656
|
+
if (lex.length >= 6 && lex.startsWith('"""') && lex.endsWith('"""')) {
|
|
2657
|
+
return lex.slice(3, -3);
|
|
2658
|
+
}
|
|
2659
|
+
// Short strings: try to decode escapes (this makes "{\"a\":1}" usable too).
|
|
2660
|
+
if (lex.length >= 2 && lex[0] === '"' && lex[lex.length - 1] === '"') {
|
|
2661
|
+
try {
|
|
2662
|
+
return JSON.parse(lex);
|
|
2663
|
+
}
|
|
2664
|
+
catch (e) {
|
|
2665
|
+
/* fall through */
|
|
2666
|
+
}
|
|
2667
|
+
return stripQuotes(lex);
|
|
2668
|
+
}
|
|
2669
|
+
return stripQuotes(lex);
|
|
2675
2670
|
}
|
|
2676
|
-
function
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
if (
|
|
2671
|
+
function jsonPointerUnescape(seg) {
|
|
2672
|
+
// RFC6901: ~1 -> '/', ~0 -> '~'
|
|
2673
|
+
let out = '';
|
|
2674
|
+
for (let i = 0; i < seg.length; i++) {
|
|
2675
|
+
const c = seg[i];
|
|
2676
|
+
if (c !== '~') {
|
|
2677
|
+
out += c;
|
|
2678
|
+
continue;
|
|
2679
|
+
}
|
|
2680
|
+
if (i + 1 >= seg.length)
|
|
2686
2681
|
return null;
|
|
2687
|
-
const
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2682
|
+
const n = seg[i + 1];
|
|
2683
|
+
if (n === '0')
|
|
2684
|
+
out += '~';
|
|
2685
|
+
else if (n === '1')
|
|
2686
|
+
out += '/';
|
|
2687
|
+
else
|
|
2688
|
+
return null;
|
|
2689
|
+
i++;
|
|
2693
2690
|
}
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
if (
|
|
2698
|
-
return
|
|
2699
|
-
if (
|
|
2700
|
-
return
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2691
|
+
return out;
|
|
2692
|
+
}
|
|
2693
|
+
function jsonToTerm(v) {
|
|
2694
|
+
if (v === null)
|
|
2695
|
+
return makeStringLiteral('null');
|
|
2696
|
+
if (typeof v === 'string')
|
|
2697
|
+
return makeStringLiteral(v);
|
|
2698
|
+
if (typeof v === 'number')
|
|
2699
|
+
return internLiteral(String(v));
|
|
2700
|
+
if (typeof v === 'boolean')
|
|
2701
|
+
return internLiteral(v ? 'true' : 'false');
|
|
2702
|
+
if (Array.isArray(v))
|
|
2703
|
+
return new ListTerm(v.map(jsonToTerm));
|
|
2704
|
+
if (v && typeof v === 'object') {
|
|
2705
|
+
return makeRdfJsonLiteral(JSON.stringify(v));
|
|
2705
2706
|
}
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2707
|
+
return null;
|
|
2708
|
+
}
|
|
2709
|
+
function jsonPointerLookup(jsonText, pointer) {
|
|
2710
|
+
let ptr = pointer;
|
|
2711
|
+
// Support URI fragment form "#/a/b"
|
|
2712
|
+
if (ptr.startsWith('#')) {
|
|
2713
|
+
try {
|
|
2714
|
+
ptr = decodeURIComponent(ptr.slice(1));
|
|
2715
|
+
}
|
|
2716
|
+
catch {
|
|
2717
|
+
return null;
|
|
2718
|
+
}
|
|
2712
2719
|
}
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
if (ai.kind === 'bigint' && bi.kind === 'bigint') {
|
|
2722
|
-
if (ai.value === bi.value)
|
|
2723
|
-
return { ...subst };
|
|
2724
|
-
}
|
|
2725
|
-
else {
|
|
2726
|
-
const an = ai.kind === 'bigint' ? Number(ai.value) : ai.value;
|
|
2727
|
-
const bn = bi.kind === 'bigint' ? Number(bi.value) : bi.value;
|
|
2728
|
-
if (!Number.isNaN(an) && !Number.isNaN(bn) && an === bn)
|
|
2729
|
-
return { ...subst };
|
|
2730
|
-
}
|
|
2731
|
-
}
|
|
2732
|
-
if (opts.intDecimalEq) {
|
|
2733
|
-
const intDt = XSD_NS + 'integer';
|
|
2734
|
-
const decDt = XSD_NS + 'decimal';
|
|
2735
|
-
if ((ai.dt === intDt && bi.dt === decDt) || (ai.dt === decDt && bi.dt === intDt)) {
|
|
2736
|
-
const intInfo = ai.dt === intDt ? ai : bi; // bigint
|
|
2737
|
-
const decInfo = ai.dt === decDt ? ai : bi; // number + lexStr
|
|
2738
|
-
const dec = parseXsdDecimalToBigIntScale(decInfo.lexStr);
|
|
2739
|
-
if (dec) {
|
|
2740
|
-
const scaledInt = intInfo.value * pow10n(dec.scale);
|
|
2741
|
-
if (scaledInt === dec.num)
|
|
2742
|
-
return { ...subst };
|
|
2743
|
-
}
|
|
2744
|
-
}
|
|
2745
|
-
}
|
|
2720
|
+
let entry = jsonPointerCache.get(jsonText);
|
|
2721
|
+
if (!entry) {
|
|
2722
|
+
let parsed = null;
|
|
2723
|
+
try {
|
|
2724
|
+
parsed = JSON.parse(jsonText);
|
|
2725
|
+
}
|
|
2726
|
+
catch {
|
|
2727
|
+
parsed = null;
|
|
2746
2728
|
}
|
|
2729
|
+
entry = { parsed, ptrCache: new Map() };
|
|
2730
|
+
jsonPointerCache.set(jsonText, entry);
|
|
2747
2731
|
}
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2732
|
+
if (entry.parsed === null)
|
|
2733
|
+
return null;
|
|
2734
|
+
if (entry.ptrCache.has(ptr))
|
|
2735
|
+
return entry.ptrCache.get(ptr);
|
|
2736
|
+
let cur = entry.parsed;
|
|
2737
|
+
if (ptr === '') {
|
|
2738
|
+
const t = jsonToTerm(cur);
|
|
2739
|
+
entry.ptrCache.set(ptr, t);
|
|
2740
|
+
return t;
|
|
2751
2741
|
}
|
|
2752
|
-
if (
|
|
2753
|
-
|
|
2742
|
+
if (!ptr.startsWith('/')) {
|
|
2743
|
+
entry.ptrCache.set(ptr, null);
|
|
2744
|
+
return null;
|
|
2754
2745
|
}
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2746
|
+
const parts = ptr.split('/').slice(1);
|
|
2747
|
+
for (const raw of parts) {
|
|
2748
|
+
const seg = jsonPointerUnescape(raw);
|
|
2749
|
+
if (seg === null) {
|
|
2750
|
+
entry.ptrCache.set(ptr, null);
|
|
2758
2751
|
return null;
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2752
|
+
}
|
|
2753
|
+
if (Array.isArray(cur)) {
|
|
2754
|
+
if (!/^(0|[1-9]\d*)$/.test(seg)) {
|
|
2755
|
+
entry.ptrCache.set(ptr, null);
|
|
2756
|
+
return null;
|
|
2757
|
+
}
|
|
2758
|
+
const idx = Number(seg);
|
|
2759
|
+
if (idx < 0 || idx >= cur.length) {
|
|
2760
|
+
entry.ptrCache.set(ptr, null);
|
|
2763
2761
|
return null;
|
|
2762
|
+
}
|
|
2763
|
+
cur = cur[idx];
|
|
2764
2764
|
}
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
if (a instanceof ListTerm && b instanceof ListTerm) {
|
|
2769
|
-
if (a.elems.length !== b.elems.length)
|
|
2770
|
-
return null;
|
|
2771
|
-
let s2 = { ...subst };
|
|
2772
|
-
for (let i = 0; i < a.elems.length; i++) {
|
|
2773
|
-
s2 = unifyTermWithOptions(a.elems[i], b.elems[i], s2, opts);
|
|
2774
|
-
if (s2 === null)
|
|
2765
|
+
else if (cur !== null && typeof cur === 'object') {
|
|
2766
|
+
if (!Object.prototype.hasOwnProperty.call(cur, seg)) {
|
|
2767
|
+
entry.ptrCache.set(ptr, null);
|
|
2775
2768
|
return null;
|
|
2769
|
+
}
|
|
2770
|
+
cur = cur[seg];
|
|
2771
|
+
}
|
|
2772
|
+
else {
|
|
2773
|
+
entry.ptrCache.set(ptr, null);
|
|
2774
|
+
return null;
|
|
2776
2775
|
}
|
|
2777
|
-
return s2;
|
|
2778
2776
|
}
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
return { ...subst };
|
|
2783
|
-
return unifyGraphTriples(a.triples, b.triples, subst);
|
|
2784
|
-
}
|
|
2785
|
-
return null;
|
|
2786
|
-
}
|
|
2787
|
-
function unifyTriple(pat, fact, subst) {
|
|
2788
|
-
// Predicates are usually the cheapest and most selective
|
|
2789
|
-
const s1 = unifyTerm(pat.p, fact.p, subst);
|
|
2790
|
-
if (s1 === null)
|
|
2791
|
-
return null;
|
|
2792
|
-
const s2 = unifyTerm(pat.s, fact.s, s1);
|
|
2793
|
-
if (s2 === null)
|
|
2794
|
-
return null;
|
|
2795
|
-
const s3 = unifyTerm(pat.o, fact.o, s2);
|
|
2796
|
-
return s3;
|
|
2777
|
+
const out = jsonToTerm(cur);
|
|
2778
|
+
entry.ptrCache.set(ptr, out);
|
|
2779
|
+
return out;
|
|
2797
2780
|
}
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
for (
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2781
|
+
// Tiny subset of sprintf: supports only %s and %%.
|
|
2782
|
+
// Good enough for most N3 string:format use cases that just splice strings.
|
|
2783
|
+
function simpleStringFormat(fmt, args) {
|
|
2784
|
+
let out = '';
|
|
2785
|
+
let argIndex = 0;
|
|
2786
|
+
for (let i = 0; i < fmt.length; i++) {
|
|
2787
|
+
const ch = fmt[i];
|
|
2788
|
+
if (ch === '%' && i + 1 < fmt.length) {
|
|
2789
|
+
const spec = fmt[i + 1];
|
|
2790
|
+
if (spec === 's') {
|
|
2791
|
+
const arg = argIndex < args.length ? args[argIndex++] : '';
|
|
2792
|
+
out += arg;
|
|
2793
|
+
i++;
|
|
2794
|
+
continue;
|
|
2795
|
+
}
|
|
2796
|
+
if (spec === '%') {
|
|
2797
|
+
out += '%';
|
|
2798
|
+
i++;
|
|
2799
|
+
continue;
|
|
2800
|
+
}
|
|
2801
|
+
// Unsupported specifier (like %d, %f, …) ⇒ fail the builtin.
|
|
2802
|
+
return null;
|
|
2810
2803
|
}
|
|
2804
|
+
out += ch;
|
|
2811
2805
|
}
|
|
2812
2806
|
return out;
|
|
2813
2807
|
}
|
|
2814
|
-
//
|
|
2815
|
-
//
|
|
2816
|
-
//
|
|
2817
|
-
function
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
//
|
|
2823
|
-
//
|
|
2824
|
-
//
|
|
2825
|
-
const
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2808
|
+
// -----------------------------------------------------------------------------
|
|
2809
|
+
// SWAP/N3 regex compatibility helper
|
|
2810
|
+
// -----------------------------------------------------------------------------
|
|
2811
|
+
function regexNeedsUnicodeMode(pattern) {
|
|
2812
|
+
// JS requires /u for Unicode property escapes and code point escapes.
|
|
2813
|
+
return /\\[pP]\{/.test(pattern) || /\\u\{/.test(pattern);
|
|
2814
|
+
}
|
|
2815
|
+
function sanitizeForUnicodeMode(pattern) {
|
|
2816
|
+
// In JS Unicode mode, “identity escapes” like \! are a SyntaxError.
|
|
2817
|
+
// In Perl-ish regexes they commonly mean “literal !”. So drop the redundant "\".
|
|
2818
|
+
// Keep escapes that are regex-syntax or are commonly needed in char classes.
|
|
2819
|
+
const KEEP = '^$\\.*+?()[]{}|/-';
|
|
2820
|
+
return pattern.replace(/\\([^A-Za-z0-9])/g, (m, ch) => {
|
|
2821
|
+
return KEEP.includes(ch) ? m : ch;
|
|
2822
|
+
});
|
|
2823
|
+
}
|
|
2824
|
+
function compileSwapRegex(pattern, extraFlags) {
|
|
2825
|
+
const needU = regexNeedsUnicodeMode(pattern);
|
|
2826
|
+
const flags = (extraFlags || '') + (needU ? 'u' : '');
|
|
2827
|
+
try {
|
|
2828
|
+
return new RegExp(pattern, flags);
|
|
2834
2829
|
}
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2830
|
+
catch (e) {
|
|
2831
|
+
if (needU) {
|
|
2832
|
+
const p2 = sanitizeForUnicodeMode(pattern);
|
|
2833
|
+
if (p2 !== pattern) {
|
|
2834
|
+
try {
|
|
2835
|
+
return new RegExp(p2, flags);
|
|
2836
|
+
}
|
|
2837
|
+
catch (_e2) { }
|
|
2842
2838
|
}
|
|
2843
2839
|
}
|
|
2840
|
+
return null;
|
|
2844
2841
|
}
|
|
2845
|
-
const res = [lex, dt];
|
|
2846
|
-
__literalPartsCache.set(lit, res);
|
|
2847
|
-
return res;
|
|
2848
2842
|
}
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2843
|
+
// -----------------------------------------------------------------------------
|
|
2844
|
+
// Strict numeric literal parsing for math: builtins
|
|
2845
|
+
// -----------------------------------------------------------------------------
|
|
2846
|
+
const XSD_DECIMAL_DT = XSD_NS + 'decimal';
|
|
2847
|
+
const XSD_DOUBLE_DT = XSD_NS + 'double';
|
|
2848
|
+
const XSD_FLOAT_DT = XSD_NS + 'float';
|
|
2849
|
+
const XSD_INTEGER_DT = XSD_NS + 'integer';
|
|
2850
|
+
// Integer-derived datatypes from XML Schema Part 2 (and commonly used ones).
|
|
2851
|
+
const XSD_INTEGER_DERIVED_DTS = new Set([
|
|
2852
|
+
XSD_INTEGER_DT,
|
|
2853
|
+
XSD_NS + 'nonPositiveInteger',
|
|
2854
|
+
XSD_NS + 'negativeInteger',
|
|
2855
|
+
XSD_NS + 'long',
|
|
2856
|
+
XSD_NS + 'int',
|
|
2857
|
+
XSD_NS + 'short',
|
|
2858
|
+
XSD_NS + 'byte',
|
|
2859
|
+
XSD_NS + 'nonNegativeInteger',
|
|
2860
|
+
XSD_NS + 'unsignedLong',
|
|
2861
|
+
XSD_NS + 'unsignedInt',
|
|
2862
|
+
XSD_NS + 'unsignedShort',
|
|
2863
|
+
XSD_NS + 'unsignedByte',
|
|
2864
|
+
XSD_NS + 'positiveInteger',
|
|
2865
|
+
]);
|
|
2866
|
+
function parseBooleanLiteralInfo(t) {
|
|
2867
|
+
if (!(t instanceof Literal))
|
|
2868
|
+
return null;
|
|
2869
|
+
const boolDt = XSD_NS + 'boolean';
|
|
2870
|
+
const v = t.value;
|
|
2871
|
+
const [lex, dt] = literalParts(v);
|
|
2872
|
+
// Typed xsd:boolean: accept "true"/"false"/"1"/"0"
|
|
2873
|
+
if (dt !== null) {
|
|
2874
|
+
if (dt !== boolDt)
|
|
2875
|
+
return null;
|
|
2876
|
+
const s = stripQuotes(lex);
|
|
2877
|
+
if (s === 'true' || s === '1')
|
|
2878
|
+
return { dt: boolDt, value: true };
|
|
2879
|
+
if (s === 'false' || s === '0')
|
|
2880
|
+
return { dt: boolDt, value: false };
|
|
2881
|
+
return null;
|
|
2865
2882
|
}
|
|
2866
|
-
|
|
2867
|
-
if (
|
|
2868
|
-
return
|
|
2869
|
-
|
|
2870
|
-
|
|
2883
|
+
// Untyped boolean token: true/false
|
|
2884
|
+
if (v === 'true')
|
|
2885
|
+
return { dt: boolDt, value: true };
|
|
2886
|
+
if (v === 'false')
|
|
2887
|
+
return { dt: boolDt, value: false };
|
|
2888
|
+
return null;
|
|
2871
2889
|
}
|
|
2872
|
-
function
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
return !literalHasLangTag(lit);
|
|
2890
|
+
function parseXsdFloatSpecialLex(s) {
|
|
2891
|
+
if (s === 'INF' || s === '+INF')
|
|
2892
|
+
return Infinity;
|
|
2893
|
+
if (s === '-INF')
|
|
2894
|
+
return -Infinity;
|
|
2895
|
+
if (s === 'NaN')
|
|
2896
|
+
return NaN;
|
|
2897
|
+
return null;
|
|
2881
2898
|
}
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2899
|
+
// ===========================================================================
|
|
2900
|
+
// Math builtin helpers
|
|
2901
|
+
// ===========================================================================
|
|
2902
|
+
function formatXsdFloatSpecialLex(n) {
|
|
2903
|
+
if (n === Infinity)
|
|
2904
|
+
return 'INF';
|
|
2905
|
+
if (n === -Infinity)
|
|
2906
|
+
return '-INF';
|
|
2907
|
+
if (Number.isNaN(n))
|
|
2908
|
+
return 'NaN';
|
|
2909
|
+
return null;
|
|
2910
|
+
}
|
|
2911
|
+
function isQuotedLexical(lex) {
|
|
2912
|
+
// Accept both Turtle/N3 quoting styles:
|
|
2913
|
+
// short: "..." or '...'
|
|
2914
|
+
// long: """...""" or '''...'''
|
|
2915
|
+
if (typeof lex !== 'string')
|
|
2885
2916
|
return false;
|
|
2886
|
-
const
|
|
2887
|
-
|
|
2888
|
-
|
|
2917
|
+
const n = lex.length;
|
|
2918
|
+
if (n >= 6 && ((lex.startsWith('"""') && lex.endsWith('"""')) || (lex.startsWith("'''") && lex.endsWith("'''"))))
|
|
2919
|
+
return true;
|
|
2920
|
+
if (n >= 2) {
|
|
2921
|
+
const a = lex[0];
|
|
2922
|
+
const b = lex[n - 1];
|
|
2923
|
+
return (a === '"' && b === '"') || (a === "'" && b === "'");
|
|
2924
|
+
}
|
|
2925
|
+
return false;
|
|
2926
|
+
}
|
|
2927
|
+
function isXsdNumericDatatype(dt) {
|
|
2928
|
+
if (dt === null)
|
|
2889
2929
|
return false;
|
|
2890
|
-
|
|
2891
|
-
const bPlain = bdt === null && isPlainStringLiteralValue(bLit);
|
|
2892
|
-
const aXsdStr = adt === XSD_NS + 'string';
|
|
2893
|
-
const bXsdStr = bdt === XSD_NS + 'string';
|
|
2894
|
-
return (aPlain && bXsdStr) || (bPlain && aXsdStr);
|
|
2930
|
+
return dt === XSD_DECIMAL_DT || dt === XSD_DOUBLE_DT || dt === XSD_FLOAT_DT || XSD_INTEGER_DERIVED_DTS.has(dt);
|
|
2895
2931
|
}
|
|
2896
|
-
function
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
const [lex, dt] = literalParts(lit);
|
|
2901
|
-
if (dt === XSD_NS + 'string') {
|
|
2902
|
-
return `${lex}^^<${XSD_NS}string>`;
|
|
2903
|
-
}
|
|
2904
|
-
if (dt === null && isPlainStringLiteralValue(lit)) {
|
|
2905
|
-
return `${lex}^^<${XSD_NS}string>`;
|
|
2906
|
-
}
|
|
2907
|
-
return lit;
|
|
2932
|
+
function isXsdIntegerDatatype(dt) {
|
|
2933
|
+
if (dt === null)
|
|
2934
|
+
return false;
|
|
2935
|
+
return XSD_INTEGER_DERIVED_DTS.has(dt);
|
|
2908
2936
|
}
|
|
2909
|
-
function
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
}
|
|
2925
|
-
return lex;
|
|
2937
|
+
function looksLikeUntypedNumericTokenLex(lex) {
|
|
2938
|
+
// We only treat *unquoted* tokens as "untyped numeric" (Turtle/N3 numeric literal).
|
|
2939
|
+
// Quoted literals without datatype are strings, never numbers.
|
|
2940
|
+
if (isQuotedLexical(lex))
|
|
2941
|
+
return false;
|
|
2942
|
+
// integer
|
|
2943
|
+
if (/^[+-]?\d+$/.test(lex))
|
|
2944
|
+
return true;
|
|
2945
|
+
// decimal (no exponent)
|
|
2946
|
+
if (/^[+-]?(?:\d+\.\d*|\.\d+)$/.test(lex))
|
|
2947
|
+
return true;
|
|
2948
|
+
// double (with exponent)
|
|
2949
|
+
if (/^[+-]?(?:\d+\.\d*|\.\d+|\d+)(?:[eE][+-]?\d+)$/.test(lex))
|
|
2950
|
+
return true;
|
|
2951
|
+
return false;
|
|
2926
2952
|
}
|
|
2927
|
-
function
|
|
2928
|
-
//
|
|
2929
|
-
//
|
|
2930
|
-
// - plain string literals ("...")
|
|
2931
|
-
// - "..."^^xsd:string
|
|
2932
|
-
// Reject:
|
|
2933
|
-
// - language-tagged strings ("..."@en)
|
|
2934
|
-
// - any other datatype
|
|
2953
|
+
function parseNum(t) {
|
|
2954
|
+
// Parse as JS Number, but ONLY for xsd numeric datatypes or untyped numeric tokens.
|
|
2955
|
+
// For xsd:float/xsd:double, accept INF/-INF/NaN (and +INF).
|
|
2935
2956
|
if (!(t instanceof Literal))
|
|
2936
2957
|
return null;
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
if (dt !== null
|
|
2958
|
+
const key = t.value;
|
|
2959
|
+
if (__parseNumCache.has(key))
|
|
2960
|
+
return __parseNumCache.get(key);
|
|
2961
|
+
const [lex, dt] = literalParts(key);
|
|
2962
|
+
// Typed literals: must be xsd numeric.
|
|
2963
|
+
if (dt !== null) {
|
|
2964
|
+
if (!isXsdNumericDatatype(dt)) {
|
|
2965
|
+
__parseNumCache.set(key, null);
|
|
2966
|
+
return null;
|
|
2967
|
+
}
|
|
2968
|
+
const val = stripQuotes(lex);
|
|
2969
|
+
// float/double: allow INF/-INF/NaN and allow +/-Infinity results
|
|
2970
|
+
if (dt === XSD_FLOAT_DT || dt === XSD_DOUBLE_DT) {
|
|
2971
|
+
const sp = parseXsdFloatSpecialLex(val);
|
|
2972
|
+
if (sp !== null) {
|
|
2973
|
+
__parseNumCache.set(key, sp);
|
|
2974
|
+
return sp;
|
|
2975
|
+
}
|
|
2976
|
+
const n = Number(val);
|
|
2977
|
+
if (Number.isNaN(n)) {
|
|
2978
|
+
__parseNumCache.set(key, null);
|
|
2979
|
+
return null;
|
|
2980
|
+
}
|
|
2981
|
+
__parseNumCache.set(key, n);
|
|
2982
|
+
return n; // may be finite, +/-Infinity
|
|
2983
|
+
}
|
|
2984
|
+
// decimal/integer-derived: keep strict finite parsing
|
|
2985
|
+
const n = Number(val);
|
|
2986
|
+
if (!Number.isFinite(n)) {
|
|
2987
|
+
__parseNumCache.set(key, null);
|
|
2988
|
+
return null;
|
|
2989
|
+
}
|
|
2990
|
+
__parseNumCache.set(key, n);
|
|
2991
|
+
return n;
|
|
2992
|
+
}
|
|
2993
|
+
// Untyped literals: accept only unquoted numeric tokens.
|
|
2994
|
+
if (!looksLikeUntypedNumericTokenLex(lex)) {
|
|
2995
|
+
__parseNumCache.set(key, null);
|
|
2943
2996
|
return null;
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
//
|
|
2949
|
-
// Per the N3 Builtins spec, when the domain is xsd:string we must be able to
|
|
2950
|
-
// cast *any* IRI or literal value (incl. numeric, boolean, dateTime, anyURI,
|
|
2951
|
-
// rdf:langString, and plain literals) to a string.
|
|
2952
|
-
//
|
|
2953
|
-
// We implement this as:
|
|
2954
|
-
// - IRI -> its IRI string
|
|
2955
|
-
// - Literal:
|
|
2956
|
-
// * quoted lexical form: decode N3/Turtle escapes and strip quotes
|
|
2957
|
-
// * unquoted lexical form: use as-is (e.g., 1234, true, 1971-..., 1.23E4)
|
|
2958
|
-
// - Everything else (blank nodes, lists, formulas, vars) -> fail
|
|
2959
|
-
if (t instanceof Iri)
|
|
2960
|
-
return t.value;
|
|
2961
|
-
if (!(t instanceof Literal))
|
|
2997
|
+
}
|
|
2998
|
+
const n = Number(lex);
|
|
2999
|
+
if (!Number.isFinite(n)) {
|
|
3000
|
+
__parseNumCache.set(key, null);
|
|
2962
3001
|
return null;
|
|
2963
|
-
const [lex, _dt] = literalParts(t.value);
|
|
2964
|
-
if (isQuotedLexical(lex)) {
|
|
2965
|
-
// Interpret N3/Turtle string escapes (\" \n \uXXXX \UXXXXXXXX …)
|
|
2966
|
-
// to obtain the actual string value.
|
|
2967
|
-
return decodeN3StringEscapes(stripQuotes(lex));
|
|
2968
3002
|
}
|
|
2969
|
-
|
|
2970
|
-
return
|
|
2971
|
-
}
|
|
2972
|
-
function makeStringLiteral(str) {
|
|
2973
|
-
// JSON.stringify gives us a valid N3/Turtle-style quoted string
|
|
2974
|
-
// (with proper escaping for quotes, backslashes, newlines, …).
|
|
2975
|
-
return internLiteral(JSON.stringify(str));
|
|
3003
|
+
__parseNumCache.set(key, n);
|
|
3004
|
+
return n;
|
|
2976
3005
|
}
|
|
2977
|
-
function
|
|
2978
|
-
//
|
|
2979
|
-
//
|
|
3006
|
+
function parseIntLiteral(t) {
|
|
3007
|
+
// Parse as BigInt if (and only if) it is an integer literal in an integer datatype,
|
|
3008
|
+
// or an untyped integer token.
|
|
2980
3009
|
if (!(t instanceof Literal))
|
|
2981
3010
|
return null;
|
|
2982
|
-
const
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
try {
|
|
2990
|
-
return JSON.parse(lex);
|
|
2991
|
-
}
|
|
2992
|
-
catch (e) {
|
|
2993
|
-
/* fall through */
|
|
2994
|
-
}
|
|
2995
|
-
return stripQuotes(lex);
|
|
2996
|
-
}
|
|
2997
|
-
return stripQuotes(lex);
|
|
2998
|
-
}
|
|
2999
|
-
function jsonPointerUnescape(seg) {
|
|
3000
|
-
// RFC6901: ~1 -> '/', ~0 -> '~'
|
|
3001
|
-
let out = '';
|
|
3002
|
-
for (let i = 0; i < seg.length; i++) {
|
|
3003
|
-
const c = seg[i];
|
|
3004
|
-
if (c !== '~') {
|
|
3005
|
-
out += c;
|
|
3006
|
-
continue;
|
|
3007
|
-
}
|
|
3008
|
-
if (i + 1 >= seg.length)
|
|
3009
|
-
return null;
|
|
3010
|
-
const n = seg[i + 1];
|
|
3011
|
-
if (n === '0')
|
|
3012
|
-
out += '~';
|
|
3013
|
-
else if (n === '1')
|
|
3014
|
-
out += '/';
|
|
3015
|
-
else
|
|
3011
|
+
const key = t.value;
|
|
3012
|
+
if (__parseIntCache.has(key))
|
|
3013
|
+
return __parseIntCache.get(key);
|
|
3014
|
+
const [lex, dt] = literalParts(key);
|
|
3015
|
+
if (dt !== null) {
|
|
3016
|
+
if (!isXsdIntegerDatatype(dt)) {
|
|
3017
|
+
__parseIntCache.set(key, null);
|
|
3016
3018
|
return null;
|
|
3017
|
-
i++;
|
|
3018
|
-
}
|
|
3019
|
-
return out;
|
|
3020
|
-
}
|
|
3021
|
-
function jsonToTerm(v) {
|
|
3022
|
-
if (v === null)
|
|
3023
|
-
return makeStringLiteral('null');
|
|
3024
|
-
if (typeof v === 'string')
|
|
3025
|
-
return makeStringLiteral(v);
|
|
3026
|
-
if (typeof v === 'number')
|
|
3027
|
-
return internLiteral(String(v));
|
|
3028
|
-
if (typeof v === 'boolean')
|
|
3029
|
-
return internLiteral(v ? 'true' : 'false');
|
|
3030
|
-
if (Array.isArray(v))
|
|
3031
|
-
return new ListTerm(v.map(jsonToTerm));
|
|
3032
|
-
if (v && typeof v === 'object') {
|
|
3033
|
-
return makeRdfJsonLiteral(JSON.stringify(v));
|
|
3034
|
-
}
|
|
3035
|
-
return null;
|
|
3036
|
-
}
|
|
3037
|
-
function jsonPointerLookup(jsonText, pointer) {
|
|
3038
|
-
let ptr = pointer;
|
|
3039
|
-
// Support URI fragment form "#/a/b"
|
|
3040
|
-
if (ptr.startsWith('#')) {
|
|
3041
|
-
try {
|
|
3042
|
-
ptr = decodeURIComponent(ptr.slice(1));
|
|
3043
3019
|
}
|
|
3044
|
-
|
|
3020
|
+
const val = stripQuotes(lex);
|
|
3021
|
+
if (!/^[+-]?\d+$/.test(val)) {
|
|
3022
|
+
__parseIntCache.set(key, null);
|
|
3045
3023
|
return null;
|
|
3046
3024
|
}
|
|
3047
|
-
}
|
|
3048
|
-
let entry = jsonPointerCache.get(jsonText);
|
|
3049
|
-
if (!entry) {
|
|
3050
|
-
let parsed = null;
|
|
3051
3025
|
try {
|
|
3052
|
-
|
|
3026
|
+
const out = BigInt(val);
|
|
3027
|
+
__parseIntCache.set(key, out);
|
|
3028
|
+
return out;
|
|
3053
3029
|
}
|
|
3054
3030
|
catch {
|
|
3055
|
-
|
|
3031
|
+
__parseIntCache.set(key, null);
|
|
3032
|
+
return null;
|
|
3056
3033
|
}
|
|
3057
|
-
entry = { parsed, ptrCache: new Map() };
|
|
3058
|
-
jsonPointerCache.set(jsonText, entry);
|
|
3059
3034
|
}
|
|
3060
|
-
|
|
3035
|
+
// Untyped: only accept unquoted integer tokens.
|
|
3036
|
+
if (isQuotedLexical(lex)) {
|
|
3037
|
+
__parseIntCache.set(key, null);
|
|
3061
3038
|
return null;
|
|
3062
|
-
if (entry.ptrCache.has(ptr))
|
|
3063
|
-
return entry.ptrCache.get(ptr);
|
|
3064
|
-
let cur = entry.parsed;
|
|
3065
|
-
if (ptr === '') {
|
|
3066
|
-
const t = jsonToTerm(cur);
|
|
3067
|
-
entry.ptrCache.set(ptr, t);
|
|
3068
|
-
return t;
|
|
3069
3039
|
}
|
|
3070
|
-
if (
|
|
3071
|
-
|
|
3040
|
+
if (!/^[+-]?\d+$/.test(lex)) {
|
|
3041
|
+
__parseIntCache.set(key, null);
|
|
3072
3042
|
return null;
|
|
3073
3043
|
}
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
entry.ptrCache.set(ptr, null);
|
|
3079
|
-
return null;
|
|
3080
|
-
}
|
|
3081
|
-
if (Array.isArray(cur)) {
|
|
3082
|
-
if (!/^(0|[1-9]\d*)$/.test(seg)) {
|
|
3083
|
-
entry.ptrCache.set(ptr, null);
|
|
3084
|
-
return null;
|
|
3085
|
-
}
|
|
3086
|
-
const idx = Number(seg);
|
|
3087
|
-
if (idx < 0 || idx >= cur.length) {
|
|
3088
|
-
entry.ptrCache.set(ptr, null);
|
|
3089
|
-
return null;
|
|
3090
|
-
}
|
|
3091
|
-
cur = cur[idx];
|
|
3092
|
-
}
|
|
3093
|
-
else if (cur !== null && typeof cur === 'object') {
|
|
3094
|
-
if (!Object.prototype.hasOwnProperty.call(cur, seg)) {
|
|
3095
|
-
entry.ptrCache.set(ptr, null);
|
|
3096
|
-
return null;
|
|
3097
|
-
}
|
|
3098
|
-
cur = cur[seg];
|
|
3099
|
-
}
|
|
3100
|
-
else {
|
|
3101
|
-
entry.ptrCache.set(ptr, null);
|
|
3102
|
-
return null;
|
|
3103
|
-
}
|
|
3044
|
+
try {
|
|
3045
|
+
const out = BigInt(lex);
|
|
3046
|
+
__parseIntCache.set(key, out);
|
|
3047
|
+
return out;
|
|
3104
3048
|
}
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
}
|
|
3109
|
-
// Tiny subset of sprintf: supports only %s and %%.
|
|
3110
|
-
// Good enough for most N3 string:format use cases that just splice strings.
|
|
3111
|
-
function simpleStringFormat(fmt, args) {
|
|
3112
|
-
let out = '';
|
|
3113
|
-
let argIndex = 0;
|
|
3114
|
-
for (let i = 0; i < fmt.length; i++) {
|
|
3115
|
-
const ch = fmt[i];
|
|
3116
|
-
if (ch === '%' && i + 1 < fmt.length) {
|
|
3117
|
-
const spec = fmt[i + 1];
|
|
3118
|
-
if (spec === 's') {
|
|
3119
|
-
const arg = argIndex < args.length ? args[argIndex++] : '';
|
|
3120
|
-
out += arg;
|
|
3121
|
-
i++;
|
|
3122
|
-
continue;
|
|
3123
|
-
}
|
|
3124
|
-
if (spec === '%') {
|
|
3125
|
-
out += '%';
|
|
3126
|
-
i++;
|
|
3127
|
-
continue;
|
|
3128
|
-
}
|
|
3129
|
-
// Unsupported specifier (like %d, %f, …) ⇒ fail the builtin.
|
|
3130
|
-
return null;
|
|
3131
|
-
}
|
|
3132
|
-
out += ch;
|
|
3049
|
+
catch {
|
|
3050
|
+
__parseIntCache.set(key, null);
|
|
3051
|
+
return null;
|
|
3133
3052
|
}
|
|
3134
|
-
return out;
|
|
3135
|
-
}
|
|
3136
|
-
// -----------------------------------------------------------------------------
|
|
3137
|
-
// SWAP/N3 regex compatibility helper
|
|
3138
|
-
// -----------------------------------------------------------------------------
|
|
3139
|
-
function regexNeedsUnicodeMode(pattern) {
|
|
3140
|
-
// JS requires /u for Unicode property escapes and code point escapes.
|
|
3141
|
-
return /\\[pP]\{/.test(pattern) || /\\u\{/.test(pattern);
|
|
3142
3053
|
}
|
|
3143
|
-
function
|
|
3144
|
-
|
|
3145
|
-
// In Perl-ish regexes they commonly mean “literal !”. So drop the redundant "\".
|
|
3146
|
-
// Keep escapes that are regex-syntax or are commonly needed in char classes.
|
|
3147
|
-
const KEEP = '^$\\.*+?()[]{}|/-';
|
|
3148
|
-
return pattern.replace(/\\([^A-Za-z0-9])/g, (m, ch) => {
|
|
3149
|
-
return KEEP.includes(ch) ? m : ch;
|
|
3150
|
-
});
|
|
3054
|
+
function formatNum(n) {
|
|
3055
|
+
return String(n);
|
|
3151
3056
|
}
|
|
3152
|
-
function
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3057
|
+
function parseXsdDecimalToBigIntScale(s) {
|
|
3058
|
+
let t = String(s).trim();
|
|
3059
|
+
let sign = 1n;
|
|
3060
|
+
if (t.startsWith('+'))
|
|
3061
|
+
t = t.slice(1);
|
|
3062
|
+
else if (t.startsWith('-')) {
|
|
3063
|
+
sign = -1n;
|
|
3064
|
+
t = t.slice(1);
|
|
3157
3065
|
}
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
const p2 = sanitizeForUnicodeMode(pattern);
|
|
3161
|
-
if (p2 !== pattern) {
|
|
3162
|
-
try {
|
|
3163
|
-
return new RegExp(p2, flags);
|
|
3164
|
-
}
|
|
3165
|
-
catch (_e2) { }
|
|
3166
|
-
}
|
|
3167
|
-
}
|
|
3066
|
+
// xsd:decimal lexical: (\d+(\.\d*)?|\.\d+)
|
|
3067
|
+
if (!/^(?:\d+(?:\.\d*)?|\.\d+)$/.test(t))
|
|
3168
3068
|
return null;
|
|
3069
|
+
let intPart = '0';
|
|
3070
|
+
let fracPart = '';
|
|
3071
|
+
if (t.includes('.')) {
|
|
3072
|
+
const parts = t.split('.');
|
|
3073
|
+
intPart = parts[0] === '' ? '0' : parts[0];
|
|
3074
|
+
fracPart = parts[1] || '';
|
|
3169
3075
|
}
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
// Strict numeric literal parsing for math: builtins
|
|
3173
|
-
// -----------------------------------------------------------------------------
|
|
3174
|
-
const XSD_DECIMAL_DT = XSD_NS + 'decimal';
|
|
3175
|
-
const XSD_DOUBLE_DT = XSD_NS + 'double';
|
|
3176
|
-
const XSD_FLOAT_DT = XSD_NS + 'float';
|
|
3177
|
-
const XSD_INTEGER_DT = XSD_NS + 'integer';
|
|
3178
|
-
// Integer-derived datatypes from XML Schema Part 2 (and commonly used ones).
|
|
3179
|
-
const XSD_INTEGER_DERIVED_DTS = new Set([
|
|
3180
|
-
XSD_INTEGER_DT,
|
|
3181
|
-
XSD_NS + 'nonPositiveInteger',
|
|
3182
|
-
XSD_NS + 'negativeInteger',
|
|
3183
|
-
XSD_NS + 'long',
|
|
3184
|
-
XSD_NS + 'int',
|
|
3185
|
-
XSD_NS + 'short',
|
|
3186
|
-
XSD_NS + 'byte',
|
|
3187
|
-
XSD_NS + 'nonNegativeInteger',
|
|
3188
|
-
XSD_NS + 'unsignedLong',
|
|
3189
|
-
XSD_NS + 'unsignedInt',
|
|
3190
|
-
XSD_NS + 'unsignedShort',
|
|
3191
|
-
XSD_NS + 'unsignedByte',
|
|
3192
|
-
XSD_NS + 'positiveInteger',
|
|
3193
|
-
]);
|
|
3194
|
-
function parseBooleanLiteralInfo(t) {
|
|
3195
|
-
if (!(t instanceof Literal))
|
|
3196
|
-
return null;
|
|
3197
|
-
const boolDt = XSD_NS + 'boolean';
|
|
3198
|
-
const v = t.value;
|
|
3199
|
-
const [lex, dt] = literalParts(v);
|
|
3200
|
-
// Typed xsd:boolean: accept "true"/"false"/"1"/"0"
|
|
3201
|
-
if (dt !== null) {
|
|
3202
|
-
if (dt !== boolDt)
|
|
3203
|
-
return null;
|
|
3204
|
-
const s = stripQuotes(lex);
|
|
3205
|
-
if (s === 'true' || s === '1')
|
|
3206
|
-
return { dt: boolDt, value: true };
|
|
3207
|
-
if (s === 'false' || s === '0')
|
|
3208
|
-
return { dt: boolDt, value: false };
|
|
3209
|
-
return null;
|
|
3076
|
+
else {
|
|
3077
|
+
intPart = t;
|
|
3210
3078
|
}
|
|
3211
|
-
//
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
return
|
|
3079
|
+
// normalize
|
|
3080
|
+
intPart = intPart.replace(/^0+(?=\d)/, '');
|
|
3081
|
+
fracPart = fracPart.replace(/0+$/, ''); // drop trailing zeros
|
|
3082
|
+
const scale = fracPart.length;
|
|
3083
|
+
const digits = intPart + fracPart || '0';
|
|
3084
|
+
return { num: sign * BigInt(digits), scale };
|
|
3217
3085
|
}
|
|
3218
|
-
function
|
|
3219
|
-
|
|
3220
|
-
return Infinity;
|
|
3221
|
-
if (s === '-INF')
|
|
3222
|
-
return -Infinity;
|
|
3223
|
-
if (s === 'NaN')
|
|
3224
|
-
return NaN;
|
|
3225
|
-
return null;
|
|
3086
|
+
function pow10n(k) {
|
|
3087
|
+
return 10n ** BigInt(k);
|
|
3226
3088
|
}
|
|
3227
3089
|
// ===========================================================================
|
|
3228
|
-
//
|
|
3090
|
+
// Time & duration builtin helpers
|
|
3229
3091
|
// ===========================================================================
|
|
3230
|
-
function
|
|
3231
|
-
if (n === Infinity)
|
|
3232
|
-
return 'INF';
|
|
3233
|
-
if (n === -Infinity)
|
|
3234
|
-
return '-INF';
|
|
3235
|
-
if (Number.isNaN(n))
|
|
3236
|
-
return 'NaN';
|
|
3237
|
-
return null;
|
|
3238
|
-
}
|
|
3239
|
-
function isQuotedLexical(lex) {
|
|
3240
|
-
// Accept both Turtle/N3 quoting styles:
|
|
3241
|
-
// short: "..." or '...'
|
|
3242
|
-
// long: """...""" or '''...'''
|
|
3243
|
-
if (typeof lex !== 'string')
|
|
3244
|
-
return false;
|
|
3245
|
-
const n = lex.length;
|
|
3246
|
-
if (n >= 6 && ((lex.startsWith('"""') && lex.endsWith('"""')) || (lex.startsWith("'''") && lex.endsWith("'''"))))
|
|
3247
|
-
return true;
|
|
3248
|
-
if (n >= 2) {
|
|
3249
|
-
const a = lex[0];
|
|
3250
|
-
const b = lex[n - 1];
|
|
3251
|
-
return (a === '"' && b === '"') || (a === "'" && b === "'");
|
|
3252
|
-
}
|
|
3253
|
-
return false;
|
|
3254
|
-
}
|
|
3255
|
-
function isXsdNumericDatatype(dt) {
|
|
3256
|
-
if (dt === null)
|
|
3257
|
-
return false;
|
|
3258
|
-
return dt === XSD_DECIMAL_DT || dt === XSD_DOUBLE_DT || dt === XSD_FLOAT_DT || XSD_INTEGER_DERIVED_DTS.has(dt);
|
|
3259
|
-
}
|
|
3260
|
-
function isXsdIntegerDatatype(dt) {
|
|
3261
|
-
if (dt === null)
|
|
3262
|
-
return false;
|
|
3263
|
-
return XSD_INTEGER_DERIVED_DTS.has(dt);
|
|
3264
|
-
}
|
|
3265
|
-
function looksLikeUntypedNumericTokenLex(lex) {
|
|
3266
|
-
// We only treat *unquoted* tokens as "untyped numeric" (Turtle/N3 numeric literal).
|
|
3267
|
-
// Quoted literals without datatype are strings, never numbers.
|
|
3268
|
-
if (isQuotedLexical(lex))
|
|
3269
|
-
return false;
|
|
3270
|
-
// integer
|
|
3271
|
-
if (/^[+-]?\d+$/.test(lex))
|
|
3272
|
-
return true;
|
|
3273
|
-
// decimal (no exponent)
|
|
3274
|
-
if (/^[+-]?(?:\d+\.\d*|\.\d+)$/.test(lex))
|
|
3275
|
-
return true;
|
|
3276
|
-
// double (with exponent)
|
|
3277
|
-
if (/^[+-]?(?:\d+\.\d*|\.\d+|\d+)(?:[eE][+-]?\d+)$/.test(lex))
|
|
3278
|
-
return true;
|
|
3279
|
-
return false;
|
|
3280
|
-
}
|
|
3281
|
-
function parseNum(t) {
|
|
3282
|
-
// Parse as JS Number, but ONLY for xsd numeric datatypes or untyped numeric tokens.
|
|
3283
|
-
// For xsd:float/xsd:double, accept INF/-INF/NaN (and +INF).
|
|
3092
|
+
function parseXsdDateTerm(t) {
|
|
3284
3093
|
if (!(t instanceof Literal))
|
|
3285
3094
|
return null;
|
|
3286
|
-
const
|
|
3287
|
-
if (
|
|
3288
|
-
return __parseNumCache.get(key);
|
|
3289
|
-
const [lex, dt] = literalParts(key);
|
|
3290
|
-
// Typed literals: must be xsd numeric.
|
|
3291
|
-
if (dt !== null) {
|
|
3292
|
-
if (!isXsdNumericDatatype(dt)) {
|
|
3293
|
-
__parseNumCache.set(key, null);
|
|
3294
|
-
return null;
|
|
3295
|
-
}
|
|
3296
|
-
const val = stripQuotes(lex);
|
|
3297
|
-
// float/double: allow INF/-INF/NaN and allow +/-Infinity results
|
|
3298
|
-
if (dt === XSD_FLOAT_DT || dt === XSD_DOUBLE_DT) {
|
|
3299
|
-
const sp = parseXsdFloatSpecialLex(val);
|
|
3300
|
-
if (sp !== null) {
|
|
3301
|
-
__parseNumCache.set(key, sp);
|
|
3302
|
-
return sp;
|
|
3303
|
-
}
|
|
3304
|
-
const n = Number(val);
|
|
3305
|
-
if (Number.isNaN(n)) {
|
|
3306
|
-
__parseNumCache.set(key, null);
|
|
3307
|
-
return null;
|
|
3308
|
-
}
|
|
3309
|
-
__parseNumCache.set(key, n);
|
|
3310
|
-
return n; // may be finite, +/-Infinity
|
|
3311
|
-
}
|
|
3312
|
-
// decimal/integer-derived: keep strict finite parsing
|
|
3313
|
-
const n = Number(val);
|
|
3314
|
-
if (!Number.isFinite(n)) {
|
|
3315
|
-
__parseNumCache.set(key, null);
|
|
3316
|
-
return null;
|
|
3317
|
-
}
|
|
3318
|
-
__parseNumCache.set(key, n);
|
|
3319
|
-
return n;
|
|
3320
|
-
}
|
|
3321
|
-
// Untyped literals: accept only unquoted numeric tokens.
|
|
3322
|
-
if (!looksLikeUntypedNumericTokenLex(lex)) {
|
|
3323
|
-
__parseNumCache.set(key, null);
|
|
3095
|
+
const [lex, dt] = literalParts(t.value);
|
|
3096
|
+
if (dt !== XSD_NS + 'date')
|
|
3324
3097
|
return null;
|
|
3325
|
-
|
|
3326
|
-
const
|
|
3327
|
-
if (
|
|
3328
|
-
__parseNumCache.set(key, null);
|
|
3329
|
-
return null;
|
|
3330
|
-
}
|
|
3331
|
-
__parseNumCache.set(key, n);
|
|
3332
|
-
return n;
|
|
3333
|
-
}
|
|
3334
|
-
function parseIntLiteral(t) {
|
|
3335
|
-
// Parse as BigInt if (and only if) it is an integer literal in an integer datatype,
|
|
3336
|
-
// or an untyped integer token.
|
|
3337
|
-
if (!(t instanceof Literal))
|
|
3338
|
-
return null;
|
|
3339
|
-
const key = t.value;
|
|
3340
|
-
if (__parseIntCache.has(key))
|
|
3341
|
-
return __parseIntCache.get(key);
|
|
3342
|
-
const [lex, dt] = literalParts(key);
|
|
3343
|
-
if (dt !== null) {
|
|
3344
|
-
if (!isXsdIntegerDatatype(dt)) {
|
|
3345
|
-
__parseIntCache.set(key, null);
|
|
3346
|
-
return null;
|
|
3347
|
-
}
|
|
3348
|
-
const val = stripQuotes(lex);
|
|
3349
|
-
if (!/^[+-]?\d+$/.test(val)) {
|
|
3350
|
-
__parseIntCache.set(key, null);
|
|
3351
|
-
return null;
|
|
3352
|
-
}
|
|
3353
|
-
try {
|
|
3354
|
-
const out = BigInt(val);
|
|
3355
|
-
__parseIntCache.set(key, out);
|
|
3356
|
-
return out;
|
|
3357
|
-
}
|
|
3358
|
-
catch {
|
|
3359
|
-
__parseIntCache.set(key, null);
|
|
3360
|
-
return null;
|
|
3361
|
-
}
|
|
3362
|
-
}
|
|
3363
|
-
// Untyped: only accept unquoted integer tokens.
|
|
3364
|
-
if (isQuotedLexical(lex)) {
|
|
3365
|
-
__parseIntCache.set(key, null);
|
|
3366
|
-
return null;
|
|
3367
|
-
}
|
|
3368
|
-
if (!/^[+-]?\d+$/.test(lex)) {
|
|
3369
|
-
__parseIntCache.set(key, null);
|
|
3370
|
-
return null;
|
|
3371
|
-
}
|
|
3372
|
-
try {
|
|
3373
|
-
const out = BigInt(lex);
|
|
3374
|
-
__parseIntCache.set(key, out);
|
|
3375
|
-
return out;
|
|
3376
|
-
}
|
|
3377
|
-
catch {
|
|
3378
|
-
__parseIntCache.set(key, null);
|
|
3379
|
-
return null;
|
|
3380
|
-
}
|
|
3381
|
-
}
|
|
3382
|
-
function formatNum(n) {
|
|
3383
|
-
return String(n);
|
|
3384
|
-
}
|
|
3385
|
-
function parseXsdDecimalToBigIntScale(s) {
|
|
3386
|
-
let t = String(s).trim();
|
|
3387
|
-
let sign = 1n;
|
|
3388
|
-
if (t.startsWith('+'))
|
|
3389
|
-
t = t.slice(1);
|
|
3390
|
-
else if (t.startsWith('-')) {
|
|
3391
|
-
sign = -1n;
|
|
3392
|
-
t = t.slice(1);
|
|
3393
|
-
}
|
|
3394
|
-
// xsd:decimal lexical: (\d+(\.\d*)?|\.\d+)
|
|
3395
|
-
if (!/^(?:\d+(?:\.\d*)?|\.\d+)$/.test(t))
|
|
3396
|
-
return null;
|
|
3397
|
-
let intPart = '0';
|
|
3398
|
-
let fracPart = '';
|
|
3399
|
-
if (t.includes('.')) {
|
|
3400
|
-
const parts = t.split('.');
|
|
3401
|
-
intPart = parts[0] === '' ? '0' : parts[0];
|
|
3402
|
-
fracPart = parts[1] || '';
|
|
3403
|
-
}
|
|
3404
|
-
else {
|
|
3405
|
-
intPart = t;
|
|
3406
|
-
}
|
|
3407
|
-
// normalize
|
|
3408
|
-
intPart = intPart.replace(/^0+(?=\d)/, '');
|
|
3409
|
-
fracPart = fracPart.replace(/0+$/, ''); // drop trailing zeros
|
|
3410
|
-
const scale = fracPart.length;
|
|
3411
|
-
const digits = intPart + fracPart || '0';
|
|
3412
|
-
return { num: sign * BigInt(digits), scale };
|
|
3413
|
-
}
|
|
3414
|
-
function pow10n(k) {
|
|
3415
|
-
return 10n ** BigInt(k);
|
|
3416
|
-
}
|
|
3417
|
-
// ===========================================================================
|
|
3418
|
-
// Time & duration builtin helpers
|
|
3419
|
-
// ===========================================================================
|
|
3420
|
-
function parseXsdDateTerm(t) {
|
|
3421
|
-
if (!(t instanceof Literal))
|
|
3422
|
-
return null;
|
|
3423
|
-
const [lex, dt] = literalParts(t.value);
|
|
3424
|
-
if (dt !== XSD_NS + 'date')
|
|
3425
|
-
return null;
|
|
3426
|
-
const val = stripQuotes(lex);
|
|
3427
|
-
const d = new Date(val + 'T00:00:00Z');
|
|
3428
|
-
if (Number.isNaN(d.getTime()))
|
|
3098
|
+
const val = stripQuotes(lex);
|
|
3099
|
+
const d = new Date(val + 'T00:00:00Z');
|
|
3100
|
+
if (Number.isNaN(d.getTime()))
|
|
3429
3101
|
return null;
|
|
3430
3102
|
return d;
|
|
3431
3103
|
}
|
|
@@ -6094,6 +5766,336 @@ function isBuiltinPred(p) {
|
|
|
6094
5766
|
v.startsWith(TIME_NS) ||
|
|
6095
5767
|
v.startsWith(LIST_NS));
|
|
6096
5768
|
}
|
|
5769
|
+
// @ts-nocheck
|
|
5770
|
+
/* eslint-disable */
|
|
5771
|
+
// ===========================================================================
|
|
5772
|
+
// Unification + substitution
|
|
5773
|
+
// ===========================================================================
|
|
5774
|
+
function containsVarTerm(t, v) {
|
|
5775
|
+
if (t instanceof Var)
|
|
5776
|
+
return t.name === v;
|
|
5777
|
+
if (t instanceof ListTerm)
|
|
5778
|
+
return t.elems.some((e) => containsVarTerm(e, v));
|
|
5779
|
+
if (t instanceof OpenListTerm)
|
|
5780
|
+
return t.prefix.some((e) => containsVarTerm(e, v)) || t.tailVar === v;
|
|
5781
|
+
if (t instanceof GraphTerm)
|
|
5782
|
+
return t.triples.some((tr) => containsVarTerm(tr.s, v) || containsVarTerm(tr.p, v) || containsVarTerm(tr.o, v));
|
|
5783
|
+
return false;
|
|
5784
|
+
}
|
|
5785
|
+
function isGroundTermInGraph(t) {
|
|
5786
|
+
// variables inside graph terms are treated as local placeholders,
|
|
5787
|
+
// so they don't make the *surrounding triple* non-ground.
|
|
5788
|
+
if (t instanceof OpenListTerm)
|
|
5789
|
+
return false;
|
|
5790
|
+
if (t instanceof ListTerm)
|
|
5791
|
+
return t.elems.every((e) => isGroundTermInGraph(e));
|
|
5792
|
+
if (t instanceof GraphTerm)
|
|
5793
|
+
return t.triples.every((tr) => isGroundTripleInGraph(tr));
|
|
5794
|
+
// Iri/Literal/Blank/Var are all OK inside formulas
|
|
5795
|
+
return true;
|
|
5796
|
+
}
|
|
5797
|
+
function isGroundTripleInGraph(tr) {
|
|
5798
|
+
return isGroundTermInGraph(tr.s) && isGroundTermInGraph(tr.p) && isGroundTermInGraph(tr.o);
|
|
5799
|
+
}
|
|
5800
|
+
function isGroundTerm(t) {
|
|
5801
|
+
if (t instanceof Var)
|
|
5802
|
+
return false;
|
|
5803
|
+
if (t instanceof ListTerm)
|
|
5804
|
+
return t.elems.every((e) => isGroundTerm(e));
|
|
5805
|
+
if (t instanceof OpenListTerm)
|
|
5806
|
+
return false;
|
|
5807
|
+
if (t instanceof GraphTerm)
|
|
5808
|
+
return t.triples.every((tr) => isGroundTripleInGraph(tr));
|
|
5809
|
+
return true;
|
|
5810
|
+
}
|
|
5811
|
+
function isGroundTriple(tr) {
|
|
5812
|
+
return isGroundTerm(tr.s) && isGroundTerm(tr.p) && isGroundTerm(tr.o);
|
|
5813
|
+
}
|
|
5814
|
+
// Canonical JSON-ish encoding for use as a Skolem cache key.
|
|
5815
|
+
// We only *call* this on ground terms in log:skolem, but it is
|
|
5816
|
+
// robust to seeing vars/open lists anyway.
|
|
5817
|
+
function skolemKeyFromTerm(t) {
|
|
5818
|
+
function enc(u) {
|
|
5819
|
+
if (u instanceof Iri)
|
|
5820
|
+
return ['I', u.value];
|
|
5821
|
+
if (u instanceof Literal)
|
|
5822
|
+
return ['L', u.value];
|
|
5823
|
+
if (u instanceof Blank)
|
|
5824
|
+
return ['B', u.label];
|
|
5825
|
+
if (u instanceof Var)
|
|
5826
|
+
return ['V', u.name];
|
|
5827
|
+
if (u instanceof ListTerm)
|
|
5828
|
+
return ['List', u.elems.map(enc)];
|
|
5829
|
+
if (u instanceof OpenListTerm)
|
|
5830
|
+
return ['OpenList', u.prefix.map(enc), u.tailVar];
|
|
5831
|
+
if (u instanceof GraphTerm)
|
|
5832
|
+
return ['Graph', u.triples.map((tr) => [enc(tr.s), enc(tr.p), enc(tr.o)])];
|
|
5833
|
+
return ['Other', String(u)];
|
|
5834
|
+
}
|
|
5835
|
+
return JSON.stringify(enc(t));
|
|
5836
|
+
}
|
|
5837
|
+
function applySubstTerm(t, s) {
|
|
5838
|
+
// Common case: variable
|
|
5839
|
+
if (t instanceof Var) {
|
|
5840
|
+
// Fast path: unbound variable → no change
|
|
5841
|
+
const first = s[t.name];
|
|
5842
|
+
if (first === undefined) {
|
|
5843
|
+
return t;
|
|
5844
|
+
}
|
|
5845
|
+
// Follow chains X -> Y -> ... until we hit a non-var or a cycle.
|
|
5846
|
+
let cur = first;
|
|
5847
|
+
const seen = new Set([t.name]);
|
|
5848
|
+
while (cur instanceof Var) {
|
|
5849
|
+
const name = cur.name;
|
|
5850
|
+
if (seen.has(name))
|
|
5851
|
+
break; // cycle
|
|
5852
|
+
seen.add(name);
|
|
5853
|
+
const nxt = s[name];
|
|
5854
|
+
if (!nxt)
|
|
5855
|
+
break;
|
|
5856
|
+
cur = nxt;
|
|
5857
|
+
}
|
|
5858
|
+
if (cur instanceof Var) {
|
|
5859
|
+
// Still a var: keep it as is (no need to clone)
|
|
5860
|
+
return cur;
|
|
5861
|
+
}
|
|
5862
|
+
// Bound to a non-var term: apply substitution recursively in case it
|
|
5863
|
+
// contains variables inside.
|
|
5864
|
+
return applySubstTerm(cur, s);
|
|
5865
|
+
}
|
|
5866
|
+
// Non-variable terms
|
|
5867
|
+
if (t instanceof ListTerm) {
|
|
5868
|
+
return new ListTerm(t.elems.map((e) => applySubstTerm(e, s)));
|
|
5869
|
+
}
|
|
5870
|
+
if (t instanceof OpenListTerm) {
|
|
5871
|
+
const newPrefix = t.prefix.map((e) => applySubstTerm(e, s));
|
|
5872
|
+
const tailTerm = s[t.tailVar];
|
|
5873
|
+
if (tailTerm !== undefined) {
|
|
5874
|
+
const tailApplied = applySubstTerm(tailTerm, s);
|
|
5875
|
+
if (tailApplied instanceof ListTerm) {
|
|
5876
|
+
return new ListTerm(newPrefix.concat(tailApplied.elems));
|
|
5877
|
+
}
|
|
5878
|
+
else if (tailApplied instanceof OpenListTerm) {
|
|
5879
|
+
return new OpenListTerm(newPrefix.concat(tailApplied.prefix), tailApplied.tailVar);
|
|
5880
|
+
}
|
|
5881
|
+
else {
|
|
5882
|
+
return new OpenListTerm(newPrefix, t.tailVar);
|
|
5883
|
+
}
|
|
5884
|
+
}
|
|
5885
|
+
else {
|
|
5886
|
+
return new OpenListTerm(newPrefix, t.tailVar);
|
|
5887
|
+
}
|
|
5888
|
+
}
|
|
5889
|
+
if (t instanceof GraphTerm) {
|
|
5890
|
+
return new GraphTerm(t.triples.map((tr) => applySubstTriple(tr, s)));
|
|
5891
|
+
}
|
|
5892
|
+
return t;
|
|
5893
|
+
}
|
|
5894
|
+
function applySubstTriple(tr, s) {
|
|
5895
|
+
return new Triple(applySubstTerm(tr.s, s), applySubstTerm(tr.p, s), applySubstTerm(tr.o, s));
|
|
5896
|
+
}
|
|
5897
|
+
function iriValue(t) {
|
|
5898
|
+
return t instanceof Iri ? t.value : null;
|
|
5899
|
+
}
|
|
5900
|
+
function unifyOpenWithList(prefix, tailv, ys, subst) {
|
|
5901
|
+
if (ys.length < prefix.length)
|
|
5902
|
+
return null;
|
|
5903
|
+
let s2 = { ...subst };
|
|
5904
|
+
for (let i = 0; i < prefix.length; i++) {
|
|
5905
|
+
s2 = unifyTerm(prefix[i], ys[i], s2);
|
|
5906
|
+
if (s2 === null)
|
|
5907
|
+
return null;
|
|
5908
|
+
}
|
|
5909
|
+
const rest = new ListTerm(ys.slice(prefix.length));
|
|
5910
|
+
s2 = unifyTerm(new Var(tailv), rest, s2);
|
|
5911
|
+
if (s2 === null)
|
|
5912
|
+
return null;
|
|
5913
|
+
return s2;
|
|
5914
|
+
}
|
|
5915
|
+
function unifyGraphTriples(xs, ys, subst) {
|
|
5916
|
+
if (xs.length !== ys.length)
|
|
5917
|
+
return null;
|
|
5918
|
+
// Fast path: exact same sequence.
|
|
5919
|
+
if (triplesListEqual(xs, ys))
|
|
5920
|
+
return { ...subst };
|
|
5921
|
+
// Backtracking match (order-insensitive), *threading* the substitution through.
|
|
5922
|
+
const used = new Array(ys.length).fill(false);
|
|
5923
|
+
function step(i, s) {
|
|
5924
|
+
if (i >= xs.length)
|
|
5925
|
+
return s;
|
|
5926
|
+
const x = xs[i];
|
|
5927
|
+
for (let j = 0; j < ys.length; j++) {
|
|
5928
|
+
if (used[j])
|
|
5929
|
+
continue;
|
|
5930
|
+
const y = ys[j];
|
|
5931
|
+
// Cheap pruning when both predicates are IRIs.
|
|
5932
|
+
if (x.p instanceof Iri && y.p instanceof Iri && x.p.value !== y.p.value)
|
|
5933
|
+
continue;
|
|
5934
|
+
const s2 = unifyTriple(x, y, s); // IMPORTANT: use `s`, not {}
|
|
5935
|
+
if (s2 === null)
|
|
5936
|
+
continue;
|
|
5937
|
+
used[j] = true;
|
|
5938
|
+
const s3 = step(i + 1, s2);
|
|
5939
|
+
if (s3 !== null)
|
|
5940
|
+
return s3;
|
|
5941
|
+
used[j] = false;
|
|
5942
|
+
}
|
|
5943
|
+
return null;
|
|
5944
|
+
}
|
|
5945
|
+
return step(0, { ...subst }); // IMPORTANT: start from the incoming subst
|
|
5946
|
+
}
|
|
5947
|
+
function unifyTerm(a, b, subst) {
|
|
5948
|
+
return unifyTermWithOptions(a, b, subst, {
|
|
5949
|
+
boolValueEq: true,
|
|
5950
|
+
intDecimalEq: false,
|
|
5951
|
+
});
|
|
5952
|
+
}
|
|
5953
|
+
function unifyTermListAppend(a, b, subst) {
|
|
5954
|
+
// Keep list:append behavior: allow integer<->decimal exact equality,
|
|
5955
|
+
// but do NOT add boolean-value equivalence (preserves current semantics).
|
|
5956
|
+
return unifyTermWithOptions(a, b, subst, {
|
|
5957
|
+
boolValueEq: false,
|
|
5958
|
+
intDecimalEq: true,
|
|
5959
|
+
});
|
|
5960
|
+
}
|
|
5961
|
+
function unifyTermWithOptions(a, b, subst, opts) {
|
|
5962
|
+
a = applySubstTerm(a, subst);
|
|
5963
|
+
b = applySubstTerm(b, subst);
|
|
5964
|
+
// Variable binding
|
|
5965
|
+
if (a instanceof Var) {
|
|
5966
|
+
const v = a.name;
|
|
5967
|
+
const t = b;
|
|
5968
|
+
if (t instanceof Var && t.name === v)
|
|
5969
|
+
return { ...subst };
|
|
5970
|
+
if (containsVarTerm(t, v))
|
|
5971
|
+
return null;
|
|
5972
|
+
const s2 = { ...subst };
|
|
5973
|
+
s2[v] = t;
|
|
5974
|
+
return s2;
|
|
5975
|
+
}
|
|
5976
|
+
if (b instanceof Var) {
|
|
5977
|
+
return unifyTermWithOptions(b, a, subst, opts);
|
|
5978
|
+
}
|
|
5979
|
+
// Exact matches
|
|
5980
|
+
if (a instanceof Iri && b instanceof Iri && a.value === b.value)
|
|
5981
|
+
return { ...subst };
|
|
5982
|
+
if (a instanceof Literal && b instanceof Literal && a.value === b.value)
|
|
5983
|
+
return { ...subst };
|
|
5984
|
+
if (a instanceof Blank && b instanceof Blank && a.label === b.label)
|
|
5985
|
+
return { ...subst };
|
|
5986
|
+
// Plain string vs xsd:string equivalence
|
|
5987
|
+
if (a instanceof Literal && b instanceof Literal) {
|
|
5988
|
+
if (literalsEquivalentAsXsdString(a.value, b.value))
|
|
5989
|
+
return { ...subst };
|
|
5990
|
+
}
|
|
5991
|
+
// Boolean-value equivalence (ONLY for normal unifyTerm)
|
|
5992
|
+
if (opts.boolValueEq && a instanceof Literal && b instanceof Literal) {
|
|
5993
|
+
const ai = parseBooleanLiteralInfo(a);
|
|
5994
|
+
const bi = parseBooleanLiteralInfo(b);
|
|
5995
|
+
if (ai && bi && ai.value === bi.value)
|
|
5996
|
+
return { ...subst };
|
|
5997
|
+
}
|
|
5998
|
+
// Numeric-value match:
|
|
5999
|
+
// - always allow equality when datatype matches (existing behavior)
|
|
6000
|
+
// - optionally allow integer<->decimal exact equality (list:append only)
|
|
6001
|
+
if (a instanceof Literal && b instanceof Literal) {
|
|
6002
|
+
const ai = parseNumericLiteralInfo(a);
|
|
6003
|
+
const bi = parseNumericLiteralInfo(b);
|
|
6004
|
+
if (ai && bi) {
|
|
6005
|
+
if (ai.dt === bi.dt) {
|
|
6006
|
+
if (ai.kind === 'bigint' && bi.kind === 'bigint') {
|
|
6007
|
+
if (ai.value === bi.value)
|
|
6008
|
+
return { ...subst };
|
|
6009
|
+
}
|
|
6010
|
+
else {
|
|
6011
|
+
const an = ai.kind === 'bigint' ? Number(ai.value) : ai.value;
|
|
6012
|
+
const bn = bi.kind === 'bigint' ? Number(bi.value) : bi.value;
|
|
6013
|
+
if (!Number.isNaN(an) && !Number.isNaN(bn) && an === bn)
|
|
6014
|
+
return { ...subst };
|
|
6015
|
+
}
|
|
6016
|
+
}
|
|
6017
|
+
if (opts.intDecimalEq) {
|
|
6018
|
+
const intDt = XSD_NS + 'integer';
|
|
6019
|
+
const decDt = XSD_NS + 'decimal';
|
|
6020
|
+
if ((ai.dt === intDt && bi.dt === decDt) || (ai.dt === decDt && bi.dt === intDt)) {
|
|
6021
|
+
const intInfo = ai.dt === intDt ? ai : bi; // bigint
|
|
6022
|
+
const decInfo = ai.dt === decDt ? ai : bi; // number + lexStr
|
|
6023
|
+
const dec = parseXsdDecimalToBigIntScale(decInfo.lexStr);
|
|
6024
|
+
if (dec) {
|
|
6025
|
+
const scaledInt = intInfo.value * pow10n(dec.scale);
|
|
6026
|
+
if (scaledInt === dec.num)
|
|
6027
|
+
return { ...subst };
|
|
6028
|
+
}
|
|
6029
|
+
}
|
|
6030
|
+
}
|
|
6031
|
+
}
|
|
6032
|
+
}
|
|
6033
|
+
// Open list vs concrete list
|
|
6034
|
+
if (a instanceof OpenListTerm && b instanceof ListTerm) {
|
|
6035
|
+
return unifyOpenWithList(a.prefix, a.tailVar, b.elems, subst);
|
|
6036
|
+
}
|
|
6037
|
+
if (a instanceof ListTerm && b instanceof OpenListTerm) {
|
|
6038
|
+
return unifyOpenWithList(b.prefix, b.tailVar, a.elems, subst);
|
|
6039
|
+
}
|
|
6040
|
+
// Open list vs open list
|
|
6041
|
+
if (a instanceof OpenListTerm && b instanceof OpenListTerm) {
|
|
6042
|
+
if (a.tailVar !== b.tailVar || a.prefix.length !== b.prefix.length)
|
|
6043
|
+
return null;
|
|
6044
|
+
let s2 = { ...subst };
|
|
6045
|
+
for (let i = 0; i < a.prefix.length; i++) {
|
|
6046
|
+
s2 = unifyTermWithOptions(a.prefix[i], b.prefix[i], s2, opts);
|
|
6047
|
+
if (s2 === null)
|
|
6048
|
+
return null;
|
|
6049
|
+
}
|
|
6050
|
+
return s2;
|
|
6051
|
+
}
|
|
6052
|
+
// List terms
|
|
6053
|
+
if (a instanceof ListTerm && b instanceof ListTerm) {
|
|
6054
|
+
if (a.elems.length !== b.elems.length)
|
|
6055
|
+
return null;
|
|
6056
|
+
let s2 = { ...subst };
|
|
6057
|
+
for (let i = 0; i < a.elems.length; i++) {
|
|
6058
|
+
s2 = unifyTermWithOptions(a.elems[i], b.elems[i], s2, opts);
|
|
6059
|
+
if (s2 === null)
|
|
6060
|
+
return null;
|
|
6061
|
+
}
|
|
6062
|
+
return s2;
|
|
6063
|
+
}
|
|
6064
|
+
// Graphs
|
|
6065
|
+
if (a instanceof GraphTerm && b instanceof GraphTerm) {
|
|
6066
|
+
if (alphaEqGraphTriples(a.triples, b.triples))
|
|
6067
|
+
return { ...subst };
|
|
6068
|
+
return unifyGraphTriples(a.triples, b.triples, subst);
|
|
6069
|
+
}
|
|
6070
|
+
return null;
|
|
6071
|
+
}
|
|
6072
|
+
function unifyTriple(pat, fact, subst) {
|
|
6073
|
+
// Predicates are usually the cheapest and most selective
|
|
6074
|
+
const s1 = unifyTerm(pat.p, fact.p, subst);
|
|
6075
|
+
if (s1 === null)
|
|
6076
|
+
return null;
|
|
6077
|
+
const s2 = unifyTerm(pat.s, fact.s, s1);
|
|
6078
|
+
if (s2 === null)
|
|
6079
|
+
return null;
|
|
6080
|
+
const s3 = unifyTerm(pat.o, fact.o, s2);
|
|
6081
|
+
return s3;
|
|
6082
|
+
}
|
|
6083
|
+
function composeSubst(outer, delta) {
|
|
6084
|
+
if (!delta || Object.keys(delta).length === 0) {
|
|
6085
|
+
return { ...outer };
|
|
6086
|
+
}
|
|
6087
|
+
const out = { ...outer };
|
|
6088
|
+
for (const [k, v] of Object.entries(delta)) {
|
|
6089
|
+
if (out.hasOwnProperty(k)) {
|
|
6090
|
+
if (!termsEqual(out[k], v))
|
|
6091
|
+
return null;
|
|
6092
|
+
}
|
|
6093
|
+
else {
|
|
6094
|
+
out[k] = v;
|
|
6095
|
+
}
|
|
6096
|
+
}
|
|
6097
|
+
return out;
|
|
6098
|
+
}
|
|
6097
6099
|
// ===========================================================================
|
|
6098
6100
|
// Backward proof (SLD-style)
|
|
6099
6101
|
// ===========================================================================
|