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/HANDBOOK.md +8 -8
- package/dist/browser/eyeling.browser.js +788 -32
- package/examples/output/socrates.n3 +0 -1
- package/examples/proof/age.n3 +31 -0
- package/examples/proof/socrates.n3 +19 -0
- package/examples/proof/witch.n3 +120 -0
- package/examples/socrates.n3 +0 -3
- package/eyeling.js +788 -32
- package/index.d.ts +1 -0
- package/index.js +16 -13
- package/lib/cli.js +10 -4
- package/lib/engine.js +402 -13
- package/lib/entry.js +1 -0
- package/lib/explain.js +280 -2
- package/lib/multisource.js +74 -7
- package/lib/parser.js +20 -6
- package/package.json +1 -1
- package/test/api.test.js +26 -6
- package/tools/bundle.js +3 -0
- package/examples/library-and-path.n3 +0 -499
- package/examples/output/library-and-path.n3 +0 -33
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 };
|
package/lib/multisource.js
CHANGED
|
@@ -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 =
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
|
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
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: [
|
|
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:
|
|
1042
|
-
opt: {
|
|
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: [
|
|
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:
|
|
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: [
|
|
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;',
|