eyeling 1.25.4 → 1.26.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.
@@ -4721,7 +4721,7 @@ function main() {
4721
4721
  ` -d, --deterministic-skolem Make log:skolem stable across reasoning runs.\n` +
4722
4722
  ` -e, --enforce-https Rewrite http:// IRIs to https:// for log dereferencing builtins.\n` +
4723
4723
  ` -h, --help Show this help and exit.\n` +
4724
- ` -p, --proof-comments Enable proof explanations.\n` +
4724
+ ` -p, --proof Enable proof explanations.\n` +
4725
4725
  ` -r, --rdf Enable RDF/TriG input/output compatibility.\n` +
4726
4726
  ` -s, --super-restricted Disable all builtins except => and <=.\n` +
4727
4727
  ` -t, --stream Stream derived triples as soon as they are derived.\n` +
@@ -4776,8 +4776,8 @@ function main() {
4776
4776
  if (typeof engine.setDeterministicSkolemEnabled === 'function') engine.setDeterministicSkolemEnabled(true);
4777
4777
  }
4778
4778
 
4779
- // --proof-comments / -p: enable proof explanations
4780
- if (argv.includes('--proof-comments') || argv.includes('-p')) {
4779
+ // --proof / -p: enable proof explanations as N3 proof graphs
4780
+ if (argv.includes('--proof') || argv.includes('--proof-comments') || argv.includes('-p')) {
4781
4781
  engine.setProofCommentsEnabled(true);
4782
4782
  }
4783
4783
 
@@ -4826,6 +4826,7 @@ function main() {
4826
4826
  baseIri: __sourceLabelToBaseIri(sourceLabel),
4827
4827
  label: sourceLabel,
4828
4828
  collectUsedPrefixes: streamMode,
4829
+ collectSourceLocations: engine.getProofCommentsEnabled(),
4829
4830
  keepSourceArtifacts: false,
4830
4831
  rdf: rdfMode,
4831
4832
  }),
@@ -4916,7 +4917,7 @@ function main() {
4916
4917
  const hasQueries = Array.isArray(qrules) && qrules.length;
4917
4918
  const mayAutoRenderOutputStrings = programMayProduceOutputStrings(triples, frules, qrules);
4918
4919
 
4919
- if (streamMode && !hasQueries && !mayAutoRenderOutputStrings) {
4920
+ if (streamMode && !hasQueries && !mayAutoRenderOutputStrings && !engine.getProofCommentsEnabled()) {
4920
4921
  const usedInInput = mergedDocument.usedPrefixes instanceof Set ? new Set(mergedDocument.usedPrefixes) : new Set();
4921
4922
  const outPrefixes = restrictPrefixEnv(prefixes, usedInInput);
4922
4923
 
@@ -4981,6 +4982,11 @@ function main() {
4981
4982
  return;
4982
4983
  }
4983
4984
 
4985
+ if (engine.getProofCommentsEnabled()) {
4986
+ process.stdout.write(engine.renderProofDocument(outDerived, derived, triples, prefixes, brules));
4987
+ return;
4988
+ }
4989
+
4984
4990
  let bodyText = '';
4985
4991
  if (rdfMode && !engine.getProofCommentsEnabled()) {
4986
4992
  bodyText = outTriples.map((tr) => engine.tripleToRdfCompatible(tr, prefixes)).join('\n');
@@ -5973,6 +5979,158 @@ function __varOccursElsewhereInPremise(premise, name, idx, field) {
5973
5979
  return false;
5974
5980
  }
5975
5981
 
5982
+
5983
+ function __scopedPriorityForTerm(t) {
5984
+ if (t instanceof GraphTerm) return 0;
5985
+ const p0 = __logNaturalPriorityFromTerm(t);
5986
+ return p0 !== null && p0 >= 1 ? p0 : 1;
5987
+ }
5988
+
5989
+ function __pushScopedQueryTriplesFromGraph(term, out) {
5990
+ if (!(term instanceof GraphTerm)) return;
5991
+ for (const tr of term.triples) out.push(tr);
5992
+ }
5993
+
5994
+ function __analyzeForwardRuleScopedUse(rule) {
5995
+ const out = {
5996
+ needsSnap: false,
5997
+ baseLevel: 0,
5998
+ queryTriples: [],
5999
+ hasScopedAggregate: false,
6000
+ };
6001
+
6002
+ if (!rule || !Array.isArray(rule.premise)) return out;
6003
+
6004
+ for (let i = 0; i < rule.premise.length; i++) {
6005
+ const tr = rule.premise[i];
6006
+ if (!(tr && tr.p instanceof Iri)) continue;
6007
+ const pv = tr.p.value;
6008
+
6009
+ if (pv === LOG_NS + 'includes' || pv === LOG_NS + 'notIncludes') {
6010
+ // Explicit quoted scopes are local formulas, not snapshots of the global closure.
6011
+ if (tr.s instanceof GraphTerm) continue;
6012
+
6013
+ // A scope variable that is bound by another premise may become an explicit GraphTerm.
6014
+ // Still, if it remains unbound at runtime the builtin treats it as priority 1.
6015
+ // We therefore record the scoped use, but only use quoted object triples for
6016
+ // dependency analysis; variables inside the quoted object do not bind the scope.
6017
+ out.needsSnap = true;
6018
+ out.baseLevel = Math.max(out.baseLevel, __scopedPriorityForTerm(tr.s));
6019
+ __pushScopedQueryTriplesFromGraph(tr.o, out.queryTriples);
6020
+ continue;
6021
+ }
6022
+
6023
+ if (pv === LOG_NS + 'collectAllIn') {
6024
+ if (tr.o instanceof GraphTerm) continue;
6025
+
6026
+ out.needsSnap = true;
6027
+ out.hasScopedAggregate = true;
6028
+ out.baseLevel = Math.max(out.baseLevel, __scopedPriorityForTerm(tr.o));
6029
+ if (tr.s instanceof ListTerm && tr.s.elems.length === 3) {
6030
+ __pushScopedQueryTriplesFromGraph(tr.s.elems[1], out.queryTriples);
6031
+ }
6032
+ continue;
6033
+ }
6034
+
6035
+ if (pv === LOG_NS + 'forAllIn') {
6036
+ if (tr.o instanceof GraphTerm) continue;
6037
+
6038
+ out.needsSnap = true;
6039
+ out.hasScopedAggregate = true;
6040
+ out.baseLevel = Math.max(out.baseLevel, __scopedPriorityForTerm(tr.o));
6041
+ if (tr.s instanceof ListTerm && tr.s.elems.length === 2) {
6042
+ __pushScopedQueryTriplesFromGraph(tr.s.elems[0], out.queryTriples);
6043
+ __pushScopedQueryTriplesFromGraph(tr.s.elems[1], out.queryTriples);
6044
+ }
6045
+ }
6046
+ }
6047
+
6048
+ return out;
6049
+ }
6050
+
6051
+ function __triplePatternsMayOverlap(a, b) {
6052
+ return unifyTriple(a, b, __emptySubst()) !== null || unifyTriple(b, a, __emptySubst()) !== null;
6053
+ }
6054
+
6055
+ function __ruleConclusionsMayFeedScopedQueries(producer, consumerMeta) {
6056
+ if (!consumerMeta || !consumerMeta.queryTriples || consumerMeta.queryTriples.length === 0) return false;
6057
+
6058
+ // Dynamic conclusions are only known after a proof solution. Conservatively
6059
+ // assume they can feed any scoped query if the producing rule is scoped.
6060
+ if (producer && producer.__dynamicConclusionTerm) return true;
6061
+
6062
+ if (!producer || !Array.isArray(producer.conclusion) || producer.conclusion.length === 0) return false;
6063
+ for (const q of consumerMeta.queryTriples) {
6064
+ for (const h of producer.conclusion) {
6065
+ if (__triplePatternsMayOverlap(q, h)) return true;
6066
+ }
6067
+ }
6068
+ return false;
6069
+ }
6070
+
6071
+ function __setForwardRuleScopedStratumInfo(rule, level) {
6072
+ const value =
6073
+ level > 0
6074
+ ? { needsSnap: true, requiredLevel: level, exactLevel: true }
6075
+ : { needsSnap: false, requiredLevel: 0, exactLevel: false };
6076
+
6077
+ if (!hasOwn.call(rule, '__scopedStratumInfo')) {
6078
+ Object.defineProperty(rule, '__scopedStratumInfo', {
6079
+ value,
6080
+ enumerable: false,
6081
+ writable: true,
6082
+ configurable: true,
6083
+ });
6084
+ } else {
6085
+ rule.__scopedStratumInfo = value;
6086
+ }
6087
+ }
6088
+
6089
+ function __computeForwardRuleScopedStrata(forwardRules) {
6090
+ if (!Array.isArray(forwardRules) || forwardRules.length === 0) return 0;
6091
+
6092
+ const metas = forwardRules.map((r) => __analyzeForwardRuleScopedUse(r));
6093
+ const levels = metas.map((m) => (m.needsSnap ? Math.max(1, m.baseLevel || 1) : 0));
6094
+
6095
+ let maxLevel = 0;
6096
+ for (const lvl of levels) if (lvl > maxLevel) maxLevel = lvl;
6097
+
6098
+ // Stratify scoped rules by data dependency: if a scoped query can read facts
6099
+ // produced by another scoped rule, the reader must run against a later frozen
6100
+ // snapshot. This prevents non-monotonic scoped builtins such as collectAllIn
6101
+ // from emitting an early result before lower strata have derived their facts.
6102
+ const passLimit = Math.max(1, forwardRules.length) + maxLevel + 1;
6103
+ for (let pass = 0; pass < passLimit; pass++) {
6104
+ let changed = false;
6105
+ for (let ci = 0; ci < forwardRules.length; ci++) {
6106
+ if (!metas[ci].needsSnap || !metas[ci].hasScopedAggregate) continue;
6107
+ let needed = levels[ci];
6108
+ for (let pi = 0; pi < forwardRules.length; pi++) {
6109
+ if (pi === ci) continue;
6110
+ if (levels[pi] <= 0) continue;
6111
+ if (metas[pi].hasScopedAggregate) continue;
6112
+ if (!__ruleConclusionsMayFeedScopedQueries(forwardRules[pi], metas[ci])) continue;
6113
+ needed = Math.max(needed, levels[pi] + 1);
6114
+ }
6115
+ if (needed !== levels[ci]) {
6116
+ levels[ci] = needed;
6117
+ if (needed > maxLevel) maxLevel = needed;
6118
+ changed = true;
6119
+ }
6120
+ }
6121
+ if (!changed) break;
6122
+ }
6123
+
6124
+
6125
+ maxLevel = 0;
6126
+ for (let i = 0; i < forwardRules.length; i++) {
6127
+ __setForwardRuleScopedStratumInfo(forwardRules[i], levels[i]);
6128
+ if (levels[i] > maxLevel) maxLevel = levels[i];
6129
+ }
6130
+
6131
+ return maxLevel;
6132
+ }
6133
+
5976
6134
  function __computeForwardRuleScopedSkipInfo(rule) {
5977
6135
  let needsSnap = false;
5978
6136
  let requiredLevel = 0;
@@ -6117,9 +6275,11 @@ const { evalBuiltin, isBuiltinPred } = makeBuiltins({
6117
6275
  });
6118
6276
 
6119
6277
  // Initialize proof/output helpers (implemented in lib/explain.js).
6120
- const { printExplanation, collectOutputStringsFromFacts } = makeExplain({
6278
+ const { printExplanation, renderProofDocument, collectOutputStringsFromFacts } = makeExplain({
6121
6279
  applySubstTerm,
6122
6280
  skolemKeyFromTerm,
6281
+ isBuiltinPred,
6282
+ findBackwardProofForGoal,
6123
6283
  });
6124
6284
 
6125
6285
  function skolemizeTermForHeadBlanks(t, headBlankLabels, mapping, skCounter, firingKey, globalMap) {
@@ -7158,7 +7318,7 @@ function makeSinglePremiseAgendaIndex(forwardRules, backRules) {
7158
7318
  goalPredTid: goal.p instanceof Iri ? goal.p.__tid : null,
7159
7319
  goalSKey,
7160
7320
  goalOKey,
7161
- needsSkipCheck: !!r.__needsForwardSkipCheck,
7321
+ needsSkipCheck: !!(r.__needsForwardSkipCheck || (r.__scopedStratumInfo && r.__scopedStratumInfo.needsSnap)),
7162
7322
  fastSubjectVar,
7163
7323
  fastObjectVar,
7164
7324
  };
@@ -7770,6 +7930,217 @@ function unifyTriple(pat, fact, subst) {
7770
7930
  return s3;
7771
7931
  }
7772
7932
 
7933
+
7934
+ function __copyProofSourceMetadata(from, to) {
7935
+ if (!from || !to) return to;
7936
+ for (const key of ['__sourceOffset', '__source']) {
7937
+ if (hasOwn.call(from, key)) {
7938
+ Object.defineProperty(to, key, {
7939
+ value: from[key],
7940
+ enumerable: false,
7941
+ writable: false,
7942
+ configurable: true,
7943
+ });
7944
+ }
7945
+ }
7946
+ return to;
7947
+ }
7948
+
7949
+ function __standardizeRuleForProof(rule, gen) {
7950
+ const proofVarNames = Object.create(null);
7951
+
7952
+ function renameTerm(t, vmapName, vmapVar, genArr) {
7953
+ if (t instanceof Var) {
7954
+ if (!hasOwn.call(vmapVar, t.name)) {
7955
+ const name = `${t.name}__proof_${genArr[0]}`;
7956
+ genArr[0] += 1;
7957
+ vmapName[t.name] = name;
7958
+ vmapVar[t.name] = new Var(name);
7959
+ proofVarNames[name] = t.name;
7960
+ }
7961
+ return vmapVar[t.name];
7962
+ }
7963
+ if (t instanceof ListTerm) {
7964
+ let changed = false;
7965
+ const elems2 = t.elems.map((e) => {
7966
+ const e2 = renameTerm(e, vmapName, vmapVar, genArr);
7967
+ if (e2 !== e) changed = true;
7968
+ return e2;
7969
+ });
7970
+ return changed ? new ListTerm(elems2) : t;
7971
+ }
7972
+ if (t instanceof OpenListTerm) {
7973
+ let changed = false;
7974
+ const newXs = t.prefix.map((e) => {
7975
+ const e2 = renameTerm(e, vmapName, vmapVar, genArr);
7976
+ if (e2 !== e) changed = true;
7977
+ return e2;
7978
+ });
7979
+ if (!hasOwn.call(vmapName, t.tailVar)) {
7980
+ const name = `${t.tailVar}__proof_${genArr[0]}`;
7981
+ genArr[0] += 1;
7982
+ vmapName[t.tailVar] = name;
7983
+ vmapVar[t.tailVar] = new Var(name);
7984
+ proofVarNames[name] = t.tailVar;
7985
+ }
7986
+ const newTail = vmapName[t.tailVar];
7987
+ if (newTail !== t.tailVar) changed = true;
7988
+ return changed ? new OpenListTerm(newXs, newTail) : t;
7989
+ }
7990
+ if (t instanceof GraphTerm) {
7991
+ let changed = false;
7992
+ const triples2 = t.triples.map((tr) => {
7993
+ const s2 = renameTerm(tr.s, vmapName, vmapVar, genArr);
7994
+ const p2 = renameTerm(tr.p, vmapName, vmapVar, genArr);
7995
+ const o2 = renameTerm(tr.o, vmapName, vmapVar, genArr);
7996
+ if (s2 !== tr.s || p2 !== tr.p || o2 !== tr.o) changed = true;
7997
+ return s2 === tr.s && p2 === tr.p && o2 === tr.o ? tr : new Triple(s2, p2, o2);
7998
+ });
7999
+ return changed ? copyQuotedGraphMetadata(t, new GraphTerm(triples2)) : t;
8000
+ }
8001
+ return t;
8002
+ }
8003
+
8004
+ const vmapName = Object.create(null);
8005
+ const vmapVar = Object.create(null);
8006
+ const premise = rule.premise.map((tr) => {
8007
+ const s2 = renameTerm(tr.s, vmapName, vmapVar, gen);
8008
+ const p2 = renameTerm(tr.p, vmapName, vmapVar, gen);
8009
+ const o2 = renameTerm(tr.o, vmapName, vmapVar, gen);
8010
+ return s2 === tr.s && p2 === tr.p && o2 === tr.o ? tr : new Triple(s2, p2, o2);
8011
+ });
8012
+ const conclusion = rule.conclusion.map((tr) => {
8013
+ const s2 = renameTerm(tr.s, vmapName, vmapVar, gen);
8014
+ const p2 = renameTerm(tr.p, vmapName, vmapVar, gen);
8015
+ const o2 = renameTerm(tr.o, vmapName, vmapVar, gen);
8016
+ return s2 === tr.s && p2 === tr.p && o2 === tr.o ? tr : new Triple(s2, p2, o2);
8017
+ });
8018
+ const out = new Rule(premise, conclusion, rule.isForward, rule.isFuse, rule.headBlankLabels);
8019
+ Object.defineProperty(out, '__proofVarSourceNames', {
8020
+ value: proofVarNames,
8021
+ enumerable: false,
8022
+ writable: false,
8023
+ configurable: true,
8024
+ });
8025
+ return __copyProofSourceMetadata(rule, out);
8026
+ }
8027
+
8028
+ function __mergeProofSubst(base, delta) {
8029
+ const out = __cloneSubst(base);
8030
+ for (const k in delta || {}) {
8031
+ if (!hasOwn.call(delta, k)) continue;
8032
+ const v = applySubstTerm(delta[k], out);
8033
+ if (hasOwn.call(out, k)) {
8034
+ if (!termsEqual(applySubstTerm(out[k], out), v)) return null;
8035
+ } else {
8036
+ out[k] = v;
8037
+ }
8038
+ }
8039
+ return out;
8040
+ }
8041
+
8042
+ function __sourceSortKey(obj) {
8043
+ const src = obj && obj.__source;
8044
+ const label = src && typeof src.label === 'string' ? src.label : '';
8045
+ const line = src && Number.isInteger(src.line) ? src.line : 0;
8046
+ return `${label}\t${line}`;
8047
+ }
8048
+
8049
+ function __candidateFactsForProof(facts, goal) {
8050
+ if (!Array.isArray(facts)) return [];
8051
+ ensureFactIndexes(facts);
8052
+ const candidates = candidateFacts(facts, goal);
8053
+ if (!candidates) return facts;
8054
+ const out = [];
8055
+ for (let i = 0; i < candidates.exactLen; i++) out.push(facts[candidates.exact[i]]);
8056
+ for (let i = 0; i < candidates.wildLen; i++) out.push(facts[candidates.wild[i]]);
8057
+ return out;
8058
+ }
8059
+
8060
+ function findBackwardProofForGoal(goal, facts, backRules, opts = {}) {
8061
+ const maxDepth = Number.isInteger(opts.maxDepth) && opts.maxDepth > 0 ? opts.maxDepth : 64;
8062
+ const varGen = [0];
8063
+
8064
+ function proofKey(tr) {
8065
+ return `${skolemKeyFromTerm(tr.s)}\t${skolemKeyFromTerm(tr.p)}\t${skolemKeyFromTerm(tr.o)}`;
8066
+ }
8067
+
8068
+ function proveGoal(goal0, subst, depth, visited) {
8069
+ if (depth > maxDepth) return [];
8070
+ const g = applySubstTriple(goal0, subst);
8071
+ const key = proofKey(g);
8072
+ if (visited.has(key)) return [];
8073
+
8074
+ if (isBuiltinPred(g.p)) {
8075
+ const builtinGoalForEval = g.p instanceof Iri && g.p.value === LOG_NS + 'rawType' ? goal0 : g;
8076
+ const builtinSubstForEval = g.p instanceof Iri && g.p.value === LOG_NS + 'rawType' ? subst : __emptySubst();
8077
+ const deltas = evalBuiltin(builtinGoalForEval, builtinSubstForEval, facts, backRules, depth, varGen, 1) || [];
8078
+ const out = [];
8079
+ for (const delta of deltas) {
8080
+ const subst2 = __mergeProofSubst(subst, delta);
8081
+ if (subst2 === null) continue;
8082
+ const fact = applySubstTriple(g, subst2);
8083
+ out.push({ subst: subst2, proof: { kind: 'builtin', fact, builtin: fact.p } });
8084
+ break;
8085
+ }
8086
+ return out;
8087
+ }
8088
+
8089
+ const factResults = [];
8090
+ for (const f of __candidateFactsForProof(facts, g)) {
8091
+ const subst2 = unifyTriple(g, f, subst);
8092
+ if (subst2 === null) continue;
8093
+ factResults.push({ subst: subst2, proof: { kind: 'fact', fact: applySubstTriple(f, subst2), source: f.__source } });
8094
+ break;
8095
+ }
8096
+ if (factResults.length) return factResults;
8097
+
8098
+ if (!(g.p instanceof Iri)) return [];
8099
+ ensureBackRuleIndexes(backRules);
8100
+ const rules = (backRules.__byHeadPred.get(g.p.__tid) || []).concat(backRules.__wildHeadPred || []);
8101
+ rules.sort((a, b) => (__sourceSortKey(a) < __sourceSortKey(b) ? -1 : __sourceSortKey(a) > __sourceSortKey(b) ? 1 : 0));
8102
+
8103
+ const visited2 = new Set(visited);
8104
+ visited2.add(key);
8105
+
8106
+ for (const r of rules) {
8107
+ if (!r || !Array.isArray(r.conclusion) || r.conclusion.length !== 1) continue;
8108
+ const rStd = __standardizeRuleForProof(r, varGen);
8109
+ const head = rStd.conclusion[0];
8110
+ if (head.p instanceof Iri && head.p.__tid !== g.p.__tid) continue;
8111
+ const subst1 = unifyTriple(head, g, subst);
8112
+ if (subst1 === null) continue;
8113
+ const bodyProof = proveGoalList(rStd.premise || [], subst1, depth + 1, visited2);
8114
+ if (!bodyProof) continue;
8115
+ const fact = applySubstTriple(g, bodyProof.subst);
8116
+ const premises = (rStd.premise || []).map((prem) => applySubstTriple(prem, bodyProof.subst));
8117
+ const df = new DerivedFact(fact, rStd, premises, bodyProof.subst);
8118
+ return [{ subst: bodyProof.subst, proof: { kind: 'rule', df, children: bodyProof.proofs } }];
8119
+ }
8120
+
8121
+ return [];
8122
+ }
8123
+
8124
+ function proveGoalList(goals, subst, depth, visited) {
8125
+ let states = [{ subst, proofs: [] }];
8126
+ for (const goal1 of goals || []) {
8127
+ const next = [];
8128
+ for (const state of states) {
8129
+ const proofs = proveGoal(goal1, state.subst, depth, visited);
8130
+ for (const pr of proofs) next.push({ subst: pr.subst, proofs: state.proofs.concat([pr.proof]) });
8131
+ }
8132
+ if (!next.length) return null;
8133
+ // Keep proof reconstruction deterministic and cheap: one proof is enough
8134
+ // for an explanation of the already-derived enclosing fact.
8135
+ states = [next[0]];
8136
+ }
8137
+ return states[0] || { subst, proofs: [] };
8138
+ }
8139
+
8140
+ const found = proveGoal(goal, __emptySubst(), 0, new Set());
8141
+ return found.length ? found[0].proof : null;
8142
+ }
8143
+
7773
8144
  // Strategy: when the substitution is "large" or search depth is high,
7774
8145
  // keep only bindings that are still relevant to:
7775
8146
  // - variables appearing in the remaining goals
@@ -8606,7 +8977,10 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
8606
8977
  let scopedClosureLevel = 0;
8607
8978
 
8608
8979
  // Scan known rules for the maximum requested closure priority in scoped log:* goals.
8609
- let maxScopedClosurePriorityNeeded = __computeMaxScopedClosurePriorityNeeded(forwardRules, backRules);
8980
+ let maxScopedClosurePriorityNeeded = Math.max(
8981
+ __computeMaxScopedClosurePriorityNeeded(forwardRules, backRules),
8982
+ __computeForwardRuleScopedStrata(forwardRules),
8983
+ );
8610
8984
 
8611
8985
  // Conservative fast-skip for forward rules that cannot possibly succeed
8612
8986
  // until a scoped snapshot exists (or a given closure level is reached).
@@ -8660,12 +9034,16 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
8660
9034
  // until a snapshot exists (and a certain closure level is reached).
8661
9035
  // This prevents expensive proofs that will definitely fail in Phase A
8662
9036
  // and in early closure levels.
8663
- const info = r.__scopedSkipInfo;
9037
+ const info = r.__scopedStratumInfo || r.__scopedSkipInfo;
8664
9038
  if (info && info.needsSnap) {
8665
9039
  const snapHere = facts.__scopedSnapshot || null;
8666
9040
  const lvlHere = (facts && typeof facts.__scopedClosureLevel === 'number' && facts.__scopedClosureLevel) || 0;
8667
9041
  if (!snapHere) return true;
8668
- if (lvlHere < info.requiredLevel) return true;
9042
+ if (info.exactLevel) {
9043
+ if (lvlHere !== info.requiredLevel) return true;
9044
+ } else if (lvlHere < info.requiredLevel) {
9045
+ return true;
9046
+ }
8669
9047
  }
8670
9048
 
8671
9049
  // Optimization: if the rule head is **structurally ground** (no vars anywhere, even inside
@@ -8865,6 +9243,10 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
8865
9243
 
8866
9244
  const outcome = __emitForwardRuleSolution(r, entry.ruleIndex, s);
8867
9245
  if (outcome.rulesChanged) {
9246
+ maxScopedClosurePriorityNeeded = Math.max(
9247
+ __computeMaxScopedClosurePriorityNeeded(forwardRules, backRules),
9248
+ __computeForwardRuleScopedStrata(forwardRules),
9249
+ );
8868
9250
  agendaIndex = makeSinglePremiseAgendaIndex(forwardRules, backRules);
8869
9251
  agendaCursor = 0;
8870
9252
  }
@@ -8878,7 +9260,7 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
8878
9260
  for (let i = 0; i < forwardRules.length; i++) {
8879
9261
  const r = forwardRules[i];
8880
9262
  if (agendaIndex.indexed.has(r)) continue;
8881
- if (r.__needsForwardSkipCheck && __skipForwardRuleNow(r)) continue;
9263
+ if ((r.__needsForwardSkipCheck || (r.__scopedStratumInfo && r.__scopedStratumInfo.needsSnap)) && __skipForwardRuleNow(r)) continue;
8882
9264
 
8883
9265
  const headIsStrictGround = r.__headIsStrictGround;
8884
9266
  const maxSols = r.isFuse || headIsStrictGround ? 1 : undefined;
@@ -8899,6 +9281,10 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
8899
9281
  for (const s of sols) {
8900
9282
  const outcome = __emitForwardRuleSolution(r, i, s);
8901
9283
  if (outcome.rulesChanged) {
9284
+ maxScopedClosurePriorityNeeded = Math.max(
9285
+ __computeMaxScopedClosurePriorityNeeded(forwardRules, backRules),
9286
+ __computeForwardRuleScopedStrata(forwardRules),
9287
+ );
8902
9288
  agendaIndex = makeSinglePremiseAgendaIndex(forwardRules, backRules);
8903
9289
  agendaCursor = 0;
8904
9290
  }
@@ -8926,8 +9312,8 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
8926
9312
  // Rules may have been added dynamically (rule-producing triples), possibly
8927
9313
  // introducing scoped builtins and/or higher closure priorities.
8928
9314
  maxScopedClosurePriorityNeeded = Math.max(
8929
- maxScopedClosurePriorityNeeded,
8930
9315
  __computeMaxScopedClosurePriorityNeeded(forwardRules, backRules),
9316
+ __computeForwardRuleScopedStrata(forwardRules),
8931
9317
  );
8932
9318
 
8933
9319
  // If there are no scoped builtins in the entire program, Phase B is pure
@@ -8945,8 +9331,8 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
8945
9331
 
8946
9332
  // Phase B can also derive rule-producing triples.
8947
9333
  maxScopedClosurePriorityNeeded = Math.max(
8948
- maxScopedClosurePriorityNeeded,
8949
9334
  __computeMaxScopedClosurePriorityNeeded(forwardRules, backRules),
9335
+ __computeForwardRuleScopedStrata(forwardRules),
8950
9336
  );
8951
9337
 
8952
9338
  if (!changedA && !changedB && scopedClosureLevel >= maxScopedClosurePriorityNeeded) break;
@@ -9269,11 +9655,19 @@ function reasonStream(input, opts = {}) {
9269
9655
  ? facts
9270
9656
  : derived.map((d) => d.fact);
9271
9657
 
9272
- const closureN3 = useRdfCompatibility
9273
- ? closureTriples.map((t) => tripleToRdfCompatible(t, prefixes)).join('\n')
9274
- : Array.isArray(logQueryRules) && logQueryRules.length && !proof
9275
- ? prettyPrintQueryTriples(closureTriples, prefixes)
9276
- : closureTriples.map((t) => tripleToN3(t, prefixes)).join('\n');
9658
+ const closureN3 = proof
9659
+ ? renderProofDocument(
9660
+ Array.isArray(logQueryRules) && logQueryRules.length ? queryDerived : derived,
9661
+ derived,
9662
+ triples,
9663
+ prefixes,
9664
+ brules,
9665
+ ).replace(/\n$/g, '')
9666
+ : useRdfCompatibility
9667
+ ? closureTriples.map((t) => tripleToRdfCompatible(t, prefixes)).join('\n')
9668
+ : Array.isArray(logQueryRules) && logQueryRules.length
9669
+ ? prettyPrintQueryTriples(closureTriples, prefixes)
9670
+ : closureTriples.map((t) => tripleToN3(t, prefixes)).join('\n');
9277
9671
 
9278
9672
  const __out = {
9279
9673
  prefixes,
@@ -9400,6 +9794,7 @@ module.exports = {
9400
9794
  collectLogQueryConclusions,
9401
9795
  forwardChainAndCollectLogQueryConclusions,
9402
9796
  collectOutputStringsFromFacts,
9797
+ renderProofDocument,
9403
9798
  main,
9404
9799
  version,
9405
9800
  N3SyntaxError,
@@ -9469,6 +9864,7 @@ module.exports = {
9469
9864
  materializeRdfLists: engine.materializeRdfLists,
9470
9865
  isGroundTriple: engine.isGroundTriple,
9471
9866
  printExplanation: engine.printExplanation,
9867
+ renderProofDocument: engine.renderProofDocument,
9472
9868
  tripleToN3: engine.tripleToN3,
9473
9869
  collectOutputStringsFromFacts: engine.collectOutputStringsFromFacts,
9474
9870
  prettyPrintQueryTriples: engine.prettyPrintQueryTriples,
@@ -9495,7 +9891,7 @@ module.exports = {
9495
9891
  */
9496
9892
  'use strict';
9497
9893
 
9498
- const { LOG_NS, Literal, Iri, Blank, Var, varsInRule, literalParts } = require('./prelude');
9894
+ const { LOG_NS, Literal, Iri, Blank, Var, GraphTerm, varsInRule, literalParts, PrefixEnv } = require('./prelude');
9499
9895
 
9500
9896
  const { termToN3, tripleToN3 } = require('./printing');
9501
9897
  const { parseNumericLiteralInfo, termToJsString } = require('./builtins');
@@ -9503,6 +9899,8 @@ const { parseNumericLiteralInfo, termToJsString } = require('./builtins');
9503
9899
  function makeExplain(deps) {
9504
9900
  const applySubstTerm = deps.applySubstTerm;
9505
9901
  const skolemKeyFromTerm = deps.skolemKeyFromTerm;
9902
+ const isBuiltinPred = typeof deps.isBuiltinPred === 'function' ? deps.isBuiltinPred : () => false;
9903
+ const findBackwardProofForGoal = typeof deps.findBackwardProofForGoal === 'function' ? deps.findBackwardProofForGoal : null;
9506
9904
 
9507
9905
  function printExplanation(df, prefixes) {
9508
9906
  console.log('# ----------------------------------------------------------------------');
@@ -9600,6 +9998,282 @@ function makeExplain(deps) {
9600
9998
  console.log('# ----------------------------------------------------------------------\n');
9601
9999
  }
9602
10000
 
10001
+
10002
+ const PE_NS = 'https://eyereasoner.github.io/pe#';
10003
+
10004
+ function n3String(value) {
10005
+ return JSON.stringify(String(value));
10006
+ }
10007
+
10008
+ function lineIndent(text, prefix) {
10009
+ return String(text)
10010
+ .split(/\r?\n/)
10011
+ .map((line) => (line.length ? prefix + line : line))
10012
+ .join('\n');
10013
+ }
10014
+
10015
+ function graphForTriple(tr, prefixes) {
10016
+ const body = tripleToN3(tr, prefixes).trimEnd();
10017
+ if (!body.includes('\n')) return `{ ${body} }`;
10018
+ return `{
10019
+ ${lineIndent(body, ' ')}
10020
+ }`;
10021
+ }
10022
+
10023
+
10024
+ function clonePrefixEnvWithProofVocabulary(prefixes) {
10025
+ const map = { ...(prefixes && prefixes.map ? prefixes.map : {}) };
10026
+ if (!map.pe) map.pe = PE_NS;
10027
+ return new PrefixEnv(map, (prefixes && prefixes.baseIri) || '');
10028
+ }
10029
+
10030
+ function proofTripleKey(tr) {
10031
+ if (!tr) return '';
10032
+ return `${skolemKeyFromTerm(tr.s)}\t${skolemKeyFromTerm(tr.p)}\t${skolemKeyFromTerm(tr.o)}`;
10033
+ }
10034
+
10035
+ function sourceLabelForProof(source) {
10036
+ if (!source || typeof source.label !== 'string' || !source.label) return '<unknown>';
10037
+ // Keep proof output stable when the CLI is invoked with paths such as examples/foo.n3.
10038
+ return source.label.replace(/\\/g, '/').split('/').pop() || source.label;
10039
+ }
10040
+
10041
+ function byBlankNode(kind, source) {
10042
+ const src = source || {};
10043
+ const props = [`pe:${kind} ${n3String(sourceLabelForProof(src))}`];
10044
+ if (Number.isInteger(src.line) && src.line > 0) props.push(`pe:line ${src.line}`);
10045
+ return `[ ${props.join('; ')} ]`;
10046
+ }
10047
+
10048
+ function renderBindingList(df, prefixes) {
10049
+ if (!df || !df.rule || !df.subst) return '';
10050
+ const ruleVars = varsInRule(df.rule);
10051
+ const visibleNames = Object.keys(df.subst)
10052
+ .filter((name) => ruleVars.has(name))
10053
+ .sort();
10054
+ if (!visibleNames.length) return '';
10055
+ const sourceNames = (df.rule && df.rule.__proofVarSourceNames) || null;
10056
+ return visibleNames
10057
+ .map((name) => {
10058
+ const value = applySubstTerm(new Var(name), df.subst);
10059
+ const displayName = sourceNames && sourceNames[name] ? sourceNames[name] : name;
10060
+ return `[ pe:var ${n3String(displayName)}; pe:value ${termToN3(value, prefixes)} ]`;
10061
+ })
10062
+ .join(', ');
10063
+ }
10064
+
10065
+ function proofSourceKey(source) {
10066
+ if (!source) return '';
10067
+ const label = typeof source.label === 'string' ? source.label : '';
10068
+ const line = Number.isInteger(source.line) ? source.line : 0;
10069
+ return `${label}\t${line}`;
10070
+ }
10071
+
10072
+ function proofEntryKey(kind, fact, source, extra) {
10073
+ return `${kind}\t${proofTripleKey(fact)}\t${proofSourceKey(source)}\t${extra || ''}`;
10074
+ }
10075
+
10076
+ function ruleEntryKey(df) {
10077
+ const rule = df && df.rule;
10078
+ const premises = (df && df.premises ? df.premises : []).map((prem) => proofTripleKey(prem)).join('\t');
10079
+ return proofEntryKey('rule', df && df.fact, rule && rule.__source, premises);
10080
+ }
10081
+
10082
+ function collectProofEntries(rootDf, derivedByKey, baseFactByKey, resolveBackwardProof) {
10083
+ const entries = [];
10084
+ const seen = new Set();
10085
+
10086
+ function entryKeyForProof(proof, fallbackTriple) {
10087
+ if (!proof) return proofEntryKey('fact', fallbackTriple, null);
10088
+ if (proof.kind === 'rule') return ruleEntryKey(proof.df);
10089
+ if (proof.kind === 'builtin') return proofEntryKey('builtin', proof.fact || fallbackTriple, null);
10090
+ return proofEntryKey('fact', proof.fact || fallbackTriple, proof.source);
10091
+ }
10092
+
10093
+ function visitProofNode(proof, fallbackTriple) {
10094
+ if (!proof) {
10095
+ visitFactTriple(fallbackTriple);
10096
+ return;
10097
+ }
10098
+ const key = entryKeyForProof(proof, fallbackTriple);
10099
+ if (!key || seen.has(key)) return;
10100
+ if (proof.kind === 'rule' && proof.df) {
10101
+ visitDerivedFact(proof.df, proof.children || null);
10102
+ return;
10103
+ }
10104
+ seen.add(key);
10105
+ if (proof.kind === 'builtin') {
10106
+ entries.push({
10107
+ kind: 'builtin',
10108
+ fact: proof.fact || fallbackTriple,
10109
+ builtin: proof.builtin || (proof.fact && proof.fact.p),
10110
+ });
10111
+ } else {
10112
+ entries.push({ kind: 'fact', fact: proof.fact || fallbackTriple, source: proof.source });
10113
+ }
10114
+ }
10115
+
10116
+ function visitFactTriple(tr) {
10117
+ const tripleKey = proofTripleKey(tr);
10118
+ if (!tripleKey) return;
10119
+ const df = derivedByKey.get(tripleKey);
10120
+ if (df) {
10121
+ visitDerivedFact(df);
10122
+ return;
10123
+ }
10124
+ const base = baseFactByKey.get(tripleKey) || null;
10125
+ if (base) {
10126
+ const key = proofEntryKey('fact', base, base.__source);
10127
+ if (seen.has(key)) return;
10128
+ seen.add(key);
10129
+ entries.push({ kind: 'fact', fact: base, source: base.__source });
10130
+ return;
10131
+ }
10132
+ if (tr && isBuiltinPred(tr.p)) {
10133
+ const key = proofEntryKey('builtin', tr, null);
10134
+ if (seen.has(key)) return;
10135
+ seen.add(key);
10136
+ entries.push({ kind: 'builtin', fact: tr, builtin: tr.p });
10137
+ return;
10138
+ }
10139
+ if (resolveBackwardProof) {
10140
+ const proof = resolveBackwardProof(tr);
10141
+ if (proof) {
10142
+ visitProofNode(proof, tr);
10143
+ return;
10144
+ }
10145
+ }
10146
+ const key = proofEntryKey('fact', tr, null);
10147
+ if (seen.has(key)) return;
10148
+ seen.add(key);
10149
+ entries.push({ kind: 'fact', fact: tr, source: null });
10150
+ }
10151
+
10152
+ function visitDerivedFact(df, children) {
10153
+ const key = ruleEntryKey(df);
10154
+ if (!key || seen.has(key)) return;
10155
+ seen.add(key);
10156
+ entries.push({ kind: 'rule', df });
10157
+
10158
+ if (Array.isArray(children) && children.length) {
10159
+ for (const child of children) visitProofNode(child);
10160
+ return;
10161
+ }
10162
+ for (const prem of df.premises || []) visitFactTriple(prem);
10163
+ }
10164
+
10165
+ visitDerivedFact(rootDf);
10166
+ return entries;
10167
+ }
10168
+
10169
+ function collectProofOutputTriples(outputDerived) {
10170
+ const out = [];
10171
+ const seen = new Set();
10172
+ for (const df of outputDerived || []) {
10173
+ if (!df || !df.fact) continue;
10174
+ const key = proofTripleKey(df.fact);
10175
+ if (seen.has(key)) continue;
10176
+ seen.add(key);
10177
+ out.push(df.fact);
10178
+ }
10179
+ return out;
10180
+ }
10181
+
10182
+ function renderProofEntry(entry, prefixes) {
10183
+ if (!entry) return '';
10184
+ if (entry.kind === 'fact') {
10185
+ return ` ${graphForTriple(entry.fact, prefixes)}\n pe:by ${byBlankNode('fact', entry.source)}.`;
10186
+ }
10187
+ if (entry.kind === 'builtin') {
10188
+ return ` ${graphForTriple(entry.fact, prefixes)}\n pe:by [ pe:builtin ${termToN3(entry.builtin, prefixes)} ].`;
10189
+ }
10190
+
10191
+ const df = entry.df;
10192
+ const props = [`pe:by ${byBlankNode('rule', df.rule && df.rule.__source)}`];
10193
+ const bindings = renderBindingList(df, prefixes);
10194
+ if (bindings) props.push(`pe:binding ${bindings}`);
10195
+ if (df.premises && df.premises.length) {
10196
+ props.push(`pe:uses ${df.premises.map((prem) => graphForTriple(prem, prefixes)).join(', ')}`);
10197
+ }
10198
+
10199
+ let out = ` ${graphForTriple(df.fact, prefixes)}\n`;
10200
+ for (let i = 0; i < props.length; i++) {
10201
+ out += ` ${props[i]}${i === props.length - 1 ? '.' : ';'}\n`;
10202
+ }
10203
+ return out.trimEnd();
10204
+ }
10205
+
10206
+ function renderProofBlock(rootDf, derivedByKey, baseFactByKey, prefixes, resolveBackwardProof) {
10207
+ const entries = collectProofEntries(rootDf, derivedByKey, baseFactByKey, resolveBackwardProof);
10208
+ const rootGraph = graphForTriple(rootDf.fact, prefixes);
10209
+ const proofBody = entries.map((entry) => renderProofEntry(entry, prefixes)).join('\n\n');
10210
+ const proofGraph = proofBody ? `{
10211
+ ${proofBody}
10212
+ }` : '{}';
10213
+ return `${rootGraph} pe:why ${proofGraph}.`;
10214
+ }
10215
+
10216
+ function renderProofDocument(outputDerived, allDerived, baseFacts, prefixes, backRules) {
10217
+ const selectedDerived = Array.isArray(outputDerived) ? outputDerived.filter((df) => df && df.fact) : [];
10218
+ if (!selectedDerived.length) return '';
10219
+
10220
+ const proofPrefixes = clonePrefixEnvWithProofVocabulary(prefixes);
10221
+ const derivedByKey = new Map();
10222
+ for (const df of allDerived || []) {
10223
+ if (!df || !df.fact) continue;
10224
+ const key = proofTripleKey(df.fact);
10225
+ if (!derivedByKey.has(key)) derivedByKey.set(key, df);
10226
+ }
10227
+ // Do not insert query-wrapper output facts here: those wrappers often derive
10228
+ // the same triple they use, and would hide the underlying fact/rule proof.
10229
+
10230
+ const baseFactByKey = new Map();
10231
+ for (const tr of baseFacts || []) {
10232
+ if (!tr) continue;
10233
+ const key = proofTripleKey(tr);
10234
+ if (!baseFactByKey.has(key)) baseFactByKey.set(key, tr);
10235
+ }
10236
+
10237
+ const resolveBackwardProof = findBackwardProofForGoal
10238
+ ? (tr) => findBackwardProofForGoal(tr, baseFacts || [], backRules || [], { maxDepth: 64 })
10239
+ : null;
10240
+
10241
+ const outputTriples = collectProofOutputTriples(selectedDerived);
10242
+ const proofRelationTriples = [];
10243
+ for (const df of selectedDerived) {
10244
+ proofRelationTriples.push({ s: new GraphTerm([df.fact]), p: new Iri(PE_NS + 'why'), o: new GraphTerm([]) });
10245
+ for (const entry of collectProofEntries(df, derivedByKey, baseFactByKey, resolveBackwardProof)) {
10246
+ const fact = entry.kind === 'rule' ? entry.df.fact : entry.fact;
10247
+ const byObject = entry.kind === 'builtin' && entry.builtin ? entry.builtin : new Iri(PE_NS + 'source');
10248
+ proofRelationTriples.push({ s: new GraphTerm([fact]), p: new Iri(PE_NS + 'by'), o: byObject });
10249
+ if (entry.kind === 'rule') {
10250
+ for (const prem of entry.df.premises || []) proofRelationTriples.push({ s: new GraphTerm([fact]), p: new Iri(PE_NS + 'uses'), o: new GraphTerm([prem]) });
10251
+ }
10252
+ }
10253
+ }
10254
+
10255
+ const usedPrefixes = proofPrefixes.prefixesUsedForOutput(outputTriples.concat(proofRelationTriples));
10256
+ if (!usedPrefixes.some(([pfx]) => pfx === 'pe')) usedPrefixes.push(['pe', PE_NS]);
10257
+ usedPrefixes.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
10258
+
10259
+ const parts = [];
10260
+ for (const [pfx, base] of usedPrefixes) {
10261
+ if (!base) continue;
10262
+ if (pfx === '') parts.push(`@prefix : <${base}> .`);
10263
+ else parts.push(`@prefix ${pfx}: <${base}> .`);
10264
+ }
10265
+ if (parts.length) parts.push('');
10266
+
10267
+ parts.push(...outputTriples.map((tr) => tripleToN3(tr, proofPrefixes)));
10268
+ parts.push('');
10269
+ for (let i = 0; i < selectedDerived.length; i++) {
10270
+ if (i > 0) parts.push('');
10271
+ parts.push(renderProofBlock(selectedDerived[i], derivedByKey, baseFactByKey, proofPrefixes, resolveBackwardProof));
10272
+ }
10273
+
10274
+ return parts.join('\n').replace(/[ \t]+$/gm, '').replace(/\s*$/g, '') + '\n';
10275
+ }
10276
+
9603
10277
  // ===========================================================================
9604
10278
  // CLI entry point
9605
10279
  // ===========================================================================
@@ -9706,7 +10380,7 @@ function makeExplain(deps) {
9706
10380
  return addMarkdownHardBreaks(pairs.map((p) => p.text).join(''));
9707
10381
  }
9708
10382
 
9709
- return { printExplanation, collectOutputStringsFromFacts };
10383
+ return { printExplanation, renderProofDocument, collectOutputStringsFromFacts };
9710
10384
  }
9711
10385
 
9712
10386
  module.exports = { makeExplain };
@@ -11498,6 +12172,67 @@ function emptyParsedDocument() {
11498
12172
  };
11499
12173
  }
11500
12174
 
12175
+ function lineStartsForText(text) {
12176
+ const s = String(text);
12177
+ const starts = [0];
12178
+ for (let i = 0; i < s.length; i++) {
12179
+ const c = s.charCodeAt(i);
12180
+ if (c === 10) starts.push(i + 1); // LF
12181
+ else if (c === 13) { // CR or CRLF
12182
+ if (i + 1 < s.length && s.charCodeAt(i + 1) === 10) i++;
12183
+ starts.push(i + 1);
12184
+ }
12185
+ }
12186
+ return starts;
12187
+ }
12188
+
12189
+ function offsetToLineFromStarts(offset, lineStarts) {
12190
+ if (typeof offset !== 'number') return null;
12191
+ let lo = 0;
12192
+ let hi = lineStarts.length;
12193
+ while (lo + 1 < hi) {
12194
+ const mid = (lo + hi) >> 1;
12195
+ if (lineStarts[mid] <= offset) lo = mid;
12196
+ else hi = mid;
12197
+ }
12198
+ return lo + 1;
12199
+ }
12200
+
12201
+ function annotateSourceLocation(obj, label, lineStarts) {
12202
+ if (!obj || typeof obj.__sourceOffset !== 'number') return obj;
12203
+ const line = offsetToLineFromStarts(obj.__sourceOffset, lineStarts);
12204
+ Object.defineProperty(obj, '__source', {
12205
+ value: { label, line },
12206
+ enumerable: false,
12207
+ writable: false,
12208
+ configurable: true,
12209
+ });
12210
+ return obj;
12211
+ }
12212
+
12213
+ function annotateParsedSourceLocations(doc, text, label) {
12214
+ const lineStarts = lineStartsForText(text);
12215
+ for (const tr of doc.triples || []) annotateSourceLocation(tr, label, lineStarts);
12216
+ for (const r of doc.frules || []) annotateSourceLocation(r, label, lineStarts);
12217
+ for (const r of doc.brules || []) annotateSourceLocation(r, label, lineStarts);
12218
+ for (const r of doc.logQueryRules || []) annotateSourceLocation(r, label, lineStarts);
12219
+ }
12220
+
12221
+ function copySourceMetadata(from, to) {
12222
+ if (!from || !to) return to;
12223
+ for (const key of ['__sourceOffset', '__source']) {
12224
+ if (Object.prototype.hasOwnProperty.call(from, key)) {
12225
+ Object.defineProperty(to, key, {
12226
+ value: from[key],
12227
+ enumerable: false,
12228
+ writable: false,
12229
+ configurable: true,
12230
+ });
12231
+ }
12232
+ }
12233
+ return to;
12234
+ }
12235
+
11501
12236
  function prefixesUsedInTokens(tokens, prefEnv) {
11502
12237
  const used = new Set();
11503
12238
  const toks = Array.isArray(tokens) ? tokens : [];
@@ -11575,6 +12310,7 @@ function parseN3Text(text, opts = {}) {
11575
12310
  label = '<input>',
11576
12311
  keepSourceArtifacts = true,
11577
12312
  collectUsedPrefixes = false,
12313
+ collectSourceLocations = true,
11578
12314
  rdf = false,
11579
12315
  } = opts || {};
11580
12316
  const tokens = lex(text, { rdf });
@@ -11583,6 +12319,7 @@ function parseN3Text(text, opts = {}) {
11583
12319
  const [prefixes, triples, frules, brules, logQueryRules] = parser.parseDocument();
11584
12320
 
11585
12321
  const doc = { prefixes, triples, frules, brules, logQueryRules, label };
12322
+ if (collectSourceLocations) annotateParsedSourceLocations(doc, text, label);
11586
12323
 
11587
12324
  if (collectUsedPrefixes) {
11588
12325
  Object.defineProperty(doc, 'usedPrefixes', {
@@ -11628,7 +12365,7 @@ function scopeBlankNodesInDocument(doc, sourceIndex) {
11628
12365
  }
11629
12366
 
11630
12367
  function cloneTriple(triple) {
11631
- return new Triple(cloneTerm(triple.s), cloneTerm(triple.p), cloneTerm(triple.o));
12368
+ return copySourceMetadata(triple, new Triple(cloneTerm(triple.s), cloneTerm(triple.p), cloneTerm(triple.o)));
11632
12369
  }
11633
12370
 
11634
12371
  function cloneRule(rule) {
@@ -11637,12 +12374,15 @@ function scopeBlankNodesInDocument(doc, sourceIndex) {
11637
12374
  for (const label of rule.headBlankLabels) headBlankLabels.add(scopedBlankLabel(label, sourceIndex, mapping));
11638
12375
  }
11639
12376
 
11640
- const out = new Rule(
11641
- (rule.premise || []).map(cloneTriple),
11642
- (rule.conclusion || []).map(cloneTriple),
11643
- rule.isForward,
11644
- rule.isFuse,
11645
- headBlankLabels,
12377
+ const out = copySourceMetadata(
12378
+ rule,
12379
+ new Rule(
12380
+ (rule.premise || []).map(cloneTriple),
12381
+ (rule.conclusion || []).map(cloneTriple),
12382
+ rule.isForward,
12383
+ rule.isFuse,
12384
+ headBlankLabels,
12385
+ ),
11646
12386
  );
11647
12387
 
11648
12388
  if (rule && Object.prototype.hasOwnProperty.call(rule, '__dynamicConclusionTerm')) {
@@ -11770,6 +12510,7 @@ function parseN3SourceList(input, opts = {}) {
11770
12510
  label: source.label,
11771
12511
  baseIri: source.baseIri || (sources.length === 1 ? defaultBaseIri : ''),
11772
12512
  collectUsedPrefixes: true,
12513
+ collectSourceLocations: !!opts.collectSourceLocations,
11773
12514
  keepSourceArtifacts: !!opts.keepSourceArtifacts,
11774
12515
  rdf: !!opts.rdf,
11775
12516
  }),
@@ -11836,6 +12577,17 @@ function failInvalidKeywordLikeIdent(fail, tok, name) {
11836
12577
  fail(`invalid_keyword(${name})`, tok);
11837
12578
  }
11838
12579
 
12580
+ function annotateSourceOffset(obj, offset) {
12581
+ if (!obj || typeof offset !== 'number') return obj;
12582
+ Object.defineProperty(obj, '__sourceOffset', {
12583
+ value: offset,
12584
+ enumerable: false,
12585
+ writable: false,
12586
+ configurable: true,
12587
+ });
12588
+ return obj;
12589
+ }
12590
+
11839
12591
  class Parser {
11840
12592
  constructor(tokens) {
11841
12593
  this.toks = tokens;
@@ -11960,17 +12712,19 @@ class Parser {
11960
12712
  continue;
11961
12713
  }
11962
12714
 
12715
+ const statementToken = this.peek();
12716
+ const statementOffset = statementToken && typeof statementToken.offset === 'number' ? statementToken.offset : null;
11963
12717
  const first = this.parseTerm();
11964
12718
  if (this.peek().typ === 'OpImplies') {
11965
12719
  this.next();
11966
12720
  const second = this.parseTerm();
11967
12721
  this.expectDot();
11968
- forwardRules.push(this.makeRule(first, second, true));
12722
+ forwardRules.push(annotateSourceOffset(this.makeRule(first, second, true), statementOffset));
11969
12723
  } else if (this.peek().typ === 'OpImpliedBy') {
11970
12724
  this.next();
11971
12725
  const second = this.parseTerm();
11972
12726
  this.expectDot();
11973
- backwardRules.push(this.makeRule(first, second, false));
12727
+ backwardRules.push(annotateSourceOffset(this.makeRule(first, second, false), statementOffset));
11974
12728
  } else {
11975
12729
  let more;
11976
12730
 
@@ -11988,16 +12742,17 @@ class Parser {
11988
12742
  }
11989
12743
 
11990
12744
  // normalize explicit log:implies / log:impliedBy at top-level
11991
- for (const tr of more) {
12745
+ for (const tr0 of more) {
12746
+ const tr = annotateSourceOffset(tr0, statementOffset);
11992
12747
  if (isLogImplies(tr.p) && tr.s instanceof GraphTerm && tr.o instanceof GraphTerm) {
11993
- forwardRules.push(this.makeRule(tr.s, tr.o, true));
12748
+ forwardRules.push(annotateSourceOffset(this.makeRule(tr.s, tr.o, true), statementOffset));
11994
12749
  } else if (isLogImpliedBy(tr.p) && tr.s instanceof GraphTerm && tr.o instanceof GraphTerm) {
11995
- backwardRules.push(this.makeRule(tr.s, tr.o, false));
12750
+ backwardRules.push(annotateSourceOffset(this.makeRule(tr.s, tr.o, false), statementOffset));
11996
12751
  } else if (isLogQuery(tr.p) && tr.s instanceof GraphTerm && tr.o instanceof GraphTerm) {
11997
12752
  // Output-selection directive: { premise } log:query { conclusion }.
11998
12753
  // When present at top-level, eyeling prints only the instantiated conclusion
11999
12754
  // triples (unique) instead of all newly derived facts.
12000
- logQueries.push(this.makeRule(tr.s, tr.o, true));
12755
+ logQueries.push(annotateSourceOffset(this.makeRule(tr.s, tr.o, true), statementOffset));
12001
12756
  } else {
12002
12757
  triples.push(tr);
12003
12758
  }
@@ -14938,6 +15693,7 @@ module.exports = {
14938
15693
  if (typeof __entry.materializeRdfLists === "function") __outerSelf.materializeRdfLists = __entry.materializeRdfLists;
14939
15694
  if (typeof __entry.isGroundTriple === "function") __outerSelf.isGroundTriple = __entry.isGroundTriple;
14940
15695
  if (typeof __entry.printExplanation === "function") __outerSelf.printExplanation = __entry.printExplanation;
15696
+ if (typeof __entry.renderProofDocument === "function") __outerSelf.renderProofDocument = __entry.renderProofDocument;
14941
15697
  if (typeof __entry.tripleToN3 === "function") __outerSelf.tripleToN3 = __entry.tripleToN3;
14942
15698
  if (typeof __entry.collectOutputStringsFromFacts === "function") __outerSelf.collectOutputStringsFromFacts = __entry.collectOutputStringsFromFacts;
14943
15699
  if (typeof __entry.prettyPrintQueryTriples === "function") __outerSelf.prettyPrintQueryTriples = __entry.prettyPrintQueryTriples;