eyeling 1.26.0 → 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/lib/explain.js CHANGED
@@ -135,7 +135,6 @@ ${lineIndent(body, ' ')}
135
135
  }`;
136
136
  }
137
137
 
138
-
139
138
  function clonePrefixEnvWithProofVocabulary(prefixes) {
140
139
  const map = { ...(prefixes && prefixes.map ? prefixes.map : {}) };
141
140
  if (!map.pe) map.pe = PE_NS;
@@ -153,6 +152,11 @@ ${lineIndent(body, ' ')}
153
152
  return source.label.replace(/\\/g, '/').split('/').pop() || source.label;
154
153
  }
155
154
 
155
+ function sourceKeyForProof(source) {
156
+ if (!source) return '<unknown>';
157
+ return `${sourceLabelForProof(source)}:${Number.isInteger(source.line) ? source.line : ''}`;
158
+ }
159
+
156
160
  function byBlankNode(kind, source) {
157
161
  const src = source || {};
158
162
  const props = [`pe:${kind} ${n3String(sourceLabelForProof(src))}`];
@@ -160,121 +164,129 @@ ${lineIndent(body, ' ')}
160
164
  return `[ ${props.join('; ')} ]`;
161
165
  }
162
166
 
163
- function renderBindingList(df, prefixes) {
164
- if (!df || !df.rule || !df.subst) return '';
167
+ function renderBindingItems(df, prefixes) {
168
+ if (!df || !df.rule || !df.subst) return [];
165
169
  const ruleVars = varsInRule(df.rule);
166
170
  const visibleNames = Object.keys(df.subst)
167
171
  .filter((name) => ruleVars.has(name))
168
172
  .sort();
169
- if (!visibleNames.length) return '';
173
+ if (!visibleNames.length) return [];
170
174
  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(', ');
175
+ return visibleNames.map((name) => {
176
+ const value = applySubstTerm(new Var(name), df.subst);
177
+ const displayName = sourceNames && sourceNames[name] ? sourceNames[name] : name;
178
+ return `[ pe:var ${n3String(displayName)}; pe:value ${termToN3(value, prefixes)} ]`;
179
+ });
178
180
  }
179
181
 
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}`;
182
+ function withLastLineSuffix(text, suffix) {
183
+ const lines = String(text).split(/\r?\n/);
184
+ lines[lines.length - 1] += suffix;
185
+ return lines.join('\n');
185
186
  }
186
187
 
187
- function proofEntryKey(kind, fact, source, extra) {
188
- return `${kind}\t${proofTripleKey(fact)}\t${proofSourceKey(source)}\t${extra || ''}`;
188
+ function renderPredicateObjects(predicate, objects, isLast) {
189
+ const values = (objects || []).filter((value) => value);
190
+ if (!values.length) return [];
191
+ const end = isLast ? '.' : ';';
192
+ if (values.length === 1 && !values[0].includes('\n')) {
193
+ return [` ${predicate} ${values[0]}${end}`];
194
+ }
195
+ const out = [` ${predicate}`];
196
+ for (let i = 0; i < values.length; i++) {
197
+ const suffix = i === values.length - 1 ? end : ',';
198
+ out.push(lineIndent(withLastLineSuffix(values[i], suffix), ' '));
199
+ }
200
+ return out;
189
201
  }
190
202
 
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);
203
+ function proofEntryKey(entry) {
204
+ if (!entry) return '';
205
+ if (entry.kind === 'rule') {
206
+ const df = entry.df;
207
+ const source = df && df.rule && df.rule.__source;
208
+ const premiseKey = (df && df.premises ? df.premises : []).map(proofTripleKey).join('|');
209
+ return `rule:${proofTripleKey(df && df.fact)}:${sourceKeyForProof(source)}:${premiseKey}`;
210
+ }
211
+ if (entry.kind === 'builtin') return `builtin:${proofTripleKey(entry.fact)}`;
212
+ return `fact:${proofTripleKey(entry.fact)}:${sourceKeyForProof(entry.source)}`;
195
213
  }
196
214
 
197
215
  function collectProofEntries(rootDf, derivedByKey, baseFactByKey, resolveBackwardProof) {
198
216
  const entries = [];
199
217
  const seen = new Set();
200
218
 
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);
219
+ function remember(entry) {
220
+ const key = proofEntryKey(entry);
221
+ if (!key || seen.has(key)) return false;
222
+ seen.add(key);
223
+ entries.push(entry);
224
+ return true;
225
+ }
226
+
227
+ function derivedCandidatesForKey(key) {
228
+ const found = derivedByKey.get(key);
229
+ if (!found) return [];
230
+ return Array.isArray(found) ? found : [found];
206
231
  }
207
232
 
208
- function visitProofNode(proof, fallbackTriple) {
233
+ function visitProofNode(proof, fallbackTriple, parentDf) {
209
234
  if (!proof) {
210
- visitFactTriple(fallbackTriple);
235
+ visitFactTriple(fallbackTriple, parentDf);
211
236
  return;
212
237
  }
213
- const key = entryKeyForProof(proof, fallbackTriple);
214
- if (!key || seen.has(key)) return;
215
238
  if (proof.kind === 'rule' && proof.df) {
216
239
  visitDerivedFact(proof.df, proof.children || null);
217
240
  return;
218
241
  }
219
- seen.add(key);
220
242
  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 });
243
+ remember({ kind: 'builtin', fact: proof.fact || fallbackTriple, builtin: proof.builtin || (proof.fact && proof.fact.p) });
244
+ return;
228
245
  }
246
+ remember({ kind: 'fact', fact: proof.fact || fallbackTriple, source: proof.source });
229
247
  }
230
248
 
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);
249
+ function visitFactTriple(tr, parentDf) {
250
+ const key = proofTripleKey(tr);
251
+ const base = baseFactByKey.get(key) || null;
252
+ if (base) {
253
+ remember({ kind: 'fact', fact: base, source: base.__source });
237
254
  return;
238
255
  }
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 });
256
+
257
+ const candidates = derivedCandidatesForKey(key);
258
+ const df = candidates.find((candidate) => candidate !== parentDf) || null;
259
+ if (df) {
260
+ visitDerivedFact(df);
245
261
  return;
246
262
  }
263
+
247
264
  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 });
265
+ remember({ kind: 'builtin', fact: tr, builtin: tr.p });
252
266
  return;
253
267
  }
268
+
254
269
  if (resolveBackwardProof) {
255
270
  const proof = resolveBackwardProof(tr);
256
271
  if (proof) {
257
- visitProofNode(proof, tr);
272
+ visitProofNode(proof, tr, parentDf);
258
273
  return;
259
274
  }
260
275
  }
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 });
276
+
277
+ remember({ kind: 'fact', fact: tr, source: null });
265
278
  }
266
279
 
267
280
  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 });
281
+ if (!df || !df.fact) return;
282
+ const entry = { kind: 'rule', df };
283
+ if (!remember(entry)) return;
272
284
 
273
285
  if (Array.isArray(children) && children.length) {
274
- for (const child of children) visitProofNode(child);
286
+ for (const child of children) visitProofNode(child, null, df);
275
287
  return;
276
288
  }
277
- for (const prem of df.premises || []) visitFactTriple(prem);
289
+ for (const prem of df.premises || []) visitFactTriple(prem, df);
278
290
  }
279
291
 
280
292
  visitDerivedFact(rootDf);
@@ -304,16 +316,18 @@ ${lineIndent(body, ' ')}
304
316
  }
305
317
 
306
318
  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
- }
319
+ const bindingItems = renderBindingItems(df, prefixes);
320
+ const useItems = (df.premises || []).map((prem) => graphForTriple(prem, prefixes));
321
+ const propertyGroups = [
322
+ { predicate: 'pe:by', objects: [byBlankNode('rule', df.rule && df.rule.__source)] },
323
+ { predicate: 'pe:binding', objects: bindingItems },
324
+ { predicate: 'pe:uses', objects: useItems },
325
+ ].filter((group) => group.objects.length);
313
326
 
314
327
  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`;
328
+ for (let i = 0; i < propertyGroups.length; i++) {
329
+ const group = propertyGroups[i];
330
+ out += renderPredicateObjects(group.predicate, group.objects, i === propertyGroups.length - 1).join('\n') + '\n';
317
331
  }
318
332
  return out.trimEnd();
319
333
  }
@@ -334,13 +348,18 @@ ${proofBody}
334
348
 
335
349
  const proofPrefixes = clonePrefixEnvWithProofVocabulary(prefixes);
336
350
  const derivedByKey = new Map();
337
- for (const df of allDerived || []) {
338
- if (!df || !df.fact) continue;
351
+ function addDerived(df) {
352
+ if (!df || !df.fact) return;
339
353
  const key = proofTripleKey(df.fact);
340
- if (!derivedByKey.has(key)) derivedByKey.set(key, df);
354
+ let bucket = derivedByKey.get(key);
355
+ if (!bucket) {
356
+ bucket = [];
357
+ derivedByKey.set(key, bucket);
358
+ }
359
+ if (!bucket.includes(df)) bucket.push(df);
341
360
  }
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.
361
+ for (const df of allDerived || []) addDerived(df);
362
+ for (const df of selectedDerived) addDerived(df);
344
363
 
345
364
  const baseFactByKey = new Map();
346
365
  for (const tr of baseFacts || []) {
@@ -32,20 +32,20 @@ function emptyParsedDocument() {
32
32
  }
33
33
 
34
34
  function lineStartsForText(text) {
35
- const s = String(text);
36
35
  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);
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
43
  }
44
44
  }
45
45
  return starts;
46
46
  }
47
47
 
48
- function offsetToLineFromStarts(offset, lineStarts) {
48
+ function offsetToLine(lineStarts, offset) {
49
49
  if (typeof offset !== 'number') return null;
50
50
  let lo = 0;
51
51
  let hi = lineStarts.length;
@@ -57,9 +57,9 @@ function offsetToLineFromStarts(offset, lineStarts) {
57
57
  return lo + 1;
58
58
  }
59
59
 
60
- function annotateSourceLocation(obj, label, lineStarts) {
60
+ function annotateSourceLocation(obj, lineStarts, label) {
61
61
  if (!obj || typeof obj.__sourceOffset !== 'number') return obj;
62
- const line = offsetToLineFromStarts(obj.__sourceOffset, lineStarts);
62
+ const line = offsetToLine(lineStarts, obj.__sourceOffset);
63
63
  Object.defineProperty(obj, '__source', {
64
64
  value: { label, line },
65
65
  enumerable: false,
@@ -71,10 +71,10 @@ function annotateSourceLocation(obj, label, lineStarts) {
71
71
 
72
72
  function annotateParsedSourceLocations(doc, text, label) {
73
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);
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
78
  }
79
79
 
80
80
  function copySourceMetadata(from, to) {
@@ -169,7 +169,7 @@ function parseN3Text(text, opts = {}) {
169
169
  label = '<input>',
170
170
  keepSourceArtifacts = true,
171
171
  collectUsedPrefixes = false,
172
- collectSourceLocations = true,
172
+ sourceLocations = false,
173
173
  rdf = false,
174
174
  } = opts || {};
175
175
  const tokens = lex(text, { rdf });
@@ -178,7 +178,7 @@ function parseN3Text(text, opts = {}) {
178
178
  const [prefixes, triples, frules, brules, logQueryRules] = parser.parseDocument();
179
179
 
180
180
  const doc = { prefixes, triples, frules, brules, logQueryRules, label };
181
- if (collectSourceLocations) annotateParsedSourceLocations(doc, text, label);
181
+ if (sourceLocations) annotateParsedSourceLocations(doc, text, label);
182
182
 
183
183
  if (collectUsedPrefixes) {
184
184
  Object.defineProperty(doc, 'usedPrefixes', {
@@ -369,8 +369,8 @@ function parseN3SourceList(input, opts = {}) {
369
369
  label: source.label,
370
370
  baseIri: source.baseIri || (sources.length === 1 ? defaultBaseIri : ''),
371
371
  collectUsedPrefixes: true,
372
- collectSourceLocations: !!opts.collectSourceLocations,
373
372
  keepSourceArtifacts: !!opts.keepSourceArtifacts,
373
+ sourceLocations: !!opts.sourceLocations,
374
374
  rdf: !!opts.rdf,
375
375
  }),
376
376
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.26.0",
3
+ "version": "1.26.1",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [