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.
package/lib/explain.js CHANGED
@@ -6,7 +6,7 @@
6
6
  */
7
7
  'use strict';
8
8
 
9
- const { LOG_NS, Literal, Iri, Blank, Var, varsInRule, literalParts } = require('./prelude');
9
+ const { LOG_NS, Literal, Iri, Blank, Var, GraphTerm, varsInRule, literalParts, PrefixEnv } = require('./prelude');
10
10
 
11
11
  const { termToN3, tripleToN3 } = require('./printing');
12
12
  const { parseNumericLiteralInfo, termToJsString } = require('./builtins');
@@ -14,6 +14,8 @@ const { parseNumericLiteralInfo, termToJsString } = require('./builtins');
14
14
  function makeExplain(deps) {
15
15
  const applySubstTerm = deps.applySubstTerm;
16
16
  const skolemKeyFromTerm = deps.skolemKeyFromTerm;
17
+ const isBuiltinPred = typeof deps.isBuiltinPred === 'function' ? deps.isBuiltinPred : () => false;
18
+ const findBackwardProofForGoal = typeof deps.findBackwardProofForGoal === 'function' ? deps.findBackwardProofForGoal : null;
17
19
 
18
20
  function printExplanation(df, prefixes) {
19
21
  console.log('# ----------------------------------------------------------------------');
@@ -111,6 +113,282 @@ function makeExplain(deps) {
111
113
  console.log('# ----------------------------------------------------------------------\n');
112
114
  }
113
115
 
116
+
117
+ const PE_NS = 'https://eyereasoner.github.io/pe#';
118
+
119
+ function n3String(value) {
120
+ return JSON.stringify(String(value));
121
+ }
122
+
123
+ function lineIndent(text, prefix) {
124
+ return String(text)
125
+ .split(/\r?\n/)
126
+ .map((line) => (line.length ? prefix + line : line))
127
+ .join('\n');
128
+ }
129
+
130
+ function graphForTriple(tr, prefixes) {
131
+ const body = tripleToN3(tr, prefixes).trimEnd();
132
+ if (!body.includes('\n')) return `{ ${body} }`;
133
+ return `{
134
+ ${lineIndent(body, ' ')}
135
+ }`;
136
+ }
137
+
138
+
139
+ function clonePrefixEnvWithProofVocabulary(prefixes) {
140
+ const map = { ...(prefixes && prefixes.map ? prefixes.map : {}) };
141
+ if (!map.pe) map.pe = PE_NS;
142
+ return new PrefixEnv(map, (prefixes && prefixes.baseIri) || '');
143
+ }
144
+
145
+ function proofTripleKey(tr) {
146
+ if (!tr) return '';
147
+ return `${skolemKeyFromTerm(tr.s)}\t${skolemKeyFromTerm(tr.p)}\t${skolemKeyFromTerm(tr.o)}`;
148
+ }
149
+
150
+ function sourceLabelForProof(source) {
151
+ if (!source || typeof source.label !== 'string' || !source.label) return '<unknown>';
152
+ // Keep proof output stable when the CLI is invoked with paths such as examples/foo.n3.
153
+ return source.label.replace(/\\/g, '/').split('/').pop() || source.label;
154
+ }
155
+
156
+ function byBlankNode(kind, source) {
157
+ const src = source || {};
158
+ const props = [`pe:${kind} ${n3String(sourceLabelForProof(src))}`];
159
+ if (Number.isInteger(src.line) && src.line > 0) props.push(`pe:line ${src.line}`);
160
+ return `[ ${props.join('; ')} ]`;
161
+ }
162
+
163
+ function renderBindingList(df, prefixes) {
164
+ if (!df || !df.rule || !df.subst) return '';
165
+ const ruleVars = varsInRule(df.rule);
166
+ const visibleNames = Object.keys(df.subst)
167
+ .filter((name) => ruleVars.has(name))
168
+ .sort();
169
+ if (!visibleNames.length) return '';
170
+ const sourceNames = (df.rule && df.rule.__proofVarSourceNames) || null;
171
+ return visibleNames
172
+ .map((name) => {
173
+ const value = applySubstTerm(new Var(name), df.subst);
174
+ const displayName = sourceNames && sourceNames[name] ? sourceNames[name] : name;
175
+ return `[ pe:var ${n3String(displayName)}; pe:value ${termToN3(value, prefixes)} ]`;
176
+ })
177
+ .join(', ');
178
+ }
179
+
180
+ function proofSourceKey(source) {
181
+ if (!source) return '';
182
+ const label = typeof source.label === 'string' ? source.label : '';
183
+ const line = Number.isInteger(source.line) ? source.line : 0;
184
+ return `${label}\t${line}`;
185
+ }
186
+
187
+ function proofEntryKey(kind, fact, source, extra) {
188
+ return `${kind}\t${proofTripleKey(fact)}\t${proofSourceKey(source)}\t${extra || ''}`;
189
+ }
190
+
191
+ function ruleEntryKey(df) {
192
+ const rule = df && df.rule;
193
+ const premises = (df && df.premises ? df.premises : []).map((prem) => proofTripleKey(prem)).join('\t');
194
+ return proofEntryKey('rule', df && df.fact, rule && rule.__source, premises);
195
+ }
196
+
197
+ function collectProofEntries(rootDf, derivedByKey, baseFactByKey, resolveBackwardProof) {
198
+ const entries = [];
199
+ const seen = new Set();
200
+
201
+ function entryKeyForProof(proof, fallbackTriple) {
202
+ if (!proof) return proofEntryKey('fact', fallbackTriple, null);
203
+ if (proof.kind === 'rule') return ruleEntryKey(proof.df);
204
+ if (proof.kind === 'builtin') return proofEntryKey('builtin', proof.fact || fallbackTriple, null);
205
+ return proofEntryKey('fact', proof.fact || fallbackTriple, proof.source);
206
+ }
207
+
208
+ function visitProofNode(proof, fallbackTriple) {
209
+ if (!proof) {
210
+ visitFactTriple(fallbackTriple);
211
+ return;
212
+ }
213
+ const key = entryKeyForProof(proof, fallbackTriple);
214
+ if (!key || seen.has(key)) return;
215
+ if (proof.kind === 'rule' && proof.df) {
216
+ visitDerivedFact(proof.df, proof.children || null);
217
+ return;
218
+ }
219
+ seen.add(key);
220
+ if (proof.kind === 'builtin') {
221
+ entries.push({
222
+ kind: 'builtin',
223
+ fact: proof.fact || fallbackTriple,
224
+ builtin: proof.builtin || (proof.fact && proof.fact.p),
225
+ });
226
+ } else {
227
+ entries.push({ kind: 'fact', fact: proof.fact || fallbackTriple, source: proof.source });
228
+ }
229
+ }
230
+
231
+ function visitFactTriple(tr) {
232
+ const tripleKey = proofTripleKey(tr);
233
+ if (!tripleKey) return;
234
+ const df = derivedByKey.get(tripleKey);
235
+ if (df) {
236
+ visitDerivedFact(df);
237
+ return;
238
+ }
239
+ const base = baseFactByKey.get(tripleKey) || null;
240
+ if (base) {
241
+ const key = proofEntryKey('fact', base, base.__source);
242
+ if (seen.has(key)) return;
243
+ seen.add(key);
244
+ entries.push({ kind: 'fact', fact: base, source: base.__source });
245
+ return;
246
+ }
247
+ if (tr && isBuiltinPred(tr.p)) {
248
+ const key = proofEntryKey('builtin', tr, null);
249
+ if (seen.has(key)) return;
250
+ seen.add(key);
251
+ entries.push({ kind: 'builtin', fact: tr, builtin: tr.p });
252
+ return;
253
+ }
254
+ if (resolveBackwardProof) {
255
+ const proof = resolveBackwardProof(tr);
256
+ if (proof) {
257
+ visitProofNode(proof, tr);
258
+ return;
259
+ }
260
+ }
261
+ const key = proofEntryKey('fact', tr, null);
262
+ if (seen.has(key)) return;
263
+ seen.add(key);
264
+ entries.push({ kind: 'fact', fact: tr, source: null });
265
+ }
266
+
267
+ function visitDerivedFact(df, children) {
268
+ const key = ruleEntryKey(df);
269
+ if (!key || seen.has(key)) return;
270
+ seen.add(key);
271
+ entries.push({ kind: 'rule', df });
272
+
273
+ if (Array.isArray(children) && children.length) {
274
+ for (const child of children) visitProofNode(child);
275
+ return;
276
+ }
277
+ for (const prem of df.premises || []) visitFactTriple(prem);
278
+ }
279
+
280
+ visitDerivedFact(rootDf);
281
+ return entries;
282
+ }
283
+
284
+ function collectProofOutputTriples(outputDerived) {
285
+ const out = [];
286
+ const seen = new Set();
287
+ for (const df of outputDerived || []) {
288
+ if (!df || !df.fact) continue;
289
+ const key = proofTripleKey(df.fact);
290
+ if (seen.has(key)) continue;
291
+ seen.add(key);
292
+ out.push(df.fact);
293
+ }
294
+ return out;
295
+ }
296
+
297
+ function renderProofEntry(entry, prefixes) {
298
+ if (!entry) return '';
299
+ if (entry.kind === 'fact') {
300
+ return ` ${graphForTriple(entry.fact, prefixes)}\n pe:by ${byBlankNode('fact', entry.source)}.`;
301
+ }
302
+ if (entry.kind === 'builtin') {
303
+ return ` ${graphForTriple(entry.fact, prefixes)}\n pe:by [ pe:builtin ${termToN3(entry.builtin, prefixes)} ].`;
304
+ }
305
+
306
+ const df = entry.df;
307
+ const props = [`pe:by ${byBlankNode('rule', df.rule && df.rule.__source)}`];
308
+ const bindings = renderBindingList(df, prefixes);
309
+ if (bindings) props.push(`pe:binding ${bindings}`);
310
+ if (df.premises && df.premises.length) {
311
+ props.push(`pe:uses ${df.premises.map((prem) => graphForTriple(prem, prefixes)).join(', ')}`);
312
+ }
313
+
314
+ let out = ` ${graphForTriple(df.fact, prefixes)}\n`;
315
+ for (let i = 0; i < props.length; i++) {
316
+ out += ` ${props[i]}${i === props.length - 1 ? '.' : ';'}\n`;
317
+ }
318
+ return out.trimEnd();
319
+ }
320
+
321
+ function renderProofBlock(rootDf, derivedByKey, baseFactByKey, prefixes, resolveBackwardProof) {
322
+ const entries = collectProofEntries(rootDf, derivedByKey, baseFactByKey, resolveBackwardProof);
323
+ const rootGraph = graphForTriple(rootDf.fact, prefixes);
324
+ const proofBody = entries.map((entry) => renderProofEntry(entry, prefixes)).join('\n\n');
325
+ const proofGraph = proofBody ? `{
326
+ ${proofBody}
327
+ }` : '{}';
328
+ return `${rootGraph} pe:why ${proofGraph}.`;
329
+ }
330
+
331
+ function renderProofDocument(outputDerived, allDerived, baseFacts, prefixes, backRules) {
332
+ const selectedDerived = Array.isArray(outputDerived) ? outputDerived.filter((df) => df && df.fact) : [];
333
+ if (!selectedDerived.length) return '';
334
+
335
+ const proofPrefixes = clonePrefixEnvWithProofVocabulary(prefixes);
336
+ const derivedByKey = new Map();
337
+ for (const df of allDerived || []) {
338
+ if (!df || !df.fact) continue;
339
+ const key = proofTripleKey(df.fact);
340
+ if (!derivedByKey.has(key)) derivedByKey.set(key, df);
341
+ }
342
+ // Do not insert query-wrapper output facts here: those wrappers often derive
343
+ // the same triple they use, and would hide the underlying fact/rule proof.
344
+
345
+ const baseFactByKey = new Map();
346
+ for (const tr of baseFacts || []) {
347
+ if (!tr) continue;
348
+ const key = proofTripleKey(tr);
349
+ if (!baseFactByKey.has(key)) baseFactByKey.set(key, tr);
350
+ }
351
+
352
+ const resolveBackwardProof = findBackwardProofForGoal
353
+ ? (tr) => findBackwardProofForGoal(tr, baseFacts || [], backRules || [], { maxDepth: 64 })
354
+ : null;
355
+
356
+ const outputTriples = collectProofOutputTriples(selectedDerived);
357
+ const proofRelationTriples = [];
358
+ for (const df of selectedDerived) {
359
+ proofRelationTriples.push({ s: new GraphTerm([df.fact]), p: new Iri(PE_NS + 'why'), o: new GraphTerm([]) });
360
+ for (const entry of collectProofEntries(df, derivedByKey, baseFactByKey, resolveBackwardProof)) {
361
+ const fact = entry.kind === 'rule' ? entry.df.fact : entry.fact;
362
+ const byObject = entry.kind === 'builtin' && entry.builtin ? entry.builtin : new Iri(PE_NS + 'source');
363
+ proofRelationTriples.push({ s: new GraphTerm([fact]), p: new Iri(PE_NS + 'by'), o: byObject });
364
+ if (entry.kind === 'rule') {
365
+ for (const prem of entry.df.premises || []) proofRelationTriples.push({ s: new GraphTerm([fact]), p: new Iri(PE_NS + 'uses'), o: new GraphTerm([prem]) });
366
+ }
367
+ }
368
+ }
369
+
370
+ const usedPrefixes = proofPrefixes.prefixesUsedForOutput(outputTriples.concat(proofRelationTriples));
371
+ if (!usedPrefixes.some(([pfx]) => pfx === 'pe')) usedPrefixes.push(['pe', PE_NS]);
372
+ usedPrefixes.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
373
+
374
+ const parts = [];
375
+ for (const [pfx, base] of usedPrefixes) {
376
+ if (!base) continue;
377
+ if (pfx === '') parts.push(`@prefix : <${base}> .`);
378
+ else parts.push(`@prefix ${pfx}: <${base}> .`);
379
+ }
380
+ if (parts.length) parts.push('');
381
+
382
+ parts.push(...outputTriples.map((tr) => tripleToN3(tr, proofPrefixes)));
383
+ parts.push('');
384
+ for (let i = 0; i < selectedDerived.length; i++) {
385
+ if (i > 0) parts.push('');
386
+ parts.push(renderProofBlock(selectedDerived[i], derivedByKey, baseFactByKey, proofPrefixes, resolveBackwardProof));
387
+ }
388
+
389
+ return parts.join('\n').replace(/[ \t]+$/gm, '').replace(/\s*$/g, '') + '\n';
390
+ }
391
+
114
392
  // ===========================================================================
115
393
  // CLI entry point
116
394
  // ===========================================================================
@@ -217,7 +495,7 @@ function makeExplain(deps) {
217
495
  return addMarkdownHardBreaks(pairs.map((p) => p.text).join(''));
218
496
  }
219
497
 
220
- return { printExplanation, collectOutputStringsFromFacts };
498
+ return { printExplanation, renderProofDocument, collectOutputStringsFromFacts };
221
499
  }
222
500
 
223
501
  module.exports = { makeExplain };
@@ -31,6 +31,67 @@ function emptyParsedDocument() {
31
31
  };
32
32
  }
33
33
 
34
+ function lineStartsForText(text) {
35
+ const s = String(text);
36
+ const starts = [0];
37
+ for (let i = 0; i < s.length; i++) {
38
+ const c = s.charCodeAt(i);
39
+ if (c === 10) starts.push(i + 1); // LF
40
+ else if (c === 13) { // CR or CRLF
41
+ if (i + 1 < s.length && s.charCodeAt(i + 1) === 10) i++;
42
+ starts.push(i + 1);
43
+ }
44
+ }
45
+ return starts;
46
+ }
47
+
48
+ function offsetToLineFromStarts(offset, lineStarts) {
49
+ if (typeof offset !== 'number') return null;
50
+ let lo = 0;
51
+ let hi = lineStarts.length;
52
+ while (lo + 1 < hi) {
53
+ const mid = (lo + hi) >> 1;
54
+ if (lineStarts[mid] <= offset) lo = mid;
55
+ else hi = mid;
56
+ }
57
+ return lo + 1;
58
+ }
59
+
60
+ function annotateSourceLocation(obj, label, lineStarts) {
61
+ if (!obj || typeof obj.__sourceOffset !== 'number') return obj;
62
+ const line = offsetToLineFromStarts(obj.__sourceOffset, lineStarts);
63
+ Object.defineProperty(obj, '__source', {
64
+ value: { label, line },
65
+ enumerable: false,
66
+ writable: false,
67
+ configurable: true,
68
+ });
69
+ return obj;
70
+ }
71
+
72
+ function annotateParsedSourceLocations(doc, text, label) {
73
+ const lineStarts = lineStartsForText(text);
74
+ for (const tr of doc.triples || []) annotateSourceLocation(tr, label, lineStarts);
75
+ for (const r of doc.frules || []) annotateSourceLocation(r, label, lineStarts);
76
+ for (const r of doc.brules || []) annotateSourceLocation(r, label, lineStarts);
77
+ for (const r of doc.logQueryRules || []) annotateSourceLocation(r, label, lineStarts);
78
+ }
79
+
80
+ function copySourceMetadata(from, to) {
81
+ if (!from || !to) return to;
82
+ for (const key of ['__sourceOffset', '__source']) {
83
+ if (Object.prototype.hasOwnProperty.call(from, key)) {
84
+ Object.defineProperty(to, key, {
85
+ value: from[key],
86
+ enumerable: false,
87
+ writable: false,
88
+ configurable: true,
89
+ });
90
+ }
91
+ }
92
+ return to;
93
+ }
94
+
34
95
  function prefixesUsedInTokens(tokens, prefEnv) {
35
96
  const used = new Set();
36
97
  const toks = Array.isArray(tokens) ? tokens : [];
@@ -108,6 +169,7 @@ function parseN3Text(text, opts = {}) {
108
169
  label = '<input>',
109
170
  keepSourceArtifacts = true,
110
171
  collectUsedPrefixes = false,
172
+ collectSourceLocations = true,
111
173
  rdf = false,
112
174
  } = opts || {};
113
175
  const tokens = lex(text, { rdf });
@@ -116,6 +178,7 @@ function parseN3Text(text, opts = {}) {
116
178
  const [prefixes, triples, frules, brules, logQueryRules] = parser.parseDocument();
117
179
 
118
180
  const doc = { prefixes, triples, frules, brules, logQueryRules, label };
181
+ if (collectSourceLocations) annotateParsedSourceLocations(doc, text, label);
119
182
 
120
183
  if (collectUsedPrefixes) {
121
184
  Object.defineProperty(doc, 'usedPrefixes', {
@@ -161,7 +224,7 @@ function scopeBlankNodesInDocument(doc, sourceIndex) {
161
224
  }
162
225
 
163
226
  function cloneTriple(triple) {
164
- return new Triple(cloneTerm(triple.s), cloneTerm(triple.p), cloneTerm(triple.o));
227
+ return copySourceMetadata(triple, new Triple(cloneTerm(triple.s), cloneTerm(triple.p), cloneTerm(triple.o)));
165
228
  }
166
229
 
167
230
  function cloneRule(rule) {
@@ -170,12 +233,15 @@ function scopeBlankNodesInDocument(doc, sourceIndex) {
170
233
  for (const label of rule.headBlankLabels) headBlankLabels.add(scopedBlankLabel(label, sourceIndex, mapping));
171
234
  }
172
235
 
173
- const out = new Rule(
174
- (rule.premise || []).map(cloneTriple),
175
- (rule.conclusion || []).map(cloneTriple),
176
- rule.isForward,
177
- rule.isFuse,
178
- headBlankLabels,
236
+ const out = copySourceMetadata(
237
+ rule,
238
+ new Rule(
239
+ (rule.premise || []).map(cloneTriple),
240
+ (rule.conclusion || []).map(cloneTriple),
241
+ rule.isForward,
242
+ rule.isFuse,
243
+ headBlankLabels,
244
+ ),
179
245
  );
180
246
 
181
247
  if (rule && Object.prototype.hasOwnProperty.call(rule, '__dynamicConclusionTerm')) {
@@ -303,6 +369,7 @@ function parseN3SourceList(input, opts = {}) {
303
369
  label: source.label,
304
370
  baseIri: source.baseIri || (sources.length === 1 ? defaultBaseIri : ''),
305
371
  collectUsedPrefixes: true,
372
+ collectSourceLocations: !!opts.collectSourceLocations,
306
373
  keepSourceArtifacts: !!opts.keepSourceArtifacts,
307
374
  rdf: !!opts.rdf,
308
375
  }),
package/lib/parser.js CHANGED
@@ -42,6 +42,17 @@ function failInvalidKeywordLikeIdent(fail, tok, name) {
42
42
  fail(`invalid_keyword(${name})`, tok);
43
43
  }
44
44
 
45
+ function annotateSourceOffset(obj, offset) {
46
+ if (!obj || typeof offset !== 'number') return obj;
47
+ Object.defineProperty(obj, '__sourceOffset', {
48
+ value: offset,
49
+ enumerable: false,
50
+ writable: false,
51
+ configurable: true,
52
+ });
53
+ return obj;
54
+ }
55
+
45
56
  class Parser {
46
57
  constructor(tokens) {
47
58
  this.toks = tokens;
@@ -166,17 +177,19 @@ class Parser {
166
177
  continue;
167
178
  }
168
179
 
180
+ const statementToken = this.peek();
181
+ const statementOffset = statementToken && typeof statementToken.offset === 'number' ? statementToken.offset : null;
169
182
  const first = this.parseTerm();
170
183
  if (this.peek().typ === 'OpImplies') {
171
184
  this.next();
172
185
  const second = this.parseTerm();
173
186
  this.expectDot();
174
- forwardRules.push(this.makeRule(first, second, true));
187
+ forwardRules.push(annotateSourceOffset(this.makeRule(first, second, true), statementOffset));
175
188
  } else if (this.peek().typ === 'OpImpliedBy') {
176
189
  this.next();
177
190
  const second = this.parseTerm();
178
191
  this.expectDot();
179
- backwardRules.push(this.makeRule(first, second, false));
192
+ backwardRules.push(annotateSourceOffset(this.makeRule(first, second, false), statementOffset));
180
193
  } else {
181
194
  let more;
182
195
 
@@ -194,16 +207,17 @@ class Parser {
194
207
  }
195
208
 
196
209
  // normalize explicit log:implies / log:impliedBy at top-level
197
- for (const tr of more) {
210
+ for (const tr0 of more) {
211
+ const tr = annotateSourceOffset(tr0, statementOffset);
198
212
  if (isLogImplies(tr.p) && tr.s instanceof GraphTerm && tr.o instanceof GraphTerm) {
199
- forwardRules.push(this.makeRule(tr.s, tr.o, true));
213
+ forwardRules.push(annotateSourceOffset(this.makeRule(tr.s, tr.o, true), statementOffset));
200
214
  } else if (isLogImpliedBy(tr.p) && tr.s instanceof GraphTerm && tr.o instanceof GraphTerm) {
201
- backwardRules.push(this.makeRule(tr.s, tr.o, false));
215
+ backwardRules.push(annotateSourceOffset(this.makeRule(tr.s, tr.o, false), statementOffset));
202
216
  } else if (isLogQuery(tr.p) && tr.s instanceof GraphTerm && tr.o instanceof GraphTerm) {
203
217
  // Output-selection directive: { premise } log:query { conclusion }.
204
218
  // When present at top-level, eyeling prints only the instantiated conclusion
205
219
  // triples (unique) instead of all newly derived facts.
206
- logQueries.push(this.makeRule(tr.s, tr.o, true));
220
+ logQueries.push(annotateSourceOffset(this.makeRule(tr.s, tr.o, true), statementOffset));
207
221
  } else {
208
222
  triples.push(tr);
209
223
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.25.4",
3
+ "version": "1.26.0",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [
package/test/api.test.js CHANGED
@@ -454,7 +454,7 @@ ${U('x')} ${U('age')} "42".
454
454
  ${U('s')} ${U('p')} ${U('o')}.
455
455
  `,
456
456
  expect: [new RegExp(`${EX}s>\\s+<${EX}q>\\s+<${EX}o>\\s*\\.`)],
457
- notExpect: [/^#/m],
457
+ notExpect: [/pe:why/m],
458
458
  },
459
459
  {
460
460
  name: '11 negative entailment: rule derives false (expect exit 65 => throws)',
@@ -1038,23 +1038,23 @@ ${U('a')} ${U('p')} ${U('b')}.
1038
1038
  expect: [new RegExp(`${EX}a>\\s+<${EX}q2>\\s+<${EX}b>\\s*\\.`)],
1039
1039
  },
1040
1040
  {
1041
- name: '30 sanity: proofComments:true enables proof comments',
1042
- opt: { proofComments: true },
1041
+ name: '30 sanity: proof:true enables N3 proof explanations',
1042
+ opt: { proof: true },
1043
1043
  input: `
1044
1044
  { ${U('s')} ${U('p')} ${U('o')}. } => { ${U('s')} ${U('q')} ${U('o')}. }.
1045
1045
  ${U('s')} ${U('p')} ${U('o')}.
1046
1046
  `,
1047
- expect: [/^#/m, new RegExp(`${EX}s>\\s+<${EX}q>\\s+<${EX}o>\\s*\\.`)],
1047
+ expect: [/pe:why/m, /pe:by/m, new RegExp(`${EX}s>\\s+<${EX}q>\\s+<${EX}o>\\s*\\.`)],
1048
1048
  },
1049
1049
  {
1050
- name: '31 sanity: -n suppresses proof comments',
1050
+ name: '31 sanity: default output has no proof explanations',
1051
1051
  opt: ['-n'],
1052
1052
  input: `
1053
1053
  { ${U('s')} ${U('p')} ${U('o')}. } => { ${U('s')} ${U('q')} ${U('o')}. }.
1054
1054
  ${U('s')} ${U('p')} ${U('o')}.
1055
1055
  `,
1056
1056
  expect: [new RegExp(`${EX}s>\\s+<${EX}q>\\s+<${EX}o>\\s*\\.`)],
1057
- notExpect: [/^#/m],
1057
+ notExpect: [/pe:why/m],
1058
1058
  },
1059
1059
 
1060
1060
  // -------------------------
@@ -2496,6 +2496,26 @@ _:b a ex:Person ; ex:name "B" .
2496
2496
  ],
2497
2497
  notExpect: [/:result\s+:sumX\s+"329"\^\^xsd:decimal\s*\./, /:result\s+:meanX\s+"47"\^\^xsd:decimal\s*\./],
2498
2498
  },
2499
+ {
2500
+ name: '244 regression: scoped collectAllIn is stratified after scoped notIncludes',
2501
+ opt: { proofComments: false },
2502
+ input: `@prefix : <http://example.org/> .
2503
+ @prefix log: <http://www.w3.org/2000/10/swap/log#> .
2504
+
2505
+ :a :x 1 .
2506
+ :b :x 1 .
2507
+
2508
+ { ?s :x 1 . ?X log:notIncludes { ?s :marked ?X } } => { ?s :marked true } .
2509
+ { ( ?s { ?s :marked true } ?all ) log:collectAllIn ?scope } => { :result :marked ?all } .
2510
+ `,
2511
+ expect: [
2512
+ /^:a\s+:marked\s+true\s*\./m,
2513
+ /^:b\s+:marked\s+true\s*\./m,
2514
+ /^:result\s+:marked\s+\(:a\s+:b\)\s*\./m,
2515
+ ],
2516
+ notExpect: [/^:result\s+:marked\s+\(\)\s*\./m],
2517
+ },
2518
+
2499
2519
  {
2500
2520
  name: '244 regression: log:dtlit recognizes shorthand numeric and boolean literals',
2501
2521
  opt: { proofComments: false },
package/tools/bundle.js CHANGED
@@ -158,6 +158,9 @@ function buildBundleSource({ autoRunMain }) {
158
158
  out.push(
159
159
  ' if (typeof __entry.printExplanation === "function") __outerSelf.printExplanation = __entry.printExplanation;',
160
160
  );
161
+ out.push(
162
+ ' if (typeof __entry.renderProofDocument === "function") __outerSelf.renderProofDocument = __entry.renderProofDocument;',
163
+ );
161
164
  out.push(' if (typeof __entry.tripleToN3 === "function") __outerSelf.tripleToN3 = __entry.tripleToN3;');
162
165
  out.push(
163
166
  ' if (typeof __entry.collectOutputStringsFromFacts === "function") __outerSelf.collectOutputStringsFromFacts = __entry.collectOutputStringsFromFacts;',