lemmascript 0.0.1 → 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.
@@ -0,0 +1,7 @@
1
+ /**
2
+ * IR — the intermediate representation between transform and emit.
3
+ *
4
+ * The transform phase produces these types.
5
+ * The emit phase pretty-prints them to backend syntax (Lean or Dafny).
6
+ */
7
+ export {};
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Lean backend commands: gen, check.
3
+ */
4
+ import { existsSync, writeFileSync } from "fs";
5
+ import { execSync } from "child_process";
6
+ import path from "path";
7
+ export function leanGen(typesPath, defPath, typesText, defText) {
8
+ if (typesPath && typesText) {
9
+ writeFileSync(typesPath, typesText);
10
+ console.log(`Generated: ${typesPath}`);
11
+ }
12
+ writeFileSync(defPath, defText);
13
+ console.log(`Generated: ${defPath}`);
14
+ }
15
+ export function leanCheck(dir, base) {
16
+ let lakeDir = dir;
17
+ while (lakeDir !== path.dirname(lakeDir)) {
18
+ if (existsSync(path.join(lakeDir, "lakefile.lean")))
19
+ break;
20
+ lakeDir = path.dirname(lakeDir);
21
+ }
22
+ const proofPath = path.join(dir, `${base}.proof.lean`);
23
+ if (!existsSync(proofPath)) {
24
+ console.error(`No proof file: ${proofPath}`);
25
+ return false;
26
+ }
27
+ console.log("Running lake build...");
28
+ try {
29
+ execSync(`lake build`, { cwd: lakeDir, stdio: "inherit" });
30
+ return true;
31
+ }
32
+ catch {
33
+ return false;
34
+ }
35
+ }
@@ -0,0 +1,393 @@
1
+ /**
2
+ * Lean emitter — IR → Lean text.
3
+ * No logic, no type decisions — just serialization.
4
+ */
5
+ // ── Ty → Lean type string ──────────────────────────────────
6
+ function tyToLean(ty) {
7
+ switch (ty.kind) {
8
+ case "nat": return "Nat";
9
+ case "int": return "Int";
10
+ case "real": return "Float"; // Lean doesn't have exact reals; Float is approximate
11
+ case "bool": return "Bool";
12
+ case "string": return "String";
13
+ case "void": return "Unit";
14
+ case "array": {
15
+ const elem = tyToLean(ty.elem);
16
+ return elem.includes(" ") ? `Array (${elem})` : `Array ${elem}`;
17
+ }
18
+ case "map": {
19
+ const k = tyToLean(ty.key);
20
+ const v = tyToLean(ty.value);
21
+ const kStr = k.includes(" ") ? `(${k})` : k;
22
+ const vStr = v.includes(" ") ? `(${v})` : v;
23
+ return `Std.HashMap ${kStr} ${vStr}`;
24
+ }
25
+ case "set": {
26
+ const elem = tyToLean(ty.elem);
27
+ return elem.includes(" ") ? `Std.HashSet (${elem})` : `Std.HashSet ${elem}`;
28
+ }
29
+ case "optional": {
30
+ const inner = tyToLean(ty.inner);
31
+ return inner.includes(" ") ? `Option (${inner})` : `Option ${inner}`;
32
+ }
33
+ case "user": return ty.name;
34
+ case "unknown": return "_";
35
+ }
36
+ }
37
+ // ── Lean keyword escaping ────────────────────────────────────
38
+ const LEAN_KEYWORDS = new Set([
39
+ "def", "theorem", "lemma", "example", "structure", "class", "instance",
40
+ "inductive", "where", "match", "with", "if", "then", "else", "do",
41
+ "let", "mut", "return", "for", "in", "while", "break", "continue",
42
+ "import", "open", "section", "namespace", "end", "set_option",
43
+ "variable", "axiom", "constant", "private", "protected", "noncomputable",
44
+ "partial", "unsafe", "macro", "syntax", "by", "fun", "have", "show",
45
+ "at", "from", "to", "deriving", "extends", "true", "false",
46
+ ]);
47
+ function escapeName(name) {
48
+ return LEAN_KEYWORDS.has(name) ? `«${name}»` : name;
49
+ }
50
+ // ── Operator precedence (for parenthesization) ──────────────
51
+ const PREC = {
52
+ "→": 1, "∨": 2, "∧": 3,
53
+ "=": 4, "≠": 4, "≥": 4, "≤": 4, ">": 4, "<": 4,
54
+ "+": 5, "-": 5, "*": 6, "/": 6, "%": 6,
55
+ };
56
+ function prec(op) { return PREC[op] ?? 10; }
57
+ // ── Method call → Lean syntax ───────────────────────────────
58
+ let _needsJSString = false;
59
+ function emitMethodCall(tyKind, method, monadic, obj, args) {
60
+ // Array methods
61
+ if (tyKind === "array") {
62
+ if (method === "map")
63
+ return `${obj}.${monadic ? "mapM" : "map"} ${args[0]}`;
64
+ if (method === "filter")
65
+ return `${obj}.${monadic ? "filterM" : "filter"} ${args[0]}`;
66
+ if (method === "every")
67
+ return `${obj}.${monadic ? "allM" : "all"} ${args[0]}`;
68
+ if (method === "some")
69
+ return `${obj}.${monadic ? "anyM" : "any"} ${args[0]}`;
70
+ if (method === "includes")
71
+ return `${obj}.contains ${args[0]}`;
72
+ if (method === "find")
73
+ return `${obj}.find? ${args[0]}`;
74
+ if (method === "with")
75
+ return `${obj}.set! ${args[0]} ${args[1]}`;
76
+ if (method === "push")
77
+ return `Array.push ${obj} ${args[0]}`;
78
+ }
79
+ // String methods
80
+ if (tyKind === "string") {
81
+ _needsJSString = true;
82
+ if (method === "indexOf")
83
+ return `JSString.indexOf ${obj} ${args[0]}`;
84
+ if (method === "slice")
85
+ return `JSString.slice ${obj} ${args[0]} ${args[1]}`;
86
+ }
87
+ // Map methods
88
+ if (tyKind === "map") {
89
+ if (method === "get")
90
+ return `${obj}.get? ${args[0]}`;
91
+ if (method === "getDirect")
92
+ return `${obj}.get! ${args[0]}`;
93
+ if (method === "has")
94
+ return `${obj}.contains ${args[0]}`;
95
+ if (method === "set")
96
+ return `${obj}.insert ${args[0]} ${args[1]}`;
97
+ }
98
+ // Set methods
99
+ if (tyKind === "set") {
100
+ if (method === "has")
101
+ return `${obj}.contains ${args[0]}`;
102
+ if (method === "add")
103
+ return `${obj}.insert ${args[0]}`;
104
+ if (method === "delete")
105
+ return `${obj}.erase ${args[0]}`;
106
+ }
107
+ throw new Error(`Unsupported Lean method call: .${method}() on ${tyKind}`);
108
+ }
109
+ // ── Expression emission ─────────────────────────────────────
110
+ function emitExpr(e, parentPrec) {
111
+ switch (e.kind) {
112
+ case "var": return escapeName(e.name);
113
+ case "num": return `${e.value}`;
114
+ case "bool": return e.value ? "true" : "false";
115
+ case "str": return `"${e.value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n')}"`;
116
+ case "constructor": return `.${e.name}`;
117
+ case "arrayLiteral":
118
+ if (e.elems.length === 0)
119
+ return `#[]`;
120
+ return `#[${e.elems.map(el => emitExpr(el)).join(", ")}]`;
121
+ case "emptyMap": return `Std.HashMap.empty`;
122
+ case "emptySet": return `Std.HashSet.empty`;
123
+ case "methodCall": {
124
+ const obj = emitExpr(e.obj);
125
+ const wrap = e.obj.kind === "binop" || e.obj.kind === "app" || e.obj.kind === "methodCall";
126
+ const receiver = wrap ? `(${obj})` : obj;
127
+ const args = e.args.map(a => (a.kind === "binop" || a.kind === "unop" || a.kind === "implies" || a.kind === "app" || a.kind === "methodCall") ? `(${emitExpr(a)})` : emitExpr(a));
128
+ return emitMethodCall(e.objTy.kind, e.method, e.monadic, receiver, args);
129
+ }
130
+ case "lambda": {
131
+ const params = e.params.map(p => p.name).join(" ");
132
+ // Single return statement → expression lambda
133
+ if (e.body.length === 1 && e.body[0].kind === "return") {
134
+ return `(fun ${params} => ${emitExpr(e.body[0].value)})`;
135
+ }
136
+ // Multi-statement → do block
137
+ return `(fun ${params} => do\n${emitStmts(e.body, 2)})`;
138
+ }
139
+ case "unop":
140
+ if (e.op === "¬")
141
+ return `¬(${emitExpr(e.expr)})`;
142
+ if (e.op === "-" && e.expr.kind === "num")
143
+ return `-${e.expr.value}`;
144
+ return `(-${emitExpr(e.expr)})`;
145
+ case "binop": {
146
+ const s = `${emitExpr(e.left, prec(e.op))} ${e.op} ${emitExpr(e.right, prec(e.op))}`;
147
+ return (parentPrec !== undefined && prec(e.op) < parentPrec) ? `(${s})` : s;
148
+ }
149
+ case "implies": {
150
+ const parts = [...e.premises.map(p => emitExpr(p)), emitExpr(e.conclusion)];
151
+ const s = parts.join(" → ");
152
+ return parentPrec !== undefined ? `(${s})` : s;
153
+ }
154
+ case "app": {
155
+ const args = e.args.map(a => (a.kind === "binop" || a.kind === "unop" || a.kind === "implies" || a.kind === "app" || a.kind === "methodCall") ? `(${emitExpr(a)})` : emitExpr(a));
156
+ // SetToSeq → .toArray for Lean (HashSet has native toArray)
157
+ if (e.fn === "SetToSeq" && args.length === 1)
158
+ return `${args[0]}.toArray`;
159
+ return `${e.fn} ${args.join(" ")}`;
160
+ }
161
+ case "field": {
162
+ const obj = emitExpr(e.obj);
163
+ if (e.field === "collectionSize")
164
+ return `${obj}.size`;
165
+ const wrap = e.obj.kind !== "var" && e.obj.kind !== "num" && e.obj.kind !== "bool";
166
+ return wrap ? `(${obj}).${escapeName(e.field)}` : `${obj}.${escapeName(e.field)}`;
167
+ }
168
+ case "toNat": {
169
+ const inner = emitExpr(e.expr);
170
+ const wrap = e.expr.kind !== "var" && e.expr.kind !== "num";
171
+ return wrap ? `(${inner}).toNat` : `${inner}.toNat`;
172
+ }
173
+ case "index":
174
+ return `${emitExpr(e.arr)}[${emitExpr(e.idx)}]!`;
175
+ case "record": {
176
+ const fields = e.fields.map(f => `${escapeName(f.name)} := ${emitExpr(f.value)}`);
177
+ if (e.spread)
178
+ return `{ ${emitExpr(e.spread)} with ${fields.join(", ")} }`;
179
+ return `{ ${fields.join(", ")} }`;
180
+ }
181
+ case "if":
182
+ return `if ${emitExpr(e.cond)} then ${emitExpr(e.then)} else ${emitExpr(e.else)}`;
183
+ case "match": {
184
+ const arms = e.arms.map(a => `| ${a.pattern} => ${emitExpr(a.body)}`);
185
+ return `match ${typeof e.scrutinee === "string" ? e.scrutinee : emitExpr(e.scrutinee)} with ${arms.join(" ")}`;
186
+ }
187
+ case "forall": return `∀ ${e.var} : ${tyToLean(e.type)}, ${emitExpr(e.body)}`;
188
+ case "exists": return `∃ ${e.var} : ${tyToLean(e.type)}, ${emitExpr(e.body)}`;
189
+ case "let": return `let ${e.name} := ${emitExpr(e.value)}\n${emitExpr(e.body)}`;
190
+ default: throw new Error(`Unsupported Lean expression: ${e.kind}`);
191
+ }
192
+ }
193
+ // ── Statement emission ──────────────────────────────────────
194
+ function emitStmts(stmts, indent) {
195
+ const pad = " ".repeat(indent);
196
+ return stmts.map(s => emitStmt(s, indent)).join("\n");
197
+ }
198
+ function emitStmt(s, indent) {
199
+ const pad = " ".repeat(indent);
200
+ switch (s.kind) {
201
+ case "let":
202
+ return s.mutable
203
+ ? `${pad}let mut ${escapeName(s.name)} : ${tyToLean(s.type)} := ${emitExpr(s.value)}`
204
+ : `${pad}let ${escapeName(s.name)} := ${emitExpr(s.value)}`;
205
+ case "assign": return `${pad}${escapeName(s.target)} := ${emitExpr(s.value)}`;
206
+ case "ghostLet":
207
+ return `${pad}let mut ${escapeName(s.name)} : ${tyToLean(s.type)} := ${emitExpr(s.value)}`;
208
+ case "ghostAssign": return `${pad}${escapeName(s.target)} := ${emitExpr(s.value)}`;
209
+ case "assert": return `${pad}assertGadget (${emitExpr(s.expr)})`;
210
+ case "bind": return `${pad}${escapeName(s.target)} ← ${emitExpr(s.value)}`;
211
+ case "let-bind": return `${pad}let ${s.name} ← ${emitExpr(s.value)}`;
212
+ case "return": return `${pad}return ${emitExpr(s.value)}`;
213
+ case "break": return `${pad}break`;
214
+ case "continue": return `${pad}continue`;
215
+ case "if": {
216
+ let out = `${pad}if ${emitExpr(s.cond)} then\n${emitStmts(s.then, indent + 1)}`;
217
+ if (s.else.length > 0) {
218
+ if (s.else.length === 1 && s.else[0].kind === "if") {
219
+ const ei = s.else[0];
220
+ out += `\n${pad}else if ${emitExpr(ei.cond)} then\n${emitStmts(ei.then, indent + 1)}`;
221
+ if (ei.else.length > 0)
222
+ out += `\n${pad}else\n${emitStmts(ei.else, indent + 1)}`;
223
+ }
224
+ else {
225
+ out += `\n${pad}else\n${emitStmts(s.else, indent + 1)}`;
226
+ }
227
+ }
228
+ return out;
229
+ }
230
+ case "match": {
231
+ const scrut = typeof s.scrutinee === "string" ? s.scrutinee : emitExpr(s.scrutinee);
232
+ // Option match (.some/.none) → emit as if/let for WPGen.if compatibility
233
+ if (s.arms.length === 2) {
234
+ const someArm = s.arms.find(a => a.pattern.startsWith(".some "));
235
+ const noneArm = s.arms.find(a => a.pattern === ".none");
236
+ if (someArm && noneArm) {
237
+ const boundVar = someArm.pattern.slice(6); // strip ".some "
238
+ const hName = `h_${scrut.replace(/[^a-zA-Z0-9_]/g, "_")}`;
239
+ const lines = [
240
+ `${pad}if ${hName} : (${scrut}).isSome = true then`,
241
+ `${pad} let ${boundVar} := (${scrut}).get ${hName}`,
242
+ ];
243
+ if (someArm.body.length === 0) {
244
+ lines.push(`${pad} pure ()`);
245
+ }
246
+ else {
247
+ lines.push(emitStmts(someArm.body, indent + 1));
248
+ }
249
+ lines.push(`${pad}else`);
250
+ if (noneArm.body.length === 0) {
251
+ lines.push(`${pad} pure ()`);
252
+ }
253
+ else {
254
+ lines.push(emitStmts(noneArm.body, indent + 1));
255
+ }
256
+ return lines.join("\n");
257
+ }
258
+ }
259
+ // General match
260
+ const lines = [`${pad}match ${scrut} with`];
261
+ for (const arm of s.arms) {
262
+ lines.push(`${pad}| ${arm.pattern} =>`);
263
+ if (arm.body.length === 0) {
264
+ lines.push(`${pad} pure ()`);
265
+ }
266
+ else {
267
+ lines.push(emitStmts(arm.body, indent + 1));
268
+ }
269
+ }
270
+ return lines.join("\n");
271
+ }
272
+ case "while": {
273
+ const lines = [`${pad}while ${emitExpr(s.cond)}`];
274
+ for (const inv of s.invariants)
275
+ lines.push(`${pad} invariant ${emitExpr(inv)}`);
276
+ if (s.doneWith)
277
+ lines.push(`${pad} done_with ${emitExpr(s.doneWith)}`);
278
+ if (s.decreasing)
279
+ lines.push(`${pad} decreasing ${emitExpr(s.decreasing)}`);
280
+ lines.push(`${pad}do`);
281
+ lines.push(emitStmts(s.body, indent + 1));
282
+ return lines.join("\n");
283
+ }
284
+ case "forin": {
285
+ const lines = [`${pad}for ${s.idx} in [:${emitExpr(s.bound)}]`];
286
+ for (const inv of s.invariants)
287
+ lines.push(`${pad} invariant ${emitExpr(inv)}`);
288
+ lines.push(`${pad}do`);
289
+ lines.push(emitStmts(s.body, indent + 1));
290
+ return lines.join("\n");
291
+ }
292
+ }
293
+ }
294
+ // ── Declaration emission ─────────────────────────────────────
295
+ function emitDecl(d) {
296
+ switch (d.kind) {
297
+ case "inductive": {
298
+ const lines = [`inductive ${d.name} where`];
299
+ for (const c of d.constructors) {
300
+ if (c.fields.length === 0) {
301
+ lines.push(` | ${c.name} : ${d.name}`);
302
+ }
303
+ else {
304
+ const params = c.fields.map(f => `(${escapeName(f.name)} : ${tyToLean(f.type)})`).join(" ");
305
+ lines.push(` | ${c.name} ${params} : ${d.name}`);
306
+ }
307
+ }
308
+ if (d.deriving.length > 0)
309
+ lines.push(`deriving ${d.deriving.join(", ")}`);
310
+ return lines.join("\n");
311
+ }
312
+ case "structure": {
313
+ const lines = [`structure ${d.name} where`];
314
+ for (const f of d.fields)
315
+ lines.push(` ${escapeName(f.name)} : ${tyToLean(f.type)}`);
316
+ if (d.deriving.length > 0)
317
+ lines.push(`deriving ${d.deriving.join(", ")}`);
318
+ return lines.join("\n");
319
+ }
320
+ case "def": {
321
+ const params = d.params.map(p => `(${escapeName(p.name)} : ${tyToLean(p.type)})`).join(" ");
322
+ return `def ${d.name} ${params} : ${tyToLean(d.returnType)} :=\n${emitPureExpr(d.body, 1)}`;
323
+ }
324
+ case "method": {
325
+ const params = d.params.map(p => `(${escapeName(p.name)} : ${tyToLean(p.type)})`).join(" ");
326
+ const lines = [`method ${d.name} ${params} return (res : ${tyToLean(d.returnType)})`];
327
+ for (const r of d.requires)
328
+ lines.push(` require ${emitExpr(r)}`);
329
+ for (const e of d.ensures)
330
+ lines.push(` ensures ${emitExpr(e)}`);
331
+ lines.push(" do");
332
+ lines.push(emitStmts(d.body, 2));
333
+ return lines.join("\n");
334
+ }
335
+ case "namespace": {
336
+ const lines = [`namespace ${d.name}`];
337
+ for (const inner of d.decls)
338
+ lines.push("", emitDecl(inner));
339
+ lines.push("", `end ${d.name}`);
340
+ return lines.join("\n");
341
+ }
342
+ case "class":
343
+ throw new Error(`Lean class support not yet implemented: ${d.name}`);
344
+ case "const":
345
+ return `def ${escapeName(d.name)} : ${tyToLean(d.type)} := ${emitExpr(d.value)}`;
346
+ }
347
+ }
348
+ /** Emit a pure expression with indented if/match blocks. */
349
+ function emitPureExpr(e, indent) {
350
+ const pad = " ".repeat(indent);
351
+ switch (e.kind) {
352
+ case "if":
353
+ return `${pad}if ${emitExpr(e.cond)} then\n${emitPureExpr(e.then, indent + 1)}\n${pad}else\n${emitPureExpr(e.else, indent + 1)}`;
354
+ case "match": {
355
+ const lines = [`${pad}match ${typeof e.scrutinee === "string" ? e.scrutinee : emitExpr(e.scrutinee)} with`];
356
+ for (const arm of e.arms) {
357
+ lines.push(`${pad}| ${arm.pattern} =>`);
358
+ lines.push(emitPureExpr(arm.body, indent + 1));
359
+ }
360
+ return lines.join("\n");
361
+ }
362
+ case "let":
363
+ return `${pad}let ${e.name} := ${emitExpr(e.value)}\n${emitPureExpr(e.body, indent)}`;
364
+ default:
365
+ return `${pad}${emitExpr(e)}`;
366
+ }
367
+ }
368
+ // ── File emission ────────────────────────────────────────────
369
+ export function emitLeanFile(file) {
370
+ _needsJSString = false;
371
+ // Emit declarations first so _needsJSString is set
372
+ const declLines = [];
373
+ for (const decl of file.decls) {
374
+ declLines.push("");
375
+ declLines.push(emitDecl(decl));
376
+ }
377
+ const lines = [];
378
+ if (file.comment) {
379
+ lines.push("/-");
380
+ lines.push(file.comment);
381
+ lines.push("-/");
382
+ }
383
+ for (const imp of file.imports)
384
+ lines.push(`import ${imp}`);
385
+ if (_needsJSString && !file.imports.includes("LemmaScript.JSString"))
386
+ lines.push("import LemmaScript.JSString");
387
+ if (file.options.length > 0)
388
+ lines.push("");
389
+ for (const opt of file.options)
390
+ lines.push(`set_option ${opt.key} ${opt.value}`);
391
+ lines.push(...declLines);
392
+ return lines.join("\n") + "\n";
393
+ }
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * lsc — LemmaScript compiler CLI
4
+ *
5
+ * Pipeline: extract → resolve → transform → emit
6
+ */
7
+ import { Project, ScriptTarget } from "ts-morph";
8
+ import { existsSync } from "fs";
9
+ import path from "path";
10
+ import { extractModule } from "./extract.js";
11
+ import { resolveModule } from "./resolve.js";
12
+ import { transformModule, transformModuleDafny } from "./transform.js";
13
+ import { emitLeanFile } from "./lean-emit.js";
14
+ import { emitDafnyFile } from "./dafny-emit.js";
15
+ import { dafnyGen, dafnyCheckDiff, dafnyVerify, dafnyRegen } from "./dafny-commands.js";
16
+ import { leanGen, leanCheck } from "./lean-commands.js";
17
+ function main() {
18
+ const args = process.argv.slice(2);
19
+ const backendIdx = args.findIndex(a => a.startsWith("--backend="));
20
+ let backend = "lean";
21
+ if (backendIdx >= 0) {
22
+ const val = args[backendIdx].split("=")[1];
23
+ if (val !== "lean" && val !== "dafny") {
24
+ console.error(`Unknown backend: ${val}. Use --backend=lean or --backend=dafny`);
25
+ process.exit(1);
26
+ }
27
+ backend = val;
28
+ args.splice(backendIdx, 1);
29
+ }
30
+ const timeLimitIdx = args.findIndex(a => a.startsWith("--time-limit="));
31
+ let timeLimit;
32
+ if (timeLimitIdx >= 0) {
33
+ timeLimit = parseInt(args[timeLimitIdx].split("=")[1]);
34
+ args.splice(timeLimitIdx, 1);
35
+ }
36
+ const [cmd, filePath] = args;
37
+ if (!cmd || !filePath) {
38
+ console.error("Usage: lsc <gen|check|regen|extract> [--backend=lean|dafny] <file.ts>");
39
+ process.exit(1);
40
+ }
41
+ const absPath = path.resolve(filePath);
42
+ if (!existsSync(absPath)) {
43
+ console.error(`File not found: ${absPath}`);
44
+ process.exit(1);
45
+ }
46
+ const project = new Project({ compilerOptions: { strict: true, target: ScriptTarget.ESNext, lib: ["lib.esnext.d.ts"] } });
47
+ const sourceFile = project.addSourceFileAtPath(absPath);
48
+ // Check //@ backend directive — skip if backend doesn't match
49
+ const backendDirective = sourceFile.getFullText().match(/\/\/@ backend (\w+)/);
50
+ if (backendDirective && backendDirective[1] !== backend) {
51
+ console.log(`Skipped: ${path.basename(filePath)} (//@ backend ${backendDirective[1]}, current: ${backend})`);
52
+ return;
53
+ }
54
+ // Extract: ts-morph → Raw IR
55
+ const raw = extractModule(sourceFile);
56
+ if (cmd === "extract") {
57
+ console.log(JSON.stringify(raw, null, 2));
58
+ return;
59
+ }
60
+ // Resolve: Raw IR → Typed IR
61
+ const typed = resolveModule(raw);
62
+ const dir = path.dirname(absPath);
63
+ const base = path.basename(filePath, ".ts");
64
+ // ── Dafny backend ─────────────────────────────────────────
65
+ if (backend === "dafny") {
66
+ const { typesFile, defFile } = transformModuleDafny(typed);
67
+ const allDecls = [...(typesFile?.decls ?? []), ...defFile.decls];
68
+ const merged = { ...defFile, decls: allDecls };
69
+ const text = emitDafnyFile(merged, path.basename(filePath));
70
+ const genPath = path.join(dir, `${base}.dfy.gen`);
71
+ const dfyPath = path.join(dir, `${base}.dfy`);
72
+ const basePath = path.join(dir, `${base}.dfy.base`);
73
+ if (cmd === "gen") {
74
+ dafnyGen(genPath, dfyPath, text);
75
+ return;
76
+ }
77
+ if (cmd === "gen-check") {
78
+ dafnyGen(genPath, dfyPath, text);
79
+ if (!dafnyCheckDiff(genPath, dfyPath))
80
+ process.exit(1);
81
+ return;
82
+ }
83
+ if (cmd === "check") {
84
+ dafnyGen(genPath, dfyPath, text);
85
+ if (!dafnyCheckDiff(genPath, dfyPath))
86
+ process.exit(1);
87
+ if (!dafnyVerify(dfyPath, dir, timeLimit))
88
+ process.exit(1);
89
+ return;
90
+ }
91
+ if (cmd === "regen") {
92
+ dafnyRegen(genPath, dfyPath, basePath, text, dir);
93
+ return;
94
+ }
95
+ console.error(`Unknown command: ${cmd}`);
96
+ process.exit(1);
97
+ }
98
+ // ── Lean backend ──────────────────────────────────────────
99
+ const specPath = path.join(dir, `${base}.spec.lean`);
100
+ const specImport = existsSync(specPath) ? `«${base}.spec»` : undefined;
101
+ const { typesFile, defFile } = transformModule(typed, specImport);
102
+ const typesPath = typesFile ? path.join(dir, `${base}.types.lean`) : null;
103
+ const typesText = typesFile ? emitLeanFile(typesFile) : null;
104
+ const defPath = path.join(dir, `${base}.def.lean`);
105
+ const defText = emitLeanFile(defFile);
106
+ if (cmd === "gen") {
107
+ leanGen(typesPath, defPath, typesText, defText);
108
+ return;
109
+ }
110
+ if (cmd === "check") {
111
+ leanGen(typesPath, defPath, typesText, defText);
112
+ if (!leanCheck(dir, base))
113
+ process.exit(1);
114
+ return;
115
+ }
116
+ console.error(`Unknown command: ${cmd}`);
117
+ process.exit(1);
118
+ }
119
+ main();
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Raw IR — structured AST for expressions and statements.
3
+ *
4
+ * Produced by the extract phase (ts-morph → RawExpr for body expressions)
5
+ * and the specparser (annotation strings → RawExpr for spec expressions).
6
+ *
7
+ * Layer 1: structured (no strings for expressions)
8
+ * Layer 2 (planned): add Ty to each node
9
+ */
10
+ export {};