eyeling 1.24.1 → 1.24.2

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 (64) hide show
  1. package/HANDBOOK.md +98 -0
  2. package/dist/browser/eyeling.browser.js +103 -0
  3. package/eyeling.js +103 -0
  4. package/lib/lexer.js +103 -0
  5. package/package.json +1 -1
  6. package/see/README.md +3 -0
  7. package/see/examples/_see.js +33 -2
  8. package/see/examples/age.js +27 -1
  9. package/see/examples/annotation.js +27 -1
  10. package/see/examples/backward.js +27 -1
  11. package/see/examples/backward_recursion.js +27 -1
  12. package/see/examples/bayes_diagnosis.js +27 -1
  13. package/see/examples/bayes_therapy.js +27 -1
  14. package/see/examples/bmi.js +27 -1
  15. package/see/examples/builtin_coverage.js +27 -1
  16. package/see/examples/collection.js +27 -1
  17. package/see/examples/complex.js +27 -1
  18. package/see/examples/complex_matrix_stability.js +27 -1
  19. package/see/examples/composition_of_injective_functions_is_injective.js +27 -1
  20. package/see/examples/control_system.js +27 -1
  21. package/see/examples/crypto_builtins_tests.js +27 -1
  22. package/see/examples/delfour.js +27 -1
  23. package/see/examples/digital_product_passport.js +27 -1
  24. package/see/examples/dijkstra.js +27 -1
  25. package/see/examples/dijkstra_risk_path.js +27 -1
  26. package/see/examples/doc/triple_terms.md +26 -0
  27. package/see/examples/dog.js +27 -1
  28. package/see/examples/eco_route_insight.js +27 -1
  29. package/see/examples/equals.js +27 -1
  30. package/see/examples/equivalence_classes_overlap_implies_same_class.js +27 -1
  31. package/see/examples/euler_identity.js +27 -1
  32. package/see/examples/ev_roundtrip_planner.js +27 -1
  33. package/see/examples/existential_rule.js +27 -1
  34. package/see/examples/expression_eval.js +27 -1
  35. package/see/examples/family_cousins.js +27 -1
  36. package/see/examples/fastpow.js +27 -1
  37. package/see/examples/fibonacci.js +27 -1
  38. package/see/examples/french_cities.js +27 -1
  39. package/see/examples/fundamental_theorem_arithmetic.js +27 -1
  40. package/see/examples/genetic_knapsack_selection.js +27 -1
  41. package/see/examples/goldbach_1000.js +27 -1
  42. package/see/examples/good_cobbler.js +27 -1
  43. package/see/examples/gps.js +27 -1
  44. package/see/examples/gray_code_counter.js +27 -1
  45. package/see/examples/greatest_lower_bound_uniqueness.js +27 -1
  46. package/see/examples/group_inverse_uniqueness.js +27 -1
  47. package/see/examples/hadamard_approx.js +27 -1
  48. package/see/examples/hanoi.js +27 -1
  49. package/see/examples/input/triple_terms.trig +28 -0
  50. package/see/examples/n3/triple_terms.n3 +23 -0
  51. package/see/examples/odrl_dpv_risk_ranked.js +27 -1
  52. package/see/examples/output/triple_terms.md +53 -0
  53. package/see/examples/path_discovery.js +27 -1
  54. package/see/examples/rc_discharge_envelope.js +27 -1
  55. package/see/examples/rdf_message_flow.js +27 -1
  56. package/see/examples/rdf_messages.js +27 -1
  57. package/see/examples/school_placement_audit.js +27 -1
  58. package/see/examples/smoke_arithmetic.js +27 -1
  59. package/see/examples/socrates.js +27 -1
  60. package/see/examples/triple_terms.js +1442 -0
  61. package/see/examples/wind_turbine.js +27 -1
  62. package/see/examples/witch.js +27 -1
  63. package/see/see.js +75 -2
  64. package/test/api.test.js +20 -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) };
@@ -300,6 +311,11 @@ class Parser {
300
311
  // Prefix declaration is irrelevant after QName compaction; skip until final dot.
301
312
  while (!this.eof() && !this.accept('.')) this.next();
302
313
  }
314
+ skipVersion() {
315
+ this.expect('VERSION');
316
+ if (this.peek('string')) this.next();
317
+ this.accept('.');
318
+ }
303
319
  parseProgram() {
304
320
  const facts = [],
305
321
  rules = [],
@@ -318,6 +334,10 @@ class Parser {
318
334
  if (m) prefixes[(m[1] || ':').replace(/:$/, '')] = m[2];
319
335
  continue;
320
336
  }
337
+ if (this.peek('VERSION')) {
338
+ this.skipVersion();
339
+ continue;
340
+ }
321
341
  if (this.peek('{')) {
322
342
  const lhs = this.parseFormula('body');
323
343
  if (this.accept('=>')) {
@@ -417,6 +437,13 @@ class Parser {
417
437
  }
418
438
  if (tok.type === 'number' || tok.type === 'boolean') return L(tok.value);
419
439
  if (tok.type === 'iri' || tok.type === 'qname') return I(tok.value);
440
+ if (tok.type === '<<(') {
441
+ const subject = this.parseTerm(mode, sink);
442
+ const predicate = this.parsePredicate();
443
+ const object = this.parseTerm(mode, sink);
444
+ this.expect(')>>');
445
+ return { kind: 'triple', s: subject, p: predicate, o: object };
446
+ }
420
447
  if (tok.type === '(') {
421
448
  const items = [];
422
449
  while (!this.accept(')')) items.push(this.parseTerm(mode, sink));
@@ -470,6 +497,7 @@ function termToJsComment(term) {
470
497
  if (term.kind === 'var') return `?${term.value}`;
471
498
  if (term.kind === 'blank') return term.value;
472
499
  if (term.kind === 'list') return `(${term.items.map(termToJsComment).join(' ')})`;
500
+ if (term.kind === 'triple') return `<<( ${termToJsComment(term.s)} ${termToJsComment(term.p)} ${termToJsComment(term.o)} )>>`;
473
501
  if (term.kind === 'formula') return `{ ${term.atoms.map(atomToComment).join(' . ')} }`;
474
502
  return String(term.value ?? term);
475
503
  }
@@ -524,12 +552,30 @@ function inputTermToN3(term) {
524
552
  if (term.kind === 'var') return '?' + term.value;
525
553
  if (term.kind === 'blank') return term.value.startsWith('_:') ? term.value : '_:' + term.value.replace(/^_+/, '');
526
554
  if (term.kind === 'list') return '(' + term.items.map(inputTermToN3).join(' ') + ')';
555
+ if (term.kind === 'triple') return '<<( ' + inputTermToN3(term.s) + ' ' + inputTermToN3(term.p) + ' ' + inputTermToN3(term.o) + ' )>>';
527
556
  if (term.kind === 'formula') return '{ ' + term.atoms.map(inputAtomToN3).join(' . ') + ' }';
528
557
  return String(term.value ?? term);
529
558
  }
530
559
  function inputAtomToN3(atom) {
531
560
  return inputTermToN3(atom.s) + ' ' + inputTermToN3(atom.p) + ' ' + inputTermToN3(atom.o);
532
561
  }
562
+ function inputTermHasTripleTerm(term) {
563
+ if (!term) return false;
564
+ if (term.kind === 'triple') return true;
565
+ if (term.kind === 'list') return term.items.some(inputTermHasTripleTerm);
566
+ if (term.kind === 'formula') return term.atoms.some(inputAtomHasTripleTerm);
567
+ return false;
568
+ }
569
+ function inputAtomHasTripleTerm(atom) {
570
+ return inputTermHasTripleTerm(atom.s) || inputTermHasTripleTerm(atom.p) || inputTermHasTripleTerm(atom.o);
571
+ }
572
+ function programHasTripleTerms(program) {
573
+ return [
574
+ ...(program.facts || []),
575
+ ...(program.rules || []).flatMap((r) => [...(r.body || []), ...(r.head || [])]),
576
+ ...(program.queries || []).flatMap((q) => [...(q.premise || []), ...(q.conclusion || [])]),
577
+ ].some(inputAtomHasTripleTerm);
578
+ }
533
579
  function formulaBlock(label, atoms) {
534
580
  const lines = [label + ' {'];
535
581
  for (const atom of atoms) lines.push(' ' + inputAtomToN3(atom) + ' .');
@@ -584,6 +630,7 @@ function generateInputTrig(n3Path, name, title, header, stats, program) {
584
630
  '}',
585
631
  ].join('\n');
586
632
  const sections = [
633
+ ...(programHasTripleTerms(program) ? ['VERSION "1.2"', ''] : []),
587
634
  ...prefixLines(program.prefixes),
588
635
  '',
589
636
  '# Formal SEE input evidence in RDF 1.2 TriG.',
@@ -604,6 +651,7 @@ const crypto = require('crypto');
604
651
 
605
652
  function canonical(term) {
606
653
  if (term.kind === 'list') return ['list', term.items.map(canonical)];
654
+ if (term.kind === 'triple') return ['triple', canonical(term.s), canonical(term.p), canonical(term.o)];
607
655
  if (term.kind === 'formula') return ['formula', term.atoms.map((a) => [canonical(a.s), canonical(a.p), canonical(a.o)])];
608
656
  return [term.kind, term.value];
609
657
  }
@@ -613,6 +661,7 @@ function compoundIndexKey() { return Array.from(arguments).map(termIndexKey).joi
613
661
  function termIsConcrete(t) {
614
662
  if (!t || t.kind === 'var') return false;
615
663
  if (t.kind === 'list') return t.items.every(termIsConcrete);
664
+ if (t.kind === 'triple') return termIsConcrete(t.s) && termIsConcrete(t.p) && termIsConcrete(t.o);
616
665
  if (t.kind === 'formula') return t.atoms.every((a) => termIsConcrete(a.s) && termIsConcrete(a.p) && termIsConcrete(a.o));
617
666
  return true;
618
667
  }
@@ -628,6 +677,7 @@ function primitive(t) {
628
677
  if (t.kind === 'iri') return t.value.replace(/^:/, '');
629
678
  if (t.kind === 'blank') return t.value;
630
679
  if (t.kind === 'list') return t.items.map(primitive);
680
+ if (t.kind === 'triple') return termToN3(t);
631
681
  if (t.kind === 'formula') return termToN3(t);
632
682
  return undefined;
633
683
  }
@@ -648,6 +698,7 @@ function termToN3(t) {
648
698
  if (t.kind === 'var') return '?' + t.value;
649
699
  if (t.kind === 'blank') return t.value.startsWith('_:') ? t.value : '_:' + t.value.replace(/^_+/, '');
650
700
  if (t.kind === 'list') return '(' + t.items.map(termToN3).join(' ') + ')';
701
+ if (t.kind === 'triple') return '<<( ' + termToN3(t.s) + ' ' + termToN3(t.p) + ' ' + termToN3(t.o) + ' )>>';
651
702
  if (t.kind === 'formula') return '{ ' + t.atoms.map(atomToN3).join(' . ') + ' }';
652
703
  return String(t.value ?? t);
653
704
  }
@@ -670,6 +721,7 @@ function resolve(term, env, seen = new Set()) {
670
721
  return resolve(env[term.value], env, seen);
671
722
  }
672
723
  if (term.kind === 'list') return list(term.items.map((item) => resolve(item, env, seen)));
724
+ if (term.kind === 'triple') return { kind: 'triple', s: resolve(term.s, env), p: resolve(term.p, env), o: resolve(term.o, env) };
673
725
  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
726
  return term;
675
727
  }
@@ -687,6 +739,14 @@ function unify(a, b, env) {
687
739
  }
688
740
  return out;
689
741
  }
742
+ if (a.kind === 'triple' || b.kind === 'triple') {
743
+ if (a.kind !== 'triple' || b.kind !== 'triple') return null;
744
+ let out = unify(a.s, b.s, env);
745
+ if (!out) return null;
746
+ out = unify(a.p, b.p, out);
747
+ if (!out) return null;
748
+ return unify(a.o, b.o, out);
749
+ }
690
750
  return deepEqual(a, b) ? env : null;
691
751
  }
692
752
  function bind(pattern, value, env) { return unify(pattern, value, env); }
@@ -702,6 +762,7 @@ function termIsGround(t, env) {
702
762
  const r = resolve(t, env);
703
763
  if (r.kind === 'var') return false;
704
764
  if (r.kind === 'list') return r.items.every((item) => termIsGround(item, env));
765
+ if (r.kind === 'triple') return termIsGround(r.s, env) && termIsGround(r.p, env) && termIsGround(r.o, env);
705
766
  if (r.kind === 'formula') return r.atoms.every((atom) => atomIsGround(atom, env));
706
767
  return true;
707
768
  }
@@ -1261,6 +1322,7 @@ function instantiate(term, env, ruleId) {
1261
1322
  }
1262
1323
  if (term.kind === 'blank') return blank('_:r' + ruleId + '_' + envSignature(env) + '_' + term.value.replace(/^_/, ''));
1263
1324
  if (term.kind === 'list') return list(term.items.map((item) => instantiate(item, env, ruleId)));
1325
+ 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
1326
  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
1327
  return cloneTerm(term);
1266
1328
  }
@@ -1844,6 +1906,16 @@ function formalOutputFacts(graph, queries, rules, initialFacts) {
1844
1906
  }
1845
1907
  return out;
1846
1908
  }
1909
+ function termHasTripleTerm(term) {
1910
+ if (!term) return false;
1911
+ if (term.kind === 'triple') return true;
1912
+ if (term.kind === 'list') return term.items.some(termHasTripleTerm);
1913
+ if (term.kind === 'formula') return term.atoms.some(atomHasTripleTerm);
1914
+ return false;
1915
+ }
1916
+ function atomHasTripleTerm(atom) { return termHasTripleTerm(atom.s) || termHasTripleTerm(atom.p) || termHasTripleTerm(atom.o); }
1917
+ function factsHaveTripleTerms(facts) { return (facts || []).some(atomHasTripleTerm); }
1918
+ function trigHasVersion12(trig) { return /^\s*(?:@version|VERSION)\s+["']1\.2["']/mi.test(String(trig || '')); }
1847
1919
  function trigGraphBlock(label, atoms) {
1848
1920
  const lines = [label + ' {'];
1849
1921
  for (const atom of atoms || []) lines.push(' ' + atomToN3(atom) + ' .');
@@ -1891,7 +1963,8 @@ function formalOutputToTrig(facts, trig) {
1891
1963
  const prefixes = prefixLinesFromTrig(trig);
1892
1964
  if (state.needOutPrefix && !prefixes.some((line) => line.toLowerCase().startsWith('@prefix out:'))) prefixes.push('@prefix out: <https://example.org/see/output#> .');
1893
1965
  const nl = String.fromCharCode(10);
1894
- return prefixes.join(nl) + nl + nl + body.join(nl);
1966
+ const version = factsHaveTripleTerms(facts) ? 'VERSION "1.2"' + nl + nl : '';
1967
+ return version + prefixes.join(nl) + nl + nl + body.join(nl);
1895
1968
  }
1896
1969
  function appendFormalTrigOutput(markdown, graph, queries, rules, initialFacts, data) {
1897
1970
  const trig = formalOutputToTrig(formalOutputFacts(graph, queries, rules, initialFacts), data && data.trig);
package/test/api.test.js CHANGED
@@ -2539,6 +2539,26 @@ _: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 are accepted as N3 singleton graph terms',
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
+ :overheating :requires :inspection .
2552
+
2553
+ {
2554
+ ?observation rdf:reifies <<( ?device :reports ?condition )>> .
2555
+ ?condition :requires ?action .
2556
+ } => {
2557
+ ?observation :entails <<( ?device :needs ?action )>> .
2558
+ } .
2559
+ `,
2560
+ expect: [/:observation\s+:entails\s+\{\s+:sensor\s+:needs\s+:inspection\s*\.\s*\}\s*\./m],
2561
+ },
2542
2562
  ];
2543
2563
 
2544
2564
  let passed = 0;