lemmascript 0.1.0 → 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,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
+ }
package/tools/dist/lsc.js CHANGED
@@ -4,26 +4,38 @@
4
4
  *
5
5
  * Pipeline: extract → resolve → transform → emit
6
6
  */
7
- import { Project } from "ts-morph";
8
- import { existsSync, writeFileSync } from "fs";
9
- import { execSync } from "child_process";
7
+ import { Project, ScriptTarget } from "ts-morph";
8
+ import { existsSync } from "fs";
10
9
  import path from "path";
11
10
  import { extractModule } from "./extract.js";
12
11
  import { resolveModule } from "./resolve.js";
13
- import { transformModule } from "./transform.js";
14
- import { emitFile } from "./emit.js";
15
- import { transformModuleDafny } from "./transform.js";
12
+ import { transformModule, transformModuleDafny } from "./transform.js";
13
+ import { emitLeanFile } from "./lean-emit.js";
16
14
  import { emitDafnyFile } from "./dafny-emit.js";
17
15
  import { dafnyGen, dafnyCheckDiff, dafnyVerify, dafnyRegen } from "./dafny-commands.js";
16
+ import { leanGen, leanCheck } from "./lean-commands.js";
18
17
  function main() {
19
18
  const args = process.argv.slice(2);
20
- const backendIdx = args.indexOf("--backend=dafny");
21
- const backend = backendIdx >= 0 ? "dafny" : "lean";
22
- if (backendIdx >= 0)
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;
23
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
+ }
24
36
  const [cmd, filePath] = args;
25
37
  if (!cmd || !filePath) {
26
- console.error("Usage: lsc <gen|check|regen|extract> [--backend=dafny] <file.ts>");
38
+ console.error("Usage: lsc <gen|check|regen|extract> [--backend=lean|dafny] <file.ts>");
27
39
  process.exit(1);
28
40
  }
29
41
  const absPath = path.resolve(filePath);
@@ -31,8 +43,14 @@ function main() {
31
43
  console.error(`File not found: ${absPath}`);
32
44
  process.exit(1);
33
45
  }
34
- const project = new Project({ compilerOptions: { strict: true } });
46
+ const project = new Project({ compilerOptions: { strict: true, target: ScriptTarget.ESNext, lib: ["lib.esnext.d.ts"] } });
35
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
+ }
36
54
  // Extract: ts-morph → Raw IR
37
55
  const raw = extractModule(sourceFile);
38
56
  if (cmd === "extract") {
@@ -46,27 +64,32 @@ function main() {
46
64
  // ── Dafny backend ─────────────────────────────────────────
47
65
  if (backend === "dafny") {
48
66
  const { typesFile, defFile } = transformModuleDafny(typed);
49
- // Emit types + def into a single Dafny file
50
67
  const allDecls = [...(typesFile?.decls ?? []), ...defFile.decls];
51
68
  const merged = { ...defFile, decls: allDecls };
52
69
  const text = emitDafnyFile(merged, path.basename(filePath));
53
70
  const genPath = path.join(dir, `${base}.dfy.gen`);
54
71
  const dfyPath = path.join(dir, `${base}.dfy`);
55
- const patchPath = path.join(dir, `${base}.dfy.patch`);
72
+ const basePath = path.join(dir, `${base}.dfy.base`);
56
73
  if (cmd === "gen") {
57
74
  dafnyGen(genPath, dfyPath, text);
58
75
  return;
59
76
  }
77
+ if (cmd === "gen-check") {
78
+ dafnyGen(genPath, dfyPath, text);
79
+ if (!dafnyCheckDiff(genPath, dfyPath))
80
+ process.exit(1);
81
+ return;
82
+ }
60
83
  if (cmd === "check") {
61
84
  dafnyGen(genPath, dfyPath, text);
62
85
  if (!dafnyCheckDiff(genPath, dfyPath))
63
86
  process.exit(1);
64
- if (!dafnyVerify(dfyPath, dir))
87
+ if (!dafnyVerify(dfyPath, dir, timeLimit))
65
88
  process.exit(1);
66
89
  return;
67
90
  }
68
91
  if (cmd === "regen") {
69
- dafnyRegen(genPath, dfyPath, patchPath, text, dir);
92
+ dafnyRegen(genPath, dfyPath, basePath, text, dir);
70
93
  return;
71
94
  }
72
95
  console.error(`Unknown command: ${cmd}`);
@@ -75,41 +98,19 @@ function main() {
75
98
  // ── Lean backend ──────────────────────────────────────────
76
99
  const specPath = path.join(dir, `${base}.spec.lean`);
77
100
  const specImport = existsSync(specPath) ? `«${base}.spec»` : undefined;
78
- // Transform: Typed IR → Lean IR
79
101
  const { typesFile, defFile } = transformModule(typed, specImport);
80
- // Emit: Lean IR text
81
- if (typesFile) {
82
- const typesPath = path.join(dir, `${base}.types.lean`);
83
- writeFileSync(typesPath, emitFile(typesFile));
84
- console.log(`Generated: ${typesPath}`);
85
- }
102
+ const typesPath = typesFile ? path.join(dir, `${base}.types.lean`) : null;
103
+ const typesText = typesFile ? emitLeanFile(typesFile) : null;
86
104
  const defPath = path.join(dir, `${base}.def.lean`);
105
+ const defText = emitLeanFile(defFile);
87
106
  if (cmd === "gen") {
88
- writeFileSync(defPath, emitFile(defFile));
89
- console.log(`Generated: ${defPath}`);
107
+ leanGen(typesPath, defPath, typesText, defText);
90
108
  return;
91
109
  }
92
110
  if (cmd === "check") {
93
- writeFileSync(defPath, emitFile(defFile));
94
- console.log(`Generated: ${defPath}`);
95
- let lakeDir = dir;
96
- while (lakeDir !== path.dirname(lakeDir)) {
97
- if (existsSync(path.join(lakeDir, "lakefile.lean")))
98
- break;
99
- lakeDir = path.dirname(lakeDir);
100
- }
101
- const proofPath = path.join(dir, `${base}.proof.lean`);
102
- if (!existsSync(proofPath)) {
103
- console.error(`No proof file: ${proofPath}`);
111
+ leanGen(typesPath, defPath, typesText, defText);
112
+ if (!leanCheck(dir, base))
104
113
  process.exit(1);
105
- }
106
- console.log("Running lake build...");
107
- try {
108
- execSync(`lake build`, { cwd: lakeDir, stdio: "inherit" });
109
- }
110
- catch {
111
- process.exit(1);
112
- }
113
114
  return;
114
115
  }
115
116
  console.error(`Unknown command: ${cmd}`);