eyeling 1.23.1 → 1.23.3
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 +19 -7
- package/dist/browser/eyeling.browser.js +137 -565
- package/examples/builtin/queens.js +141 -0
- package/{lib/builtin-sudoku.js → examples/builtin/sudoku.js} +15 -0
- package/examples/output/queens.txt +21 -0
- package/examples/queens.n3 +20 -0
- package/examples/sudoku.n3 +7 -1
- package/eyeling.js +147 -562
- package/lib/cli.js +3 -75
- package/lib/engine.js +0 -4
- package/lib/multisource.js +133 -14
- package/package.json +1 -1
- package/test/examples.test.js +33 -10
package/lib/cli.js
CHANGED
|
@@ -208,6 +208,8 @@ function main() {
|
|
|
208
208
|
parseN3Text(text, {
|
|
209
209
|
baseIri: __sourceLabelToBaseIri(sourceLabel),
|
|
210
210
|
label: sourceLabel,
|
|
211
|
+
collectUsedPrefixes: true,
|
|
212
|
+
keepSourceArtifacts: false,
|
|
211
213
|
}),
|
|
212
214
|
);
|
|
213
215
|
} catch (e) {
|
|
@@ -225,8 +227,6 @@ function main() {
|
|
|
225
227
|
const frules = mergedDocument.frules;
|
|
226
228
|
const brules = mergedDocument.brules;
|
|
227
229
|
const qrules = mergedDocument.logQueryRules;
|
|
228
|
-
const tokenSets = parsedSources.map((source) => ({ tokens: source.tokens, prefixes: source.prefixes }));
|
|
229
|
-
|
|
230
230
|
if (showAst) {
|
|
231
231
|
function astReplacer(unusedJsonKey, value) {
|
|
232
232
|
if (value instanceof Set) return Array.from(value);
|
|
@@ -282,75 +282,6 @@ function main() {
|
|
|
282
282
|
// In --stream mode we print prefixes *before* any derivations happen.
|
|
283
283
|
// To keep the header small and stable, emit only prefixes that are actually
|
|
284
284
|
// used (as QNames) in the *input* N3 program.
|
|
285
|
-
function prefixesUsedInInputTokens(toks2, prefEnv) {
|
|
286
|
-
const used = new Set();
|
|
287
|
-
|
|
288
|
-
function maybeAddFromQName(name) {
|
|
289
|
-
if (typeof name !== 'string') return;
|
|
290
|
-
if (!name.includes(':')) return;
|
|
291
|
-
if (name.startsWith('_:')) return; // blank node
|
|
292
|
-
|
|
293
|
-
// Split only on the first ':'
|
|
294
|
-
const idx = name.indexOf(':');
|
|
295
|
-
const p = name.slice(0, idx); // may be '' for ":foo"
|
|
296
|
-
|
|
297
|
-
// Ignore things like "http://..." unless that prefix is actually defined.
|
|
298
|
-
if (!Object.prototype.hasOwnProperty.call(prefEnv.map, p)) return;
|
|
299
|
-
|
|
300
|
-
used.add(p);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
for (let i = 0; i < toks2.length; i++) {
|
|
304
|
-
const t = toks2[i];
|
|
305
|
-
|
|
306
|
-
// Skip @prefix ... .
|
|
307
|
-
if (t.typ === 'AtPrefix') {
|
|
308
|
-
while (i < toks2.length && toks2[i].typ !== 'Dot' && toks2[i].typ !== 'EOF') i++;
|
|
309
|
-
continue;
|
|
310
|
-
}
|
|
311
|
-
// Skip @base ... .
|
|
312
|
-
if (t.typ === 'AtBase') {
|
|
313
|
-
while (i < toks2.length && toks2[i].typ !== 'Dot' && toks2[i].typ !== 'EOF') i++;
|
|
314
|
-
continue;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// Skip SPARQL/Turtle PREFIX pfx: <iri>
|
|
318
|
-
if (
|
|
319
|
-
t.typ === 'Ident' &&
|
|
320
|
-
typeof t.value === 'string' &&
|
|
321
|
-
t.value.toLowerCase() === 'prefix' &&
|
|
322
|
-
toks2[i + 1] &&
|
|
323
|
-
toks2[i + 1].typ === 'Ident' &&
|
|
324
|
-
typeof toks2[i + 1].value === 'string' &&
|
|
325
|
-
toks2[i + 1].value.endsWith(':') &&
|
|
326
|
-
toks2[i + 2] &&
|
|
327
|
-
(toks2[i + 2].typ === 'IriRef' || toks2[i + 2].typ === 'Ident')
|
|
328
|
-
) {
|
|
329
|
-
i += 2;
|
|
330
|
-
continue;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// Skip SPARQL BASE <iri>
|
|
334
|
-
if (
|
|
335
|
-
t.typ === 'Ident' &&
|
|
336
|
-
typeof t.value === 'string' &&
|
|
337
|
-
t.value.toLowerCase() === 'base' &&
|
|
338
|
-
toks2[i + 1] &&
|
|
339
|
-
toks2[i + 1].typ === 'IriRef'
|
|
340
|
-
) {
|
|
341
|
-
i += 1;
|
|
342
|
-
continue;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// Count QNames in identifiers (including datatypes like xsd:integer).
|
|
346
|
-
if (t.typ === 'Ident') {
|
|
347
|
-
maybeAddFromQName(t.value);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
return used;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
285
|
function restrictPrefixEnv(prefEnv, usedSet) {
|
|
355
286
|
const m = {};
|
|
356
287
|
for (const p of usedSet) {
|
|
@@ -368,10 +299,7 @@ function main() {
|
|
|
368
299
|
const mayAutoRenderOutputStrings = programMayProduceOutputStrings(triples, frules, qrules);
|
|
369
300
|
|
|
370
301
|
if (streamMode && !hasQueries && !mayAutoRenderOutputStrings) {
|
|
371
|
-
const usedInInput = new Set();
|
|
372
|
-
for (const source of tokenSets) {
|
|
373
|
-
for (const pfx of prefixesUsedInInputTokens(source.tokens, source.prefixes)) usedInInput.add(pfx);
|
|
374
|
-
}
|
|
302
|
+
const usedInInput = mergedDocument.usedPrefixes instanceof Set ? new Set(mergedDocument.usedPrefixes) : new Set();
|
|
375
303
|
const outPrefixes = restrictPrefixEnv(prefixes, usedInInput);
|
|
376
304
|
|
|
377
305
|
// Ensure log:trace uses the same compact prefix set as the output.
|
package/lib/engine.js
CHANGED
|
@@ -610,10 +610,6 @@ const { evalBuiltin, isBuiltinPred } = makeBuiltins({
|
|
|
610
610
|
termsEqualNoIntDecimal,
|
|
611
611
|
});
|
|
612
612
|
|
|
613
|
-
try {
|
|
614
|
-
registerBuiltinModule(require('./builtin-sudoku'), './builtin-sudoku');
|
|
615
|
-
} catch (_) {}
|
|
616
|
-
|
|
617
613
|
// Initialize proof/output helpers (implemented in lib/explain.js).
|
|
618
614
|
const { printExplanation, collectOutputStringsFromFacts } = makeExplain({
|
|
619
615
|
applySubstTerm,
|
package/lib/multisource.js
CHANGED
|
@@ -31,13 +31,101 @@ function emptyParsedDocument() {
|
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
function prefixesUsedInTokens(tokens, prefEnv) {
|
|
35
|
+
const used = new Set();
|
|
36
|
+
const toks = Array.isArray(tokens) ? tokens : [];
|
|
37
|
+
const prefixes = prefEnv && prefEnv.map ? prefEnv.map : {};
|
|
38
|
+
|
|
39
|
+
function maybeAddFromQName(name) {
|
|
40
|
+
if (typeof name !== 'string') return;
|
|
41
|
+
if (!name.includes(':')) return;
|
|
42
|
+
if (name.startsWith('_:')) return; // blank node
|
|
43
|
+
|
|
44
|
+
// Split only on the first ':'; the empty prefix is valid for ":foo".
|
|
45
|
+
const idx = name.indexOf(':');
|
|
46
|
+
const p = name.slice(0, idx);
|
|
47
|
+
|
|
48
|
+
// Ignore strings like "http://..." unless that prefix is actually defined.
|
|
49
|
+
if (!Object.prototype.hasOwnProperty.call(prefixes, p)) return;
|
|
50
|
+
|
|
51
|
+
used.add(p);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (let i = 0; i < toks.length; i++) {
|
|
55
|
+
const t = toks[i];
|
|
56
|
+
if (!t) continue;
|
|
57
|
+
|
|
58
|
+
// Skip @prefix ... .
|
|
59
|
+
if (t.typ === 'AtPrefix') {
|
|
60
|
+
while (i < toks.length && toks[i].typ !== 'Dot' && toks[i].typ !== 'EOF') i++;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Skip @base ... .
|
|
65
|
+
if (t.typ === 'AtBase') {
|
|
66
|
+
while (i < toks.length && toks[i].typ !== 'Dot' && toks[i].typ !== 'EOF') i++;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Skip SPARQL/Turtle PREFIX pfx: <iri>
|
|
71
|
+
if (
|
|
72
|
+
t.typ === 'Ident' &&
|
|
73
|
+
typeof t.value === 'string' &&
|
|
74
|
+
t.value.toLowerCase() === 'prefix' &&
|
|
75
|
+
toks[i + 1] &&
|
|
76
|
+
toks[i + 1].typ === 'Ident' &&
|
|
77
|
+
typeof toks[i + 1].value === 'string' &&
|
|
78
|
+
toks[i + 1].value.endsWith(':') &&
|
|
79
|
+
toks[i + 2] &&
|
|
80
|
+
(toks[i + 2].typ === 'IriRef' || toks[i + 2].typ === 'Ident')
|
|
81
|
+
) {
|
|
82
|
+
i += 2;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Skip SPARQL BASE <iri>
|
|
87
|
+
if (
|
|
88
|
+
t.typ === 'Ident' &&
|
|
89
|
+
typeof t.value === 'string' &&
|
|
90
|
+
t.value.toLowerCase() === 'base' &&
|
|
91
|
+
toks[i + 1] &&
|
|
92
|
+
toks[i + 1].typ === 'IriRef'
|
|
93
|
+
) {
|
|
94
|
+
i += 1;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Count QNames in identifiers, including datatypes like xsd:integer.
|
|
99
|
+
if (t.typ === 'Ident') maybeAddFromQName(t.value);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return used;
|
|
103
|
+
}
|
|
104
|
+
|
|
34
105
|
function parseN3Text(text, opts = {}) {
|
|
35
|
-
const { baseIri = '', label = '<input>' } = opts || {};
|
|
106
|
+
const { baseIri = '', label = '<input>', keepSourceArtifacts = true, collectUsedPrefixes = false } = opts || {};
|
|
36
107
|
const tokens = lex(text);
|
|
37
108
|
const parser = new Parser(tokens);
|
|
38
109
|
if (baseIri) parser.prefixes.setBase(baseIri);
|
|
39
110
|
const [prefixes, triples, frules, brules, logQueryRules] = parser.parseDocument();
|
|
40
|
-
|
|
111
|
+
|
|
112
|
+
const doc = { prefixes, triples, frules, brules, logQueryRules, label };
|
|
113
|
+
|
|
114
|
+
if (collectUsedPrefixes) {
|
|
115
|
+
Object.defineProperty(doc, 'usedPrefixes', {
|
|
116
|
+
value: prefixesUsedInTokens(tokens, prefixes),
|
|
117
|
+
enumerable: false,
|
|
118
|
+
writable: false,
|
|
119
|
+
configurable: true,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (keepSourceArtifacts) {
|
|
124
|
+
doc.tokens = tokens;
|
|
125
|
+
doc.text = text;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return doc;
|
|
41
129
|
}
|
|
42
130
|
|
|
43
131
|
function sourceBlankPrefix(sourceIndex) {
|
|
@@ -96,16 +184,27 @@ function scopeBlankNodesInDocument(doc, sourceIndex) {
|
|
|
96
184
|
return out;
|
|
97
185
|
}
|
|
98
186
|
|
|
99
|
-
|
|
187
|
+
const out = {
|
|
100
188
|
prefixes: doc.prefixes,
|
|
101
189
|
triples: (doc.triples || []).map(cloneTriple),
|
|
102
190
|
frules: (doc.frules || []).map(cloneRule),
|
|
103
191
|
brules: (doc.brules || []).map(cloneRule),
|
|
104
192
|
logQueryRules: (doc.logQueryRules || []).map(cloneRule),
|
|
105
|
-
tokens: doc.tokens,
|
|
106
|
-
text: doc.text,
|
|
107
193
|
label: doc.label,
|
|
108
194
|
};
|
|
195
|
+
|
|
196
|
+
if (doc.usedPrefixes instanceof Set) {
|
|
197
|
+
Object.defineProperty(out, 'usedPrefixes', {
|
|
198
|
+
value: new Set(doc.usedPrefixes),
|
|
199
|
+
enumerable: false,
|
|
200
|
+
writable: false,
|
|
201
|
+
configurable: true,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
if (Object.prototype.hasOwnProperty.call(doc, 'tokens')) out.tokens = doc.tokens;
|
|
205
|
+
if (Object.prototype.hasOwnProperty.call(doc, 'text')) out.text = doc.text;
|
|
206
|
+
|
|
207
|
+
return out;
|
|
109
208
|
}
|
|
110
209
|
|
|
111
210
|
function mergePrefixEnvs(target, source) {
|
|
@@ -124,9 +223,10 @@ function mergePrefixEnvs(target, source) {
|
|
|
124
223
|
function mergeParsedDocuments(docs, opts = {}) {
|
|
125
224
|
const documents = Array.isArray(docs) ? docs : [];
|
|
126
225
|
const scopeBlankNodes = typeof opts.scopeBlankNodes === 'boolean' ? opts.scopeBlankNodes : documents.length > 1;
|
|
226
|
+
const keepSources = !!opts.keepSources || !!opts.keepSourceArtifacts;
|
|
127
227
|
|
|
128
228
|
const merged = emptyParsedDocument();
|
|
129
|
-
const mergedSources = [];
|
|
229
|
+
const mergedSources = keepSources ? [] : null;
|
|
130
230
|
|
|
131
231
|
for (let i = 0; i < documents.length; i++) {
|
|
132
232
|
const originalDoc = documents[i] || emptyParsedDocument();
|
|
@@ -137,15 +237,30 @@ function mergeParsedDocuments(docs, opts = {}) {
|
|
|
137
237
|
merged.frules.push(...(doc.frules || []));
|
|
138
238
|
merged.brules.push(...(doc.brules || []));
|
|
139
239
|
merged.logQueryRules.push(...(doc.logQueryRules || []));
|
|
140
|
-
|
|
240
|
+
|
|
241
|
+
if (doc.usedPrefixes instanceof Set) {
|
|
242
|
+
if (!(merged.usedPrefixes instanceof Set)) {
|
|
243
|
+
Object.defineProperty(merged, 'usedPrefixes', {
|
|
244
|
+
value: new Set(),
|
|
245
|
+
enumerable: false,
|
|
246
|
+
writable: false,
|
|
247
|
+
configurable: true,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
for (const pfx of doc.usedPrefixes) merged.usedPrefixes.add(pfx);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (keepSources) mergedSources.push(doc);
|
|
141
254
|
}
|
|
142
255
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
256
|
+
if (keepSources) {
|
|
257
|
+
Object.defineProperty(merged, 'sources', {
|
|
258
|
+
value: mergedSources,
|
|
259
|
+
enumerable: false,
|
|
260
|
+
writable: false,
|
|
261
|
+
configurable: true,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
149
264
|
|
|
150
265
|
return merged;
|
|
151
266
|
}
|
|
@@ -177,14 +292,17 @@ function parseN3SourceList(input, opts = {}) {
|
|
|
177
292
|
if (!isN3SourceListInput(input)) return null;
|
|
178
293
|
const sources = input.sources.map(normalizeN3SourceItem);
|
|
179
294
|
const defaultBaseIri = typeof opts.baseIri === 'string' ? opts.baseIri : '';
|
|
180
|
-
const parsed = sources.map((source
|
|
295
|
+
const parsed = sources.map((source) =>
|
|
181
296
|
parseN3Text(source.text, {
|
|
182
297
|
label: source.label,
|
|
183
298
|
baseIri: source.baseIri || (sources.length === 1 ? defaultBaseIri : ''),
|
|
299
|
+
collectUsedPrefixes: true,
|
|
300
|
+
keepSourceArtifacts: !!opts.keepSourceArtifacts,
|
|
184
301
|
}),
|
|
185
302
|
);
|
|
186
303
|
return mergeParsedDocuments(parsed, {
|
|
187
304
|
scopeBlankNodes: typeof input.scopeBlankNodes === 'boolean' ? input.scopeBlankNodes : parsed.length > 1,
|
|
305
|
+
keepSources: !!opts.keepSourceArtifacts,
|
|
188
306
|
});
|
|
189
307
|
}
|
|
190
308
|
|
|
@@ -193,6 +311,7 @@ module.exports = {
|
|
|
193
311
|
parseN3Text,
|
|
194
312
|
mergeParsedDocuments,
|
|
195
313
|
scopeBlankNodesInDocument,
|
|
314
|
+
prefixesUsedInTokens,
|
|
196
315
|
isN3SourceListInput,
|
|
197
316
|
parseN3SourceList,
|
|
198
317
|
};
|
package/package.json
CHANGED
package/test/examples.test.js
CHANGED
|
@@ -96,6 +96,14 @@ function resolveExpectedPath(outputDir, inputFile) {
|
|
|
96
96
|
return path.join(outputDir, candidates[0]);
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
function resolveExampleBuiltinPath(root, inputFile) {
|
|
100
|
+
const stem = path.basename(inputFile, path.extname(inputFile));
|
|
101
|
+
const rel = path.join('examples', 'builtin', `${stem}.js`);
|
|
102
|
+
const abs = path.join(root, rel);
|
|
103
|
+
if (!fs.existsSync(abs)) return null;
|
|
104
|
+
return { abs, rel };
|
|
105
|
+
}
|
|
106
|
+
|
|
99
107
|
function main() {
|
|
100
108
|
const suiteStart = Date.now();
|
|
101
109
|
|
|
@@ -170,17 +178,32 @@ function main() {
|
|
|
170
178
|
const tmpDir = mkTmpDir();
|
|
171
179
|
const generatedPath = path.join(tmpDir, 'generated.n3');
|
|
172
180
|
|
|
173
|
-
// Run eyeling on this file
|
|
181
|
+
// Run eyeling on this file. If examples/builtin/<stem>.js exists,
|
|
182
|
+
// load it for the matching examples/<stem>.n3 file. Builtin-backed examples
|
|
183
|
+
// run from the repository root so the command shape matches documented usage:
|
|
184
|
+
// node eyeling.js --builtin examples/builtin/foo.js examples/foo.n3
|
|
185
|
+
const builtin = resolveExampleBuiltinPath(root, file);
|
|
174
186
|
const outFd = fs.openSync(generatedPath, 'w');
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
187
|
+
let r;
|
|
188
|
+
try {
|
|
189
|
+
if (builtin) {
|
|
190
|
+
r = cp.spawnSync(nodePath, [eyelingJsPath, '-d', '--builtin', builtin.rel, path.join('examples', file)], {
|
|
191
|
+
cwd: root,
|
|
192
|
+
stdio: ['ignore', outFd, 'pipe'], // stdout -> file, stderr captured
|
|
193
|
+
maxBuffer: 200 * 1024 * 1024,
|
|
194
|
+
encoding: 'utf8',
|
|
195
|
+
});
|
|
196
|
+
} else {
|
|
197
|
+
r = cp.spawnSync(nodePath, [eyelingJsPath, '-d', file], {
|
|
198
|
+
cwd: examplesDir,
|
|
199
|
+
stdio: ['ignore', outFd, 'pipe'], // stdout -> file, stderr captured
|
|
200
|
+
maxBuffer: 200 * 1024 * 1024,
|
|
201
|
+
encoding: 'utf8',
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
} finally {
|
|
205
|
+
fs.closeSync(outFd);
|
|
206
|
+
}
|
|
184
207
|
|
|
185
208
|
const rc = r.status == null ? 1 : r.status;
|
|
186
209
|
|