eyeling 1.24.1 → 1.24.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/HANDBOOK.md +134 -0
- package/dist/browser/eyeling.browser.js +454 -22
- package/eyeling.js +454 -22
- package/index.js +2 -0
- package/lib/cli.js +27 -10
- package/lib/engine.js +17 -7
- package/lib/lexer.js +295 -2
- package/lib/multisource.js +9 -2
- package/lib/printing.js +106 -1
- package/package.json +1 -1
- package/see/README.md +4 -0
- package/see/examples/_see.js +33 -2
- package/see/examples/age.js +27 -1
- package/see/examples/annotation.js +27 -1
- package/see/examples/backward.js +27 -1
- package/see/examples/backward_recursion.js +27 -1
- package/see/examples/bayes_diagnosis.js +27 -1
- package/see/examples/bayes_therapy.js +27 -1
- package/see/examples/bmi.js +27 -1
- package/see/examples/builtin_coverage.js +27 -1
- package/see/examples/collection.js +27 -1
- package/see/examples/complex.js +27 -1
- package/see/examples/complex_matrix_stability.js +27 -1
- package/see/examples/composition_of_injective_functions_is_injective.js +27 -1
- package/see/examples/control_system.js +27 -1
- package/see/examples/crypto_builtins_tests.js +27 -1
- package/see/examples/delfour.js +27 -1
- package/see/examples/digital_product_passport.js +27 -1
- package/see/examples/dijkstra.js +27 -1
- package/see/examples/dijkstra_risk_path.js +27 -1
- package/see/examples/doc/rdf_dataset.md +26 -0
- package/see/examples/doc/triple_terms.md +26 -0
- package/see/examples/dog.js +27 -1
- package/see/examples/eco_route_insight.js +27 -1
- package/see/examples/equals.js +27 -1
- package/see/examples/equivalence_classes_overlap_implies_same_class.js +27 -1
- package/see/examples/euler_identity.js +27 -1
- package/see/examples/ev_roundtrip_planner.js +27 -1
- package/see/examples/existential_rule.js +27 -1
- package/see/examples/expression_eval.js +27 -1
- package/see/examples/family_cousins.js +27 -1
- package/see/examples/fastpow.js +27 -1
- package/see/examples/fibonacci.js +27 -1
- package/see/examples/french_cities.js +27 -1
- package/see/examples/fundamental_theorem_arithmetic.js +27 -1
- package/see/examples/genetic_knapsack_selection.js +27 -1
- package/see/examples/goldbach_1000.js +27 -1
- package/see/examples/good_cobbler.js +27 -1
- package/see/examples/gps.js +27 -1
- package/see/examples/gray_code_counter.js +27 -1
- package/see/examples/greatest_lower_bound_uniqueness.js +27 -1
- package/see/examples/group_inverse_uniqueness.js +27 -1
- package/see/examples/hadamard_approx.js +27 -1
- package/see/examples/hanoi.js +27 -1
- package/see/examples/input/rdf_dataset.trig +34 -0
- package/see/examples/input/triple_terms.trig +28 -0
- package/see/examples/n3/rdf_dataset.n3 +34 -0
- package/see/examples/n3/triple_terms.n3 +23 -0
- package/see/examples/odrl_dpv_risk_ranked.js +27 -1
- package/see/examples/output/rdf_dataset.md +54 -0
- package/see/examples/output/triple_terms.md +53 -0
- package/see/examples/path_discovery.js +27 -1
- package/see/examples/rc_discharge_envelope.js +27 -1
- package/see/examples/rdf_dataset.js +1512 -0
- package/see/examples/rdf_message_flow.js +27 -1
- package/see/examples/rdf_messages.js +27 -1
- package/see/examples/school_placement_audit.js +27 -1
- package/see/examples/smoke_arithmetic.js +27 -1
- package/see/examples/socrates.js +27 -1
- package/see/examples/triple_terms.js +1442 -0
- package/see/examples/wind_turbine.js +27 -1
- package/see/examples/witch.js +27 -1
- package/see/see.js +101 -4
- package/test/api.test.js +86 -0
- package/test/see.test.js +0 -0
- package/tools/bundle.js +0 -0
- package/tools/n3gen.js +0 -0
|
@@ -8,6 +8,7 @@ const crypto = require('crypto');
|
|
|
8
8
|
|
|
9
9
|
function canonical(term) {
|
|
10
10
|
if (term.kind === 'list') return ['list', term.items.map(canonical)];
|
|
11
|
+
if (term.kind === 'triple') return ['triple', canonical(term.s), canonical(term.p), canonical(term.o)];
|
|
11
12
|
if (term.kind === 'formula') return ['formula', term.atoms.map((a) => [canonical(a.s), canonical(a.p), canonical(a.o)])];
|
|
12
13
|
return [term.kind, term.value];
|
|
13
14
|
}
|
|
@@ -17,6 +18,7 @@ function compoundIndexKey() { return Array.from(arguments).map(termIndexKey).joi
|
|
|
17
18
|
function termIsConcrete(t) {
|
|
18
19
|
if (!t || t.kind === 'var') return false;
|
|
19
20
|
if (t.kind === 'list') return t.items.every(termIsConcrete);
|
|
21
|
+
if (t.kind === 'triple') return termIsConcrete(t.s) && termIsConcrete(t.p) && termIsConcrete(t.o);
|
|
20
22
|
if (t.kind === 'formula') return t.atoms.every((a) => termIsConcrete(a.s) && termIsConcrete(a.p) && termIsConcrete(a.o));
|
|
21
23
|
return true;
|
|
22
24
|
}
|
|
@@ -32,6 +34,7 @@ function primitive(t) {
|
|
|
32
34
|
if (t.kind === 'iri') return t.value.replace(/^:/, '');
|
|
33
35
|
if (t.kind === 'blank') return t.value;
|
|
34
36
|
if (t.kind === 'list') return t.items.map(primitive);
|
|
37
|
+
if (t.kind === 'triple') return termToN3(t);
|
|
35
38
|
if (t.kind === 'formula') return termToN3(t);
|
|
36
39
|
return undefined;
|
|
37
40
|
}
|
|
@@ -52,6 +55,7 @@ function termToN3(t) {
|
|
|
52
55
|
if (t.kind === 'var') return '?' + t.value;
|
|
53
56
|
if (t.kind === 'blank') return t.value.startsWith('_:') ? t.value : '_:' + t.value.replace(/^_+/, '');
|
|
54
57
|
if (t.kind === 'list') return '(' + t.items.map(termToN3).join(' ') + ')';
|
|
58
|
+
if (t.kind === 'triple') return '<<( ' + termToN3(t.s) + ' ' + termToN3(t.p) + ' ' + termToN3(t.o) + ' )>>';
|
|
55
59
|
if (t.kind === 'formula') return '{ ' + t.atoms.map(atomToN3).join(' . ') + ' }';
|
|
56
60
|
return String(t.value ?? t);
|
|
57
61
|
}
|
|
@@ -74,6 +78,7 @@ function resolve(term, env, seen = new Set()) {
|
|
|
74
78
|
return resolve(env[term.value], env, seen);
|
|
75
79
|
}
|
|
76
80
|
if (term.kind === 'list') return list(term.items.map((item) => resolve(item, env, seen)));
|
|
81
|
+
if (term.kind === 'triple') return { kind: 'triple', s: resolve(term.s, env), p: resolve(term.p, env), o: resolve(term.o, env) };
|
|
77
82
|
if (term.kind === 'formula') return { kind: 'formula', atoms: term.atoms.map((a) => ({ s: resolve(a.s, env), p: resolve(a.p, env), o: resolve(a.o, env) })) };
|
|
78
83
|
return term;
|
|
79
84
|
}
|
|
@@ -91,6 +96,14 @@ function unify(a, b, env) {
|
|
|
91
96
|
}
|
|
92
97
|
return out;
|
|
93
98
|
}
|
|
99
|
+
if (a.kind === 'triple' || b.kind === 'triple') {
|
|
100
|
+
if (a.kind !== 'triple' || b.kind !== 'triple') return null;
|
|
101
|
+
let out = unify(a.s, b.s, env);
|
|
102
|
+
if (!out) return null;
|
|
103
|
+
out = unify(a.p, b.p, out);
|
|
104
|
+
if (!out) return null;
|
|
105
|
+
return unify(a.o, b.o, out);
|
|
106
|
+
}
|
|
94
107
|
return deepEqual(a, b) ? env : null;
|
|
95
108
|
}
|
|
96
109
|
function bind(pattern, value, env) { return unify(pattern, value, env); }
|
|
@@ -106,6 +119,7 @@ function termIsGround(t, env) {
|
|
|
106
119
|
const r = resolve(t, env);
|
|
107
120
|
if (r.kind === 'var') return false;
|
|
108
121
|
if (r.kind === 'list') return r.items.every((item) => termIsGround(item, env));
|
|
122
|
+
if (r.kind === 'triple') return termIsGround(r.s, env) && termIsGround(r.p, env) && termIsGround(r.o, env);
|
|
109
123
|
if (r.kind === 'formula') return r.atoms.every((atom) => atomIsGround(atom, env));
|
|
110
124
|
return true;
|
|
111
125
|
}
|
|
@@ -665,6 +679,7 @@ function instantiate(term, env, ruleId) {
|
|
|
665
679
|
}
|
|
666
680
|
if (term.kind === 'blank') return blank('_:r' + ruleId + '_' + envSignature(env) + '_' + term.value.replace(/^_/, ''));
|
|
667
681
|
if (term.kind === 'list') return list(term.items.map((item) => instantiate(item, env, ruleId)));
|
|
682
|
+
if (term.kind === 'triple') return { kind: 'triple', s: instantiate(term.s, env, ruleId), p: instantiate(term.p, env, ruleId), o: instantiate(term.o, env, ruleId) };
|
|
668
683
|
if (term.kind === 'formula') return { kind: 'formula', atoms: term.atoms.map((a) => ({ s: instantiate(a.s, env, ruleId), p: instantiate(a.p, env, ruleId), o: instantiate(a.o, env, ruleId) })) };
|
|
669
684
|
return cloneTerm(term);
|
|
670
685
|
}
|
|
@@ -2762,6 +2777,16 @@ function formalOutputFacts(graph, queries, rules, initialFacts) {
|
|
|
2762
2777
|
}
|
|
2763
2778
|
return out;
|
|
2764
2779
|
}
|
|
2780
|
+
function termHasTripleTerm(term) {
|
|
2781
|
+
if (!term) return false;
|
|
2782
|
+
if (term.kind === 'triple') return true;
|
|
2783
|
+
if (term.kind === 'list') return term.items.some(termHasTripleTerm);
|
|
2784
|
+
if (term.kind === 'formula') return term.atoms.some(atomHasTripleTerm);
|
|
2785
|
+
return false;
|
|
2786
|
+
}
|
|
2787
|
+
function atomHasTripleTerm(atom) { return termHasTripleTerm(atom.s) || termHasTripleTerm(atom.p) || termHasTripleTerm(atom.o); }
|
|
2788
|
+
function factsHaveTripleTerms(facts) { return (facts || []).some(atomHasTripleTerm); }
|
|
2789
|
+
function trigHasVersion12(trig) { return /^s*(?:@version|VERSION)s+["']1.2["']/mi.test(String(trig || '')); }
|
|
2765
2790
|
function trigGraphBlock(label, atoms) {
|
|
2766
2791
|
const lines = [label + ' {'];
|
|
2767
2792
|
for (const atom of atoms || []) lines.push(' ' + atomToN3(atom) + ' .');
|
|
@@ -2809,7 +2834,8 @@ function formalOutputToTrig(facts, trig) {
|
|
|
2809
2834
|
const prefixes = prefixLinesFromTrig(trig);
|
|
2810
2835
|
if (state.needOutPrefix && !prefixes.some((line) => line.toLowerCase().startsWith('@prefix out:'))) prefixes.push('@prefix out: <https://example.org/see/output#> .');
|
|
2811
2836
|
const nl = String.fromCharCode(10);
|
|
2812
|
-
|
|
2837
|
+
const version = factsHaveTripleTerms(facts) ? 'VERSION "1.2"' + nl + nl : '';
|
|
2838
|
+
return version + prefixes.join(nl) + nl + nl + body.join(nl);
|
|
2813
2839
|
}
|
|
2814
2840
|
function appendFormalTrigOutput(markdown, graph, queries, rules, initialFacts, data) {
|
|
2815
2841
|
const trig = formalOutputToTrig(formalOutputFacts(graph, queries, rules, initialFacts), data && data.trig);
|
package/see/examples/witch.js
CHANGED
|
@@ -8,6 +8,7 @@ const crypto = require('crypto');
|
|
|
8
8
|
|
|
9
9
|
function canonical(term) {
|
|
10
10
|
if (term.kind === 'list') return ['list', term.items.map(canonical)];
|
|
11
|
+
if (term.kind === 'triple') return ['triple', canonical(term.s), canonical(term.p), canonical(term.o)];
|
|
11
12
|
if (term.kind === 'formula') return ['formula', term.atoms.map((a) => [canonical(a.s), canonical(a.p), canonical(a.o)])];
|
|
12
13
|
return [term.kind, term.value];
|
|
13
14
|
}
|
|
@@ -17,6 +18,7 @@ function compoundIndexKey() { return Array.from(arguments).map(termIndexKey).joi
|
|
|
17
18
|
function termIsConcrete(t) {
|
|
18
19
|
if (!t || t.kind === 'var') return false;
|
|
19
20
|
if (t.kind === 'list') return t.items.every(termIsConcrete);
|
|
21
|
+
if (t.kind === 'triple') return termIsConcrete(t.s) && termIsConcrete(t.p) && termIsConcrete(t.o);
|
|
20
22
|
if (t.kind === 'formula') return t.atoms.every((a) => termIsConcrete(a.s) && termIsConcrete(a.p) && termIsConcrete(a.o));
|
|
21
23
|
return true;
|
|
22
24
|
}
|
|
@@ -32,6 +34,7 @@ function primitive(t) {
|
|
|
32
34
|
if (t.kind === 'iri') return t.value.replace(/^:/, '');
|
|
33
35
|
if (t.kind === 'blank') return t.value;
|
|
34
36
|
if (t.kind === 'list') return t.items.map(primitive);
|
|
37
|
+
if (t.kind === 'triple') return termToN3(t);
|
|
35
38
|
if (t.kind === 'formula') return termToN3(t);
|
|
36
39
|
return undefined;
|
|
37
40
|
}
|
|
@@ -52,6 +55,7 @@ function termToN3(t) {
|
|
|
52
55
|
if (t.kind === 'var') return '?' + t.value;
|
|
53
56
|
if (t.kind === 'blank') return t.value.startsWith('_:') ? t.value : '_:' + t.value.replace(/^_+/, '');
|
|
54
57
|
if (t.kind === 'list') return '(' + t.items.map(termToN3).join(' ') + ')';
|
|
58
|
+
if (t.kind === 'triple') return '<<( ' + termToN3(t.s) + ' ' + termToN3(t.p) + ' ' + termToN3(t.o) + ' )>>';
|
|
55
59
|
if (t.kind === 'formula') return '{ ' + t.atoms.map(atomToN3).join(' . ') + ' }';
|
|
56
60
|
return String(t.value ?? t);
|
|
57
61
|
}
|
|
@@ -74,6 +78,7 @@ function resolve(term, env, seen = new Set()) {
|
|
|
74
78
|
return resolve(env[term.value], env, seen);
|
|
75
79
|
}
|
|
76
80
|
if (term.kind === 'list') return list(term.items.map((item) => resolve(item, env, seen)));
|
|
81
|
+
if (term.kind === 'triple') return { kind: 'triple', s: resolve(term.s, env), p: resolve(term.p, env), o: resolve(term.o, env) };
|
|
77
82
|
if (term.kind === 'formula') return { kind: 'formula', atoms: term.atoms.map((a) => ({ s: resolve(a.s, env), p: resolve(a.p, env), o: resolve(a.o, env) })) };
|
|
78
83
|
return term;
|
|
79
84
|
}
|
|
@@ -91,6 +96,14 @@ function unify(a, b, env) {
|
|
|
91
96
|
}
|
|
92
97
|
return out;
|
|
93
98
|
}
|
|
99
|
+
if (a.kind === 'triple' || b.kind === 'triple') {
|
|
100
|
+
if (a.kind !== 'triple' || b.kind !== 'triple') return null;
|
|
101
|
+
let out = unify(a.s, b.s, env);
|
|
102
|
+
if (!out) return null;
|
|
103
|
+
out = unify(a.p, b.p, out);
|
|
104
|
+
if (!out) return null;
|
|
105
|
+
return unify(a.o, b.o, out);
|
|
106
|
+
}
|
|
94
107
|
return deepEqual(a, b) ? env : null;
|
|
95
108
|
}
|
|
96
109
|
function bind(pattern, value, env) { return unify(pattern, value, env); }
|
|
@@ -106,6 +119,7 @@ function termIsGround(t, env) {
|
|
|
106
119
|
const r = resolve(t, env);
|
|
107
120
|
if (r.kind === 'var') return false;
|
|
108
121
|
if (r.kind === 'list') return r.items.every((item) => termIsGround(item, env));
|
|
122
|
+
if (r.kind === 'triple') return termIsGround(r.s, env) && termIsGround(r.p, env) && termIsGround(r.o, env);
|
|
109
123
|
if (r.kind === 'formula') return r.atoms.every((atom) => atomIsGround(atom, env));
|
|
110
124
|
return true;
|
|
111
125
|
}
|
|
@@ -665,6 +679,7 @@ function instantiate(term, env, ruleId) {
|
|
|
665
679
|
}
|
|
666
680
|
if (term.kind === 'blank') return blank('_:r' + ruleId + '_' + envSignature(env) + '_' + term.value.replace(/^_/, ''));
|
|
667
681
|
if (term.kind === 'list') return list(term.items.map((item) => instantiate(item, env, ruleId)));
|
|
682
|
+
if (term.kind === 'triple') return { kind: 'triple', s: instantiate(term.s, env, ruleId), p: instantiate(term.p, env, ruleId), o: instantiate(term.o, env, ruleId) };
|
|
668
683
|
if (term.kind === 'formula') return { kind: 'formula', atoms: term.atoms.map((a) => ({ s: instantiate(a.s, env, ruleId), p: instantiate(a.p, env, ruleId), o: instantiate(a.o, env, ruleId) })) };
|
|
669
684
|
return cloneTerm(term);
|
|
670
685
|
}
|
|
@@ -1428,6 +1443,16 @@ function formalOutputFacts(graph, queries, rules, initialFacts) {
|
|
|
1428
1443
|
}
|
|
1429
1444
|
return out;
|
|
1430
1445
|
}
|
|
1446
|
+
function termHasTripleTerm(term) {
|
|
1447
|
+
if (!term) return false;
|
|
1448
|
+
if (term.kind === 'triple') return true;
|
|
1449
|
+
if (term.kind === 'list') return term.items.some(termHasTripleTerm);
|
|
1450
|
+
if (term.kind === 'formula') return term.atoms.some(atomHasTripleTerm);
|
|
1451
|
+
return false;
|
|
1452
|
+
}
|
|
1453
|
+
function atomHasTripleTerm(atom) { return termHasTripleTerm(atom.s) || termHasTripleTerm(atom.p) || termHasTripleTerm(atom.o); }
|
|
1454
|
+
function factsHaveTripleTerms(facts) { return (facts || []).some(atomHasTripleTerm); }
|
|
1455
|
+
function trigHasVersion12(trig) { return /^s*(?:@version|VERSION)s+["']1.2["']/mi.test(String(trig || '')); }
|
|
1431
1456
|
function trigGraphBlock(label, atoms) {
|
|
1432
1457
|
const lines = [label + ' {'];
|
|
1433
1458
|
for (const atom of atoms || []) lines.push(' ' + atomToN3(atom) + ' .');
|
|
@@ -1475,7 +1500,8 @@ function formalOutputToTrig(facts, trig) {
|
|
|
1475
1500
|
const prefixes = prefixLinesFromTrig(trig);
|
|
1476
1501
|
if (state.needOutPrefix && !prefixes.some((line) => line.toLowerCase().startsWith('@prefix out:'))) prefixes.push('@prefix out: <https://example.org/see/output#> .');
|
|
1477
1502
|
const nl = String.fromCharCode(10);
|
|
1478
|
-
|
|
1503
|
+
const version = factsHaveTripleTerms(facts) ? 'VERSION "1.2"' + nl + nl : '';
|
|
1504
|
+
return version + prefixes.join(nl) + nl + nl + body.join(nl);
|
|
1479
1505
|
}
|
|
1480
1506
|
function appendFormalTrigOutput(markdown, graph, queries, rules, initialFacts, data) {
|
|
1481
1507
|
const trig = formalOutputToTrig(formalOutputFacts(graph, queries, rules, initialFacts), data && data.trig);
|
package/see/see.js
CHANGED
|
@@ -183,7 +183,7 @@ function Blank(id) {
|
|
|
183
183
|
|
|
184
184
|
// This tokenizer/parser is deliberately smaller than a complete N3 parser. It
|
|
185
185
|
// accepts the SEE example subset: triples, lists, blank-node property lists,
|
|
186
|
-
// quoted formulas, implication arrows, variables, literals, and prefix lines.
|
|
186
|
+
// quoted formulas, RDF 1.2 triple terms, implication arrows, variables, literals, and prefix/version lines.
|
|
187
187
|
function tokenize(source) {
|
|
188
188
|
const s = removeComments(source);
|
|
189
189
|
const tokens = [];
|
|
@@ -196,6 +196,16 @@ function tokenize(source) {
|
|
|
196
196
|
i += 1;
|
|
197
197
|
continue;
|
|
198
198
|
}
|
|
199
|
+
if (s.startsWith('<<(', i)) {
|
|
200
|
+
tokens.push({ type: '<<(', value: '<<(' });
|
|
201
|
+
i += 3;
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (s.startsWith(')>>', i)) {
|
|
205
|
+
tokens.push({ type: ')>>', value: ')>>' });
|
|
206
|
+
i += 3;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
199
209
|
if (s.startsWith('=>', i) || s.startsWith('<=', i) || s.startsWith('^^', i)) {
|
|
200
210
|
tokens.push({ type: s.slice(i, i + 2), value: s.slice(i, i + 2) });
|
|
201
211
|
i += 2;
|
|
@@ -255,6 +265,7 @@ function tokenize(source) {
|
|
|
255
265
|
|
|
256
266
|
function classifyToken(raw) {
|
|
257
267
|
if (raw === '@prefix') return { type: '@prefix', value: raw };
|
|
268
|
+
if (/^VERSION$/i.test(raw)) return { type: 'VERSION', value: raw };
|
|
258
269
|
if (raw === 'a') return { type: 'qname', value: 'rdf:type' };
|
|
259
270
|
if (raw.startsWith('?')) return { type: 'var', value: raw.slice(1) };
|
|
260
271
|
if (/^(true|false)$/i.test(raw)) return { type: 'boolean', value: /^true$/i.test(raw) };
|
|
@@ -273,11 +284,28 @@ class Parser {
|
|
|
273
284
|
eof() {
|
|
274
285
|
return this.pos >= this.tokens.length;
|
|
275
286
|
}
|
|
276
|
-
peek(value = undefined) {
|
|
277
|
-
const tok = this.tokens[this.pos];
|
|
287
|
+
peek(value = undefined, offset = 0) {
|
|
288
|
+
const tok = this.tokens[this.pos + offset];
|
|
278
289
|
if (value === undefined) return tok;
|
|
279
290
|
return tok && tok.type === value;
|
|
280
291
|
}
|
|
292
|
+
isTermToken(tok) {
|
|
293
|
+
return tok && ['qname', 'iri', 'var', 'string', 'number', 'boolean', '<<(', '(', '{', '['].includes(tok.type);
|
|
294
|
+
}
|
|
295
|
+
isNamedGraphStart() {
|
|
296
|
+
const tok = this.peek();
|
|
297
|
+
if (tok?.type === 'qname' && /^GRAPH$/i.test(tok.value)) {
|
|
298
|
+
return this.isTermToken(this.peek(undefined, 1)) && this.peek('{', 2);
|
|
299
|
+
}
|
|
300
|
+
return (tok?.type === 'qname' || tok?.type === 'iri') && this.peek('{', 1);
|
|
301
|
+
}
|
|
302
|
+
parseNamedGraphFact() {
|
|
303
|
+
if (this.peek()?.type === 'qname' && /^GRAPH$/i.test(this.peek().value)) this.next();
|
|
304
|
+
const name = this.parseTerm('fact', []);
|
|
305
|
+
const atoms = this.parseFormula('fact');
|
|
306
|
+
this.accept('.');
|
|
307
|
+
return { s: name, p: I('log:nameOf'), o: { kind: 'formula', atoms } };
|
|
308
|
+
}
|
|
281
309
|
next() {
|
|
282
310
|
if (this.eof()) throw new Error('Unexpected end of input');
|
|
283
311
|
return this.tokens[this.pos++];
|
|
@@ -300,6 +328,11 @@ class Parser {
|
|
|
300
328
|
// Prefix declaration is irrelevant after QName compaction; skip until final dot.
|
|
301
329
|
while (!this.eof() && !this.accept('.')) this.next();
|
|
302
330
|
}
|
|
331
|
+
skipVersion() {
|
|
332
|
+
this.expect('VERSION');
|
|
333
|
+
if (this.peek('string')) this.next();
|
|
334
|
+
this.accept('.');
|
|
335
|
+
}
|
|
303
336
|
parseProgram() {
|
|
304
337
|
const facts = [],
|
|
305
338
|
rules = [],
|
|
@@ -318,6 +351,14 @@ class Parser {
|
|
|
318
351
|
if (m) prefixes[(m[1] || ':').replace(/:$/, '')] = m[2];
|
|
319
352
|
continue;
|
|
320
353
|
}
|
|
354
|
+
if (this.peek('VERSION')) {
|
|
355
|
+
this.skipVersion();
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
if (this.isNamedGraphStart()) {
|
|
359
|
+
facts.push(this.parseNamedGraphFact());
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
321
362
|
if (this.peek('{')) {
|
|
322
363
|
const lhs = this.parseFormula('body');
|
|
323
364
|
if (this.accept('=>')) {
|
|
@@ -346,6 +387,9 @@ class Parser {
|
|
|
346
387
|
this.accept('.');
|
|
347
388
|
rules.push({ kind: 'backward', id: rules.length + 1, body: rhs, head: lhs });
|
|
348
389
|
}
|
|
390
|
+
} else if (this.peek('.') || this.eof()) {
|
|
391
|
+
this.accept('.');
|
|
392
|
+
facts.push(...lhs);
|
|
349
393
|
} else {
|
|
350
394
|
const subject = { kind: 'formula', atoms: lhs };
|
|
351
395
|
const triples = this.parseStatementRest('fact', subject);
|
|
@@ -417,6 +461,13 @@ class Parser {
|
|
|
417
461
|
}
|
|
418
462
|
if (tok.type === 'number' || tok.type === 'boolean') return L(tok.value);
|
|
419
463
|
if (tok.type === 'iri' || tok.type === 'qname') return I(tok.value);
|
|
464
|
+
if (tok.type === '<<(') {
|
|
465
|
+
const subject = this.parseTerm(mode, sink);
|
|
466
|
+
const predicate = this.parsePredicate();
|
|
467
|
+
const object = this.parseTerm(mode, sink);
|
|
468
|
+
this.expect(')>>');
|
|
469
|
+
return { kind: 'triple', s: subject, p: predicate, o: object };
|
|
470
|
+
}
|
|
420
471
|
if (tok.type === '(') {
|
|
421
472
|
const items = [];
|
|
422
473
|
while (!this.accept(')')) items.push(this.parseTerm(mode, sink));
|
|
@@ -470,6 +521,7 @@ function termToJsComment(term) {
|
|
|
470
521
|
if (term.kind === 'var') return `?${term.value}`;
|
|
471
522
|
if (term.kind === 'blank') return term.value;
|
|
472
523
|
if (term.kind === 'list') return `(${term.items.map(termToJsComment).join(' ')})`;
|
|
524
|
+
if (term.kind === 'triple') return `<<( ${termToJsComment(term.s)} ${termToJsComment(term.p)} ${termToJsComment(term.o)} )>>`;
|
|
473
525
|
if (term.kind === 'formula') return `{ ${term.atoms.map(atomToComment).join(' . ')} }`;
|
|
474
526
|
return String(term.value ?? term);
|
|
475
527
|
}
|
|
@@ -524,12 +576,30 @@ function inputTermToN3(term) {
|
|
|
524
576
|
if (term.kind === 'var') return '?' + term.value;
|
|
525
577
|
if (term.kind === 'blank') return term.value.startsWith('_:') ? term.value : '_:' + term.value.replace(/^_+/, '');
|
|
526
578
|
if (term.kind === 'list') return '(' + term.items.map(inputTermToN3).join(' ') + ')';
|
|
579
|
+
if (term.kind === 'triple') return '<<( ' + inputTermToN3(term.s) + ' ' + inputTermToN3(term.p) + ' ' + inputTermToN3(term.o) + ' )>>';
|
|
527
580
|
if (term.kind === 'formula') return '{ ' + term.atoms.map(inputAtomToN3).join(' . ') + ' }';
|
|
528
581
|
return String(term.value ?? term);
|
|
529
582
|
}
|
|
530
583
|
function inputAtomToN3(atom) {
|
|
531
584
|
return inputTermToN3(atom.s) + ' ' + inputTermToN3(atom.p) + ' ' + inputTermToN3(atom.o);
|
|
532
585
|
}
|
|
586
|
+
function inputTermHasTripleTerm(term) {
|
|
587
|
+
if (!term) return false;
|
|
588
|
+
if (term.kind === 'triple') return true;
|
|
589
|
+
if (term.kind === 'list') return term.items.some(inputTermHasTripleTerm);
|
|
590
|
+
if (term.kind === 'formula') return term.atoms.some(inputAtomHasTripleTerm);
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
function inputAtomHasTripleTerm(atom) {
|
|
594
|
+
return inputTermHasTripleTerm(atom.s) || inputTermHasTripleTerm(atom.p) || inputTermHasTripleTerm(atom.o);
|
|
595
|
+
}
|
|
596
|
+
function programHasTripleTerms(program) {
|
|
597
|
+
return [
|
|
598
|
+
...(program.facts || []),
|
|
599
|
+
...(program.rules || []).flatMap((r) => [...(r.body || []), ...(r.head || [])]),
|
|
600
|
+
...(program.queries || []).flatMap((q) => [...(q.premise || []), ...(q.conclusion || [])]),
|
|
601
|
+
].some(inputAtomHasTripleTerm);
|
|
602
|
+
}
|
|
533
603
|
function formulaBlock(label, atoms) {
|
|
534
604
|
const lines = [label + ' {'];
|
|
535
605
|
for (const atom of atoms) lines.push(' ' + inputAtomToN3(atom) + ' .');
|
|
@@ -584,6 +654,7 @@ function generateInputTrig(n3Path, name, title, header, stats, program) {
|
|
|
584
654
|
'}',
|
|
585
655
|
].join('\n');
|
|
586
656
|
const sections = [
|
|
657
|
+
...(programHasTripleTerms(program) ? ['VERSION "1.2"', ''] : []),
|
|
587
658
|
...prefixLines(program.prefixes),
|
|
588
659
|
'',
|
|
589
660
|
'# Formal SEE input evidence in RDF 1.2 TriG.',
|
|
@@ -604,6 +675,7 @@ const crypto = require('crypto');
|
|
|
604
675
|
|
|
605
676
|
function canonical(term) {
|
|
606
677
|
if (term.kind === 'list') return ['list', term.items.map(canonical)];
|
|
678
|
+
if (term.kind === 'triple') return ['triple', canonical(term.s), canonical(term.p), canonical(term.o)];
|
|
607
679
|
if (term.kind === 'formula') return ['formula', term.atoms.map((a) => [canonical(a.s), canonical(a.p), canonical(a.o)])];
|
|
608
680
|
return [term.kind, term.value];
|
|
609
681
|
}
|
|
@@ -613,6 +685,7 @@ function compoundIndexKey() { return Array.from(arguments).map(termIndexKey).joi
|
|
|
613
685
|
function termIsConcrete(t) {
|
|
614
686
|
if (!t || t.kind === 'var') return false;
|
|
615
687
|
if (t.kind === 'list') return t.items.every(termIsConcrete);
|
|
688
|
+
if (t.kind === 'triple') return termIsConcrete(t.s) && termIsConcrete(t.p) && termIsConcrete(t.o);
|
|
616
689
|
if (t.kind === 'formula') return t.atoms.every((a) => termIsConcrete(a.s) && termIsConcrete(a.p) && termIsConcrete(a.o));
|
|
617
690
|
return true;
|
|
618
691
|
}
|
|
@@ -628,6 +701,7 @@ function primitive(t) {
|
|
|
628
701
|
if (t.kind === 'iri') return t.value.replace(/^:/, '');
|
|
629
702
|
if (t.kind === 'blank') return t.value;
|
|
630
703
|
if (t.kind === 'list') return t.items.map(primitive);
|
|
704
|
+
if (t.kind === 'triple') return termToN3(t);
|
|
631
705
|
if (t.kind === 'formula') return termToN3(t);
|
|
632
706
|
return undefined;
|
|
633
707
|
}
|
|
@@ -648,6 +722,7 @@ function termToN3(t) {
|
|
|
648
722
|
if (t.kind === 'var') return '?' + t.value;
|
|
649
723
|
if (t.kind === 'blank') return t.value.startsWith('_:') ? t.value : '_:' + t.value.replace(/^_+/, '');
|
|
650
724
|
if (t.kind === 'list') return '(' + t.items.map(termToN3).join(' ') + ')';
|
|
725
|
+
if (t.kind === 'triple') return '<<( ' + termToN3(t.s) + ' ' + termToN3(t.p) + ' ' + termToN3(t.o) + ' )>>';
|
|
651
726
|
if (t.kind === 'formula') return '{ ' + t.atoms.map(atomToN3).join(' . ') + ' }';
|
|
652
727
|
return String(t.value ?? t);
|
|
653
728
|
}
|
|
@@ -670,6 +745,7 @@ function resolve(term, env, seen = new Set()) {
|
|
|
670
745
|
return resolve(env[term.value], env, seen);
|
|
671
746
|
}
|
|
672
747
|
if (term.kind === 'list') return list(term.items.map((item) => resolve(item, env, seen)));
|
|
748
|
+
if (term.kind === 'triple') return { kind: 'triple', s: resolve(term.s, env), p: resolve(term.p, env), o: resolve(term.o, env) };
|
|
673
749
|
if (term.kind === 'formula') return { kind: 'formula', atoms: term.atoms.map((a) => ({ s: resolve(a.s, env), p: resolve(a.p, env), o: resolve(a.o, env) })) };
|
|
674
750
|
return term;
|
|
675
751
|
}
|
|
@@ -687,6 +763,14 @@ function unify(a, b, env) {
|
|
|
687
763
|
}
|
|
688
764
|
return out;
|
|
689
765
|
}
|
|
766
|
+
if (a.kind === 'triple' || b.kind === 'triple') {
|
|
767
|
+
if (a.kind !== 'triple' || b.kind !== 'triple') return null;
|
|
768
|
+
let out = unify(a.s, b.s, env);
|
|
769
|
+
if (!out) return null;
|
|
770
|
+
out = unify(a.p, b.p, out);
|
|
771
|
+
if (!out) return null;
|
|
772
|
+
return unify(a.o, b.o, out);
|
|
773
|
+
}
|
|
690
774
|
return deepEqual(a, b) ? env : null;
|
|
691
775
|
}
|
|
692
776
|
function bind(pattern, value, env) { return unify(pattern, value, env); }
|
|
@@ -702,6 +786,7 @@ function termIsGround(t, env) {
|
|
|
702
786
|
const r = resolve(t, env);
|
|
703
787
|
if (r.kind === 'var') return false;
|
|
704
788
|
if (r.kind === 'list') return r.items.every((item) => termIsGround(item, env));
|
|
789
|
+
if (r.kind === 'triple') return termIsGround(r.s, env) && termIsGround(r.p, env) && termIsGround(r.o, env);
|
|
705
790
|
if (r.kind === 'formula') return r.atoms.every((atom) => atomIsGround(atom, env));
|
|
706
791
|
return true;
|
|
707
792
|
}
|
|
@@ -1261,6 +1346,7 @@ function instantiate(term, env, ruleId) {
|
|
|
1261
1346
|
}
|
|
1262
1347
|
if (term.kind === 'blank') return blank('_:r' + ruleId + '_' + envSignature(env) + '_' + term.value.replace(/^_/, ''));
|
|
1263
1348
|
if (term.kind === 'list') return list(term.items.map((item) => instantiate(item, env, ruleId)));
|
|
1349
|
+
if (term.kind === 'triple') return { kind: 'triple', s: instantiate(term.s, env, ruleId), p: instantiate(term.p, env, ruleId), o: instantiate(term.o, env, ruleId) };
|
|
1264
1350
|
if (term.kind === 'formula') return { kind: 'formula', atoms: term.atoms.map((a) => ({ s: instantiate(a.s, env, ruleId), p: instantiate(a.p, env, ruleId), o: instantiate(a.o, env, ruleId) })) };
|
|
1265
1351
|
return cloneTerm(term);
|
|
1266
1352
|
}
|
|
@@ -1844,6 +1930,16 @@ function formalOutputFacts(graph, queries, rules, initialFacts) {
|
|
|
1844
1930
|
}
|
|
1845
1931
|
return out;
|
|
1846
1932
|
}
|
|
1933
|
+
function termHasTripleTerm(term) {
|
|
1934
|
+
if (!term) return false;
|
|
1935
|
+
if (term.kind === 'triple') return true;
|
|
1936
|
+
if (term.kind === 'list') return term.items.some(termHasTripleTerm);
|
|
1937
|
+
if (term.kind === 'formula') return term.atoms.some(atomHasTripleTerm);
|
|
1938
|
+
return false;
|
|
1939
|
+
}
|
|
1940
|
+
function atomHasTripleTerm(atom) { return termHasTripleTerm(atom.s) || termHasTripleTerm(atom.p) || termHasTripleTerm(atom.o); }
|
|
1941
|
+
function factsHaveTripleTerms(facts) { return (facts || []).some(atomHasTripleTerm); }
|
|
1942
|
+
function trigHasVersion12(trig) { return /^\s*(?:@version|VERSION)\s+["']1\.2["']/mi.test(String(trig || '')); }
|
|
1847
1943
|
function trigGraphBlock(label, atoms) {
|
|
1848
1944
|
const lines = [label + ' {'];
|
|
1849
1945
|
for (const atom of atoms || []) lines.push(' ' + atomToN3(atom) + ' .');
|
|
@@ -1891,7 +1987,8 @@ function formalOutputToTrig(facts, trig) {
|
|
|
1891
1987
|
const prefixes = prefixLinesFromTrig(trig);
|
|
1892
1988
|
if (state.needOutPrefix && !prefixes.some((line) => line.toLowerCase().startsWith('@prefix out:'))) prefixes.push('@prefix out: <https://example.org/see/output#> .');
|
|
1893
1989
|
const nl = String.fromCharCode(10);
|
|
1894
|
-
|
|
1990
|
+
const version = factsHaveTripleTerms(facts) ? 'VERSION "1.2"' + nl + nl : '';
|
|
1991
|
+
return version + prefixes.join(nl) + nl + nl + body.join(nl);
|
|
1895
1992
|
}
|
|
1896
1993
|
function appendFormalTrigOutput(markdown, graph, queries, rules, initialFacts, data) {
|
|
1897
1994
|
const trig = formalOutputToTrig(formalOutputFacts(graph, queries, rules, initialFacts), data && data.trig);
|
package/test/api.test.js
CHANGED
|
@@ -2539,8 +2539,94 @@ _:b a ex:Person ; ex:name "B" .
|
|
|
2539
2539
|
`,
|
|
2540
2540
|
expect: [/^:test\s+:is\s+true\s*\./m],
|
|
2541
2541
|
},
|
|
2542
|
+
|
|
2543
|
+
{
|
|
2544
|
+
name: 'RDF 1.2 triple terms require explicit RDF compatibility mode',
|
|
2545
|
+
opt: { proofComments: false },
|
|
2546
|
+
input: `VERSION "1.2"
|
|
2547
|
+
@prefix : <http://example.org/triple-terms#> .
|
|
2548
|
+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
|
2549
|
+
|
|
2550
|
+
:observation rdf:reifies <<( :sensor :reports :overheating )>> .
|
|
2551
|
+
`,
|
|
2552
|
+
expectError: true,
|
|
2553
|
+
},
|
|
2554
|
+
|
|
2555
|
+
{
|
|
2556
|
+
name: 'RDF 1.2 triple terms are accepted in RDF compatibility mode',
|
|
2557
|
+
opt: { proofComments: false, rdf: true },
|
|
2558
|
+
input: `VERSION "1.2"
|
|
2559
|
+
@prefix : <http://example.org/triple-terms#> .
|
|
2560
|
+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
|
2561
|
+
|
|
2562
|
+
:observation rdf:reifies <<( :sensor :reports :overheating )>> .
|
|
2563
|
+
:overheating :requires :inspection .
|
|
2564
|
+
|
|
2565
|
+
{
|
|
2566
|
+
?observation rdf:reifies <<( ?device :reports ?condition )>> .
|
|
2567
|
+
?condition :requires ?action .
|
|
2568
|
+
} => {
|
|
2569
|
+
?observation :entails <<( ?device :needs ?action )>> .
|
|
2570
|
+
} .
|
|
2571
|
+
`,
|
|
2572
|
+
expect: [/:observation\s+:entails\s+<<\(\s+:sensor\s+:needs\s+:inspection\s*\)>>\s*\./m],
|
|
2573
|
+
},
|
|
2574
|
+
|
|
2575
|
+
{
|
|
2576
|
+
name: 'RDF mode serializes only valid triple terms as RDF 1.2 triple terms',
|
|
2577
|
+
opt: { proofComments: false, rdf: true },
|
|
2578
|
+
input: `@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
2579
|
+
@prefix math: <http://www.w3.org/2000/10/swap/math#> .
|
|
2580
|
+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
|
2581
|
+
@prefix : <http://example.org/ns#> .
|
|
2582
|
+
|
|
2583
|
+
{
|
|
2584
|
+
(1 3) math:sum ?X .
|
|
2585
|
+
}
|
|
2586
|
+
=>
|
|
2587
|
+
{
|
|
2588
|
+
<<(?X a :Answer)>> a :Result.
|
|
2589
|
+
}.
|
|
2590
|
+
`,
|
|
2591
|
+
expect: [/\{\s*4\s+a\s+:Answer\s*\.\s*\}\s+a\s+:Result\s*\./m],
|
|
2592
|
+
notExpect: [/<<\(\s*4\s+a\s+:Answer\s*\)>>/m],
|
|
2593
|
+
},
|
|
2594
|
+
|
|
2595
|
+
{
|
|
2596
|
+
name: 'RDF dataset named graphs round-trip as TriG in RDF compatibility mode',
|
|
2597
|
+
opt: { proofComments: false, rdf: true },
|
|
2598
|
+
input: `VERSION "1.2"
|
|
2599
|
+
@prefix : <http://example.org/dataset#> .
|
|
2600
|
+
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
2601
|
+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
|
2602
|
+
|
|
2603
|
+
# Ordinary TriG default-graph triples remain ordinary N3 facts.
|
|
2604
|
+
:sensor :reports :overheating .
|
|
2605
|
+
:overheating :requires :inspection .
|
|
2606
|
+
|
|
2607
|
+
# A TriG named graph block is accepted and normalized to a graph term.
|
|
2608
|
+
:factoryDataset {
|
|
2609
|
+
:observation rdf:reifies <<( :sensor :reports :overheating )>> .
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
{
|
|
2613
|
+
:sensor :reports ?condition .
|
|
2614
|
+
?condition :requires ?action .
|
|
2615
|
+
} => {
|
|
2616
|
+
:workOrder :entails <<( :sensor :needs ?action )>> .
|
|
2617
|
+
:audit log:nameOf {
|
|
2618
|
+
:workOrder :basedOn :factoryDataset .
|
|
2619
|
+
} .
|
|
2620
|
+
} .
|
|
2621
|
+
`,
|
|
2622
|
+
expect: [
|
|
2623
|
+
/:workOrder\s+:entails\s+<<\(\s+:sensor\s+:needs\s+:inspection\s*\)>>\s*\./m,
|
|
2624
|
+
/:audit\s+\{[\s\S]*:workOrder\s+:basedOn\s+:factoryDataset\s*\.[\s\S]*\}/m,
|
|
2625
|
+
],
|
|
2626
|
+
},
|
|
2542
2627
|
];
|
|
2543
2628
|
|
|
2629
|
+
|
|
2544
2630
|
let passed = 0;
|
|
2545
2631
|
let failed = 0;
|
|
2546
2632
|
|
package/test/see.test.js
CHANGED
|
File without changes
|
package/tools/bundle.js
CHANGED
|
File without changes
|
package/tools/n3gen.js
CHANGED
|
File without changes
|