eyeling 1.25.0 → 1.25.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/eyeling.browser.js +333 -110
- package/eyeling.js +333 -110
- package/lib/cli.js +1 -1
- package/lib/engine.js +131 -41
- package/lib/lexer.js +143 -49
- package/lib/parser.js +24 -12
- package/lib/prelude.js +34 -7
- package/package.json +1 -1
package/lib/cli.js
CHANGED
package/lib/engine.js
CHANGED
|
@@ -362,6 +362,14 @@ function __prepareForwardRule(r) {
|
|
|
362
362
|
configurable: true,
|
|
363
363
|
});
|
|
364
364
|
}
|
|
365
|
+
if (!hasOwn.call(r, '__needsForwardSkipCheck')) {
|
|
366
|
+
Object.defineProperty(r, '__needsForwardSkipCheck', {
|
|
367
|
+
value: !!(r.__headIsStrictGround || (r.__scopedSkipInfo && r.__scopedSkipInfo.needsSnap)),
|
|
368
|
+
enumerable: false,
|
|
369
|
+
writable: false,
|
|
370
|
+
configurable: true,
|
|
371
|
+
});
|
|
372
|
+
}
|
|
365
373
|
}
|
|
366
374
|
|
|
367
375
|
function __graphTriplesOrTrue(term) {
|
|
@@ -680,6 +688,11 @@ function skolemizeTermForHeadBlanks(t, headBlankLabels, mapping, skCounter, firi
|
|
|
680
688
|
}
|
|
681
689
|
|
|
682
690
|
function skolemizeTripleForHeadBlanks(tr, headBlankLabels, mapping, skCounter, firingKey, globalMap) {
|
|
691
|
+
// Fast path: the common case has no explicit head blanks. Do not allocate a
|
|
692
|
+
// replacement Triple or compute a firing key when skolemization cannot change
|
|
693
|
+
// anything. This matters for long single-premise chains such as
|
|
694
|
+
// deep-taxonomy-100000, where every derived head triple is otherwise copied.
|
|
695
|
+
if (!headBlankLabels || headBlankLabels.size === 0) return tr;
|
|
683
696
|
return new Triple(
|
|
684
697
|
skolemizeTermForHeadBlanks(tr.s, headBlankLabels, mapping, skCounter, firingKey, globalMap),
|
|
685
698
|
skolemizeTermForHeadBlanks(tr.p, headBlankLabels, mapping, skCounter, firingKey, globalMap),
|
|
@@ -1049,11 +1062,13 @@ function termFastKey(t) {
|
|
|
1049
1062
|
if (t instanceof Iri || t instanceof Blank) return t.__tid;
|
|
1050
1063
|
|
|
1051
1064
|
if (t instanceof Literal) {
|
|
1052
|
-
//
|
|
1053
|
-
//
|
|
1054
|
-
//
|
|
1055
|
-
//
|
|
1056
|
-
//
|
|
1065
|
+
// Literal construction already computed a value-stable __tid for ordinary
|
|
1066
|
+
// short literals. Avoid re-running literalParts()/datatype normalization
|
|
1067
|
+
// while building fact indexes; on data-heavy inputs this is a hot path.
|
|
1068
|
+
// Only the rare over-sized literal needs the value-based fallback because
|
|
1069
|
+
// prelude intentionally gives such literals per-object ids to avoid
|
|
1070
|
+
// retaining huge strings in the global interner.
|
|
1071
|
+
if (typeof t.value !== 'string' || t.value.length + 64 <= MAX_LITERAL_TID_LEN) return t.__tid;
|
|
1057
1072
|
const norm = normalizeLiteralForTid(t.value);
|
|
1058
1073
|
if (typeof norm === 'string' && norm.length > MAX_LITERAL_TID_LEN) return 'L:' + norm;
|
|
1059
1074
|
return t.__tid;
|
|
@@ -1140,17 +1155,57 @@ function ensureFactIndexes(facts) {
|
|
|
1140
1155
|
enumerable: false,
|
|
1141
1156
|
writable: true,
|
|
1142
1157
|
});
|
|
1158
|
+
Object.defineProperty(facts, '__keySetComplete', {
|
|
1159
|
+
value: false,
|
|
1160
|
+
enumerable: false,
|
|
1161
|
+
writable: true,
|
|
1162
|
+
});
|
|
1163
|
+
|
|
1164
|
+
// Build lookup indexes eagerly, but do not populate the duplicate-detection
|
|
1165
|
+
// string Set for every input fact. The predicate/subject/object indexes are
|
|
1166
|
+
// enough to verify duplicates when needed; avoiding 100k+ joined string keys
|
|
1167
|
+
// saves substantial time and GC on data-heavy query workloads.
|
|
1168
|
+
for (let i = 0; i < facts.length; i++) indexFact(facts, facts[i], i, false);
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
function cloneFactIndexesForSnapshot(src, dest) {
|
|
1172
|
+
ensureFactIndexes(src);
|
|
1173
|
+
|
|
1174
|
+
function cloneArrayMap(map) {
|
|
1175
|
+
const out = new Map();
|
|
1176
|
+
for (const [k, arr] of map) out.set(k, arr.slice());
|
|
1177
|
+
return out;
|
|
1178
|
+
}
|
|
1143
1179
|
|
|
1144
|
-
|
|
1180
|
+
function cloneNestedArrayMap(map) {
|
|
1181
|
+
const out = new Map();
|
|
1182
|
+
for (const [k, inner] of map) {
|
|
1183
|
+
const innerOut = new Map();
|
|
1184
|
+
for (const [k2, arr] of inner) innerOut.set(k2, arr.slice());
|
|
1185
|
+
out.set(k, innerOut);
|
|
1186
|
+
}
|
|
1187
|
+
return out;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
Object.defineProperty(dest, '__byPred', { value: cloneArrayMap(src.__byPred), enumerable: false, writable: true });
|
|
1191
|
+
Object.defineProperty(dest, '__byPS', { value: cloneNestedArrayMap(src.__byPS), enumerable: false, writable: true });
|
|
1192
|
+
Object.defineProperty(dest, '__byPO', { value: cloneNestedArrayMap(src.__byPO), enumerable: false, writable: true });
|
|
1193
|
+
Object.defineProperty(dest, '__wildPred', { value: src.__wildPred.slice(), enumerable: false, writable: true });
|
|
1194
|
+
Object.defineProperty(dest, '__wildPS', { value: cloneArrayMap(src.__wildPS), enumerable: false, writable: true });
|
|
1195
|
+
Object.defineProperty(dest, '__wildPO', { value: cloneArrayMap(src.__wildPO), enumerable: false, writable: true });
|
|
1196
|
+
Object.defineProperty(dest, '__keySet', { value: new Set(src.__keySet), enumerable: false, writable: true });
|
|
1197
|
+
Object.defineProperty(dest, '__keySetComplete', { value: !!src.__keySetComplete, enumerable: false, writable: true });
|
|
1145
1198
|
}
|
|
1146
1199
|
|
|
1147
|
-
function indexFact(facts, tr, idx) {
|
|
1200
|
+
function indexFact(facts, tr, idx, addKeySet = true) {
|
|
1148
1201
|
const sk = termFastKey(tr.s);
|
|
1149
1202
|
const ok = termFastKey(tr.o);
|
|
1203
|
+
let pkForKey = null;
|
|
1150
1204
|
|
|
1151
1205
|
if (tr.p instanceof Iri) {
|
|
1152
1206
|
// Use predicate term id as the primary key to avoid hashing long IRI strings.
|
|
1153
1207
|
const pk = tr.p.__tid;
|
|
1208
|
+
pkForKey = pk;
|
|
1154
1209
|
|
|
1155
1210
|
let pb = facts.__byPred.get(pk);
|
|
1156
1211
|
if (!pb) {
|
|
@@ -1208,8 +1263,10 @@ function indexFact(facts, tr, idx) {
|
|
|
1208
1263
|
}
|
|
1209
1264
|
}
|
|
1210
1265
|
|
|
1211
|
-
|
|
1212
|
-
|
|
1266
|
+
if (addKeySet && sk !== null && ok !== null) {
|
|
1267
|
+
if (pkForKey === null) pkForKey = termFastKey(tr.p);
|
|
1268
|
+
if (pkForKey !== null) facts.__keySet.add(sk + '\t' + pkForKey + '\t' + ok);
|
|
1269
|
+
}
|
|
1213
1270
|
}
|
|
1214
1271
|
|
|
1215
1272
|
function candidateFacts(facts, goal) {
|
|
@@ -1271,7 +1328,10 @@ function hasFactIndexed(facts, tr) {
|
|
|
1271
1328
|
ensureFactIndexes(facts);
|
|
1272
1329
|
|
|
1273
1330
|
const key = tripleFastKey(tr);
|
|
1274
|
-
if (key !== null)
|
|
1331
|
+
if (key !== null) {
|
|
1332
|
+
if (facts.__keySet.has(key)) return true;
|
|
1333
|
+
if (facts.__keySetComplete) return false;
|
|
1334
|
+
}
|
|
1275
1335
|
|
|
1276
1336
|
if (tr.p instanceof Iri) {
|
|
1277
1337
|
const pk = tr.p.__tid;
|
|
@@ -1301,7 +1361,7 @@ function pushFactIndexed(facts, tr) {
|
|
|
1301
1361
|
ensureFactIndexes(facts);
|
|
1302
1362
|
const idx = facts.length;
|
|
1303
1363
|
facts.push(tr);
|
|
1304
|
-
indexFact(facts, tr, idx);
|
|
1364
|
+
indexFact(facts, tr, idx, true);
|
|
1305
1365
|
}
|
|
1306
1366
|
|
|
1307
1367
|
function makeDerivedRecord(fact, rule, premises, subst, captureExplanations) {
|
|
@@ -1423,13 +1483,20 @@ function makeSinglePremiseAgendaIndex(forwardRules, backRules) {
|
|
|
1423
1483
|
if (!isSinglePremiseAgendaRuleSafe(r, backRules)) continue;
|
|
1424
1484
|
|
|
1425
1485
|
const goal = r.premise[0];
|
|
1486
|
+
const goalSKey = termFastKey(goal.s);
|
|
1487
|
+
const goalOKey = termFastKey(goal.o);
|
|
1488
|
+
const fastSubjectVar = goal.p instanceof Iri && goal.s instanceof Var && goalOKey !== null ? goal.s.name : null;
|
|
1489
|
+
const fastObjectVar = goal.p instanceof Iri && goal.o instanceof Var && goalSKey !== null ? goal.o.name : null;
|
|
1426
1490
|
const entry = {
|
|
1427
1491
|
rule: r,
|
|
1428
1492
|
ruleIndex: i,
|
|
1429
1493
|
goal,
|
|
1430
1494
|
goalPredTid: goal.p instanceof Iri ? goal.p.__tid : null,
|
|
1431
|
-
goalSKey
|
|
1432
|
-
goalOKey
|
|
1495
|
+
goalSKey,
|
|
1496
|
+
goalOKey,
|
|
1497
|
+
needsSkipCheck: !!r.__needsForwardSkipCheck,
|
|
1498
|
+
fastSubjectVar,
|
|
1499
|
+
fastObjectVar,
|
|
1433
1500
|
};
|
|
1434
1501
|
|
|
1435
1502
|
index.indexed.add(r);
|
|
@@ -2838,11 +2905,9 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
2838
2905
|
const varGen = [0];
|
|
2839
2906
|
const skCounter = [0];
|
|
2840
2907
|
|
|
2841
|
-
//
|
|
2842
|
-
//
|
|
2843
|
-
|
|
2844
|
-
__ensureRuleKeySet(forwardRules);
|
|
2845
|
-
__ensureRuleKeySet(backRules);
|
|
2908
|
+
// Rule-key sets are only needed if a program actually derives rule-producing
|
|
2909
|
+
// triples. Building them eagerly is expensive on large static rule sets, so
|
|
2910
|
+
// dynamic-promotion sites create them lazily before duplicate checks.
|
|
2846
2911
|
|
|
2847
2912
|
// Cache head blank-node skolemization per (rule firing, head blank label).
|
|
2848
2913
|
// This prevents repeatedly generating fresh _:sk_N blanks for the *same*
|
|
@@ -2892,7 +2957,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
2892
2957
|
|
|
2893
2958
|
function makeScopedSnapshot() {
|
|
2894
2959
|
const snap = facts.slice();
|
|
2895
|
-
|
|
2960
|
+
cloneFactIndexesForSnapshot(facts, snap);
|
|
2896
2961
|
Object.defineProperty(snap, '__scopedSnapshot', {
|
|
2897
2962
|
value: snap,
|
|
2898
2963
|
enumerable: false,
|
|
@@ -2946,10 +3011,21 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
2946
3011
|
let changedHere = false;
|
|
2947
3012
|
let rulesChanged = false;
|
|
2948
3013
|
|
|
2949
|
-
// IMPORTANT: one skolem map per *rule firing
|
|
3014
|
+
// IMPORTANT: one skolem map per *rule firing*. Instantiate premise
|
|
3015
|
+
// triples and build the firing key lazily: normal CLI runs do not capture
|
|
3016
|
+
// proof records, and most rules have no explicit head blanks, so the eager
|
|
3017
|
+
// work was pure allocation on large forward chains.
|
|
2950
3018
|
const skMap = {};
|
|
2951
|
-
|
|
2952
|
-
|
|
3019
|
+
let instantiatedPremises = null;
|
|
3020
|
+
let fireKey = null;
|
|
3021
|
+
function getInstantiatedPremises() {
|
|
3022
|
+
if (instantiatedPremises === null) instantiatedPremises = r.premise.map((b) => applySubstTriple(b, s));
|
|
3023
|
+
return instantiatedPremises;
|
|
3024
|
+
}
|
|
3025
|
+
function getFireKey() {
|
|
3026
|
+
if (fireKey === null) fireKey = __firingKey(ruleIndex, getInstantiatedPremises());
|
|
3027
|
+
return fireKey;
|
|
3028
|
+
}
|
|
2953
3029
|
|
|
2954
3030
|
// Support "dynamic" rule heads where the consequent is a term that
|
|
2955
3031
|
// (after substitution) evaluates to a quoted formula.
|
|
@@ -3002,7 +3078,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
3002
3078
|
if (isFwRuleTriple || isBwRuleTriple) {
|
|
3003
3079
|
if (!hasFactIndexed(facts, instantiated)) {
|
|
3004
3080
|
pushFactIndexed(facts, instantiated);
|
|
3005
|
-
const df = makeDerivedRecord(instantiated, r,
|
|
3081
|
+
const df = makeDerivedRecord(instantiated, r, getInstantiatedPremises(), s, captureExplanations);
|
|
3006
3082
|
derivedForward.push(df);
|
|
3007
3083
|
if (typeof onDerived === 'function') onDerived(df);
|
|
3008
3084
|
changedHere = true;
|
|
@@ -3021,8 +3097,9 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
3021
3097
|
newRule.conclusion,
|
|
3022
3098
|
newRule.__dynamicConclusionTerm || null,
|
|
3023
3099
|
);
|
|
3024
|
-
|
|
3025
|
-
|
|
3100
|
+
const forwardRuleKeySet = __ensureRuleKeySet(forwardRules);
|
|
3101
|
+
if (!forwardRuleKeySet.has(key)) {
|
|
3102
|
+
forwardRuleKeySet.add(key);
|
|
3026
3103
|
forwardRules.push(newRule);
|
|
3027
3104
|
rulesChanged = true;
|
|
3028
3105
|
}
|
|
@@ -3036,8 +3113,9 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
3036
3113
|
newRule.conclusion,
|
|
3037
3114
|
newRule.__dynamicConclusionTerm || null,
|
|
3038
3115
|
);
|
|
3039
|
-
|
|
3040
|
-
|
|
3116
|
+
const backRuleKeySet = __ensureRuleKeySet(backRules);
|
|
3117
|
+
if (!backRuleKeySet.has(key)) {
|
|
3118
|
+
backRuleKeySet.add(key);
|
|
3041
3119
|
backRules.push(newRule);
|
|
3042
3120
|
indexBackRule(backRules, newRule);
|
|
3043
3121
|
rulesChanged = true;
|
|
@@ -3048,20 +3126,23 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
3048
3126
|
}
|
|
3049
3127
|
|
|
3050
3128
|
// Only skolemize blank nodes that occur explicitly in the rule head
|
|
3051
|
-
const inst =
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3129
|
+
const inst =
|
|
3130
|
+
headBlankLabelsHere && headBlankLabelsHere.size
|
|
3131
|
+
? skolemizeTripleForHeadBlanks(
|
|
3132
|
+
instantiated,
|
|
3133
|
+
headBlankLabelsHere,
|
|
3134
|
+
skMap,
|
|
3135
|
+
skCounter,
|
|
3136
|
+
getFireKey(),
|
|
3137
|
+
headSkolemCache,
|
|
3138
|
+
)
|
|
3139
|
+
: instantiated;
|
|
3059
3140
|
|
|
3060
3141
|
if (!isGroundTriple(inst)) continue;
|
|
3061
3142
|
if (hasFactIndexed(facts, inst)) continue;
|
|
3062
3143
|
|
|
3063
3144
|
pushFactIndexed(facts, inst);
|
|
3064
|
-
const df = makeDerivedRecord(inst, r,
|
|
3145
|
+
const df = makeDerivedRecord(inst, r, getInstantiatedPremises(), s, captureExplanations);
|
|
3065
3146
|
derivedForward.push(df);
|
|
3066
3147
|
if (typeof onDerived === 'function') onDerived(df);
|
|
3067
3148
|
|
|
@@ -3088,10 +3169,19 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
3088
3169
|
for (let ci = 0; ci < total; ci++) {
|
|
3089
3170
|
const entry = ci < candidates.exactLen ? candidates.exact[ci] : candidates.wild[ci - candidates.exactLen];
|
|
3090
3171
|
const r = entry.rule;
|
|
3091
|
-
if (__skipForwardRuleNow(r)) continue;
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
if (
|
|
3172
|
+
if (entry.needsSkipCheck && __skipForwardRuleNow(r)) continue;
|
|
3173
|
+
|
|
3174
|
+
let s;
|
|
3175
|
+
if (entry.fastSubjectVar !== null) {
|
|
3176
|
+
s = __emptySubst();
|
|
3177
|
+
s[entry.fastSubjectVar] = fact.s;
|
|
3178
|
+
} else if (entry.fastObjectVar !== null) {
|
|
3179
|
+
s = __emptySubst();
|
|
3180
|
+
s[entry.fastObjectVar] = fact.o;
|
|
3181
|
+
} else {
|
|
3182
|
+
s = unifyTriple(entry.goal, fact, __emptySubst());
|
|
3183
|
+
if (s === null) continue;
|
|
3184
|
+
}
|
|
3095
3185
|
|
|
3096
3186
|
const outcome = __emitForwardRuleSolution(r, entry.ruleIndex, s);
|
|
3097
3187
|
if (outcome.rulesChanged) {
|
|
@@ -3108,7 +3198,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
3108
3198
|
for (let i = 0; i < forwardRules.length; i++) {
|
|
3109
3199
|
const r = forwardRules[i];
|
|
3110
3200
|
if (agendaIndex.indexed.has(r)) continue;
|
|
3111
|
-
if (__skipForwardRuleNow(r)) continue;
|
|
3201
|
+
if (r.__needsForwardSkipCheck && __skipForwardRuleNow(r)) continue;
|
|
3112
3202
|
|
|
3113
3203
|
const headIsStrictGround = r.__headIsStrictGround;
|
|
3114
3204
|
const maxSols = r.isFuse || headIsStrictGround ? 1 : undefined;
|