eyeling 1.25.5 → 1.26.1

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
 
@@ -4827,6 +4827,7 @@ function main() {
4827
4827
  label: sourceLabel,
4828
4828
  collectUsedPrefixes: streamMode,
4829
4829
  keepSourceArtifacts: false,
4830
+ sourceLocations: engine.getProofCommentsEnabled(),
4830
4831
  rdf: rdfMode,
4831
4832
  }),
4832
4833
  );
@@ -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.concat(outDerived || []), 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');
@@ -5514,6 +5520,7 @@ const {
5514
5520
  Triple,
5515
5521
  Rule,
5516
5522
  DerivedFact,
5523
+ varsInRule,
5517
5524
  internIri,
5518
5525
  collectBlankLabelsInTriples,
5519
5526
  copyQuotedGraphMetadata,
@@ -5531,7 +5538,7 @@ const EMPTY_LIST_TERM = new ListTerm([]);
5531
5538
  const { lex, N3SyntaxError } = require('./lexer');
5532
5539
  const { Parser } = require('./parser');
5533
5540
  const { liftBlankRuleVars } = require('./rules');
5534
- const { parseN3SourceList } = require('./multisource');
5541
+ const { parseN3Text, parseN3SourceList } = require('./multisource');
5535
5542
 
5536
5543
  const {
5537
5544
  makeBuiltins,
@@ -6269,9 +6276,11 @@ const { evalBuiltin, isBuiltinPred } = makeBuiltins({
6269
6276
  });
6270
6277
 
6271
6278
  // Initialize proof/output helpers (implemented in lib/explain.js).
6272
- const { printExplanation, collectOutputStringsFromFacts } = makeExplain({
6279
+ const { printExplanation, renderProofDocument, collectOutputStringsFromFacts } = makeExplain({
6273
6280
  applySubstTerm,
6274
6281
  skolemKeyFromTerm,
6282
+ isBuiltinPred,
6283
+ findBackwardProofForGoal,
6275
6284
  });
6276
6285
 
6277
6286
  function skolemizeTermForHeadBlanks(t, headBlankLabels, mapping, skCounter, firingKey, globalMap) {
@@ -8643,6 +8652,120 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxR
8643
8652
  return results;
8644
8653
  }
8645
8654
 
8655
+
8656
+ // Reconstruct one backward proof for N3 proof output. This is intentionally
8657
+ // called only by renderProofDocument(), so normal reasoning does not pay for it.
8658
+ function __proofTripleKey(tr) {
8659
+ if (!tr) return '';
8660
+ return `${skolemKeyFromTerm(tr.s)}\t${skolemKeyFromTerm(tr.p)}\t${skolemKeyFromTerm(tr.o)}`;
8661
+ }
8662
+
8663
+ function __copyProofSource(from, to) {
8664
+ if (from && to && Object.prototype.hasOwnProperty.call(from, '__source')) {
8665
+ Object.defineProperty(to, '__source', {
8666
+ value: from.__source,
8667
+ enumerable: false,
8668
+ writable: false,
8669
+ configurable: true,
8670
+ });
8671
+ }
8672
+ return to;
8673
+ }
8674
+
8675
+ function __annotateProofVarSourceNames(rule) {
8676
+ if (!rule) return rule;
8677
+ const sourceNames = Object.create(null);
8678
+ for (const name of varsInRule(rule)) {
8679
+ const m = /^(.*)__\d+$/.exec(name);
8680
+ sourceNames[name] = m ? m[1] : name;
8681
+ }
8682
+ Object.defineProperty(rule, '__proofVarSourceNames', {
8683
+ value: sourceNames,
8684
+ enumerable: false,
8685
+ writable: false,
8686
+ configurable: true,
8687
+ });
8688
+ return rule;
8689
+ }
8690
+
8691
+ function findBackwardProofForGoal(goal, facts, backRules, opts = {}) {
8692
+ const maxDepth = Number.isInteger(opts.maxDepth) && opts.maxDepth >= 0 ? opts.maxDepth : 64;
8693
+ const varGen = Array.isArray(opts.varGen) ? opts.varGen : [1];
8694
+ const visited = opts.visited instanceof Set ? opts.visited : new Set();
8695
+ const factList = Array.isArray(facts) ? facts : [];
8696
+ const ruleList = Array.isArray(backRules) ? backRules : [];
8697
+
8698
+ function matchingBaseFact(g) {
8699
+ const candidates = g && g.p instanceof Iri ? candidateFacts(factList, g) : null;
8700
+ const total = candidates ? candidates.totalLen : factList.length;
8701
+ for (let i = 0; i < total; i++) {
8702
+ let f;
8703
+ if (candidates) {
8704
+ if (i < candidates.exactLen) f = factList[candidates.exact[i]];
8705
+ else f = factList[candidates.wild[i - candidates.exactLen]];
8706
+ } else {
8707
+ f = factList[i];
8708
+ }
8709
+ if (unifyTriple(g, f, __emptySubst()) !== null) return f;
8710
+ }
8711
+ return null;
8712
+ }
8713
+
8714
+ function builtinProof(g) {
8715
+ if (!(g && isBuiltinPred(g.p))) return null;
8716
+ return { kind: 'builtin', fact: g, builtin: g.p };
8717
+ }
8718
+
8719
+ function solve(g, depth) {
8720
+ if (!g || depth > maxDepth) return null;
8721
+
8722
+ const base = matchingBaseFact(g);
8723
+ if (base) return { kind: 'fact', fact: base, source: base.__source };
8724
+
8725
+ const builtin = builtinProof(g);
8726
+ if (builtin) return builtin;
8727
+
8728
+ if (!(g.p instanceof Iri)) return null;
8729
+ const gKey = __proofTripleKey(g);
8730
+ if (visited.has(gKey)) return null;
8731
+ visited.add(gKey);
8732
+
8733
+ try {
8734
+ ensureBackRuleIndexes(ruleList);
8735
+ const candRules = (ruleList.__byHeadPred.get(g.p.__tid) || []).concat(ruleList.__wildHeadPred);
8736
+ for (const r of candRules) {
8737
+ if (!r || !Array.isArray(r.conclusion) || r.conclusion.length !== 1) continue;
8738
+ const rawHead = r.conclusion[0];
8739
+ if (rawHead.p instanceof Iri && rawHead.p.__tid !== g.p.__tid) continue;
8740
+
8741
+ const rStd = __annotateProofVarSourceNames(__copyProofSource(r, standardizeRule(r, varGen)));
8742
+ const head = rStd.conclusion[0];
8743
+ const s0 = unifyTriple(head, g, __emptySubst());
8744
+ if (s0 === null) continue;
8745
+
8746
+ const solutions = proveGoals(rStd.premise, s0, factList, ruleList, depth + 1, [], varGen, 1, {
8747
+ keepVars: varsInRule(rStd),
8748
+ deferBuiltins: true,
8749
+ });
8750
+ if (!solutions.length) continue;
8751
+
8752
+ const subst = solutions[0];
8753
+ const fact = applySubstTriple(head, subst);
8754
+ const premises = rStd.premise.map((prem) => applySubstTriple(prem, subst));
8755
+ const df = new DerivedFact(fact, rStd, premises, __cloneSubst(subst));
8756
+ const children = premises.map((prem) => builtinProof(prem) || solve(prem, depth + 1));
8757
+ return { kind: 'rule', df, children };
8758
+ }
8759
+ } finally {
8760
+ visited.delete(gKey);
8761
+ }
8762
+
8763
+ return null;
8764
+ }
8765
+
8766
+ return solve(goal, 0);
8767
+ }
8768
+
8646
8769
  // ===========================================================================
8647
8770
  // Forward chaining to fixpoint
8648
8771
  // ===========================================================================
@@ -9328,7 +9451,7 @@ function reasonStream(input, opts = {}) {
9328
9451
 
9329
9452
  const useRdfCompatibility = !!rdf;
9330
9453
 
9331
- const parsedSourceList = parseN3SourceList(input, { baseIri, rdf: useRdfCompatibility });
9454
+ const parsedSourceList = parseN3SourceList(input, { baseIri, rdf: useRdfCompatibility, sourceLocations: proof });
9332
9455
  const parsedInput = parsedSourceList || normalizeParsedReasonerInputSync(input);
9333
9456
  const rdfFactory = rdfjs ? getDataFactory(dataFactory) : null;
9334
9457
 
@@ -9364,11 +9487,22 @@ function reasonStream(input, opts = {}) {
9364
9487
  if (baseIri) prefixes.setBase(baseIri);
9365
9488
  } else {
9366
9489
  const n3Text = normalizeReasonerInputSync(input);
9367
- const toks = lex(n3Text, { rdf: useRdfCompatibility });
9368
- const parser = new Parser(toks);
9369
- if (baseIri) parser.prefixes.setBase(baseIri);
9490
+ if (proof) {
9491
+ const parsed = parseN3Text(n3Text, {
9492
+ label: 'input.n3',
9493
+ baseIri: baseIri || '',
9494
+ keepSourceArtifacts: false,
9495
+ sourceLocations: true,
9496
+ rdf: useRdfCompatibility,
9497
+ });
9498
+ ({ prefixes, triples, frules, brules, logQueryRules } = parsed);
9499
+ } else {
9500
+ const toks = lex(n3Text, { rdf: useRdfCompatibility });
9501
+ const parser = new Parser(toks);
9502
+ if (baseIri) parser.prefixes.setBase(baseIri);
9370
9503
 
9371
- [prefixes, triples, frules, brules, logQueryRules] = parser.parseDocument();
9504
+ [prefixes, triples, frules, brules, logQueryRules] = parser.parseDocument();
9505
+ }
9372
9506
  }
9373
9507
  // Make the parsed prefixes available to log:trace output
9374
9508
  trace.setTracePrefixes(prefixes);
@@ -9436,11 +9570,19 @@ function reasonStream(input, opts = {}) {
9436
9570
  ? facts
9437
9571
  : derived.map((d) => d.fact);
9438
9572
 
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');
9573
+ const closureN3 = proof
9574
+ ? renderProofDocument(
9575
+ Array.isArray(logQueryRules) && logQueryRules.length ? queryDerived : derived,
9576
+ derived.concat(queryDerived || []),
9577
+ triples,
9578
+ prefixes,
9579
+ brules,
9580
+ ).replace(/\n$/g, '')
9581
+ : useRdfCompatibility
9582
+ ? closureTriples.map((t) => tripleToRdfCompatible(t, prefixes)).join('\n')
9583
+ : Array.isArray(logQueryRules) && logQueryRules.length
9584
+ ? prettyPrintQueryTriples(closureTriples, prefixes)
9585
+ : closureTriples.map((t) => tripleToN3(t, prefixes)).join('\n');
9444
9586
 
9445
9587
  const __out = {
9446
9588
  prefixes,
@@ -9484,7 +9626,7 @@ function reasonRdfJs(input, opts = {}) {
9484
9626
 
9485
9627
  Promise.resolve().then(async () => {
9486
9628
  try {
9487
- const normalizedInput = parseN3SourceList(input, restOpts) || (await normalizeReasonerInputAsync(input));
9629
+ const normalizedInput = parseN3SourceList(input, { ...restOpts, sourceLocations: !!(restOpts.proof || restOpts.proofComments) }) || (await normalizeReasonerInputAsync(input));
9488
9630
  reasonStream(normalizedInput, {
9489
9631
  ...restOpts,
9490
9632
  rdfjs: false,
@@ -9567,6 +9709,7 @@ module.exports = {
9567
9709
  collectLogQueryConclusions,
9568
9710
  forwardChainAndCollectLogQueryConclusions,
9569
9711
  collectOutputStringsFromFacts,
9712
+ renderProofDocument,
9570
9713
  main,
9571
9714
  version,
9572
9715
  N3SyntaxError,
@@ -9636,6 +9779,7 @@ module.exports = {
9636
9779
  materializeRdfLists: engine.materializeRdfLists,
9637
9780
  isGroundTriple: engine.isGroundTriple,
9638
9781
  printExplanation: engine.printExplanation,
9782
+ renderProofDocument: engine.renderProofDocument,
9639
9783
  tripleToN3: engine.tripleToN3,
9640
9784
  collectOutputStringsFromFacts: engine.collectOutputStringsFromFacts,
9641
9785
  prettyPrintQueryTriples: engine.prettyPrintQueryTriples,
@@ -9662,7 +9806,7 @@ module.exports = {
9662
9806
  */
9663
9807
  'use strict';
9664
9808
 
9665
- const { LOG_NS, Literal, Iri, Blank, Var, varsInRule, literalParts } = require('./prelude');
9809
+ const { LOG_NS, Literal, Iri, Blank, Var, GraphTerm, varsInRule, literalParts, PrefixEnv } = require('./prelude');
9666
9810
 
9667
9811
  const { termToN3, tripleToN3 } = require('./printing');
9668
9812
  const { parseNumericLiteralInfo, termToJsString } = require('./builtins');
@@ -9670,6 +9814,8 @@ const { parseNumericLiteralInfo, termToJsString } = require('./builtins');
9670
9814
  function makeExplain(deps) {
9671
9815
  const applySubstTerm = deps.applySubstTerm;
9672
9816
  const skolemKeyFromTerm = deps.skolemKeyFromTerm;
9817
+ const isBuiltinPred = typeof deps.isBuiltinPred === 'function' ? deps.isBuiltinPred : () => false;
9818
+ const findBackwardProofForGoal = typeof deps.findBackwardProofForGoal === 'function' ? deps.findBackwardProofForGoal : null;
9673
9819
 
9674
9820
  function printExplanation(df, prefixes) {
9675
9821
  console.log('# ----------------------------------------------------------------------');
@@ -9767,6 +9913,301 @@ function makeExplain(deps) {
9767
9913
  console.log('# ----------------------------------------------------------------------\n');
9768
9914
  }
9769
9915
 
9916
+
9917
+ const PE_NS = 'https://eyereasoner.github.io/pe#';
9918
+
9919
+ function n3String(value) {
9920
+ return JSON.stringify(String(value));
9921
+ }
9922
+
9923
+ function lineIndent(text, prefix) {
9924
+ return String(text)
9925
+ .split(/\r?\n/)
9926
+ .map((line) => (line.length ? prefix + line : line))
9927
+ .join('\n');
9928
+ }
9929
+
9930
+ function graphForTriple(tr, prefixes) {
9931
+ const body = tripleToN3(tr, prefixes).trimEnd();
9932
+ if (!body.includes('\n')) return `{ ${body} }`;
9933
+ return `{
9934
+ ${lineIndent(body, ' ')}
9935
+ }`;
9936
+ }
9937
+
9938
+ function clonePrefixEnvWithProofVocabulary(prefixes) {
9939
+ const map = { ...(prefixes && prefixes.map ? prefixes.map : {}) };
9940
+ if (!map.pe) map.pe = PE_NS;
9941
+ return new PrefixEnv(map, (prefixes && prefixes.baseIri) || '');
9942
+ }
9943
+
9944
+ function proofTripleKey(tr) {
9945
+ if (!tr) return '';
9946
+ return `${skolemKeyFromTerm(tr.s)}\t${skolemKeyFromTerm(tr.p)}\t${skolemKeyFromTerm(tr.o)}`;
9947
+ }
9948
+
9949
+ function sourceLabelForProof(source) {
9950
+ if (!source || typeof source.label !== 'string' || !source.label) return '<unknown>';
9951
+ // Keep proof output stable when the CLI is invoked with paths such as examples/foo.n3.
9952
+ return source.label.replace(/\\/g, '/').split('/').pop() || source.label;
9953
+ }
9954
+
9955
+ function sourceKeyForProof(source) {
9956
+ if (!source) return '<unknown>';
9957
+ return `${sourceLabelForProof(source)}:${Number.isInteger(source.line) ? source.line : ''}`;
9958
+ }
9959
+
9960
+ function byBlankNode(kind, source) {
9961
+ const src = source || {};
9962
+ const props = [`pe:${kind} ${n3String(sourceLabelForProof(src))}`];
9963
+ if (Number.isInteger(src.line) && src.line > 0) props.push(`pe:line ${src.line}`);
9964
+ return `[ ${props.join('; ')} ]`;
9965
+ }
9966
+
9967
+ function renderBindingItems(df, prefixes) {
9968
+ if (!df || !df.rule || !df.subst) return [];
9969
+ const ruleVars = varsInRule(df.rule);
9970
+ const visibleNames = Object.keys(df.subst)
9971
+ .filter((name) => ruleVars.has(name))
9972
+ .sort();
9973
+ if (!visibleNames.length) return [];
9974
+ const sourceNames = (df.rule && df.rule.__proofVarSourceNames) || null;
9975
+ return visibleNames.map((name) => {
9976
+ const value = applySubstTerm(new Var(name), df.subst);
9977
+ const displayName = sourceNames && sourceNames[name] ? sourceNames[name] : name;
9978
+ return `[ pe:var ${n3String(displayName)}; pe:value ${termToN3(value, prefixes)} ]`;
9979
+ });
9980
+ }
9981
+
9982
+ function withLastLineSuffix(text, suffix) {
9983
+ const lines = String(text).split(/\r?\n/);
9984
+ lines[lines.length - 1] += suffix;
9985
+ return lines.join('\n');
9986
+ }
9987
+
9988
+ function renderPredicateObjects(predicate, objects, isLast) {
9989
+ const values = (objects || []).filter((value) => value);
9990
+ if (!values.length) return [];
9991
+ const end = isLast ? '.' : ';';
9992
+ if (values.length === 1 && !values[0].includes('\n')) {
9993
+ return [` ${predicate} ${values[0]}${end}`];
9994
+ }
9995
+ const out = [` ${predicate}`];
9996
+ for (let i = 0; i < values.length; i++) {
9997
+ const suffix = i === values.length - 1 ? end : ',';
9998
+ out.push(lineIndent(withLastLineSuffix(values[i], suffix), ' '));
9999
+ }
10000
+ return out;
10001
+ }
10002
+
10003
+ function proofEntryKey(entry) {
10004
+ if (!entry) return '';
10005
+ if (entry.kind === 'rule') {
10006
+ const df = entry.df;
10007
+ const source = df && df.rule && df.rule.__source;
10008
+ const premiseKey = (df && df.premises ? df.premises : []).map(proofTripleKey).join('|');
10009
+ return `rule:${proofTripleKey(df && df.fact)}:${sourceKeyForProof(source)}:${premiseKey}`;
10010
+ }
10011
+ if (entry.kind === 'builtin') return `builtin:${proofTripleKey(entry.fact)}`;
10012
+ return `fact:${proofTripleKey(entry.fact)}:${sourceKeyForProof(entry.source)}`;
10013
+ }
10014
+
10015
+ function collectProofEntries(rootDf, derivedByKey, baseFactByKey, resolveBackwardProof) {
10016
+ const entries = [];
10017
+ const seen = new Set();
10018
+
10019
+ function remember(entry) {
10020
+ const key = proofEntryKey(entry);
10021
+ if (!key || seen.has(key)) return false;
10022
+ seen.add(key);
10023
+ entries.push(entry);
10024
+ return true;
10025
+ }
10026
+
10027
+ function derivedCandidatesForKey(key) {
10028
+ const found = derivedByKey.get(key);
10029
+ if (!found) return [];
10030
+ return Array.isArray(found) ? found : [found];
10031
+ }
10032
+
10033
+ function visitProofNode(proof, fallbackTriple, parentDf) {
10034
+ if (!proof) {
10035
+ visitFactTriple(fallbackTriple, parentDf);
10036
+ return;
10037
+ }
10038
+ if (proof.kind === 'rule' && proof.df) {
10039
+ visitDerivedFact(proof.df, proof.children || null);
10040
+ return;
10041
+ }
10042
+ if (proof.kind === 'builtin') {
10043
+ remember({ kind: 'builtin', fact: proof.fact || fallbackTriple, builtin: proof.builtin || (proof.fact && proof.fact.p) });
10044
+ return;
10045
+ }
10046
+ remember({ kind: 'fact', fact: proof.fact || fallbackTriple, source: proof.source });
10047
+ }
10048
+
10049
+ function visitFactTriple(tr, parentDf) {
10050
+ const key = proofTripleKey(tr);
10051
+ const base = baseFactByKey.get(key) || null;
10052
+ if (base) {
10053
+ remember({ kind: 'fact', fact: base, source: base.__source });
10054
+ return;
10055
+ }
10056
+
10057
+ const candidates = derivedCandidatesForKey(key);
10058
+ const df = candidates.find((candidate) => candidate !== parentDf) || null;
10059
+ if (df) {
10060
+ visitDerivedFact(df);
10061
+ return;
10062
+ }
10063
+
10064
+ if (tr && isBuiltinPred(tr.p)) {
10065
+ remember({ kind: 'builtin', fact: tr, builtin: tr.p });
10066
+ return;
10067
+ }
10068
+
10069
+ if (resolveBackwardProof) {
10070
+ const proof = resolveBackwardProof(tr);
10071
+ if (proof) {
10072
+ visitProofNode(proof, tr, parentDf);
10073
+ return;
10074
+ }
10075
+ }
10076
+
10077
+ remember({ kind: 'fact', fact: tr, source: null });
10078
+ }
10079
+
10080
+ function visitDerivedFact(df, children) {
10081
+ if (!df || !df.fact) return;
10082
+ const entry = { kind: 'rule', df };
10083
+ if (!remember(entry)) return;
10084
+
10085
+ if (Array.isArray(children) && children.length) {
10086
+ for (const child of children) visitProofNode(child, null, df);
10087
+ return;
10088
+ }
10089
+ for (const prem of df.premises || []) visitFactTriple(prem, df);
10090
+ }
10091
+
10092
+ visitDerivedFact(rootDf);
10093
+ return entries;
10094
+ }
10095
+
10096
+ function collectProofOutputTriples(outputDerived) {
10097
+ const out = [];
10098
+ const seen = new Set();
10099
+ for (const df of outputDerived || []) {
10100
+ if (!df || !df.fact) continue;
10101
+ const key = proofTripleKey(df.fact);
10102
+ if (seen.has(key)) continue;
10103
+ seen.add(key);
10104
+ out.push(df.fact);
10105
+ }
10106
+ return out;
10107
+ }
10108
+
10109
+ function renderProofEntry(entry, prefixes) {
10110
+ if (!entry) return '';
10111
+ if (entry.kind === 'fact') {
10112
+ return ` ${graphForTriple(entry.fact, prefixes)}\n pe:by ${byBlankNode('fact', entry.source)}.`;
10113
+ }
10114
+ if (entry.kind === 'builtin') {
10115
+ return ` ${graphForTriple(entry.fact, prefixes)}\n pe:by [ pe:builtin ${termToN3(entry.builtin, prefixes)} ].`;
10116
+ }
10117
+
10118
+ const df = entry.df;
10119
+ const bindingItems = renderBindingItems(df, prefixes);
10120
+ const useItems = (df.premises || []).map((prem) => graphForTriple(prem, prefixes));
10121
+ const propertyGroups = [
10122
+ { predicate: 'pe:by', objects: [byBlankNode('rule', df.rule && df.rule.__source)] },
10123
+ { predicate: 'pe:binding', objects: bindingItems },
10124
+ { predicate: 'pe:uses', objects: useItems },
10125
+ ].filter((group) => group.objects.length);
10126
+
10127
+ let out = ` ${graphForTriple(df.fact, prefixes)}\n`;
10128
+ for (let i = 0; i < propertyGroups.length; i++) {
10129
+ const group = propertyGroups[i];
10130
+ out += renderPredicateObjects(group.predicate, group.objects, i === propertyGroups.length - 1).join('\n') + '\n';
10131
+ }
10132
+ return out.trimEnd();
10133
+ }
10134
+
10135
+ function renderProofBlock(rootDf, derivedByKey, baseFactByKey, prefixes, resolveBackwardProof) {
10136
+ const entries = collectProofEntries(rootDf, derivedByKey, baseFactByKey, resolveBackwardProof);
10137
+ const rootGraph = graphForTriple(rootDf.fact, prefixes);
10138
+ const proofBody = entries.map((entry) => renderProofEntry(entry, prefixes)).join('\n\n');
10139
+ const proofGraph = proofBody ? `{
10140
+ ${proofBody}
10141
+ }` : '{}';
10142
+ return `${rootGraph} pe:why ${proofGraph}.`;
10143
+ }
10144
+
10145
+ function renderProofDocument(outputDerived, allDerived, baseFacts, prefixes, backRules) {
10146
+ const selectedDerived = Array.isArray(outputDerived) ? outputDerived.filter((df) => df && df.fact) : [];
10147
+ if (!selectedDerived.length) return '';
10148
+
10149
+ const proofPrefixes = clonePrefixEnvWithProofVocabulary(prefixes);
10150
+ const derivedByKey = new Map();
10151
+ function addDerived(df) {
10152
+ if (!df || !df.fact) return;
10153
+ const key = proofTripleKey(df.fact);
10154
+ let bucket = derivedByKey.get(key);
10155
+ if (!bucket) {
10156
+ bucket = [];
10157
+ derivedByKey.set(key, bucket);
10158
+ }
10159
+ if (!bucket.includes(df)) bucket.push(df);
10160
+ }
10161
+ for (const df of allDerived || []) addDerived(df);
10162
+ for (const df of selectedDerived) addDerived(df);
10163
+
10164
+ const baseFactByKey = new Map();
10165
+ for (const tr of baseFacts || []) {
10166
+ if (!tr) continue;
10167
+ const key = proofTripleKey(tr);
10168
+ if (!baseFactByKey.has(key)) baseFactByKey.set(key, tr);
10169
+ }
10170
+
10171
+ const resolveBackwardProof = findBackwardProofForGoal
10172
+ ? (tr) => findBackwardProofForGoal(tr, baseFacts || [], backRules || [], { maxDepth: 64 })
10173
+ : null;
10174
+
10175
+ const outputTriples = collectProofOutputTriples(selectedDerived);
10176
+ const proofRelationTriples = [];
10177
+ for (const df of selectedDerived) {
10178
+ proofRelationTriples.push({ s: new GraphTerm([df.fact]), p: new Iri(PE_NS + 'why'), o: new GraphTerm([]) });
10179
+ for (const entry of collectProofEntries(df, derivedByKey, baseFactByKey, resolveBackwardProof)) {
10180
+ const fact = entry.kind === 'rule' ? entry.df.fact : entry.fact;
10181
+ const byObject = entry.kind === 'builtin' && entry.builtin ? entry.builtin : new Iri(PE_NS + 'source');
10182
+ proofRelationTriples.push({ s: new GraphTerm([fact]), p: new Iri(PE_NS + 'by'), o: byObject });
10183
+ if (entry.kind === 'rule') {
10184
+ for (const prem of entry.df.premises || []) proofRelationTriples.push({ s: new GraphTerm([fact]), p: new Iri(PE_NS + 'uses'), o: new GraphTerm([prem]) });
10185
+ }
10186
+ }
10187
+ }
10188
+
10189
+ const usedPrefixes = proofPrefixes.prefixesUsedForOutput(outputTriples.concat(proofRelationTriples));
10190
+ if (!usedPrefixes.some(([pfx]) => pfx === 'pe')) usedPrefixes.push(['pe', PE_NS]);
10191
+ usedPrefixes.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
10192
+
10193
+ const parts = [];
10194
+ for (const [pfx, base] of usedPrefixes) {
10195
+ if (!base) continue;
10196
+ if (pfx === '') parts.push(`@prefix : <${base}> .`);
10197
+ else parts.push(`@prefix ${pfx}: <${base}> .`);
10198
+ }
10199
+ if (parts.length) parts.push('');
10200
+
10201
+ parts.push(...outputTriples.map((tr) => tripleToN3(tr, proofPrefixes)));
10202
+ parts.push('');
10203
+ for (let i = 0; i < selectedDerived.length; i++) {
10204
+ if (i > 0) parts.push('');
10205
+ parts.push(renderProofBlock(selectedDerived[i], derivedByKey, baseFactByKey, proofPrefixes, resolveBackwardProof));
10206
+ }
10207
+
10208
+ return parts.join('\n').replace(/[ \t]+$/gm, '').replace(/\s*$/g, '') + '\n';
10209
+ }
10210
+
9770
10211
  // ===========================================================================
9771
10212
  // CLI entry point
9772
10213
  // ===========================================================================
@@ -9873,7 +10314,7 @@ function makeExplain(deps) {
9873
10314
  return addMarkdownHardBreaks(pairs.map((p) => p.text).join(''));
9874
10315
  }
9875
10316
 
9876
- return { printExplanation, collectOutputStringsFromFacts };
10317
+ return { printExplanation, renderProofDocument, collectOutputStringsFromFacts };
9877
10318
  }
9878
10319
 
9879
10320
  module.exports = { makeExplain };
@@ -11665,6 +12106,67 @@ function emptyParsedDocument() {
11665
12106
  };
11666
12107
  }
11667
12108
 
12109
+ function lineStartsForText(text) {
12110
+ const starts = [0];
12111
+ const str = String(text);
12112
+ for (let i = 0; i < str.length; i++) {
12113
+ const c = str[i];
12114
+ if (c === '\n') starts.push(i + 1);
12115
+ else if (c === '\r') {
12116
+ starts.push(i + 1 < str.length && str[i + 1] === '\n' ? i + 2 : i + 1);
12117
+ if (i + 1 < str.length && str[i + 1] === '\n') i++;
12118
+ }
12119
+ }
12120
+ return starts;
12121
+ }
12122
+
12123
+ function offsetToLine(lineStarts, offset) {
12124
+ if (typeof offset !== 'number') return null;
12125
+ let lo = 0;
12126
+ let hi = lineStarts.length;
12127
+ while (lo + 1 < hi) {
12128
+ const mid = (lo + hi) >> 1;
12129
+ if (lineStarts[mid] <= offset) lo = mid;
12130
+ else hi = mid;
12131
+ }
12132
+ return lo + 1;
12133
+ }
12134
+
12135
+ function annotateSourceLocation(obj, lineStarts, label) {
12136
+ if (!obj || typeof obj.__sourceOffset !== 'number') return obj;
12137
+ const line = offsetToLine(lineStarts, obj.__sourceOffset);
12138
+ Object.defineProperty(obj, '__source', {
12139
+ value: { label, line },
12140
+ enumerable: false,
12141
+ writable: false,
12142
+ configurable: true,
12143
+ });
12144
+ return obj;
12145
+ }
12146
+
12147
+ function annotateParsedSourceLocations(doc, text, label) {
12148
+ const lineStarts = lineStartsForText(text);
12149
+ for (const tr of doc.triples || []) annotateSourceLocation(tr, lineStarts, label);
12150
+ for (const r of doc.frules || []) annotateSourceLocation(r, lineStarts, label);
12151
+ for (const r of doc.brules || []) annotateSourceLocation(r, lineStarts, label);
12152
+ for (const r of doc.logQueryRules || []) annotateSourceLocation(r, lineStarts, label);
12153
+ }
12154
+
12155
+ function copySourceMetadata(from, to) {
12156
+ if (!from || !to) return to;
12157
+ for (const key of ['__sourceOffset', '__source']) {
12158
+ if (Object.prototype.hasOwnProperty.call(from, key)) {
12159
+ Object.defineProperty(to, key, {
12160
+ value: from[key],
12161
+ enumerable: false,
12162
+ writable: false,
12163
+ configurable: true,
12164
+ });
12165
+ }
12166
+ }
12167
+ return to;
12168
+ }
12169
+
11668
12170
  function prefixesUsedInTokens(tokens, prefEnv) {
11669
12171
  const used = new Set();
11670
12172
  const toks = Array.isArray(tokens) ? tokens : [];
@@ -11742,6 +12244,7 @@ function parseN3Text(text, opts = {}) {
11742
12244
  label = '<input>',
11743
12245
  keepSourceArtifacts = true,
11744
12246
  collectUsedPrefixes = false,
12247
+ sourceLocations = false,
11745
12248
  rdf = false,
11746
12249
  } = opts || {};
11747
12250
  const tokens = lex(text, { rdf });
@@ -11750,6 +12253,7 @@ function parseN3Text(text, opts = {}) {
11750
12253
  const [prefixes, triples, frules, brules, logQueryRules] = parser.parseDocument();
11751
12254
 
11752
12255
  const doc = { prefixes, triples, frules, brules, logQueryRules, label };
12256
+ if (sourceLocations) annotateParsedSourceLocations(doc, text, label);
11753
12257
 
11754
12258
  if (collectUsedPrefixes) {
11755
12259
  Object.defineProperty(doc, 'usedPrefixes', {
@@ -11795,7 +12299,7 @@ function scopeBlankNodesInDocument(doc, sourceIndex) {
11795
12299
  }
11796
12300
 
11797
12301
  function cloneTriple(triple) {
11798
- return new Triple(cloneTerm(triple.s), cloneTerm(triple.p), cloneTerm(triple.o));
12302
+ return copySourceMetadata(triple, new Triple(cloneTerm(triple.s), cloneTerm(triple.p), cloneTerm(triple.o)));
11799
12303
  }
11800
12304
 
11801
12305
  function cloneRule(rule) {
@@ -11804,12 +12308,15 @@ function scopeBlankNodesInDocument(doc, sourceIndex) {
11804
12308
  for (const label of rule.headBlankLabels) headBlankLabels.add(scopedBlankLabel(label, sourceIndex, mapping));
11805
12309
  }
11806
12310
 
11807
- const out = new Rule(
11808
- (rule.premise || []).map(cloneTriple),
11809
- (rule.conclusion || []).map(cloneTriple),
11810
- rule.isForward,
11811
- rule.isFuse,
11812
- headBlankLabels,
12311
+ const out = copySourceMetadata(
12312
+ rule,
12313
+ new Rule(
12314
+ (rule.premise || []).map(cloneTriple),
12315
+ (rule.conclusion || []).map(cloneTriple),
12316
+ rule.isForward,
12317
+ rule.isFuse,
12318
+ headBlankLabels,
12319
+ ),
11813
12320
  );
11814
12321
 
11815
12322
  if (rule && Object.prototype.hasOwnProperty.call(rule, '__dynamicConclusionTerm')) {
@@ -11938,6 +12445,7 @@ function parseN3SourceList(input, opts = {}) {
11938
12445
  baseIri: source.baseIri || (sources.length === 1 ? defaultBaseIri : ''),
11939
12446
  collectUsedPrefixes: true,
11940
12447
  keepSourceArtifacts: !!opts.keepSourceArtifacts,
12448
+ sourceLocations: !!opts.sourceLocations,
11941
12449
  rdf: !!opts.rdf,
11942
12450
  }),
11943
12451
  );
@@ -12003,6 +12511,17 @@ function failInvalidKeywordLikeIdent(fail, tok, name) {
12003
12511
  fail(`invalid_keyword(${name})`, tok);
12004
12512
  }
12005
12513
 
12514
+ function annotateSourceOffset(obj, offset) {
12515
+ if (!obj || typeof offset !== 'number') return obj;
12516
+ Object.defineProperty(obj, '__sourceOffset', {
12517
+ value: offset,
12518
+ enumerable: false,
12519
+ writable: false,
12520
+ configurable: true,
12521
+ });
12522
+ return obj;
12523
+ }
12524
+
12006
12525
  class Parser {
12007
12526
  constructor(tokens) {
12008
12527
  this.toks = tokens;
@@ -12127,17 +12646,19 @@ class Parser {
12127
12646
  continue;
12128
12647
  }
12129
12648
 
12649
+ const statementToken = this.peek();
12650
+ const statementOffset = statementToken && typeof statementToken.offset === 'number' ? statementToken.offset : null;
12130
12651
  const first = this.parseTerm();
12131
12652
  if (this.peek().typ === 'OpImplies') {
12132
12653
  this.next();
12133
12654
  const second = this.parseTerm();
12134
12655
  this.expectDot();
12135
- forwardRules.push(this.makeRule(first, second, true));
12656
+ forwardRules.push(annotateSourceOffset(this.makeRule(first, second, true), statementOffset));
12136
12657
  } else if (this.peek().typ === 'OpImpliedBy') {
12137
12658
  this.next();
12138
12659
  const second = this.parseTerm();
12139
12660
  this.expectDot();
12140
- backwardRules.push(this.makeRule(first, second, false));
12661
+ backwardRules.push(annotateSourceOffset(this.makeRule(first, second, false), statementOffset));
12141
12662
  } else {
12142
12663
  let more;
12143
12664
 
@@ -12155,16 +12676,17 @@ class Parser {
12155
12676
  }
12156
12677
 
12157
12678
  // normalize explicit log:implies / log:impliedBy at top-level
12158
- for (const tr of more) {
12679
+ for (const tr0 of more) {
12680
+ const tr = annotateSourceOffset(tr0, statementOffset);
12159
12681
  if (isLogImplies(tr.p) && tr.s instanceof GraphTerm && tr.o instanceof GraphTerm) {
12160
- forwardRules.push(this.makeRule(tr.s, tr.o, true));
12682
+ forwardRules.push(annotateSourceOffset(this.makeRule(tr.s, tr.o, true), statementOffset));
12161
12683
  } else if (isLogImpliedBy(tr.p) && tr.s instanceof GraphTerm && tr.o instanceof GraphTerm) {
12162
- backwardRules.push(this.makeRule(tr.s, tr.o, false));
12684
+ backwardRules.push(annotateSourceOffset(this.makeRule(tr.s, tr.o, false), statementOffset));
12163
12685
  } else if (isLogQuery(tr.p) && tr.s instanceof GraphTerm && tr.o instanceof GraphTerm) {
12164
12686
  // Output-selection directive: { premise } log:query { conclusion }.
12165
12687
  // When present at top-level, eyeling prints only the instantiated conclusion
12166
12688
  // triples (unique) instead of all newly derived facts.
12167
- logQueries.push(this.makeRule(tr.s, tr.o, true));
12689
+ logQueries.push(annotateSourceOffset(this.makeRule(tr.s, tr.o, true), statementOffset));
12168
12690
  } else {
12169
12691
  triples.push(tr);
12170
12692
  }
@@ -15105,6 +15627,7 @@ module.exports = {
15105
15627
  if (typeof __entry.materializeRdfLists === "function") __outerSelf.materializeRdfLists = __entry.materializeRdfLists;
15106
15628
  if (typeof __entry.isGroundTriple === "function") __outerSelf.isGroundTriple = __entry.isGroundTriple;
15107
15629
  if (typeof __entry.printExplanation === "function") __outerSelf.printExplanation = __entry.printExplanation;
15630
+ if (typeof __entry.renderProofDocument === "function") __outerSelf.renderProofDocument = __entry.renderProofDocument;
15108
15631
  if (typeof __entry.tripleToN3 === "function") __outerSelf.tripleToN3 = __entry.tripleToN3;
15109
15632
  if (typeof __entry.collectOutputStringsFromFacts === "function") __outerSelf.collectOutputStringsFromFacts = __entry.collectOutputStringsFromFacts;
15110
15633
  if (typeof __entry.prettyPrintQueryTriples === "function") __outerSelf.prettyPrintQueryTriples = __entry.prettyPrintQueryTriples;