eyeling 1.5.34 → 1.5.36
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/examples/json-pointer.n3 +5 -0
- package/examples/json-reconcile-vat.n3 +5 -0
- package/examples/output/rdf-list.n3 +31 -0
- package/examples/rdf-list.n3 +26 -0
- package/eyeling.js +172 -0
- package/package.json +1 -1
- package/test/api.test.js +30 -0
package/examples/json-pointer.n3
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
# =================================================
|
|
2
|
+
# JSON Pointer example
|
|
3
|
+
# See https://datatracker.ietf.org/doc/html/rfc6901
|
|
4
|
+
# =================================================
|
|
5
|
+
|
|
1
6
|
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
|
2
7
|
@prefix string: <http://www.w3.org/2000/10/swap/string#> .
|
|
3
8
|
@prefix list: <http://www.w3.org/2000/10/swap/list#> .
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
# =================================================
|
|
2
|
+
# Rconcile VAT JSON Pointer example
|
|
3
|
+
# See https://datatracker.ietf.org/doc/html/rfc6901
|
|
4
|
+
# =================================================
|
|
5
|
+
|
|
1
6
|
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
|
2
7
|
@prefix string: <http://www.w3.org/2000/10/swap/string#> .
|
|
3
8
|
@prefix list: <http://www.w3.org/2000/10/swap/list#> .
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
@prefix : <http://example.org/ns#> .
|
|
2
|
+
|
|
3
|
+
# ----------------------------------------------------------------------
|
|
4
|
+
# Proof for derived triple:
|
|
5
|
+
# :result :is true .
|
|
6
|
+
# It holds because the following instance of the rule body is provable:
|
|
7
|
+
# :X :val (1 2) .
|
|
8
|
+
# (1 2) :head 1 .
|
|
9
|
+
# (1 2) :tail (2) .
|
|
10
|
+
# 1 log:equalTo 1 .
|
|
11
|
+
# (2) log:equalTo (2) .
|
|
12
|
+
# via the schematic forward rule:
|
|
13
|
+
# {
|
|
14
|
+
# ?X :val ?Y .
|
|
15
|
+
# ?Y :head ?H .
|
|
16
|
+
# ?Y :tail ?T .
|
|
17
|
+
# ?H log:equalTo 1 .
|
|
18
|
+
# ?T log:equalTo (2) .
|
|
19
|
+
# } => {
|
|
20
|
+
# :result :is true .
|
|
21
|
+
# } .
|
|
22
|
+
# with substitution (on rule variables):
|
|
23
|
+
# ?H = 1
|
|
24
|
+
# ?T = (2)
|
|
25
|
+
# ?X = :X
|
|
26
|
+
# ?Y = (1 2)
|
|
27
|
+
# Therefore the derived triple above is entailed by the rules and facts.
|
|
28
|
+
# ----------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
:result :is true .
|
|
31
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# =================================
|
|
2
|
+
# RDF list
|
|
3
|
+
# Example from Patrick Hochstenbach
|
|
4
|
+
# =================================
|
|
5
|
+
|
|
6
|
+
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
7
|
+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
|
8
|
+
@prefix : <http://example.org/ns#> .
|
|
9
|
+
|
|
10
|
+
:X :val (1 2).
|
|
11
|
+
|
|
12
|
+
{ ?X :head ?Y } <= { ?X rdf:first ?Y }.
|
|
13
|
+
{ ?X :tail ?Y } <= { ?X rdf:rest ?Y} .
|
|
14
|
+
|
|
15
|
+
{
|
|
16
|
+
?X :val ?Y.
|
|
17
|
+
?Y :head ?H .
|
|
18
|
+
?Y :tail ?T .
|
|
19
|
+
|
|
20
|
+
?H log:equalTo 1.
|
|
21
|
+
?T log:equalTo (2).
|
|
22
|
+
}
|
|
23
|
+
=>
|
|
24
|
+
{
|
|
25
|
+
:result :is true.
|
|
26
|
+
}.
|
package/eyeling.js
CHANGED
|
@@ -2951,6 +2951,40 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
2951
2951
|
return [];
|
|
2952
2952
|
}
|
|
2953
2953
|
|
|
2954
|
+
// rdf:first (alias of list:first)
|
|
2955
|
+
// Schema: $s+ rdf:first $o-
|
|
2956
|
+
if (g.p instanceof Iri && g.p.value === RDF_NS + "first") {
|
|
2957
|
+
if (!(g.s instanceof ListTerm)) return [];
|
|
2958
|
+
if (!g.s.elems.length) return [];
|
|
2959
|
+
const first = g.s.elems[0];
|
|
2960
|
+
const s2 = unifyTerm(g.o, first, subst);
|
|
2961
|
+
return s2 !== null ? [s2] : [];
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2964
|
+
// rdf:rest (alias of list:rest)
|
|
2965
|
+
// Schema: $s+ rdf:rest $o-
|
|
2966
|
+
if (g.p instanceof Iri && g.p.value === RDF_NS + "rest") {
|
|
2967
|
+
// Closed list: (a b c) -> (b c)
|
|
2968
|
+
if (g.s instanceof ListTerm) {
|
|
2969
|
+
if (!g.s.elems.length) return [];
|
|
2970
|
+
const rest = new ListTerm(g.s.elems.slice(1));
|
|
2971
|
+
const s2 = unifyTerm(g.o, rest, subst);
|
|
2972
|
+
return s2 !== null ? [s2] : [];
|
|
2973
|
+
}
|
|
2974
|
+
// Open list: (a b ... ?T) -> (b ... ?T)
|
|
2975
|
+
if (g.s instanceof OpenListTerm) {
|
|
2976
|
+
if (!g.s.prefix.length) return [];
|
|
2977
|
+
if (g.s.prefix.length === 1) {
|
|
2978
|
+
const s2 = unifyTerm(g.o, new Var(g.s.tailVar), subst);
|
|
2979
|
+
return s2 !== null ? [s2] : [];
|
|
2980
|
+
}
|
|
2981
|
+
const rest = new OpenListTerm(g.s.prefix.slice(1), g.s.tailVar);
|
|
2982
|
+
const s2 = unifyTerm(g.o, rest, subst);
|
|
2983
|
+
return s2 !== null ? [s2] : [];
|
|
2984
|
+
}
|
|
2985
|
+
return [];
|
|
2986
|
+
}
|
|
2987
|
+
|
|
2954
2988
|
// list:iterate
|
|
2955
2989
|
// true iff $s is a list and $o is a list (index value),
|
|
2956
2990
|
// where index is a valid 0-based index into $s and value is the element at that index.
|
|
@@ -3658,6 +3692,12 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3658
3692
|
function isBuiltinPred(p) {
|
|
3659
3693
|
if (!(p instanceof Iri)) return false;
|
|
3660
3694
|
const v = p.value;
|
|
3695
|
+
|
|
3696
|
+
// Treat RDF Collections as list-term builtins too.
|
|
3697
|
+
if (v === RDF_NS + "first" || v === RDF_NS + "rest") {
|
|
3698
|
+
return true;
|
|
3699
|
+
}
|
|
3700
|
+
|
|
3661
3701
|
return (
|
|
3662
3702
|
v.startsWith(CRYPTO_NS) ||
|
|
3663
3703
|
v.startsWith(MATH_NS) ||
|
|
@@ -4278,6 +4318,135 @@ function printExplanation(df, prefixes) {
|
|
|
4278
4318
|
// Misc helpers
|
|
4279
4319
|
// ============================================================================
|
|
4280
4320
|
|
|
4321
|
+
// Turn RDF Collections described with rdf:first/rdf:rest (+ rdf:nil) into ListTerm terms.
|
|
4322
|
+
// This mutates triples/rules in-place so list:* builtins work on RDF-serialized lists too.
|
|
4323
|
+
function materializeRdfLists(triples, forwardRules, backwardRules) {
|
|
4324
|
+
const RDF_FIRST = RDF_NS + "first";
|
|
4325
|
+
const RDF_REST = RDF_NS + "rest";
|
|
4326
|
+
const RDF_NIL = RDF_NS + "nil";
|
|
4327
|
+
|
|
4328
|
+
function nodeKey(t) {
|
|
4329
|
+
if (t instanceof Blank) return "B:" + t.label;
|
|
4330
|
+
if (t instanceof Iri) return "I:" + t.value;
|
|
4331
|
+
return null;
|
|
4332
|
+
}
|
|
4333
|
+
|
|
4334
|
+
// Collect first/rest arcs from *input triples*
|
|
4335
|
+
const firstMap = new Map(); // key(subject) -> Term (object)
|
|
4336
|
+
const restMap = new Map(); // key(subject) -> Term (object)
|
|
4337
|
+
for (const tr of triples) {
|
|
4338
|
+
if (!(tr.p instanceof Iri)) continue;
|
|
4339
|
+
const k = nodeKey(tr.s);
|
|
4340
|
+
if (!k) continue;
|
|
4341
|
+
if (tr.p.value === RDF_FIRST) firstMap.set(k, tr.o);
|
|
4342
|
+
else if (tr.p.value === RDF_REST) restMap.set(k, tr.o);
|
|
4343
|
+
}
|
|
4344
|
+
if (!firstMap.size && !restMap.size) return;
|
|
4345
|
+
|
|
4346
|
+
const cache = new Map(); // key(node) -> ListTerm
|
|
4347
|
+
const visiting = new Set(); // cycle guard
|
|
4348
|
+
|
|
4349
|
+
function buildListForKey(k) {
|
|
4350
|
+
if (cache.has(k)) return cache.get(k);
|
|
4351
|
+
if (visiting.has(k)) return null; // cycle => not a well-formed list
|
|
4352
|
+
visiting.add(k);
|
|
4353
|
+
|
|
4354
|
+
// rdf:nil => ()
|
|
4355
|
+
if (k === "I:" + RDF_NIL) {
|
|
4356
|
+
const empty = new ListTerm([]);
|
|
4357
|
+
cache.set(k, empty);
|
|
4358
|
+
visiting.delete(k);
|
|
4359
|
+
return empty;
|
|
4360
|
+
}
|
|
4361
|
+
|
|
4362
|
+
const head = firstMap.get(k);
|
|
4363
|
+
const tail = restMap.get(k);
|
|
4364
|
+
if (head === undefined || tail === undefined) {
|
|
4365
|
+
visiting.delete(k);
|
|
4366
|
+
return null; // not a full cons cell
|
|
4367
|
+
}
|
|
4368
|
+
|
|
4369
|
+
const headTerm = rewriteTerm(head);
|
|
4370
|
+
|
|
4371
|
+
let tailListElems = null;
|
|
4372
|
+
if (tail instanceof Iri && tail.value === RDF_NIL) {
|
|
4373
|
+
tailListElems = [];
|
|
4374
|
+
} else {
|
|
4375
|
+
const tk = nodeKey(tail);
|
|
4376
|
+
if (!tk) {
|
|
4377
|
+
visiting.delete(k);
|
|
4378
|
+
return null;
|
|
4379
|
+
}
|
|
4380
|
+
const tailList = buildListForKey(tk);
|
|
4381
|
+
if (!tailList) {
|
|
4382
|
+
visiting.delete(k);
|
|
4383
|
+
return null;
|
|
4384
|
+
}
|
|
4385
|
+
tailListElems = tailList.elems;
|
|
4386
|
+
}
|
|
4387
|
+
|
|
4388
|
+
const out = new ListTerm([headTerm, ...tailListElems]);
|
|
4389
|
+
cache.set(k, out);
|
|
4390
|
+
visiting.delete(k);
|
|
4391
|
+
return out;
|
|
4392
|
+
}
|
|
4393
|
+
|
|
4394
|
+
function rewriteTerm(t) {
|
|
4395
|
+
// Replace list nodes (Blank/Iri) by their constructed ListTerm when possible
|
|
4396
|
+
const k = nodeKey(t);
|
|
4397
|
+
if (k) {
|
|
4398
|
+
const built = buildListForKey(k);
|
|
4399
|
+
if (built) return built;
|
|
4400
|
+
// Also rewrite rdf:nil even if not otherwise referenced
|
|
4401
|
+
if (t instanceof Iri && t.value === RDF_NIL) return new ListTerm([]);
|
|
4402
|
+
return t;
|
|
4403
|
+
}
|
|
4404
|
+
if (t instanceof ListTerm) {
|
|
4405
|
+
let changed = false;
|
|
4406
|
+
const elems = t.elems.map(e => {
|
|
4407
|
+
const r = rewriteTerm(e);
|
|
4408
|
+
if (r !== e) changed = true;
|
|
4409
|
+
return r;
|
|
4410
|
+
});
|
|
4411
|
+
return changed ? new ListTerm(elems) : t;
|
|
4412
|
+
}
|
|
4413
|
+
if (t instanceof OpenListTerm) {
|
|
4414
|
+
let changed = false;
|
|
4415
|
+
const prefix = t.prefix.map(e => {
|
|
4416
|
+
const r = rewriteTerm(e);
|
|
4417
|
+
if (r !== e) changed = true;
|
|
4418
|
+
return r;
|
|
4419
|
+
});
|
|
4420
|
+
return changed ? new OpenListTerm(prefix, t.tailVar) : t;
|
|
4421
|
+
}
|
|
4422
|
+
if (t instanceof FormulaTerm) {
|
|
4423
|
+
for (const tr of t.triples) rewriteTriple(tr);
|
|
4424
|
+
return t;
|
|
4425
|
+
}
|
|
4426
|
+
return t;
|
|
4427
|
+
}
|
|
4428
|
+
|
|
4429
|
+
function rewriteTriple(tr) {
|
|
4430
|
+
tr.s = rewriteTerm(tr.s);
|
|
4431
|
+
tr.p = rewriteTerm(tr.p);
|
|
4432
|
+
tr.o = rewriteTerm(tr.o);
|
|
4433
|
+
}
|
|
4434
|
+
|
|
4435
|
+
// Pre-build all reachable list heads
|
|
4436
|
+
for (const k of firstMap.keys()) buildListForKey(k);
|
|
4437
|
+
|
|
4438
|
+
// Rewrite input triples + rules in-place
|
|
4439
|
+
for (const tr of triples) rewriteTriple(tr);
|
|
4440
|
+
for (const r of forwardRules) {
|
|
4441
|
+
for (const tr of r.premise) rewriteTriple(tr);
|
|
4442
|
+
for (const tr of r.conclusion) rewriteTriple(tr);
|
|
4443
|
+
}
|
|
4444
|
+
for (const r of backwardRules) {
|
|
4445
|
+
for (const tr of r.premise) rewriteTriple(tr);
|
|
4446
|
+
for (const tr of r.conclusion) rewriteTriple(tr);
|
|
4447
|
+
}
|
|
4448
|
+
}
|
|
4449
|
+
|
|
4281
4450
|
function localIsoDateTimeString(d) {
|
|
4282
4451
|
function pad(n, width = 2) {
|
|
4283
4452
|
return String(n).padStart(width, "0");
|
|
@@ -4365,6 +4534,9 @@ function main() {
|
|
|
4365
4534
|
const [prefixes, triples, frules, brules] = parser.parseDocument();
|
|
4366
4535
|
// console.log(JSON.stringify([prefixes, triples, frules, brules], null, 2));
|
|
4367
4536
|
|
|
4537
|
+
// Build internal ListTerm values from rdf:first/rdf:rest (+ rdf:nil) input triples
|
|
4538
|
+
materializeRdfLists(triples, frules, brules);
|
|
4539
|
+
|
|
4368
4540
|
const facts = triples.filter(tr => isGroundTriple(tr));
|
|
4369
4541
|
const derived = forwardChain(facts, frules, brules);
|
|
4370
4542
|
|
package/package.json
CHANGED
package/test/api.test.js
CHANGED
|
@@ -672,6 +672,36 @@ ${U('a')} <-${U('p')} ${U('b')}.`,
|
|
|
672
672
|
new RegExp(`_:b2\\s+<${EX}q>\\s+<${EX}joe>\\s*\\.`),
|
|
673
673
|
],
|
|
674
674
|
},
|
|
675
|
+
|
|
676
|
+
{ name: '48 rdf:first: works on list terms (alias of list:first)',
|
|
677
|
+
opt: { proofComments: false }, input: ` { ( ${U('a')} ${U('b')} ${U('c')} ) rdf:first ?x. } => { ${U('s')} ${U('first')} ?x. }.
|
|
678
|
+
`,
|
|
679
|
+
expect: [new RegExp(`${EX}s>\\s+<${EX}first>\\s+<${EX}a>\\s*\\.`)],
|
|
680
|
+
},
|
|
681
|
+
|
|
682
|
+
{ name: '49 rdf:rest: works on list terms (alias of list:rest)',
|
|
683
|
+
opt: { proofComments: false }, input: ` { ( ${U('a')} ${U('b')} ${U('c')} ) rdf:rest ?r. ?r rdf:first ?y. } => { ${U('s')} ${U('second')} ?y. }.
|
|
684
|
+
`,
|
|
685
|
+
expect: [new RegExp(`${EX}s>\\s+<${EX}second>\\s+<${EX}b>\\s*\\.`)],
|
|
686
|
+
},
|
|
687
|
+
|
|
688
|
+
{ name: '50 rdf collection materialization: rdf:first/rdf:rest triples become list terms',
|
|
689
|
+
opt: { proofComments: false }, input: ` ${U('s')} ${U('p')} _:l1.
|
|
690
|
+
_:l1 rdf:first ${U('a')}.
|
|
691
|
+
_:l1 rdf:rest _:l2.
|
|
692
|
+
_:l2 rdf:first ${U('b')}.
|
|
693
|
+
_:l2 rdf:rest rdf:nil.
|
|
694
|
+
|
|
695
|
+
{ ${U('s')} ${U('p')} ?lst. ?lst rdf:first ?x. } => { ${U('s')} ${U('q')} ?x. }.
|
|
696
|
+
{ ${U('s')} ${U('p')} ?lst. ?lst rdf:rest ?r. ?r rdf:first ?y. } => { ${U('s')} ${U('q2')} ?y. }.
|
|
697
|
+
{ ${U('s')} ${U('p')} ?lst. ?lst list:rest ?r. ?r list:first ?y. } => { ${U('s')} ${U('q3')} ?y. }.
|
|
698
|
+
`,
|
|
699
|
+
expect: [
|
|
700
|
+
new RegExp(`${EX}s>\\s+<${EX}q>\\s+<${EX}a>\\s*\\.`),
|
|
701
|
+
new RegExp(`${EX}s>\\s+<${EX}q2>\\s+<${EX}b>\\s*\\.`),
|
|
702
|
+
new RegExp(`${EX}s>\\s+<${EX}q3>\\s+<${EX}b>\\s*\\.`),
|
|
703
|
+
],
|
|
704
|
+
},
|
|
675
705
|
];
|
|
676
706
|
|
|
677
707
|
let passed = 0;
|