eyeling 1.24.2 → 1.24.4
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 +43 -7
- package/dist/browser/eyeling.browser.js +401 -72
- package/eyeling.js +401 -72
- package/index.js +2 -0
- package/lib/cli.js +27 -10
- package/lib/engine.js +17 -7
- package/lib/lexer.js +242 -52
- package/lib/multisource.js +9 -2
- package/lib/printing.js +106 -1
- package/package.json +2 -3
- package/see/README.md +2 -1
- package/see/examples/doc/rdf_dataset.md +26 -0
- package/see/examples/input/path_discovery.trig +1 -1
- package/see/examples/input/rdf_dataset.trig +34 -0
- package/see/examples/n3/rdf_dataset.n3 +34 -0
- package/see/examples/output/rdf_dataset.md +54 -0
- package/see/examples/rdf_dataset.js +1512 -0
- package/see/see.js +27 -3
- package/test/api.test.js +68 -2
- package/test/see.test.js +0 -0
- package/tools/bundle.js +0 -0
- package/test/n3gen.test.js +0 -166
- package/tools/n3gen.js +0 -2166
package/see/see.js
CHANGED
|
@@ -284,11 +284,28 @@ class Parser {
|
|
|
284
284
|
eof() {
|
|
285
285
|
return this.pos >= this.tokens.length;
|
|
286
286
|
}
|
|
287
|
-
peek(value = undefined) {
|
|
288
|
-
const tok = this.tokens[this.pos];
|
|
287
|
+
peek(value = undefined, offset = 0) {
|
|
288
|
+
const tok = this.tokens[this.pos + offset];
|
|
289
289
|
if (value === undefined) return tok;
|
|
290
290
|
return tok && tok.type === value;
|
|
291
291
|
}
|
|
292
|
+
isTermToken(tok) {
|
|
293
|
+
return tok && ['qname', 'iri', 'var', 'string', 'number', 'boolean', '<<(', '(', '{', '['].includes(tok.type);
|
|
294
|
+
}
|
|
295
|
+
isNamedGraphStart() {
|
|
296
|
+
const tok = this.peek();
|
|
297
|
+
if (tok?.type === 'qname' && /^GRAPH$/i.test(tok.value)) {
|
|
298
|
+
return this.isTermToken(this.peek(undefined, 1)) && this.peek('{', 2);
|
|
299
|
+
}
|
|
300
|
+
return (tok?.type === 'qname' || tok?.type === 'iri') && this.peek('{', 1);
|
|
301
|
+
}
|
|
302
|
+
parseNamedGraphFact() {
|
|
303
|
+
if (this.peek()?.type === 'qname' && /^GRAPH$/i.test(this.peek().value)) this.next();
|
|
304
|
+
const name = this.parseTerm('fact', []);
|
|
305
|
+
const atoms = this.parseFormula('fact');
|
|
306
|
+
this.accept('.');
|
|
307
|
+
return { s: name, p: I('log:nameOf'), o: { kind: 'formula', atoms } };
|
|
308
|
+
}
|
|
292
309
|
next() {
|
|
293
310
|
if (this.eof()) throw new Error('Unexpected end of input');
|
|
294
311
|
return this.tokens[this.pos++];
|
|
@@ -338,6 +355,10 @@ class Parser {
|
|
|
338
355
|
this.skipVersion();
|
|
339
356
|
continue;
|
|
340
357
|
}
|
|
358
|
+
if (this.isNamedGraphStart()) {
|
|
359
|
+
facts.push(this.parseNamedGraphFact());
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
341
362
|
if (this.peek('{')) {
|
|
342
363
|
const lhs = this.parseFormula('body');
|
|
343
364
|
if (this.accept('=>')) {
|
|
@@ -366,6 +387,9 @@ class Parser {
|
|
|
366
387
|
this.accept('.');
|
|
367
388
|
rules.push({ kind: 'backward', id: rules.length + 1, body: rhs, head: lhs });
|
|
368
389
|
}
|
|
390
|
+
} else if (this.peek('.') || this.eof()) {
|
|
391
|
+
this.accept('.');
|
|
392
|
+
facts.push(...lhs);
|
|
369
393
|
} else {
|
|
370
394
|
const subject = { kind: 'formula', atoms: lhs };
|
|
371
395
|
const triples = this.parseStatementRest('fact', subject);
|
|
@@ -530,7 +554,7 @@ function compilationStats(program) {
|
|
|
530
554
|
|
|
531
555
|
// Source facts are emitted as RDF 1.2 TriG. Formulas that appear as objects are
|
|
532
556
|
// lifted into named graphs so the generated runner can load evidence directly
|
|
533
|
-
// from .trig without going through an intermediate
|
|
557
|
+
// from .trig without going through an intermediate conversion step.
|
|
534
558
|
function trigString(value) {
|
|
535
559
|
return JSON.stringify(String(value));
|
|
536
560
|
}
|
package/test/api.test.js
CHANGED
|
@@ -2541,12 +2541,24 @@ _:b a ex:Person ; ex:name "B" .
|
|
|
2541
2541
|
},
|
|
2542
2542
|
|
|
2543
2543
|
{
|
|
2544
|
-
name: 'RDF 1.2 triple terms
|
|
2544
|
+
name: 'RDF 1.2 triple terms require explicit RDF compatibility mode',
|
|
2545
2545
|
opt: { proofComments: false },
|
|
2546
2546
|
input: `VERSION "1.2"
|
|
2547
2547
|
@prefix : <http://example.org/triple-terms#> .
|
|
2548
2548
|
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
|
2549
2549
|
|
|
2550
|
+
:observation rdf:reifies <<( :sensor :reports :overheating )>> .
|
|
2551
|
+
`,
|
|
2552
|
+
expectError: true,
|
|
2553
|
+
},
|
|
2554
|
+
|
|
2555
|
+
{
|
|
2556
|
+
name: 'RDF 1.2 triple terms are accepted in RDF compatibility mode',
|
|
2557
|
+
opt: { proofComments: false, rdf: true },
|
|
2558
|
+
input: `VERSION "1.2"
|
|
2559
|
+
@prefix : <http://example.org/triple-terms#> .
|
|
2560
|
+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
|
2561
|
+
|
|
2550
2562
|
:observation rdf:reifies <<( :sensor :reports :overheating )>> .
|
|
2551
2563
|
:overheating :requires :inspection .
|
|
2552
2564
|
|
|
@@ -2557,10 +2569,64 @@ _:b a ex:Person ; ex:name "B" .
|
|
|
2557
2569
|
?observation :entails <<( ?device :needs ?action )>> .
|
|
2558
2570
|
} .
|
|
2559
2571
|
`,
|
|
2560
|
-
expect: [/:observation\s+:entails\s
|
|
2572
|
+
expect: [/:observation\s+:entails\s+<<\(\s+:sensor\s+:needs\s+:inspection\s*\)>>\s*\./m],
|
|
2573
|
+
},
|
|
2574
|
+
|
|
2575
|
+
{
|
|
2576
|
+
name: 'RDF mode serializes only valid triple terms as RDF 1.2 triple terms',
|
|
2577
|
+
opt: { proofComments: false, rdf: true },
|
|
2578
|
+
input: `@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
2579
|
+
@prefix math: <http://www.w3.org/2000/10/swap/math#> .
|
|
2580
|
+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
|
2581
|
+
@prefix : <http://example.org/ns#> .
|
|
2582
|
+
|
|
2583
|
+
{
|
|
2584
|
+
(1 3) math:sum ?X .
|
|
2585
|
+
}
|
|
2586
|
+
=>
|
|
2587
|
+
{
|
|
2588
|
+
<<(?X a :Answer)>> a :Result.
|
|
2589
|
+
}.
|
|
2590
|
+
`,
|
|
2591
|
+
expect: [/\{\s*4\s+a\s+:Answer\s*\.\s*\}\s+a\s+:Result\s*\./m],
|
|
2592
|
+
notExpect: [/<<\(\s*4\s+a\s+:Answer\s*\)>>/m],
|
|
2593
|
+
},
|
|
2594
|
+
|
|
2595
|
+
{
|
|
2596
|
+
name: 'RDF dataset named graphs round-trip as TriG in RDF compatibility mode',
|
|
2597
|
+
opt: { proofComments: false, rdf: true },
|
|
2598
|
+
input: `VERSION "1.2"
|
|
2599
|
+
@prefix : <http://example.org/dataset#> .
|
|
2600
|
+
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
2601
|
+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
|
2602
|
+
|
|
2603
|
+
# Ordinary TriG default-graph triples remain ordinary N3 facts.
|
|
2604
|
+
:sensor :reports :overheating .
|
|
2605
|
+
:overheating :requires :inspection .
|
|
2606
|
+
|
|
2607
|
+
# A TriG named graph block is accepted and normalized to a graph term.
|
|
2608
|
+
:factoryDataset {
|
|
2609
|
+
:observation rdf:reifies <<( :sensor :reports :overheating )>> .
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
{
|
|
2613
|
+
:sensor :reports ?condition .
|
|
2614
|
+
?condition :requires ?action .
|
|
2615
|
+
} => {
|
|
2616
|
+
:workOrder :entails <<( :sensor :needs ?action )>> .
|
|
2617
|
+
:audit log:nameOf {
|
|
2618
|
+
:workOrder :basedOn :factoryDataset .
|
|
2619
|
+
} .
|
|
2620
|
+
} .
|
|
2621
|
+
`,
|
|
2622
|
+
expect: [
|
|
2623
|
+
/:workOrder\s+:entails\s+<<\(\s+:sensor\s+:needs\s+:inspection\s*\)>>\s*\./m,
|
|
2624
|
+
/:audit\s+\{[\s\S]*:workOrder\s+:basedOn\s+:factoryDataset\s*\.[\s\S]*\}/m,
|
|
2625
|
+
],
|
|
2561
2626
|
},
|
|
2562
2627
|
];
|
|
2563
2628
|
|
|
2629
|
+
|
|
2564
2630
|
let passed = 0;
|
|
2565
2631
|
let failed = 0;
|
|
2566
2632
|
|
package/test/see.test.js
CHANGED
|
File without changes
|
package/tools/bundle.js
CHANGED
|
File without changes
|
package/test/n3gen.test.js
DELETED
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
// Convert examples/input/*.{ttl,trig} -> examples/*.n3 using n3gen.js
|
|
5
|
-
//
|
|
6
|
-
// For reproducibility and to avoid mutating tracked files during tests, generated output
|
|
7
|
-
// is always written to a temporary file and compared against examples/<name>.n3.
|
|
8
|
-
|
|
9
|
-
const fs = require('node:fs');
|
|
10
|
-
const os = require('node:os');
|
|
11
|
-
const path = require('node:path');
|
|
12
|
-
const cp = require('node:child_process');
|
|
13
|
-
|
|
14
|
-
const TTY = process.stdout.isTTY;
|
|
15
|
-
const C = TTY
|
|
16
|
-
? { g: '\x1b[32m', r: '\x1b[31m', y: '\x1b[33m', dim: '\x1b[2m', n: '\x1b[0m' }
|
|
17
|
-
: { g: '', r: '', y: '', dim: '', n: '' };
|
|
18
|
-
|
|
19
|
-
function ok(msg) {
|
|
20
|
-
console.log(`${C.g}OK ${C.n} ${msg}`);
|
|
21
|
-
}
|
|
22
|
-
function fail(msg) {
|
|
23
|
-
console.error(`${C.r}FAIL${C.n} ${msg}`);
|
|
24
|
-
}
|
|
25
|
-
function info(msg) {
|
|
26
|
-
console.log(`${C.y}==${C.n} ${msg}`);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function run(cmd, args, opts = {}) {
|
|
30
|
-
return cp.spawnSync(cmd, args, {
|
|
31
|
-
encoding: 'utf8',
|
|
32
|
-
maxBuffer: 200 * 1024 * 1024,
|
|
33
|
-
...opts,
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function mkTmpDir() {
|
|
38
|
-
return fs.mkdtempSync(path.join(os.tmpdir(), 'eyeling-n3-'));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function rmrf(p) {
|
|
42
|
-
try {
|
|
43
|
-
fs.rmSync(p, { recursive: true, force: true });
|
|
44
|
-
} catch {}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function showDiff({ examplesDir, expectedPath, generatedPath }) {
|
|
48
|
-
const d = run('diff', ['-u', expectedPath, generatedPath], { cwd: examplesDir });
|
|
49
|
-
if (d.stdout) process.stdout.write(d.stdout);
|
|
50
|
-
if (d.stderr) process.stderr.write(d.stderr);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function main() {
|
|
54
|
-
const suiteStart = Date.now();
|
|
55
|
-
|
|
56
|
-
// test/n3gen.test.js -> repo root is one level up
|
|
57
|
-
const root = path.resolve(__dirname, '..');
|
|
58
|
-
const examplesDir = path.join(root, 'examples');
|
|
59
|
-
const inputDir = path.join(examplesDir, 'input');
|
|
60
|
-
const n3GenJsPath = path.join(root, 'tools/n3gen.js');
|
|
61
|
-
const nodePath = process.execPath;
|
|
62
|
-
|
|
63
|
-
if (!fs.existsSync(examplesDir)) {
|
|
64
|
-
fail(`Cannot find examples directory: ${examplesDir}`);
|
|
65
|
-
process.exit(1);
|
|
66
|
-
}
|
|
67
|
-
if (!fs.existsSync(inputDir)) {
|
|
68
|
-
fail(`Cannot find examples/input directory: ${inputDir}`);
|
|
69
|
-
process.exit(1);
|
|
70
|
-
}
|
|
71
|
-
if (!fs.existsSync(n3GenJsPath)) {
|
|
72
|
-
fail(`Cannot find n3gen.js: ${n3GenJsPath}`);
|
|
73
|
-
process.exit(1);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const inputs = fs
|
|
77
|
-
.readdirSync(inputDir)
|
|
78
|
-
.filter((f) => /\.(ttl|trig)$/i.test(f))
|
|
79
|
-
.sort((a, b) => a.localeCompare(b));
|
|
80
|
-
|
|
81
|
-
info(`Running n3 conversions for ${inputs.length} inputs`);
|
|
82
|
-
console.log(`${C.dim}node ${process.version}${C.n}`);
|
|
83
|
-
|
|
84
|
-
if (inputs.length === 0) {
|
|
85
|
-
ok('No .ttl/.trig files found in examples/input/');
|
|
86
|
-
process.exit(0);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
let passed = 0;
|
|
90
|
-
let failed = 0;
|
|
91
|
-
|
|
92
|
-
for (let i = 0; i < inputs.length; i++) {
|
|
93
|
-
const idx = String(i + 1).padStart(2, '0');
|
|
94
|
-
const inFile = inputs[i];
|
|
95
|
-
const start = Date.now();
|
|
96
|
-
|
|
97
|
-
const inPath = path.join(inputDir, inFile);
|
|
98
|
-
const base = inFile.replace(/\.(ttl|trig)$/i, '');
|
|
99
|
-
const outFile = `${base}.n3`;
|
|
100
|
-
|
|
101
|
-
const expectedPath = path.join(examplesDir, outFile);
|
|
102
|
-
|
|
103
|
-
if (!fs.existsSync(expectedPath)) {
|
|
104
|
-
const ms = Date.now() - start;
|
|
105
|
-
fail(`${idx} ${inFile} -> ${outFile} (${ms} ms)`);
|
|
106
|
-
fail(`Missing expected examples/${outFile}`);
|
|
107
|
-
failed++;
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const tmpDir = mkTmpDir();
|
|
112
|
-
const generatedPath = path.join(tmpDir, outFile);
|
|
113
|
-
|
|
114
|
-
// Run converter (stdout -> file; stderr captured)
|
|
115
|
-
const outFd = fs.openSync(generatedPath, 'w');
|
|
116
|
-
const r = cp.spawnSync(nodePath, [n3GenJsPath, inPath], {
|
|
117
|
-
cwd: root,
|
|
118
|
-
stdio: ['ignore', outFd, 'pipe'],
|
|
119
|
-
encoding: 'utf8',
|
|
120
|
-
maxBuffer: 200 * 1024 * 1024,
|
|
121
|
-
});
|
|
122
|
-
fs.closeSync(outFd);
|
|
123
|
-
|
|
124
|
-
const rc = r.status == null ? 1 : r.status;
|
|
125
|
-
const ms = Date.now() - start;
|
|
126
|
-
|
|
127
|
-
if (rc !== 0) {
|
|
128
|
-
fail(`${idx} ${inFile} -> ${outFile} (${ms} ms)`);
|
|
129
|
-
fail(`Converter exit code ${rc}`);
|
|
130
|
-
if (r.stderr) process.stderr.write(String(r.stderr));
|
|
131
|
-
failed++;
|
|
132
|
-
rmrf(tmpDir);
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Compare output (always compare expected vs generated temp file)
|
|
137
|
-
const d = run('diff', ['-u', expectedPath, generatedPath], { cwd: examplesDir });
|
|
138
|
-
const diffOk = d.status === 0;
|
|
139
|
-
|
|
140
|
-
if (diffOk) {
|
|
141
|
-
ok(`${idx} ${inFile} -> ${outFile} (${ms} ms)`);
|
|
142
|
-
passed++;
|
|
143
|
-
} else {
|
|
144
|
-
fail(`${idx} ${inFile} -> ${outFile} (${ms} ms)`);
|
|
145
|
-
fail('Output differs');
|
|
146
|
-
showDiff({ examplesDir, expectedPath, generatedPath });
|
|
147
|
-
failed++;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
rmrf(tmpDir);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
console.log('');
|
|
154
|
-
const suiteMs = Date.now() - suiteStart;
|
|
155
|
-
info(`Total elapsed: ${suiteMs} ms (${(suiteMs / 1000).toFixed(2)} s)`);
|
|
156
|
-
|
|
157
|
-
if (failed === 0) {
|
|
158
|
-
ok(`All n3 conversions passed (${passed}/${inputs.length})`);
|
|
159
|
-
process.exit(0);
|
|
160
|
-
} else {
|
|
161
|
-
fail(`Some n3 conversions failed (${passed}/${inputs.length})`);
|
|
162
|
-
process.exit(2);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
main();
|