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.
- package/HANDBOOK.md +8 -8
- package/dist/browser/eyeling.browser.js +555 -32
- package/examples/output/socrates.n3 +0 -1
- package/examples/proof/age.n3 +40 -0
- package/examples/proof/socrates.n3 +24 -0
- package/examples/proof/witch.n3 +138 -0
- package/examples/socrates.n3 +0 -3
- package/eyeling.js +555 -32
- package/index.d.ts +1 -0
- package/index.js +16 -13
- package/lib/cli.js +10 -4
- package/lib/engine.js +150 -13
- package/lib/entry.js +1 -0
- package/lib/explain.js +299 -2
- package/lib/multisource.js +74 -7
- package/lib/parser.js +20 -6
- package/package.json +1 -1
- package/test/api.test.js +6 -6
- package/tools/bundle.js +3 -0
package/lib/multisource.js
CHANGED
|
@@ -31,6 +31,67 @@ function emptyParsedDocument() {
|
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
function lineStartsForText(text) {
|
|
35
|
+
const starts = [0];
|
|
36
|
+
const str = String(text);
|
|
37
|
+
for (let i = 0; i < str.length; i++) {
|
|
38
|
+
const c = str[i];
|
|
39
|
+
if (c === '\n') starts.push(i + 1);
|
|
40
|
+
else if (c === '\r') {
|
|
41
|
+
starts.push(i + 1 < str.length && str[i + 1] === '\n' ? i + 2 : i + 1);
|
|
42
|
+
if (i + 1 < str.length && str[i + 1] === '\n') i++;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return starts;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function offsetToLine(lineStarts, offset) {
|
|
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, lineStarts, label) {
|
|
61
|
+
if (!obj || typeof obj.__sourceOffset !== 'number') return obj;
|
|
62
|
+
const line = offsetToLine(lineStarts, obj.__sourceOffset);
|
|
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, lineStarts, label);
|
|
75
|
+
for (const r of doc.frules || []) annotateSourceLocation(r, lineStarts, label);
|
|
76
|
+
for (const r of doc.brules || []) annotateSourceLocation(r, lineStarts, label);
|
|
77
|
+
for (const r of doc.logQueryRules || []) annotateSourceLocation(r, lineStarts, label);
|
|
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
|
+
sourceLocations = false,
|
|
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 (sourceLocations) 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')) {
|
|
@@ -304,6 +370,7 @@ function parseN3SourceList(input, opts = {}) {
|
|
|
304
370
|
baseIri: source.baseIri || (sources.length === 1 ? defaultBaseIri : ''),
|
|
305
371
|
collectUsedPrefixes: true,
|
|
306
372
|
keepSourceArtifacts: !!opts.keepSourceArtifacts,
|
|
373
|
+
sourceLocations: !!opts.sourceLocations,
|
|
307
374
|
rdf: !!opts.rdf,
|
|
308
375
|
}),
|
|
309
376
|
);
|
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
|
// -------------------------
|
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;',
|