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.
- package/README.md +4 -4
- package/package.json +1 -1
- package/see/README.md +10 -11
- package/see/examples/age.js +10 -10
- package/see/examples/annotation.js +10 -10
- package/see/examples/backward.js +10 -10
- package/see/examples/backward_recursion.js +10 -10
- package/see/examples/bayes_diagnosis.js +10 -10
- package/see/examples/bayes_therapy.js +10 -10
- package/see/examples/bmi.js +10 -10
- package/see/examples/builtin_coverage.js +10 -10
- package/see/examples/collection.js +10 -10
- package/see/examples/complex.js +10 -10
- package/see/examples/complex_matrix_stability.js +10 -10
- package/see/examples/composition_of_injective_functions_is_injective.js +10 -10
- package/see/examples/control_system.js +10 -10
- package/see/examples/crypto_builtins_tests.js +10 -10
- package/see/examples/delfour.js +10 -10
- package/see/examples/digital_product_passport.js +10 -10
- package/see/examples/dijkstra.js +10 -10
- package/see/examples/dijkstra_risk_path.js +10 -10
- package/see/examples/doc/age.md +1 -1
- package/see/examples/doc/annotation.md +1 -1
- package/see/examples/doc/backward.md +1 -1
- package/see/examples/doc/backward_recursion.md +1 -1
- package/see/examples/doc/bayes_diagnosis.md +1 -1
- package/see/examples/doc/bayes_therapy.md +1 -1
- package/see/examples/doc/bmi.md +1 -1
- package/see/examples/doc/builtin_coverage.md +1 -1
- package/see/examples/doc/collection.md +1 -1
- package/see/examples/doc/complex.md +1 -1
- package/see/examples/doc/complex_matrix_stability.md +1 -1
- package/see/examples/doc/composition_of_injective_functions_is_injective.md +1 -1
- package/see/examples/doc/control_system.md +1 -1
- package/see/examples/doc/crypto_builtins_tests.md +1 -1
- package/see/examples/doc/delfour.md +1 -1
- package/see/examples/doc/digital_product_passport.md +1 -1
- package/see/examples/doc/dijkstra.md +1 -1
- package/see/examples/doc/dijkstra_risk_path.md +1 -1
- package/see/examples/doc/dog.md +1 -1
- package/see/examples/doc/eco_route_insight.md +1 -1
- package/see/examples/doc/equals.md +1 -1
- package/see/examples/doc/equivalence_classes_overlap_implies_same_class.md +1 -1
- package/see/examples/doc/euler_identity.md +1 -1
- package/see/examples/doc/ev_roundtrip_planner.md +1 -1
- package/see/examples/doc/existential_rule.md +1 -1
- package/see/examples/doc/expression_eval.md +1 -1
- package/see/examples/doc/family_cousins.md +1 -1
- package/see/examples/doc/fastpow.md +1 -1
- package/see/examples/doc/fibonacci.md +1 -1
- package/see/examples/doc/french_cities.md +1 -1
- package/see/examples/doc/fundamental_theorem_arithmetic.md +1 -1
- package/see/examples/doc/genetic_knapsack_selection.md +1 -1
- package/see/examples/doc/goldbach_1000.md +1 -1
- package/see/examples/doc/good_cobbler.md +1 -1
- package/see/examples/doc/gps.md +1 -1
- package/see/examples/doc/gray_code_counter.md +1 -1
- package/see/examples/doc/greatest_lower_bound_uniqueness.md +1 -1
- package/see/examples/doc/group_inverse_uniqueness.md +1 -1
- package/see/examples/doc/hadamard_approx.md +1 -1
- package/see/examples/doc/hanoi.md +1 -1
- package/see/examples/doc/odrl_dpv_risk_ranked.md +1 -1
- package/see/examples/doc/path_discovery.md +1 -1
- package/see/examples/doc/rc_discharge_envelope.md +1 -1
- package/see/examples/doc/rdf_message_flow.md +1 -1
- package/see/examples/doc/rdf_messages.md +1 -1
- package/see/examples/doc/school_placement_audit.md +1 -1
- package/see/examples/doc/smoke_arithmetic.md +1 -1
- package/see/examples/doc/socrates.md +1 -1
- package/see/examples/doc/wind_turbine.md +1 -1
- package/see/examples/doc/witch.md +1 -1
- package/see/examples/dog.js +10 -10
- package/see/examples/eco_route_insight.js +10 -10
- package/see/examples/equals.js +10 -10
- package/see/examples/equivalence_classes_overlap_implies_same_class.js +10 -10
- package/see/examples/euler_identity.js +10 -10
- package/see/examples/ev_roundtrip_planner.js +10 -10
- package/see/examples/existential_rule.js +10 -10
- package/see/examples/expression_eval.js +10 -10
- package/see/examples/family_cousins.js +10 -10
- package/see/examples/fastpow.js +10 -10
- package/see/examples/fibonacci.js +10 -10
- package/see/examples/french_cities.js +10 -10
- package/see/examples/fundamental_theorem_arithmetic.js +10 -10
- package/see/examples/genetic_knapsack_selection.js +10 -10
- package/see/examples/goldbach_1000.js +10 -10
- package/see/examples/good_cobbler.js +10 -10
- package/see/examples/gps.js +10 -10
- package/see/examples/gray_code_counter.js +10 -10
- package/see/examples/greatest_lower_bound_uniqueness.js +10 -10
- package/see/examples/group_inverse_uniqueness.js +10 -10
- package/see/examples/hadamard_approx.js +10 -10
- package/see/examples/hanoi.js +10 -10
- package/see/examples/odrl_dpv_risk_ranked.js +10 -10
- package/see/examples/output/age.md +3 -3
- package/see/examples/output/annotation.md +4 -4
- package/see/examples/output/backward.md +3 -3
- package/see/examples/output/backward_recursion.md +3 -3
- package/see/examples/output/bayes_diagnosis.md +1 -1
- package/see/examples/output/bayes_therapy.md +1 -1
- package/see/examples/output/bmi.md +1 -1
- package/see/examples/output/builtin_coverage.md +3 -3
- package/see/examples/output/collection.md +3 -3
- package/see/examples/output/complex.md +4 -4
- package/see/examples/output/complex_matrix_stability.md +1 -1
- package/see/examples/output/composition_of_injective_functions_is_injective.md +3 -3
- package/see/examples/output/control_system.md +3 -3
- package/see/examples/output/crypto_builtins_tests.md +3 -3
- package/see/examples/output/delfour.md +1 -1
- package/see/examples/output/digital_product_passport.md +1 -1
- package/see/examples/output/dijkstra.md +3 -3
- package/see/examples/output/dijkstra_risk_path.md +1 -1
- package/see/examples/output/dog.md +3 -3
- package/see/examples/output/eco_route_insight.md +1 -1
- package/see/examples/output/equals.md +3 -3
- package/see/examples/output/equivalence_classes_overlap_implies_same_class.md +3 -3
- package/see/examples/output/euler_identity.md +3 -3
- package/see/examples/output/ev_roundtrip_planner.md +1 -1
- package/see/examples/output/existential_rule.md +3 -3
- package/see/examples/output/expression_eval.md +3 -3
- package/see/examples/output/family_cousins.md +3 -3
- package/see/examples/output/fastpow.md +1 -1
- package/see/examples/output/fibonacci.md +1 -1
- package/see/examples/output/french_cities.md +3 -3
- package/see/examples/output/fundamental_theorem_arithmetic.md +1 -1
- package/see/examples/output/genetic_knapsack_selection.md +1 -1
- package/see/examples/output/goldbach_1000.md +1 -1
- package/see/examples/output/good_cobbler.md +4 -4
- package/see/examples/output/gps.md +1 -1
- package/see/examples/output/gray_code_counter.md +1 -1
- package/see/examples/output/greatest_lower_bound_uniqueness.md +3 -3
- package/see/examples/output/group_inverse_uniqueness.md +3 -3
- package/see/examples/output/hadamard_approx.md +3 -3
- package/see/examples/output/hanoi.md +3 -3
- package/see/examples/output/odrl_dpv_risk_ranked.md +3 -3
- package/see/examples/output/path_discovery.md +3 -3
- package/see/examples/output/rc_discharge_envelope.md +1 -1
- package/see/examples/output/rdf_message_flow.md +1 -1
- package/see/examples/output/rdf_messages.md +1 -1
- package/see/examples/output/school_placement_audit.md +1 -1
- package/see/examples/output/smoke_arithmetic.md +1 -1
- package/see/examples/output/socrates.md +3 -3
- package/see/examples/output/wind_turbine.md +1 -1
- package/see/examples/output/witch.md +3 -3
- package/see/examples/path_discovery.js +10 -10
- package/see/examples/rc_discharge_envelope.js +10 -10
- package/see/examples/rdf_message_flow.js +10 -10
- package/see/examples/rdf_messages.js +10 -10
- package/see/examples/school_placement_audit.js +10 -10
- package/see/examples/smoke_arithmetic.js +10 -10
- package/see/examples/socrates.js +10 -10
- package/see/examples/wind_turbine.js +10 -10
- package/see/examples/witch.js +10 -10
- 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) {
|
|
36
|
-
|
|
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) {
|
|
45
|
-
|
|
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
|
|
58
|
-
.
|
|
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
|
-
|
|
62
|
-
|
|
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)) {
|
|
67
|
-
|
|
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 {
|
|
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
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
function
|
|
111
|
-
|
|
112
|
-
|
|
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)) {
|
|
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) });
|
|
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)) {
|
|
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 = '',
|
|
217
|
+
let out = '',
|
|
218
|
+
escaped = false;
|
|
219
|
+
i += 1;
|
|
136
220
|
while (i < s.length) {
|
|
137
221
|
const c = s[i++];
|
|
138
|
-
if (escaped) {
|
|
139
|
-
|
|
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 = '';
|
|
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))
|
|
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() {
|
|
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) {
|
|
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') {
|
|
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 = [],
|
|
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
|
|
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 (
|
|
218
|
-
this.
|
|
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');
|
|
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 (
|
|
226
|
-
this.
|
|
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');
|
|
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 (
|
|
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 =
|
|
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) {
|
|
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 [
|
|
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
|
-
|
|
369
|
-
|
|
370
|
-
function
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
function
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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('##
|
|
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
|
|
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
|
|
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
|
|
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+
|
|
1491
|
-
text = text.replace(/^(#\s+[^\n]+\n*)/, '$1\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 === '
|
|
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, '##
|
|
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 = '##
|
|
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) => ({
|
|
1571
|
-
|
|
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
|
-
|
|
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
|
-
`
|
|
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)
|
|
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
|
-
|
|
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))
|
|
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
|
-
|
|
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') {
|
|
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(
|
|
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(
|
|
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 {
|
|
1791
|
-
|
|
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 };
|