eyeleng 1.1.1 → 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 +69 -21
- package/dist/browser/eyeleng.browser.js +65 -13
- package/examples/README.md +2 -0
- package/eyeleng.js +119 -30
- 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/src/parser.js +15 -5
- package/src/rdfSyntax.js +5 -5
- package/test/api.test.js +35 -0
- 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
|
|
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
|
|
|
@@ -76,6 +110,10 @@ then the body matches with `?parent = :alice` and `?child = :bob`, and the head
|
|
|
76
110
|
|
|
77
111
|
Negation is handled by stratified evaluation: rules are grouped into dependency layers, and recursion through negation is rejected so the result stays deterministic.
|
|
78
112
|
|
|
113
|
+
By default, run mode uses **auto-hybrid reasoning**. Ordinary rules are still materialized forward into the closure. Rules that behave like internal functions can instead be proved backward with tabling when their predicates occur as demanded body goals. For example, a Fibonacci helper predicate can be computed just in time for the public `:fib` results, without printing all intermediate helper triples. Use `--no-hybrid` or `{ hybrid: false }` to force pure forward materialization, and use `--hybrid` or `{ hybrid: true }` to force more aggressive backward orientation.
|
|
114
|
+
|
|
115
|
+
For explicit `--query` / `--query-file` use, the query body is the top-level goal. Each triple pattern in that body is then a sub-goal that may be answered by the backward prover. If pure backward proving is not safe for the demanded predicates, query mode can run a hybrid plan and finally fall back to ordinary forward closure.
|
|
116
|
+
|
|
79
117
|
Recursive rules that create new terms through `SET`/`BIND` or head blank nodes run in relaxed mode by default. Relaxed mode can derive useful finite closures, but termination is not guaranteed; use `--max-iterations` as a safety valve. `--strict` rejects these recursive term-generating cycles at analysis time. Head blank nodes are deterministically skolemized per rule and solution mapping, so the same firing reuses the same witness node instead of creating a fresh one each pass.
|
|
80
118
|
|
|
81
119
|
## Language surface
|
|
@@ -153,7 +191,6 @@ npm run w3c:rules:earl
|
|
|
153
191
|
npm run w3c:rdf
|
|
154
192
|
npm run w3c:rdf:json
|
|
155
193
|
npm run w3c:rdf:earl
|
|
156
|
-
npm run w3c:all
|
|
157
194
|
```
|
|
158
195
|
|
|
159
196
|
`npm test` includes the W3C harnesses. When W3C URLs are reachable, progress is printed test by test. In offline environments, remote W3C checks are reported as unreachable unless `EYELENG_W3C_REQUIRED=1` is set. The `*:earl` scripts also print test progress, but write the EARL Turtle only to `reports/` instead of printing the report to the terminal.
|
|
@@ -186,15 +223,23 @@ The replay data includes message streams, envelopes, offsets, next-envelope link
|
|
|
186
223
|
|
|
187
224
|
## CLI
|
|
188
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
|
+
|
|
189
232
|
Common commands:
|
|
190
233
|
|
|
191
234
|
```sh
|
|
192
235
|
./eyeleng.js examples/family.srl
|
|
193
236
|
./eyeleng.js --all examples/family.srl
|
|
194
237
|
./eyeleng.js --check --deps examples/stratified-negation.srl
|
|
195
|
-
./eyeleng.js --json --
|
|
238
|
+
./eyeleng.js --json --prove --stats examples/if-then.srl
|
|
196
239
|
./eyeleng.js --query-file examples/query-body.txt examples/query.srl
|
|
197
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
|
|
198
243
|
```
|
|
199
244
|
|
|
200
245
|
Important options:
|
|
@@ -202,7 +247,7 @@ Important options:
|
|
|
202
247
|
```text
|
|
203
248
|
--all print the full closure, including input facts
|
|
204
249
|
--json print JSON instead of compact triples/bindings
|
|
205
|
-
--
|
|
250
|
+
--prove print proof explanations
|
|
206
251
|
--stats print iteration and triple counts to stderr
|
|
207
252
|
--check parse and analyze only; do not run rules
|
|
208
253
|
--strict treat static warnings as errors, including recursive term generation
|
|
@@ -238,28 +283,30 @@ RULE { ?x a :Mortal } WHERE { ?x a :Man }
|
|
|
238
283
|
console.log(formatTriples(result.inferred, result.prefixes));
|
|
239
284
|
```
|
|
240
285
|
|
|
241
|
-
|
|
286
|
+
Hybrid and query mode:
|
|
242
287
|
|
|
243
288
|
```js
|
|
244
|
-
const { runQuery, formatBindings } = require('./src/index.js');
|
|
289
|
+
const { run, runQuery, formatBindings } = require('./src/index.js');
|
|
245
290
|
|
|
291
|
+
// Normal run mode defaults to conservative auto-hybrid reasoning.
|
|
292
|
+
// Ordinary output rules still materialize forward, while selected
|
|
293
|
+
// function-like helper predicates can be proved backward on demand.
|
|
294
|
+
const resultWithAutoHybrid = run(source);
|
|
295
|
+
|
|
296
|
+
// Force pure forward closure if you want every derivable helper fact materialized.
|
|
297
|
+
const pureForward = run(source, { hybrid: false });
|
|
298
|
+
|
|
299
|
+
// For explicit queries, the whole raw body is the top-level goal.
|
|
300
|
+
// Each triple pattern in that body is a sub-goal for the planner.
|
|
246
301
|
const result = runQuery(source, '?x :ancestorOf ?y');
|
|
247
302
|
console.log(formatBindings(result.query.bindings, result.prefixes));
|
|
248
303
|
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
-
|
|
252
|
-
// hybrid plan before falling back to plain forward closure.
|
|
253
|
-
const justInTime = runQuery(source, ':alice :computedValue ?value', { queryMode: 'backward' });
|
|
254
|
-
|
|
255
|
-
// Run mode uses conservative auto-hybrid planning by default. It keeps
|
|
256
|
-
// ordinary rules materialized, but can prove demanded function-like predicates
|
|
257
|
-
// backward with tabling. Pass { hybrid: false } to force pure forward closure,
|
|
258
|
-
// or { hybrid: true } to force aggressive hybrid orientation.
|
|
259
|
-
const resultWithAutoHybrid = run(source);
|
|
304
|
+
// queryMode defaults to auto: try tabled backward proving for demanded
|
|
305
|
+
// predicates, then hybrid execution, then ordinary forward closure.
|
|
306
|
+
const justInTime = runQuery(source, ':alice :computedValue ?value');
|
|
260
307
|
|
|
261
|
-
//
|
|
262
|
-
|
|
308
|
+
// Force the tabled backward prover for supported query shapes.
|
|
309
|
+
const backwardOnly = runQuery(source, ':alice :computedValue ?value', { queryMode: 'backward' });
|
|
263
310
|
```
|
|
264
311
|
|
|
265
312
|
Imports:
|
|
@@ -326,6 +373,7 @@ Examples live in [examples/](./examples/):
|
|
|
326
373
|
- `property-paths.srl` — path matching in bodies
|
|
327
374
|
- `basic-ruleset.ttl` — RDF Rules syntax
|
|
328
375
|
- `rdf-messages.srl` / `rdf-messages.trig` — RDF Message Log replay
|
|
376
|
+
- `fibonacci.srl` — fast-doubling Fibonacci using just-in-time backward helper predicates
|
|
329
377
|
- `deep-taxonomy-*.srl` — generated benchmark programs
|
|
330
378
|
|
|
331
379
|
## Known boundaries
|
|
@@ -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
|
|
|
@@ -441,7 +442,7 @@
|
|
|
441
442
|
}
|
|
442
443
|
|
|
443
444
|
parseTripleStatement(options = {}) {
|
|
444
|
-
const subjectNode = this.parseGraphNode(options);
|
|
445
|
+
const subjectNode = this.parseGraphNode({ ...options, position: 'subject' });
|
|
445
446
|
const triples = [...subjectNode.triples];
|
|
446
447
|
triples.push(...this.parsePropertyListForSubject(subjectNode.term, options));
|
|
447
448
|
return triples;
|
|
@@ -455,7 +456,7 @@
|
|
|
455
456
|
if (terminators.some((value) => this.checkValue(value)) || this.checkValue('.')) break;
|
|
456
457
|
const predicate = options.allowPath ? this.parseVerbPathOrSimple(options) : this.parseVerbTerm(options);
|
|
457
458
|
do {
|
|
458
|
-
const objectNode = this.parseGraphNode(options);
|
|
459
|
+
const objectNode = this.parseGraphNode({ ...options, position: 'object' });
|
|
459
460
|
triples.push(...objectNode.triples);
|
|
460
461
|
const baseTriple = { s: subject, p: predicate, o: objectNode.term };
|
|
461
462
|
triples.push(baseTriple);
|
|
@@ -522,6 +523,7 @@
|
|
|
522
523
|
currentReifier = this.parseOptionalReifier(options);
|
|
523
524
|
triples.push({ s: currentReifier, p: iri(RDF_REIFIES), o: reified });
|
|
524
525
|
} else if (this.matchValue('{|')) {
|
|
526
|
+
if (this.checkValue('|}')) throw this.error('Annotation blocks may not be empty');
|
|
525
527
|
const annotationSubject = currentReifier || this.freshGraphNode(options);
|
|
526
528
|
triples.push({ s: annotationSubject, p: iri(RDF_REIFIES), o: reified });
|
|
527
529
|
triples.push(...this.parsePropertyListForSubject(annotationSubject, options, ['|}']));
|
|
@@ -571,7 +573,7 @@
|
|
|
571
573
|
}
|
|
572
574
|
|
|
573
575
|
parseVerbTerm(options = {}) {
|
|
574
|
-
const term = this.parseTerm(options);
|
|
576
|
+
const term = this.parseTerm({ ...options, position: 'predicate' });
|
|
575
577
|
if (term.type !== 'iri' && term.type !== 'var') throw this.error('Expected IRI or variable as predicate');
|
|
576
578
|
return term;
|
|
577
579
|
}
|
|
@@ -694,7 +696,10 @@
|
|
|
694
696
|
if (token.value === '<<(') return this.parseTripleTermAfterOpen(options);
|
|
695
697
|
if (token.value === '<<') throw this.error('Use << s p o >> as a graph node reifier; use <<( s p o )>> for a triple term', token);
|
|
696
698
|
if (token.type === 'word') {
|
|
697
|
-
if (token.value === 'a')
|
|
699
|
+
if (token.value === 'a') {
|
|
700
|
+
if (options.position !== 'predicate') throw this.error('a is only allowed as a predicate', token);
|
|
701
|
+
return iri(RDF_TYPE);
|
|
702
|
+
}
|
|
698
703
|
if (token.value === 'true') return literal(true, XSD_BOOLEAN);
|
|
699
704
|
if (token.value === 'false') return literal(false, XSD_BOOLEAN);
|
|
700
705
|
if (token.value.startsWith('_:')) return blankNode(token.value.slice(2));
|
|
@@ -728,7 +733,13 @@
|
|
|
728
733
|
return literal(coerceLexicalLiteral(token.value, datatype), datatype, null);
|
|
729
734
|
}
|
|
730
735
|
if (this.checkType('word') && /^@[A-Za-z]+(?:-[A-Za-z0-9]+)*(?:--[A-Za-z]+)?$/.test(this.peek().value)) {
|
|
731
|
-
const
|
|
736
|
+
const tagToken = this.advance();
|
|
737
|
+
const rawTag = tagToken.value.slice(1);
|
|
738
|
+
const direction = rawTag.includes('--') ? rawTag.slice(rawTag.lastIndexOf('--') + 2) : null;
|
|
739
|
+
if (direction && direction !== 'ltr' && direction !== 'rtl') {
|
|
740
|
+
throw this.error(`Invalid base direction --${direction}; expected --ltr or --rtl`, tagToken);
|
|
741
|
+
}
|
|
742
|
+
const tag = rawTag.toLowerCase();
|
|
732
743
|
const [lang, langDir = null] = tag.split('--');
|
|
733
744
|
return literal(token.value, null, lang, langDir);
|
|
734
745
|
}
|
|
@@ -2507,6 +2518,7 @@
|
|
|
2507
2518
|
const syntaxProfile = String(options.profile || options.profileId || '').toLowerCase();
|
|
2508
2519
|
const rdf12Surface = syntaxProfile === 'turtle' || syntaxProfile === 'trig';
|
|
2509
2520
|
const implicitStatementNodes = new Set();
|
|
2521
|
+
function implicitStatementNodeKey(term) { return `${term.kind}:${term.value}`; }
|
|
2510
2522
|
|
|
2511
2523
|
function freshBlank() { bnodeCounter += 1; return blank(`b${bnodeCounter}`); }
|
|
2512
2524
|
function peek(offset = 0) { return tokens[i + offset]; }
|
|
@@ -2657,7 +2669,7 @@
|
|
|
2657
2669
|
expect('>>');
|
|
2658
2670
|
const node = reifier || freshBlank();
|
|
2659
2671
|
out.push(triple(node, iri(RDF_REIFIES), tripleTerm(s, p, o), graph));
|
|
2660
|
-
|
|
2672
|
+
implicitStatementNodes.add(implicitStatementNodeKey(node));
|
|
2661
2673
|
return node;
|
|
2662
2674
|
}
|
|
2663
2675
|
|
|
@@ -2686,7 +2698,7 @@
|
|
|
2686
2698
|
if (accept(']')) return node;
|
|
2687
2699
|
parsePredicateObjectList(node, out, graph);
|
|
2688
2700
|
expect(']');
|
|
2689
|
-
if (node.kind === 'blank') implicitStatementNodes.add(node
|
|
2701
|
+
if (node.kind === 'blank') implicitStatementNodes.add(implicitStatementNodeKey(node));
|
|
2690
2702
|
return node;
|
|
2691
2703
|
}
|
|
2692
2704
|
|
|
@@ -2764,9 +2776,8 @@
|
|
|
2764
2776
|
if (options3.requireDot) expect('.'); else accept('.');
|
|
2765
2777
|
return;
|
|
2766
2778
|
}
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
if ((peek()?.type === '.' || peek()?.type === '}' || peek()?.type === undefined) && subject.kind === 'blank' && implicitStatementNodes.has(subject.value)) {
|
|
2779
|
+
const subject = parseTerm(out, graph, { noLiteral: !rdf12Surface, noA: true });
|
|
2780
|
+
if ((peek()?.type === '.' || peek()?.type === '}' || peek()?.type === undefined) && implicitStatementNodes.has(implicitStatementNodeKey(subject))) {
|
|
2770
2781
|
if (options3.requireDot) expect('.'); else accept('.');
|
|
2771
2782
|
return;
|
|
2772
2783
|
}
|
|
@@ -4253,6 +4264,7 @@
|
|
|
4253
4264
|
rule: rule.name || `rule#${ruleIndex + 1}`,
|
|
4254
4265
|
triple,
|
|
4255
4266
|
binding,
|
|
4267
|
+
uses: proofUses(rule.body, binding),
|
|
4256
4268
|
});
|
|
4257
4269
|
}
|
|
4258
4270
|
}
|
|
@@ -4263,6 +4275,17 @@
|
|
|
4263
4275
|
return { applications, added };
|
|
4264
4276
|
}
|
|
4265
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
|
+
|
|
4266
4289
|
function prepareBodyContext(program, store, context) {
|
|
4267
4290
|
if (!context.hybridBackwardPredicates || context.hybridBackwardPredicates.size === 0) return context;
|
|
4268
4291
|
return {
|
|
@@ -6053,6 +6076,35 @@
|
|
|
6053
6076
|
return trace.map((entry) => `#${entry.iteration} ${entry.rule} => ${formatTriple(entry.triple, prefixes)}`).join('\n');
|
|
6054
6077
|
}
|
|
6055
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
|
+
|
|
6056
6108
|
function formatBindings(bindings, prefixes = {}, select = null) {
|
|
6057
6109
|
const columns = select && select.length > 0 ? select : inferColumns(bindings);
|
|
6058
6110
|
return bindings
|
|
@@ -6084,7 +6136,7 @@
|
|
|
6084
6136
|
prefixes: result.prefixes,
|
|
6085
6137
|
diagnostics: result.diagnostics || [],
|
|
6086
6138
|
triples: sortTriples(triples, result.prefixes).map(jsonSafeTriple),
|
|
6087
|
-
|
|
6139
|
+
proof: options.proof ? result.trace : undefined,
|
|
6088
6140
|
};
|
|
6089
6141
|
if (result.query) json.query = jsonSafeValue(result.query);
|
|
6090
6142
|
if (result.analysis && options.analysis) json.analysis = result.analysis;
|
|
@@ -6113,7 +6165,7 @@
|
|
|
6113
6165
|
return value;
|
|
6114
6166
|
}
|
|
6115
6167
|
|
|
6116
|
-
module.exports = { sortTriples, formatTriples, formatTrace, formatBindings, formatBinding, toJSON };
|
|
6168
|
+
module.exports = { sortTriples, formatTriples, formatTrace, formatProof, formatBindings, formatBinding, toJSON };
|
|
6117
6169
|
|
|
6118
6170
|
},
|
|
6119
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
|
|