bonescript-compiler 0.2.0
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/LICENSE +21 -0
- package/dist/algorithm_catalog.d.ts +32 -0
- package/dist/algorithm_catalog.js +323 -0
- package/dist/algorithm_catalog.js.map +1 -0
- package/dist/ast.d.ts +244 -0
- package/dist/ast.js +8 -0
- package/dist/ast.js.map +1 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +605 -0
- package/dist/cli.js.map +1 -0
- package/dist/emit_batch.d.ts +7 -0
- package/dist/emit_batch.js +133 -0
- package/dist/emit_batch.js.map +1 -0
- package/dist/emit_capability.d.ts +7 -0
- package/dist/emit_capability.js +376 -0
- package/dist/emit_capability.js.map +1 -0
- package/dist/emit_composition.d.ts +22 -0
- package/dist/emit_composition.js +184 -0
- package/dist/emit_composition.js.map +1 -0
- package/dist/emit_deploy.d.ts +9 -0
- package/dist/emit_deploy.js +191 -0
- package/dist/emit_deploy.js.map +1 -0
- package/dist/emit_events.d.ts +14 -0
- package/dist/emit_events.js +305 -0
- package/dist/emit_events.js.map +1 -0
- package/dist/emit_extras.d.ts +12 -0
- package/dist/emit_extras.js +234 -0
- package/dist/emit_extras.js.map +1 -0
- package/dist/emit_full.d.ts +13 -0
- package/dist/emit_full.js +273 -0
- package/dist/emit_full.js.map +1 -0
- package/dist/emit_maintenance.d.ts +16 -0
- package/dist/emit_maintenance.js +442 -0
- package/dist/emit_maintenance.js.map +1 -0
- package/dist/emit_runtime.d.ts +13 -0
- package/dist/emit_runtime.js +691 -0
- package/dist/emit_runtime.js.map +1 -0
- package/dist/emit_sourcemap.d.ts +29 -0
- package/dist/emit_sourcemap.js +123 -0
- package/dist/emit_sourcemap.js.map +1 -0
- package/dist/emit_tests.d.ts +15 -0
- package/dist/emit_tests.js +185 -0
- package/dist/emit_tests.js.map +1 -0
- package/dist/emit_websocket.d.ts +6 -0
- package/dist/emit_websocket.js +223 -0
- package/dist/emit_websocket.js.map +1 -0
- package/dist/emitter.d.ts +25 -0
- package/dist/emitter.js +511 -0
- package/dist/emitter.js.map +1 -0
- package/dist/extension_manager.d.ts +38 -0
- package/dist/extension_manager.js +170 -0
- package/dist/extension_manager.js.map +1 -0
- package/dist/formatter.d.ts +34 -0
- package/dist/formatter.js +317 -0
- package/dist/formatter.js.map +1 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +113 -0
- package/dist/index.js.map +1 -0
- package/dist/ir.d.ts +168 -0
- package/dist/ir.js +10 -0
- package/dist/ir.js.map +1 -0
- package/dist/lexer.d.ts +195 -0
- package/dist/lexer.js +619 -0
- package/dist/lexer.js.map +1 -0
- package/dist/lowering.d.ts +25 -0
- package/dist/lowering.js +500 -0
- package/dist/lowering.js.map +1 -0
- package/dist/module_loader.d.ts +25 -0
- package/dist/module_loader.js +126 -0
- package/dist/module_loader.js.map +1 -0
- package/dist/optimizer.d.ts +26 -0
- package/dist/optimizer.js +158 -0
- package/dist/optimizer.js.map +1 -0
- package/dist/parse_decls.d.ts +13 -0
- package/dist/parse_decls.js +442 -0
- package/dist/parse_decls.js.map +1 -0
- package/dist/parse_decls2.d.ts +13 -0
- package/dist/parse_decls2.js +295 -0
- package/dist/parse_decls2.js.map +1 -0
- package/dist/parse_expr.d.ts +7 -0
- package/dist/parse_expr.js +197 -0
- package/dist/parse_expr.js.map +1 -0
- package/dist/parse_types.d.ts +6 -0
- package/dist/parse_types.js +51 -0
- package/dist/parse_types.js.map +1 -0
- package/dist/parser.d.ts +10 -0
- package/dist/parser.js +62 -0
- package/dist/parser.js.map +1 -0
- package/dist/parser_base.d.ts +19 -0
- package/dist/parser_base.js +50 -0
- package/dist/parser_base.js.map +1 -0
- package/dist/parser_recovery.d.ts +26 -0
- package/dist/parser_recovery.js +140 -0
- package/dist/parser_recovery.js.map +1 -0
- package/dist/scaffold.d.ts +13 -0
- package/dist/scaffold.js +376 -0
- package/dist/scaffold.js.map +1 -0
- package/dist/solver.d.ts +26 -0
- package/dist/solver.js +281 -0
- package/dist/solver.js.map +1 -0
- package/dist/typechecker.d.ts +52 -0
- package/dist/typechecker.js +534 -0
- package/dist/typechecker.js.map +1 -0
- package/dist/types.d.ts +38 -0
- package/dist/types.js +85 -0
- package/dist/types.js.map +1 -0
- package/dist/verifier.d.ts +46 -0
- package/dist/verifier.js +307 -0
- package/dist/verifier.js.map +1 -0
- package/package.json +52 -0
- package/src/algorithm_catalog.ts +345 -0
- package/src/ast.ts +334 -0
- package/src/cli.ts +624 -0
- package/src/emit_batch.ts +140 -0
- package/src/emit_capability.ts +436 -0
- package/src/emit_composition.ts +196 -0
- package/src/emit_deploy.ts +190 -0
- package/src/emit_events.ts +307 -0
- package/src/emit_extras.ts +240 -0
- package/src/emit_full.ts +309 -0
- package/src/emit_maintenance.ts +459 -0
- package/src/emit_runtime.ts +731 -0
- package/src/emit_sourcemap.ts +140 -0
- package/src/emit_tests.ts +205 -0
- package/src/emit_websocket.ts +229 -0
- package/src/emitter.ts +566 -0
- package/src/extension_manager.ts +187 -0
- package/src/formatter.ts +297 -0
- package/src/index.ts +88 -0
- package/src/ir.ts +215 -0
- package/src/lexer.ts +630 -0
- package/src/lowering.ts +556 -0
- package/src/module_loader.ts +114 -0
- package/src/optimizer.ts +196 -0
- package/src/parse_decls.ts +409 -0
- package/src/parse_decls2.ts +244 -0
- package/src/parse_expr.ts +197 -0
- package/src/parse_types.ts +54 -0
- package/src/parser.ts +64 -0
- package/src/parser_base.ts +57 -0
- package/src/parser_recovery.ts +153 -0
- package/src/scaffold.ts +375 -0
- package/src/solver.ts +330 -0
- package/src/typechecker.ts +591 -0
- package/src/types.ts +122 -0
- package/src/verifier.ts +348 -0
package/src/cli.ts
ADDED
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BoneScript compiler CLI
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as fs from "fs";
|
|
6
|
+
import * as path from "path";
|
|
7
|
+
import { createHash } from "crypto";
|
|
8
|
+
import { Lexer } from "./lexer";
|
|
9
|
+
import { Parser } from "./parser";
|
|
10
|
+
import { RecoveringParser } from "./parser_recovery";
|
|
11
|
+
import { TypeChecker } from "./typechecker";
|
|
12
|
+
import { Lowering } from "./lowering";
|
|
13
|
+
import { ConstraintSolver } from "./solver";
|
|
14
|
+
import { FullEmitter } from "./emit_full";
|
|
15
|
+
import { Verifier } from "./verifier";
|
|
16
|
+
import { ModuleLoader } from "./module_loader";
|
|
17
|
+
import { Formatter } from "./formatter";
|
|
18
|
+
import { scaffold, ScaffoldDomain } from "./scaffold";
|
|
19
|
+
import { mergeWithExisting } from "./extension_manager";
|
|
20
|
+
import { optimize } from "./optimizer";
|
|
21
|
+
|
|
22
|
+
function main() {
|
|
23
|
+
const args = process.argv.slice(2);
|
|
24
|
+
|
|
25
|
+
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
26
|
+
showHelp();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const command = args[0];
|
|
31
|
+
|
|
32
|
+
switch (command) {
|
|
33
|
+
case "compile":
|
|
34
|
+
requireFile(args[1], runCompile);
|
|
35
|
+
break;
|
|
36
|
+
case "lex":
|
|
37
|
+
requireFile(args[1], runLex);
|
|
38
|
+
break;
|
|
39
|
+
case "parse":
|
|
40
|
+
requireFile(args[1], runParse);
|
|
41
|
+
break;
|
|
42
|
+
case "ir":
|
|
43
|
+
requireFile(args[1], runIR);
|
|
44
|
+
break;
|
|
45
|
+
case "check":
|
|
46
|
+
requireFile(args[1], runCheck);
|
|
47
|
+
break;
|
|
48
|
+
case "fmt":
|
|
49
|
+
requireFile(args[1], runFormat);
|
|
50
|
+
break;
|
|
51
|
+
case "watch":
|
|
52
|
+
requireFile(args[1], runWatch);
|
|
53
|
+
break;
|
|
54
|
+
case "init":
|
|
55
|
+
runInit(args.slice(1));
|
|
56
|
+
break;
|
|
57
|
+
case "diff":
|
|
58
|
+
runDiff(args.slice(1));
|
|
59
|
+
break;
|
|
60
|
+
case "debug":
|
|
61
|
+
requireFile(args[1], runDebug);
|
|
62
|
+
break;
|
|
63
|
+
case "test":
|
|
64
|
+
runTest(args.slice(1));
|
|
65
|
+
break;
|
|
66
|
+
case "verify-determinism":
|
|
67
|
+
requireFile(args[1], runVerifyDeterminism);
|
|
68
|
+
break;
|
|
69
|
+
default:
|
|
70
|
+
console.error(`Unknown command: ${command}`);
|
|
71
|
+
showHelp();
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function showHelp() {
|
|
77
|
+
console.log("BoneScript compiler v0.2.0");
|
|
78
|
+
console.log("");
|
|
79
|
+
console.log("Usage:");
|
|
80
|
+
console.log(" bonec compile <file> Compile to runnable project");
|
|
81
|
+
console.log(" bonec check <file> Lex + parse + type check (no codegen)");
|
|
82
|
+
console.log(" bonec lex <file> Show token stream");
|
|
83
|
+
console.log(" bonec parse <file> Show AST");
|
|
84
|
+
console.log(" bonec ir <file> Show IR (JSON)");
|
|
85
|
+
console.log(" bonec fmt <file> Format file in place");
|
|
86
|
+
console.log(" bonec watch <file> Recompile on change");
|
|
87
|
+
console.log(" bonec diff <old.bone> <new.bone> Show schema migration diff");
|
|
88
|
+
console.log("");
|
|
89
|
+
console.log("init options:");
|
|
90
|
+
console.log(" bonec init <name> --domain <name> Scaffold from a domain template");
|
|
91
|
+
console.log(" --domain <name> Domain template (default: saas_platform)");
|
|
92
|
+
console.log(" Options: multiplayer_game, saas_platform, iot_system,");
|
|
93
|
+
console.log(" social_network, marketplace, realtime_collaboration");
|
|
94
|
+
console.log(" --out <dir> Output directory (default: current dir)");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function requireFile(filePath: string | undefined, action: (source: string, resolved: string) => void) {
|
|
98
|
+
if (!filePath) {
|
|
99
|
+
console.error("Error: No input file specified.");
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const resolved = path.resolve(filePath);
|
|
104
|
+
if (!fs.existsSync(resolved)) {
|
|
105
|
+
console.error(`Error: File not found: ${resolved}`);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const source = fs.readFileSync(resolved, "utf-8");
|
|
110
|
+
action(source, resolved);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ─── Lex ─────────────────────────────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
function runLex(source: string) {
|
|
116
|
+
try {
|
|
117
|
+
const tokens = new Lexer(source).tokenize();
|
|
118
|
+
console.log(JSON.stringify(tokens, null, 2));
|
|
119
|
+
console.log(`\nv ${tokens.length} tokens produced.`);
|
|
120
|
+
} catch (e: any) {
|
|
121
|
+
console.error(`x ${e.message}`);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ─── Parse ───────────────────────────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
function runParse(source: string) {
|
|
129
|
+
try {
|
|
130
|
+
const tokens = new Lexer(source).tokenize();
|
|
131
|
+
const result = new RecoveringParser(tokens).parse();
|
|
132
|
+
if (result.errors.length > 0) {
|
|
133
|
+
console.error(`x ${result.errors.length} parse error(s):`);
|
|
134
|
+
for (const e of result.errors) console.error(` ${e.message}`);
|
|
135
|
+
if (!result.ast) process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
console.log(JSON.stringify(result.ast, null, 2));
|
|
138
|
+
console.log(`\nv Parsed ${result.ast?.systems.length || 0} system(s).`);
|
|
139
|
+
} catch (e: any) {
|
|
140
|
+
console.error(`x ${e.message}`);
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ─── IR ──────────────────────────────────────────────────────────────────────
|
|
146
|
+
|
|
147
|
+
function runIR(source: string) {
|
|
148
|
+
try {
|
|
149
|
+
const tokens = new Lexer(source).tokenize();
|
|
150
|
+
const ast = new Parser(tokens).parse();
|
|
151
|
+
const sourceHash = createHash("sha256").update(source).digest("hex").slice(0, 16);
|
|
152
|
+
const irSystems = new Lowering().lower(ast, sourceHash);
|
|
153
|
+
console.log(JSON.stringify(irSystems, null, 2));
|
|
154
|
+
console.log(`\nv Lowered to ${irSystems.length} IR system(s).`);
|
|
155
|
+
} catch (e: any) {
|
|
156
|
+
console.error(`x ${e.message}`);
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ─── Check ───────────────────────────────────────────────────────────────────
|
|
162
|
+
|
|
163
|
+
function runCheck(source: string) {
|
|
164
|
+
const tokens = new Lexer(source).tokenize();
|
|
165
|
+
const result = new RecoveringParser(tokens).parse();
|
|
166
|
+
|
|
167
|
+
let totalErrors = 0;
|
|
168
|
+
|
|
169
|
+
if (result.errors.length > 0) {
|
|
170
|
+
for (const e of result.errors) {
|
|
171
|
+
console.error(` parse: ${e.message}`);
|
|
172
|
+
totalErrors++;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (result.ast) {
|
|
177
|
+
const typeErrors = new TypeChecker().check(result.ast);
|
|
178
|
+
for (const err of typeErrors) {
|
|
179
|
+
console.error(` type: ${err.code} at ${err.loc.line}:${err.loc.column}: ${err.message}`);
|
|
180
|
+
totalErrors++;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (totalErrors === 0) {
|
|
185
|
+
console.log("v Check passed (0 errors)");
|
|
186
|
+
} else {
|
|
187
|
+
console.log(`x ${totalErrors} error(s) found.`);
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ─── Format ──────────────────────────────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
function runFormat(source: string, resolved: string) {
|
|
195
|
+
try {
|
|
196
|
+
const tokens = new Lexer(source).tokenize();
|
|
197
|
+
const ast = new Parser(tokens).parse();
|
|
198
|
+
const formatted = new Formatter().format(ast);
|
|
199
|
+
fs.writeFileSync(resolved, formatted, "utf-8");
|
|
200
|
+
console.log(`v Formatted ${resolved}`);
|
|
201
|
+
} catch (e: any) {
|
|
202
|
+
console.error(`x ${e.message}`);
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ─── Watch ───────────────────────────────────────────────────────────────────
|
|
208
|
+
|
|
209
|
+
function runWatch(_source: string, resolved: string) {
|
|
210
|
+
console.log(`Watching ${resolved}...`);
|
|
211
|
+
|
|
212
|
+
const compile = () => {
|
|
213
|
+
try {
|
|
214
|
+
const fresh = fs.readFileSync(resolved, "utf-8");
|
|
215
|
+
console.log(`\n[${new Date().toLocaleTimeString()}] Compiling...`);
|
|
216
|
+
runCompile(fresh, resolved);
|
|
217
|
+
} catch (e: any) {
|
|
218
|
+
console.error(`x ${e.message}`);
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
compile();
|
|
223
|
+
fs.watchFile(resolved, { interval: 500 }, (curr, prev) => {
|
|
224
|
+
if (curr.mtimeMs !== prev.mtimeMs) compile();
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ─── Init ────────────────────────────────────────────────────────────────────
|
|
229
|
+
|
|
230
|
+
function runInit(args: string[]) {
|
|
231
|
+
if (args.length === 0) {
|
|
232
|
+
console.error("Error: bone init requires a project name.");
|
|
233
|
+
console.error("Example: bone init my-project --domain saas_platform");
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const name = args[0];
|
|
238
|
+
let domain: ScaffoldDomain = "saas_platform";
|
|
239
|
+
let outDir = path.resolve(name);
|
|
240
|
+
|
|
241
|
+
for (let i = 1; i < args.length; i++) {
|
|
242
|
+
if (args[i] === "--domain" && args[i + 1]) {
|
|
243
|
+
domain = args[i + 1] as ScaffoldDomain;
|
|
244
|
+
i++;
|
|
245
|
+
} else if (args[i] === "--out" && args[i + 1]) {
|
|
246
|
+
outDir = path.resolve(args[i + 1]);
|
|
247
|
+
i++;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const validDomains: ScaffoldDomain[] = [
|
|
252
|
+
"multiplayer_game", "saas_platform", "iot_system",
|
|
253
|
+
"social_network", "marketplace", "realtime_collaboration",
|
|
254
|
+
];
|
|
255
|
+
if (!validDomains.includes(domain)) {
|
|
256
|
+
console.error(`Error: Invalid domain '${domain}'. Valid: ${validDomains.join(", ")}`);
|
|
257
|
+
process.exit(1);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const result = scaffold({ name, domain, outDir });
|
|
261
|
+
console.log(`v Created ${result.created.length} file(s):`);
|
|
262
|
+
for (const f of result.created) console.log(` ${f}`);
|
|
263
|
+
console.log(`\nNext steps:`);
|
|
264
|
+
console.log(` cd ${outDir}`);
|
|
265
|
+
console.log(` bone compile ${name}.bone`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ─── Compile (full pipeline) ─────────────────────────────────────────────────
|
|
269
|
+
|
|
270
|
+
function runCompile(source: string, resolved: string) {
|
|
271
|
+
try {
|
|
272
|
+
const tokens = new Lexer(source).tokenize();
|
|
273
|
+
console.log(` [1/7] Lexed: ${tokens.length} tokens`);
|
|
274
|
+
|
|
275
|
+
// Use module loader to handle imports
|
|
276
|
+
const loader = new ModuleLoader();
|
|
277
|
+
const loadResult = loader.load(resolved);
|
|
278
|
+
|
|
279
|
+
if (loadResult.errors.length > 0) {
|
|
280
|
+
console.log(` [2/7] Parse: ${loadResult.errors.length} error(s)`);
|
|
281
|
+
for (const e of loadResult.errors.slice(0, 10)) {
|
|
282
|
+
console.log(` ${path.basename(e.file)}: ${e.error.message}`);
|
|
283
|
+
}
|
|
284
|
+
if (!loadResult.ast) process.exit(1);
|
|
285
|
+
} else {
|
|
286
|
+
const sysCount = loadResult.ast?.systems.length || 0;
|
|
287
|
+
console.log(` [2/7] Parsed: ${sysCount} system(s) from ${loadResult.loadedFiles.length} file(s)`);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const ast = loadResult.ast!;
|
|
291
|
+
|
|
292
|
+
for (const sys of ast.systems) {
|
|
293
|
+
console.log(` System '${sys.name}':`);
|
|
294
|
+
const counts: Record<string, number> = {};
|
|
295
|
+
for (const d of sys.declarations) counts[d.kind] = (counts[d.kind] || 0) + 1;
|
|
296
|
+
for (const [kind, count] of Object.entries(counts)) {
|
|
297
|
+
console.log(` ${kind}: ${count}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Stage 3: Type Check
|
|
302
|
+
const checker = new TypeChecker();
|
|
303
|
+
const typeErrors = checker.check(ast);
|
|
304
|
+
if (typeErrors.length > 0) {
|
|
305
|
+
console.log(` [3/7] Type check: ${typeErrors.length} error(s)`);
|
|
306
|
+
for (const err of typeErrors) {
|
|
307
|
+
console.log(` ${err.code} at ${err.loc.line}:${err.loc.column}: ${err.message}`);
|
|
308
|
+
}
|
|
309
|
+
} else {
|
|
310
|
+
console.log(` [3/7] Type check: v (0 errors)`);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Stage 4: Lower to IR
|
|
314
|
+
const sourceHash = createHash("sha256").update(source).digest("hex").slice(0, 16);
|
|
315
|
+
const lowering = new Lowering();
|
|
316
|
+
const irSystems = lowering.lower(ast, sourceHash);
|
|
317
|
+
const totalModules = irSystems.reduce((sum, s) => sum + s.modules.length, 0);
|
|
318
|
+
const totalEvents = irSystems.reduce((sum, s) => sum + s.events.length, 0);
|
|
319
|
+
const totalFlows = irSystems.reduce((sum, s) => sum + s.flows.length, 0);
|
|
320
|
+
console.log(` [4/7] Lower to IR: ${totalModules} modules, ${totalEvents} events, ${totalFlows} flows`);
|
|
321
|
+
for (const sys of irSystems) {
|
|
322
|
+
for (const mod of sys.modules) {
|
|
323
|
+
const methodCount = mod.interfaces.reduce((s, i) => s + i.methods.length, 0);
|
|
324
|
+
console.log(` ${mod.kind.padEnd(16)} ${mod.name.padEnd(24)} (${methodCount} methods, ${mod.models.length} models)`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Stage 4.5: IR Optimization
|
|
329
|
+
for (let i = 0; i < irSystems.length; i++) {
|
|
330
|
+
const result = optimize(irSystems[i]);
|
|
331
|
+
irSystems[i] = result.system;
|
|
332
|
+
if (result.log.length > 0) {
|
|
333
|
+
console.log(` [4.5] IR optimize: ${result.modulesRemoved} modules removed, ${result.eventsDeduped} events deduped, ${result.depsRemoved} deps minimized`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Stage 5: Constraint Solve
|
|
338
|
+
const solver = new ConstraintSolver();
|
|
339
|
+
let totalResolved = 0;
|
|
340
|
+
for (const sys of irSystems) {
|
|
341
|
+
const result = solver.solve(sys);
|
|
342
|
+
sys.resolution = result.resolution;
|
|
343
|
+
totalResolved += Object.keys(result.resolution).length;
|
|
344
|
+
if (result.errors.length > 0) {
|
|
345
|
+
console.log(` [5/7] Constraint solve: ${result.errors.length} error(s)`);
|
|
346
|
+
for (const err of result.errors) console.log(` x ${err}`);
|
|
347
|
+
} else {
|
|
348
|
+
console.log(` [5/7] Constraint solve: v (${totalResolved} resolved, ${result.assumptions.length} assumptions)`);
|
|
349
|
+
for (const a of result.assumptions.slice(0, 5)) console.log(` ${a}`);
|
|
350
|
+
if (result.assumptions.length > 5) console.log(` ... and ${result.assumptions.length - 5} more`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Stage 6: Code Emit
|
|
355
|
+
const emitter = new FullEmitter();
|
|
356
|
+
const allFiles: ReturnType<typeof emitter.emit> = [];
|
|
357
|
+
for (const sys of irSystems) {
|
|
358
|
+
const files = emitter.emit(sys);
|
|
359
|
+
allFiles.push(...files);
|
|
360
|
+
}
|
|
361
|
+
console.log(` [6/7] Code emit: ${allFiles.length} files generated`);
|
|
362
|
+
const byLang: Record<string, number> = {};
|
|
363
|
+
for (const f of allFiles) byLang[f.language] = (byLang[f.language] || 0) + 1;
|
|
364
|
+
for (const [lang, count] of Object.entries(byLang)) {
|
|
365
|
+
console.log(` ${lang}: ${count} file(s)`);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Stage 7: Verify
|
|
369
|
+
const verifier = new Verifier();
|
|
370
|
+
const verifyResult = verifier.verify(irSystems[0], allFiles);
|
|
371
|
+
const errCount = verifyResult.issues.filter(i => i.severity === "error").length;
|
|
372
|
+
const warnCount = verifyResult.issues.filter(i => i.severity === "warning").length;
|
|
373
|
+
if (verifyResult.passed) {
|
|
374
|
+
console.log(` [7/7] Verify: v (${allFiles.length} files, ${warnCount} warnings)`);
|
|
375
|
+
} else {
|
|
376
|
+
console.log(` [7/7] Verify: FAILED (${errCount} errors, ${warnCount} warnings)`);
|
|
377
|
+
}
|
|
378
|
+
for (const issue of verifyResult.issues.slice(0, 10)) {
|
|
379
|
+
const icon = issue.severity === "error" ? "x" : "!";
|
|
380
|
+
console.log(` ${icon} ${issue.code}: ${issue.message}`);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Write output — merge extension point implementations from existing files
|
|
384
|
+
const outputDir = path.resolve(path.dirname(resolved), "output");
|
|
385
|
+
const allExtensions = irSystems.flatMap(s => s.extension_points || []);
|
|
386
|
+
let extensionErrors: string[] = [];
|
|
387
|
+
|
|
388
|
+
for (const f of allFiles) {
|
|
389
|
+
const outPath = path.join(outputDir, f.path);
|
|
390
|
+
const dir = path.dirname(outPath);
|
|
391
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
392
|
+
|
|
393
|
+
// For extensions.ts: merge preserved implementations
|
|
394
|
+
if (f.path === "src/extensions.ts" && allExtensions.length > 0) {
|
|
395
|
+
const astExtensions = ast.systems.flatMap(s =>
|
|
396
|
+
s.declarations.filter((d): d is any => d.kind === "ExtensionPointDecl")
|
|
397
|
+
);
|
|
398
|
+
const { content, validationErrors } = mergeWithExisting(f.content, outPath, astExtensions);
|
|
399
|
+
for (const e of validationErrors) extensionErrors.push(e.message);
|
|
400
|
+
fs.writeFileSync(outPath, content, "utf-8");
|
|
401
|
+
} else {
|
|
402
|
+
fs.writeFileSync(outPath, f.content, "utf-8");
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (extensionErrors.length > 0) {
|
|
407
|
+
console.log(`\n Extension point errors:`);
|
|
408
|
+
for (const e of extensionErrors) console.log(` x ${e}`);
|
|
409
|
+
process.exit(1);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
console.log(`\nv Compilation complete. ${allFiles.length} files written to output/`);
|
|
413
|
+
} catch (e: any) {
|
|
414
|
+
console.error(`x ${e.message}`);
|
|
415
|
+
process.exit(1);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
main();
|
|
420
|
+
|
|
421
|
+
// ─── Diff ─────────────────────────────────────────────────────────────────────
|
|
422
|
+
|
|
423
|
+
function runDiff(args: string[]) {
|
|
424
|
+
if (args.length < 2) {
|
|
425
|
+
console.error("Usage: bone diff <old.bone> <new.bone>");
|
|
426
|
+
process.exit(1);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const [oldFile, newFile] = args;
|
|
430
|
+
|
|
431
|
+
const compileToIR = (filePath: string) => {
|
|
432
|
+
const resolved = path.resolve(filePath);
|
|
433
|
+
if (!fs.existsSync(resolved)) {
|
|
434
|
+
console.error(`File not found: ${resolved}`);
|
|
435
|
+
process.exit(1);
|
|
436
|
+
}
|
|
437
|
+
const source = fs.readFileSync(resolved, "utf-8");
|
|
438
|
+
const tokens = new Lexer(source).tokenize();
|
|
439
|
+
const ast = new Parser(tokens).parse();
|
|
440
|
+
const hash = createHash("sha256").update(source).digest("hex").slice(0, 16);
|
|
441
|
+
return new Lowering().lower(ast, hash);
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
const oldIR = compileToIR(oldFile);
|
|
445
|
+
const newIR = compileToIR(newFile);
|
|
446
|
+
|
|
447
|
+
const oldModels: any[] = [];
|
|
448
|
+
const newModels: any[] = [];
|
|
449
|
+
for (const sys of oldIR) for (const mod of sys.modules) for (const m of mod.models) oldModels.push(m);
|
|
450
|
+
for (const sys of newIR) for (const mod of sys.modules) for (const m of mod.models) newModels.push(m);
|
|
451
|
+
|
|
452
|
+
const oldByName = new Map(oldModels.map(m => [m.name, m]));
|
|
453
|
+
const newByName = new Map(newModels.map(m => [m.name, m]));
|
|
454
|
+
const statements: string[] = [];
|
|
455
|
+
|
|
456
|
+
// New tables
|
|
457
|
+
for (const [name, model] of newByName) {
|
|
458
|
+
if (!oldByName.has(name)) {
|
|
459
|
+
statements.push(`-- NEW TABLE: ${name}`);
|
|
460
|
+
statements.push(`-- Run: bone compile ${newFile} (generates full migration)`);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Removed tables
|
|
465
|
+
for (const [name] of oldByName) {
|
|
466
|
+
if (!newByName.has(name)) {
|
|
467
|
+
const table = name.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase() + "s";
|
|
468
|
+
statements.push(`-- WARNING: Table '${table}' removed from schema`);
|
|
469
|
+
statements.push(`-- Manual: ALTER TABLE ${table} ... (or DROP TABLE ${table})`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Modified tables
|
|
474
|
+
for (const [name, newModel] of newByName) {
|
|
475
|
+
const oldModel = oldByName.get(name);
|
|
476
|
+
if (!oldModel) continue;
|
|
477
|
+
|
|
478
|
+
const table = name.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase() + "s";
|
|
479
|
+
const oldFields = new Map(oldModel.fields.map((f: any) => [f.name, f]));
|
|
480
|
+
const newFields = new Map(newModel.fields.map((f: any) => [f.name, f]));
|
|
481
|
+
|
|
482
|
+
const sqlTypeMap: Record<string, string> = {
|
|
483
|
+
string: "VARCHAR", uint: "BIGINT", int: "BIGINT", float: "DOUBLE PRECISION",
|
|
484
|
+
bool: "BOOLEAN", timestamp: "TIMESTAMPTZ", uuid: "UUID", bytes: "BYTEA", json: "JSONB",
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
for (const [fname, field] of newFields) {
|
|
488
|
+
if (!oldFields.has(fname)) {
|
|
489
|
+
const sqlType = sqlTypeMap[(field as any).type] || "JSONB";
|
|
490
|
+
statements.push(`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${fname} ${sqlType};`);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
for (const [fname] of oldFields) {
|
|
495
|
+
if (!newFields.has(fname)) {
|
|
496
|
+
statements.push(`-- WARNING: Column '${table}.${fname}' removed`);
|
|
497
|
+
statements.push(`-- Manual: ALTER TABLE ${table} DROP COLUMN ${fname};`);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (statements.length === 0) {
|
|
503
|
+
console.log("No schema changes detected.");
|
|
504
|
+
} else {
|
|
505
|
+
console.log(`-- BoneScript schema diff: ${path.basename(oldFile)} → ${path.basename(newFile)}`);
|
|
506
|
+
console.log(`-- Generated: ${new Date().toISOString()}`);
|
|
507
|
+
console.log(``);
|
|
508
|
+
console.log(statements.join("\n"));
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// ─── Debug ────────────────────────────────────────────────────────────────────
|
|
513
|
+
|
|
514
|
+
function runDebug(source: string, resolved: string) {
|
|
515
|
+
try {
|
|
516
|
+
const tokens = new Lexer(source).tokenize();
|
|
517
|
+
const ast = new Parser(tokens).parse();
|
|
518
|
+
const sourceHash = createHash("sha256").update(source).digest("hex").slice(0, 16);
|
|
519
|
+
const irSystems = new Lowering().lower(ast, sourceHash);
|
|
520
|
+
|
|
521
|
+
const { emitSourceMapFile } = require("./emit_sourcemap");
|
|
522
|
+
for (const sys of irSystems) {
|
|
523
|
+
const mapContent = emitSourceMapFile(sys, path.basename(resolved));
|
|
524
|
+
const mapPath = path.join(path.dirname(resolved), `${sys.name}.bone.map`);
|
|
525
|
+
fs.writeFileSync(mapPath, mapContent, "utf-8");
|
|
526
|
+
console.log(`v Source map written: ${mapPath}`);
|
|
527
|
+
console.log(` ${sys.modules.length} modules mapped`);
|
|
528
|
+
console.log(` Use output/src/debug.ts to get annotated runtime errors`);
|
|
529
|
+
}
|
|
530
|
+
} catch (e: any) {
|
|
531
|
+
console.error(`x ${e.message}`);
|
|
532
|
+
process.exit(1);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// ─── Test ─────────────────────────────────────────────────────────────────────
|
|
537
|
+
|
|
538
|
+
function runTest(args: string[]) {
|
|
539
|
+
const outputDir = args[0] ? path.resolve(args[0]) : path.resolve("output");
|
|
540
|
+
const testFile = path.join(outputDir, "src", "tests.ts");
|
|
541
|
+
|
|
542
|
+
if (!fs.existsSync(testFile)) {
|
|
543
|
+
console.error(`No test file found at ${testFile}`);
|
|
544
|
+
console.error("Run 'bone compile <file>' first to generate tests.");
|
|
545
|
+
process.exit(1);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
console.log(`Running BoneScript regression tests...`);
|
|
549
|
+
console.log(`Test file: ${testFile}`);
|
|
550
|
+
console.log(`Target: ${process.env.TEST_BASE_URL || "http://localhost:3000"}`);
|
|
551
|
+
console.log(``);
|
|
552
|
+
|
|
553
|
+
// Run the generated test file using ts-node
|
|
554
|
+
const { execSync } = require("child_process");
|
|
555
|
+
try {
|
|
556
|
+
execSync(`npx ts-node ${testFile}`, {
|
|
557
|
+
cwd: outputDir,
|
|
558
|
+
stdio: "inherit",
|
|
559
|
+
env: { ...process.env },
|
|
560
|
+
});
|
|
561
|
+
} catch {
|
|
562
|
+
process.exit(1);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// ─── Verify Determinism ───────────────────────────────────────────────────────
|
|
567
|
+
|
|
568
|
+
function runVerifyDeterminism(source: string, resolved: string) {
|
|
569
|
+
console.log("Verifying compilation determinism...");
|
|
570
|
+
|
|
571
|
+
const compile = () => {
|
|
572
|
+
const tokens = new Lexer(source).tokenize();
|
|
573
|
+
const ast = new Parser(tokens).parse();
|
|
574
|
+
const hash = createHash("sha256").update(source).digest("hex").slice(0, 16);
|
|
575
|
+
const ir = new Lowering().lower(ast, hash);
|
|
576
|
+
const emitter = new FullEmitter();
|
|
577
|
+
const files: { path: string; content: string }[] = [];
|
|
578
|
+
for (const sys of ir) {
|
|
579
|
+
for (const f of emitter.emit(sys)) {
|
|
580
|
+
files.push({ path: f.path, content: f.content });
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
// Sort for canonical comparison
|
|
584
|
+
files.sort((a, b) => a.path.localeCompare(b.path));
|
|
585
|
+
return JSON.stringify(files);
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
const run1 = compile();
|
|
589
|
+
const run2 = compile();
|
|
590
|
+
|
|
591
|
+
if (run1 === run2) {
|
|
592
|
+
const hash = createHash("sha256").update(run1).digest("hex").slice(0, 16);
|
|
593
|
+
console.log(`v Deterministic. Both runs produced identical output.`);
|
|
594
|
+
console.log(` Output hash: ${hash}`);
|
|
595
|
+
} else {
|
|
596
|
+
// Find first divergence
|
|
597
|
+
const files1: { path: string; content: string }[] = JSON.parse(run1);
|
|
598
|
+
const files2: { path: string; content: string }[] = JSON.parse(run2);
|
|
599
|
+
|
|
600
|
+
for (let i = 0; i < Math.max(files1.length, files2.length); i++) {
|
|
601
|
+
const f1 = files1[i];
|
|
602
|
+
const f2 = files2[i];
|
|
603
|
+
if (!f1 || !f2 || f1.path !== f2.path || f1.content !== f2.content) {
|
|
604
|
+
console.error(`x NON-DETERMINISTIC: First divergence at file ${i}`);
|
|
605
|
+
console.error(` Run 1: ${f1?.path || "(missing)"}`);
|
|
606
|
+
console.error(` Run 2: ${f2?.path || "(missing)"}`);
|
|
607
|
+
if (f1 && f2 && f1.path === f2.path) {
|
|
608
|
+
// Find first differing line
|
|
609
|
+
const lines1 = f1.content.split("\n");
|
|
610
|
+
const lines2 = f2.content.split("\n");
|
|
611
|
+
for (let j = 0; j < Math.max(lines1.length, lines2.length); j++) {
|
|
612
|
+
if (lines1[j] !== lines2[j]) {
|
|
613
|
+
console.error(` First differing line ${j + 1}:`);
|
|
614
|
+
console.error(` Run 1: ${lines1[j]}`);
|
|
615
|
+
console.error(` Run 2: ${lines2[j]}`);
|
|
616
|
+
break;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
process.exit(1);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|