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.
Files changed (77) hide show
  1. package/HANDBOOK.md +134 -0
  2. package/dist/browser/eyeling.browser.js +454 -22
  3. package/eyeling.js +454 -22
  4. package/index.js +2 -0
  5. package/lib/cli.js +27 -10
  6. package/lib/engine.js +17 -7
  7. package/lib/lexer.js +295 -2
  8. package/lib/multisource.js +9 -2
  9. package/lib/printing.js +106 -1
  10. package/package.json +1 -1
  11. package/see/README.md +4 -0
  12. package/see/examples/_see.js +33 -2
  13. package/see/examples/age.js +27 -1
  14. package/see/examples/annotation.js +27 -1
  15. package/see/examples/backward.js +27 -1
  16. package/see/examples/backward_recursion.js +27 -1
  17. package/see/examples/bayes_diagnosis.js +27 -1
  18. package/see/examples/bayes_therapy.js +27 -1
  19. package/see/examples/bmi.js +27 -1
  20. package/see/examples/builtin_coverage.js +27 -1
  21. package/see/examples/collection.js +27 -1
  22. package/see/examples/complex.js +27 -1
  23. package/see/examples/complex_matrix_stability.js +27 -1
  24. package/see/examples/composition_of_injective_functions_is_injective.js +27 -1
  25. package/see/examples/control_system.js +27 -1
  26. package/see/examples/crypto_builtins_tests.js +27 -1
  27. package/see/examples/delfour.js +27 -1
  28. package/see/examples/digital_product_passport.js +27 -1
  29. package/see/examples/dijkstra.js +27 -1
  30. package/see/examples/dijkstra_risk_path.js +27 -1
  31. package/see/examples/doc/rdf_dataset.md +26 -0
  32. package/see/examples/doc/triple_terms.md +26 -0
  33. package/see/examples/dog.js +27 -1
  34. package/see/examples/eco_route_insight.js +27 -1
  35. package/see/examples/equals.js +27 -1
  36. package/see/examples/equivalence_classes_overlap_implies_same_class.js +27 -1
  37. package/see/examples/euler_identity.js +27 -1
  38. package/see/examples/ev_roundtrip_planner.js +27 -1
  39. package/see/examples/existential_rule.js +27 -1
  40. package/see/examples/expression_eval.js +27 -1
  41. package/see/examples/family_cousins.js +27 -1
  42. package/see/examples/fastpow.js +27 -1
  43. package/see/examples/fibonacci.js +27 -1
  44. package/see/examples/french_cities.js +27 -1
  45. package/see/examples/fundamental_theorem_arithmetic.js +27 -1
  46. package/see/examples/genetic_knapsack_selection.js +27 -1
  47. package/see/examples/goldbach_1000.js +27 -1
  48. package/see/examples/good_cobbler.js +27 -1
  49. package/see/examples/gps.js +27 -1
  50. package/see/examples/gray_code_counter.js +27 -1
  51. package/see/examples/greatest_lower_bound_uniqueness.js +27 -1
  52. package/see/examples/group_inverse_uniqueness.js +27 -1
  53. package/see/examples/hadamard_approx.js +27 -1
  54. package/see/examples/hanoi.js +27 -1
  55. package/see/examples/input/rdf_dataset.trig +34 -0
  56. package/see/examples/input/triple_terms.trig +28 -0
  57. package/see/examples/n3/rdf_dataset.n3 +34 -0
  58. package/see/examples/n3/triple_terms.n3 +23 -0
  59. package/see/examples/odrl_dpv_risk_ranked.js +27 -1
  60. package/see/examples/output/rdf_dataset.md +54 -0
  61. package/see/examples/output/triple_terms.md +53 -0
  62. package/see/examples/path_discovery.js +27 -1
  63. package/see/examples/rc_discharge_envelope.js +27 -1
  64. package/see/examples/rdf_dataset.js +1512 -0
  65. package/see/examples/rdf_message_flow.js +27 -1
  66. package/see/examples/rdf_messages.js +27 -1
  67. package/see/examples/school_placement_audit.js +27 -1
  68. package/see/examples/smoke_arithmetic.js +27 -1
  69. package/see/examples/socrates.js +27 -1
  70. package/see/examples/triple_terms.js +1442 -0
  71. package/see/examples/wind_turbine.js +27 -1
  72. package/see/examples/witch.js +27 -1
  73. package/see/see.js +101 -4
  74. package/test/api.test.js +86 -0
  75. package/test/see.test.js +0 -0
  76. package/tools/bundle.js +0 -0
  77. 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
- return prefixes.join(nl) + nl + nl + body.join(nl);
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);
@@ -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
- return prefixes.join(nl) + nl + nl + body.join(nl);
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
- return prefixes.join(nl) + nl + nl + body.join(nl);
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