eyeling 2.34.7 → 2.35.0

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/engine.js CHANGED
@@ -44,6 +44,7 @@ const LOG_COLLECT_ALL_IN_IRI = LOG_NS + 'collectAllIn';
44
44
  const LOG_FOR_ALL_IN_IRI = LOG_NS + 'forAllIn';
45
45
  const LOG_INCLUDES_IRI = LOG_NS + 'includes';
46
46
  const LOG_NOT_INCLUDES_IRI = LOG_NS + 'notIncludes';
47
+ const LOG_MEMOIZE_IRI = LOG_NS + 'memoize';
47
48
  const LOG_IMPLIES_IRI = LOG_NS + 'implies';
48
49
  const LOG_IMPLIED_BY_IRI = LOG_NS + 'impliedBy';
49
50
 
@@ -145,6 +146,7 @@ const FACT_INDEX_ARRAY_MAP_FIELDS = ['__byPred', '__byPNonFastS', '__byPNonFastO
145
146
  const FACT_INDEX_NESTED_ARRAY_MAP_FIELDS = ['__byPS', '__byPO'];
146
147
  const FACT_INDEX_MAP_FIELDS = FACT_INDEX_ARRAY_MAP_FIELDS.concat(FACT_INDEX_NESTED_ARRAY_MAP_FIELDS);
147
148
  const FACT_INDEX_REQUIRED_FIELDS = FACT_INDEX_ARRAY_FIELDS.concat(FACT_INDEX_MAP_FIELDS, ['__keySet']);
149
+ const FACT_INDEX_ALL_FIELDS = FACT_INDEX_REQUIRED_FIELDS.concat(['__keySetComplete']);
148
150
 
149
151
  let version = 'dev';
150
152
  try {
@@ -1212,7 +1214,13 @@ function termLookupKey(t) {
1212
1214
  return t.__tid;
1213
1215
  }
1214
1216
  if (t instanceof Blank) return t.__tid;
1215
- if (t instanceof Literal) return literalLookupKey(t);
1217
+ if (t instanceof Literal) {
1218
+ const cached = t.__lookupKey;
1219
+ if (cached !== undefined) return cached;
1220
+ const key = literalLookupKey(t);
1221
+ Object.defineProperty(t, '__lookupKey', { value: key, enumerable: false });
1222
+ return key;
1223
+ }
1216
1224
 
1217
1225
  if (t instanceof ListTerm) {
1218
1226
  const cached = t.__lookupKey;
@@ -1250,6 +1258,13 @@ function tripleFastKey(tr) {
1250
1258
  return ks + '\t' + kp + '\t' + ko;
1251
1259
  }
1252
1260
 
1261
+ function resetFactIndexes(facts) {
1262
+ if (!facts) return;
1263
+ for (const name of FACT_INDEX_ALL_FIELDS) {
1264
+ try { delete facts[name]; } catch {}
1265
+ }
1266
+ }
1267
+
1253
1268
  function ensureFactIndexes(facts) {
1254
1269
  if (FACT_INDEX_REQUIRED_FIELDS.every((name) => facts[name])) return;
1255
1270
 
@@ -1680,6 +1695,157 @@ function isLogImpliedBy(p) {
1680
1695
  return p instanceof Iri && p.value === LOG_IMPLIED_BY_IRI;
1681
1696
  }
1682
1697
 
1698
+ function isLogMemoize(p) {
1699
+ return p instanceof Iri && p.value === LOG_MEMOIZE_IRI;
1700
+ }
1701
+
1702
+ function isTruthyMemoizeObject(o) {
1703
+ if (!(o instanceof Literal)) return false;
1704
+ if (o.value === 'true') return true;
1705
+ const info = parseBooleanLiteralInfo(o);
1706
+ return !!(info && info.value === true);
1707
+ }
1708
+
1709
+ // ===========================================================================
1710
+ // Predicate memoization declarations
1711
+ // ===========================================================================
1712
+ //
1713
+ // Eyeling accepts top-level declarations of the form:
1714
+ // :predicate log:memoize true.
1715
+ // They are engine directives, not data facts. Each declaration asks the
1716
+ // backward prover to table completed answers for that predicate during the
1717
+ // current reasoning scope. The hint is deliberately predicate-local and
1718
+ // conservative: unbound calls and finite-result probes still use the normal
1719
+ // path, and cached answers are invalidated when the fact/rule scope changes.
1720
+
1721
+ function __extractLogMemoizeDeclarations(facts) {
1722
+ const memoized = new Set();
1723
+ if (!Array.isArray(facts) || facts.length === 0) return memoized;
1724
+
1725
+ let write = 0;
1726
+ let removed = false;
1727
+ for (let read = 0; read < facts.length; read++) {
1728
+ const tr = facts[read];
1729
+ if (tr && isLogMemoize(tr.p) && tr.s instanceof Iri && isTruthyMemoizeObject(tr.o)) {
1730
+ memoized.add(tr.s.__tid);
1731
+ removed = true;
1732
+ continue;
1733
+ }
1734
+ if (write !== read) facts[write] = tr;
1735
+ write++;
1736
+ }
1737
+
1738
+ if (removed) {
1739
+ facts.length = write;
1740
+ resetFactIndexes(facts);
1741
+ }
1742
+ return memoized;
1743
+ }
1744
+
1745
+ function __attachMemoizedPredicates(scopeCarrier, memoized) {
1746
+ if (!scopeCarrier || !memoized || memoized.size === 0) return;
1747
+ __setHiddenWritable(scopeCarrier, 'memoizedPredicates', memoized);
1748
+ }
1749
+
1750
+ function __memoizedPredicateSet(facts, backRules) {
1751
+ return (facts && facts.memoizedPredicates) || (backRules && backRules.memoizedPredicates) || null;
1752
+ }
1753
+
1754
+ function __predicateIsMemoized(facts, backRules, p) {
1755
+ if (!(p instanceof Iri)) return false;
1756
+ const memoized = __memoizedPredicateSet(facts, backRules);
1757
+ return !!(memoized && memoized.has(p.__tid));
1758
+ }
1759
+
1760
+ function __makePredicateMemoTable() {
1761
+ return {
1762
+ scopeVersion: null,
1763
+ entries: new Map(),
1764
+ };
1765
+ }
1766
+
1767
+ function __ensurePredicateMemoTable(facts, backRules) {
1768
+ let table = (facts && facts.predicateMemoTable) || (backRules && backRules.predicateMemoTable) || null;
1769
+ if (!table) table = __makePredicateMemoTable();
1770
+ __setHiddenWritable(facts, 'predicateMemoTable', table);
1771
+ __setHiddenWritable(backRules, 'predicateMemoTable', table);
1772
+
1773
+ const version = goalTableScopeVersion(facts, backRules);
1774
+ if (table.scopeVersion !== version) {
1775
+ table.scopeVersion = version;
1776
+ table.entries.clear();
1777
+ }
1778
+ return table;
1779
+ }
1780
+
1781
+ function __memoKeyPart(t) {
1782
+ if (__termHasVarOrBlank(t)) return { bound: false, text: '_' };
1783
+ const fast = termLookupKey(t);
1784
+ if (fast !== null) return { bound: true, text: encodeLookupKeyPart(fast) };
1785
+ return { bound: true, text: skolemKeyFromTerm(t) };
1786
+ }
1787
+
1788
+ function __predicateMemoKey(goal) {
1789
+ if (!(goal && goal.p instanceof Iri)) return null;
1790
+ const sPart = __memoKeyPart(goal.s);
1791
+ const oPart = __memoKeyPart(goal.o);
1792
+ if (!sPart.bound && !oPart.bound) return null;
1793
+ return `${goal.p.__tid}|${sPart.text}|${oPart.text}`;
1794
+ }
1795
+
1796
+ function __makePredicateMemoEntry() {
1797
+ return {
1798
+ computing: false,
1799
+ complete: false,
1800
+ unsafe: false,
1801
+ answers: [],
1802
+ answerKeys: new Set(),
1803
+ };
1804
+ }
1805
+
1806
+ function __getPredicateMemoEntry(facts, backRules, key) {
1807
+ const table = __ensurePredicateMemoTable(facts, backRules);
1808
+ let entry = table.entries.get(key);
1809
+ if (!entry) {
1810
+ entry = __makePredicateMemoEntry();
1811
+ table.entries.set(key, entry);
1812
+ }
1813
+ return entry;
1814
+ }
1815
+
1816
+ function __canStorePredicateMemo(maxResults) {
1817
+ return !(typeof maxResults === 'number' && maxResults > 0);
1818
+ }
1819
+
1820
+ function __canMemoizeAnswerTerm(t) {
1821
+ if (t instanceof Var || t instanceof OpenListTerm) return false;
1822
+ if (t instanceof ListTerm) return t.elems.every(__canMemoizeAnswerTerm);
1823
+ if (t instanceof GraphTerm) return t.triples.every((tr) => __canMemoizeAnswerTerm(tr.s) && __canMemoizeAnswerTerm(tr.p) && __canMemoizeAnswerTerm(tr.o));
1824
+ return true;
1825
+ }
1826
+
1827
+ function __memoAnswerKeyPart(t) {
1828
+ const fast = termLookupKey(t);
1829
+ if (fast !== null) return encodeLookupKeyPart(fast);
1830
+ return skolemKeyFromTerm(t);
1831
+ }
1832
+
1833
+ function __memoAnswerKey(tr) {
1834
+ return __memoAnswerKeyPart(tr.s) + '\t' + __memoAnswerKeyPart(tr.p) + '\t' + __memoAnswerKeyPart(tr.o);
1835
+ }
1836
+
1837
+ function __storePredicateMemoAnswer(entry, rawGoal, subst) {
1838
+ const answer = applySubstTriple(rawGoal, subst || {});
1839
+ if (!__canMemoizeAnswerTerm(answer.s) || !__canMemoizeAnswerTerm(answer.p) || !__canMemoizeAnswerTerm(answer.o)) {
1840
+ entry.unsafe = true;
1841
+ return;
1842
+ }
1843
+ const key = __memoAnswerKey(answer);
1844
+ if (entry.answerKeys.has(key)) return;
1845
+ entry.answerKeys.add(key);
1846
+ entry.answers.push(answer);
1847
+ }
1848
+
1683
1849
  // ===========================================================================
1684
1850
  // Completed-goal answer tables (minimal tabling)
1685
1851
  // ===========================================================================
@@ -2593,6 +2759,47 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxR
2593
2759
  continue;
2594
2760
  }
2595
2761
 
2762
+ if (frame.kind === 'memoComplete') {
2763
+ frame.entry.computing = false;
2764
+ if (frame.entry.unsafe) {
2765
+ frame.table.entries.delete(frame.key);
2766
+ } else {
2767
+ frame.entry.complete = true;
2768
+ }
2769
+ continue;
2770
+ }
2771
+
2772
+ if (frame.kind === 'memoAnswerIter') {
2773
+ const answers = frame.entry.answers;
2774
+ while (frame.idx < answers.length && results.length < max) {
2775
+ const answer = answers[frame.idx++];
2776
+ const mark = trail.length;
2777
+ if (!unifyTripleTrail(frame.goal0, answer)) {
2778
+ undoTo(mark);
2779
+ continue;
2780
+ }
2781
+
2782
+ if (!frame.restGoals.length) {
2783
+ results.push(gcCompactForGoals(substMut, [], answerVars));
2784
+ undoTo(mark);
2785
+ if (results.length >= max) return results;
2786
+ continue;
2787
+ }
2788
+
2789
+ stack.push(frame);
2790
+ stack.push({ kind: 'undo', substMark: mark, visitedMark: visitedTrail.length });
2791
+ stack.push({
2792
+ kind: 'node',
2793
+ goalsNow: frame.restGoals,
2794
+ curDepth: frame.curDepth + 1,
2795
+ canDeferBuiltins: frame.canDeferBuiltins,
2796
+ deferCount: 0,
2797
+ });
2798
+ break;
2799
+ }
2800
+ continue;
2801
+ }
2802
+
2596
2803
  if (frame.kind === 'deltaIter') {
2597
2804
  const deltas = frame.deltas;
2598
2805
  while (frame.idx < deltas.length && results.length < max) {
@@ -2734,7 +2941,21 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxR
2734
2941
  }
2735
2942
 
2736
2943
  const rawGoal = goalsNow[0];
2737
- const restGoals = goalsNow.length > 1 ? goalsNow.slice(1) : [];
2944
+ let restGoals = goalsNow.length > 1 ? goalsNow.slice(1) : [];
2945
+
2946
+ if (rawGoal && rawGoal.__kind === 'memoStore') {
2947
+ __storePredicateMemoAnswer(rawGoal.entry, rawGoal.goal, substMut);
2948
+ if (rawGoal.stop && restGoals.length === 0) continue;
2949
+ stack.push({
2950
+ kind: 'node',
2951
+ goalsNow: restGoals,
2952
+ curDepth: frame.curDepth,
2953
+ canDeferBuiltins: frame.canDeferBuiltins,
2954
+ deferCount: 0,
2955
+ });
2956
+ continue;
2957
+ }
2958
+
2738
2959
  const goal0 = applySubstTriple(rawGoal, substMut);
2739
2960
 
2740
2961
  // 1) Builtins
@@ -2830,8 +3051,46 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxR
2830
3051
  const goalKey = tripleKeyForVisited(goal0);
2831
3052
  const goalWasVisited = visitedCounts.has(goalKey);
2832
3053
 
3054
+ let memoCompleteFrame = null;
3055
+ let memoReplayFrame = null;
3056
+ if (__predicateIsMemoized(facts, backRules, goal0.p)) {
3057
+ const memoKey = __predicateMemoKey(goal0);
3058
+ if (memoKey !== null) {
3059
+ const memoEntry = __getPredicateMemoEntry(facts, backRules, memoKey);
3060
+ if (memoEntry.complete) {
3061
+ stack.push({
3062
+ kind: 'memoAnswerIter',
3063
+ entry: memoEntry,
3064
+ idx: 0,
3065
+ goal0,
3066
+ restGoals,
3067
+ curDepth: frame.curDepth,
3068
+ canDeferBuiltins: frame.canDeferBuiltins,
3069
+ });
3070
+ continue;
3071
+ }
3072
+ if (!memoEntry.computing && __canStorePredicateMemo(maxResults)) {
3073
+ memoEntry.computing = true;
3074
+ const table = __ensurePredicateMemoTable(facts, backRules);
3075
+ memoReplayFrame = {
3076
+ kind: 'memoAnswerIter',
3077
+ entry: memoEntry,
3078
+ idx: 0,
3079
+ goal0,
3080
+ restGoals,
3081
+ curDepth: frame.curDepth,
3082
+ canDeferBuiltins: frame.canDeferBuiltins,
3083
+ };
3084
+ memoCompleteFrame = { kind: 'memoComplete', entry: memoEntry, table, key: memoKey };
3085
+ restGoals = [{ __kind: 'memoStore', entry: memoEntry, goal: rawGoal, stop: true }];
3086
+ }
3087
+ }
3088
+ }
3089
+
2833
3090
  // 3) Backward rules (indexed by head predicate) — explored first
2834
3091
  if (goal0.p instanceof Iri) {
3092
+ if (memoReplayFrame) stack.push(memoReplayFrame);
3093
+ if (memoCompleteFrame) stack.push(memoCompleteFrame);
2835
3094
  ensureBackRuleIndexes(backRules);
2836
3095
  const candRules = (backRules.__byHeadPred.get(goal0.p.__tid) || []).concat(backRules.__wildHeadPred);
2837
3096
 
@@ -2863,6 +3122,8 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxR
2863
3122
  });
2864
3123
  }
2865
3124
  } else {
3125
+ if (memoReplayFrame) stack.push(memoReplayFrame);
3126
+ if (memoCompleteFrame) stack.push(memoCompleteFrame);
2866
3127
  // No IRI predicate: rule indexing doesn't apply; only try all facts.
2867
3128
  stack.push({
2868
3129
  kind: 'factIter',
@@ -3068,6 +3329,10 @@ function __exitReasoning(code, message /* optional */) {
3068
3329
  function forwardChain(facts, forwardRules, backRules, onDerived /* optional */, opts = {}) {
3069
3330
  enterReasoningRun();
3070
3331
  try {
3332
+ const memoizedPredicates = __extractLogMemoizeDeclarations(facts);
3333
+ __attachMemoizedPredicates(facts, memoizedPredicates);
3334
+ __attachMemoizedPredicates(backRules, memoizedPredicates);
3335
+
3071
3336
  ensureFactIndexes(facts);
3072
3337
  ensureBackRuleIndexes(backRules);
3073
3338
 
@@ -3706,7 +3971,7 @@ function reasonStream(input, opts = {}) {
3706
3971
  if (Array.isArray(logQueryRules) && logQueryRules.length) {
3707
3972
  // Query-selection mode: derive full closure, then output only the unique
3708
3973
  // instantiated conclusion triples of the log:query directives.
3709
- const res = forwardChainAndCollectLogQueryConclusions(facts, frules, brules, logQueryRules, { prefixes });
3974
+ const res = forwardChainAndCollectLogQueryConclusions(facts, frules, brules, logQueryRules, null, { captureExplanations: proof, prefixes });
3710
3975
  derived = res.derived;
3711
3976
  queryTriples = res.queryTriples;
3712
3977
  queryDerived = res.queryDerived;
@@ -3740,7 +4005,7 @@ function reasonStream(input, opts = {}) {
3740
4005
  onDerived(payload);
3741
4006
  }
3742
4007
  },
3743
- { prefixes },
4008
+ { captureExplanations: proof, prefixes },
3744
4009
  );
3745
4010
  }
3746
4011
 
@@ -184,7 +184,7 @@ function parseN3Text(text, opts = {}) {
184
184
  }
185
185
 
186
186
  const tokens = lex(text, { rdf });
187
- const parser = new Parser(tokens);
187
+ const parser = new Parser(tokens, { sourceOffsets: sourceLocations });
188
188
  if (baseIri) parser.prefixes.setBase(baseIri);
189
189
  const [prefixes, triples, frules, brules, logQueryRules] = parser.parseDocument();
190
190
 
package/lib/parser.js CHANGED
@@ -54,7 +54,7 @@ function annotateSourceOffset(obj, offset) {
54
54
  }
55
55
 
56
56
  class Parser {
57
- constructor(tokens) {
57
+ constructor(tokens, options = {}) {
58
58
  this.toks = tokens;
59
59
  this.pos = 0;
60
60
  this.prefixes = PrefixEnv.newDefault();
@@ -68,6 +68,7 @@ class Parser {
68
68
  // makes derived output read naturally, e.g. ':s :p _:b.' preceding
69
69
  // '_:b :q :r.'
70
70
  this.pendingTriplesAfter = [];
71
+ this.trackSourceOffsets = !!(options && options.sourceOffsets);
71
72
  }
72
73
 
73
74
  peek() {
@@ -80,6 +81,10 @@ class Parser {
80
81
  return tok;
81
82
  }
82
83
 
84
+ annotateSourceOffset(obj, offset) {
85
+ return this.trackSourceOffsets ? annotateSourceOffset(obj, offset) : obj;
86
+ }
87
+
83
88
  fail(message, tok = this.peek()) {
84
89
  const off = tok && typeof tok.offset === 'number' ? tok.offset : null;
85
90
  throw new N3SyntaxError(message, off);
@@ -184,12 +189,12 @@ class Parser {
184
189
  this.next();
185
190
  const second = this.parseTerm();
186
191
  this.expectDot();
187
- forwardRules.push(annotateSourceOffset(this.makeRule(first, second, true), statementOffset));
192
+ forwardRules.push(this.annotateSourceOffset(this.makeRule(first, second, true), statementOffset));
188
193
  } else if (this.peek().typ === 'OpImpliedBy') {
189
194
  this.next();
190
195
  const second = this.parseTerm();
191
196
  this.expectDot();
192
- backwardRules.push(annotateSourceOffset(this.makeRule(first, second, false), statementOffset));
197
+ backwardRules.push(this.annotateSourceOffset(this.makeRule(first, second, false), statementOffset));
193
198
  } else {
194
199
  let more;
195
200
 
@@ -208,16 +213,16 @@ class Parser {
208
213
 
209
214
  // normalize explicit log:implies / log:impliedBy at top-level
210
215
  for (const tr0 of more) {
211
- const tr = annotateSourceOffset(tr0, statementOffset);
216
+ const tr = this.annotateSourceOffset(tr0, statementOffset);
212
217
  if (isLogImplies(tr.p) && tr.s instanceof GraphTerm && tr.o instanceof GraphTerm) {
213
- forwardRules.push(annotateSourceOffset(this.makeRule(tr.s, tr.o, true), statementOffset));
218
+ forwardRules.push(this.annotateSourceOffset(this.makeRule(tr.s, tr.o, true), statementOffset));
214
219
  } else if (isLogImpliedBy(tr.p) && tr.s instanceof GraphTerm && tr.o instanceof GraphTerm) {
215
- backwardRules.push(annotateSourceOffset(this.makeRule(tr.s, tr.o, false), statementOffset));
220
+ backwardRules.push(this.annotateSourceOffset(this.makeRule(tr.s, tr.o, false), statementOffset));
216
221
  } else if (isLogQuery(tr.p) && tr.s instanceof GraphTerm && tr.o instanceof GraphTerm) {
217
222
  // Output-selection directive: { premise } log:query { conclusion }.
218
223
  // When present at top-level, eyeling prints only the instantiated conclusion
219
224
  // triples (unique) instead of all newly derived facts.
220
- logQueries.push(annotateSourceOffset(this.makeRule(tr.s, tr.o, true), statementOffset));
225
+ logQueries.push(this.annotateSourceOffset(this.makeRule(tr.s, tr.o, true), statementOffset));
221
226
  } else {
222
227
  triples.push(tr);
223
228
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "2.34.7",
3
+ "version": "2.35.0",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [