eyeling 1.25.3 → 1.25.4
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 +12 -8
- package/dist/browser/eyeling.browser.js +272 -92
- package/eyeling.js +272 -92
- package/lib/engine.js +272 -83
- package/package.json +1 -1
- package/test/api.test.js +40 -0
package/HANDBOOK.md
CHANGED
|
@@ -559,21 +559,25 @@ Facts live in an array `facts: Triple[]`.
|
|
|
559
559
|
Eyeling attaches hidden (non-enumerable) index fields:
|
|
560
560
|
|
|
561
561
|
- `facts.__byPred: Map<predicateId, number[]>` where each entry is an index into `facts` (and `predicateId` is `predicate.__tid`)
|
|
562
|
-
- `facts.__byPS: Map<predicateId, Map<
|
|
563
|
-
- `facts.__byPO: Map<predicateId, Map<
|
|
562
|
+
- `facts.__byPS: Map<predicateId, Map<lookupKey, number[]>>` where each entry is an index into `facts`
|
|
563
|
+
- `facts.__byPO: Map<predicateId, Map<lookupKey, number[]>>` where each entry is an index into `facts`
|
|
564
|
+
- `facts.__byPNonFastS` / `facts.__byPNonFastO` for the small fallback set of IRI-predicate facts whose subject/object cannot be fast-keyed (for example variables or quoted formulas)
|
|
565
|
+
- `facts.__varPred*` for top-level facts whose predicate is a variable; these are the only non-IRI-predicate facts that can match a ground IRI predicate goal
|
|
564
566
|
- `facts.__keySet: Set<string>` for a fast-path `"sid pid oid"` key (all three are `__tid` values)
|
|
565
567
|
|
|
566
|
-
`termFastKey(term)` returns a `termId` (`term.__tid`) for **Iri**, **Literal**,
|
|
568
|
+
`termFastKey(term)` returns a `termId` (`term.__tid`) for **Iri**, **Literal**, **Blank**, and strict-ground list terms, and `null` for quoted graphs, open lists, and variables. Proving-time lookup uses `termLookupKey(term)`, which is aligned with `unifyTerm`: it keeps exact ids for IRIs/blanks/strings, but canonicalizes value-equivalent booleans and numerics such as `true` / `"1"^^xsd:boolean` and `1.0` / `1.00`.
|
|
567
569
|
|
|
568
|
-
The “fast key” only exists when `termFastKey` succeeds for all three terms
|
|
570
|
+
The duplicate-check “fast key” only exists when `termFastKey` succeeds for all three terms; the proof indexes use the broader `termLookupKey`.
|
|
569
571
|
|
|
570
572
|
### 7.2 Candidate selection: pick the smallest bucket
|
|
571
573
|
|
|
572
574
|
When proving a goal with IRI predicate, Eyeling computes candidate facts by:
|
|
573
575
|
|
|
574
|
-
1. restricting to predicate bucket
|
|
575
|
-
2.
|
|
576
|
-
3. choosing the smaller of
|
|
576
|
+
1. restricting to the IRI predicate bucket
|
|
577
|
+
2. if subject or object has a lookup key, using the matching `(p,s)` or `(p,o)` bucket plus only the non-fast fallback facts for that same position
|
|
578
|
+
3. choosing the smaller of the subject-constrained and object-constrained candidate sets when both exist
|
|
579
|
+
|
|
580
|
+
Variable-predicate facts are kept in a separate tiny fallback index. Blank/list/formula predicates are not scanned for an IRI predicate goal because they cannot unify with an IRI predicate.
|
|
577
581
|
|
|
578
582
|
This is a cheap selectivity heuristic. In type-heavy RDF, `(p,o)` is often extremely selective (e.g., `rdf:type` + a class IRI), so the PO index can be a major speed win.
|
|
579
583
|
|
|
@@ -583,7 +587,7 @@ The same selectivity idea is also reused by the single-premise forward-rule agen
|
|
|
583
587
|
|
|
584
588
|
When adding derived facts, Eyeling uses a fast-path duplicate check when possible:
|
|
585
589
|
|
|
586
|
-
- If all three terms have a fast key
|
|
590
|
+
- If all three terms have a duplicate-check fast key, it checks membership in `facts.__keySet` using the `"sid pid oid"` key.
|
|
587
591
|
- Otherwise (lists, quoted graphs, variables), it falls back to structural triple equality.
|
|
588
592
|
|
|
589
593
|
This still treats blanks correctly: blanks are _not_ interchangeable; the blank **label** (and thus its `__tid`) is part of the key.
|
|
@@ -6523,6 +6523,12 @@ function alphaEqGraphTriples(xs, ys, opts) {
|
|
|
6523
6523
|
// - __byPred: Map<predicateId, number[]> (indices into facts array)
|
|
6524
6524
|
// - __byPS: Map<predicateId, Map<subjectId, number[]>>
|
|
6525
6525
|
// - __byPO: Map<predicateId, Map<objectId, number[]>>
|
|
6526
|
+
// - __byPNonFastS / __byPNonFastO: Map<predicateId, number[]>
|
|
6527
|
+
// IRI-predicate facts whose subject/object cannot be indexed by fast key.
|
|
6528
|
+
// These are the fallback clauses for a constrained subject/object, just
|
|
6529
|
+
// like a Prolog clause index keeps variable-headed clauses as fallback.
|
|
6530
|
+
// - __varPred* indexes: facts whose predicate is a Var. Only these non-IRI
|
|
6531
|
+
// predicate facts can unify with a ground IRI predicate goal.
|
|
6526
6532
|
// - __keySet: Set<"S\tP\tO"> for Iri/Literal/Blank-only triples (fast dup check)
|
|
6527
6533
|
//
|
|
6528
6534
|
// Backward rules:
|
|
@@ -6538,6 +6544,8 @@ const __compoundKeyToTid = new Map();
|
|
|
6538
6544
|
// Use a negative id space so we never collide with __tid (which is positive).
|
|
6539
6545
|
let __nextCompoundTid = -1;
|
|
6540
6546
|
|
|
6547
|
+
const EMPTY_FACT_INDEX_BUCKET = Object.freeze([]);
|
|
6548
|
+
|
|
6541
6549
|
function __internCompoundTid(key) {
|
|
6542
6550
|
const hit = __compoundKeyToTid.get(key);
|
|
6543
6551
|
if (hit !== undefined) return hit;
|
|
@@ -6589,6 +6597,71 @@ function termFastKey(t) {
|
|
|
6589
6597
|
return null;
|
|
6590
6598
|
}
|
|
6591
6599
|
|
|
6600
|
+
function encodeLookupKeyPart(k) {
|
|
6601
|
+
if (typeof k === 'number') return 'T' + k;
|
|
6602
|
+
const s = String(k);
|
|
6603
|
+
return 'K' + s.length + ':' + s;
|
|
6604
|
+
}
|
|
6605
|
+
|
|
6606
|
+
function literalLookupKey(t) {
|
|
6607
|
+
const boolInfo = parseBooleanLiteralInfo(t);
|
|
6608
|
+
if (boolInfo) return '\u0000B' + (boolInfo.value ? '1' : '0');
|
|
6609
|
+
|
|
6610
|
+
const numInfo = parseNumericLiteralInfo(t);
|
|
6611
|
+
if (numInfo) {
|
|
6612
|
+
if (numInfo.kind === 'bigint') return '\u0000N' + numInfo.dt + '\u0000' + numInfo.value.toString();
|
|
6613
|
+
|
|
6614
|
+
const n = numInfo.value;
|
|
6615
|
+
// Normal unification intentionally does not make NaN value-equal to NaN;
|
|
6616
|
+
// only identical lexical literals match through the ordinary __tid path.
|
|
6617
|
+
if (!Number.isNaN(n)) return '\u0000N' + numInfo.dt + '\u0000' + String(n);
|
|
6618
|
+
}
|
|
6619
|
+
|
|
6620
|
+
// Covers exact literals plus plain string / xsd:string canonicalization, which
|
|
6621
|
+
// Literal construction already normalizes into a shared __tid.
|
|
6622
|
+
return termFastKey(t);
|
|
6623
|
+
}
|
|
6624
|
+
|
|
6625
|
+
function termLookupKey(t) {
|
|
6626
|
+
// Lookup keys summarize the equality accepted by ordinary unifyTerm(), not
|
|
6627
|
+
// merely object identity. This keeps literal-index pruning complete for
|
|
6628
|
+
// value-equivalent booleans/numerics such as true/"1"^^xsd:boolean and
|
|
6629
|
+
// 1.0/1.00, while preserving exact fast ids for IRIs, blanks, and strings.
|
|
6630
|
+
if (t instanceof Iri) {
|
|
6631
|
+
if (t.value === RDF_NIL_IRI) return '\u0000L0';
|
|
6632
|
+
return t.__tid;
|
|
6633
|
+
}
|
|
6634
|
+
if (t instanceof Blank) return t.__tid;
|
|
6635
|
+
if (t instanceof Literal) return literalLookupKey(t);
|
|
6636
|
+
|
|
6637
|
+
if (t instanceof ListTerm) {
|
|
6638
|
+
const cached = t.__lookupKey;
|
|
6639
|
+
if (cached !== undefined) return cached === false ? null : cached;
|
|
6640
|
+
|
|
6641
|
+
const xs = t.elems;
|
|
6642
|
+
if (xs.length === 0) {
|
|
6643
|
+
Object.defineProperty(t, '__lookupKey', { value: '\u0000L0', enumerable: false });
|
|
6644
|
+
return '\u0000L0';
|
|
6645
|
+
}
|
|
6646
|
+
|
|
6647
|
+
const parts = new Array(xs.length);
|
|
6648
|
+
for (let i = 0; i < xs.length; i++) {
|
|
6649
|
+
const k = termLookupKey(xs[i]);
|
|
6650
|
+
if (k === null) {
|
|
6651
|
+
Object.defineProperty(t, '__lookupKey', { value: false, enumerable: false });
|
|
6652
|
+
return null;
|
|
6653
|
+
}
|
|
6654
|
+
parts[i] = encodeLookupKeyPart(k);
|
|
6655
|
+
}
|
|
6656
|
+
|
|
6657
|
+
const key = '\u0000L' + xs.length + '\u0001' + parts.join('\u0001');
|
|
6658
|
+
Object.defineProperty(t, '__lookupKey', { value: key, enumerable: false });
|
|
6659
|
+
return key;
|
|
6660
|
+
}
|
|
6661
|
+
|
|
6662
|
+
return null;
|
|
6663
|
+
}
|
|
6664
|
+
|
|
6592
6665
|
function tripleFastKey(tr) {
|
|
6593
6666
|
const ks = termFastKey(tr.s);
|
|
6594
6667
|
const kp = termFastKey(tr.p);
|
|
@@ -6602,9 +6675,13 @@ function ensureFactIndexes(facts) {
|
|
|
6602
6675
|
facts.__byPred &&
|
|
6603
6676
|
facts.__byPS &&
|
|
6604
6677
|
facts.__byPO &&
|
|
6605
|
-
facts.
|
|
6606
|
-
facts.
|
|
6607
|
-
facts.
|
|
6678
|
+
facts.__byPNonFastS &&
|
|
6679
|
+
facts.__byPNonFastO &&
|
|
6680
|
+
facts.__varPred &&
|
|
6681
|
+
facts.__varPredPS &&
|
|
6682
|
+
facts.__varPredPO &&
|
|
6683
|
+
facts.__varPredNonFastS &&
|
|
6684
|
+
facts.__varPredNonFastO &&
|
|
6608
6685
|
facts.__keySet
|
|
6609
6686
|
)
|
|
6610
6687
|
return;
|
|
@@ -6624,21 +6701,41 @@ function ensureFactIndexes(facts) {
|
|
|
6624
6701
|
enumerable: false,
|
|
6625
6702
|
writable: true,
|
|
6626
6703
|
});
|
|
6627
|
-
Object.defineProperty(facts, '
|
|
6704
|
+
Object.defineProperty(facts, '__byPNonFastS', {
|
|
6705
|
+
value: new Map(),
|
|
6706
|
+
enumerable: false,
|
|
6707
|
+
writable: true,
|
|
6708
|
+
});
|
|
6709
|
+
Object.defineProperty(facts, '__byPNonFastO', {
|
|
6710
|
+
value: new Map(),
|
|
6711
|
+
enumerable: false,
|
|
6712
|
+
writable: true,
|
|
6713
|
+
});
|
|
6714
|
+
Object.defineProperty(facts, '__varPred', {
|
|
6628
6715
|
value: [],
|
|
6629
6716
|
enumerable: false,
|
|
6630
6717
|
writable: true,
|
|
6631
6718
|
});
|
|
6632
|
-
Object.defineProperty(facts, '
|
|
6719
|
+
Object.defineProperty(facts, '__varPredPS', {
|
|
6633
6720
|
value: new Map(),
|
|
6634
6721
|
enumerable: false,
|
|
6635
6722
|
writable: true,
|
|
6636
6723
|
});
|
|
6637
|
-
Object.defineProperty(facts, '
|
|
6724
|
+
Object.defineProperty(facts, '__varPredPO', {
|
|
6638
6725
|
value: new Map(),
|
|
6639
6726
|
enumerable: false,
|
|
6640
6727
|
writable: true,
|
|
6641
6728
|
});
|
|
6729
|
+
Object.defineProperty(facts, '__varPredNonFastS', {
|
|
6730
|
+
value: [],
|
|
6731
|
+
enumerable: false,
|
|
6732
|
+
writable: true,
|
|
6733
|
+
});
|
|
6734
|
+
Object.defineProperty(facts, '__varPredNonFastO', {
|
|
6735
|
+
value: [],
|
|
6736
|
+
enumerable: false,
|
|
6737
|
+
writable: true,
|
|
6738
|
+
});
|
|
6642
6739
|
Object.defineProperty(facts, '__keySet', {
|
|
6643
6740
|
value: new Set(),
|
|
6644
6741
|
enumerable: false,
|
|
@@ -6679,16 +6776,53 @@ function cloneFactIndexesForSnapshot(src, dest) {
|
|
|
6679
6776
|
Object.defineProperty(dest, '__byPred', { value: cloneArrayMap(src.__byPred), enumerable: false, writable: true });
|
|
6680
6777
|
Object.defineProperty(dest, '__byPS', { value: cloneNestedArrayMap(src.__byPS), enumerable: false, writable: true });
|
|
6681
6778
|
Object.defineProperty(dest, '__byPO', { value: cloneNestedArrayMap(src.__byPO), enumerable: false, writable: true });
|
|
6682
|
-
Object.defineProperty(dest, '
|
|
6683
|
-
|
|
6684
|
-
|
|
6779
|
+
Object.defineProperty(dest, '__byPNonFastS', {
|
|
6780
|
+
value: cloneArrayMap(src.__byPNonFastS),
|
|
6781
|
+
enumerable: false,
|
|
6782
|
+
writable: true,
|
|
6783
|
+
});
|
|
6784
|
+
Object.defineProperty(dest, '__byPNonFastO', {
|
|
6785
|
+
value: cloneArrayMap(src.__byPNonFastO),
|
|
6786
|
+
enumerable: false,
|
|
6787
|
+
writable: true,
|
|
6788
|
+
});
|
|
6789
|
+
Object.defineProperty(dest, '__varPred', { value: src.__varPred.slice(), enumerable: false, writable: true });
|
|
6790
|
+
Object.defineProperty(dest, '__varPredPS', {
|
|
6791
|
+
value: cloneArrayMap(src.__varPredPS),
|
|
6792
|
+
enumerable: false,
|
|
6793
|
+
writable: true,
|
|
6794
|
+
});
|
|
6795
|
+
Object.defineProperty(dest, '__varPredPO', {
|
|
6796
|
+
value: cloneArrayMap(src.__varPredPO),
|
|
6797
|
+
enumerable: false,
|
|
6798
|
+
writable: true,
|
|
6799
|
+
});
|
|
6800
|
+
Object.defineProperty(dest, '__varPredNonFastS', {
|
|
6801
|
+
value: src.__varPredNonFastS.slice(),
|
|
6802
|
+
enumerable: false,
|
|
6803
|
+
writable: true,
|
|
6804
|
+
});
|
|
6805
|
+
Object.defineProperty(dest, '__varPredNonFastO', {
|
|
6806
|
+
value: src.__varPredNonFastO.slice(),
|
|
6807
|
+
enumerable: false,
|
|
6808
|
+
writable: true,
|
|
6809
|
+
});
|
|
6685
6810
|
Object.defineProperty(dest, '__keySet', { value: new Set(src.__keySet), enumerable: false, writable: true });
|
|
6686
6811
|
Object.defineProperty(dest, '__keySetComplete', { value: !!src.__keySetComplete, enumerable: false, writable: true });
|
|
6687
6812
|
}
|
|
6688
6813
|
|
|
6814
|
+
function addToIndexArrayMap(map, key, value) {
|
|
6815
|
+
let bucket = map.get(key);
|
|
6816
|
+
if (!bucket) {
|
|
6817
|
+
bucket = [];
|
|
6818
|
+
map.set(key, bucket);
|
|
6819
|
+
}
|
|
6820
|
+
bucket.push(value);
|
|
6821
|
+
}
|
|
6822
|
+
|
|
6689
6823
|
function indexFact(facts, tr, idx, addKeySet = true) {
|
|
6690
|
-
const sk =
|
|
6691
|
-
const ok =
|
|
6824
|
+
const sk = termLookupKey(tr.s);
|
|
6825
|
+
const ok = termLookupKey(tr.o);
|
|
6692
6826
|
let pkForKey = null;
|
|
6693
6827
|
|
|
6694
6828
|
if (tr.p instanceof Iri) {
|
|
@@ -6709,12 +6843,9 @@ function indexFact(facts, tr, idx, addKeySet = true) {
|
|
|
6709
6843
|
ps = new Map();
|
|
6710
6844
|
facts.__byPS.set(pk, ps);
|
|
6711
6845
|
}
|
|
6712
|
-
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
ps.set(sk, psb);
|
|
6716
|
-
}
|
|
6717
|
-
psb.push(idx);
|
|
6846
|
+
addToIndexArrayMap(ps, sk, idx);
|
|
6847
|
+
} else {
|
|
6848
|
+
addToIndexArrayMap(facts.__byPNonFastS, pk, idx);
|
|
6718
6849
|
}
|
|
6719
6850
|
|
|
6720
6851
|
if (ok !== null) {
|
|
@@ -6723,32 +6854,23 @@ function indexFact(facts, tr, idx, addKeySet = true) {
|
|
|
6723
6854
|
po = new Map();
|
|
6724
6855
|
facts.__byPO.set(pk, po);
|
|
6725
6856
|
}
|
|
6726
|
-
|
|
6727
|
-
|
|
6728
|
-
|
|
6729
|
-
po.set(ok, pob);
|
|
6730
|
-
}
|
|
6731
|
-
pob.push(idx);
|
|
6857
|
+
addToIndexArrayMap(po, ok, idx);
|
|
6858
|
+
} else {
|
|
6859
|
+
addToIndexArrayMap(facts.__byPNonFastO, pk, idx);
|
|
6732
6860
|
}
|
|
6733
|
-
} else {
|
|
6734
|
-
facts.
|
|
6861
|
+
} else if (tr.p instanceof Var) {
|
|
6862
|
+
facts.__varPred.push(idx);
|
|
6735
6863
|
|
|
6736
6864
|
if (sk !== null) {
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
|
|
6740
|
-
facts.__wildPS.set(sk, psb);
|
|
6741
|
-
}
|
|
6742
|
-
psb.push(idx);
|
|
6865
|
+
addToIndexArrayMap(facts.__varPredPS, sk, idx);
|
|
6866
|
+
} else {
|
|
6867
|
+
facts.__varPredNonFastS.push(idx);
|
|
6743
6868
|
}
|
|
6744
6869
|
|
|
6745
6870
|
if (ok !== null) {
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
facts.__wildPO.set(ok, pob);
|
|
6750
|
-
}
|
|
6751
|
-
pob.push(idx);
|
|
6871
|
+
addToIndexArrayMap(facts.__varPredPO, ok, idx);
|
|
6872
|
+
} else {
|
|
6873
|
+
facts.__varPredNonFastO.push(idx);
|
|
6752
6874
|
}
|
|
6753
6875
|
}
|
|
6754
6876
|
|
|
@@ -6758,55 +6880,86 @@ function indexFact(facts, tr, idx, addKeySet = true) {
|
|
|
6758
6880
|
}
|
|
6759
6881
|
}
|
|
6760
6882
|
|
|
6883
|
+
function mergeIndexBuckets(primary, fallback) {
|
|
6884
|
+
const a = primary && primary.length ? primary : null;
|
|
6885
|
+
const b = fallback && fallback.length ? fallback : null;
|
|
6886
|
+
if (!a && !b) return EMPTY_FACT_INDEX_BUCKET;
|
|
6887
|
+
if (!a) return b;
|
|
6888
|
+
if (!b) return a;
|
|
6889
|
+
const out = new Array(a.length + b.length);
|
|
6890
|
+
for (let i = 0; i < a.length; i++) out[i] = a[i];
|
|
6891
|
+
for (let i = 0; i < b.length; i++) out[a.length + i] = b[i];
|
|
6892
|
+
return out;
|
|
6893
|
+
}
|
|
6894
|
+
|
|
6895
|
+
function selectPositionIndexedCandidates(all, exactByS, fallbackS, sk, exactByO, fallbackO, ok) {
|
|
6896
|
+
if (sk === null && ok === null) return all && all.length ? all : EMPTY_FACT_INDEX_BUCKET;
|
|
6897
|
+
|
|
6898
|
+
let sBucket = null;
|
|
6899
|
+
if (sk !== null) sBucket = mergeIndexBuckets(exactByS || null, fallbackS || null);
|
|
6900
|
+
|
|
6901
|
+
let oBucket = null;
|
|
6902
|
+
if (ok !== null) oBucket = mergeIndexBuckets(exactByO || null, fallbackO || null);
|
|
6903
|
+
|
|
6904
|
+
if (sk !== null && ok !== null) return sBucket.length <= oBucket.length ? sBucket : oBucket;
|
|
6905
|
+
return sk !== null ? sBucket : oBucket;
|
|
6906
|
+
}
|
|
6907
|
+
|
|
6761
6908
|
function candidateFacts(facts, goal) {
|
|
6762
6909
|
ensureFactIndexes(facts);
|
|
6763
6910
|
|
|
6764
6911
|
if (goal.p instanceof Iri) {
|
|
6765
6912
|
const pk = goal.p.__tid;
|
|
6766
6913
|
|
|
6767
|
-
const sk =
|
|
6768
|
-
const ok =
|
|
6914
|
+
const sk = termLookupKey(goal.s);
|
|
6915
|
+
const ok = termLookupKey(goal.o);
|
|
6769
6916
|
|
|
6770
|
-
/** @type {number[] | null} */
|
|
6771
6917
|
let byPS = null;
|
|
6772
6918
|
if (sk !== null) {
|
|
6773
6919
|
const ps = facts.__byPS.get(pk);
|
|
6774
6920
|
if (ps) byPS = ps.get(sk) || null;
|
|
6775
6921
|
}
|
|
6922
|
+
const byPNonFastS = sk !== null ? facts.__byPNonFastS.get(pk) || null : null;
|
|
6776
6923
|
|
|
6777
|
-
/** @type {number[] | null} */
|
|
6778
6924
|
let byPO = null;
|
|
6779
6925
|
if (ok !== null) {
|
|
6780
6926
|
const po = facts.__byPO.get(pk);
|
|
6781
6927
|
if (po) byPO = po.get(ok) || null;
|
|
6782
6928
|
}
|
|
6929
|
+
const byPNonFastO = ok !== null ? facts.__byPNonFastO.get(pk) || null : null;
|
|
6783
6930
|
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
|
|
6931
|
+
const exact = selectPositionIndexedCandidates(
|
|
6932
|
+
facts.__byPred.get(pk) || null,
|
|
6933
|
+
byPS,
|
|
6934
|
+
byPNonFastS,
|
|
6935
|
+
sk,
|
|
6936
|
+
byPO,
|
|
6937
|
+
byPNonFastO,
|
|
6938
|
+
ok,
|
|
6939
|
+
);
|
|
6789
6940
|
|
|
6790
|
-
|
|
6791
|
-
|
|
6792
|
-
if (sk !== null) wildPS = facts.__wildPS.get(sk) || null;
|
|
6941
|
+
let varPredPS = null;
|
|
6942
|
+
if (sk !== null) varPredPS = facts.__varPredPS.get(sk) || null;
|
|
6793
6943
|
|
|
6794
|
-
|
|
6795
|
-
|
|
6796
|
-
if (ok !== null) wildPO = facts.__wildPO.get(ok) || null;
|
|
6944
|
+
let varPredPO = null;
|
|
6945
|
+
if (ok !== null) varPredPO = facts.__varPredPO.get(ok) || null;
|
|
6797
6946
|
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
|
|
6801
|
-
|
|
6802
|
-
|
|
6947
|
+
const wild = selectPositionIndexedCandidates(
|
|
6948
|
+
facts.__varPred,
|
|
6949
|
+
varPredPS,
|
|
6950
|
+
sk !== null ? facts.__varPredNonFastS : null,
|
|
6951
|
+
sk,
|
|
6952
|
+
varPredPO,
|
|
6953
|
+
ok !== null ? facts.__varPredNonFastO : null,
|
|
6954
|
+
ok,
|
|
6955
|
+
);
|
|
6803
6956
|
|
|
6804
6957
|
return {
|
|
6805
|
-
exact
|
|
6806
|
-
wild
|
|
6807
|
-
exactLen: exact
|
|
6808
|
-
wildLen: wild
|
|
6809
|
-
totalLen:
|
|
6958
|
+
exact,
|
|
6959
|
+
wild,
|
|
6960
|
+
exactLen: exact.length,
|
|
6961
|
+
wildLen: wild.length,
|
|
6962
|
+
totalLen: exact.length + wild.length,
|
|
6810
6963
|
};
|
|
6811
6964
|
}
|
|
6812
6965
|
|
|
@@ -6824,20 +6977,28 @@ function hasFactIndexed(facts, tr) {
|
|
|
6824
6977
|
|
|
6825
6978
|
if (tr.p instanceof Iri) {
|
|
6826
6979
|
const pk = tr.p.__tid;
|
|
6980
|
+
const sk = termLookupKey(tr.s);
|
|
6981
|
+
let best = null;
|
|
6827
6982
|
|
|
6828
|
-
|
|
6983
|
+
if (sk !== null) {
|
|
6984
|
+
const ps = facts.__byPS.get(pk);
|
|
6985
|
+
if (!ps) return false;
|
|
6986
|
+
const psb = ps.get(sk);
|
|
6987
|
+
if (!psb || psb.length === 0) return false;
|
|
6988
|
+
best = psb;
|
|
6989
|
+
}
|
|
6990
|
+
|
|
6991
|
+
const ok = termLookupKey(tr.o);
|
|
6829
6992
|
if (ok !== null) {
|
|
6830
6993
|
const po = facts.__byPO.get(pk);
|
|
6831
|
-
if (po)
|
|
6832
|
-
|
|
6833
|
-
|
|
6834
|
-
|
|
6835
|
-
// triples as duplicates modulo blank renaming, or you'll incorrectly
|
|
6836
|
-
// drop facts like: _:sk_0 :x 8.0 (because _:b8 :x 8.0 exists).
|
|
6837
|
-
return pob.some((i) => triplesEqual(facts[i], tr));
|
|
6838
|
-
}
|
|
6994
|
+
if (!po) return false;
|
|
6995
|
+
const pob = po.get(ok);
|
|
6996
|
+
if (!pob || pob.length === 0) return false;
|
|
6997
|
+
if (!best || pob.length < best.length) best = pob;
|
|
6839
6998
|
}
|
|
6840
6999
|
|
|
7000
|
+
if (best) return best.some((i) => triplesEqual(facts[i], tr));
|
|
7001
|
+
|
|
6841
7002
|
const pb = facts.__byPred.get(pk) || [];
|
|
6842
7003
|
return pb.some((i) => triplesEqual(facts[i], tr));
|
|
6843
7004
|
}
|
|
@@ -6946,11 +7107,25 @@ function mergeSinglePremiseAgendaBuckets() {
|
|
|
6946
7107
|
return out;
|
|
6947
7108
|
}
|
|
6948
7109
|
|
|
7110
|
+
function termContainsVarForAgenda(t) {
|
|
7111
|
+
if (t instanceof Var) return true;
|
|
7112
|
+
if (t instanceof ListTerm) return t.elems.some(termContainsVarForAgenda);
|
|
7113
|
+
if (t instanceof OpenListTerm) return true;
|
|
7114
|
+
if (t instanceof GraphTerm)
|
|
7115
|
+
return t.triples.some(
|
|
7116
|
+
(tr) =>
|
|
7117
|
+
termContainsVarForAgenda(tr.s) || termContainsVarForAgenda(tr.p) || termContainsVarForAgenda(tr.o),
|
|
7118
|
+
);
|
|
7119
|
+
return false;
|
|
7120
|
+
}
|
|
7121
|
+
|
|
6949
7122
|
function makeSinglePremiseAgendaIndex(forwardRules, backRules) {
|
|
6950
7123
|
const index = {
|
|
6951
7124
|
byPred: new Map(),
|
|
7125
|
+
byPredAll: new Map(),
|
|
6952
7126
|
byPS: new Map(),
|
|
6953
7127
|
byPO: new Map(),
|
|
7128
|
+
allIriPred: [],
|
|
6954
7129
|
wildPred: [],
|
|
6955
7130
|
wildPS: new Map(),
|
|
6956
7131
|
wildPO: new Map(),
|
|
@@ -6972,8 +7147,8 @@ function makeSinglePremiseAgendaIndex(forwardRules, backRules) {
|
|
|
6972
7147
|
if (!isSinglePremiseAgendaRuleSafe(r, backRules)) continue;
|
|
6973
7148
|
|
|
6974
7149
|
const goal = r.premise[0];
|
|
6975
|
-
const goalSKey =
|
|
6976
|
-
const goalOKey =
|
|
7150
|
+
const goalSKey = termLookupKey(goal.s);
|
|
7151
|
+
const goalOKey = termLookupKey(goal.o);
|
|
6977
7152
|
const fastSubjectVar = goal.p instanceof Iri && goal.s instanceof Var && goalOKey !== null ? goal.s.name : null;
|
|
6978
7153
|
const fastObjectVar = goal.p instanceof Iri && goal.o instanceof Var && goalSKey !== null ? goal.o.name : null;
|
|
6979
7154
|
const entry = {
|
|
@@ -6992,6 +7167,8 @@ function makeSinglePremiseAgendaIndex(forwardRules, backRules) {
|
|
|
6992
7167
|
index.size += 1;
|
|
6993
7168
|
|
|
6994
7169
|
if (entry.goalPredTid !== null) {
|
|
7170
|
+
addToMapArray(index.byPredAll, entry.goalPredTid, entry);
|
|
7171
|
+
index.allIriPred.push(entry);
|
|
6995
7172
|
if (entry.goalSKey === null && entry.goalOKey === null) addToMapArray(index.byPred, entry.goalPredTid, entry);
|
|
6996
7173
|
if (entry.goalSKey !== null) {
|
|
6997
7174
|
let ps = index.byPS.get(entry.goalPredTid);
|
|
@@ -7022,25 +7199,37 @@ function makeSinglePremiseAgendaIndex(forwardRules, backRules) {
|
|
|
7022
7199
|
function getSinglePremiseAgendaCandidates(index, fact) {
|
|
7023
7200
|
if (!index || index.size === 0) return null;
|
|
7024
7201
|
|
|
7025
|
-
const sk =
|
|
7026
|
-
const ok =
|
|
7202
|
+
const sk = termLookupKey(fact.s);
|
|
7203
|
+
const ok = termLookupKey(fact.o);
|
|
7027
7204
|
|
|
7028
7205
|
let exact = null;
|
|
7029
7206
|
if (fact.p instanceof Iri) {
|
|
7030
7207
|
const pk = fact.p.__tid;
|
|
7031
|
-
|
|
7032
|
-
|
|
7033
|
-
|
|
7208
|
+
if ((sk === null && termContainsVarForAgenda(fact.s)) || (ok === null && termContainsVarForAgenda(fact.o))) {
|
|
7209
|
+
// A fact with a variable-bearing subject/object (most importantly a
|
|
7210
|
+
// top-level variable fact such as `?S :p ?O.`) can match rules whose
|
|
7211
|
+
// premise is fixed in that position. The ordinary `(p,s)` / `(p,o)` lookup
|
|
7212
|
+
// would miss those rules, so fall back to all agenda-indexed rules for
|
|
7213
|
+
// this predicate. Do not do this merely for non-fast quoted formulas:
|
|
7214
|
+
// they are not wildcards, and broad fallback would over-fire rules that
|
|
7215
|
+
// rely on protected blank-node/formula unification semantics.
|
|
7216
|
+
exact = index.byPredAll.get(pk) || null;
|
|
7217
|
+
} else {
|
|
7218
|
+
const byPred = index.byPred.get(pk) || null;
|
|
7219
|
+
let byPS = null;
|
|
7034
7220
|
const ps = index.byPS.get(pk);
|
|
7035
7221
|
if (ps) byPS = ps.get(sk) || null;
|
|
7036
|
-
|
|
7037
|
-
let byPO = null;
|
|
7038
|
-
if (ok !== null) {
|
|
7222
|
+
let byPO = null;
|
|
7039
7223
|
const po = index.byPO.get(pk);
|
|
7040
7224
|
if (po) byPO = po.get(ok) || null;
|
|
7041
|
-
}
|
|
7042
7225
|
|
|
7043
|
-
|
|
7226
|
+
exact = mergeSinglePremiseAgendaBuckets(byPred, byPS, byPO);
|
|
7227
|
+
}
|
|
7228
|
+
} else if (fact.p instanceof Var) {
|
|
7229
|
+
// A variable-predicate fact can match any IRI-predicate agenda rule.
|
|
7230
|
+
// This is deliberately broad and relies on final unification below; such
|
|
7231
|
+
// facts are uncommon and correctness matters more than over-indexing them.
|
|
7232
|
+
exact = index.allIriPred.length ? index.allIriPred : null;
|
|
7044
7233
|
}
|
|
7045
7234
|
|
|
7046
7235
|
const wildPred = index.wildPred.length ? index.wildPred : null;
|
|
@@ -9647,15 +9836,6 @@ function isIdentChar(c) {
|
|
|
9647
9836
|
return c === ':' || isPnChars(c);
|
|
9648
9837
|
}
|
|
9649
9838
|
|
|
9650
|
-
function canContinueAfterDot(next) {
|
|
9651
|
-
// PN_LOCAL allows '.' but it cannot appear at the end.
|
|
9652
|
-
// We include '.' only if it is followed by something that could continue a name.
|
|
9653
|
-
if (next === null) return false;
|
|
9654
|
-
if (isIdentChar(next)) return true;
|
|
9655
|
-
if (next === '%' || next === '\\') return true;
|
|
9656
|
-
return false;
|
|
9657
|
-
}
|
|
9658
|
-
|
|
9659
9839
|
function isForbiddenNoncharacterCodePoint(cp) {
|
|
9660
9840
|
return (cp & 0xffff) === 0xfffe || (cp & 0xffff) === 0xffff;
|
|
9661
9841
|
}
|