eyeleng 1.1.2 → 1.2.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/README.md CHANGED
@@ -3,15 +3,49 @@
3
3
  [![npm version](https://img.shields.io/npm/v/eyeleng.svg)](https://www.npmjs.com/package/eyeleng)
4
4
  [![DOI](https://img.shields.io/badge/DOI-10.5281%2Fzenodo.20342577-blue.svg)](https://doi.org/10.5281/zenodo.20342577)
5
5
 
6
- `eyeleng` stands for **EYE Logic Engine**. It is a compact JavaScript implementation of SHACL 1.2 Rules with two rule front-ends:
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 still uses forward chaining for ordinary finite materialization, but the default execution mode now includes conservative auto-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 by 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.
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 the reasoner works
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 --trace --stats examples/if-then.srl
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
- --trace print derivation trace to stderr, or include it in JSON
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
- trace: options.trace ? result.trace : undefined,
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) {
@@ -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
 
@@ -7,3 +9,5 @@ It also includes SRL adaptations of selected eyeling N3 examples.
7
9
  - `.ttl` files exercise the RDF Rules syntax front-end.
8
10
 
9
11
  All `.srl` and `.ttl` files in this directory are run by `test/examples.test.js`; most have golden TriG outputs in `examples/output/*.trig`.
12
+
13
+ For a minimal starting point, `socrates.srl` derives the classic conclusion that Socrates is mortal from the facts that Socrates is human and every human is mortal.
@@ -0,0 +1 @@
1
+ :Socrates a :Mortal .
@@ -0,0 +1,12 @@
1
+ PREFIX : <http://example.org/socrates#>
2
+ PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
3
+
4
+ DATA {
5
+ :Socrates a :Human .
6
+ :Human rdfs:subClassOf :Mortal .
7
+ }
8
+
9
+ RULE { ?individual a ?superclass } WHERE {
10
+ ?individual a ?class .
11
+ ?class rdfs:subClassOf ?superclass .
12
+ }
package/eyeleng.js CHANGED
@@ -17,7 +17,7 @@
17
17
  queryProgram,
18
18
  queryRunOptions,
19
19
  formatTriples,
20
- formatTrace,
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 help() {
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
- trace: false,
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 === '--trace') options.trace = true;
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
- if (files.length === 0) return { source: fs.readFileSync(0, 'utf8'), filename: '<stdin>', baseIRI: null };
126
- if (files.length === 1) {
127
- const filename = path.resolve(files[0]);
128
- return { source: fs.readFileSync(filename, 'utf8'), filename, baseIRI: pathToFileURL(filename).href };
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
- return { source: files.map((file) => fs.readFileSync(file, 'utf8')).join('\n'), filename: '<input>', baseIRI: null };
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
- const input = readInput(files);
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, trace: options.trace, analysis: options.deps }), null, 2)}\n`);
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 = main();
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
- trace: options.trace ? result.trace : undefined,
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
- process.exitCode = __require("src/cli.js").main(process.argv.slice(2));
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeleng",
3
- "version": "1.1.2",
3
+ "version": "1.2.1",
4
4
  "description": "The EYE Logic Engine: a JavaScript implementation of SHACL Rules, including SRL and RDF Rules syntax front-ends.",
5
5
  "keywords": [
6
6
  "SRL",
@@ -14,7 +14,7 @@
14
14
  },
15
15
  "homepage": "https://github.com/eyereasoner/eyeleng#readme",
16
16
  "bin": {
17
- "eyeleng": "./eyeleng.js"
17
+ "eyeleng": "eyeleng.js"
18
18
  },
19
19
  "files": [
20
20
  "LICENSE.md",