eyeling 1.24.0 → 1.24.1

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 (154) hide show
  1. package/README.md +4 -4
  2. package/package.json +1 -1
  3. package/see/README.md +10 -11
  4. package/see/examples/age.js +10 -10
  5. package/see/examples/annotation.js +10 -10
  6. package/see/examples/backward.js +10 -10
  7. package/see/examples/backward_recursion.js +10 -10
  8. package/see/examples/bayes_diagnosis.js +10 -10
  9. package/see/examples/bayes_therapy.js +10 -10
  10. package/see/examples/bmi.js +10 -10
  11. package/see/examples/builtin_coverage.js +10 -10
  12. package/see/examples/collection.js +10 -10
  13. package/see/examples/complex.js +10 -10
  14. package/see/examples/complex_matrix_stability.js +10 -10
  15. package/see/examples/composition_of_injective_functions_is_injective.js +10 -10
  16. package/see/examples/control_system.js +10 -10
  17. package/see/examples/crypto_builtins_tests.js +10 -10
  18. package/see/examples/delfour.js +10 -10
  19. package/see/examples/digital_product_passport.js +10 -10
  20. package/see/examples/dijkstra.js +10 -10
  21. package/see/examples/dijkstra_risk_path.js +10 -10
  22. package/see/examples/doc/age.md +1 -1
  23. package/see/examples/doc/annotation.md +1 -1
  24. package/see/examples/doc/backward.md +1 -1
  25. package/see/examples/doc/backward_recursion.md +1 -1
  26. package/see/examples/doc/bayes_diagnosis.md +1 -1
  27. package/see/examples/doc/bayes_therapy.md +1 -1
  28. package/see/examples/doc/bmi.md +1 -1
  29. package/see/examples/doc/builtin_coverage.md +1 -1
  30. package/see/examples/doc/collection.md +1 -1
  31. package/see/examples/doc/complex.md +1 -1
  32. package/see/examples/doc/complex_matrix_stability.md +1 -1
  33. package/see/examples/doc/composition_of_injective_functions_is_injective.md +1 -1
  34. package/see/examples/doc/control_system.md +1 -1
  35. package/see/examples/doc/crypto_builtins_tests.md +1 -1
  36. package/see/examples/doc/delfour.md +1 -1
  37. package/see/examples/doc/digital_product_passport.md +1 -1
  38. package/see/examples/doc/dijkstra.md +1 -1
  39. package/see/examples/doc/dijkstra_risk_path.md +1 -1
  40. package/see/examples/doc/dog.md +1 -1
  41. package/see/examples/doc/eco_route_insight.md +1 -1
  42. package/see/examples/doc/equals.md +1 -1
  43. package/see/examples/doc/equivalence_classes_overlap_implies_same_class.md +1 -1
  44. package/see/examples/doc/euler_identity.md +1 -1
  45. package/see/examples/doc/ev_roundtrip_planner.md +1 -1
  46. package/see/examples/doc/existential_rule.md +1 -1
  47. package/see/examples/doc/expression_eval.md +1 -1
  48. package/see/examples/doc/family_cousins.md +1 -1
  49. package/see/examples/doc/fastpow.md +1 -1
  50. package/see/examples/doc/fibonacci.md +1 -1
  51. package/see/examples/doc/french_cities.md +1 -1
  52. package/see/examples/doc/fundamental_theorem_arithmetic.md +1 -1
  53. package/see/examples/doc/genetic_knapsack_selection.md +1 -1
  54. package/see/examples/doc/goldbach_1000.md +1 -1
  55. package/see/examples/doc/good_cobbler.md +1 -1
  56. package/see/examples/doc/gps.md +1 -1
  57. package/see/examples/doc/gray_code_counter.md +1 -1
  58. package/see/examples/doc/greatest_lower_bound_uniqueness.md +1 -1
  59. package/see/examples/doc/group_inverse_uniqueness.md +1 -1
  60. package/see/examples/doc/hadamard_approx.md +1 -1
  61. package/see/examples/doc/hanoi.md +1 -1
  62. package/see/examples/doc/odrl_dpv_risk_ranked.md +1 -1
  63. package/see/examples/doc/path_discovery.md +1 -1
  64. package/see/examples/doc/rc_discharge_envelope.md +1 -1
  65. package/see/examples/doc/rdf_message_flow.md +1 -1
  66. package/see/examples/doc/rdf_messages.md +1 -1
  67. package/see/examples/doc/school_placement_audit.md +1 -1
  68. package/see/examples/doc/smoke_arithmetic.md +1 -1
  69. package/see/examples/doc/socrates.md +1 -1
  70. package/see/examples/doc/wind_turbine.md +1 -1
  71. package/see/examples/doc/witch.md +1 -1
  72. package/see/examples/dog.js +10 -10
  73. package/see/examples/eco_route_insight.js +10 -10
  74. package/see/examples/equals.js +10 -10
  75. package/see/examples/equivalence_classes_overlap_implies_same_class.js +10 -10
  76. package/see/examples/euler_identity.js +10 -10
  77. package/see/examples/ev_roundtrip_planner.js +10 -10
  78. package/see/examples/existential_rule.js +10 -10
  79. package/see/examples/expression_eval.js +10 -10
  80. package/see/examples/family_cousins.js +10 -10
  81. package/see/examples/fastpow.js +10 -10
  82. package/see/examples/fibonacci.js +10 -10
  83. package/see/examples/french_cities.js +10 -10
  84. package/see/examples/fundamental_theorem_arithmetic.js +10 -10
  85. package/see/examples/genetic_knapsack_selection.js +10 -10
  86. package/see/examples/goldbach_1000.js +10 -10
  87. package/see/examples/good_cobbler.js +10 -10
  88. package/see/examples/gps.js +10 -10
  89. package/see/examples/gray_code_counter.js +10 -10
  90. package/see/examples/greatest_lower_bound_uniqueness.js +10 -10
  91. package/see/examples/group_inverse_uniqueness.js +10 -10
  92. package/see/examples/hadamard_approx.js +10 -10
  93. package/see/examples/hanoi.js +10 -10
  94. package/see/examples/odrl_dpv_risk_ranked.js +10 -10
  95. package/see/examples/output/age.md +3 -3
  96. package/see/examples/output/annotation.md +4 -4
  97. package/see/examples/output/backward.md +3 -3
  98. package/see/examples/output/backward_recursion.md +3 -3
  99. package/see/examples/output/bayes_diagnosis.md +1 -1
  100. package/see/examples/output/bayes_therapy.md +1 -1
  101. package/see/examples/output/bmi.md +1 -1
  102. package/see/examples/output/builtin_coverage.md +3 -3
  103. package/see/examples/output/collection.md +3 -3
  104. package/see/examples/output/complex.md +4 -4
  105. package/see/examples/output/complex_matrix_stability.md +1 -1
  106. package/see/examples/output/composition_of_injective_functions_is_injective.md +3 -3
  107. package/see/examples/output/control_system.md +3 -3
  108. package/see/examples/output/crypto_builtins_tests.md +3 -3
  109. package/see/examples/output/delfour.md +1 -1
  110. package/see/examples/output/digital_product_passport.md +1 -1
  111. package/see/examples/output/dijkstra.md +3 -3
  112. package/see/examples/output/dijkstra_risk_path.md +1 -1
  113. package/see/examples/output/dog.md +3 -3
  114. package/see/examples/output/eco_route_insight.md +1 -1
  115. package/see/examples/output/equals.md +3 -3
  116. package/see/examples/output/equivalence_classes_overlap_implies_same_class.md +3 -3
  117. package/see/examples/output/euler_identity.md +3 -3
  118. package/see/examples/output/ev_roundtrip_planner.md +1 -1
  119. package/see/examples/output/existential_rule.md +3 -3
  120. package/see/examples/output/expression_eval.md +3 -3
  121. package/see/examples/output/family_cousins.md +3 -3
  122. package/see/examples/output/fastpow.md +1 -1
  123. package/see/examples/output/fibonacci.md +1 -1
  124. package/see/examples/output/french_cities.md +3 -3
  125. package/see/examples/output/fundamental_theorem_arithmetic.md +1 -1
  126. package/see/examples/output/genetic_knapsack_selection.md +1 -1
  127. package/see/examples/output/goldbach_1000.md +1 -1
  128. package/see/examples/output/good_cobbler.md +4 -4
  129. package/see/examples/output/gps.md +1 -1
  130. package/see/examples/output/gray_code_counter.md +1 -1
  131. package/see/examples/output/greatest_lower_bound_uniqueness.md +3 -3
  132. package/see/examples/output/group_inverse_uniqueness.md +3 -3
  133. package/see/examples/output/hadamard_approx.md +3 -3
  134. package/see/examples/output/hanoi.md +3 -3
  135. package/see/examples/output/odrl_dpv_risk_ranked.md +3 -3
  136. package/see/examples/output/path_discovery.md +3 -3
  137. package/see/examples/output/rc_discharge_envelope.md +1 -1
  138. package/see/examples/output/rdf_message_flow.md +1 -1
  139. package/see/examples/output/rdf_messages.md +1 -1
  140. package/see/examples/output/school_placement_audit.md +1 -1
  141. package/see/examples/output/smoke_arithmetic.md +1 -1
  142. package/see/examples/output/socrates.md +3 -3
  143. package/see/examples/output/wind_turbine.md +1 -1
  144. package/see/examples/output/witch.md +3 -3
  145. package/see/examples/path_discovery.js +10 -10
  146. package/see/examples/rc_discharge_envelope.js +10 -10
  147. package/see/examples/rdf_message_flow.js +10 -10
  148. package/see/examples/rdf_messages.js +10 -10
  149. package/see/examples/school_placement_audit.js +10 -10
  150. package/see/examples/smoke_arithmetic.js +10 -10
  151. package/see/examples/socrates.js +10 -10
  152. package/see/examples/wind_turbine.js +10 -10
  153. package/see/examples/witch.js +10 -10
  154. package/see/see.js +381 -93
package/see/see.js CHANGED
@@ -1,11 +1,23 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
+ // SEE, Specialized Eyeling Executables, compiles a small, practical Notation3
5
+ // subset into standalone JavaScript examples. The compiler runs once at
6
+ // generation time: it extracts source facts into formal TriG evidence and bakes
7
+ // the supported rules, queries, and fuses into the generated runner.
8
+ //
9
+ // The generated examples intentionally do not call Eyeling or another reasoner
10
+ // at runtime. They read their .trig evidence directly and perform a local
11
+ // fixpoint derivation, which makes the resulting programs easy to inspect,
12
+ // snapshot, and publish as self-contained executable explanations.
13
+
4
14
  const crypto = require('crypto');
5
15
  const fs = require('fs');
6
16
  const path = require('path');
7
17
  const { spawnSync } = require('child_process');
8
18
 
19
+ // All SEE-owned artefacts stay below /see/examples so the directory can be
20
+ // generated, tested, and documented from the eyeling repository root.
9
21
  const ROOT = __dirname;
10
22
  const EXAMPLES_DIR = path.join(ROOT, 'examples');
11
23
  const INPUT_DIR = path.join(EXAMPLES_DIR, 'input');
@@ -32,8 +44,12 @@ rule/query/fuse subset into JavaScript, and the resulting examples/<name>.js
32
44
  loads the TriG evidence directly and performs the forward derivation itself.`;
33
45
  }
34
46
 
35
- function ensureDir(dir) { fs.mkdirSync(dir, { recursive: true }); }
36
- function readText(file) { return fs.readFileSync(file, 'utf8'); }
47
+ function ensureDir(dir) {
48
+ fs.mkdirSync(dir, { recursive: true });
49
+ }
50
+ function readText(file) {
51
+ return fs.readFileSync(file, 'utf8');
52
+ }
37
53
  function writeText(file, text, force) {
38
54
  if (!force && fs.existsSync(file)) {
39
55
  throw new Error(`${path.relative(ROOT, file)} already exists; pass --force to overwrite`);
@@ -41,8 +57,12 @@ function writeText(file, text, force) {
41
57
  ensureDir(path.dirname(file));
42
58
  fs.writeFileSync(file, text, 'utf8');
43
59
  }
44
- function sha256(text) { return crypto.createHash('sha256').update(text, 'utf8').digest('hex'); }
45
- function js(value) { return JSON.stringify(value, null, 2); }
60
+ function sha256(text) {
61
+ return crypto.createHash('sha256').update(text, 'utf8').digest('hex');
62
+ }
63
+ function js(value) {
64
+ return JSON.stringify(value, null, 2);
65
+ }
46
66
 
47
67
  function slugify(value) {
48
68
  const base = String(value || 'example')
@@ -54,38 +74,73 @@ function slugify(value) {
54
74
  return base || 'example';
55
75
  }
56
76
  function titleFromSlug(slug) {
57
- return slug.split(/[_-]+/).filter(Boolean)
58
- .map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(' ');
77
+ return slug
78
+ .split(/[_-]+/)
79
+ .filter(Boolean)
80
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
81
+ .join(' ');
59
82
  }
60
83
 
61
- function stripComment(line) { return line.replace(/^\s*#\s?/, '').trimEnd(); }
62
- function isSeparator(line) { const t = line.trim(); return /^[-=]{3,}$/.test(t) || t === ''; }
84
+ // A leading comment block in each source .n3 file becomes the public example
85
+ // title and description used in generated documentation and metadata.
86
+ function stripComment(line) {
87
+ return line.replace(/^\s*#\s?/, '').trimEnd();
88
+ }
89
+ function isSeparator(line) {
90
+ const t = line.trim();
91
+ return /^[-=]{3,}$/.test(t) || t === '';
92
+ }
63
93
  function parseHeader(n3, fallbackTitle) {
64
94
  const raw = [];
65
95
  for (const line of n3.split(/\r?\n/)) {
66
- if (/^\s*#/.test(line)) { raw.push(stripComment(line)); continue; }
67
- if (/^\s*$/.test(line) && raw.length) { raw.push(''); continue; }
96
+ if (/^\s*#/.test(line)) {
97
+ raw.push(stripComment(line));
98
+ continue;
99
+ }
100
+ if (/^\s*$/.test(line) && raw.length) {
101
+ raw.push('');
102
+ continue;
103
+ }
68
104
  if (/^\s*$/.test(line)) continue;
69
105
  break;
70
106
  }
71
107
  const useful = raw.map((line) => line.trim()).filter((line) => !isSeparator(line));
72
- return { title: useful[0] || fallbackTitle, description: useful.slice(1).join('\n').replace(/\n{3,}/g, '\n\n').trim(), headerComments: raw };
108
+ return {
109
+ title: useful[0] || fallbackTitle,
110
+ description: useful
111
+ .slice(1)
112
+ .join('\n')
113
+ .replace(/\n{3,}/g, '\n\n')
114
+ .trim(),
115
+ headerComments: raw,
116
+ };
73
117
  }
74
118
 
75
119
  function removeComments(n3) {
76
- return n3.split(/\r?\n/).map((line) => {
77
- let inString = false, escaped = false, inIri = false;
78
- for (let i = 0; i < line.length; i += 1) {
79
- const ch = line[i];
80
- if (escaped) { escaped = false; continue; }
81
- if (ch === '\\' && inString) { escaped = true; continue; }
82
- if (ch === '"' && !inIri) inString = !inString;
83
- if (ch === '<' && !inString) inIri = true;
84
- if (ch === '>' && inIri) inIri = false;
85
- if (ch === '#' && !inString && !inIri) return line.slice(0, i);
86
- }
87
- return line;
88
- }).join('\n');
120
+ return n3
121
+ .split(/\r?\n/)
122
+ .map((line) => {
123
+ let inString = false,
124
+ escaped = false,
125
+ inIri = false;
126
+ for (let i = 0; i < line.length; i += 1) {
127
+ const ch = line[i];
128
+ if (escaped) {
129
+ escaped = false;
130
+ continue;
131
+ }
132
+ if (ch === '\\' && inString) {
133
+ escaped = true;
134
+ continue;
135
+ }
136
+ if (ch === '"' && !inIri) inString = !inString;
137
+ if (ch === '<' && !inString) inIri = true;
138
+ if (ch === '>' && inIri) inIri = false;
139
+ if (ch === '#' && !inString && !inIri) return line.slice(0, i);
140
+ }
141
+ return line;
142
+ })
143
+ .join('\n');
89
144
  }
90
145
 
91
146
  function decodeEscapes(value) {
@@ -104,13 +159,31 @@ function decodeEscapes(value) {
104
159
  });
105
160
  }
106
161
 
107
- function t(kind, value) { return { kind, value }; }
108
- function I(value) { return t('iri', value); }
109
- function V(value) { return t('var', value); }
110
- function L(value) { return t('lit', value); }
111
- function List(items) { return { kind: 'list', items }; }
112
- function Blank(id) { return { kind: 'blank', value: id }; }
162
+ // Internal terms use a tiny AST shared by the compiler and the generated
163
+ // runtime. Variables are stored without the leading '?'; IRIs and compact
164
+ // QNames are preserved as source-facing strings for readable snapshots.
165
+ function t(kind, value) {
166
+ return { kind, value };
167
+ }
168
+ function I(value) {
169
+ return t('iri', value);
170
+ }
171
+ function V(value) {
172
+ return t('var', value);
173
+ }
174
+ function L(value) {
175
+ return t('lit', value);
176
+ }
177
+ function List(items) {
178
+ return { kind: 'list', items };
179
+ }
180
+ function Blank(id) {
181
+ return { kind: 'blank', value: id };
182
+ }
113
183
 
184
+ // This tokenizer/parser is deliberately smaller than a complete N3 parser. It
185
+ // accepts the SEE example subset: triples, lists, blank-node property lists,
186
+ // quoted formulas, implication arrows, variables, literals, and prefix lines.
114
187
  function tokenize(source) {
115
188
  const s = removeComments(source);
116
189
  const tokens = [];
@@ -119,9 +192,14 @@ function tokenize(source) {
119
192
  const one = new Set(['{', '}', '[', ']', '(', ')', ';', ',', '.']);
120
193
  while (i < s.length) {
121
194
  const ch = s[i];
122
- if (isWs(ch)) { i += 1; continue; }
195
+ if (isWs(ch)) {
196
+ i += 1;
197
+ continue;
198
+ }
123
199
  if (s.startsWith('=>', i) || s.startsWith('<=', i) || s.startsWith('^^', i)) {
124
- tokens.push({ type: s.slice(i, i + 2), value: s.slice(i, i + 2) }); i += 2; continue;
200
+ tokens.push({ type: s.slice(i, i + 2), value: s.slice(i, i + 2) });
201
+ i += 2;
202
+ continue;
125
203
  }
126
204
  if (/^[+-]?(?:\d+\.\d*|\d*\.\d+|\d+)(?:[eE][+-]?\d+)?/.test(s.slice(i)) && /[+\-0-9.]/.test(ch)) {
127
205
  const m = s.slice(i).match(/^[+-]?(?:\d+\.\d*|\d*\.\d+|\d+)(?:[eE][+-]?\d+)?/)[0];
@@ -130,13 +208,26 @@ function tokenize(source) {
130
208
  i += m.length;
131
209
  continue;
132
210
  }
133
- if (one.has(ch)) { tokens.push({ type: ch, value: ch }); i += 1; continue; }
211
+ if (one.has(ch)) {
212
+ tokens.push({ type: ch, value: ch });
213
+ i += 1;
214
+ continue;
215
+ }
134
216
  if (ch === '"') {
135
- let out = '', escaped = false; i += 1;
217
+ let out = '',
218
+ escaped = false;
219
+ i += 1;
136
220
  while (i < s.length) {
137
221
  const c = s[i++];
138
- if (escaped) { out += `\\${c}`; escaped = false; continue; }
139
- if (c === '\\') { escaped = true; continue; }
222
+ if (escaped) {
223
+ out += `\\${c}`;
224
+ escaped = false;
225
+ continue;
226
+ }
227
+ if (c === '\\') {
228
+ escaped = true;
229
+ continue;
230
+ }
140
231
  if (c === '"') break;
141
232
  out += c;
142
233
  }
@@ -144,7 +235,8 @@ function tokenize(source) {
144
235
  continue;
145
236
  }
146
237
  if (ch === '<') {
147
- let out = ''; i += 1;
238
+ let out = '';
239
+ i += 1;
148
240
  while (i < s.length && s[i] !== '>') out += s[i++];
149
241
  if (s[i] !== '>') throw new Error('Unterminated IRI');
150
242
  i += 1;
@@ -167,7 +259,8 @@ function classifyToken(raw) {
167
259
  if (raw.startsWith('?')) return { type: 'var', value: raw.slice(1) };
168
260
  if (/^(true|false)$/i.test(raw)) return { type: 'boolean', value: /^true$/i.test(raw) };
169
261
  if (/^[+-]?\d+$/.test(raw)) return { type: 'number', value: Number.parseInt(raw, 10) };
170
- if (/^[+-]?(?:\d+\.\d*|\d*\.\d+)(?:[eE][+-]?\d+)?$/.test(raw) || /^[+-]?\d+[eE][+-]?\d+$/.test(raw)) return { type: 'number', value: Number(raw) };
262
+ if (/^[+-]?(?:\d+\.\d*|\d*\.\d+)(?:[eE][+-]?\d+)?$/.test(raw) || /^[+-]?\d+[eE][+-]?\d+$/.test(raw))
263
+ return { type: 'number', value: Number(raw) };
171
264
  return { type: 'qname', value: raw };
172
265
  }
173
266
 
@@ -177,7 +270,9 @@ class Parser {
177
270
  this.pos = 0;
178
271
  this.blankCounter = 0;
179
272
  }
180
- eof() { return this.pos >= this.tokens.length; }
273
+ eof() {
274
+ return this.pos >= this.tokens.length;
275
+ }
181
276
  peek(value = undefined) {
182
277
  const tok = this.tokens[this.pos];
183
278
  if (value === undefined) return tok;
@@ -187,26 +282,38 @@ class Parser {
187
282
  if (this.eof()) throw new Error('Unexpected end of input');
188
283
  return this.tokens[this.pos++];
189
284
  }
190
- accept(type) { if (this.peek(type)) return this.next(); return null; }
285
+ accept(type) {
286
+ if (this.peek(type)) return this.next();
287
+ return null;
288
+ }
191
289
  expect(type) {
192
290
  const tok = this.next();
193
291
  if (tok.type !== type) throw new Error(`Expected ${type}, got ${tok.type} (${tok.value})`);
194
292
  return tok;
195
293
  }
196
- freshBlank(prefix = 'b') { this.blankCounter += 1; return `_${prefix}${this.blankCounter}`; }
294
+ freshBlank(prefix = 'b') {
295
+ this.blankCounter += 1;
296
+ return `_${prefix}${this.blankCounter}`;
297
+ }
197
298
  skipPrefix() {
198
299
  this.expect('@prefix');
199
300
  // Prefix declaration is irrelevant after QName compaction; skip until final dot.
200
301
  while (!this.eof() && !this.accept('.')) this.next();
201
302
  }
202
303
  parseProgram() {
203
- const facts = [], rules = [], queries = [], prefixes = {};
304
+ const facts = [],
305
+ rules = [],
306
+ queries = [],
307
+ prefixes = {};
204
308
  while (!this.eof()) {
205
309
  if (this.accept('@prefix')) {
206
310
  this.pos -= 1;
207
311
  const start = this.pos;
208
312
  this.skipPrefix();
209
- const slice = this.tokens.slice(start, this.pos).map((tok) => tok.value).join(' ');
313
+ const slice = this.tokens
314
+ .slice(start, this.pos)
315
+ .map((tok) => tok.value)
316
+ .join(' ');
210
317
  const m = slice.match(/@prefix\s+([^\s]*)\s+<([^>]+)>/);
211
318
  if (m) prefixes[(m[1] || ':').replace(/:$/, '')] = m[2];
212
319
  continue;
@@ -214,19 +321,29 @@ class Parser {
214
321
  if (this.peek('{')) {
215
322
  const lhs = this.parseFormula('body');
216
323
  if (this.accept('=>')) {
217
- if ((this.peek('qname') && this.tokens[this.pos].value === 'false') || (this.peek('boolean') && this.tokens[this.pos].value === false)) {
218
- this.next(); this.accept('.');
324
+ if (
325
+ (this.peek('qname') && this.tokens[this.pos].value === 'false') ||
326
+ (this.peek('boolean') && this.tokens[this.pos].value === false)
327
+ ) {
328
+ this.next();
329
+ this.accept('.');
219
330
  rules.push({ kind: 'fuse', id: rules.length + 1, body: lhs });
220
331
  } else {
221
- const head = this.parseFormula('head'); this.accept('.');
332
+ const head = this.parseFormula('head');
333
+ this.accept('.');
222
334
  rules.push({ kind: 'rule', id: rules.length + 1, body: lhs, head });
223
335
  }
224
336
  } else if (this.accept('<=')) {
225
- if ((this.peek('qname') && this.tokens[this.pos].value === 'true') || (this.peek('boolean') && this.tokens[this.pos].value === true)) {
226
- this.next(); this.accept('.');
337
+ if (
338
+ (this.peek('qname') && this.tokens[this.pos].value === 'true') ||
339
+ (this.peek('boolean') && this.tokens[this.pos].value === true)
340
+ ) {
341
+ this.next();
342
+ this.accept('.');
227
343
  rules.push({ kind: 'backward', id: rules.length + 1, body: [], head: lhs });
228
344
  } else {
229
- const rhs = this.parseFormula('body'); this.accept('.');
345
+ const rhs = this.parseFormula('body');
346
+ this.accept('.');
230
347
  rules.push({ kind: 'backward', id: rules.length + 1, body: rhs, head: lhs });
231
348
  }
232
349
  } else {
@@ -234,7 +351,12 @@ class Parser {
234
351
  const triples = this.parseStatementRest('fact', subject);
235
352
  this.accept('.');
236
353
  for (const triple of triples) {
237
- if (triple.s?.kind === 'formula' && triple.p?.kind === 'iri' && triple.p.value === 'log:query' && triple.o?.kind === 'formula') {
354
+ if (
355
+ triple.s?.kind === 'formula' &&
356
+ triple.p?.kind === 'iri' &&
357
+ triple.p.value === 'log:query' &&
358
+ triple.o?.kind === 'formula'
359
+ ) {
238
360
  queries.push({ id: queries.length + 1, premise: triple.s.atoms, conclusion: triple.o.atoms });
239
361
  } else {
240
362
  facts.push(triple);
@@ -310,7 +432,10 @@ class Parser {
310
432
  return { kind: 'formula', atoms };
311
433
  }
312
434
  if (tok.type === '[') {
313
- const id = mode === 'body' ? V(this.freshBlank('bodyBlank')) : Blank(this.freshBlank(mode === 'head' ? 'headBlank' : 'blank'));
435
+ const id =
436
+ mode === 'body'
437
+ ? V(this.freshBlank('bodyBlank'))
438
+ : Blank(this.freshBlank(mode === 'head' ? 'headBlank' : 'blank'));
314
439
  if (!this.accept(']')) {
315
440
  while (true) {
316
441
  const predicate = this.parsePredicate();
@@ -329,6 +454,11 @@ class Parser {
329
454
  }
330
455
  }
331
456
 
457
+ // parseN3 separates the source file into four compiler inputs:
458
+ // facts -> serialized as examples/input/<name>.trig
459
+ // rules -> compiled into JavaScript fixpoint code
460
+ // queries -> rendered as selected output checks
461
+ // prefixes -> carried into generated TriG evidence
332
462
  function parseN3(n3) {
333
463
  const parser = new Parser(tokenize(n3));
334
464
  return parser.parseProgram();
@@ -343,12 +473,18 @@ function termToJsComment(term) {
343
473
  if (term.kind === 'formula') return `{ ${term.atoms.map(atomToComment).join(' . ')} }`;
344
474
  return String(term.value ?? term);
345
475
  }
346
- function atomToComment(atom) { return `${termToJsComment(atom.s)} ${termToJsComment(atom.p)} ${termToJsComment(atom.o)}`; }
476
+ function atomToComment(atom) {
477
+ return `${termToJsComment(atom.s)} ${termToJsComment(atom.p)} ${termToJsComment(atom.o)}`;
478
+ }
347
479
 
348
480
  function compilationStats(program) {
349
481
  const predicates = new Set();
350
482
  const builtins = new Set();
351
- for (const atom of [...program.facts, ...program.rules.flatMap((r) => [...(r.body || []), ...(r.head || [])]), ...(program.queries || []).flatMap((q) => [...(q.premise || []), ...(q.conclusion || [])])]) {
483
+ for (const atom of [
484
+ ...program.facts,
485
+ ...program.rules.flatMap((r) => [...(r.body || []), ...(r.head || [])]),
486
+ ...(program.queries || []).flatMap((q) => [...(q.premise || []), ...(q.conclusion || [])]),
487
+ ]) {
352
488
  const p = atom.p?.value;
353
489
  if (p) predicates.add(p);
354
490
  if (/^(math|string|list|log|crypto):/.test(p)) builtins.add(p);
@@ -364,17 +500,104 @@ function compilationStats(program) {
364
500
  };
365
501
  }
366
502
 
367
-
368
- function trigString(value) { return JSON.stringify(String(value)); }
369
- function trigNumber(value) { if (Object.is(value, -0)) return '0'; if (Number.isInteger(value)) return String(value); return Number(value.toPrecision(15)).toString(); }
370
- function inputLiteralToN3(value) { if (typeof value === 'string') return trigString(value); if (typeof value === 'number') return trigNumber(value); if (typeof value === 'boolean') return value ? 'true' : 'false'; return trigString(value); }
371
- function inputTermToN3(term) { if (!term) return 'undefined'; if (term.kind === 'iri') return term.value; if (term.kind === 'lit') return inputLiteralToN3(term.value); if (term.kind === 'var') return '?' + term.value; if (term.kind === 'blank') return term.value.startsWith('_:') ? term.value : '_:' + term.value.replace(/^_+/, ''); if (term.kind === 'list') return '(' + term.items.map(inputTermToN3).join(' ') + ')'; if (term.kind === 'formula') return '{ ' + term.atoms.map(inputAtomToN3).join(' . ') + ' }'; return String(term.value ?? term); }
372
- function inputAtomToN3(atom) { return inputTermToN3(atom.s) + ' ' + inputTermToN3(atom.p) + ' ' + inputTermToN3(atom.o); }
373
- function formulaBlock(label, atoms) { const lines = [label + ' {']; for (const atom of atoms) lines.push(' ' + inputAtomToN3(atom) + ' .'); lines.push('}'); return lines.join('\n'); }
374
- function atomToTrig(atom, state) { if (atom.o && atom.o.kind === 'formula') { if (atom.p && atom.p.kind === 'iri' && atom.p.value === 'log:nameOf') { state.graphs.push(formulaBlock(inputTermToN3(atom.s), atom.o.atoms)); return null; } state.formulaCounter += 1; const label = `in:formula${state.formulaCounter}`; state.graphs.push(formulaBlock(label, atom.o.atoms)); return inputTermToN3(atom.s) + ' ' + inputTermToN3(atom.p) + ' ' + label + ' .'; } return inputAtomToN3(atom) + ' .'; }
375
- function inputFactsToTrig(facts) { const state = { formulaCounter: 0, graphs: [] }; const triples = []; for (const atom of facts) { const line = atomToTrig(atom, state); if (line) triples.push(line); } return { triples, graphs: state.graphs }; }
376
- function prefixLines(prefixes) { const merged = { ...(prefixes || {}) }; if (!Object.hasOwn(merged, 'log')) merged.log = 'http://www.w3.org/2000/10/swap/log#'; if (!Object.hasOwn(merged, 'see')) merged.see = 'https://example.org/see#'; if (!Object.hasOwn(merged, 'in')) merged.in = 'https://example.org/see/input#'; return Object.entries(merged).map(([prefix, iri]) => `@prefix ${prefix ? prefix + ':' : ':'} <${iri}> .`); }
377
- function generateInputTrig(n3Path, name, title, header, stats, program) { const { triples, graphs } = inputFactsToTrig(program.facts); const metadata = ['in:metadata {', ' in:run a see:InputDataset .', ` in:run see:name ${trigString(name)} .`, ` in:run see:title ${trigString(title)} .`, ` in:run see:sourceFile ${trigString(path.relative(ROOT, path.resolve(n3Path)))} .`, ` in:run see:sourceSHA256 ${trigString(stats.sourceHash)} .`, ` in:run see:description ${trigString(header.description || '')} .`, ' in:run see:compiler "see.js N3-to-JS compiler" .', ` in:run see:inputFacts ${stats.facts} .`, ` in:run see:compiledRules ${stats.rules} .`, ` in:run see:compiledBackwardRules ${stats.backwardRules} .`, ` in:run see:compiledFuses ${stats.fuses} .`, ` in:run see:compiledQueries ${stats.queries} .`, '}'].join('\n'); const sections = [...prefixLines(program.prefixes), '', '# Formal SEE input evidence in RDF 1.2 TriG.', '# The generated runner reads this TriG evidence directly.', '', triples.length ? triples.join('\n') : '# No source facts were present in the N3 program.']; if (graphs.length) sections.push('', graphs.join('\n\n')); sections.push('', metadata, ''); return sections.join('\n'); }
503
+ // Source facts are emitted as RDF 1.2 TriG. Formulas that appear as objects are
504
+ // lifted into named graphs so the generated runner can load evidence directly
505
+ // from .trig without going through an intermediate n3gen conversion step.
506
+ function trigString(value) {
507
+ return JSON.stringify(String(value));
508
+ }
509
+ function trigNumber(value) {
510
+ if (Object.is(value, -0)) return '0';
511
+ if (Number.isInteger(value)) return String(value);
512
+ return Number(value.toPrecision(15)).toString();
513
+ }
514
+ function inputLiteralToN3(value) {
515
+ if (typeof value === 'string') return trigString(value);
516
+ if (typeof value === 'number') return trigNumber(value);
517
+ if (typeof value === 'boolean') return value ? 'true' : 'false';
518
+ return trigString(value);
519
+ }
520
+ function inputTermToN3(term) {
521
+ if (!term) return 'undefined';
522
+ if (term.kind === 'iri') return term.value;
523
+ if (term.kind === 'lit') return inputLiteralToN3(term.value);
524
+ if (term.kind === 'var') return '?' + term.value;
525
+ if (term.kind === 'blank') return term.value.startsWith('_:') ? term.value : '_:' + term.value.replace(/^_+/, '');
526
+ if (term.kind === 'list') return '(' + term.items.map(inputTermToN3).join(' ') + ')';
527
+ if (term.kind === 'formula') return '{ ' + term.atoms.map(inputAtomToN3).join(' . ') + ' }';
528
+ return String(term.value ?? term);
529
+ }
530
+ function inputAtomToN3(atom) {
531
+ return inputTermToN3(atom.s) + ' ' + inputTermToN3(atom.p) + ' ' + inputTermToN3(atom.o);
532
+ }
533
+ function formulaBlock(label, atoms) {
534
+ const lines = [label + ' {'];
535
+ for (const atom of atoms) lines.push(' ' + inputAtomToN3(atom) + ' .');
536
+ lines.push('}');
537
+ return lines.join('\n');
538
+ }
539
+ function atomToTrig(atom, state) {
540
+ if (atom.o && atom.o.kind === 'formula') {
541
+ if (atom.p && atom.p.kind === 'iri' && atom.p.value === 'log:nameOf') {
542
+ state.graphs.push(formulaBlock(inputTermToN3(atom.s), atom.o.atoms));
543
+ return null;
544
+ }
545
+ state.formulaCounter += 1;
546
+ const label = `in:formula${state.formulaCounter}`;
547
+ state.graphs.push(formulaBlock(label, atom.o.atoms));
548
+ return inputTermToN3(atom.s) + ' ' + inputTermToN3(atom.p) + ' ' + label + ' .';
549
+ }
550
+ return inputAtomToN3(atom) + ' .';
551
+ }
552
+ function inputFactsToTrig(facts) {
553
+ const state = { formulaCounter: 0, graphs: [] };
554
+ const triples = [];
555
+ for (const atom of facts) {
556
+ const line = atomToTrig(atom, state);
557
+ if (line) triples.push(line);
558
+ }
559
+ return { triples, graphs: state.graphs };
560
+ }
561
+ function prefixLines(prefixes) {
562
+ const merged = { ...(prefixes || {}) };
563
+ if (!Object.hasOwn(merged, 'log')) merged.log = 'http://www.w3.org/2000/10/swap/log#';
564
+ if (!Object.hasOwn(merged, 'see')) merged.see = 'https://example.org/see#';
565
+ if (!Object.hasOwn(merged, 'in')) merged.in = 'https://example.org/see/input#';
566
+ return Object.entries(merged).map(([prefix, iri]) => `@prefix ${prefix ? prefix + ':' : ':'} <${iri}> .`);
567
+ }
568
+ function generateInputTrig(n3Path, name, title, header, stats, program) {
569
+ const { triples, graphs } = inputFactsToTrig(program.facts);
570
+ const metadata = [
571
+ 'in:metadata {',
572
+ ' in:run a see:InputDataset .',
573
+ ` in:run see:name ${trigString(name)} .`,
574
+ ` in:run see:title ${trigString(title)} .`,
575
+ ` in:run see:sourceFile ${trigString(path.relative(ROOT, path.resolve(n3Path)))} .`,
576
+ ` in:run see:sourceSHA256 ${trigString(stats.sourceHash)} .`,
577
+ ` in:run see:description ${trigString(header.description || '')} .`,
578
+ ' in:run see:compiler "see.js N3-to-JS compiler" .',
579
+ ` in:run see:inputFacts ${stats.facts} .`,
580
+ ` in:run see:compiledRules ${stats.rules} .`,
581
+ ` in:run see:compiledBackwardRules ${stats.backwardRules} .`,
582
+ ` in:run see:compiledFuses ${stats.fuses} .`,
583
+ ` in:run see:compiledQueries ${stats.queries} .`,
584
+ '}',
585
+ ].join('\n');
586
+ const sections = [
587
+ ...prefixLines(program.prefixes),
588
+ '',
589
+ '# Formal SEE input evidence in RDF 1.2 TriG.',
590
+ '# The generated runner reads this TriG evidence directly.',
591
+ '',
592
+ triples.length ? triples.join('\n') : '# No source facts were present in the N3 program.',
593
+ ];
594
+ if (graphs.length) sections.push('', graphs.join('\n\n'));
595
+ sections.push('', metadata, '');
596
+ return sections.join('\n');
597
+ }
598
+ // The runtime below is copied verbatim into each generated example. Keep it
599
+ // dependency-light: generated examples should be executable with Node alone plus
600
+ // the local examples/_see.js TriG loader.
378
601
  function runtimeSource() {
379
602
  return String.raw`
380
603
  const crypto = require('crypto');
@@ -1413,19 +1636,19 @@ function renderStructuredOutput({ title, graph, queries = [], rules = [], initia
1413
1636
  const lines = [];
1414
1637
  lines.push('# ' + title);
1415
1638
  lines.push('');
1416
- lines.push('## Insight');
1639
+ lines.push('## Entailment');
1417
1640
  if (mode === 'query') {
1418
1641
  lines.push('The compiled query selected ' + selected.length + ' fact(s) after the rule closure was computed.');
1419
1642
  } else if (mode === 'formula') {
1420
- lines.push('The derivation produced ' + selected.length + ' formula-valued conclusion(s).');
1643
+ lines.push('The derivation produced ' + selected.length + ' formula-valued entailment(s).');
1421
1644
  } else {
1422
1645
  lines.push('The derivation produced ' + derived.length + ' new fact(s) from ' + initialFacts.length + ' stated fact(s).');
1423
1646
  }
1424
- if (keyFact) lines.push('Main conclusion: **' + factSentence(keyFact) + '**');
1647
+ if (keyFact) lines.push('Main entailment: **' + factSentence(keyFact) + '**');
1425
1648
  const bullets = selected.slice(-6).reverse();
1426
1649
  if (bullets.length) {
1427
1650
  lines.push('');
1428
- lines.push('Selected conclusions:');
1651
+ lines.push('Selected entailments:');
1429
1652
  for (const fact of bullets) lines.push('- ' + codeFact(fact));
1430
1653
  }
1431
1654
  lines.push('');
@@ -1487,15 +1710,15 @@ function dedupeExplanationHeadings(text) {
1487
1710
  function normalizePublicReport(markdown, title) {
1488
1711
  let text = String(markdown || '').trimEnd();
1489
1712
  if (!/^\s*#\s+/m.test(text)) text = '# ' + title + '\n\n' + text;
1490
- if (!/^##\s+Insight\s*$/mi.test(text)) {
1491
- text = text.replace(/^(#\s+[^\n]+\n*)/, '$1\n## Insight\n');
1713
+ if (!/^##\s+Entailment\s*$/mi.test(text)) {
1714
+ text = text.replace(/^(#\s+[^\n]+\n*)/, '$1\n## Entailment\n');
1492
1715
  }
1493
1716
  if (!/^##\s+Explanation\s*$/mi.test(text)) {
1494
1717
  text += '\n\n## Explanation\nNo additional explanation was provided by the generated output.';
1495
1718
  }
1496
1719
  text = text.replace(/^##\s+([^\n]+?)\s*$/gm, (line, heading) => {
1497
1720
  const normalized = heading.trim().toLowerCase();
1498
- if (normalized === 'insight' || normalized === 'explanation') return '## ' + (normalized === 'insight' ? 'Insight' : 'Explanation');
1721
+ if (normalized === 'insight' || normalized === 'conclusion' || normalized === 'entailment' || normalized === 'explanation') return '## ' + (normalized === 'explanation' ? 'Explanation' : 'Entailment');
1499
1722
  return '**' + heading.trim() + '**';
1500
1723
  });
1501
1724
  text = dedupeExplanationHeadings(text);
@@ -1504,13 +1727,13 @@ function normalizePublicReport(markdown, title) {
1504
1727
  function markdownize(raw, title) {
1505
1728
  let text = String(raw || '');
1506
1729
  text = text
1507
- .replace(/===\s*Answer\s*===/g, '## Insight')
1730
+ .replace(/===\s*Answer\s*===/g, '## Entailment')
1508
1731
  .replace(/===\s*Reason\s+Why\s*===/gi, '## Explanation')
1509
1732
  .replace(/===\s*Explanation\s*===/gi, '## Explanation')
1510
1733
  .replace(/===\s*([^=]+?)\s*===/g, (_, h) => '**' + h.trim() + '**');
1511
1734
  text = text.replace(/^C(\d+)\s+OK\s*-\s*/gm, 'C$1: ');
1512
1735
  text = dedupeExplanationHeadings(text);
1513
- if (!text.trim()) text = '## Insight\nNo log:outputString facts were derived.\n\n## Explanation\nThe compiled derivation did not produce authored report text.';
1736
+ if (!text.trim()) text = '## Entailment\nNo log:outputString facts were derived.\n\n## Explanation\nThe compiled derivation did not produce authored report text.';
1514
1737
  return normalizePublicReport(text, title);
1515
1738
  }
1516
1739
  function authoredSupportAppendix(graph, queries, rules, initialFacts, trace) {
@@ -1565,10 +1788,17 @@ function renderPresentation(graph, queries, rules, initialFacts, title, trace) {
1565
1788
  `;
1566
1789
  }
1567
1790
 
1568
-
1569
1791
  function generateExampleJs(name, title, program, stats, doc) {
1570
- const rulesWithComments = program.rules.map((rule) => ({ ...rule, bodyComment: (rule.body || []).map(atomToComment), headComment: (rule.head || []).map(atomToComment) }));
1571
- const queriesWithComments = (program.queries || []).map((query) => ({ ...query, premiseComment: (query.premise || []).map(atomToComment), conclusionComment: (query.conclusion || []).map(atomToComment) }));
1792
+ const rulesWithComments = program.rules.map((rule) => ({
1793
+ ...rule,
1794
+ bodyComment: (rule.body || []).map(atomToComment),
1795
+ headComment: (rule.head || []).map(atomToComment),
1796
+ }));
1797
+ const queriesWithComments = (program.queries || []).map((query) => ({
1798
+ ...query,
1799
+ premiseComment: (query.premise || []).map(atomToComment),
1800
+ conclusionComment: (query.conclusion || []).map(atomToComment),
1801
+ }));
1572
1802
  return `#!/usr/bin/env node
1573
1803
  'use strict';
1574
1804
  const fs = require('fs');
@@ -1680,19 +1910,43 @@ module.exports = { trustedDerivation, outputMarkdown, documentationMarkdown, wri
1680
1910
  `;
1681
1911
  }
1682
1912
 
1683
-
1684
- function generateDoc(name, title, header, stats) { const description = header.description ? `
1913
+ // Documentation is generated from compilation metadata rather than hand-written
1914
+ // per example, keeping examples/output and examples/doc reproducible snapshots.
1915
+ function generateDoc(name, title, header, stats) {
1916
+ const description = header.description
1917
+ ? `
1685
1918
  ${header.description}
1686
- ` : ''; const builtins = stats.builtins.length ? stats.builtins.map((b) => `- \`${b}\``).join('\n') : '- none'; return `# ${title}\n\nGenerated by \`see.js\` from a Notation3 source file.\n${description}\n## Compilation summary\n\n- Example name: \`${name}\`\n- Input facts emitted: ${stats.facts}\n- Forward rules compiled: ${stats.rules}\n- Backward predicate rules compiled: ${stats.backwardRules}\n- Fuses compiled: ${stats.fuses}\n- Predicate count: ${stats.predicates}\n\n## Built-ins used\n\n${builtins}\n\n## Runtime model\n\nThe generated \`examples/${name}.js\` is a specialized JavaScript derivation program. For ordinary sources, \`see.js\` emits the source facts as \`examples/input/${name}.trig\`. For rules-only sources, generation can reuse an existing external evidence file such as \`examples/input/${name.replace(/_/g, '-')}.trig\` or \`examples/input/${name}.trig\`. The runner reads that TriG evidence directly and performs a local fixpoint derivation; it does not parse the program source or call an external reasoner.\n\n## Output model\n\nRunning \`node examples/${name}.js\` produces a SEE-style Markdown report with an **Insight** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.\n`; }
1919
+ `
1920
+ : '';
1921
+ const builtins = stats.builtins.length ? stats.builtins.map((b) => `- \`${b}\``).join('\n') : '- none';
1922
+ return `# ${title}\n\nGenerated by \`see.js\` from a Notation3 source file.\n${description}\n## Compilation summary\n\n- Example name: \`${name}\`\n- Input facts emitted: ${stats.facts}\n- Forward rules compiled: ${stats.rules}\n- Backward predicate rules compiled: ${stats.backwardRules}\n- Fuses compiled: ${stats.fuses}\n- Predicate count: ${stats.predicates}\n\n## Built-ins used\n\n${builtins}\n\n## Runtime model\n\nThe generated \`examples/${name}.js\` is a specialized JavaScript derivation program. For ordinary sources, \`see.js\` emits the source facts as \`examples/input/${name}.trig\`. For rules-only sources, generation can reuse an existing external evidence file such as \`examples/input/${name.replace(/_/g, '-')}.trig\` or \`examples/input/${name}.trig\`. The runner reads that TriG evidence directly and performs a local fixpoint derivation; it does not parse the program source or call an external reasoner.\n\n## Output model\n\nRunning \`node examples/${name}.js\` produces a SEE-style Markdown report with an **Entailment** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.\n`;
1923
+ }
1687
1924
 
1688
1925
  function runNode(file, cwd = ROOT, args = []) {
1689
1926
  const result = spawnSync(process.execPath, [file, ...args], { cwd, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
1690
- if (result.status !== 0) throw new Error(`generated example failed:
1927
+ if (result.status !== 0)
1928
+ throw new Error(`generated example failed:
1691
1929
  ${result.stderr || result.stdout}`);
1692
1930
  return result.stdout;
1693
1931
  }
1694
1932
 
1695
- function compile(n3Path, options = {}) { const absolute = path.resolve(n3Path); const n3 = readText(absolute); const name = options.name || slugify(path.basename(absolute)); const title = parseHeader(n3, titleFromSlug(name)).title; const header = parseHeader(n3, titleFromSlug(name)); const program = parseN3(n3); const stats = compilationStats(program); stats.sourceHash = sha256(n3); const inputTrig = generateInputTrig(absolute, name, title, header, stats, program); const doc = generateDoc(name, title, header, stats); const exampleJs = generateExampleJs(name, title, program, stats, doc); return { name, title, program, stats, inputTrig, exampleJs, doc }; }
1933
+ // compile is pure with respect to the repository: it reads one source .n3 file
1934
+ // and returns all generated text. The generate/render commands decide whether
1935
+ // those artefacts are written to disk or executed from a temporary directory.
1936
+ function compile(n3Path, options = {}) {
1937
+ const absolute = path.resolve(n3Path);
1938
+ const n3 = readText(absolute);
1939
+ const name = options.name || slugify(path.basename(absolute));
1940
+ const title = parseHeader(n3, titleFromSlug(name)).title;
1941
+ const header = parseHeader(n3, titleFromSlug(name));
1942
+ const program = parseN3(n3);
1943
+ const stats = compilationStats(program);
1944
+ stats.sourceHash = sha256(n3);
1945
+ const inputTrig = generateInputTrig(absolute, name, title, header, stats, program);
1946
+ const doc = generateDoc(name, title, header, stats);
1947
+ const exampleJs = generateExampleJs(name, title, program, stats, doc);
1948
+ return { name, title, program, stats, inputTrig, exampleJs, doc };
1949
+ }
1696
1950
 
1697
1951
  function inputNameCandidates(name) {
1698
1952
  const out = [name];
@@ -1714,19 +1968,21 @@ function inputCandidateScore(file) {
1714
1968
  return { facts: -1, size: -1 };
1715
1969
  }
1716
1970
  }
1971
+ // Rules-only examples can reuse an externally authored TriG evidence file. The
1972
+ // scoring prefers the candidate that advertises the most input facts, then the
1973
+ // larger file, so dashed public datasets such as path-discovery.trig win over
1974
+ // empty generated placeholders.
1717
1975
  function existingExternalInputName(name) {
1718
1976
  const candidates = inputNameCandidates(name)
1719
1977
  .map((base, order) => ({ base, order, file: path.join(INPUT_DIR, `${base}.trig`) }))
1720
1978
  .filter((c) => fs.existsSync(c.file))
1721
1979
  .map((c) => ({ ...c, score: inputCandidateScore(c.file) }));
1722
1980
  if (!candidates.length) return null;
1723
- candidates.sort((a, b) =>
1724
- (b.score.facts - a.score.facts) ||
1725
- (b.score.size - a.score.size) ||
1726
- (a.order - b.order)
1727
- );
1981
+ candidates.sort((a, b) => b.score.facts - a.score.facts || b.score.size - a.score.size || a.order - b.order);
1728
1982
  return candidates[0].base;
1729
1983
  }
1984
+ // generate writes the checked-in artefacts and immediately executes the new
1985
+ // example with --write so examples/output and examples/doc remain in sync.
1730
1986
  function generate(n3Path, options = {}) {
1731
1987
  const compiled = compile(n3Path, options);
1732
1988
  const jsFile = path.join(EXAMPLES_DIR, `${compiled.name}.js`);
@@ -1738,7 +1994,8 @@ function generate(n3Path, options = {}) {
1738
1994
  if (!options.force) {
1739
1995
  const protectedInputs = externalInputName ? [] : [inputTrigFile];
1740
1996
  for (const file of [outputFile, docFile, ...protectedInputs]) {
1741
- if (fs.existsSync(file)) throw new Error(`${path.relative(ROOT, file)} already exists; pass --force to overwrite`);
1997
+ if (fs.existsSync(file))
1998
+ throw new Error(`${path.relative(ROOT, file)} already exists; pass --force to overwrite`);
1742
1999
  }
1743
2000
  }
1744
2001
  writeText(jsFile, compiled.exampleJs, options.force);
@@ -1749,7 +2006,27 @@ function generate(n3Path, options = {}) {
1749
2006
  return { ...compiled, files: { jsFile, inputTrigFile, outputFile, docFile }, output };
1750
2007
  }
1751
2008
 
1752
- function render(n3Path) { const tmpName = `_see_tmp_${process.pid}`; const compiled = compile(n3Path, { name: tmpName }); const tmpDir = fs.mkdtempSync(path.join(require('os').tmpdir(), 'see-compile-')); const tmpSeeDir = path.join(tmpDir, 'see'); const examplesDir = path.join(tmpSeeDir, 'examples'); ensureDir(path.join(examplesDir, 'input')); fs.copyFileSync(path.join(EXAMPLES_DIR, '_see.js'), path.join(examplesDir, '_see.js')); fs.copyFileSync(path.join(ROOT, 'see.js'), path.join(tmpSeeDir, 'see.js')); const jsFile = path.join(examplesDir, `${tmpName}.js`); const trigFile = path.join(examplesDir, 'input', `${tmpName}.trig`); fs.writeFileSync(jsFile, compiled.exampleJs, 'utf8'); fs.writeFileSync(trigFile, compiled.inputTrig, 'utf8'); try { return runNode(jsFile, tmpSeeDir); } finally { fs.rmSync(tmpDir, { recursive: true, force: true }); } }
2009
+ // render is the non-mutating companion to generate. It compiles into a small
2010
+ // temporary /see-shaped tree, runs the generated example, and returns Markdown.
2011
+ function render(n3Path) {
2012
+ const tmpName = `_see_tmp_${process.pid}`;
2013
+ const compiled = compile(n3Path, { name: tmpName });
2014
+ const tmpDir = fs.mkdtempSync(path.join(require('os').tmpdir(), 'see-compile-'));
2015
+ const tmpSeeDir = path.join(tmpDir, 'see');
2016
+ const examplesDir = path.join(tmpSeeDir, 'examples');
2017
+ ensureDir(path.join(examplesDir, 'input'));
2018
+ fs.copyFileSync(path.join(EXAMPLES_DIR, '_see.js'), path.join(examplesDir, '_see.js'));
2019
+ fs.copyFileSync(path.join(ROOT, 'see.js'), path.join(tmpSeeDir, 'see.js'));
2020
+ const jsFile = path.join(examplesDir, `${tmpName}.js`);
2021
+ const trigFile = path.join(examplesDir, 'input', `${tmpName}.trig`);
2022
+ fs.writeFileSync(jsFile, compiled.exampleJs, 'utf8');
2023
+ fs.writeFileSync(trigFile, compiled.inputTrig, 'utf8');
2024
+ try {
2025
+ return runNode(jsFile, tmpSeeDir);
2026
+ } finally {
2027
+ fs.rmSync(tmpDir, { recursive: true, force: true });
2028
+ }
2029
+ }
1753
2030
 
1754
2031
  function parseArgs(argv) {
1755
2032
  const args = [...argv];
@@ -1767,7 +2044,10 @@ function parseArgs(argv) {
1767
2044
 
1768
2045
  function main() {
1769
2046
  const { command, file, opts } = parseArgs(process.argv.slice(2));
1770
- if (!command || command === 'help' || command === '--help') { console.log(usage()); return; }
2047
+ if (!command || command === 'help' || command === '--help') {
2048
+ console.log(usage());
2049
+ return;
2050
+ }
1771
2051
  if (!file) throw new Error(`Missing <example.n3>\n\n${usage()}`);
1772
2052
  if (command === 'generate') {
1773
2053
  const result = generate(file, opts);
@@ -1775,20 +2055,28 @@ function main() {
1775
2055
  if (result.files.inputTrigFile) console.log(`generated ${path.relative(ROOT, result.files.inputTrigFile)}`);
1776
2056
  console.log(`generated ${path.relative(ROOT, result.files.outputFile)}`);
1777
2057
  console.log(`generated ${path.relative(ROOT, result.files.docFile)}`);
1778
- console.log(`compiled ${result.stats.facts} facts, ${result.stats.rules} forward rules, ${result.stats.backwardRules} backward rules, ${result.stats.fuses} fuses, ${result.stats.queries} queries`);
2058
+ console.log(
2059
+ `compiled ${result.stats.facts} facts, ${result.stats.rules} forward rules, ${result.stats.backwardRules} backward rules, ${result.stats.fuses} fuses, ${result.stats.queries} queries`,
2060
+ );
1779
2061
  } else if (command === 'render') {
1780
2062
  process.stdout.write(render(file));
1781
2063
  } else if (command === 'inspect') {
1782
2064
  const result = compile(file, opts);
1783
- console.log(`OK ${result.name}: ${result.stats.facts} facts, ${result.stats.rules} forward rules, ${result.stats.backwardRules} backward rules, ${result.stats.fuses} fuses, ${result.stats.queries} queries`);
2065
+ console.log(
2066
+ `OK ${result.name}: ${result.stats.facts} facts, ${result.stats.rules} forward rules, ${result.stats.backwardRules} backward rules, ${result.stats.fuses} fuses, ${result.stats.queries} queries`,
2067
+ );
1784
2068
  } else {
1785
2069
  throw new Error(`Unknown command: ${command}\n\n${usage()}`);
1786
2070
  }
1787
2071
  }
1788
2072
 
1789
2073
  if (require.main === module) {
1790
- try { main(); }
1791
- catch (err) { console.error(err.stack || err.message); process.exit(1); }
2074
+ try {
2075
+ main();
2076
+ } catch (err) {
2077
+ console.error(err.stack || err.message);
2078
+ process.exit(1);
2079
+ }
1792
2080
  }
1793
2081
 
1794
2082
  module.exports = { compile, generate, parseN3, render, tokenize };