eyeling 1.24.33 → 1.25.1
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/HANDBOOK.md +2 -2
- package/dist/browser/eyeling.browser.js +152 -52
- package/dist/browser/index.mjs +3 -0
- package/examples/fuse.n3 +1 -1
- package/examples/liar.n3 +1 -1
- package/eyeling.js +152 -52
- package/index.d.ts +3 -0
- package/lib/cli.js +1 -1
- package/lib/engine.js +45 -22
- package/lib/entry.js +1 -0
- package/lib/lexer.js +62 -21
- package/lib/parser.js +9 -1
- package/lib/prelude.js +34 -7
- package/package.json +1 -1
- package/test/api.test.js +5 -5
- package/test/package.test.js +2 -2
- package/tools/bundle.js +3 -0
package/eyeling.js
CHANGED
|
@@ -4825,7 +4825,7 @@ function main() {
|
|
|
4825
4825
|
parseN3Text(text, {
|
|
4826
4826
|
baseIri: __sourceLabelToBaseIri(sourceLabel),
|
|
4827
4827
|
label: sourceLabel,
|
|
4828
|
-
collectUsedPrefixes:
|
|
4828
|
+
collectUsedPrefixes: streamMode,
|
|
4829
4829
|
keepSourceArtifacts: false,
|
|
4830
4830
|
rdf: rdfMode,
|
|
4831
4831
|
}),
|
|
@@ -5517,6 +5517,10 @@ const {
|
|
|
5517
5517
|
copyQuotedGraphMetadata,
|
|
5518
5518
|
} = require('./prelude');
|
|
5519
5519
|
|
|
5520
|
+
// Inference fuses use sysexits.h EX_DATAERR (65): input/rules made a
|
|
5521
|
+
// forbidden condition provable, rather than a generic usage/runtime error.
|
|
5522
|
+
const INFERENCE_FUSE_EXIT_CODE = 65;
|
|
5523
|
+
|
|
5520
5524
|
// In N3/Turtle, rdf:nil is the canonical IRI for the empty RDF list.
|
|
5521
5525
|
// Eyeling represents list literals with ListTerm; ensure rdf:nil unifies with ().
|
|
5522
5526
|
const RDF_NIL_IRI = RDF_NS + 'nil';
|
|
@@ -6532,11 +6536,13 @@ function termFastKey(t) {
|
|
|
6532
6536
|
if (t instanceof Iri || t instanceof Blank) return t.__tid;
|
|
6533
6537
|
|
|
6534
6538
|
if (t instanceof Literal) {
|
|
6535
|
-
//
|
|
6536
|
-
//
|
|
6537
|
-
//
|
|
6538
|
-
//
|
|
6539
|
-
//
|
|
6539
|
+
// Literal construction already computed a value-stable __tid for ordinary
|
|
6540
|
+
// short literals. Avoid re-running literalParts()/datatype normalization
|
|
6541
|
+
// while building fact indexes; on data-heavy inputs this is a hot path.
|
|
6542
|
+
// Only the rare over-sized literal needs the value-based fallback because
|
|
6543
|
+
// prelude intentionally gives such literals per-object ids to avoid
|
|
6544
|
+
// retaining huge strings in the global interner.
|
|
6545
|
+
if (typeof t.value !== 'string' || t.value.length + 64 <= MAX_LITERAL_TID_LEN) return t.__tid;
|
|
6540
6546
|
const norm = normalizeLiteralForTid(t.value);
|
|
6541
6547
|
if (typeof norm === 'string' && norm.length > MAX_LITERAL_TID_LEN) return 'L:' + norm;
|
|
6542
6548
|
return t.__tid;
|
|
@@ -6623,17 +6629,28 @@ function ensureFactIndexes(facts) {
|
|
|
6623
6629
|
enumerable: false,
|
|
6624
6630
|
writable: true,
|
|
6625
6631
|
});
|
|
6632
|
+
Object.defineProperty(facts, '__keySetComplete', {
|
|
6633
|
+
value: false,
|
|
6634
|
+
enumerable: false,
|
|
6635
|
+
writable: true,
|
|
6636
|
+
});
|
|
6626
6637
|
|
|
6627
|
-
|
|
6638
|
+
// Build lookup indexes eagerly, but do not populate the duplicate-detection
|
|
6639
|
+
// string Set for every input fact. The predicate/subject/object indexes are
|
|
6640
|
+
// enough to verify duplicates when needed; avoiding 100k+ joined string keys
|
|
6641
|
+
// saves substantial time and GC on data-heavy query workloads.
|
|
6642
|
+
for (let i = 0; i < facts.length; i++) indexFact(facts, facts[i], i, false);
|
|
6628
6643
|
}
|
|
6629
6644
|
|
|
6630
|
-
function indexFact(facts, tr, idx) {
|
|
6645
|
+
function indexFact(facts, tr, idx, addKeySet = true) {
|
|
6631
6646
|
const sk = termFastKey(tr.s);
|
|
6632
6647
|
const ok = termFastKey(tr.o);
|
|
6648
|
+
let pkForKey = null;
|
|
6633
6649
|
|
|
6634
6650
|
if (tr.p instanceof Iri) {
|
|
6635
6651
|
// Use predicate term id as the primary key to avoid hashing long IRI strings.
|
|
6636
6652
|
const pk = tr.p.__tid;
|
|
6653
|
+
pkForKey = pk;
|
|
6637
6654
|
|
|
6638
6655
|
let pb = facts.__byPred.get(pk);
|
|
6639
6656
|
if (!pb) {
|
|
@@ -6691,8 +6708,10 @@ function indexFact(facts, tr, idx) {
|
|
|
6691
6708
|
}
|
|
6692
6709
|
}
|
|
6693
6710
|
|
|
6694
|
-
|
|
6695
|
-
|
|
6711
|
+
if (addKeySet && sk !== null && ok !== null) {
|
|
6712
|
+
if (pkForKey === null) pkForKey = termFastKey(tr.p);
|
|
6713
|
+
if (pkForKey !== null) facts.__keySet.add(sk + '\t' + pkForKey + '\t' + ok);
|
|
6714
|
+
}
|
|
6696
6715
|
}
|
|
6697
6716
|
|
|
6698
6717
|
function candidateFacts(facts, goal) {
|
|
@@ -6754,7 +6773,10 @@ function hasFactIndexed(facts, tr) {
|
|
|
6754
6773
|
ensureFactIndexes(facts);
|
|
6755
6774
|
|
|
6756
6775
|
const key = tripleFastKey(tr);
|
|
6757
|
-
if (key !== null)
|
|
6776
|
+
if (key !== null) {
|
|
6777
|
+
if (facts.__keySet.has(key)) return true;
|
|
6778
|
+
if (facts.__keySetComplete) return false;
|
|
6779
|
+
}
|
|
6758
6780
|
|
|
6759
6781
|
if (tr.p instanceof Iri) {
|
|
6760
6782
|
const pk = tr.p.__tid;
|
|
@@ -6784,7 +6806,7 @@ function pushFactIndexed(facts, tr) {
|
|
|
6784
6806
|
ensureFactIndexes(facts);
|
|
6785
6807
|
const idx = facts.length;
|
|
6786
6808
|
facts.push(tr);
|
|
6787
|
-
indexFact(facts, tr, idx);
|
|
6809
|
+
indexFact(facts, tr, idx, true);
|
|
6788
6810
|
}
|
|
6789
6811
|
|
|
6790
6812
|
function makeDerivedRecord(fact, rule, premises, subst, captureExplanations) {
|
|
@@ -8321,11 +8343,9 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
8321
8343
|
const varGen = [0];
|
|
8322
8344
|
const skCounter = [0];
|
|
8323
8345
|
|
|
8324
|
-
//
|
|
8325
|
-
//
|
|
8326
|
-
|
|
8327
|
-
__ensureRuleKeySet(forwardRules);
|
|
8328
|
-
__ensureRuleKeySet(backRules);
|
|
8346
|
+
// Rule-key sets are only needed if a program actually derives rule-producing
|
|
8347
|
+
// triples. Building them eagerly is expensive on large static rule sets, so
|
|
8348
|
+
// dynamic-promotion sites create them lazily before duplicate checks.
|
|
8329
8349
|
|
|
8330
8350
|
// Cache head blank-node skolemization per (rule firing, head blank label).
|
|
8331
8351
|
// This prevents repeatedly generating fresh _:sk_N blanks for the *same*
|
|
@@ -8445,7 +8465,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
8445
8465
|
// Allow dynamic fuses: ... => ?X. where ?X becomes false
|
|
8446
8466
|
if (dynTerm instanceof Literal && dynTerm.value === 'false') {
|
|
8447
8467
|
__printTriggeredFuse(r, opts && opts.prefixes, s, 'Dynamic head resolved to false.');
|
|
8448
|
-
__exitReasoning(
|
|
8468
|
+
__exitReasoning(INFERENCE_FUSE_EXIT_CODE, 'Inference fuse triggered.');
|
|
8449
8469
|
}
|
|
8450
8470
|
|
|
8451
8471
|
const dynTriples = __graphTriplesOrTrue(dynTerm);
|
|
@@ -8504,8 +8524,9 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
8504
8524
|
newRule.conclusion,
|
|
8505
8525
|
newRule.__dynamicConclusionTerm || null,
|
|
8506
8526
|
);
|
|
8507
|
-
|
|
8508
|
-
|
|
8527
|
+
const forwardRuleKeySet = __ensureRuleKeySet(forwardRules);
|
|
8528
|
+
if (!forwardRuleKeySet.has(key)) {
|
|
8529
|
+
forwardRuleKeySet.add(key);
|
|
8509
8530
|
forwardRules.push(newRule);
|
|
8510
8531
|
rulesChanged = true;
|
|
8511
8532
|
}
|
|
@@ -8519,8 +8540,9 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
8519
8540
|
newRule.conclusion,
|
|
8520
8541
|
newRule.__dynamicConclusionTerm || null,
|
|
8521
8542
|
);
|
|
8522
|
-
|
|
8523
|
-
|
|
8543
|
+
const backRuleKeySet = __ensureRuleKeySet(backRules);
|
|
8544
|
+
if (!backRuleKeySet.has(key)) {
|
|
8545
|
+
backRuleKeySet.add(key);
|
|
8524
8546
|
backRules.push(newRule);
|
|
8525
8547
|
indexBackRule(backRules, newRule);
|
|
8526
8548
|
rulesChanged = true;
|
|
@@ -8606,7 +8628,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
8606
8628
|
// Inference fuse
|
|
8607
8629
|
if (r.isFuse && sols.length) {
|
|
8608
8630
|
__printTriggeredFuse(r, opts && opts.prefixes, sols[0]);
|
|
8609
|
-
__exitReasoning(
|
|
8631
|
+
__exitReasoning(INFERENCE_FUSE_EXIT_CODE, 'Inference fuse triggered.');
|
|
8610
8632
|
}
|
|
8611
8633
|
|
|
8612
8634
|
for (const s of sols) {
|
|
@@ -9143,6 +9165,7 @@ module.exports = {
|
|
|
9143
9165
|
registerBuiltinModule,
|
|
9144
9166
|
loadBuiltinModule,
|
|
9145
9167
|
listBuiltinIris,
|
|
9168
|
+
INFERENCE_FUSE_EXIT_CODE,
|
|
9146
9169
|
};
|
|
9147
9170
|
|
|
9148
9171
|
};
|
|
@@ -9170,6 +9193,7 @@ module.exports = {
|
|
|
9170
9193
|
rdfjs: dataFactory,
|
|
9171
9194
|
main: engine.main,
|
|
9172
9195
|
version: engine.version,
|
|
9196
|
+
INFERENCE_FUSE_EXIT_CODE: engine.INFERENCE_FUSE_EXIT_CODE,
|
|
9173
9197
|
|
|
9174
9198
|
// internals for playground.html
|
|
9175
9199
|
lex: engine.lex,
|
|
@@ -9456,7 +9480,26 @@ class N3SyntaxError extends SyntaxError {
|
|
|
9456
9480
|
}
|
|
9457
9481
|
|
|
9458
9482
|
function isWs(c) {
|
|
9459
|
-
|
|
9483
|
+
if (c === null || c === undefined) return false;
|
|
9484
|
+
const code = c.charCodeAt(0);
|
|
9485
|
+
// Fast path for the whitespace used by N3/Turtle inputs.
|
|
9486
|
+
return code === 0x20 || code === 0x09 || code === 0x0a || code === 0x0d || code === 0x0c;
|
|
9487
|
+
}
|
|
9488
|
+
|
|
9489
|
+
function isAsciiAlphaCode(code) {
|
|
9490
|
+
return (code >= 65 && code <= 90) || (code >= 97 && code <= 122);
|
|
9491
|
+
}
|
|
9492
|
+
|
|
9493
|
+
function isAsciiDigitCode(code) {
|
|
9494
|
+
return code >= 48 && code <= 57;
|
|
9495
|
+
}
|
|
9496
|
+
|
|
9497
|
+
function isAsciiAlpha(c) {
|
|
9498
|
+
return c !== null && c !== undefined && isAsciiAlphaCode(c.charCodeAt(0));
|
|
9499
|
+
}
|
|
9500
|
+
|
|
9501
|
+
function isAsciiDigit(c) {
|
|
9502
|
+
return c !== null && c !== undefined && isAsciiDigitCode(c.charCodeAt(0));
|
|
9460
9503
|
}
|
|
9461
9504
|
|
|
9462
9505
|
// Turtle/N3 prefixed names (PNAME_*) allow many Unicode letters and certain
|
|
@@ -9469,13 +9512,18 @@ function isWs(c) {
|
|
|
9469
9512
|
//
|
|
9470
9513
|
// We implement a grammar-aligned matcher for PN_CHARS* and PLX fragments.
|
|
9471
9514
|
function isHexDigit(c) {
|
|
9472
|
-
|
|
9515
|
+
if (c === null || c === undefined) return false;
|
|
9516
|
+
const code = c.charCodeAt(0);
|
|
9517
|
+
return (code >= 48 && code <= 57) || (code >= 65 && code <= 70) || (code >= 97 && code <= 102);
|
|
9473
9518
|
}
|
|
9474
9519
|
|
|
9475
9520
|
function isPnCharsBase(c) {
|
|
9476
9521
|
// Approximation of PN_CHARS_BASE from the N3 grammar using Unicode properties.
|
|
9477
9522
|
// Covers most letters used in practice (including ñ) and common scripts.
|
|
9478
|
-
|
|
9523
|
+
if (c === null || c === undefined) return false;
|
|
9524
|
+
const code = c.charCodeAt(0);
|
|
9525
|
+
if (isAsciiAlphaCode(code)) return true;
|
|
9526
|
+
return /\p{L}|\p{Nl}/u.test(c);
|
|
9479
9527
|
}
|
|
9480
9528
|
|
|
9481
9529
|
function isPnCharsU(c) {
|
|
@@ -9485,9 +9533,11 @@ function isPnCharsU(c) {
|
|
|
9485
9533
|
|
|
9486
9534
|
function isPnChars(c) {
|
|
9487
9535
|
// PN_CHARS ::= PN_CHARS_U | '-' | [0-9] | U+00B7 | [U+0300-U+036F] | [U+203F-U+2040]
|
|
9488
|
-
if (c === null) return false;
|
|
9536
|
+
if (c === null || c === undefined) return false;
|
|
9537
|
+
const code = c.charCodeAt(0);
|
|
9538
|
+
if (isAsciiAlphaCode(code) || isAsciiDigitCode(code) || code === 95 || code === 45) return true;
|
|
9489
9539
|
if (isPnCharsU(c)) return true;
|
|
9490
|
-
if (c === '
|
|
9540
|
+
if (c === '\u00B7') return true;
|
|
9491
9541
|
const cp = c.codePointAt(0);
|
|
9492
9542
|
return (cp >= 0x0300 && cp <= 0x036f) || (cp >= 0x203f && cp <= 0x2040);
|
|
9493
9543
|
}
|
|
@@ -10592,7 +10642,10 @@ function normalizeRdfCompatibility(inputText) {
|
|
|
10592
10642
|
function lex(inputText, opts = {}) {
|
|
10593
10643
|
const rdf = !!(opts && opts.rdf);
|
|
10594
10644
|
if (rdf) inputText = normalizeRdfCompatibility(inputText);
|
|
10595
|
-
|
|
10645
|
+
// Avoid copying large ASCII/BMP inputs into an Array. Array.from() is
|
|
10646
|
+
// only needed when the text contains surrogate pairs and we want the old
|
|
10647
|
+
// code-point iteration behavior for non-BMP characters.
|
|
10648
|
+
const chars = /[\uD800-\uDFFF]/.test(inputText) ? Array.from(inputText) : inputText;
|
|
10596
10649
|
const n = chars.length;
|
|
10597
10650
|
let i = 0;
|
|
10598
10651
|
const tokens = [];
|
|
@@ -10608,19 +10661,29 @@ function lex(inputText, opts = {}) {
|
|
|
10608
10661
|
// - Accepts percent escapes (%HH) as PLX fragments.
|
|
10609
10662
|
// - Accepts PN_LOCAL_ESC backslash escapes and decodes them ("\\." -> ".").
|
|
10610
10663
|
// - Accepts '.' inside a name only when it is not terminal.
|
|
10664
|
+
function sliceChars(start, end) {
|
|
10665
|
+
return typeof chars === 'string' ? chars.slice(start, end) : chars.slice(start, end).join('');
|
|
10666
|
+
}
|
|
10667
|
+
|
|
10611
10668
|
function readIdentText(startOffsetForErrors) {
|
|
10612
|
-
const
|
|
10669
|
+
const start = i;
|
|
10670
|
+
let out = null;
|
|
10671
|
+
|
|
10672
|
+
function appendRawUntilHere() {
|
|
10673
|
+
if (out === null) out = [sliceChars(start, i)];
|
|
10674
|
+
}
|
|
10675
|
+
|
|
10613
10676
|
while (i < n) {
|
|
10614
|
-
const cc =
|
|
10615
|
-
if (cc === null || isWs(cc)) break;
|
|
10677
|
+
const cc = chars[i];
|
|
10678
|
+
if (cc === null || cc === undefined || isWs(cc)) break;
|
|
10616
10679
|
|
|
10617
10680
|
// Hard stops: delimiters cannot appear unescaped inside PNAME tokens.
|
|
10618
|
-
if ('{}()[]
|
|
10681
|
+
if (cc === '{' || cc === '}' || cc === '(' || cc === ')' || cc === '[' || cc === ']' || cc === ';' || cc === ',') break;
|
|
10619
10682
|
|
|
10620
10683
|
// Dot is allowed inside PN_LOCAL, but not at the end.
|
|
10621
10684
|
if (cc === '.') {
|
|
10622
10685
|
if (!canContinueAfterDot(peek(1))) break;
|
|
10623
|
-
out.push('.');
|
|
10686
|
+
if (out !== null) out.push('.');
|
|
10624
10687
|
i++;
|
|
10625
10688
|
continue;
|
|
10626
10689
|
}
|
|
@@ -10635,6 +10698,7 @@ function lex(inputText, opts = {}) {
|
|
|
10635
10698
|
typeof startOffsetForErrors === 'number' ? startOffsetForErrors : i,
|
|
10636
10699
|
);
|
|
10637
10700
|
}
|
|
10701
|
+
appendRawUntilHere();
|
|
10638
10702
|
out.push('%', h1, h2);
|
|
10639
10703
|
i += 3;
|
|
10640
10704
|
continue;
|
|
@@ -10644,6 +10708,7 @@ function lex(inputText, opts = {}) {
|
|
|
10644
10708
|
if (cc === '\\') {
|
|
10645
10709
|
const esc = peek(1);
|
|
10646
10710
|
if (esc !== null && PN_LOCAL_ESC_SET.has(esc)) {
|
|
10711
|
+
appendRawUntilHere();
|
|
10647
10712
|
out.push(esc); // decoded form
|
|
10648
10713
|
i += 2;
|
|
10649
10714
|
continue;
|
|
@@ -10655,14 +10720,14 @@ function lex(inputText, opts = {}) {
|
|
|
10655
10720
|
}
|
|
10656
10721
|
|
|
10657
10722
|
if (isIdentChar(cc)) {
|
|
10658
|
-
out.push(cc);
|
|
10723
|
+
if (out !== null) out.push(cc);
|
|
10659
10724
|
i++;
|
|
10660
10725
|
continue;
|
|
10661
10726
|
}
|
|
10662
10727
|
|
|
10663
10728
|
break;
|
|
10664
10729
|
}
|
|
10665
|
-
return out.join('');
|
|
10730
|
+
return out === null ? sliceChars(start, i) : out.join('');
|
|
10666
10731
|
}
|
|
10667
10732
|
|
|
10668
10733
|
while (i < n) {
|
|
@@ -10949,10 +11014,10 @@ function lex(inputText, opts = {}) {
|
|
|
10949
11014
|
// "@" [a-zA-Z]+ ("-" [a-zA-Z0-9]+)*
|
|
10950
11015
|
const tagChars = [];
|
|
10951
11016
|
let cc = peek();
|
|
10952
|
-
if (cc === null ||
|
|
11017
|
+
if (cc === null || !isAsciiAlpha(cc)) {
|
|
10953
11018
|
throw new N3SyntaxError("Invalid language tag (expected [A-Za-z] after '@')", start);
|
|
10954
11019
|
}
|
|
10955
|
-
while ((cc = peek()) !== null &&
|
|
11020
|
+
while ((cc = peek()) !== null && isAsciiAlpha(cc)) {
|
|
10956
11021
|
tagChars.push(cc);
|
|
10957
11022
|
i++;
|
|
10958
11023
|
}
|
|
@@ -10976,7 +11041,7 @@ function lex(inputText, opts = {}) {
|
|
|
10976
11041
|
// Otherwise, treat as a directive (@prefix, @base)
|
|
10977
11042
|
const wordChars = [];
|
|
10978
11043
|
let cc;
|
|
10979
|
-
while ((cc = peek()) !== null &&
|
|
11044
|
+
while ((cc = peek()) !== null && isAsciiAlpha(cc)) {
|
|
10980
11045
|
wordChars.push(cc);
|
|
10981
11046
|
i++;
|
|
10982
11047
|
}
|
|
@@ -10988,19 +11053,19 @@ function lex(inputText, opts = {}) {
|
|
|
10988
11053
|
}
|
|
10989
11054
|
|
|
10990
11055
|
// 6) Numeric literal (integer or float)
|
|
10991
|
-
if (
|
|
11056
|
+
if (isAsciiDigit(c) || (c === '-' && peek(1) !== null && isAsciiDigit(peek(1)))) {
|
|
10992
11057
|
const start = i;
|
|
10993
11058
|
const numChars = [c];
|
|
10994
11059
|
i++;
|
|
10995
11060
|
while (i < n) {
|
|
10996
11061
|
const cc = chars[i];
|
|
10997
|
-
if (
|
|
11062
|
+
if (isAsciiDigit(cc)) {
|
|
10998
11063
|
numChars.push(cc);
|
|
10999
11064
|
i++;
|
|
11000
11065
|
continue;
|
|
11001
11066
|
}
|
|
11002
11067
|
if (cc === '.') {
|
|
11003
|
-
if (i + 1 < n &&
|
|
11068
|
+
if (i + 1 < n && isAsciiDigit(chars[i + 1])) {
|
|
11004
11069
|
numChars.push('.');
|
|
11005
11070
|
i++;
|
|
11006
11071
|
continue;
|
|
@@ -11015,14 +11080,14 @@ function lex(inputText, opts = {}) {
|
|
|
11015
11080
|
if (i < n && (chars[i] === 'e' || chars[i] === 'E')) {
|
|
11016
11081
|
let j = i + 1;
|
|
11017
11082
|
if (j < n && (chars[j] === '+' || chars[j] === '-')) j++;
|
|
11018
|
-
if (j < n &&
|
|
11083
|
+
if (j < n && isAsciiDigit(chars[j])) {
|
|
11019
11084
|
numChars.push(chars[i]); // e/E
|
|
11020
11085
|
i++;
|
|
11021
11086
|
if (i < n && (chars[i] === '+' || chars[i] === '-')) {
|
|
11022
11087
|
numChars.push(chars[i]);
|
|
11023
11088
|
i++;
|
|
11024
11089
|
}
|
|
11025
|
-
while (i < n &&
|
|
11090
|
+
while (i < n && isAsciiDigit(chars[i])) {
|
|
11026
11091
|
numChars.push(chars[i]);
|
|
11027
11092
|
i++;
|
|
11028
11093
|
}
|
|
@@ -11471,7 +11536,15 @@ class Parser {
|
|
|
11471
11536
|
}
|
|
11472
11537
|
|
|
11473
11538
|
isIdentKeyword(tok, keyword) {
|
|
11474
|
-
|
|
11539
|
+
if (!tok || tok.typ !== 'Ident' || typeof tok.value !== 'string') return false;
|
|
11540
|
+
const v = tok.value;
|
|
11541
|
+
if (v.length !== keyword.length) return false;
|
|
11542
|
+
for (let i = 0; i < keyword.length; i++) {
|
|
11543
|
+
const code = v.charCodeAt(i);
|
|
11544
|
+
const lower = code >= 65 && code <= 90 ? code + 32 : code;
|
|
11545
|
+
if (lower !== keyword.charCodeAt(i)) return false;
|
|
11546
|
+
}
|
|
11547
|
+
return true;
|
|
11475
11548
|
}
|
|
11476
11549
|
|
|
11477
11550
|
canStartSparqlPrefixDirective() {
|
|
@@ -12394,21 +12467,40 @@ function literalParts(lit) {
|
|
|
12394
12467
|
// equality fast-paths than repeated string key construction.
|
|
12395
12468
|
|
|
12396
12469
|
let __nextTid = 1;
|
|
12397
|
-
const __tidIntern = new Map(); //
|
|
12470
|
+
const __tidIntern = new Map(); // legacy generic key -> number
|
|
12471
|
+
const __iriTidIntern = new Map(); // IRI value -> number
|
|
12472
|
+
const __blankTidIntern = new Map(); // blank label -> number
|
|
12473
|
+
const __literalTidIntern = new Map(); // normalized literal lexical form -> number
|
|
12398
12474
|
|
|
12399
12475
|
// Avoid storing extremely large literal keys in the global term-id intern map.
|
|
12400
12476
|
// For huge literals we still assign a unique __tid, but we do not intern the key.
|
|
12401
12477
|
const MAX_LITERAL_TID_LEN = 1024;
|
|
12402
12478
|
|
|
12403
|
-
function
|
|
12404
|
-
let id =
|
|
12479
|
+
function __getTidFromMap(map, key) {
|
|
12480
|
+
let id = map.get(key);
|
|
12405
12481
|
if (!id) {
|
|
12406
12482
|
id = __nextTid++;
|
|
12407
|
-
|
|
12483
|
+
map.set(key, id);
|
|
12408
12484
|
}
|
|
12409
12485
|
return id;
|
|
12410
12486
|
}
|
|
12411
12487
|
|
|
12488
|
+
function __getTid(key) {
|
|
12489
|
+
return __getTidFromMap(__tidIntern, key);
|
|
12490
|
+
}
|
|
12491
|
+
|
|
12492
|
+
function __getIriTid(value) {
|
|
12493
|
+
return __getTidFromMap(__iriTidIntern, value);
|
|
12494
|
+
}
|
|
12495
|
+
|
|
12496
|
+
function __getBlankTid(label) {
|
|
12497
|
+
return __getTidFromMap(__blankTidIntern, label);
|
|
12498
|
+
}
|
|
12499
|
+
|
|
12500
|
+
function __getLiteralTid(norm) {
|
|
12501
|
+
return __getTidFromMap(__literalTidIntern, norm);
|
|
12502
|
+
}
|
|
12503
|
+
|
|
12412
12504
|
function __isQuotedLexical(lit) {
|
|
12413
12505
|
if (typeof lit !== 'string') return false;
|
|
12414
12506
|
if (lit.length >= 6) {
|
|
@@ -12454,6 +12546,14 @@ function __isPlainStringLiteralValue(lit) {
|
|
|
12454
12546
|
function normalizeLiteralForTid(lit) {
|
|
12455
12547
|
// Canonicalize so that plain string and explicit xsd:string share the same id.
|
|
12456
12548
|
if (typeof lit !== 'string') return lit;
|
|
12549
|
+
|
|
12550
|
+
// Fast path for the overwhelmingly common lexer output for plain string
|
|
12551
|
+
// literals: a canonical JSON-style quoted lexical form with no suffix.
|
|
12552
|
+
// This avoids literalParts()/language-tag parsing for large fact tables.
|
|
12553
|
+
if (lit.length >= 2 && lit.charCodeAt(0) === 34 && lit.charCodeAt(lit.length - 1) === 34 && lit.indexOf('^^') < 0) {
|
|
12554
|
+
return `${lit}^^<${XSD_NS}string>`;
|
|
12555
|
+
}
|
|
12556
|
+
|
|
12457
12557
|
const [lex, dt] = literalParts(lit);
|
|
12458
12558
|
if (dt === XSD_NS + 'string') return `${lex}^^<${XSD_NS}string>`;
|
|
12459
12559
|
if (dt === null && __isPlainStringLiteralValue(lit)) return `${lex}^^<${XSD_NS}string>`;
|
|
@@ -12471,7 +12571,7 @@ class Iri extends Term {
|
|
|
12471
12571
|
super();
|
|
12472
12572
|
this.value = value;
|
|
12473
12573
|
Object.defineProperty(this, '__tid', {
|
|
12474
|
-
value:
|
|
12574
|
+
value: __getIriTid(value),
|
|
12475
12575
|
enumerable: false,
|
|
12476
12576
|
});
|
|
12477
12577
|
}
|
|
@@ -12483,7 +12583,7 @@ class Literal extends Term {
|
|
|
12483
12583
|
this.value = value; // raw lexical form, e.g. "foo", 12, true, or "\"1944-08-21\"^^..."
|
|
12484
12584
|
const norm = normalizeLiteralForTid(value);
|
|
12485
12585
|
const useIntern = typeof norm === 'string' && norm.length <= MAX_LITERAL_TID_LEN;
|
|
12486
|
-
const tid = useIntern ?
|
|
12586
|
+
const tid = useIntern ? __getLiteralTid(norm) : __nextTid++;
|
|
12487
12587
|
Object.defineProperty(this, '__tid', {
|
|
12488
12588
|
value: tid,
|
|
12489
12589
|
enumerable: false,
|
|
@@ -12503,7 +12603,7 @@ class Blank extends Term {
|
|
|
12503
12603
|
super();
|
|
12504
12604
|
this.label = label; // _:b1, etc.
|
|
12505
12605
|
Object.defineProperty(this, '__tid', {
|
|
12506
|
-
value:
|
|
12606
|
+
value: __getBlankTid(label),
|
|
12507
12607
|
enumerable: false,
|
|
12508
12608
|
});
|
|
12509
12609
|
}
|
package/index.d.ts
CHANGED
|
@@ -209,6 +209,7 @@ declare module 'eyeling' {
|
|
|
209
209
|
opts?: Omit<ReasonStreamOptions, 'rdfjs' | 'onDerived'>,
|
|
210
210
|
): AsyncIterable<RdfJsQuad>;
|
|
211
211
|
|
|
212
|
+
export const INFERENCE_FUSE_EXIT_CODE: 65;
|
|
212
213
|
export const rdfjs: RdfJsDataFactory;
|
|
213
214
|
export function registerBuiltin(iri: string, handler: BuiltinHandler): BuiltinHandler;
|
|
214
215
|
export function unregisterBuiltin(iri: string): boolean;
|
|
@@ -237,6 +238,7 @@ declare module 'eyeling/browser' {
|
|
|
237
238
|
opts?: Omit<ReasonStreamOptions, 'rdfjs' | 'onDerived'>,
|
|
238
239
|
): AsyncIterable<RdfJsQuad>;
|
|
239
240
|
|
|
241
|
+
export const INFERENCE_FUSE_EXIT_CODE: 65;
|
|
240
242
|
export const rdfjs: RdfJsDataFactory;
|
|
241
243
|
export function registerBuiltin(iri: string, handler: BuiltinHandler): BuiltinHandler;
|
|
242
244
|
export function unregisterBuiltin(iri: string): boolean;
|
|
@@ -247,6 +249,7 @@ declare module 'eyeling/browser' {
|
|
|
247
249
|
readonly version: string;
|
|
248
250
|
reasonStream: typeof reasonStream;
|
|
249
251
|
reasonRdfJs: typeof reasonRdfJs;
|
|
252
|
+
readonly INFERENCE_FUSE_EXIT_CODE: typeof INFERENCE_FUSE_EXIT_CODE;
|
|
250
253
|
rdfjs: typeof rdfjs;
|
|
251
254
|
registerBuiltin: typeof registerBuiltin;
|
|
252
255
|
unregisterBuiltin: typeof unregisterBuiltin;
|
package/lib/cli.js
CHANGED
package/lib/engine.js
CHANGED
|
@@ -30,6 +30,10 @@ const {
|
|
|
30
30
|
copyQuotedGraphMetadata,
|
|
31
31
|
} = require('./prelude');
|
|
32
32
|
|
|
33
|
+
// Inference fuses use sysexits.h EX_DATAERR (65): input/rules made a
|
|
34
|
+
// forbidden condition provable, rather than a generic usage/runtime error.
|
|
35
|
+
const INFERENCE_FUSE_EXIT_CODE = 65;
|
|
36
|
+
|
|
33
37
|
// In N3/Turtle, rdf:nil is the canonical IRI for the empty RDF list.
|
|
34
38
|
// Eyeling represents list literals with ListTerm; ensure rdf:nil unifies with ().
|
|
35
39
|
const RDF_NIL_IRI = RDF_NS + 'nil';
|
|
@@ -1045,11 +1049,13 @@ function termFastKey(t) {
|
|
|
1045
1049
|
if (t instanceof Iri || t instanceof Blank) return t.__tid;
|
|
1046
1050
|
|
|
1047
1051
|
if (t instanceof Literal) {
|
|
1048
|
-
//
|
|
1049
|
-
//
|
|
1050
|
-
//
|
|
1051
|
-
//
|
|
1052
|
-
//
|
|
1052
|
+
// Literal construction already computed a value-stable __tid for ordinary
|
|
1053
|
+
// short literals. Avoid re-running literalParts()/datatype normalization
|
|
1054
|
+
// while building fact indexes; on data-heavy inputs this is a hot path.
|
|
1055
|
+
// Only the rare over-sized literal needs the value-based fallback because
|
|
1056
|
+
// prelude intentionally gives such literals per-object ids to avoid
|
|
1057
|
+
// retaining huge strings in the global interner.
|
|
1058
|
+
if (typeof t.value !== 'string' || t.value.length + 64 <= MAX_LITERAL_TID_LEN) return t.__tid;
|
|
1053
1059
|
const norm = normalizeLiteralForTid(t.value);
|
|
1054
1060
|
if (typeof norm === 'string' && norm.length > MAX_LITERAL_TID_LEN) return 'L:' + norm;
|
|
1055
1061
|
return t.__tid;
|
|
@@ -1136,17 +1142,28 @@ function ensureFactIndexes(facts) {
|
|
|
1136
1142
|
enumerable: false,
|
|
1137
1143
|
writable: true,
|
|
1138
1144
|
});
|
|
1145
|
+
Object.defineProperty(facts, '__keySetComplete', {
|
|
1146
|
+
value: false,
|
|
1147
|
+
enumerable: false,
|
|
1148
|
+
writable: true,
|
|
1149
|
+
});
|
|
1139
1150
|
|
|
1140
|
-
|
|
1151
|
+
// Build lookup indexes eagerly, but do not populate the duplicate-detection
|
|
1152
|
+
// string Set for every input fact. The predicate/subject/object indexes are
|
|
1153
|
+
// enough to verify duplicates when needed; avoiding 100k+ joined string keys
|
|
1154
|
+
// saves substantial time and GC on data-heavy query workloads.
|
|
1155
|
+
for (let i = 0; i < facts.length; i++) indexFact(facts, facts[i], i, false);
|
|
1141
1156
|
}
|
|
1142
1157
|
|
|
1143
|
-
function indexFact(facts, tr, idx) {
|
|
1158
|
+
function indexFact(facts, tr, idx, addKeySet = true) {
|
|
1144
1159
|
const sk = termFastKey(tr.s);
|
|
1145
1160
|
const ok = termFastKey(tr.o);
|
|
1161
|
+
let pkForKey = null;
|
|
1146
1162
|
|
|
1147
1163
|
if (tr.p instanceof Iri) {
|
|
1148
1164
|
// Use predicate term id as the primary key to avoid hashing long IRI strings.
|
|
1149
1165
|
const pk = tr.p.__tid;
|
|
1166
|
+
pkForKey = pk;
|
|
1150
1167
|
|
|
1151
1168
|
let pb = facts.__byPred.get(pk);
|
|
1152
1169
|
if (!pb) {
|
|
@@ -1204,8 +1221,10 @@ function indexFact(facts, tr, idx) {
|
|
|
1204
1221
|
}
|
|
1205
1222
|
}
|
|
1206
1223
|
|
|
1207
|
-
|
|
1208
|
-
|
|
1224
|
+
if (addKeySet && sk !== null && ok !== null) {
|
|
1225
|
+
if (pkForKey === null) pkForKey = termFastKey(tr.p);
|
|
1226
|
+
if (pkForKey !== null) facts.__keySet.add(sk + '\t' + pkForKey + '\t' + ok);
|
|
1227
|
+
}
|
|
1209
1228
|
}
|
|
1210
1229
|
|
|
1211
1230
|
function candidateFacts(facts, goal) {
|
|
@@ -1267,7 +1286,10 @@ function hasFactIndexed(facts, tr) {
|
|
|
1267
1286
|
ensureFactIndexes(facts);
|
|
1268
1287
|
|
|
1269
1288
|
const key = tripleFastKey(tr);
|
|
1270
|
-
if (key !== null)
|
|
1289
|
+
if (key !== null) {
|
|
1290
|
+
if (facts.__keySet.has(key)) return true;
|
|
1291
|
+
if (facts.__keySetComplete) return false;
|
|
1292
|
+
}
|
|
1271
1293
|
|
|
1272
1294
|
if (tr.p instanceof Iri) {
|
|
1273
1295
|
const pk = tr.p.__tid;
|
|
@@ -1297,7 +1319,7 @@ function pushFactIndexed(facts, tr) {
|
|
|
1297
1319
|
ensureFactIndexes(facts);
|
|
1298
1320
|
const idx = facts.length;
|
|
1299
1321
|
facts.push(tr);
|
|
1300
|
-
indexFact(facts, tr, idx);
|
|
1322
|
+
indexFact(facts, tr, idx, true);
|
|
1301
1323
|
}
|
|
1302
1324
|
|
|
1303
1325
|
function makeDerivedRecord(fact, rule, premises, subst, captureExplanations) {
|
|
@@ -2834,11 +2856,9 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
2834
2856
|
const varGen = [0];
|
|
2835
2857
|
const skCounter = [0];
|
|
2836
2858
|
|
|
2837
|
-
//
|
|
2838
|
-
//
|
|
2839
|
-
|
|
2840
|
-
__ensureRuleKeySet(forwardRules);
|
|
2841
|
-
__ensureRuleKeySet(backRules);
|
|
2859
|
+
// Rule-key sets are only needed if a program actually derives rule-producing
|
|
2860
|
+
// triples. Building them eagerly is expensive on large static rule sets, so
|
|
2861
|
+
// dynamic-promotion sites create them lazily before duplicate checks.
|
|
2842
2862
|
|
|
2843
2863
|
// Cache head blank-node skolemization per (rule firing, head blank label).
|
|
2844
2864
|
// This prevents repeatedly generating fresh _:sk_N blanks for the *same*
|
|
@@ -2958,7 +2978,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
2958
2978
|
// Allow dynamic fuses: ... => ?X. where ?X becomes false
|
|
2959
2979
|
if (dynTerm instanceof Literal && dynTerm.value === 'false') {
|
|
2960
2980
|
__printTriggeredFuse(r, opts && opts.prefixes, s, 'Dynamic head resolved to false.');
|
|
2961
|
-
__exitReasoning(
|
|
2981
|
+
__exitReasoning(INFERENCE_FUSE_EXIT_CODE, 'Inference fuse triggered.');
|
|
2962
2982
|
}
|
|
2963
2983
|
|
|
2964
2984
|
const dynTriples = __graphTriplesOrTrue(dynTerm);
|
|
@@ -3017,8 +3037,9 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
3017
3037
|
newRule.conclusion,
|
|
3018
3038
|
newRule.__dynamicConclusionTerm || null,
|
|
3019
3039
|
);
|
|
3020
|
-
|
|
3021
|
-
|
|
3040
|
+
const forwardRuleKeySet = __ensureRuleKeySet(forwardRules);
|
|
3041
|
+
if (!forwardRuleKeySet.has(key)) {
|
|
3042
|
+
forwardRuleKeySet.add(key);
|
|
3022
3043
|
forwardRules.push(newRule);
|
|
3023
3044
|
rulesChanged = true;
|
|
3024
3045
|
}
|
|
@@ -3032,8 +3053,9 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
3032
3053
|
newRule.conclusion,
|
|
3033
3054
|
newRule.__dynamicConclusionTerm || null,
|
|
3034
3055
|
);
|
|
3035
|
-
|
|
3036
|
-
|
|
3056
|
+
const backRuleKeySet = __ensureRuleKeySet(backRules);
|
|
3057
|
+
if (!backRuleKeySet.has(key)) {
|
|
3058
|
+
backRuleKeySet.add(key);
|
|
3037
3059
|
backRules.push(newRule);
|
|
3038
3060
|
indexBackRule(backRules, newRule);
|
|
3039
3061
|
rulesChanged = true;
|
|
@@ -3119,7 +3141,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
3119
3141
|
// Inference fuse
|
|
3120
3142
|
if (r.isFuse && sols.length) {
|
|
3121
3143
|
__printTriggeredFuse(r, opts && opts.prefixes, sols[0]);
|
|
3122
|
-
__exitReasoning(
|
|
3144
|
+
__exitReasoning(INFERENCE_FUSE_EXIT_CODE, 'Inference fuse triggered.');
|
|
3123
3145
|
}
|
|
3124
3146
|
|
|
3125
3147
|
for (const s of sols) {
|
|
@@ -3656,4 +3678,5 @@ module.exports = {
|
|
|
3656
3678
|
registerBuiltinModule,
|
|
3657
3679
|
loadBuiltinModule,
|
|
3658
3680
|
listBuiltinIris,
|
|
3681
|
+
INFERENCE_FUSE_EXIT_CODE,
|
|
3659
3682
|
};
|