eyeling 1.11.27 → 1.11.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/eyeling.js +199 -186
- package/lib/engine.js +199 -186
- package/package.json +1 -1
package/eyeling.js
CHANGED
|
@@ -4437,6 +4437,8 @@ const { deterministicSkolemIdFromKey } = require('./skolem');
|
|
|
4437
4437
|
|
|
4438
4438
|
const deref = require('./deref');
|
|
4439
4439
|
|
|
4440
|
+
const hasOwn = Object.prototype.hasOwnProperty;
|
|
4441
|
+
|
|
4440
4442
|
let version = 'dev';
|
|
4441
4443
|
try {
|
|
4442
4444
|
// Node: keep package.json version if available
|
|
@@ -4556,6 +4558,191 @@ function __isStrictGroundTriple(tr) {
|
|
|
4556
4558
|
return __isStrictGroundTerm(tr.s) && __isStrictGroundTerm(tr.p) && __isStrictGroundTerm(tr.o);
|
|
4557
4559
|
}
|
|
4558
4560
|
|
|
4561
|
+
// -----------------------------------------------------------------------------
|
|
4562
|
+
// Rule identity / firing keys
|
|
4563
|
+
// -----------------------------------------------------------------------------
|
|
4564
|
+
// Used to maintain O(1) membership sets for dynamically promoted rules, and to
|
|
4565
|
+
// memoize per-firing head-blank skolemization.
|
|
4566
|
+
function __ruleKey(isForward, isFuse, premise, conclusion) {
|
|
4567
|
+
let out = (isForward ? 'F' : 'B') + (isFuse ? '!' : '') + '|P|';
|
|
4568
|
+
for (let i = 0; i < premise.length; i++) {
|
|
4569
|
+
const tr = premise[i];
|
|
4570
|
+
if (i) out += '\n';
|
|
4571
|
+
out += skolemKeyFromTerm(tr.s) + '\t' + skolemKeyFromTerm(tr.p) + '\t' + skolemKeyFromTerm(tr.o);
|
|
4572
|
+
}
|
|
4573
|
+
out += '|C|';
|
|
4574
|
+
for (let i = 0; i < conclusion.length; i++) {
|
|
4575
|
+
const tr = conclusion[i];
|
|
4576
|
+
if (i) out += '\n';
|
|
4577
|
+
out += skolemKeyFromTerm(tr.s) + '\t' + skolemKeyFromTerm(tr.p) + '\t' + skolemKeyFromTerm(tr.o);
|
|
4578
|
+
}
|
|
4579
|
+
return out;
|
|
4580
|
+
}
|
|
4581
|
+
|
|
4582
|
+
function __firingKey(ruleIndex, instantiatedPremises) {
|
|
4583
|
+
// Deterministic key derived from the instantiated body (ground per substitution).
|
|
4584
|
+
let out = `R${ruleIndex}|`;
|
|
4585
|
+
for (let i = 0; i < instantiatedPremises.length; i++) {
|
|
4586
|
+
const tr = instantiatedPremises[i];
|
|
4587
|
+
if (i) out += '\n';
|
|
4588
|
+
out += skolemKeyFromTerm(tr.s) + '\t' + skolemKeyFromTerm(tr.p) + '\t' + skolemKeyFromTerm(tr.o);
|
|
4589
|
+
}
|
|
4590
|
+
return out;
|
|
4591
|
+
}
|
|
4592
|
+
|
|
4593
|
+
// -----------------------------------------------------------------------------
|
|
4594
|
+
// Scoped-closure helpers (log:* builtins)
|
|
4595
|
+
// -----------------------------------------------------------------------------
|
|
4596
|
+
// Parse a 'naturalPriority' used by log:* scoped-closure builtins (e.g., log:collectAllIn).
|
|
4597
|
+
// Accept non-negative integral numeric literals; return null if not parseable.
|
|
4598
|
+
function __logNaturalPriorityFromTerm(t) {
|
|
4599
|
+
if (!(t instanceof Literal)) return null;
|
|
4600
|
+
const info = parseNumericLiteralInfo(t);
|
|
4601
|
+
if (!info) return null;
|
|
4602
|
+
|
|
4603
|
+
if (info.kind === 'integer') {
|
|
4604
|
+
const bi = info.value; // BigInt
|
|
4605
|
+
if (bi < 0n) return null;
|
|
4606
|
+
// clamp to MAX_SAFE_INTEGER (priorities are expected to be small)
|
|
4607
|
+
const max = BigInt(Number.MAX_SAFE_INTEGER);
|
|
4608
|
+
return Number(bi > max ? max : bi);
|
|
4609
|
+
}
|
|
4610
|
+
|
|
4611
|
+
if (info.kind === 'decimal') {
|
|
4612
|
+
const n = info.value; // number
|
|
4613
|
+
if (!Number.isFinite(n)) return null;
|
|
4614
|
+
if (Math.floor(n) !== n) return null;
|
|
4615
|
+
if (n < 0) return null;
|
|
4616
|
+
return n;
|
|
4617
|
+
}
|
|
4618
|
+
|
|
4619
|
+
return null;
|
|
4620
|
+
}
|
|
4621
|
+
|
|
4622
|
+
function __computeMaxScopedClosurePriorityNeeded(forwardRules, backRules) {
|
|
4623
|
+
let maxP = 0;
|
|
4624
|
+
|
|
4625
|
+
function scanTriple(tr) {
|
|
4626
|
+
if (!(tr && tr.p instanceof Iri)) return;
|
|
4627
|
+
const pv = tr.p.value;
|
|
4628
|
+
|
|
4629
|
+
// log:collectAllIn / log:forAllIn use the object position for the priority.
|
|
4630
|
+
if (pv === LOG_NS + 'collectAllIn' || pv === LOG_NS + 'forAllIn') {
|
|
4631
|
+
// Explicit scope graphs are immediate and do not require a closure.
|
|
4632
|
+
if (tr.o instanceof GraphTerm) return;
|
|
4633
|
+
// Variable or non-numeric object => default priority 1 (if used).
|
|
4634
|
+
if (tr.o instanceof Var) {
|
|
4635
|
+
if (maxP < 1) maxP = 1;
|
|
4636
|
+
return;
|
|
4637
|
+
}
|
|
4638
|
+
const p0 = __logNaturalPriorityFromTerm(tr.o);
|
|
4639
|
+
if (p0 !== null) {
|
|
4640
|
+
if (p0 > maxP) maxP = p0;
|
|
4641
|
+
} else {
|
|
4642
|
+
if (maxP < 1) maxP = 1;
|
|
4643
|
+
}
|
|
4644
|
+
return;
|
|
4645
|
+
}
|
|
4646
|
+
|
|
4647
|
+
// log:includes / log:notIncludes use the subject position for the priority.
|
|
4648
|
+
if (pv === LOG_NS + 'includes' || pv === LOG_NS + 'notIncludes') {
|
|
4649
|
+
// Explicit scope graphs are immediate and do not require a closure.
|
|
4650
|
+
if (tr.s instanceof GraphTerm) return;
|
|
4651
|
+
// Variable or non-numeric subject => default priority 1 (if used).
|
|
4652
|
+
if (tr.s instanceof Var) {
|
|
4653
|
+
if (maxP < 1) maxP = 1;
|
|
4654
|
+
return;
|
|
4655
|
+
}
|
|
4656
|
+
const p0 = __logNaturalPriorityFromTerm(tr.s);
|
|
4657
|
+
if (p0 !== null) {
|
|
4658
|
+
if (p0 > maxP) maxP = p0;
|
|
4659
|
+
} else {
|
|
4660
|
+
if (maxP < 1) maxP = 1;
|
|
4661
|
+
}
|
|
4662
|
+
}
|
|
4663
|
+
}
|
|
4664
|
+
|
|
4665
|
+
for (const r of forwardRules) {
|
|
4666
|
+
for (const tr of r.premise) scanTriple(tr);
|
|
4667
|
+
}
|
|
4668
|
+
for (const r of backRules) {
|
|
4669
|
+
for (const tr of r.premise) scanTriple(tr);
|
|
4670
|
+
}
|
|
4671
|
+
return maxP;
|
|
4672
|
+
}
|
|
4673
|
+
|
|
4674
|
+
function __termContainsVarName(t, name) {
|
|
4675
|
+
if (t instanceof Var) return t.name === name;
|
|
4676
|
+
if (t instanceof ListTerm) return t.elems.some((e) => __termContainsVarName(e, name));
|
|
4677
|
+
if (t instanceof OpenListTerm) return t.tailVar === name || t.prefix.some((e) => __termContainsVarName(e, name));
|
|
4678
|
+
if (t instanceof GraphTerm)
|
|
4679
|
+
return t.triples.some(
|
|
4680
|
+
(tr) =>
|
|
4681
|
+
__termContainsVarName(tr.s, name) || __termContainsVarName(tr.p, name) || __termContainsVarName(tr.o, name),
|
|
4682
|
+
);
|
|
4683
|
+
return false;
|
|
4684
|
+
}
|
|
4685
|
+
|
|
4686
|
+
function __varOccursElsewhereInPremise(premise, name, idx, field) {
|
|
4687
|
+
for (let i = 0; i < premise.length; i++) {
|
|
4688
|
+
const tr = premise[i];
|
|
4689
|
+
if (!(tr && tr.s && tr.p && tr.o)) continue;
|
|
4690
|
+
|
|
4691
|
+
// Skip the specific scope/priority occurrence we are analyzing.
|
|
4692
|
+
if (!(i === idx && field === 's') && __termContainsVarName(tr.s, name)) return true;
|
|
4693
|
+
if (!(i === idx && field === 'p') && __termContainsVarName(tr.p, name)) return true;
|
|
4694
|
+
if (!(i === idx && field === 'o') && __termContainsVarName(tr.o, name)) return true;
|
|
4695
|
+
}
|
|
4696
|
+
return false;
|
|
4697
|
+
}
|
|
4698
|
+
|
|
4699
|
+
function __computeForwardRuleScopedSkipInfo(rule) {
|
|
4700
|
+
let needsSnap = false;
|
|
4701
|
+
let requiredLevel = 0;
|
|
4702
|
+
|
|
4703
|
+
for (let i = 0; i < rule.premise.length; i++) {
|
|
4704
|
+
const tr = rule.premise[i];
|
|
4705
|
+
if (!(tr && tr.p instanceof Iri)) continue;
|
|
4706
|
+
const pv = tr.p.value;
|
|
4707
|
+
|
|
4708
|
+
if (pv === LOG_NS + 'collectAllIn' || pv === LOG_NS + 'forAllIn') {
|
|
4709
|
+
if (tr.o instanceof GraphTerm) continue; // explicit scope
|
|
4710
|
+
// If scope term is a Var that appears elsewhere, it might be bound to a GraphTerm.
|
|
4711
|
+
// Be conservative and do not skip in that case.
|
|
4712
|
+
if (tr.o instanceof Var) {
|
|
4713
|
+
if (__varOccursElsewhereInPremise(rule.premise, tr.o.name, i, 'o')) return null;
|
|
4714
|
+
needsSnap = true;
|
|
4715
|
+
requiredLevel = Math.max(requiredLevel, 1);
|
|
4716
|
+
} else {
|
|
4717
|
+
needsSnap = true;
|
|
4718
|
+
let prio = 1;
|
|
4719
|
+
const p0 = __logNaturalPriorityFromTerm(tr.o);
|
|
4720
|
+
if (p0 !== null) prio = p0;
|
|
4721
|
+
requiredLevel = Math.max(requiredLevel, prio);
|
|
4722
|
+
}
|
|
4723
|
+
continue;
|
|
4724
|
+
}
|
|
4725
|
+
|
|
4726
|
+
if (pv === LOG_NS + 'includes' || pv === LOG_NS + 'notIncludes') {
|
|
4727
|
+
if (tr.s instanceof GraphTerm) continue; // explicit scope
|
|
4728
|
+
if (tr.s instanceof Var) {
|
|
4729
|
+
if (__varOccursElsewhereInPremise(rule.premise, tr.s.name, i, 's')) return null;
|
|
4730
|
+
needsSnap = true;
|
|
4731
|
+
requiredLevel = Math.max(requiredLevel, 1);
|
|
4732
|
+
} else {
|
|
4733
|
+
needsSnap = true;
|
|
4734
|
+
let prio = 1;
|
|
4735
|
+
const p0 = __logNaturalPriorityFromTerm(tr.s);
|
|
4736
|
+
if (p0 !== null) prio = p0;
|
|
4737
|
+
requiredLevel = Math.max(requiredLevel, prio);
|
|
4738
|
+
}
|
|
4739
|
+
}
|
|
4740
|
+
}
|
|
4741
|
+
|
|
4742
|
+
if (!needsSnap) return { needsSnap: false, requiredLevel: 0 };
|
|
4743
|
+
return { needsSnap: true, requiredLevel };
|
|
4744
|
+
}
|
|
4745
|
+
|
|
4559
4746
|
// -----------------------------------------------------------------------------
|
|
4560
4747
|
// log:conclusion cache
|
|
4561
4748
|
// -----------------------------------------------------------------------------
|
|
@@ -6008,23 +6195,8 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */)
|
|
|
6008
6195
|
|
|
6009
6196
|
// Speed up dynamic rule promotion by maintaining O(1) membership sets.
|
|
6010
6197
|
// (Some workloads derive many rule-producing triples.)
|
|
6011
|
-
function __ruleKey(isForward, isFuse, premise, conclusion) {
|
|
6012
|
-
let out = (isForward ? 'F' : 'B') + (isFuse ? '!' : '') + '|P|';
|
|
6013
|
-
for (let i = 0; i < premise.length; i++) {
|
|
6014
|
-
const tr = premise[i];
|
|
6015
|
-
if (i) out += '\n';
|
|
6016
|
-
out += skolemKeyFromTerm(tr.s) + '\t' + skolemKeyFromTerm(tr.p) + '\t' + skolemKeyFromTerm(tr.o);
|
|
6017
|
-
}
|
|
6018
|
-
out += '|C|';
|
|
6019
|
-
for (let i = 0; i < conclusion.length; i++) {
|
|
6020
|
-
const tr = conclusion[i];
|
|
6021
|
-
if (i) out += '\n';
|
|
6022
|
-
out += skolemKeyFromTerm(tr.s) + '\t' + skolemKeyFromTerm(tr.p) + '\t' + skolemKeyFromTerm(tr.o);
|
|
6023
|
-
}
|
|
6024
|
-
return out;
|
|
6025
|
-
}
|
|
6026
6198
|
|
|
6027
|
-
if (!
|
|
6199
|
+
if (!hasOwn.call(forwardRules, '__ruleKeySet')) {
|
|
6028
6200
|
Object.defineProperty(forwardRules, '__ruleKeySet', {
|
|
6029
6201
|
value: new Set(forwardRules.map((r) => __ruleKey(r.isForward, r.isFuse, r.premise, r.conclusion))),
|
|
6030
6202
|
enumerable: false,
|
|
@@ -6032,7 +6204,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */)
|
|
|
6032
6204
|
configurable: true,
|
|
6033
6205
|
});
|
|
6034
6206
|
}
|
|
6035
|
-
if (!
|
|
6207
|
+
if (!hasOwn.call(backRules, '__ruleKeySet')) {
|
|
6036
6208
|
Object.defineProperty(backRules, '__ruleKeySet', {
|
|
6037
6209
|
value: new Set(backRules.map((r) => __ruleKey(r.isForward, r.isFuse, r.premise, r.conclusion))),
|
|
6038
6210
|
enumerable: false,
|
|
@@ -6046,18 +6218,6 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */)
|
|
|
6046
6218
|
// rule+substitution instance across outer fixpoint iterations.
|
|
6047
6219
|
const headSkolemCache = new Map();
|
|
6048
6220
|
|
|
6049
|
-
function firingKey(ruleIndex, instantiatedPremises) {
|
|
6050
|
-
// Deterministic key derived from the instantiated body (ground per substitution).
|
|
6051
|
-
// Avoid repeated JSON.stringify of arrays-of-strings (hot path).
|
|
6052
|
-
let out = `R${ruleIndex}|`;
|
|
6053
|
-
for (let i = 0; i < instantiatedPremises.length; i++) {
|
|
6054
|
-
const tr = instantiatedPremises[i];
|
|
6055
|
-
if (i) out += '\n';
|
|
6056
|
-
out += skolemKeyFromTerm(tr.s) + ' ' + skolemKeyFromTerm(tr.p) + ' ' + skolemKeyFromTerm(tr.o);
|
|
6057
|
-
}
|
|
6058
|
-
return out;
|
|
6059
|
-
}
|
|
6060
|
-
|
|
6061
6221
|
// Make rules visible to introspection builtins
|
|
6062
6222
|
backRules.__allForwardRules = forwardRules;
|
|
6063
6223
|
backRules.__allBackwardRules = backRules;
|
|
@@ -6066,161 +6226,14 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */)
|
|
|
6066
6226
|
// Level 0 means "no frozen snapshot" (during Phase A of each outer iteration).
|
|
6067
6227
|
let scopedClosureLevel = 0;
|
|
6068
6228
|
|
|
6069
|
-
// Scan known rules for the maximum requested closure priority in
|
|
6070
|
-
|
|
6071
|
-
function __logNaturalPriorityFromTerm(t) {
|
|
6072
|
-
// Parse a 'naturalPriority' used by log:* scoped-closure builtins (e.g., log:collectAllIn).
|
|
6073
|
-
// Accept non-negative integral numeric literals; return null if not parseable.
|
|
6074
|
-
if (!(t instanceof Literal)) return null;
|
|
6075
|
-
const info = parseNumericLiteralInfo(t);
|
|
6076
|
-
if (!info) return null;
|
|
6077
|
-
if (info.kind === 'integer') {
|
|
6078
|
-
const bi = info.value; // BigInt
|
|
6079
|
-
if (bi < 0n) return null;
|
|
6080
|
-
// clamp to MAX_SAFE_INTEGER (priorities are expected to be small)
|
|
6081
|
-
const max = BigInt(Number.MAX_SAFE_INTEGER);
|
|
6082
|
-
return Number(bi > max ? max : bi);
|
|
6083
|
-
}
|
|
6084
|
-
if (info.kind === 'decimal') {
|
|
6085
|
-
const n = info.value; // number
|
|
6086
|
-
if (!Number.isFinite(n)) return null;
|
|
6087
|
-
if (Math.floor(n) !== n) return null;
|
|
6088
|
-
if (n < 0) return null;
|
|
6089
|
-
return n;
|
|
6090
|
-
}
|
|
6091
|
-
return null;
|
|
6092
|
-
}
|
|
6093
|
-
|
|
6094
|
-
function computeMaxScopedClosurePriorityNeeded() {
|
|
6095
|
-
let maxP = 0;
|
|
6096
|
-
function scanTriple(tr) {
|
|
6097
|
-
if (!(tr && tr.p instanceof Iri)) return;
|
|
6098
|
-
const pv = tr.p.value;
|
|
6099
|
-
|
|
6100
|
-
// log:collectAllIn / log:forAllIn use the object position for the priority.
|
|
6101
|
-
if (pv === LOG_NS + 'collectAllIn' || pv === LOG_NS + 'forAllIn') {
|
|
6102
|
-
// Explicit scope graphs are immediate and do not require a closure.
|
|
6103
|
-
if (tr.o instanceof GraphTerm) return;
|
|
6104
|
-
// Variable or non-numeric object => default priority 1 (if used).
|
|
6105
|
-
if (tr.o instanceof Var) {
|
|
6106
|
-
if (maxP < 1) maxP = 1;
|
|
6107
|
-
return;
|
|
6108
|
-
}
|
|
6109
|
-
const p0 = __logNaturalPriorityFromTerm(tr.o);
|
|
6110
|
-
if (p0 !== null) {
|
|
6111
|
-
if (p0 > maxP) maxP = p0;
|
|
6112
|
-
} else {
|
|
6113
|
-
if (maxP < 1) maxP = 1;
|
|
6114
|
-
}
|
|
6115
|
-
return;
|
|
6116
|
-
}
|
|
6117
|
-
|
|
6118
|
-
// log:includes / log:notIncludes use the subject position for the priority.
|
|
6119
|
-
if (pv === LOG_NS + 'includes' || pv === LOG_NS + 'notIncludes') {
|
|
6120
|
-
// Explicit scope graphs are immediate and do not require a closure.
|
|
6121
|
-
if (tr.s instanceof GraphTerm) return;
|
|
6122
|
-
// Variable or non-numeric subject => default priority 1 (if used).
|
|
6123
|
-
if (tr.s instanceof Var) {
|
|
6124
|
-
if (maxP < 1) maxP = 1;
|
|
6125
|
-
return;
|
|
6126
|
-
}
|
|
6127
|
-
const p0 = __logNaturalPriorityFromTerm(tr.s);
|
|
6128
|
-
if (p0 !== null) {
|
|
6129
|
-
if (p0 > maxP) maxP = p0;
|
|
6130
|
-
} else {
|
|
6131
|
-
if (maxP < 1) maxP = 1;
|
|
6132
|
-
}
|
|
6133
|
-
}
|
|
6134
|
-
}
|
|
6135
|
-
|
|
6136
|
-
for (const r of forwardRules) {
|
|
6137
|
-
for (const tr of r.premise) scanTriple(tr);
|
|
6138
|
-
}
|
|
6139
|
-
for (const r of backRules) {
|
|
6140
|
-
for (const tr of r.premise) scanTriple(tr);
|
|
6141
|
-
}
|
|
6142
|
-
return maxP;
|
|
6143
|
-
}
|
|
6144
|
-
|
|
6145
|
-
let maxScopedClosurePriorityNeeded = computeMaxScopedClosurePriorityNeeded();
|
|
6229
|
+
// Scan known rules for the maximum requested closure priority in scoped log:* goals.
|
|
6230
|
+
let maxScopedClosurePriorityNeeded = __computeMaxScopedClosurePriorityNeeded(forwardRules, backRules);
|
|
6146
6231
|
|
|
6147
6232
|
// Conservative fast-skip for forward rules that cannot possibly succeed
|
|
6148
6233
|
// until a scoped snapshot exists (or a given closure level is reached).
|
|
6149
|
-
//
|
|
6150
|
-
function __termContainsVarName(t, name) {
|
|
6151
|
-
if (t instanceof Var) return t.name === name;
|
|
6152
|
-
if (t instanceof ListTerm) return t.elems.some((e) => __termContainsVarName(e, name));
|
|
6153
|
-
if (t instanceof OpenListTerm) return t.tailVar === name || t.prefix.some((e) => __termContainsVarName(e, name));
|
|
6154
|
-
if (t instanceof GraphTerm)
|
|
6155
|
-
return t.triples.some(
|
|
6156
|
-
(tr) =>
|
|
6157
|
-
__termContainsVarName(tr.s, name) || __termContainsVarName(tr.p, name) || __termContainsVarName(tr.o, name),
|
|
6158
|
-
);
|
|
6159
|
-
return false;
|
|
6160
|
-
}
|
|
6161
|
-
|
|
6162
|
-
function __varOccursElsewhereInPremise(premise, name, idx, field) {
|
|
6163
|
-
for (let i = 0; i < premise.length; i++) {
|
|
6164
|
-
const tr = premise[i];
|
|
6165
|
-
if (!(tr && tr.s && tr.p && tr.o)) continue;
|
|
6166
|
-
|
|
6167
|
-
// Skip the specific scope/priority occurrence we are analyzing.
|
|
6168
|
-
if (!(i === idx && field === 's') && __termContainsVarName(tr.s, name)) return true;
|
|
6169
|
-
if (!(i === idx && field === 'p') && __termContainsVarName(tr.p, name)) return true;
|
|
6170
|
-
if (!(i === idx && field === 'o') && __termContainsVarName(tr.o, name)) return true;
|
|
6171
|
-
}
|
|
6172
|
-
return false;
|
|
6173
|
-
}
|
|
6174
|
-
|
|
6175
|
-
function __computeForwardRuleScopedSkipInfo(rule) {
|
|
6176
|
-
let needsSnap = false;
|
|
6177
|
-
let requiredLevel = 0;
|
|
6178
|
-
|
|
6179
|
-
for (let i = 0; i < rule.premise.length; i++) {
|
|
6180
|
-
const tr = rule.premise[i];
|
|
6181
|
-
if (!(tr && tr.p instanceof Iri)) continue;
|
|
6182
|
-
const pv = tr.p.value;
|
|
6183
|
-
|
|
6184
|
-
if (pv === LOG_NS + 'collectAllIn' || pv === LOG_NS + 'forAllIn') {
|
|
6185
|
-
if (tr.o instanceof GraphTerm) continue; // explicit scope
|
|
6186
|
-
// If scope term is a Var that appears elsewhere, it might be bound to a GraphTerm.
|
|
6187
|
-
// Be conservative and do not skip in that case.
|
|
6188
|
-
if (tr.o instanceof Var) {
|
|
6189
|
-
if (__varOccursElsewhereInPremise(rule.premise, tr.o.name, i, 'o')) return null;
|
|
6190
|
-
needsSnap = true;
|
|
6191
|
-
requiredLevel = Math.max(requiredLevel, 1);
|
|
6192
|
-
} else {
|
|
6193
|
-
needsSnap = true;
|
|
6194
|
-
let prio = 1;
|
|
6195
|
-
const p0 = __logNaturalPriorityFromTerm(tr.o);
|
|
6196
|
-
if (p0 !== null) prio = p0;
|
|
6197
|
-
requiredLevel = Math.max(requiredLevel, prio);
|
|
6198
|
-
}
|
|
6199
|
-
continue;
|
|
6200
|
-
}
|
|
6201
|
-
|
|
6202
|
-
if (pv === LOG_NS + 'includes' || pv === LOG_NS + 'notIncludes') {
|
|
6203
|
-
if (tr.s instanceof GraphTerm) continue; // explicit scope
|
|
6204
|
-
if (tr.s instanceof Var) {
|
|
6205
|
-
if (__varOccursElsewhereInPremise(rule.premise, tr.s.name, i, 's')) return null;
|
|
6206
|
-
needsSnap = true;
|
|
6207
|
-
requiredLevel = Math.max(requiredLevel, 1);
|
|
6208
|
-
} else {
|
|
6209
|
-
needsSnap = true;
|
|
6210
|
-
let prio = 1;
|
|
6211
|
-
const p0 = __logNaturalPriorityFromTerm(tr.s);
|
|
6212
|
-
if (p0 !== null) prio = p0;
|
|
6213
|
-
requiredLevel = Math.max(requiredLevel, prio);
|
|
6214
|
-
}
|
|
6215
|
-
}
|
|
6216
|
-
}
|
|
6217
|
-
|
|
6218
|
-
if (!needsSnap) return { needsSnap: false, requiredLevel: 0 };
|
|
6219
|
-
return { needsSnap: true, requiredLevel };
|
|
6220
|
-
}
|
|
6221
|
-
|
|
6234
|
+
// Helper functions are module-scoped: __computeForwardRuleScopedSkipInfo, etc.
|
|
6222
6235
|
function setScopedSnapshot(snap, level) {
|
|
6223
|
-
if (!
|
|
6236
|
+
if (!hasOwn.call(facts, '__scopedSnapshot')) {
|
|
6224
6237
|
Object.defineProperty(facts, '__scopedSnapshot', {
|
|
6225
6238
|
value: snap,
|
|
6226
6239
|
enumerable: false,
|
|
@@ -6231,7 +6244,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */)
|
|
|
6231
6244
|
facts.__scopedSnapshot = snap;
|
|
6232
6245
|
}
|
|
6233
6246
|
|
|
6234
|
-
if (!
|
|
6247
|
+
if (!hasOwn.call(facts, '__scopedClosureLevel')) {
|
|
6235
6248
|
Object.defineProperty(facts, '__scopedClosureLevel', {
|
|
6236
6249
|
value: level,
|
|
6237
6250
|
enumerable: false,
|
|
@@ -6276,7 +6289,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */)
|
|
|
6276
6289
|
// until a snapshot exists (and a certain closure level is reached).
|
|
6277
6290
|
// This prevents expensive proofs that will definitely fail in Phase A
|
|
6278
6291
|
// and in early closure levels.
|
|
6279
|
-
if (!
|
|
6292
|
+
if (!hasOwn.call(r, '__scopedSkipInfo')) {
|
|
6280
6293
|
const info = __computeForwardRuleScopedSkipInfo(r);
|
|
6281
6294
|
Object.defineProperty(r, '__scopedSkipInfo', {
|
|
6282
6295
|
value: info,
|
|
@@ -6300,7 +6313,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */)
|
|
|
6300
6313
|
// quoted formulas) and has no head blanks, then the head does not depend on which body
|
|
6301
6314
|
// solution we pick. In that case, we only need *one* proof of the body, and once all head
|
|
6302
6315
|
// triples are already known we can skip proving the body entirely.
|
|
6303
|
-
if (!
|
|
6316
|
+
if (!hasOwn.call(r, '__headIsStrictGround')) {
|
|
6304
6317
|
let strict = true;
|
|
6305
6318
|
if (r.isFuse) strict = false;
|
|
6306
6319
|
else if (r.headBlankLabels && r.headBlankLabels.size) strict = false;
|
|
@@ -6353,7 +6366,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */)
|
|
|
6353
6366
|
// IMPORTANT: one skolem map per *rule firing*
|
|
6354
6367
|
const skMap = {};
|
|
6355
6368
|
const instantiatedPremises = r.premise.map((b) => applySubstTriple(b, s));
|
|
6356
|
-
const fireKey =
|
|
6369
|
+
const fireKey = __firingKey(i, instantiatedPremises);
|
|
6357
6370
|
|
|
6358
6371
|
for (const cpat of r.conclusion) {
|
|
6359
6372
|
const instantiated = applySubstTriple(cpat, s);
|
|
@@ -6474,7 +6487,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */)
|
|
|
6474
6487
|
// introducing scoped builtins and/or higher closure priorities.
|
|
6475
6488
|
maxScopedClosurePriorityNeeded = Math.max(
|
|
6476
6489
|
maxScopedClosurePriorityNeeded,
|
|
6477
|
-
|
|
6490
|
+
__computeMaxScopedClosurePriorityNeeded(forwardRules, backRules),
|
|
6478
6491
|
);
|
|
6479
6492
|
|
|
6480
6493
|
// If there are no scoped builtins in the entire program, Phase B is pure
|
|
@@ -6493,7 +6506,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */)
|
|
|
6493
6506
|
// Phase B can also derive rule-producing triples.
|
|
6494
6507
|
maxScopedClosurePriorityNeeded = Math.max(
|
|
6495
6508
|
maxScopedClosurePriorityNeeded,
|
|
6496
|
-
|
|
6509
|
+
__computeMaxScopedClosurePriorityNeeded(forwardRules, backRules),
|
|
6497
6510
|
);
|
|
6498
6511
|
|
|
6499
6512
|
if (!changedA && !changedB && scopedClosureLevel >= maxScopedClosurePriorityNeeded) break;
|
package/lib/engine.js
CHANGED
|
@@ -60,6 +60,8 @@ const { deterministicSkolemIdFromKey } = require('./skolem');
|
|
|
60
60
|
|
|
61
61
|
const deref = require('./deref');
|
|
62
62
|
|
|
63
|
+
const hasOwn = Object.prototype.hasOwnProperty;
|
|
64
|
+
|
|
63
65
|
let version = 'dev';
|
|
64
66
|
try {
|
|
65
67
|
// Node: keep package.json version if available
|
|
@@ -179,6 +181,191 @@ function __isStrictGroundTriple(tr) {
|
|
|
179
181
|
return __isStrictGroundTerm(tr.s) && __isStrictGroundTerm(tr.p) && __isStrictGroundTerm(tr.o);
|
|
180
182
|
}
|
|
181
183
|
|
|
184
|
+
// -----------------------------------------------------------------------------
|
|
185
|
+
// Rule identity / firing keys
|
|
186
|
+
// -----------------------------------------------------------------------------
|
|
187
|
+
// Used to maintain O(1) membership sets for dynamically promoted rules, and to
|
|
188
|
+
// memoize per-firing head-blank skolemization.
|
|
189
|
+
function __ruleKey(isForward, isFuse, premise, conclusion) {
|
|
190
|
+
let out = (isForward ? 'F' : 'B') + (isFuse ? '!' : '') + '|P|';
|
|
191
|
+
for (let i = 0; i < premise.length; i++) {
|
|
192
|
+
const tr = premise[i];
|
|
193
|
+
if (i) out += '\n';
|
|
194
|
+
out += skolemKeyFromTerm(tr.s) + '\t' + skolemKeyFromTerm(tr.p) + '\t' + skolemKeyFromTerm(tr.o);
|
|
195
|
+
}
|
|
196
|
+
out += '|C|';
|
|
197
|
+
for (let i = 0; i < conclusion.length; i++) {
|
|
198
|
+
const tr = conclusion[i];
|
|
199
|
+
if (i) out += '\n';
|
|
200
|
+
out += skolemKeyFromTerm(tr.s) + '\t' + skolemKeyFromTerm(tr.p) + '\t' + skolemKeyFromTerm(tr.o);
|
|
201
|
+
}
|
|
202
|
+
return out;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function __firingKey(ruleIndex, instantiatedPremises) {
|
|
206
|
+
// Deterministic key derived from the instantiated body (ground per substitution).
|
|
207
|
+
let out = `R${ruleIndex}|`;
|
|
208
|
+
for (let i = 0; i < instantiatedPremises.length; i++) {
|
|
209
|
+
const tr = instantiatedPremises[i];
|
|
210
|
+
if (i) out += '\n';
|
|
211
|
+
out += skolemKeyFromTerm(tr.s) + '\t' + skolemKeyFromTerm(tr.p) + '\t' + skolemKeyFromTerm(tr.o);
|
|
212
|
+
}
|
|
213
|
+
return out;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// -----------------------------------------------------------------------------
|
|
217
|
+
// Scoped-closure helpers (log:* builtins)
|
|
218
|
+
// -----------------------------------------------------------------------------
|
|
219
|
+
// Parse a 'naturalPriority' used by log:* scoped-closure builtins (e.g., log:collectAllIn).
|
|
220
|
+
// Accept non-negative integral numeric literals; return null if not parseable.
|
|
221
|
+
function __logNaturalPriorityFromTerm(t) {
|
|
222
|
+
if (!(t instanceof Literal)) return null;
|
|
223
|
+
const info = parseNumericLiteralInfo(t);
|
|
224
|
+
if (!info) return null;
|
|
225
|
+
|
|
226
|
+
if (info.kind === 'integer') {
|
|
227
|
+
const bi = info.value; // BigInt
|
|
228
|
+
if (bi < 0n) return null;
|
|
229
|
+
// clamp to MAX_SAFE_INTEGER (priorities are expected to be small)
|
|
230
|
+
const max = BigInt(Number.MAX_SAFE_INTEGER);
|
|
231
|
+
return Number(bi > max ? max : bi);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (info.kind === 'decimal') {
|
|
235
|
+
const n = info.value; // number
|
|
236
|
+
if (!Number.isFinite(n)) return null;
|
|
237
|
+
if (Math.floor(n) !== n) return null;
|
|
238
|
+
if (n < 0) return null;
|
|
239
|
+
return n;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function __computeMaxScopedClosurePriorityNeeded(forwardRules, backRules) {
|
|
246
|
+
let maxP = 0;
|
|
247
|
+
|
|
248
|
+
function scanTriple(tr) {
|
|
249
|
+
if (!(tr && tr.p instanceof Iri)) return;
|
|
250
|
+
const pv = tr.p.value;
|
|
251
|
+
|
|
252
|
+
// log:collectAllIn / log:forAllIn use the object position for the priority.
|
|
253
|
+
if (pv === LOG_NS + 'collectAllIn' || pv === LOG_NS + 'forAllIn') {
|
|
254
|
+
// Explicit scope graphs are immediate and do not require a closure.
|
|
255
|
+
if (tr.o instanceof GraphTerm) return;
|
|
256
|
+
// Variable or non-numeric object => default priority 1 (if used).
|
|
257
|
+
if (tr.o instanceof Var) {
|
|
258
|
+
if (maxP < 1) maxP = 1;
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const p0 = __logNaturalPriorityFromTerm(tr.o);
|
|
262
|
+
if (p0 !== null) {
|
|
263
|
+
if (p0 > maxP) maxP = p0;
|
|
264
|
+
} else {
|
|
265
|
+
if (maxP < 1) maxP = 1;
|
|
266
|
+
}
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// log:includes / log:notIncludes use the subject position for the priority.
|
|
271
|
+
if (pv === LOG_NS + 'includes' || pv === LOG_NS + 'notIncludes') {
|
|
272
|
+
// Explicit scope graphs are immediate and do not require a closure.
|
|
273
|
+
if (tr.s instanceof GraphTerm) return;
|
|
274
|
+
// Variable or non-numeric subject => default priority 1 (if used).
|
|
275
|
+
if (tr.s instanceof Var) {
|
|
276
|
+
if (maxP < 1) maxP = 1;
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
const p0 = __logNaturalPriorityFromTerm(tr.s);
|
|
280
|
+
if (p0 !== null) {
|
|
281
|
+
if (p0 > maxP) maxP = p0;
|
|
282
|
+
} else {
|
|
283
|
+
if (maxP < 1) maxP = 1;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
for (const r of forwardRules) {
|
|
289
|
+
for (const tr of r.premise) scanTriple(tr);
|
|
290
|
+
}
|
|
291
|
+
for (const r of backRules) {
|
|
292
|
+
for (const tr of r.premise) scanTriple(tr);
|
|
293
|
+
}
|
|
294
|
+
return maxP;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function __termContainsVarName(t, name) {
|
|
298
|
+
if (t instanceof Var) return t.name === name;
|
|
299
|
+
if (t instanceof ListTerm) return t.elems.some((e) => __termContainsVarName(e, name));
|
|
300
|
+
if (t instanceof OpenListTerm) return t.tailVar === name || t.prefix.some((e) => __termContainsVarName(e, name));
|
|
301
|
+
if (t instanceof GraphTerm)
|
|
302
|
+
return t.triples.some(
|
|
303
|
+
(tr) =>
|
|
304
|
+
__termContainsVarName(tr.s, name) || __termContainsVarName(tr.p, name) || __termContainsVarName(tr.o, name),
|
|
305
|
+
);
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function __varOccursElsewhereInPremise(premise, name, idx, field) {
|
|
310
|
+
for (let i = 0; i < premise.length; i++) {
|
|
311
|
+
const tr = premise[i];
|
|
312
|
+
if (!(tr && tr.s && tr.p && tr.o)) continue;
|
|
313
|
+
|
|
314
|
+
// Skip the specific scope/priority occurrence we are analyzing.
|
|
315
|
+
if (!(i === idx && field === 's') && __termContainsVarName(tr.s, name)) return true;
|
|
316
|
+
if (!(i === idx && field === 'p') && __termContainsVarName(tr.p, name)) return true;
|
|
317
|
+
if (!(i === idx && field === 'o') && __termContainsVarName(tr.o, name)) return true;
|
|
318
|
+
}
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function __computeForwardRuleScopedSkipInfo(rule) {
|
|
323
|
+
let needsSnap = false;
|
|
324
|
+
let requiredLevel = 0;
|
|
325
|
+
|
|
326
|
+
for (let i = 0; i < rule.premise.length; i++) {
|
|
327
|
+
const tr = rule.premise[i];
|
|
328
|
+
if (!(tr && tr.p instanceof Iri)) continue;
|
|
329
|
+
const pv = tr.p.value;
|
|
330
|
+
|
|
331
|
+
if (pv === LOG_NS + 'collectAllIn' || pv === LOG_NS + 'forAllIn') {
|
|
332
|
+
if (tr.o instanceof GraphTerm) continue; // explicit scope
|
|
333
|
+
// If scope term is a Var that appears elsewhere, it might be bound to a GraphTerm.
|
|
334
|
+
// Be conservative and do not skip in that case.
|
|
335
|
+
if (tr.o instanceof Var) {
|
|
336
|
+
if (__varOccursElsewhereInPremise(rule.premise, tr.o.name, i, 'o')) return null;
|
|
337
|
+
needsSnap = true;
|
|
338
|
+
requiredLevel = Math.max(requiredLevel, 1);
|
|
339
|
+
} else {
|
|
340
|
+
needsSnap = true;
|
|
341
|
+
let prio = 1;
|
|
342
|
+
const p0 = __logNaturalPriorityFromTerm(tr.o);
|
|
343
|
+
if (p0 !== null) prio = p0;
|
|
344
|
+
requiredLevel = Math.max(requiredLevel, prio);
|
|
345
|
+
}
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (pv === LOG_NS + 'includes' || pv === LOG_NS + 'notIncludes') {
|
|
350
|
+
if (tr.s instanceof GraphTerm) continue; // explicit scope
|
|
351
|
+
if (tr.s instanceof Var) {
|
|
352
|
+
if (__varOccursElsewhereInPremise(rule.premise, tr.s.name, i, 's')) return null;
|
|
353
|
+
needsSnap = true;
|
|
354
|
+
requiredLevel = Math.max(requiredLevel, 1);
|
|
355
|
+
} else {
|
|
356
|
+
needsSnap = true;
|
|
357
|
+
let prio = 1;
|
|
358
|
+
const p0 = __logNaturalPriorityFromTerm(tr.s);
|
|
359
|
+
if (p0 !== null) prio = p0;
|
|
360
|
+
requiredLevel = Math.max(requiredLevel, prio);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (!needsSnap) return { needsSnap: false, requiredLevel: 0 };
|
|
366
|
+
return { needsSnap: true, requiredLevel };
|
|
367
|
+
}
|
|
368
|
+
|
|
182
369
|
// -----------------------------------------------------------------------------
|
|
183
370
|
// log:conclusion cache
|
|
184
371
|
// -----------------------------------------------------------------------------
|
|
@@ -1631,23 +1818,8 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */)
|
|
|
1631
1818
|
|
|
1632
1819
|
// Speed up dynamic rule promotion by maintaining O(1) membership sets.
|
|
1633
1820
|
// (Some workloads derive many rule-producing triples.)
|
|
1634
|
-
function __ruleKey(isForward, isFuse, premise, conclusion) {
|
|
1635
|
-
let out = (isForward ? 'F' : 'B') + (isFuse ? '!' : '') + '|P|';
|
|
1636
|
-
for (let i = 0; i < premise.length; i++) {
|
|
1637
|
-
const tr = premise[i];
|
|
1638
|
-
if (i) out += '\n';
|
|
1639
|
-
out += skolemKeyFromTerm(tr.s) + '\t' + skolemKeyFromTerm(tr.p) + '\t' + skolemKeyFromTerm(tr.o);
|
|
1640
|
-
}
|
|
1641
|
-
out += '|C|';
|
|
1642
|
-
for (let i = 0; i < conclusion.length; i++) {
|
|
1643
|
-
const tr = conclusion[i];
|
|
1644
|
-
if (i) out += '\n';
|
|
1645
|
-
out += skolemKeyFromTerm(tr.s) + '\t' + skolemKeyFromTerm(tr.p) + '\t' + skolemKeyFromTerm(tr.o);
|
|
1646
|
-
}
|
|
1647
|
-
return out;
|
|
1648
|
-
}
|
|
1649
1821
|
|
|
1650
|
-
if (!
|
|
1822
|
+
if (!hasOwn.call(forwardRules, '__ruleKeySet')) {
|
|
1651
1823
|
Object.defineProperty(forwardRules, '__ruleKeySet', {
|
|
1652
1824
|
value: new Set(forwardRules.map((r) => __ruleKey(r.isForward, r.isFuse, r.premise, r.conclusion))),
|
|
1653
1825
|
enumerable: false,
|
|
@@ -1655,7 +1827,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */)
|
|
|
1655
1827
|
configurable: true,
|
|
1656
1828
|
});
|
|
1657
1829
|
}
|
|
1658
|
-
if (!
|
|
1830
|
+
if (!hasOwn.call(backRules, '__ruleKeySet')) {
|
|
1659
1831
|
Object.defineProperty(backRules, '__ruleKeySet', {
|
|
1660
1832
|
value: new Set(backRules.map((r) => __ruleKey(r.isForward, r.isFuse, r.premise, r.conclusion))),
|
|
1661
1833
|
enumerable: false,
|
|
@@ -1669,18 +1841,6 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */)
|
|
|
1669
1841
|
// rule+substitution instance across outer fixpoint iterations.
|
|
1670
1842
|
const headSkolemCache = new Map();
|
|
1671
1843
|
|
|
1672
|
-
function firingKey(ruleIndex, instantiatedPremises) {
|
|
1673
|
-
// Deterministic key derived from the instantiated body (ground per substitution).
|
|
1674
|
-
// Avoid repeated JSON.stringify of arrays-of-strings (hot path).
|
|
1675
|
-
let out = `R${ruleIndex}|`;
|
|
1676
|
-
for (let i = 0; i < instantiatedPremises.length; i++) {
|
|
1677
|
-
const tr = instantiatedPremises[i];
|
|
1678
|
-
if (i) out += '\n';
|
|
1679
|
-
out += skolemKeyFromTerm(tr.s) + ' ' + skolemKeyFromTerm(tr.p) + ' ' + skolemKeyFromTerm(tr.o);
|
|
1680
|
-
}
|
|
1681
|
-
return out;
|
|
1682
|
-
}
|
|
1683
|
-
|
|
1684
1844
|
// Make rules visible to introspection builtins
|
|
1685
1845
|
backRules.__allForwardRules = forwardRules;
|
|
1686
1846
|
backRules.__allBackwardRules = backRules;
|
|
@@ -1689,161 +1849,14 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */)
|
|
|
1689
1849
|
// Level 0 means "no frozen snapshot" (during Phase A of each outer iteration).
|
|
1690
1850
|
let scopedClosureLevel = 0;
|
|
1691
1851
|
|
|
1692
|
-
// Scan known rules for the maximum requested closure priority in
|
|
1693
|
-
|
|
1694
|
-
function __logNaturalPriorityFromTerm(t) {
|
|
1695
|
-
// Parse a 'naturalPriority' used by log:* scoped-closure builtins (e.g., log:collectAllIn).
|
|
1696
|
-
// Accept non-negative integral numeric literals; return null if not parseable.
|
|
1697
|
-
if (!(t instanceof Literal)) return null;
|
|
1698
|
-
const info = parseNumericLiteralInfo(t);
|
|
1699
|
-
if (!info) return null;
|
|
1700
|
-
if (info.kind === 'integer') {
|
|
1701
|
-
const bi = info.value; // BigInt
|
|
1702
|
-
if (bi < 0n) return null;
|
|
1703
|
-
// clamp to MAX_SAFE_INTEGER (priorities are expected to be small)
|
|
1704
|
-
const max = BigInt(Number.MAX_SAFE_INTEGER);
|
|
1705
|
-
return Number(bi > max ? max : bi);
|
|
1706
|
-
}
|
|
1707
|
-
if (info.kind === 'decimal') {
|
|
1708
|
-
const n = info.value; // number
|
|
1709
|
-
if (!Number.isFinite(n)) return null;
|
|
1710
|
-
if (Math.floor(n) !== n) return null;
|
|
1711
|
-
if (n < 0) return null;
|
|
1712
|
-
return n;
|
|
1713
|
-
}
|
|
1714
|
-
return null;
|
|
1715
|
-
}
|
|
1716
|
-
|
|
1717
|
-
function computeMaxScopedClosurePriorityNeeded() {
|
|
1718
|
-
let maxP = 0;
|
|
1719
|
-
function scanTriple(tr) {
|
|
1720
|
-
if (!(tr && tr.p instanceof Iri)) return;
|
|
1721
|
-
const pv = tr.p.value;
|
|
1722
|
-
|
|
1723
|
-
// log:collectAllIn / log:forAllIn use the object position for the priority.
|
|
1724
|
-
if (pv === LOG_NS + 'collectAllIn' || pv === LOG_NS + 'forAllIn') {
|
|
1725
|
-
// Explicit scope graphs are immediate and do not require a closure.
|
|
1726
|
-
if (tr.o instanceof GraphTerm) return;
|
|
1727
|
-
// Variable or non-numeric object => default priority 1 (if used).
|
|
1728
|
-
if (tr.o instanceof Var) {
|
|
1729
|
-
if (maxP < 1) maxP = 1;
|
|
1730
|
-
return;
|
|
1731
|
-
}
|
|
1732
|
-
const p0 = __logNaturalPriorityFromTerm(tr.o);
|
|
1733
|
-
if (p0 !== null) {
|
|
1734
|
-
if (p0 > maxP) maxP = p0;
|
|
1735
|
-
} else {
|
|
1736
|
-
if (maxP < 1) maxP = 1;
|
|
1737
|
-
}
|
|
1738
|
-
return;
|
|
1739
|
-
}
|
|
1740
|
-
|
|
1741
|
-
// log:includes / log:notIncludes use the subject position for the priority.
|
|
1742
|
-
if (pv === LOG_NS + 'includes' || pv === LOG_NS + 'notIncludes') {
|
|
1743
|
-
// Explicit scope graphs are immediate and do not require a closure.
|
|
1744
|
-
if (tr.s instanceof GraphTerm) return;
|
|
1745
|
-
// Variable or non-numeric subject => default priority 1 (if used).
|
|
1746
|
-
if (tr.s instanceof Var) {
|
|
1747
|
-
if (maxP < 1) maxP = 1;
|
|
1748
|
-
return;
|
|
1749
|
-
}
|
|
1750
|
-
const p0 = __logNaturalPriorityFromTerm(tr.s);
|
|
1751
|
-
if (p0 !== null) {
|
|
1752
|
-
if (p0 > maxP) maxP = p0;
|
|
1753
|
-
} else {
|
|
1754
|
-
if (maxP < 1) maxP = 1;
|
|
1755
|
-
}
|
|
1756
|
-
}
|
|
1757
|
-
}
|
|
1758
|
-
|
|
1759
|
-
for (const r of forwardRules) {
|
|
1760
|
-
for (const tr of r.premise) scanTriple(tr);
|
|
1761
|
-
}
|
|
1762
|
-
for (const r of backRules) {
|
|
1763
|
-
for (const tr of r.premise) scanTriple(tr);
|
|
1764
|
-
}
|
|
1765
|
-
return maxP;
|
|
1766
|
-
}
|
|
1767
|
-
|
|
1768
|
-
let maxScopedClosurePriorityNeeded = computeMaxScopedClosurePriorityNeeded();
|
|
1852
|
+
// Scan known rules for the maximum requested closure priority in scoped log:* goals.
|
|
1853
|
+
let maxScopedClosurePriorityNeeded = __computeMaxScopedClosurePriorityNeeded(forwardRules, backRules);
|
|
1769
1854
|
|
|
1770
1855
|
// Conservative fast-skip for forward rules that cannot possibly succeed
|
|
1771
1856
|
// until a scoped snapshot exists (or a given closure level is reached).
|
|
1772
|
-
//
|
|
1773
|
-
function __termContainsVarName(t, name) {
|
|
1774
|
-
if (t instanceof Var) return t.name === name;
|
|
1775
|
-
if (t instanceof ListTerm) return t.elems.some((e) => __termContainsVarName(e, name));
|
|
1776
|
-
if (t instanceof OpenListTerm) return t.tailVar === name || t.prefix.some((e) => __termContainsVarName(e, name));
|
|
1777
|
-
if (t instanceof GraphTerm)
|
|
1778
|
-
return t.triples.some(
|
|
1779
|
-
(tr) =>
|
|
1780
|
-
__termContainsVarName(tr.s, name) || __termContainsVarName(tr.p, name) || __termContainsVarName(tr.o, name),
|
|
1781
|
-
);
|
|
1782
|
-
return false;
|
|
1783
|
-
}
|
|
1784
|
-
|
|
1785
|
-
function __varOccursElsewhereInPremise(premise, name, idx, field) {
|
|
1786
|
-
for (let i = 0; i < premise.length; i++) {
|
|
1787
|
-
const tr = premise[i];
|
|
1788
|
-
if (!(tr && tr.s && tr.p && tr.o)) continue;
|
|
1789
|
-
|
|
1790
|
-
// Skip the specific scope/priority occurrence we are analyzing.
|
|
1791
|
-
if (!(i === idx && field === 's') && __termContainsVarName(tr.s, name)) return true;
|
|
1792
|
-
if (!(i === idx && field === 'p') && __termContainsVarName(tr.p, name)) return true;
|
|
1793
|
-
if (!(i === idx && field === 'o') && __termContainsVarName(tr.o, name)) return true;
|
|
1794
|
-
}
|
|
1795
|
-
return false;
|
|
1796
|
-
}
|
|
1797
|
-
|
|
1798
|
-
function __computeForwardRuleScopedSkipInfo(rule) {
|
|
1799
|
-
let needsSnap = false;
|
|
1800
|
-
let requiredLevel = 0;
|
|
1801
|
-
|
|
1802
|
-
for (let i = 0; i < rule.premise.length; i++) {
|
|
1803
|
-
const tr = rule.premise[i];
|
|
1804
|
-
if (!(tr && tr.p instanceof Iri)) continue;
|
|
1805
|
-
const pv = tr.p.value;
|
|
1806
|
-
|
|
1807
|
-
if (pv === LOG_NS + 'collectAllIn' || pv === LOG_NS + 'forAllIn') {
|
|
1808
|
-
if (tr.o instanceof GraphTerm) continue; // explicit scope
|
|
1809
|
-
// If scope term is a Var that appears elsewhere, it might be bound to a GraphTerm.
|
|
1810
|
-
// Be conservative and do not skip in that case.
|
|
1811
|
-
if (tr.o instanceof Var) {
|
|
1812
|
-
if (__varOccursElsewhereInPremise(rule.premise, tr.o.name, i, 'o')) return null;
|
|
1813
|
-
needsSnap = true;
|
|
1814
|
-
requiredLevel = Math.max(requiredLevel, 1);
|
|
1815
|
-
} else {
|
|
1816
|
-
needsSnap = true;
|
|
1817
|
-
let prio = 1;
|
|
1818
|
-
const p0 = __logNaturalPriorityFromTerm(tr.o);
|
|
1819
|
-
if (p0 !== null) prio = p0;
|
|
1820
|
-
requiredLevel = Math.max(requiredLevel, prio);
|
|
1821
|
-
}
|
|
1822
|
-
continue;
|
|
1823
|
-
}
|
|
1824
|
-
|
|
1825
|
-
if (pv === LOG_NS + 'includes' || pv === LOG_NS + 'notIncludes') {
|
|
1826
|
-
if (tr.s instanceof GraphTerm) continue; // explicit scope
|
|
1827
|
-
if (tr.s instanceof Var) {
|
|
1828
|
-
if (__varOccursElsewhereInPremise(rule.premise, tr.s.name, i, 's')) return null;
|
|
1829
|
-
needsSnap = true;
|
|
1830
|
-
requiredLevel = Math.max(requiredLevel, 1);
|
|
1831
|
-
} else {
|
|
1832
|
-
needsSnap = true;
|
|
1833
|
-
let prio = 1;
|
|
1834
|
-
const p0 = __logNaturalPriorityFromTerm(tr.s);
|
|
1835
|
-
if (p0 !== null) prio = p0;
|
|
1836
|
-
requiredLevel = Math.max(requiredLevel, prio);
|
|
1837
|
-
}
|
|
1838
|
-
}
|
|
1839
|
-
}
|
|
1840
|
-
|
|
1841
|
-
if (!needsSnap) return { needsSnap: false, requiredLevel: 0 };
|
|
1842
|
-
return { needsSnap: true, requiredLevel };
|
|
1843
|
-
}
|
|
1844
|
-
|
|
1857
|
+
// Helper functions are module-scoped: __computeForwardRuleScopedSkipInfo, etc.
|
|
1845
1858
|
function setScopedSnapshot(snap, level) {
|
|
1846
|
-
if (!
|
|
1859
|
+
if (!hasOwn.call(facts, '__scopedSnapshot')) {
|
|
1847
1860
|
Object.defineProperty(facts, '__scopedSnapshot', {
|
|
1848
1861
|
value: snap,
|
|
1849
1862
|
enumerable: false,
|
|
@@ -1854,7 +1867,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */)
|
|
|
1854
1867
|
facts.__scopedSnapshot = snap;
|
|
1855
1868
|
}
|
|
1856
1869
|
|
|
1857
|
-
if (!
|
|
1870
|
+
if (!hasOwn.call(facts, '__scopedClosureLevel')) {
|
|
1858
1871
|
Object.defineProperty(facts, '__scopedClosureLevel', {
|
|
1859
1872
|
value: level,
|
|
1860
1873
|
enumerable: false,
|
|
@@ -1899,7 +1912,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */)
|
|
|
1899
1912
|
// until a snapshot exists (and a certain closure level is reached).
|
|
1900
1913
|
// This prevents expensive proofs that will definitely fail in Phase A
|
|
1901
1914
|
// and in early closure levels.
|
|
1902
|
-
if (!
|
|
1915
|
+
if (!hasOwn.call(r, '__scopedSkipInfo')) {
|
|
1903
1916
|
const info = __computeForwardRuleScopedSkipInfo(r);
|
|
1904
1917
|
Object.defineProperty(r, '__scopedSkipInfo', {
|
|
1905
1918
|
value: info,
|
|
@@ -1923,7 +1936,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */)
|
|
|
1923
1936
|
// quoted formulas) and has no head blanks, then the head does not depend on which body
|
|
1924
1937
|
// solution we pick. In that case, we only need *one* proof of the body, and once all head
|
|
1925
1938
|
// triples are already known we can skip proving the body entirely.
|
|
1926
|
-
if (!
|
|
1939
|
+
if (!hasOwn.call(r, '__headIsStrictGround')) {
|
|
1927
1940
|
let strict = true;
|
|
1928
1941
|
if (r.isFuse) strict = false;
|
|
1929
1942
|
else if (r.headBlankLabels && r.headBlankLabels.size) strict = false;
|
|
@@ -1976,7 +1989,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */)
|
|
|
1976
1989
|
// IMPORTANT: one skolem map per *rule firing*
|
|
1977
1990
|
const skMap = {};
|
|
1978
1991
|
const instantiatedPremises = r.premise.map((b) => applySubstTriple(b, s));
|
|
1979
|
-
const fireKey =
|
|
1992
|
+
const fireKey = __firingKey(i, instantiatedPremises);
|
|
1980
1993
|
|
|
1981
1994
|
for (const cpat of r.conclusion) {
|
|
1982
1995
|
const instantiated = applySubstTriple(cpat, s);
|
|
@@ -2097,7 +2110,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */)
|
|
|
2097
2110
|
// introducing scoped builtins and/or higher closure priorities.
|
|
2098
2111
|
maxScopedClosurePriorityNeeded = Math.max(
|
|
2099
2112
|
maxScopedClosurePriorityNeeded,
|
|
2100
|
-
|
|
2113
|
+
__computeMaxScopedClosurePriorityNeeded(forwardRules, backRules),
|
|
2101
2114
|
);
|
|
2102
2115
|
|
|
2103
2116
|
// If there are no scoped builtins in the entire program, Phase B is pure
|
|
@@ -2116,7 +2129,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */)
|
|
|
2116
2129
|
// Phase B can also derive rule-producing triples.
|
|
2117
2130
|
maxScopedClosurePriorityNeeded = Math.max(
|
|
2118
2131
|
maxScopedClosurePriorityNeeded,
|
|
2119
|
-
|
|
2132
|
+
__computeMaxScopedClosurePriorityNeeded(forwardRules, backRules),
|
|
2120
2133
|
);
|
|
2121
2134
|
|
|
2122
2135
|
if (!changedA && !changedB && scopedClosureLevel >= maxScopedClosurePriorityNeeded) break;
|