eyeling 1.18.2 → 1.19.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 +9 -2
- package/README.md +3 -2
- package/eyeling.js +25 -12
- package/lib/builtins.js +5 -3
- package/lib/cli.js +20 -9
- package/package.json +1 -1
- package/test/api.test.js +85 -0
package/HANDBOOK.md
CHANGED
|
@@ -1777,12 +1777,16 @@ Install from npm:
|
|
|
1777
1777
|
npm i eyeling
|
|
1778
1778
|
```
|
|
1779
1779
|
|
|
1780
|
-
Run a
|
|
1780
|
+
Run a self-contained example from stdin:
|
|
1781
1781
|
|
|
1782
1782
|
```bash
|
|
1783
|
-
|
|
1783
|
+
echo '@prefix : <http://example.org/> .
|
|
1784
|
+
:Socrates a :Man .
|
|
1785
|
+
{ ?x a :Man } => { ?x a :Mortal } .' | npx eyeling
|
|
1784
1786
|
```
|
|
1785
1787
|
|
|
1788
|
+
You can also pass a file path, or `-` to read explicitly from stdin.
|
|
1789
|
+
|
|
1786
1790
|
Show the available options:
|
|
1787
1791
|
|
|
1788
1792
|
```bash
|
|
@@ -1816,6 +1820,7 @@ The bundle contains the whole engine. The CLI path is the “canonical behavior
|
|
|
1816
1820
|
The current CLI supports a small set of flags (see `lib/cli.js`):
|
|
1817
1821
|
|
|
1818
1822
|
- `-a`, `--ast` — print the parsed AST as JSON and exit.
|
|
1823
|
+
- `--builtin <module.js>` — load a custom builtin module (repeatable).
|
|
1819
1824
|
- `-d`, `--deterministic-skolem` — make `log:skolem` stable across runs.
|
|
1820
1825
|
- `-e`, `--enforce-https` — rewrite `http://…` to `https://…` for dereferencing builtins.
|
|
1821
1826
|
- `-p`, `--proof-comments` — include per-fact proof comment blocks in output.
|
|
@@ -1823,6 +1828,8 @@ The current CLI supports a small set of flags (see `lib/cli.js`):
|
|
|
1823
1828
|
- `-t`, `--stream` — stream derived triples as soon as they are derived.
|
|
1824
1829
|
- `-v`, `--version` — print version and exit.
|
|
1825
1830
|
- `-h`, `--help` — show usage.
|
|
1831
|
+
- With no positional argument, Eyeling reads from stdin when input is piped.
|
|
1832
|
+
- Use `-` as the input path to read explicitly from stdin.
|
|
1826
1833
|
|
|
1827
1834
|
### 14.3 `lib/entry.js`: bundler-friendly exports
|
|
1828
1835
|
|
package/README.md
CHANGED
|
@@ -7,8 +7,9 @@ A compact [Notation3 (N3)](https://notation3.org/) reasoner in **JavaScript**.
|
|
|
7
7
|
## Quick start
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
echo '@prefix : <http://example.org/> .
|
|
11
|
+
:Socrates a :Man .
|
|
12
|
+
{ ?x a :Man } => { ?x a :Mortal } .' | npx eyeling
|
|
12
13
|
```
|
|
13
14
|
|
|
14
15
|
## Read more
|
package/eyeling.js
CHANGED
|
@@ -3670,7 +3670,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
3670
3670
|
// - subject = GraphTerm: explicit scope, run immediately (no closure gating)
|
|
3671
3671
|
// - subject = positive integer literal N (>= 1): delay until saturated closure level >= N
|
|
3672
3672
|
// - subject = Var: treat as priority 1 (do not bind)
|
|
3673
|
-
// - any other subject:
|
|
3673
|
+
// - any other subject: invalid, so the builtin fails
|
|
3674
3674
|
if (pv === LOG_NS + 'includes') {
|
|
3675
3675
|
let scopeFacts = null;
|
|
3676
3676
|
let scopeBackRules = backRules;
|
|
@@ -3698,7 +3698,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
3698
3698
|
prio = 1; // do not bind
|
|
3699
3699
|
} else {
|
|
3700
3700
|
const p0 = __logNaturalPriorityFromTerm(g.s);
|
|
3701
|
-
if (p0
|
|
3701
|
+
if (p0 === null) return [];
|
|
3702
|
+
prio = p0;
|
|
3702
3703
|
}
|
|
3703
3704
|
|
|
3704
3705
|
const snap = facts.__scopedSnapshot || null;
|
|
@@ -3781,7 +3782,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
3781
3782
|
prio = 1; // do not bind
|
|
3782
3783
|
} else {
|
|
3783
3784
|
const p0 = __logNaturalPriorityFromTerm(g.s);
|
|
3784
|
-
if (p0
|
|
3785
|
+
if (p0 === null) return [];
|
|
3786
|
+
prio = p0;
|
|
3785
3787
|
}
|
|
3786
3788
|
|
|
3787
3789
|
const snap = facts.__scopedSnapshot || null;
|
|
@@ -4536,6 +4538,11 @@ function formatN3SyntaxError(err, text, path) {
|
|
|
4536
4538
|
}
|
|
4537
4539
|
|
|
4538
4540
|
// CLI entry point (invoked when eyeling.js is run directly)
|
|
4541
|
+
function readTextFromStdinSync() {
|
|
4542
|
+
const fs = require('node:fs');
|
|
4543
|
+
return fs.readFileSync(0, { encoding: 'utf8' });
|
|
4544
|
+
}
|
|
4545
|
+
|
|
4539
4546
|
function main() {
|
|
4540
4547
|
// Drop "node" and script name; keep only user-provided args
|
|
4541
4548
|
// Expand combined short options: -pt == -p -t
|
|
@@ -4555,7 +4562,8 @@ function main() {
|
|
|
4555
4562
|
|
|
4556
4563
|
function printHelp(toStderr = false) {
|
|
4557
4564
|
const msg =
|
|
4558
|
-
`Usage: ${prog} [options]
|
|
4565
|
+
`Usage: ${prog} [options] [file.n3|-]\n\n` +
|
|
4566
|
+
`When no file is given and stdin is piped, read N3 from stdin.\n\n` +
|
|
4559
4567
|
`Options:\n` +
|
|
4560
4568
|
` -a, --ast Print parsed AST as JSON and exit.\n` +
|
|
4561
4569
|
` --builtin <module.js> Load a custom builtin module (repeatable).\n` +
|
|
@@ -4626,12 +4634,13 @@ function main() {
|
|
|
4626
4634
|
}
|
|
4627
4635
|
|
|
4628
4636
|
// Positional args (the N3 file)
|
|
4629
|
-
|
|
4637
|
+
const useImplicitStdin = positional.length === 0 && !process.stdin.isTTY;
|
|
4638
|
+
if (positional.length === 0 && !useImplicitStdin) {
|
|
4630
4639
|
printHelp(false);
|
|
4631
4640
|
process.exit(0);
|
|
4632
4641
|
}
|
|
4633
|
-
if (positional.length
|
|
4634
|
-
console.error('Error: expected
|
|
4642
|
+
if (positional.length > 1) {
|
|
4643
|
+
console.error('Error: expected at most one input [file.n3|-].');
|
|
4635
4644
|
printHelp(true);
|
|
4636
4645
|
process.exit(1);
|
|
4637
4646
|
}
|
|
@@ -4646,13 +4655,17 @@ function main() {
|
|
|
4646
4655
|
}
|
|
4647
4656
|
}
|
|
4648
4657
|
|
|
4649
|
-
const
|
|
4658
|
+
const sourceLabel = useImplicitStdin || positional[0] === '-' ? '<stdin>' : positional[0];
|
|
4650
4659
|
let text;
|
|
4651
4660
|
try {
|
|
4652
|
-
|
|
4653
|
-
|
|
4661
|
+
if (sourceLabel === '<stdin>') text = readTextFromStdinSync();
|
|
4662
|
+
else {
|
|
4663
|
+
const fs = require('node:fs');
|
|
4664
|
+
text = fs.readFileSync(sourceLabel, { encoding: 'utf8' });
|
|
4665
|
+
}
|
|
4654
4666
|
} catch (e) {
|
|
4655
|
-
console.error(`Error reading
|
|
4667
|
+
if (sourceLabel === '<stdin>') console.error(`Error reading stdin: ${e.message}`);
|
|
4668
|
+
else console.error(`Error reading file ${JSON.stringify(sourceLabel)}: ${e.message}`);
|
|
4656
4669
|
process.exit(1);
|
|
4657
4670
|
}
|
|
4658
4671
|
|
|
@@ -4666,7 +4679,7 @@ function main() {
|
|
|
4666
4679
|
engine.setTracePrefixes(prefixes);
|
|
4667
4680
|
} catch (e) {
|
|
4668
4681
|
if (e && e.name === 'N3SyntaxError') {
|
|
4669
|
-
console.error(formatN3SyntaxError(e, text,
|
|
4682
|
+
console.error(formatN3SyntaxError(e, text, sourceLabel));
|
|
4670
4683
|
process.exit(1);
|
|
4671
4684
|
}
|
|
4672
4685
|
throw e;
|
package/lib/builtins.js
CHANGED
|
@@ -3190,7 +3190,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
3190
3190
|
// - subject = GraphTerm: explicit scope, run immediately (no closure gating)
|
|
3191
3191
|
// - subject = positive integer literal N (>= 1): delay until saturated closure level >= N
|
|
3192
3192
|
// - subject = Var: treat as priority 1 (do not bind)
|
|
3193
|
-
// - any other subject:
|
|
3193
|
+
// - any other subject: invalid, so the builtin fails
|
|
3194
3194
|
if (pv === LOG_NS + 'includes') {
|
|
3195
3195
|
let scopeFacts = null;
|
|
3196
3196
|
let scopeBackRules = backRules;
|
|
@@ -3218,7 +3218,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
3218
3218
|
prio = 1; // do not bind
|
|
3219
3219
|
} else {
|
|
3220
3220
|
const p0 = __logNaturalPriorityFromTerm(g.s);
|
|
3221
|
-
if (p0
|
|
3221
|
+
if (p0 === null) return [];
|
|
3222
|
+
prio = p0;
|
|
3222
3223
|
}
|
|
3223
3224
|
|
|
3224
3225
|
const snap = facts.__scopedSnapshot || null;
|
|
@@ -3301,7 +3302,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
|
|
|
3301
3302
|
prio = 1; // do not bind
|
|
3302
3303
|
} else {
|
|
3303
3304
|
const p0 = __logNaturalPriorityFromTerm(g.s);
|
|
3304
|
-
if (p0
|
|
3305
|
+
if (p0 === null) return [];
|
|
3306
|
+
prio = p0;
|
|
3305
3307
|
}
|
|
3306
3308
|
|
|
3307
3309
|
const snap = facts.__scopedSnapshot || null;
|
package/lib/cli.js
CHANGED
|
@@ -45,6 +45,11 @@ function formatN3SyntaxError(err, text, path) {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
// CLI entry point (invoked when eyeling.js is run directly)
|
|
48
|
+
function readTextFromStdinSync() {
|
|
49
|
+
const fs = require('node:fs');
|
|
50
|
+
return fs.readFileSync(0, { encoding: 'utf8' });
|
|
51
|
+
}
|
|
52
|
+
|
|
48
53
|
function main() {
|
|
49
54
|
// Drop "node" and script name; keep only user-provided args
|
|
50
55
|
// Expand combined short options: -pt == -p -t
|
|
@@ -64,7 +69,8 @@ function main() {
|
|
|
64
69
|
|
|
65
70
|
function printHelp(toStderr = false) {
|
|
66
71
|
const msg =
|
|
67
|
-
`Usage: ${prog} [options]
|
|
72
|
+
`Usage: ${prog} [options] [file.n3|-]\n\n` +
|
|
73
|
+
`When no file is given and stdin is piped, read N3 from stdin.\n\n` +
|
|
68
74
|
`Options:\n` +
|
|
69
75
|
` -a, --ast Print parsed AST as JSON and exit.\n` +
|
|
70
76
|
` --builtin <module.js> Load a custom builtin module (repeatable).\n` +
|
|
@@ -135,12 +141,13 @@ function main() {
|
|
|
135
141
|
}
|
|
136
142
|
|
|
137
143
|
// Positional args (the N3 file)
|
|
138
|
-
|
|
144
|
+
const useImplicitStdin = positional.length === 0 && !process.stdin.isTTY;
|
|
145
|
+
if (positional.length === 0 && !useImplicitStdin) {
|
|
139
146
|
printHelp(false);
|
|
140
147
|
process.exit(0);
|
|
141
148
|
}
|
|
142
|
-
if (positional.length
|
|
143
|
-
console.error('Error: expected
|
|
149
|
+
if (positional.length > 1) {
|
|
150
|
+
console.error('Error: expected at most one input [file.n3|-].');
|
|
144
151
|
printHelp(true);
|
|
145
152
|
process.exit(1);
|
|
146
153
|
}
|
|
@@ -155,13 +162,17 @@ function main() {
|
|
|
155
162
|
}
|
|
156
163
|
}
|
|
157
164
|
|
|
158
|
-
const
|
|
165
|
+
const sourceLabel = useImplicitStdin || positional[0] === '-' ? '<stdin>' : positional[0];
|
|
159
166
|
let text;
|
|
160
167
|
try {
|
|
161
|
-
|
|
162
|
-
|
|
168
|
+
if (sourceLabel === '<stdin>') text = readTextFromStdinSync();
|
|
169
|
+
else {
|
|
170
|
+
const fs = require('node:fs');
|
|
171
|
+
text = fs.readFileSync(sourceLabel, { encoding: 'utf8' });
|
|
172
|
+
}
|
|
163
173
|
} catch (e) {
|
|
164
|
-
console.error(`Error reading
|
|
174
|
+
if (sourceLabel === '<stdin>') console.error(`Error reading stdin: ${e.message}`);
|
|
175
|
+
else console.error(`Error reading file ${JSON.stringify(sourceLabel)}: ${e.message}`);
|
|
165
176
|
process.exit(1);
|
|
166
177
|
}
|
|
167
178
|
|
|
@@ -175,7 +186,7 @@ function main() {
|
|
|
175
186
|
engine.setTracePrefixes(prefixes);
|
|
176
187
|
} catch (e) {
|
|
177
188
|
if (e && e.name === 'N3SyntaxError') {
|
|
178
|
-
console.error(formatN3SyntaxError(e, text,
|
|
189
|
+
console.error(formatN3SyntaxError(e, text, sourceLabel));
|
|
179
190
|
process.exit(1);
|
|
180
191
|
}
|
|
181
192
|
throw e;
|
package/package.json
CHANGED
package/test/api.test.js
CHANGED
|
@@ -1462,6 +1462,42 @@ _:x :hates { _:foo :making :mess }.
|
|
|
1462
1462
|
notExpect: [/:(?:test)\s+:(?:is)\s+false\s*\./],
|
|
1463
1463
|
},
|
|
1464
1464
|
|
|
1465
|
+
{
|
|
1466
|
+
name: '59b regression: log:includes rejects non-scope literal or term subjects',
|
|
1467
|
+
opt: { proofComments: false },
|
|
1468
|
+
input: `@prefix : <http://example.org/> .
|
|
1469
|
+
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
1470
|
+
@base <http://example.org/> .
|
|
1471
|
+
|
|
1472
|
+
{ false log:includes true. } => { :result :has :fail-literal-1. }.
|
|
1473
|
+
{ "foo" log:includes true. } => { :result :has :fail-literal-2. }.
|
|
1474
|
+
{ :foo log:includes true. } => { :result :has :fail-literal-3. }.
|
|
1475
|
+
{ 0 log:includes true. } => { :result :has :fail-literal-4. }.
|
|
1476
|
+
{ 42.3 log:includes true. } => { :result :has :fail-literal-5. }.
|
|
1477
|
+
{ (:foo 1 _:x) log:includes true. } => { :result :has :fail-literal-6. }.
|
|
1478
|
+
|
|
1479
|
+
{ } => {
|
|
1480
|
+
:test :contains :fail-literal-1, :fail-literal-2, :fail-literal-3, :fail-literal-4, :fail-literal-5, :fail-literal-6.
|
|
1481
|
+
}.
|
|
1482
|
+
`,
|
|
1483
|
+
expect: [
|
|
1484
|
+
/:(?:test)\s+:(?:contains)\s+:(?:fail-literal-1)\s*\./,
|
|
1485
|
+
/:(?:test)\s+:(?:contains)\s+:(?:fail-literal-2)\s*\./,
|
|
1486
|
+
/:(?:test)\s+:(?:contains)\s+:(?:fail-literal-3)\s*\./,
|
|
1487
|
+
/:(?:test)\s+:(?:contains)\s+:(?:fail-literal-4)\s*\./,
|
|
1488
|
+
/:(?:test)\s+:(?:contains)\s+:(?:fail-literal-5)\s*\./,
|
|
1489
|
+
/:(?:test)\s+:(?:contains)\s+:(?:fail-literal-6)\s*\./,
|
|
1490
|
+
],
|
|
1491
|
+
notExpect: [
|
|
1492
|
+
/:(?:result)\s+:(?:has)\s+:(?:fail-literal-1)\s*\./,
|
|
1493
|
+
/:(?:result)\s+:(?:has)\s+:(?:fail-literal-2)\s*\./,
|
|
1494
|
+
/:(?:result)\s+:(?:has)\s+:(?:fail-literal-3)\s*\./,
|
|
1495
|
+
/:(?:result)\s+:(?:has)\s+:(?:fail-literal-4)\s*\./,
|
|
1496
|
+
/:(?:result)\s+:(?:has)\s+:(?:fail-literal-5)\s*\./,
|
|
1497
|
+
/:(?:result)\s+:(?:has)\s+:(?:fail-literal-6)\s*\./,
|
|
1498
|
+
],
|
|
1499
|
+
},
|
|
1500
|
+
|
|
1465
1501
|
{
|
|
1466
1502
|
name: '60 regression: log:includes must match quoted triples with variable predicates',
|
|
1467
1503
|
opt: { proofComments: false },
|
|
@@ -1762,6 +1798,55 @@ _:x :hates { _:foo :making :mess }.
|
|
|
1762
1798
|
},
|
|
1763
1799
|
expect: [/http:\/\/example\.org\/ancestor/m],
|
|
1764
1800
|
},
|
|
1801
|
+
{
|
|
1802
|
+
name: '67 CLI stdin: accepts piped N3 when no file argument is given',
|
|
1803
|
+
run() {
|
|
1804
|
+
const input = `@prefix : <http://example.org/> .
|
|
1805
|
+
:Socrates a :Man .
|
|
1806
|
+
{ ?x a :Man } => { ?x a :Mortal } .
|
|
1807
|
+
`;
|
|
1808
|
+
const r = spawnSync(process.execPath, [path.join(ROOT, 'eyeling.js')], {
|
|
1809
|
+
input,
|
|
1810
|
+
encoding: 'utf8',
|
|
1811
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1812
|
+
});
|
|
1813
|
+
if (r.error) throw r.error;
|
|
1814
|
+
if (r.status !== 0) {
|
|
1815
|
+
const err = new Error(`CLI failed with exit ${r.status}`);
|
|
1816
|
+
err.code = r.status;
|
|
1817
|
+
err.stdout = r.stdout;
|
|
1818
|
+
err.stderr = r.stderr;
|
|
1819
|
+
throw err;
|
|
1820
|
+
}
|
|
1821
|
+
return r.stdout;
|
|
1822
|
+
},
|
|
1823
|
+
expect: [/:(?:Socrates)\s+a\s+:(?:Mortal)\s*\./],
|
|
1824
|
+
},
|
|
1825
|
+
{
|
|
1826
|
+
name: '68 CLI stdin: accepts explicit - for stdin',
|
|
1827
|
+
run() {
|
|
1828
|
+
const input = `@prefix : <http://example.org/> .
|
|
1829
|
+
:Socrates a :Man .
|
|
1830
|
+
{ ?x a :Man } => { ?x a :Mortal } .
|
|
1831
|
+
`;
|
|
1832
|
+
const r = spawnSync(process.execPath, [path.join(ROOT, 'eyeling.js'), '-'], {
|
|
1833
|
+
input,
|
|
1834
|
+
encoding: 'utf8',
|
|
1835
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1836
|
+
});
|
|
1837
|
+
if (r.error) throw r.error;
|
|
1838
|
+
if (r.status !== 0) {
|
|
1839
|
+
const err = new Error(`CLI failed with exit ${r.status}`);
|
|
1840
|
+
err.code = r.status;
|
|
1841
|
+
err.stdout = r.stdout;
|
|
1842
|
+
err.stderr = r.stderr;
|
|
1843
|
+
throw err;
|
|
1844
|
+
}
|
|
1845
|
+
return r.stdout;
|
|
1846
|
+
},
|
|
1847
|
+
expect: [/:(?:Socrates)\s+a\s+:(?:Mortal)\s*\./],
|
|
1848
|
+
},
|
|
1849
|
+
|
|
1765
1850
|
{
|
|
1766
1851
|
name: '240 custom builtin module can be loaded via --builtin',
|
|
1767
1852
|
run() {
|