eyeling 1.11.19 → 1.11.21
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 +4 -7
- package/eyeling.js +33 -273
- package/lib/builtins.js +7 -4
- package/lib/engine.js +26 -267
- package/package.json +1 -1
package/HANDBOOK.md
CHANGED
|
@@ -182,7 +182,7 @@ Eyeling interns IRIs and Literals by string value. Interning is a quiet performa
|
|
|
182
182
|
|
|
183
183
|
In addition, interned **Iri**/**Literal** terms (and generated **Blank** terms) get a small, non-enumerable integer id `.__tid` that is stable for the lifetime of the process. This `__tid` is used as the engine’s “fast key”:
|
|
184
184
|
|
|
185
|
-
- fact indexes (`__byPS` / `__byPO`) key by `__tid`
|
|
185
|
+
- fact indexes (`__byPred` / `__byPS` / `__byPO`) key by `__tid` values **and store fact *indices*** (predicate buckets are keyed by `predicate.__tid`, and PS/PO buckets are keyed by the subject/object `.__tid`; buckets contain integer indices into the `facts` array)
|
|
186
186
|
- duplicate detection uses `"sid pid oid"` where each component is a `__tid`
|
|
187
187
|
- unification/equality has an early-out when two terms share the same `__tid`
|
|
188
188
|
|
|
@@ -441,9 +441,9 @@ Facts live in an array `facts: Triple[]`.
|
|
|
441
441
|
|
|
442
442
|
Eyeling attaches hidden (non-enumerable) index fields:
|
|
443
443
|
|
|
444
|
-
* `facts.__byPred: Map<
|
|
445
|
-
* `facts.__byPS: Map<
|
|
446
|
-
* `facts.__byPO: Map<
|
|
444
|
+
* `facts.__byPred: Map<predicateId, number[]>` where each entry is an index into `facts` (and `predicateId` is `predicate.__tid`)
|
|
445
|
+
* `facts.__byPS: Map<predicateId, Map<termId, number[]>>` where each entry is an index into `facts` (and `termId` is `term.__tid`)
|
|
446
|
+
* `facts.__byPO: Map<predicateId, Map<termId, number[]>>` where each entry is an index into `facts` (and `termId` is `term.__tid`)
|
|
447
447
|
* `facts.__keySet: Set<string>` for a fast-path `"sid pid oid"` key (all three are `__tid` values)
|
|
448
448
|
|
|
449
449
|
`termFastKey(term)` returns a `termId` (`term.__tid`) for **Iri**, **Literal**, and **Blank** terms, and `null` for structured terms (lists, quoted graphs) and variables.
|
|
@@ -519,9 +519,6 @@ for delta in deltas:
|
|
|
519
519
|
|
|
520
520
|
**Implementation note (performance):** as of this version, Eyeling also avoids allocating short-lived substitution objects when matching goals against **facts** and when unifying a **backward-rule head** with the current goal. Instead of calling the pure `unifyTriple(..., subst)` (which clones the substitution on each variable bind), the prover performs an **in-place unification** directly into the mutable `substMut` store and records only the newly-bound variable names on the trail. This typically reduces GC pressure significantly on reachability / path-search workloads, where unification is executed extremely frequently.
|
|
521
521
|
|
|
522
|
-
**Implementation note (performance): ground-goal memoization.** When the current goal triple is fully ground (contains no `Var`, no open-list term, and no quoted formula), satisfying it cannot introduce new bindings. Eyeling maintains a small per-proof memo cache for such ground goals, keyed by a canonical triple key. If the cache records that a ground goal is satisfiable (either by a direct fact match or only via backward rules), the prover skips re-proving it and continues with the remaining goals. This reduces repeated work in programs that issue the same membership-style checks many times (even without full tabling). The cache is bounded (default: 20k entries) and uses an LRU-style eviction; advanced callers can override the bound via `opts.groundMemoMax` passed to `proveGoals`.
|
|
523
|
-
|
|
524
|
-
|
|
525
522
|
|
|
526
523
|
So built-ins behave like relations that can generate zero, one, or many possible bindings. A list generator might yield many deltas; a numeric test yields zero or one.
|
|
527
524
|
|
package/eyeling.js
CHANGED
|
@@ -1020,19 +1020,22 @@ function listAppendSplit(parts, resElems, subst) {
|
|
|
1020
1020
|
|
|
1021
1021
|
function __rdfListObjectsForSP(facts, predIri, subj) {
|
|
1022
1022
|
ensureFactIndexes(facts);
|
|
1023
|
+
// Engine indexes predicate buckets by predicate term id.
|
|
1024
|
+
const pk = internIri(predIri).__tid;
|
|
1023
1025
|
const sk = termFastKey(subj);
|
|
1024
1026
|
if (sk !== null) {
|
|
1025
|
-
const ps = facts.__byPS.get(
|
|
1027
|
+
const ps = facts.__byPS.get(pk);
|
|
1026
1028
|
if (ps) {
|
|
1027
1029
|
const bucket = ps.get(sk);
|
|
1028
|
-
if (bucket && bucket.length) return bucket.map((
|
|
1030
|
+
if (bucket && bucket.length) return bucket.map((i) => facts[i].o);
|
|
1029
1031
|
}
|
|
1030
1032
|
}
|
|
1031
1033
|
|
|
1032
1034
|
// Fallback scan (covers non-indexable terms)
|
|
1033
|
-
const pb = facts.__byPred.get(
|
|
1035
|
+
const pb = facts.__byPred.get(pk) || [];
|
|
1034
1036
|
const out = [];
|
|
1035
|
-
for (const
|
|
1037
|
+
for (const i of pb) {
|
|
1038
|
+
const tr = facts[i];
|
|
1036
1039
|
if (termsEqual(tr.s, subj)) out.push(tr.o);
|
|
1037
1040
|
}
|
|
1038
1041
|
return out;
|
|
@@ -4953,12 +4956,13 @@ function alphaEqGraphTriples(xs, ys) {
|
|
|
4953
4956
|
// ===========================================================================
|
|
4954
4957
|
//
|
|
4955
4958
|
// Facts:
|
|
4956
|
-
// - __byPred: Map<
|
|
4957
|
-
// -
|
|
4958
|
-
// -
|
|
4959
|
+
// - __byPred: Map<predicateId, number[]> (indices into facts array)
|
|
4960
|
+
// - __byPS: Map<predicateId, Map<subjectId, number[]>>
|
|
4961
|
+
// - __byPO: Map<predicateId, Map<objectId, number[]>>
|
|
4962
|
+
// - __keySet: Set<"S\tP\tO"> for Iri/Literal/Blank-only triples (fast dup check)
|
|
4959
4963
|
//
|
|
4960
4964
|
// Backward rules:
|
|
4961
|
-
// - __byHeadPred: Map<
|
|
4965
|
+
// - __byHeadPred: Map<headPredicateId, Rule[]>
|
|
4962
4966
|
// - __wildHeadPred: Rule[] (non-IRI head predicate)
|
|
4963
4967
|
|
|
4964
4968
|
function termFastKey(t) {
|
|
@@ -4998,19 +5002,20 @@ function ensureFactIndexes(facts) {
|
|
|
4998
5002
|
writable: true,
|
|
4999
5003
|
});
|
|
5000
5004
|
|
|
5001
|
-
for (
|
|
5005
|
+
for (let i = 0; i < facts.length; i++) indexFact(facts, facts[i], i);
|
|
5002
5006
|
}
|
|
5003
5007
|
|
|
5004
|
-
function indexFact(facts, tr) {
|
|
5008
|
+
function indexFact(facts, tr, idx) {
|
|
5005
5009
|
if (tr.p instanceof Iri) {
|
|
5006
|
-
|
|
5010
|
+
// Use predicate term id as the primary key to avoid hashing long IRI strings.
|
|
5011
|
+
const pk = tr.p.__tid;
|
|
5007
5012
|
|
|
5008
5013
|
let pb = facts.__byPred.get(pk);
|
|
5009
5014
|
if (!pb) {
|
|
5010
5015
|
pb = [];
|
|
5011
5016
|
facts.__byPred.set(pk, pb);
|
|
5012
5017
|
}
|
|
5013
|
-
pb.push(
|
|
5018
|
+
pb.push(idx);
|
|
5014
5019
|
|
|
5015
5020
|
const sk = termFastKey(tr.s);
|
|
5016
5021
|
if (sk !== null) {
|
|
@@ -5024,7 +5029,7 @@ function indexFact(facts, tr) {
|
|
|
5024
5029
|
psb = [];
|
|
5025
5030
|
ps.set(sk, psb);
|
|
5026
5031
|
}
|
|
5027
|
-
psb.push(
|
|
5032
|
+
psb.push(idx);
|
|
5028
5033
|
}
|
|
5029
5034
|
|
|
5030
5035
|
const ok = termFastKey(tr.o);
|
|
@@ -5039,7 +5044,7 @@ function indexFact(facts, tr) {
|
|
|
5039
5044
|
pob = [];
|
|
5040
5045
|
po.set(ok, pob);
|
|
5041
5046
|
}
|
|
5042
|
-
pob.push(
|
|
5047
|
+
pob.push(idx);
|
|
5043
5048
|
}
|
|
5044
5049
|
}
|
|
5045
5050
|
|
|
@@ -5051,19 +5056,19 @@ function candidateFacts(facts, goal) {
|
|
|
5051
5056
|
ensureFactIndexes(facts);
|
|
5052
5057
|
|
|
5053
5058
|
if (goal.p instanceof Iri) {
|
|
5054
|
-
const pk = goal.p.
|
|
5059
|
+
const pk = goal.p.__tid;
|
|
5055
5060
|
|
|
5056
5061
|
const sk = termFastKey(goal.s);
|
|
5057
5062
|
const ok = termFastKey(goal.o);
|
|
5058
5063
|
|
|
5059
|
-
/** @type {
|
|
5064
|
+
/** @type {number[] | null} */
|
|
5060
5065
|
let byPS = null;
|
|
5061
5066
|
if (sk !== null) {
|
|
5062
5067
|
const ps = facts.__byPS.get(pk);
|
|
5063
5068
|
if (ps) byPS = ps.get(sk) || null;
|
|
5064
5069
|
}
|
|
5065
5070
|
|
|
5066
|
-
/** @type {
|
|
5071
|
+
/** @type {number[] | null} */
|
|
5067
5072
|
let byPO = null;
|
|
5068
5073
|
if (ok !== null) {
|
|
5069
5074
|
const po = facts.__byPO.get(pk);
|
|
@@ -5077,7 +5082,7 @@ function candidateFacts(facts, goal) {
|
|
|
5077
5082
|
return facts.__byPred.get(pk) || [];
|
|
5078
5083
|
}
|
|
5079
5084
|
|
|
5080
|
-
return
|
|
5085
|
+
return null;
|
|
5081
5086
|
}
|
|
5082
5087
|
|
|
5083
5088
|
function hasFactIndexed(facts, tr) {
|
|
@@ -5087,7 +5092,7 @@ function hasFactIndexed(facts, tr) {
|
|
|
5087
5092
|
if (key !== null) return facts.__keySet.has(key);
|
|
5088
5093
|
|
|
5089
5094
|
if (tr.p instanceof Iri) {
|
|
5090
|
-
const pk = tr.p.
|
|
5095
|
+
const pk = tr.p.__tid;
|
|
5091
5096
|
|
|
5092
5097
|
const ok = termFastKey(tr.o);
|
|
5093
5098
|
if (ok !== null) {
|
|
@@ -5098,12 +5103,12 @@ function hasFactIndexed(facts, tr) {
|
|
|
5098
5103
|
// different existentials unless explicitly connected. Do NOT treat
|
|
5099
5104
|
// triples as duplicates modulo blank renaming, or you'll incorrectly
|
|
5100
5105
|
// drop facts like: _:sk_0 :x 8.0 (because _:b8 :x 8.0 exists).
|
|
5101
|
-
return pob.some((
|
|
5106
|
+
return pob.some((i) => triplesEqual(facts[i], tr));
|
|
5102
5107
|
}
|
|
5103
5108
|
}
|
|
5104
5109
|
|
|
5105
5110
|
const pb = facts.__byPred.get(pk) || [];
|
|
5106
|
-
return pb.some((
|
|
5111
|
+
return pb.some((i) => triplesEqual(facts[i], tr));
|
|
5107
5112
|
}
|
|
5108
5113
|
|
|
5109
5114
|
// Non-IRI predicate: fall back to strict triple equality.
|
|
@@ -5112,8 +5117,9 @@ function hasFactIndexed(facts, tr) {
|
|
|
5112
5117
|
|
|
5113
5118
|
function pushFactIndexed(facts, tr) {
|
|
5114
5119
|
ensureFactIndexes(facts);
|
|
5120
|
+
const idx = facts.length;
|
|
5115
5121
|
facts.push(tr);
|
|
5116
|
-
indexFact(facts, tr);
|
|
5122
|
+
indexFact(facts, tr, idx);
|
|
5117
5123
|
}
|
|
5118
5124
|
|
|
5119
5125
|
function ensureBackRuleIndexes(backRules) {
|
|
@@ -5137,7 +5143,7 @@ function indexBackRule(backRules, r) {
|
|
|
5137
5143
|
if (!r || !r.conclusion || r.conclusion.length !== 1) return;
|
|
5138
5144
|
const head = r.conclusion[0];
|
|
5139
5145
|
if (head && head.p instanceof Iri) {
|
|
5140
|
-
const k = head.p.
|
|
5146
|
+
const k = head.p.__tid;
|
|
5141
5147
|
let bucket = backRules.__byHeadPred.get(k);
|
|
5142
5148
|
if (!bucket) {
|
|
5143
5149
|
bucket = [];
|
|
@@ -5691,61 +5697,6 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxR
|
|
|
5691
5697
|
trail.length = mark;
|
|
5692
5698
|
}
|
|
5693
5699
|
|
|
5694
|
-
|
|
5695
|
-
// ---------------------------------------------------------------------------
|
|
5696
|
-
// Ground-goal memoization (small cache)
|
|
5697
|
-
// ---------------------------------------------------------------------------
|
|
5698
|
-
//
|
|
5699
|
-
// For fully ground (variable-free) triple subgoals, the proof search can be
|
|
5700
|
-
// repeated many times across different branches of the DFS. Because a ground
|
|
5701
|
-
// goal never produces bindings, it is safe to memoize whether it is satisfiable.
|
|
5702
|
-
//
|
|
5703
|
-
// Cache values:
|
|
5704
|
-
// 0 = unsatisfiable
|
|
5705
|
-
// 1 = satisfiable (direct fact match exists)
|
|
5706
|
-
// 2 = satisfiable (only via backward rules)
|
|
5707
|
-
// 3 = in progress (cycle breaker)
|
|
5708
|
-
const __GROUND_MEMO_MAX =
|
|
5709
|
-
opts && typeof opts.groundMemoMax === 'number' && opts.groundMemoMax > 0 ? opts.groundMemoMax : 20000;
|
|
5710
|
-
const __groundGoalMemo = new Map();
|
|
5711
|
-
const __MEMO_FALSE = 0;
|
|
5712
|
-
const __MEMO_TRUE_FACT = 1;
|
|
5713
|
-
const __MEMO_TRUE_RULE = 2;
|
|
5714
|
-
const __MEMO_INPROGRESS = 3;
|
|
5715
|
-
|
|
5716
|
-
function __memoSet(key, val) {
|
|
5717
|
-
if (__groundGoalMemo.has(key)) __groundGoalMemo.delete(key);
|
|
5718
|
-
__groundGoalMemo.set(key, val);
|
|
5719
|
-
if (__groundGoalMemo.size > __GROUND_MEMO_MAX) {
|
|
5720
|
-
const firstKey = __groundGoalMemo.keys().next().value;
|
|
5721
|
-
__groundGoalMemo.delete(firstKey);
|
|
5722
|
-
}
|
|
5723
|
-
}
|
|
5724
|
-
|
|
5725
|
-
function __isMemoGroundTerm(t) {
|
|
5726
|
-
if (t instanceof Var) return false;
|
|
5727
|
-
if (t instanceof OpenListTerm) return false;
|
|
5728
|
-
if (t instanceof GraphTerm) return false;
|
|
5729
|
-
if (t instanceof ListTerm) return t.elems.every(__isMemoGroundTerm);
|
|
5730
|
-
// Iri, Literal, Blank (and other atomics) are considered ground here.
|
|
5731
|
-
return true;
|
|
5732
|
-
}
|
|
5733
|
-
|
|
5734
|
-
function __memoKeyTerm(t) {
|
|
5735
|
-
if (t instanceof Iri || t instanceof Literal || t instanceof Blank) return t.__tid || null;
|
|
5736
|
-
if (t instanceof ListTerm) return 'L(' + t.elems.map(__memoKeyTerm).join(',') + ')';
|
|
5737
|
-
return null;
|
|
5738
|
-
}
|
|
5739
|
-
|
|
5740
|
-
function __memoKeyTriple(tr) {
|
|
5741
|
-
if (!__isMemoGroundTerm(tr.s) || !__isMemoGroundTerm(tr.p) || !__isMemoGroundTerm(tr.o)) return null;
|
|
5742
|
-
const ks = __memoKeyTerm(tr.s);
|
|
5743
|
-
const kp = __memoKeyTerm(tr.p);
|
|
5744
|
-
const ko = __memoKeyTerm(tr.o);
|
|
5745
|
-
if (ks === null || kp === null || ko === null) return null;
|
|
5746
|
-
return ks + ' ' + kp + ' ' + ko;
|
|
5747
|
-
}
|
|
5748
|
-
|
|
5749
5700
|
// In-place unification into the mutable substitution + trail.
|
|
5750
5701
|
// This avoids allocating short-lived "delta" substitution objects on the hot path
|
|
5751
5702
|
// (facts and backward-rule head matching).
|
|
@@ -5875,163 +5826,6 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxR
|
|
|
5875
5826
|
return true;
|
|
5876
5827
|
}
|
|
5877
5828
|
|
|
5878
|
-
|
|
5879
|
-
// Boolean (existence) prover used by ground-goal memoization.
|
|
5880
|
-
// Returns true as soon as one proof for the goal list is found.
|
|
5881
|
-
function __existsGoals(goalsNow, curDepth, visitedNow) {
|
|
5882
|
-
if (!goalsNow.length) return true;
|
|
5883
|
-
|
|
5884
|
-
const rawGoal = goalsNow[0];
|
|
5885
|
-
const restGoals = goalsNow.length > 1 ? goalsNow.slice(1) : [];
|
|
5886
|
-
const goal0 = applySubstTriple(rawGoal, substMut);
|
|
5887
|
-
|
|
5888
|
-
// Builtins
|
|
5889
|
-
const __pv0 = goal0.p instanceof Iri ? goal0.p.value : null;
|
|
5890
|
-
const __rdfFirstOrRest = __pv0 === RDF_NS + 'first' || __pv0 === RDF_NS + 'rest';
|
|
5891
|
-
const __treatBuiltin =
|
|
5892
|
-
isBuiltinPred(goal0.p) &&
|
|
5893
|
-
!(__rdfFirstOrRest && !(goal0.s instanceof ListTerm || goal0.s instanceof OpenListTerm));
|
|
5894
|
-
|
|
5895
|
-
if (__treatBuiltin) {
|
|
5896
|
-
const deltas = evalBuiltin(goal0, {}, facts, backRules, curDepth, varGen, 1);
|
|
5897
|
-
for (const delta of deltas) {
|
|
5898
|
-
const mark = trail.length;
|
|
5899
|
-
if (!applyDeltaToSubst(delta)) {
|
|
5900
|
-
undoTo(mark);
|
|
5901
|
-
continue;
|
|
5902
|
-
}
|
|
5903
|
-
const ok = __existsGoals(restGoals, curDepth + 1, visitedNow);
|
|
5904
|
-
undoTo(mark);
|
|
5905
|
-
if (ok) return true;
|
|
5906
|
-
}
|
|
5907
|
-
return false;
|
|
5908
|
-
}
|
|
5909
|
-
|
|
5910
|
-
// Loop check for backward reasoning
|
|
5911
|
-
if (listHasTriple(visitedNow, goal0)) return false;
|
|
5912
|
-
const visitedForRules = visitedNow.concat([goal0]);
|
|
5913
|
-
|
|
5914
|
-
// Ground-goal memo check (only safe when the goal is fully ground)
|
|
5915
|
-
const memoKey = __memoKeyTriple(goal0);
|
|
5916
|
-
if (memoKey) {
|
|
5917
|
-
const mv = __groundGoalMemo.get(memoKey);
|
|
5918
|
-
if (mv === __MEMO_FALSE) return false;
|
|
5919
|
-
if (mv === __MEMO_TRUE_FACT) return __existsGoals(restGoals, curDepth + 1, visitedNow);
|
|
5920
|
-
if (mv === __MEMO_TRUE_RULE) return __existsGoals(restGoals, curDepth + 1, visitedForRules);
|
|
5921
|
-
if (mv === __MEMO_INPROGRESS) return false;
|
|
5922
|
-
|
|
5923
|
-
__memoSet(memoKey, __MEMO_INPROGRESS);
|
|
5924
|
-
const kind = __groundGoalSatisfiableKind(goal0, curDepth, visitedNow, visitedForRules);
|
|
5925
|
-
__memoSet(memoKey, kind);
|
|
5926
|
-
|
|
5927
|
-
if (kind === __MEMO_FALSE) return false;
|
|
5928
|
-
if (kind === __MEMO_TRUE_FACT) return __existsGoals(restGoals, curDepth + 1, visitedNow);
|
|
5929
|
-
return __existsGoals(restGoals, curDepth + 1, visitedForRules);
|
|
5930
|
-
}
|
|
5931
|
-
|
|
5932
|
-
// Backward rules (indexed by head predicate) — explored first
|
|
5933
|
-
if (goal0.p instanceof Iri) {
|
|
5934
|
-
ensureBackRuleIndexes(backRules);
|
|
5935
|
-
const candRules = (backRules.__byHeadPred.get(goal0.p.value) || []).concat(backRules.__wildHeadPred);
|
|
5936
|
-
|
|
5937
|
-
for (const r of candRules) {
|
|
5938
|
-
if (r.conclusion.length !== 1) continue;
|
|
5939
|
-
const rawHead = r.conclusion[0];
|
|
5940
|
-
if (rawHead.p instanceof Iri && rawHead.p.value !== goal0.p.value) continue;
|
|
5941
|
-
|
|
5942
|
-
const rStd = standardizeRule(r, varGen);
|
|
5943
|
-
const head = rStd.conclusion[0];
|
|
5944
|
-
|
|
5945
|
-
const mark = trail.length;
|
|
5946
|
-
if (!unifyTripleTrail(head, goal0)) {
|
|
5947
|
-
undoTo(mark);
|
|
5948
|
-
continue;
|
|
5949
|
-
}
|
|
5950
|
-
|
|
5951
|
-
const newGoals = rStd.premise.concat(restGoals);
|
|
5952
|
-
const ok = __existsGoals(newGoals, curDepth + 1, visitedForRules);
|
|
5953
|
-
undoTo(mark);
|
|
5954
|
-
if (ok) return true;
|
|
5955
|
-
}
|
|
5956
|
-
}
|
|
5957
|
-
|
|
5958
|
-
// Facts
|
|
5959
|
-
if (goal0.p instanceof Iri) {
|
|
5960
|
-
const candidates = candidateFacts(facts, goal0);
|
|
5961
|
-
for (const f of candidates) {
|
|
5962
|
-
const mark = trail.length;
|
|
5963
|
-
if (!unifyTripleTrail(goal0, f)) {
|
|
5964
|
-
undoTo(mark);
|
|
5965
|
-
continue;
|
|
5966
|
-
}
|
|
5967
|
-
const ok = __existsGoals(restGoals, curDepth + 1, visitedNow);
|
|
5968
|
-
undoTo(mark);
|
|
5969
|
-
if (ok) return true;
|
|
5970
|
-
}
|
|
5971
|
-
return false;
|
|
5972
|
-
}
|
|
5973
|
-
|
|
5974
|
-
for (const f of facts) {
|
|
5975
|
-
const mark = trail.length;
|
|
5976
|
-
if (!unifyTripleTrail(goal0, f)) {
|
|
5977
|
-
undoTo(mark);
|
|
5978
|
-
continue;
|
|
5979
|
-
}
|
|
5980
|
-
const ok = __existsGoals(restGoals, curDepth + 1, visitedNow);
|
|
5981
|
-
undoTo(mark);
|
|
5982
|
-
if (ok) return true;
|
|
5983
|
-
}
|
|
5984
|
-
return false;
|
|
5985
|
-
}
|
|
5986
|
-
|
|
5987
|
-
function __groundGoalSatisfiableKind(goal0, curDepth, visitedNow, visitedForRules) {
|
|
5988
|
-
// Try direct facts first (cheap): if any fact matches, we can treat this as "fact satisfiable"
|
|
5989
|
-
// for visited semantics.
|
|
5990
|
-
if (goal0.p instanceof Iri) {
|
|
5991
|
-
const candidates = candidateFacts(facts, goal0);
|
|
5992
|
-
for (const f of candidates) {
|
|
5993
|
-
const mark = trail.length;
|
|
5994
|
-
const ok = unifyTripleTrail(goal0, f);
|
|
5995
|
-
undoTo(mark);
|
|
5996
|
-
if (ok) return __MEMO_TRUE_FACT;
|
|
5997
|
-
}
|
|
5998
|
-
} else {
|
|
5999
|
-
for (const f of facts) {
|
|
6000
|
-
const mark = trail.length;
|
|
6001
|
-
const ok = unifyTripleTrail(goal0, f);
|
|
6002
|
-
undoTo(mark);
|
|
6003
|
-
if (ok) return __MEMO_TRUE_FACT;
|
|
6004
|
-
}
|
|
6005
|
-
}
|
|
6006
|
-
|
|
6007
|
-
// Otherwise, look for a proof via backward rules.
|
|
6008
|
-
if (goal0.p instanceof Iri) {
|
|
6009
|
-
ensureBackRuleIndexes(backRules);
|
|
6010
|
-
const candRules = (backRules.__byHeadPred.get(goal0.p.value) || []).concat(backRules.__wildHeadPred);
|
|
6011
|
-
|
|
6012
|
-
for (const r of candRules) {
|
|
6013
|
-
if (r.conclusion.length !== 1) continue;
|
|
6014
|
-
const rawHead = r.conclusion[0];
|
|
6015
|
-
if (rawHead.p instanceof Iri && rawHead.p.value !== goal0.p.value) continue;
|
|
6016
|
-
|
|
6017
|
-
const rStd = standardizeRule(r, varGen);
|
|
6018
|
-
const head = rStd.conclusion[0];
|
|
6019
|
-
|
|
6020
|
-
const mark = trail.length;
|
|
6021
|
-
if (!unifyTripleTrail(head, goal0)) {
|
|
6022
|
-
undoTo(mark);
|
|
6023
|
-
continue;
|
|
6024
|
-
}
|
|
6025
|
-
|
|
6026
|
-
const ok = __existsGoals(rStd.premise, curDepth + 1, visitedForRules);
|
|
6027
|
-
undoTo(mark);
|
|
6028
|
-
if (ok) return __MEMO_TRUE_RULE;
|
|
6029
|
-
}
|
|
6030
|
-
}
|
|
6031
|
-
|
|
6032
|
-
return __MEMO_FALSE;
|
|
6033
|
-
}
|
|
6034
|
-
|
|
6035
5829
|
function dfs(goalsNow, curDepth, visitedNow, canDeferBuiltins, deferCount) {
|
|
6036
5830
|
if (results.length >= max) return;
|
|
6037
5831
|
if (!goalsNow.length) {
|
|
@@ -6108,50 +5902,15 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxR
|
|
|
6108
5902
|
if (listHasTriple(visitedNow, goal0)) return;
|
|
6109
5903
|
const visitedForRules = visitedNow.concat([goal0]);
|
|
6110
5904
|
|
|
6111
|
-
// Ground-goal memoization: if a goal is fully ground, it cannot introduce new bindings,
|
|
6112
|
-
// so we can cache whether it is satisfiable and skip re-proving it across branches.
|
|
6113
|
-
const __memoKey0 = __memoKeyTriple(goal0);
|
|
6114
|
-
if (__memoKey0) {
|
|
6115
|
-
const mv = __groundGoalMemo.get(__memoKey0);
|
|
6116
|
-
if (mv === __MEMO_FALSE) return;
|
|
6117
|
-
|
|
6118
|
-
if (mv === __MEMO_TRUE_FACT || mv === __MEMO_TRUE_RULE) {
|
|
6119
|
-
const __nextVisited = mv === __MEMO_TRUE_FACT ? visitedNow : visitedForRules;
|
|
6120
|
-
if (!restGoals.length) {
|
|
6121
|
-
results.push(gcCompactForGoals(substMut, [], answerVars));
|
|
6122
|
-
} else {
|
|
6123
|
-
dfs(restGoals, curDepth + 1, __nextVisited, canDeferBuiltins, 0);
|
|
6124
|
-
}
|
|
6125
|
-
return;
|
|
6126
|
-
}
|
|
6127
|
-
|
|
6128
|
-
// Cycle breaker for recursive ground goals.
|
|
6129
|
-
if (mv === __MEMO_INPROGRESS) return;
|
|
6130
|
-
|
|
6131
|
-
__memoSet(__memoKey0, __MEMO_INPROGRESS);
|
|
6132
|
-
const kind = __groundGoalSatisfiableKind(goal0, curDepth, visitedNow, visitedForRules);
|
|
6133
|
-
__memoSet(__memoKey0, kind);
|
|
6134
|
-
|
|
6135
|
-
if (kind === __MEMO_FALSE) return;
|
|
6136
|
-
|
|
6137
|
-
const __nextVisited = kind === __MEMO_TRUE_FACT ? visitedNow : visitedForRules;
|
|
6138
|
-
if (!restGoals.length) {
|
|
6139
|
-
results.push(gcCompactForGoals(substMut, [], answerVars));
|
|
6140
|
-
} else {
|
|
6141
|
-
dfs(restGoals, curDepth + 1, __nextVisited, canDeferBuiltins, 0);
|
|
6142
|
-
}
|
|
6143
|
-
return;
|
|
6144
|
-
}
|
|
6145
|
-
|
|
6146
5905
|
// 3) Backward rules (indexed by head predicate) — explored first
|
|
6147
5906
|
if (goal0.p instanceof Iri) {
|
|
6148
5907
|
ensureBackRuleIndexes(backRules);
|
|
6149
|
-
const candRules = (backRules.__byHeadPred.get(goal0.p.
|
|
5908
|
+
const candRules = (backRules.__byHeadPred.get(goal0.p.__tid) || []).concat(backRules.__wildHeadPred);
|
|
6150
5909
|
|
|
6151
5910
|
for (const r of candRules) {
|
|
6152
5911
|
if (r.conclusion.length !== 1) continue;
|
|
6153
5912
|
const rawHead = r.conclusion[0];
|
|
6154
|
-
if (rawHead.p instanceof Iri && rawHead.p.
|
|
5913
|
+
if (rawHead.p instanceof Iri && rawHead.p.__tid !== goal0.p.__tid) continue;
|
|
6155
5914
|
|
|
6156
5915
|
const rStd = standardizeRule(r, varGen);
|
|
6157
5916
|
const head = rStd.conclusion[0];
|
|
@@ -6177,7 +5936,8 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxR
|
|
|
6177
5936
|
// 4) Try to satisfy the goal from known facts
|
|
6178
5937
|
if (goal0.p instanceof Iri) {
|
|
6179
5938
|
const candidates = candidateFacts(facts, goal0);
|
|
6180
|
-
for (const
|
|
5939
|
+
for (const idx of candidates) {
|
|
5940
|
+
const f = facts[idx];
|
|
6181
5941
|
const mark = trail.length;
|
|
6182
5942
|
if (!unifyTripleTrail(goal0, f)) {
|
|
6183
5943
|
undoTo(mark);
|
package/lib/builtins.js
CHANGED
|
@@ -1008,19 +1008,22 @@ function listAppendSplit(parts, resElems, subst) {
|
|
|
1008
1008
|
|
|
1009
1009
|
function __rdfListObjectsForSP(facts, predIri, subj) {
|
|
1010
1010
|
ensureFactIndexes(facts);
|
|
1011
|
+
// Engine indexes predicate buckets by predicate term id.
|
|
1012
|
+
const pk = internIri(predIri).__tid;
|
|
1011
1013
|
const sk = termFastKey(subj);
|
|
1012
1014
|
if (sk !== null) {
|
|
1013
|
-
const ps = facts.__byPS.get(
|
|
1015
|
+
const ps = facts.__byPS.get(pk);
|
|
1014
1016
|
if (ps) {
|
|
1015
1017
|
const bucket = ps.get(sk);
|
|
1016
|
-
if (bucket && bucket.length) return bucket.map((
|
|
1018
|
+
if (bucket && bucket.length) return bucket.map((i) => facts[i].o);
|
|
1017
1019
|
}
|
|
1018
1020
|
}
|
|
1019
1021
|
|
|
1020
1022
|
// Fallback scan (covers non-indexable terms)
|
|
1021
|
-
const pb = facts.__byPred.get(
|
|
1023
|
+
const pb = facts.__byPred.get(pk) || [];
|
|
1022
1024
|
const out = [];
|
|
1023
|
-
for (const
|
|
1025
|
+
for (const i of pb) {
|
|
1026
|
+
const tr = facts[i];
|
|
1024
1027
|
if (termsEqual(tr.s, subj)) out.push(tr.o);
|
|
1025
1028
|
}
|
|
1026
1029
|
return out;
|
package/lib/engine.js
CHANGED
|
@@ -579,12 +579,13 @@ function alphaEqGraphTriples(xs, ys) {
|
|
|
579
579
|
// ===========================================================================
|
|
580
580
|
//
|
|
581
581
|
// Facts:
|
|
582
|
-
// - __byPred: Map<
|
|
583
|
-
// -
|
|
584
|
-
// -
|
|
582
|
+
// - __byPred: Map<predicateId, number[]> (indices into facts array)
|
|
583
|
+
// - __byPS: Map<predicateId, Map<subjectId, number[]>>
|
|
584
|
+
// - __byPO: Map<predicateId, Map<objectId, number[]>>
|
|
585
|
+
// - __keySet: Set<"S\tP\tO"> for Iri/Literal/Blank-only triples (fast dup check)
|
|
585
586
|
//
|
|
586
587
|
// Backward rules:
|
|
587
|
-
// - __byHeadPred: Map<
|
|
588
|
+
// - __byHeadPred: Map<headPredicateId, Rule[]>
|
|
588
589
|
// - __wildHeadPred: Rule[] (non-IRI head predicate)
|
|
589
590
|
|
|
590
591
|
function termFastKey(t) {
|
|
@@ -624,19 +625,20 @@ function ensureFactIndexes(facts) {
|
|
|
624
625
|
writable: true,
|
|
625
626
|
});
|
|
626
627
|
|
|
627
|
-
for (
|
|
628
|
+
for (let i = 0; i < facts.length; i++) indexFact(facts, facts[i], i);
|
|
628
629
|
}
|
|
629
630
|
|
|
630
|
-
function indexFact(facts, tr) {
|
|
631
|
+
function indexFact(facts, tr, idx) {
|
|
631
632
|
if (tr.p instanceof Iri) {
|
|
632
|
-
|
|
633
|
+
// Use predicate term id as the primary key to avoid hashing long IRI strings.
|
|
634
|
+
const pk = tr.p.__tid;
|
|
633
635
|
|
|
634
636
|
let pb = facts.__byPred.get(pk);
|
|
635
637
|
if (!pb) {
|
|
636
638
|
pb = [];
|
|
637
639
|
facts.__byPred.set(pk, pb);
|
|
638
640
|
}
|
|
639
|
-
pb.push(
|
|
641
|
+
pb.push(idx);
|
|
640
642
|
|
|
641
643
|
const sk = termFastKey(tr.s);
|
|
642
644
|
if (sk !== null) {
|
|
@@ -650,7 +652,7 @@ function indexFact(facts, tr) {
|
|
|
650
652
|
psb = [];
|
|
651
653
|
ps.set(sk, psb);
|
|
652
654
|
}
|
|
653
|
-
psb.push(
|
|
655
|
+
psb.push(idx);
|
|
654
656
|
}
|
|
655
657
|
|
|
656
658
|
const ok = termFastKey(tr.o);
|
|
@@ -665,7 +667,7 @@ function indexFact(facts, tr) {
|
|
|
665
667
|
pob = [];
|
|
666
668
|
po.set(ok, pob);
|
|
667
669
|
}
|
|
668
|
-
pob.push(
|
|
670
|
+
pob.push(idx);
|
|
669
671
|
}
|
|
670
672
|
}
|
|
671
673
|
|
|
@@ -677,19 +679,19 @@ function candidateFacts(facts, goal) {
|
|
|
677
679
|
ensureFactIndexes(facts);
|
|
678
680
|
|
|
679
681
|
if (goal.p instanceof Iri) {
|
|
680
|
-
const pk = goal.p.
|
|
682
|
+
const pk = goal.p.__tid;
|
|
681
683
|
|
|
682
684
|
const sk = termFastKey(goal.s);
|
|
683
685
|
const ok = termFastKey(goal.o);
|
|
684
686
|
|
|
685
|
-
/** @type {
|
|
687
|
+
/** @type {number[] | null} */
|
|
686
688
|
let byPS = null;
|
|
687
689
|
if (sk !== null) {
|
|
688
690
|
const ps = facts.__byPS.get(pk);
|
|
689
691
|
if (ps) byPS = ps.get(sk) || null;
|
|
690
692
|
}
|
|
691
693
|
|
|
692
|
-
/** @type {
|
|
694
|
+
/** @type {number[] | null} */
|
|
693
695
|
let byPO = null;
|
|
694
696
|
if (ok !== null) {
|
|
695
697
|
const po = facts.__byPO.get(pk);
|
|
@@ -703,7 +705,7 @@ function candidateFacts(facts, goal) {
|
|
|
703
705
|
return facts.__byPred.get(pk) || [];
|
|
704
706
|
}
|
|
705
707
|
|
|
706
|
-
return
|
|
708
|
+
return null;
|
|
707
709
|
}
|
|
708
710
|
|
|
709
711
|
function hasFactIndexed(facts, tr) {
|
|
@@ -713,7 +715,7 @@ function hasFactIndexed(facts, tr) {
|
|
|
713
715
|
if (key !== null) return facts.__keySet.has(key);
|
|
714
716
|
|
|
715
717
|
if (tr.p instanceof Iri) {
|
|
716
|
-
const pk = tr.p.
|
|
718
|
+
const pk = tr.p.__tid;
|
|
717
719
|
|
|
718
720
|
const ok = termFastKey(tr.o);
|
|
719
721
|
if (ok !== null) {
|
|
@@ -724,12 +726,12 @@ function hasFactIndexed(facts, tr) {
|
|
|
724
726
|
// different existentials unless explicitly connected. Do NOT treat
|
|
725
727
|
// triples as duplicates modulo blank renaming, or you'll incorrectly
|
|
726
728
|
// drop facts like: _:sk_0 :x 8.0 (because _:b8 :x 8.0 exists).
|
|
727
|
-
return pob.some((
|
|
729
|
+
return pob.some((i) => triplesEqual(facts[i], tr));
|
|
728
730
|
}
|
|
729
731
|
}
|
|
730
732
|
|
|
731
733
|
const pb = facts.__byPred.get(pk) || [];
|
|
732
|
-
return pb.some((
|
|
734
|
+
return pb.some((i) => triplesEqual(facts[i], tr));
|
|
733
735
|
}
|
|
734
736
|
|
|
735
737
|
// Non-IRI predicate: fall back to strict triple equality.
|
|
@@ -738,8 +740,9 @@ function hasFactIndexed(facts, tr) {
|
|
|
738
740
|
|
|
739
741
|
function pushFactIndexed(facts, tr) {
|
|
740
742
|
ensureFactIndexes(facts);
|
|
743
|
+
const idx = facts.length;
|
|
741
744
|
facts.push(tr);
|
|
742
|
-
indexFact(facts, tr);
|
|
745
|
+
indexFact(facts, tr, idx);
|
|
743
746
|
}
|
|
744
747
|
|
|
745
748
|
function ensureBackRuleIndexes(backRules) {
|
|
@@ -763,7 +766,7 @@ function indexBackRule(backRules, r) {
|
|
|
763
766
|
if (!r || !r.conclusion || r.conclusion.length !== 1) return;
|
|
764
767
|
const head = r.conclusion[0];
|
|
765
768
|
if (head && head.p instanceof Iri) {
|
|
766
|
-
const k = head.p.
|
|
769
|
+
const k = head.p.__tid;
|
|
767
770
|
let bucket = backRules.__byHeadPred.get(k);
|
|
768
771
|
if (!bucket) {
|
|
769
772
|
bucket = [];
|
|
@@ -1317,60 +1320,6 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxR
|
|
|
1317
1320
|
trail.length = mark;
|
|
1318
1321
|
}
|
|
1319
1322
|
|
|
1320
|
-
// ---------------------------------------------------------------------------
|
|
1321
|
-
// Ground-goal memoization (small cache)
|
|
1322
|
-
// ---------------------------------------------------------------------------
|
|
1323
|
-
//
|
|
1324
|
-
// For fully ground (variable-free) triple subgoals, the proof search can be
|
|
1325
|
-
// repeated many times across different branches of the DFS. Because a ground
|
|
1326
|
-
// goal never produces bindings, it is safe to memoize whether it is satisfiable.
|
|
1327
|
-
//
|
|
1328
|
-
// Cache values:
|
|
1329
|
-
// 0 = unsatisfiable
|
|
1330
|
-
// 1 = satisfiable (direct fact match exists)
|
|
1331
|
-
// 2 = satisfiable (only via backward rules)
|
|
1332
|
-
// 3 = in progress (cycle breaker)
|
|
1333
|
-
const __GROUND_MEMO_MAX =
|
|
1334
|
-
opts && typeof opts.groundMemoMax === 'number' && opts.groundMemoMax > 0 ? opts.groundMemoMax : 20000;
|
|
1335
|
-
const __groundGoalMemo = new Map();
|
|
1336
|
-
const __MEMO_FALSE = 0;
|
|
1337
|
-
const __MEMO_TRUE_FACT = 1;
|
|
1338
|
-
const __MEMO_TRUE_RULE = 2;
|
|
1339
|
-
const __MEMO_INPROGRESS = 3;
|
|
1340
|
-
|
|
1341
|
-
function __memoSet(key, val) {
|
|
1342
|
-
if (__groundGoalMemo.has(key)) __groundGoalMemo.delete(key);
|
|
1343
|
-
__groundGoalMemo.set(key, val);
|
|
1344
|
-
if (__groundGoalMemo.size > __GROUND_MEMO_MAX) {
|
|
1345
|
-
const firstKey = __groundGoalMemo.keys().next().value;
|
|
1346
|
-
__groundGoalMemo.delete(firstKey);
|
|
1347
|
-
}
|
|
1348
|
-
}
|
|
1349
|
-
|
|
1350
|
-
function __isMemoGroundTerm(t) {
|
|
1351
|
-
if (t instanceof Var) return false;
|
|
1352
|
-
if (t instanceof OpenListTerm) return false;
|
|
1353
|
-
if (t instanceof GraphTerm) return false;
|
|
1354
|
-
if (t instanceof ListTerm) return t.elems.every(__isMemoGroundTerm);
|
|
1355
|
-
// Iri, Literal, Blank (and other atomics) are considered ground here.
|
|
1356
|
-
return true;
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
function __memoKeyTerm(t) {
|
|
1360
|
-
if (t instanceof Iri || t instanceof Literal || t instanceof Blank) return t.__tid || null;
|
|
1361
|
-
if (t instanceof ListTerm) return 'L(' + t.elems.map(__memoKeyTerm).join(',') + ')';
|
|
1362
|
-
return null;
|
|
1363
|
-
}
|
|
1364
|
-
|
|
1365
|
-
function __memoKeyTriple(tr) {
|
|
1366
|
-
if (!__isMemoGroundTerm(tr.s) || !__isMemoGroundTerm(tr.p) || !__isMemoGroundTerm(tr.o)) return null;
|
|
1367
|
-
const ks = __memoKeyTerm(tr.s);
|
|
1368
|
-
const kp = __memoKeyTerm(tr.p);
|
|
1369
|
-
const ko = __memoKeyTerm(tr.o);
|
|
1370
|
-
if (ks === null || kp === null || ko === null) return null;
|
|
1371
|
-
return ks + ' ' + kp + ' ' + ko;
|
|
1372
|
-
}
|
|
1373
|
-
|
|
1374
1323
|
// In-place unification into the mutable substitution + trail.
|
|
1375
1324
|
// This avoids allocating short-lived "delta" substitution objects on the hot path
|
|
1376
1325
|
// (facts and backward-rule head matching).
|
|
@@ -1500,162 +1449,6 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxR
|
|
|
1500
1449
|
return true;
|
|
1501
1450
|
}
|
|
1502
1451
|
|
|
1503
|
-
// Boolean (existence) prover used by ground-goal memoization.
|
|
1504
|
-
// Returns true as soon as one proof for the goal list is found.
|
|
1505
|
-
function __existsGoals(goalsNow, curDepth, visitedNow) {
|
|
1506
|
-
if (!goalsNow.length) return true;
|
|
1507
|
-
|
|
1508
|
-
const rawGoal = goalsNow[0];
|
|
1509
|
-
const restGoals = goalsNow.length > 1 ? goalsNow.slice(1) : [];
|
|
1510
|
-
const goal0 = applySubstTriple(rawGoal, substMut);
|
|
1511
|
-
|
|
1512
|
-
// Builtins
|
|
1513
|
-
const __pv0 = goal0.p instanceof Iri ? goal0.p.value : null;
|
|
1514
|
-
const __rdfFirstOrRest = __pv0 === RDF_NS + 'first' || __pv0 === RDF_NS + 'rest';
|
|
1515
|
-
const __treatBuiltin =
|
|
1516
|
-
isBuiltinPred(goal0.p) &&
|
|
1517
|
-
!(__rdfFirstOrRest && !(goal0.s instanceof ListTerm || goal0.s instanceof OpenListTerm));
|
|
1518
|
-
|
|
1519
|
-
if (__treatBuiltin) {
|
|
1520
|
-
const deltas = evalBuiltin(goal0, {}, facts, backRules, curDepth, varGen, 1);
|
|
1521
|
-
for (const delta of deltas) {
|
|
1522
|
-
const mark = trail.length;
|
|
1523
|
-
if (!applyDeltaToSubst(delta)) {
|
|
1524
|
-
undoTo(mark);
|
|
1525
|
-
continue;
|
|
1526
|
-
}
|
|
1527
|
-
const ok = __existsGoals(restGoals, curDepth + 1, visitedNow);
|
|
1528
|
-
undoTo(mark);
|
|
1529
|
-
if (ok) return true;
|
|
1530
|
-
}
|
|
1531
|
-
return false;
|
|
1532
|
-
}
|
|
1533
|
-
|
|
1534
|
-
// Loop check for backward reasoning
|
|
1535
|
-
if (listHasTriple(visitedNow, goal0)) return false;
|
|
1536
|
-
const visitedForRules = visitedNow.concat([goal0]);
|
|
1537
|
-
|
|
1538
|
-
// Ground-goal memo check (only safe when the goal is fully ground)
|
|
1539
|
-
const memoKey = __memoKeyTriple(goal0);
|
|
1540
|
-
if (memoKey) {
|
|
1541
|
-
const mv = __groundGoalMemo.get(memoKey);
|
|
1542
|
-
if (mv === __MEMO_FALSE) return false;
|
|
1543
|
-
if (mv === __MEMO_TRUE_FACT) return __existsGoals(restGoals, curDepth + 1, visitedNow);
|
|
1544
|
-
if (mv === __MEMO_TRUE_RULE) return __existsGoals(restGoals, curDepth + 1, visitedForRules);
|
|
1545
|
-
if (mv === __MEMO_INPROGRESS) return false;
|
|
1546
|
-
|
|
1547
|
-
__memoSet(memoKey, __MEMO_INPROGRESS);
|
|
1548
|
-
const kind = __groundGoalSatisfiableKind(goal0, curDepth, visitedNow, visitedForRules);
|
|
1549
|
-
__memoSet(memoKey, kind);
|
|
1550
|
-
|
|
1551
|
-
if (kind === __MEMO_FALSE) return false;
|
|
1552
|
-
if (kind === __MEMO_TRUE_FACT) return __existsGoals(restGoals, curDepth + 1, visitedNow);
|
|
1553
|
-
return __existsGoals(restGoals, curDepth + 1, visitedForRules);
|
|
1554
|
-
}
|
|
1555
|
-
|
|
1556
|
-
// Backward rules (indexed by head predicate) — explored first
|
|
1557
|
-
if (goal0.p instanceof Iri) {
|
|
1558
|
-
ensureBackRuleIndexes(backRules);
|
|
1559
|
-
const candRules = (backRules.__byHeadPred.get(goal0.p.value) || []).concat(backRules.__wildHeadPred);
|
|
1560
|
-
|
|
1561
|
-
for (const r of candRules) {
|
|
1562
|
-
if (r.conclusion.length !== 1) continue;
|
|
1563
|
-
const rawHead = r.conclusion[0];
|
|
1564
|
-
if (rawHead.p instanceof Iri && rawHead.p.value !== goal0.p.value) continue;
|
|
1565
|
-
|
|
1566
|
-
const rStd = standardizeRule(r, varGen);
|
|
1567
|
-
const head = rStd.conclusion[0];
|
|
1568
|
-
|
|
1569
|
-
const mark = trail.length;
|
|
1570
|
-
if (!unifyTripleTrail(head, goal0)) {
|
|
1571
|
-
undoTo(mark);
|
|
1572
|
-
continue;
|
|
1573
|
-
}
|
|
1574
|
-
|
|
1575
|
-
const newGoals = rStd.premise.concat(restGoals);
|
|
1576
|
-
const ok = __existsGoals(newGoals, curDepth + 1, visitedForRules);
|
|
1577
|
-
undoTo(mark);
|
|
1578
|
-
if (ok) return true;
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
1581
|
-
|
|
1582
|
-
// Facts
|
|
1583
|
-
if (goal0.p instanceof Iri) {
|
|
1584
|
-
const candidates = candidateFacts(facts, goal0);
|
|
1585
|
-
for (const f of candidates) {
|
|
1586
|
-
const mark = trail.length;
|
|
1587
|
-
if (!unifyTripleTrail(goal0, f)) {
|
|
1588
|
-
undoTo(mark);
|
|
1589
|
-
continue;
|
|
1590
|
-
}
|
|
1591
|
-
const ok = __existsGoals(restGoals, curDepth + 1, visitedNow);
|
|
1592
|
-
undoTo(mark);
|
|
1593
|
-
if (ok) return true;
|
|
1594
|
-
}
|
|
1595
|
-
return false;
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
|
-
for (const f of facts) {
|
|
1599
|
-
const mark = trail.length;
|
|
1600
|
-
if (!unifyTripleTrail(goal0, f)) {
|
|
1601
|
-
undoTo(mark);
|
|
1602
|
-
continue;
|
|
1603
|
-
}
|
|
1604
|
-
const ok = __existsGoals(restGoals, curDepth + 1, visitedNow);
|
|
1605
|
-
undoTo(mark);
|
|
1606
|
-
if (ok) return true;
|
|
1607
|
-
}
|
|
1608
|
-
return false;
|
|
1609
|
-
}
|
|
1610
|
-
|
|
1611
|
-
function __groundGoalSatisfiableKind(goal0, curDepth, visitedNow, visitedForRules) {
|
|
1612
|
-
// Try direct facts first (cheap): if any fact matches, we can treat this as "fact satisfiable"
|
|
1613
|
-
// for visited semantics.
|
|
1614
|
-
if (goal0.p instanceof Iri) {
|
|
1615
|
-
const candidates = candidateFacts(facts, goal0);
|
|
1616
|
-
for (const f of candidates) {
|
|
1617
|
-
const mark = trail.length;
|
|
1618
|
-
const ok = unifyTripleTrail(goal0, f);
|
|
1619
|
-
undoTo(mark);
|
|
1620
|
-
if (ok) return __MEMO_TRUE_FACT;
|
|
1621
|
-
}
|
|
1622
|
-
} else {
|
|
1623
|
-
for (const f of facts) {
|
|
1624
|
-
const mark = trail.length;
|
|
1625
|
-
const ok = unifyTripleTrail(goal0, f);
|
|
1626
|
-
undoTo(mark);
|
|
1627
|
-
if (ok) return __MEMO_TRUE_FACT;
|
|
1628
|
-
}
|
|
1629
|
-
}
|
|
1630
|
-
|
|
1631
|
-
// Otherwise, look for a proof via backward rules.
|
|
1632
|
-
if (goal0.p instanceof Iri) {
|
|
1633
|
-
ensureBackRuleIndexes(backRules);
|
|
1634
|
-
const candRules = (backRules.__byHeadPred.get(goal0.p.value) || []).concat(backRules.__wildHeadPred);
|
|
1635
|
-
|
|
1636
|
-
for (const r of candRules) {
|
|
1637
|
-
if (r.conclusion.length !== 1) continue;
|
|
1638
|
-
const rawHead = r.conclusion[0];
|
|
1639
|
-
if (rawHead.p instanceof Iri && rawHead.p.value !== goal0.p.value) continue;
|
|
1640
|
-
|
|
1641
|
-
const rStd = standardizeRule(r, varGen);
|
|
1642
|
-
const head = rStd.conclusion[0];
|
|
1643
|
-
|
|
1644
|
-
const mark = trail.length;
|
|
1645
|
-
if (!unifyTripleTrail(head, goal0)) {
|
|
1646
|
-
undoTo(mark);
|
|
1647
|
-
continue;
|
|
1648
|
-
}
|
|
1649
|
-
|
|
1650
|
-
const ok = __existsGoals(rStd.premise, curDepth + 1, visitedForRules);
|
|
1651
|
-
undoTo(mark);
|
|
1652
|
-
if (ok) return __MEMO_TRUE_RULE;
|
|
1653
|
-
}
|
|
1654
|
-
}
|
|
1655
|
-
|
|
1656
|
-
return __MEMO_FALSE;
|
|
1657
|
-
}
|
|
1658
|
-
|
|
1659
1452
|
function dfs(goalsNow, curDepth, visitedNow, canDeferBuiltins, deferCount) {
|
|
1660
1453
|
if (results.length >= max) return;
|
|
1661
1454
|
if (!goalsNow.length) {
|
|
@@ -1732,50 +1525,15 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxR
|
|
|
1732
1525
|
if (listHasTriple(visitedNow, goal0)) return;
|
|
1733
1526
|
const visitedForRules = visitedNow.concat([goal0]);
|
|
1734
1527
|
|
|
1735
|
-
// Ground-goal memoization: if a goal is fully ground, it cannot introduce new bindings,
|
|
1736
|
-
// so we can cache whether it is satisfiable and skip re-proving it across branches.
|
|
1737
|
-
const __memoKey0 = __memoKeyTriple(goal0);
|
|
1738
|
-
if (__memoKey0) {
|
|
1739
|
-
const mv = __groundGoalMemo.get(__memoKey0);
|
|
1740
|
-
if (mv === __MEMO_FALSE) return;
|
|
1741
|
-
|
|
1742
|
-
if (mv === __MEMO_TRUE_FACT || mv === __MEMO_TRUE_RULE) {
|
|
1743
|
-
const __nextVisited = mv === __MEMO_TRUE_FACT ? visitedNow : visitedForRules;
|
|
1744
|
-
if (!restGoals.length) {
|
|
1745
|
-
results.push(gcCompactForGoals(substMut, [], answerVars));
|
|
1746
|
-
} else {
|
|
1747
|
-
dfs(restGoals, curDepth + 1, __nextVisited, canDeferBuiltins, 0);
|
|
1748
|
-
}
|
|
1749
|
-
return;
|
|
1750
|
-
}
|
|
1751
|
-
|
|
1752
|
-
// Cycle breaker for recursive ground goals.
|
|
1753
|
-
if (mv === __MEMO_INPROGRESS) return;
|
|
1754
|
-
|
|
1755
|
-
__memoSet(__memoKey0, __MEMO_INPROGRESS);
|
|
1756
|
-
const kind = __groundGoalSatisfiableKind(goal0, curDepth, visitedNow, visitedForRules);
|
|
1757
|
-
__memoSet(__memoKey0, kind);
|
|
1758
|
-
|
|
1759
|
-
if (kind === __MEMO_FALSE) return;
|
|
1760
|
-
|
|
1761
|
-
const __nextVisited = kind === __MEMO_TRUE_FACT ? visitedNow : visitedForRules;
|
|
1762
|
-
if (!restGoals.length) {
|
|
1763
|
-
results.push(gcCompactForGoals(substMut, [], answerVars));
|
|
1764
|
-
} else {
|
|
1765
|
-
dfs(restGoals, curDepth + 1, __nextVisited, canDeferBuiltins, 0);
|
|
1766
|
-
}
|
|
1767
|
-
return;
|
|
1768
|
-
}
|
|
1769
|
-
|
|
1770
1528
|
// 3) Backward rules (indexed by head predicate) — explored first
|
|
1771
1529
|
if (goal0.p instanceof Iri) {
|
|
1772
1530
|
ensureBackRuleIndexes(backRules);
|
|
1773
|
-
const candRules = (backRules.__byHeadPred.get(goal0.p.
|
|
1531
|
+
const candRules = (backRules.__byHeadPred.get(goal0.p.__tid) || []).concat(backRules.__wildHeadPred);
|
|
1774
1532
|
|
|
1775
1533
|
for (const r of candRules) {
|
|
1776
1534
|
if (r.conclusion.length !== 1) continue;
|
|
1777
1535
|
const rawHead = r.conclusion[0];
|
|
1778
|
-
if (rawHead.p instanceof Iri && rawHead.p.
|
|
1536
|
+
if (rawHead.p instanceof Iri && rawHead.p.__tid !== goal0.p.__tid) continue;
|
|
1779
1537
|
|
|
1780
1538
|
const rStd = standardizeRule(r, varGen);
|
|
1781
1539
|
const head = rStd.conclusion[0];
|
|
@@ -1801,7 +1559,8 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxR
|
|
|
1801
1559
|
// 4) Try to satisfy the goal from known facts
|
|
1802
1560
|
if (goal0.p instanceof Iri) {
|
|
1803
1561
|
const candidates = candidateFacts(facts, goal0);
|
|
1804
|
-
for (const
|
|
1562
|
+
for (const idx of candidates) {
|
|
1563
|
+
const f = facts[idx];
|
|
1805
1564
|
const mark = trail.length;
|
|
1806
1565
|
if (!unifyTripleTrail(goal0, f)) {
|
|
1807
1566
|
undoTo(mark);
|