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/lib/cli.js CHANGED
@@ -210,7 +210,7 @@ function main() {
210
210
  parseN3Text(text, {
211
211
  baseIri: __sourceLabelToBaseIri(sourceLabel),
212
212
  label: sourceLabel,
213
- collectUsedPrefixes: true,
213
+ collectUsedPrefixes: streamMode,
214
214
  keepSourceArtifacts: false,
215
215
  rdf: rdfMode,
216
216
  }),
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
- // Very large literals intentionally skip global interning in prelude.js to
1053
- // avoid retaining huge strings forever. Their per-object __tid is therefore
1054
- // not value-stable, so using it here breaks duplicate detection for facts
1055
- // such as long log:outputString blocks that are re-derived during forward
1056
- // chaining. Fall back to a value-based key in that case.
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
- for (let i = 0; i < facts.length; i++) indexFact(facts, facts[i], i);
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
- const key = tripleFastKey(tr);
1212
- if (key !== null) facts.__keySet.add(key);
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) return facts.__keySet.has(key);
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: termFastKey(goal.s),
1432
- goalOKey: termFastKey(goal.o),
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
- // Speed up dynamic rule promotion by maintaining O(1) membership sets.
2842
- // (Some workloads derive many rule-producing triples.)
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
- ensureFactIndexes(snap);
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
- const instantiatedPremises = r.premise.map((b) => applySubstTriple(b, s));
2952
- const fireKey = __firingKey(ruleIndex, instantiatedPremises);
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, instantiatedPremises, s, captureExplanations);
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
- if (!forwardRules.__ruleKeySet.has(key)) {
3025
- forwardRules.__ruleKeySet.add(key);
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
- if (!backRules.__ruleKeySet.has(key)) {
3040
- backRules.__ruleKeySet.add(key);
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 = skolemizeTripleForHeadBlanks(
3052
- instantiated,
3053
- headBlankLabelsHere,
3054
- skMap,
3055
- skCounter,
3056
- fireKey,
3057
- headSkolemCache,
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, instantiatedPremises, s, captureExplanations);
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
- const s = unifyTriple(entry.goal, fact, __emptySubst());
3094
- if (s === null) continue;
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;