eyeleng 1.1.2 → 1.2.0
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/README.md +47 -5
- package/dist/browser/eyeleng.browser.js +45 -3
- package/examples/README.md +2 -0
- package/eyeleng.js +99 -20
- package/package.json +1 -1
- package/playground.html +4 -4
- package/reports/w3c-shacl12-rules-earl.ttl +1540 -214
- package/src/api.js +2 -1
- package/src/cli.js +51 -16
- package/src/engine.js +12 -0
- package/src/format.js +31 -2
- package/test/cli.test.js +33 -1
- package/tools/browser-bundle.js +1 -0
- package/tools/bundle.js +3 -1
package/README.md
CHANGED
|
@@ -3,15 +3,49 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/eyeleng)
|
|
4
4
|
[](https://doi.org/10.5281/zenodo.20342577)
|
|
5
5
|
|
|
6
|
-
`eyeleng` stands for **
|
|
6
|
+
The `leng` in `eyeleng` stands for **Logic Engine Next Generation**. Eyeleng's main purpose is **automatic hybrid reasoning**: it combines forward materialization with tabled backward proving and automatically chooses how rules should be evaluated.
|
|
7
|
+
|
|
8
|
+
Eyeleng is a compact JavaScript implementation of SHACL 1.2 Rules with two rule front-ends:
|
|
7
9
|
|
|
8
10
|
- **SRL** — the Shape Rules Language syntax used by the SHACL 1.2 Rules draft.
|
|
9
11
|
- **RDF Rules** — a Turtle/RDF syntax for rule sets.
|
|
10
12
|
|
|
11
|
-
Eyeleng is a compact reasoner over RDF-style triples. It
|
|
13
|
+
Eyeleng is a compact automatic hybrid reasoner over RDF-style triples. It uses forward chaining for ordinary finite materialization while its default execution mode applies conservative hybrid planning: selected function-like predicates can be proved just in time by a tabled backward prover when they are demanded by a query or another rule body. It is deliberately small, dependency-free at runtime, readable as ordinary JavaScript, and usable from the CLI, Node.js, and the browser playground.
|
|
12
14
|
|
|
13
15
|
Eyeleng implements the rules/reasoning surface. It is **not** a SHACL validation engine and does not emit SHACL validation reports.
|
|
14
16
|
|
|
17
|
+
## Why Eyeleng?
|
|
18
|
+
|
|
19
|
+
Eyeleng is a next-generation path for rule-based RDF reasoning: it combines a language being developed on the W3C standards track with an execution model that automatically uses both forward and backward reasoning.
|
|
20
|
+
|
|
21
|
+
The central choice is **SHACL 1.2 Rules instead of N3 as the native rule language**. SHACL 1.2 Rules is being developed by the W3C Data Shapes Working Group as a W3C Working Draft on the Recommendation track. It defines both an RDF representation of rule sets and the concise Shape Rules Language (SRL), together with `infer` and `query` operations. By contrast, the current N3 specification is a W3C Community Group Report. Community Group Reports are useful specifications, but they are not on the W3C standards track and are not W3C-endorsed standards.
|
|
22
|
+
|
|
23
|
+
That standards position does not require giving up the kinds of programs traditionally written in N3. The N3 examples translated to SRL or RDF Rules and tested with Eyeleng so far have retained their intended reasoning behavior. This is practical evidence that existing N3 rule programs can migrate to the SHACL Rules model, although Eyeleng does not parse N3 syntax directly and this is not yet a claim that every possible N3 extension has a translation.
|
|
24
|
+
|
|
25
|
+
Eyeleng also removes a choice that N3 engines commonly expose to the rule author. In EYE, Eyeling, and Eyeron, `=>` and `<=` explicitly select forward and backward rules. In Eyeleng, rules describe the logical relationship while the engine analyzes dependencies and demand. It materializes ordinary consequences forward and can prove safe, function-like predicates backward with tabling only when they are needed. This avoids exposing internal helper triples merely because they were required during a computation, while preserving forward closure where materialization is appropriate.
|
|
26
|
+
|
|
27
|
+
The related engines therefore mark stages and implementation choices rather than hard limits on what Eyeleng may replace:
|
|
28
|
+
|
|
29
|
+
| Project | Native language and runtime | Main reason to choose it |
|
|
30
|
+
| --- | --- | --- |
|
|
31
|
+
| **EYE** | N3 on SWI-Prolog | The mature, extensive N3 implementation and ecosystem |
|
|
32
|
+
| **Eyeling** | N3 implemented in JavaScript | Direct JavaScript and RDF-JS integration with explicit forward and backward N3 rules |
|
|
33
|
+
| **Eyeron** | N3 implemented in Rust, with native and WebAssembly APIs | Rust-native or WebAssembly deployment with N3 proofs and built-ins |
|
|
34
|
+
| **Eyeleng** | SHACL 1.2 SRL and RDF Rules implemented in JavaScript | A W3C Recommendation-track language plus automatic hybrid reasoning |
|
|
35
|
+
|
|
36
|
+
Choose Eyeleng when you want to:
|
|
37
|
+
|
|
38
|
+
- build new rule systems on SHACL 1.2 Rules rather than a Community Group language;
|
|
39
|
+
- migrate N3 reasoning workloads to SRL or RDF Rules;
|
|
40
|
+
- let the engine choose between materialization and goal-directed evaluation;
|
|
41
|
+
- use tabling for recursive, function-like computations without publishing their intermediate facts;
|
|
42
|
+
- combine stratified negation, dependency analysis, RDF 1.2 syntax, and rule execution in one compact engine;
|
|
43
|
+
- run the same dependency-free implementation from a CLI, Node.js, or a browser.
|
|
44
|
+
|
|
45
|
+
In short: **Eyeleng aims to carry the practical reasoning power demonstrated by the EYE family into SHACL 1.2 Rules, with automatic hybrid execution as the default rather than explicit reasoning direction as a language-level choice.**
|
|
46
|
+
|
|
47
|
+
Standards references: [SHACL 1.2 Rules](https://www.w3.org/TR/shacl12-rules/), [Notation3 Community Group](https://www.w3.org/groups/cg/n3-dev/), and [W3C document types](https://www.w3.org/standards/types/).
|
|
48
|
+
|
|
15
49
|
## Quick start
|
|
16
50
|
|
|
17
51
|
```sh
|
|
@@ -43,7 +77,7 @@ It derives:
|
|
|
43
77
|
|
|
44
78
|
Open the [Playground](https://eyereasoner.github.io/eyeleng/playground) for a self-contained browser UI with URL loading, autosave, share links, diagnostics, queries, and SRL/RDF Rules syntax selection.
|
|
45
79
|
|
|
46
|
-
## How
|
|
80
|
+
## How automatic hybrid reasoning works
|
|
47
81
|
|
|
48
82
|
Eyeleng computes the closure of a rule set:
|
|
49
83
|
|
|
@@ -189,15 +223,23 @@ The replay data includes message streams, envelopes, offsets, next-envelope link
|
|
|
189
223
|
|
|
190
224
|
## CLI
|
|
191
225
|
|
|
226
|
+
```text
|
|
227
|
+
Usage: eyeleng [options] [file-or-url.n3|- ...]
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
With no input arguments, Eyeleng prints help. Pass `-` to read from standard input; local files and HTTP(S) URLs can be combined as positional inputs.
|
|
231
|
+
|
|
192
232
|
Common commands:
|
|
193
233
|
|
|
194
234
|
```sh
|
|
195
235
|
./eyeleng.js examples/family.srl
|
|
196
236
|
./eyeleng.js --all examples/family.srl
|
|
197
237
|
./eyeleng.js --check --deps examples/stratified-negation.srl
|
|
198
|
-
./eyeleng.js --json --
|
|
238
|
+
./eyeleng.js --json --prove --stats examples/if-then.srl
|
|
199
239
|
./eyeleng.js --query-file examples/query-body.txt examples/query.srl
|
|
200
240
|
./eyeleng.js --syntax rdf examples/w3c-rule-set-snippet.ttl
|
|
241
|
+
cat examples/family.srl | ./eyeleng.js -
|
|
242
|
+
./eyeleng.js https://example.org/rules.n3
|
|
201
243
|
```
|
|
202
244
|
|
|
203
245
|
Important options:
|
|
@@ -205,7 +247,7 @@ Important options:
|
|
|
205
247
|
```text
|
|
206
248
|
--all print the full closure, including input facts
|
|
207
249
|
--json print JSON instead of compact triples/bindings
|
|
208
|
-
--
|
|
250
|
+
--prove print proof explanations
|
|
209
251
|
--stats print iteration and triple counts to stderr
|
|
210
252
|
--check parse and analyze only; do not run rules
|
|
211
253
|
--strict treat static warnings as errors, including recursive term generation
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
const { parseRdfMessageLog, looksLikeRdfMessageLog } = require('./rdfMessages.js');
|
|
16
16
|
const { evaluate } = require('./engine.js');
|
|
17
17
|
const { analyze } = require('./analyze.js');
|
|
18
|
-
const { formatTriples, sortTriples, toJSON, formatTrace, formatBindings } = require('./format.js');
|
|
18
|
+
const { formatTriples, sortTriples, toJSON, formatTrace, formatProof, formatBindings } = require('./format.js');
|
|
19
19
|
const { runQuery, queryResult, queryProgram, queryRunOptions, shouldUseHybridForQuery } = require('./query.js');
|
|
20
20
|
const { resultTriples } = require('./output.js');
|
|
21
21
|
|
|
@@ -138,6 +138,7 @@
|
|
|
138
138
|
sortTriples,
|
|
139
139
|
toJSON,
|
|
140
140
|
formatTrace,
|
|
141
|
+
formatProof,
|
|
141
142
|
resultTriples,
|
|
142
143
|
};
|
|
143
144
|
|
|
@@ -4263,6 +4264,7 @@
|
|
|
4263
4264
|
rule: rule.name || `rule#${ruleIndex + 1}`,
|
|
4264
4265
|
triple,
|
|
4265
4266
|
binding,
|
|
4267
|
+
uses: proofUses(rule.body, binding),
|
|
4266
4268
|
});
|
|
4267
4269
|
}
|
|
4268
4270
|
}
|
|
@@ -4273,6 +4275,17 @@
|
|
|
4273
4275
|
return { applications, added };
|
|
4274
4276
|
}
|
|
4275
4277
|
|
|
4278
|
+
function proofUses(body, binding) {
|
|
4279
|
+
return body
|
|
4280
|
+
.filter((clause) => clause.type === 'triple')
|
|
4281
|
+
.map((clause) => ({
|
|
4282
|
+
s: instantiateTerm(clause.triple.s, binding),
|
|
4283
|
+
p: instantiateTerm(clause.triple.p, binding),
|
|
4284
|
+
o: instantiateTerm(clause.triple.o, binding),
|
|
4285
|
+
}))
|
|
4286
|
+
.filter((triple) => ![triple.s, triple.p, triple.o].some((term) => term && term.type === 'var'));
|
|
4287
|
+
}
|
|
4288
|
+
|
|
4276
4289
|
function prepareBodyContext(program, store, context) {
|
|
4277
4290
|
if (!context.hybridBackwardPredicates || context.hybridBackwardPredicates.size === 0) return context;
|
|
4278
4291
|
return {
|
|
@@ -6063,6 +6076,35 @@
|
|
|
6063
6076
|
return trace.map((entry) => `#${entry.iteration} ${entry.rule} => ${formatTriple(entry.triple, prefixes)}`).join('\n');
|
|
6064
6077
|
}
|
|
6065
6078
|
|
|
6079
|
+
function formatProof(trace, prefixes = {}) {
|
|
6080
|
+
if (!trace.length) return '';
|
|
6081
|
+
const lines = ['@prefix pe: <https://eyereasoner.github.io/pe#> .', ''];
|
|
6082
|
+
for (const entry of trace) {
|
|
6083
|
+
const conclusion = formatTriple(entry.triple, prefixes);
|
|
6084
|
+
lines.push(`{ ${conclusion} } pe:why {`);
|
|
6085
|
+
lines.push(` { ${conclusion} }`);
|
|
6086
|
+
lines.push(` pe:by [ pe:rule ${quoteString(entry.rule)} ]${proofDetails(entry, prefixes)} .`);
|
|
6087
|
+
lines.push('}.', '');
|
|
6088
|
+
}
|
|
6089
|
+
return lines.join('\n').trimEnd();
|
|
6090
|
+
}
|
|
6091
|
+
|
|
6092
|
+
function proofDetails(entry, prefixes) {
|
|
6093
|
+
const details = [];
|
|
6094
|
+
const bindings = Object.entries(entry.binding || {}).sort(([a], [b]) => a.localeCompare(b));
|
|
6095
|
+
if (bindings.length > 0) {
|
|
6096
|
+
details.push(`\n pe:binding ${bindings.map(([name, value]) => `[ pe:var ${quoteString(name)}; pe:value ${formatTerm(value, prefixes)} ]`).join(', ')}`);
|
|
6097
|
+
}
|
|
6098
|
+
if (entry.uses && entry.uses.length > 0) {
|
|
6099
|
+
details.push(`\n pe:uses ${entry.uses.map((triple) => `{ ${formatTriple(triple, prefixes)} }`).join(', ')}`);
|
|
6100
|
+
}
|
|
6101
|
+
return details.length ? `;${details.join(';')}` : '';
|
|
6102
|
+
}
|
|
6103
|
+
|
|
6104
|
+
function quoteString(value) {
|
|
6105
|
+
return `"${String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n')}"`;
|
|
6106
|
+
}
|
|
6107
|
+
|
|
6066
6108
|
function formatBindings(bindings, prefixes = {}, select = null) {
|
|
6067
6109
|
const columns = select && select.length > 0 ? select : inferColumns(bindings);
|
|
6068
6110
|
return bindings
|
|
@@ -6094,7 +6136,7 @@
|
|
|
6094
6136
|
prefixes: result.prefixes,
|
|
6095
6137
|
diagnostics: result.diagnostics || [],
|
|
6096
6138
|
triples: sortTriples(triples, result.prefixes).map(jsonSafeTriple),
|
|
6097
|
-
|
|
6139
|
+
proof: options.proof ? result.trace : undefined,
|
|
6098
6140
|
};
|
|
6099
6141
|
if (result.query) json.query = jsonSafeValue(result.query);
|
|
6100
6142
|
if (result.analysis && options.analysis) json.analysis = result.analysis;
|
|
@@ -6123,7 +6165,7 @@
|
|
|
6123
6165
|
return value;
|
|
6124
6166
|
}
|
|
6125
6167
|
|
|
6126
|
-
module.exports = { sortTriples, formatTriples, formatTrace, formatBindings, formatBinding, toJSON };
|
|
6168
|
+
module.exports = { sortTriples, formatTriples, formatTrace, formatProof, formatBindings, formatBinding, toJSON };
|
|
6127
6169
|
|
|
6128
6170
|
},
|
|
6129
6171
|
"src/query.js": function (require, module, exports) {
|
package/examples/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# W3C SHACL 1.2 Rules draft examples
|
|
2
2
|
|
|
3
|
+
Eyeleng—where `leng` stands for **Logic Engine Next Generation**—is built for automatic hybrid reasoning, combining forward materialization with tabled backward proving.
|
|
4
|
+
|
|
3
5
|
This directory contains runnable examples, including files mirrored from the SHACL 1.2 Rules draft.
|
|
4
6
|
It also includes SRL adaptations of selected eyeling N3 examples.
|
|
5
7
|
|
package/eyeleng.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
queryProgram,
|
|
18
18
|
queryRunOptions,
|
|
19
19
|
formatTriples,
|
|
20
|
-
|
|
20
|
+
formatProof,
|
|
21
21
|
formatBindings,
|
|
22
22
|
toJSON,
|
|
23
23
|
resultTriples,
|
|
@@ -41,15 +41,28 @@
|
|
|
41
41
|
|
|
42
42
|
const VERSION = readPackageVersion();
|
|
43
43
|
|
|
44
|
-
function
|
|
44
|
+
function legacyHelp() {
|
|
45
45
|
return `eyeleng ${VERSION}\n\nA dependency-free JavaScript implementation experiment for the SHACL 1.2 Rules draft, including SRL and RDF Rules syntax front-ends.\n\nUsage:\n eyeleng [options] [file ...]\n\nOptions:\n --all Print the full closure, including input facts\n --json Print JSON instead of compact triples/bindings\n --trace Print derivation trace to stderr, or include it in JSON\n --stats Print iteration and triple counts to stderr\n --check Parse and analyze only; do not run rules\n --strict Treat static warnings as errors, including recursive term generation\n --deps Print rule dependency edges during --check\n --query TEXT Run a raw SRL body pattern over the closure or backward planner\n --query-file FILE Read a raw SRL body pattern from a file\n --query-mode MODE Use auto, forward, or backward query planning (default auto)\n --hybrid Force aggressive hybrid orientation for function-like rules\n --no-hybrid Disable automatic hybrid forward/backward execution\n --max-iterations N Stop after N fixpoint iterations within a recursive layer\n --no-imports Parse IMPORTS/owl:imports but do not load imported rule sets\n --rdf-messages Parse input as an RDF Message Log\n --include-message-facts Include payload facts while parsing RDF Message Logs\n --syntax MODE Use srl, rdf, or auto syntax detection (default auto)\n --ruleset TERM In RDF syntax, run only the selected srl:RuleSet\n --version Print version\n -h, --help Print this help\n\nWith no file arguments, eyeleng reads from stdin.\n`;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
function help() {
|
|
49
|
+
return legacyHelp().replace(
|
|
50
|
+
' --trace Print derivation trace to stderr, or include it in JSON',
|
|
51
|
+
' --prove Print proof explanations',
|
|
52
|
+
).replace(
|
|
53
|
+
'Usage:\n eyeleng [options] [file ...]',
|
|
54
|
+
'Usage: eyeleng [options] [file-or-url.n3|- ...]',
|
|
55
|
+
).replace(
|
|
56
|
+
'With no file arguments, eyeleng reads from stdin.',
|
|
57
|
+
'With no input arguments, eyeleng prints this help. Use - to read from stdin.',
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
48
61
|
function parseArgs(argv) {
|
|
49
62
|
const options = {
|
|
50
63
|
all: false,
|
|
51
64
|
json: false,
|
|
52
|
-
|
|
65
|
+
prove: false,
|
|
53
66
|
stats: false,
|
|
54
67
|
check: false,
|
|
55
68
|
strict: false,
|
|
@@ -70,7 +83,7 @@
|
|
|
70
83
|
const arg = argv[i];
|
|
71
84
|
if (arg === '--all') options.all = true;
|
|
72
85
|
else if (arg === '--json') options.json = true;
|
|
73
|
-
else if (arg === '--
|
|
86
|
+
else if (arg === '--prove') options.prove = true;
|
|
74
87
|
else if (arg === '--stats') options.stats = true;
|
|
75
88
|
else if (arg === '--check') options.check = true;
|
|
76
89
|
else if (arg === '--strict') options.strict = true;
|
|
@@ -111,6 +124,8 @@
|
|
|
111
124
|
options.version = true;
|
|
112
125
|
} else if (arg === '-h' || arg === '--help') {
|
|
113
126
|
options.help = true;
|
|
127
|
+
} else if (arg === '-') {
|
|
128
|
+
files.push(arg);
|
|
114
129
|
} else if (arg.startsWith('-')) {
|
|
115
130
|
throw new Error(`Unknown option ${arg}`);
|
|
116
131
|
} else {
|
|
@@ -121,13 +136,26 @@
|
|
|
121
136
|
return { options, files };
|
|
122
137
|
}
|
|
123
138
|
|
|
124
|
-
function readInput(files) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
139
|
+
async function readInput(files, fetchImpl = globalThis.fetch) {
|
|
140
|
+
const inputs = [];
|
|
141
|
+
let readStdin = false;
|
|
142
|
+
for (const spec of files) {
|
|
143
|
+
if (spec === '-') {
|
|
144
|
+
if (readStdin) throw new Error('Standard input (-) may only be specified once');
|
|
145
|
+
readStdin = true;
|
|
146
|
+
inputs.push({ source: fs.readFileSync(0, 'utf8'), filename: '<stdin>', baseIRI: null });
|
|
147
|
+
} else if (/^https?:\/\//i.test(spec)) {
|
|
148
|
+
if (typeof fetchImpl !== 'function') throw new Error(`Cannot fetch URL ${spec}: fetch is unavailable`);
|
|
149
|
+
const response = await fetchImpl(spec);
|
|
150
|
+
if (!response.ok) throw new Error(`Cannot fetch URL ${spec}: HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ''}`);
|
|
151
|
+
inputs.push({ source: await response.text(), filename: spec, baseIRI: spec });
|
|
152
|
+
} else {
|
|
153
|
+
const filename = path.resolve(spec);
|
|
154
|
+
inputs.push({ source: fs.readFileSync(filename, 'utf8'), filename, baseIRI: pathToFileURL(filename).href });
|
|
155
|
+
}
|
|
129
156
|
}
|
|
130
|
-
|
|
157
|
+
if (inputs.length === 1) return inputs[0];
|
|
158
|
+
return { source: inputs.map((input) => input.source).join('\n'), filename: '<input>', baseIRI: null };
|
|
131
159
|
}
|
|
132
160
|
|
|
133
161
|
function createFileImportResolver() {
|
|
@@ -172,7 +200,7 @@
|
|
|
172
200
|
return /^https?:/.test(name) ? compactIRI(name, prefixes) : name;
|
|
173
201
|
}
|
|
174
202
|
|
|
175
|
-
function main(argv = process.argv.slice(2), io = process) {
|
|
203
|
+
async function main(argv = process.argv.slice(2), io = process) {
|
|
176
204
|
try {
|
|
177
205
|
const { options, files } = parseArgs(argv);
|
|
178
206
|
if (options.help) {
|
|
@@ -183,7 +211,11 @@
|
|
|
183
211
|
io.stdout.write(`${VERSION}\n`);
|
|
184
212
|
return 0;
|
|
185
213
|
}
|
|
186
|
-
|
|
214
|
+
if (files.length === 0) {
|
|
215
|
+
io.stdout.write(help());
|
|
216
|
+
return 0;
|
|
217
|
+
}
|
|
218
|
+
const input = await readInput(files);
|
|
187
219
|
const compiled = compile(input.source, {
|
|
188
220
|
filename: input.filename,
|
|
189
221
|
baseIRI: input.baseIRI,
|
|
@@ -245,15 +277,18 @@
|
|
|
245
277
|
}
|
|
246
278
|
|
|
247
279
|
if (options.json) {
|
|
248
|
-
io.stdout.write(`${JSON.stringify(toJSON(result, { all: options.all,
|
|
280
|
+
io.stdout.write(`${JSON.stringify(toJSON(result, { all: options.all, proof: options.prove, analysis: options.deps }), null, 2)}\n`);
|
|
249
281
|
} else if (result.query) {
|
|
250
282
|
const out = formatBindings(result.query.bindings, result.prefixes, result.query.select);
|
|
251
283
|
if (out) io.stdout.write(`${out}\n`);
|
|
252
284
|
} else {
|
|
253
|
-
if (options.trace && result.trace.length > 0) io.stderr.write(`${formatTrace(result.trace, result.prefixes)}\n`);
|
|
254
285
|
const triples = resultTriples(result, compiled.program, options);
|
|
255
286
|
const out = formatTriples(triples, result.prefixes);
|
|
256
287
|
if (out) io.stdout.write(`${out}\n`);
|
|
288
|
+
if (options.prove) {
|
|
289
|
+
const proof = formatProof(result.trace, result.prefixes);
|
|
290
|
+
if (proof) io.stdout.write(`${out ? '\n' : ''}${proof}\n`);
|
|
291
|
+
}
|
|
257
292
|
}
|
|
258
293
|
|
|
259
294
|
if (options.stats) {
|
|
@@ -271,9 +306,9 @@
|
|
|
271
306
|
}
|
|
272
307
|
}
|
|
273
308
|
|
|
274
|
-
if (require.main === module) process.exitCode =
|
|
309
|
+
if (require.main === module) main().then((code) => { process.exitCode = code; });
|
|
275
310
|
|
|
276
|
-
module.exports = { main, parseArgs, help, VERSION, createFileImportResolver };
|
|
311
|
+
module.exports = { main, parseArgs, readInput, help, VERSION, createFileImportResolver };
|
|
277
312
|
|
|
278
313
|
},
|
|
279
314
|
"src/api.js": function (require, module, exports) {
|
|
@@ -284,7 +319,7 @@
|
|
|
284
319
|
const { parseRdfMessageLog, looksLikeRdfMessageLog } = require('./rdfMessages.js');
|
|
285
320
|
const { evaluate } = require('./engine.js');
|
|
286
321
|
const { analyze } = require('./analyze.js');
|
|
287
|
-
const { formatTriples, sortTriples, toJSON, formatTrace, formatBindings } = require('./format.js');
|
|
322
|
+
const { formatTriples, sortTriples, toJSON, formatTrace, formatProof, formatBindings } = require('./format.js');
|
|
288
323
|
const { runQuery, queryResult, queryProgram, queryRunOptions, shouldUseHybridForQuery } = require('./query.js');
|
|
289
324
|
const { resultTriples } = require('./output.js');
|
|
290
325
|
|
|
@@ -407,6 +442,7 @@
|
|
|
407
442
|
sortTriples,
|
|
408
443
|
toJSON,
|
|
409
444
|
formatTrace,
|
|
445
|
+
formatProof,
|
|
410
446
|
resultTriples,
|
|
411
447
|
};
|
|
412
448
|
|
|
@@ -4532,6 +4568,7 @@
|
|
|
4532
4568
|
rule: rule.name || `rule#${ruleIndex + 1}`,
|
|
4533
4569
|
triple,
|
|
4534
4570
|
binding,
|
|
4571
|
+
uses: proofUses(rule.body, binding),
|
|
4535
4572
|
});
|
|
4536
4573
|
}
|
|
4537
4574
|
}
|
|
@@ -4542,6 +4579,17 @@
|
|
|
4542
4579
|
return { applications, added };
|
|
4543
4580
|
}
|
|
4544
4581
|
|
|
4582
|
+
function proofUses(body, binding) {
|
|
4583
|
+
return body
|
|
4584
|
+
.filter((clause) => clause.type === 'triple')
|
|
4585
|
+
.map((clause) => ({
|
|
4586
|
+
s: instantiateTerm(clause.triple.s, binding),
|
|
4587
|
+
p: instantiateTerm(clause.triple.p, binding),
|
|
4588
|
+
o: instantiateTerm(clause.triple.o, binding),
|
|
4589
|
+
}))
|
|
4590
|
+
.filter((triple) => ![triple.s, triple.p, triple.o].some((term) => term && term.type === 'var'));
|
|
4591
|
+
}
|
|
4592
|
+
|
|
4545
4593
|
function prepareBodyContext(program, store, context) {
|
|
4546
4594
|
if (!context.hybridBackwardPredicates || context.hybridBackwardPredicates.size === 0) return context;
|
|
4547
4595
|
return {
|
|
@@ -6332,6 +6380,35 @@
|
|
|
6332
6380
|
return trace.map((entry) => `#${entry.iteration} ${entry.rule} => ${formatTriple(entry.triple, prefixes)}`).join('\n');
|
|
6333
6381
|
}
|
|
6334
6382
|
|
|
6383
|
+
function formatProof(trace, prefixes = {}) {
|
|
6384
|
+
if (!trace.length) return '';
|
|
6385
|
+
const lines = ['@prefix pe: <https://eyereasoner.github.io/pe#> .', ''];
|
|
6386
|
+
for (const entry of trace) {
|
|
6387
|
+
const conclusion = formatTriple(entry.triple, prefixes);
|
|
6388
|
+
lines.push(`{ ${conclusion} } pe:why {`);
|
|
6389
|
+
lines.push(` { ${conclusion} }`);
|
|
6390
|
+
lines.push(` pe:by [ pe:rule ${quoteString(entry.rule)} ]${proofDetails(entry, prefixes)} .`);
|
|
6391
|
+
lines.push('}.', '');
|
|
6392
|
+
}
|
|
6393
|
+
return lines.join('\n').trimEnd();
|
|
6394
|
+
}
|
|
6395
|
+
|
|
6396
|
+
function proofDetails(entry, prefixes) {
|
|
6397
|
+
const details = [];
|
|
6398
|
+
const bindings = Object.entries(entry.binding || {}).sort(([a], [b]) => a.localeCompare(b));
|
|
6399
|
+
if (bindings.length > 0) {
|
|
6400
|
+
details.push(`\n pe:binding ${bindings.map(([name, value]) => `[ pe:var ${quoteString(name)}; pe:value ${formatTerm(value, prefixes)} ]`).join(', ')}`);
|
|
6401
|
+
}
|
|
6402
|
+
if (entry.uses && entry.uses.length > 0) {
|
|
6403
|
+
details.push(`\n pe:uses ${entry.uses.map((triple) => `{ ${formatTriple(triple, prefixes)} }`).join(', ')}`);
|
|
6404
|
+
}
|
|
6405
|
+
return details.length ? `;${details.join(';')}` : '';
|
|
6406
|
+
}
|
|
6407
|
+
|
|
6408
|
+
function quoteString(value) {
|
|
6409
|
+
return `"${String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n')}"`;
|
|
6410
|
+
}
|
|
6411
|
+
|
|
6335
6412
|
function formatBindings(bindings, prefixes = {}, select = null) {
|
|
6336
6413
|
const columns = select && select.length > 0 ? select : inferColumns(bindings);
|
|
6337
6414
|
return bindings
|
|
@@ -6363,7 +6440,7 @@
|
|
|
6363
6440
|
prefixes: result.prefixes,
|
|
6364
6441
|
diagnostics: result.diagnostics || [],
|
|
6365
6442
|
triples: sortTriples(triples, result.prefixes).map(jsonSafeTriple),
|
|
6366
|
-
|
|
6443
|
+
proof: options.proof ? result.trace : undefined,
|
|
6367
6444
|
};
|
|
6368
6445
|
if (result.query) json.query = jsonSafeValue(result.query);
|
|
6369
6446
|
if (result.analysis && options.analysis) json.analysis = result.analysis;
|
|
@@ -6392,7 +6469,7 @@
|
|
|
6392
6469
|
return value;
|
|
6393
6470
|
}
|
|
6394
6471
|
|
|
6395
|
-
module.exports = { sortTriples, formatTriples, formatTrace, formatBindings, formatBinding, toJSON };
|
|
6472
|
+
module.exports = { sortTriples, formatTriples, formatTrace, formatProof, formatBindings, formatBinding, toJSON };
|
|
6396
6473
|
|
|
6397
6474
|
},
|
|
6398
6475
|
"src/query.js": function (require, module, exports) {
|
|
@@ -6544,5 +6621,7 @@
|
|
|
6544
6621
|
__modules[id](localRequire, module, module.exports);
|
|
6545
6622
|
return module.exports;
|
|
6546
6623
|
}
|
|
6547
|
-
|
|
6624
|
+
Promise.resolve(__require("src/cli.js").main(process.argv.slice(2)))
|
|
6625
|
+
.then((code) => { process.exitCode = code; })
|
|
6626
|
+
.catch((error) => { console.error(error); process.exitCode = 1; });
|
|
6548
6627
|
}());
|
package/package.json
CHANGED
package/playground.html
CHANGED
|
@@ -438,7 +438,7 @@
|
|
|
438
438
|
<div class="checks" aria-label="Reasoning options">
|
|
439
439
|
<label class="toggle"><input id="all-output" type="checkbox" /> Print full closure (--all)</label>
|
|
440
440
|
<label class="toggle"><input id="json-output" type="checkbox" /> JSON output (--json)</label>
|
|
441
|
-
<label class="toggle"><input id="trace-output" type="checkbox" /> Include
|
|
441
|
+
<label class="toggle"><input id="trace-output" type="checkbox" /> Include proof explanations (--prove)</label>
|
|
442
442
|
<label class="toggle"><input id="strict-mode" type="checkbox" /> Treat warnings as errors (--strict)</label>
|
|
443
443
|
<label class="toggle"><input id="check-only" type="checkbox" /> Check only, do not run rules</label>
|
|
444
444
|
<label class="toggle"><input id="show-analysis" type="checkbox" /> Include analysis in JSON</label>
|
|
@@ -946,7 +946,7 @@
|
|
|
946
946
|
if (els.json.checked) {
|
|
947
947
|
text = JSON.stringify(eyeleng.toJSON(result, {
|
|
948
948
|
all: els.all.checked,
|
|
949
|
-
|
|
949
|
+
proof: els.trace.checked,
|
|
950
950
|
analysis: els.analysis.checked,
|
|
951
951
|
}), null, 2);
|
|
952
952
|
} else if (result.query) {
|
|
@@ -955,8 +955,8 @@
|
|
|
955
955
|
const triples = els.all.checked ? result.closure : result.inferred;
|
|
956
956
|
text = eyeleng.formatTriples(triples, result.prefixes);
|
|
957
957
|
if (els.trace.checked && result.trace.length > 0) {
|
|
958
|
-
const
|
|
959
|
-
text = (text ? text + '\n\n' : '') +
|
|
958
|
+
const proof = eyeleng.formatProof(result.trace, result.prefixes);
|
|
959
|
+
text = (text ? text + '\n\n' : '') + proof;
|
|
960
960
|
}
|
|
961
961
|
}
|
|
962
962
|
if (!text) text = '(no output)';
|