eyeling 1.25.5 → 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.
package/eyeling.js CHANGED
@@ -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');
@@ -6269,9 +6275,11 @@ const { evalBuiltin, isBuiltinPred } = makeBuiltins({
6269
6275
  });
6270
6276
 
6271
6277
  // Initialize proof/output helpers (implemented in lib/explain.js).
6272
- const { printExplanation, collectOutputStringsFromFacts } = makeExplain({
6278
+ const { printExplanation, renderProofDocument, collectOutputStringsFromFacts } = makeExplain({
6273
6279
  applySubstTerm,
6274
6280
  skolemKeyFromTerm,
6281
+ isBuiltinPred,
6282
+ findBackwardProofForGoal,
6275
6283
  });
6276
6284
 
6277
6285
  function skolemizeTermForHeadBlanks(t, headBlankLabels, mapping, skCounter, firingKey, globalMap) {
@@ -7922,6 +7930,217 @@ function unifyTriple(pat, fact, subst) {
7922
7930
  return s3;
7923
7931
  }
7924
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
+
7925
8144
  // Strategy: when the substitution is "large" or search depth is high,
7926
8145
  // keep only bindings that are still relevant to:
7927
8146
  // - variables appearing in the remaining goals
@@ -9436,11 +9655,19 @@ function reasonStream(input, opts = {}) {
9436
9655
  ? facts
9437
9656
  : derived.map((d) => d.fact);
9438
9657
 
9439
- const closureN3 = useRdfCompatibility
9440
- ? closureTriples.map((t) => tripleToRdfCompatible(t, prefixes)).join('\n')
9441
- : Array.isArray(logQueryRules) && logQueryRules.length && !proof
9442
- ? prettyPrintQueryTriples(closureTriples, prefixes)
9443
- : 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');
9444
9671
 
9445
9672
  const __out = {
9446
9673
  prefixes,
@@ -9567,6 +9794,7 @@ module.exports = {
9567
9794
  collectLogQueryConclusions,
9568
9795
  forwardChainAndCollectLogQueryConclusions,
9569
9796
  collectOutputStringsFromFacts,
9797
+ renderProofDocument,
9570
9798
  main,
9571
9799
  version,
9572
9800
  N3SyntaxError,
@@ -9636,6 +9864,7 @@ module.exports = {
9636
9864
  materializeRdfLists: engine.materializeRdfLists,
9637
9865
  isGroundTriple: engine.isGroundTriple,
9638
9866
  printExplanation: engine.printExplanation,
9867
+ renderProofDocument: engine.renderProofDocument,
9639
9868
  tripleToN3: engine.tripleToN3,
9640
9869
  collectOutputStringsFromFacts: engine.collectOutputStringsFromFacts,
9641
9870
  prettyPrintQueryTriples: engine.prettyPrintQueryTriples,
@@ -9662,7 +9891,7 @@ module.exports = {
9662
9891
  */
9663
9892
  'use strict';
9664
9893
 
9665
- 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');
9666
9895
 
9667
9896
  const { termToN3, tripleToN3 } = require('./printing');
9668
9897
  const { parseNumericLiteralInfo, termToJsString } = require('./builtins');
@@ -9670,6 +9899,8 @@ const { parseNumericLiteralInfo, termToJsString } = require('./builtins');
9670
9899
  function makeExplain(deps) {
9671
9900
  const applySubstTerm = deps.applySubstTerm;
9672
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;
9673
9904
 
9674
9905
  function printExplanation(df, prefixes) {
9675
9906
  console.log('# ----------------------------------------------------------------------');
@@ -9767,6 +9998,282 @@ function makeExplain(deps) {
9767
9998
  console.log('# ----------------------------------------------------------------------\n');
9768
9999
  }
9769
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
+
9770
10277
  // ===========================================================================
9771
10278
  // CLI entry point
9772
10279
  // ===========================================================================
@@ -9873,7 +10380,7 @@ function makeExplain(deps) {
9873
10380
  return addMarkdownHardBreaks(pairs.map((p) => p.text).join(''));
9874
10381
  }
9875
10382
 
9876
- return { printExplanation, collectOutputStringsFromFacts };
10383
+ return { printExplanation, renderProofDocument, collectOutputStringsFromFacts };
9877
10384
  }
9878
10385
 
9879
10386
  module.exports = { makeExplain };
@@ -11665,6 +12172,67 @@ function emptyParsedDocument() {
11665
12172
  };
11666
12173
  }
11667
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
+
11668
12236
  function prefixesUsedInTokens(tokens, prefEnv) {
11669
12237
  const used = new Set();
11670
12238
  const toks = Array.isArray(tokens) ? tokens : [];
@@ -11742,6 +12310,7 @@ function parseN3Text(text, opts = {}) {
11742
12310
  label = '<input>',
11743
12311
  keepSourceArtifacts = true,
11744
12312
  collectUsedPrefixes = false,
12313
+ collectSourceLocations = true,
11745
12314
  rdf = false,
11746
12315
  } = opts || {};
11747
12316
  const tokens = lex(text, { rdf });
@@ -11750,6 +12319,7 @@ function parseN3Text(text, opts = {}) {
11750
12319
  const [prefixes, triples, frules, brules, logQueryRules] = parser.parseDocument();
11751
12320
 
11752
12321
  const doc = { prefixes, triples, frules, brules, logQueryRules, label };
12322
+ if (collectSourceLocations) annotateParsedSourceLocations(doc, text, label);
11753
12323
 
11754
12324
  if (collectUsedPrefixes) {
11755
12325
  Object.defineProperty(doc, 'usedPrefixes', {
@@ -11795,7 +12365,7 @@ function scopeBlankNodesInDocument(doc, sourceIndex) {
11795
12365
  }
11796
12366
 
11797
12367
  function cloneTriple(triple) {
11798
- 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)));
11799
12369
  }
11800
12370
 
11801
12371
  function cloneRule(rule) {
@@ -11804,12 +12374,15 @@ function scopeBlankNodesInDocument(doc, sourceIndex) {
11804
12374
  for (const label of rule.headBlankLabels) headBlankLabels.add(scopedBlankLabel(label, sourceIndex, mapping));
11805
12375
  }
11806
12376
 
11807
- const out = new Rule(
11808
- (rule.premise || []).map(cloneTriple),
11809
- (rule.conclusion || []).map(cloneTriple),
11810
- rule.isForward,
11811
- rule.isFuse,
11812
- 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
+ ),
11813
12386
  );
11814
12387
 
11815
12388
  if (rule && Object.prototype.hasOwnProperty.call(rule, '__dynamicConclusionTerm')) {
@@ -11937,6 +12510,7 @@ function parseN3SourceList(input, opts = {}) {
11937
12510
  label: source.label,
11938
12511
  baseIri: source.baseIri || (sources.length === 1 ? defaultBaseIri : ''),
11939
12512
  collectUsedPrefixes: true,
12513
+ collectSourceLocations: !!opts.collectSourceLocations,
11940
12514
  keepSourceArtifacts: !!opts.keepSourceArtifacts,
11941
12515
  rdf: !!opts.rdf,
11942
12516
  }),
@@ -12003,6 +12577,17 @@ function failInvalidKeywordLikeIdent(fail, tok, name) {
12003
12577
  fail(`invalid_keyword(${name})`, tok);
12004
12578
  }
12005
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
+
12006
12591
  class Parser {
12007
12592
  constructor(tokens) {
12008
12593
  this.toks = tokens;
@@ -12127,17 +12712,19 @@ class Parser {
12127
12712
  continue;
12128
12713
  }
12129
12714
 
12715
+ const statementToken = this.peek();
12716
+ const statementOffset = statementToken && typeof statementToken.offset === 'number' ? statementToken.offset : null;
12130
12717
  const first = this.parseTerm();
12131
12718
  if (this.peek().typ === 'OpImplies') {
12132
12719
  this.next();
12133
12720
  const second = this.parseTerm();
12134
12721
  this.expectDot();
12135
- forwardRules.push(this.makeRule(first, second, true));
12722
+ forwardRules.push(annotateSourceOffset(this.makeRule(first, second, true), statementOffset));
12136
12723
  } else if (this.peek().typ === 'OpImpliedBy') {
12137
12724
  this.next();
12138
12725
  const second = this.parseTerm();
12139
12726
  this.expectDot();
12140
- backwardRules.push(this.makeRule(first, second, false));
12727
+ backwardRules.push(annotateSourceOffset(this.makeRule(first, second, false), statementOffset));
12141
12728
  } else {
12142
12729
  let more;
12143
12730
 
@@ -12155,16 +12742,17 @@ class Parser {
12155
12742
  }
12156
12743
 
12157
12744
  // normalize explicit log:implies / log:impliedBy at top-level
12158
- for (const tr of more) {
12745
+ for (const tr0 of more) {
12746
+ const tr = annotateSourceOffset(tr0, statementOffset);
12159
12747
  if (isLogImplies(tr.p) && tr.s instanceof GraphTerm && tr.o instanceof GraphTerm) {
12160
- forwardRules.push(this.makeRule(tr.s, tr.o, true));
12748
+ forwardRules.push(annotateSourceOffset(this.makeRule(tr.s, tr.o, true), statementOffset));
12161
12749
  } else if (isLogImpliedBy(tr.p) && tr.s instanceof GraphTerm && tr.o instanceof GraphTerm) {
12162
- backwardRules.push(this.makeRule(tr.s, tr.o, false));
12750
+ backwardRules.push(annotateSourceOffset(this.makeRule(tr.s, tr.o, false), statementOffset));
12163
12751
  } else if (isLogQuery(tr.p) && tr.s instanceof GraphTerm && tr.o instanceof GraphTerm) {
12164
12752
  // Output-selection directive: { premise } log:query { conclusion }.
12165
12753
  // When present at top-level, eyeling prints only the instantiated conclusion
12166
12754
  // triples (unique) instead of all newly derived facts.
12167
- logQueries.push(this.makeRule(tr.s, tr.o, true));
12755
+ logQueries.push(annotateSourceOffset(this.makeRule(tr.s, tr.o, true), statementOffset));
12168
12756
  } else {
12169
12757
  triples.push(tr);
12170
12758
  }
@@ -15105,6 +15693,7 @@ module.exports = {
15105
15693
  if (typeof __entry.materializeRdfLists === "function") __outerSelf.materializeRdfLists = __entry.materializeRdfLists;
15106
15694
  if (typeof __entry.isGroundTriple === "function") __outerSelf.isGroundTriple = __entry.isGroundTriple;
15107
15695
  if (typeof __entry.printExplanation === "function") __outerSelf.printExplanation = __entry.printExplanation;
15696
+ if (typeof __entry.renderProofDocument === "function") __outerSelf.renderProofDocument = __entry.renderProofDocument;
15108
15697
  if (typeof __entry.tripleToN3 === "function") __outerSelf.tripleToN3 = __entry.tripleToN3;
15109
15698
  if (typeof __entry.collectOutputStringsFromFacts === "function") __outerSelf.collectOutputStringsFromFacts = __entry.collectOutputStringsFromFacts;
15110
15699
  if (typeof __entry.prettyPrintQueryTriples === "function") __outerSelf.prettyPrintQueryTriples = __entry.prettyPrintQueryTriples;