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.
@@ -4,13 +4,48 @@
4
4
  * Produces structured AST nodes, not strings.
5
5
  * The only strings are //@ annotation expressions (parsed later by specparser).
6
6
  */
7
- import { Project, Node, SyntaxKind } from "ts-morph";
7
+ import { Project, Node, SyntaxKind, ScriptTarget } from "ts-morph";
8
8
  // ── Expression extraction ────────────────────────────────────
9
+ /** When set, calls whose function/method name matches this key are replaced with havoc. */
10
+ let _havocKey = null;
9
11
  function extractExpr(node) {
12
+ // Havoc key matching: replace matching calls with havoc expression
13
+ if (_havocKey && Node.isCallExpression(node)) {
14
+ const fnExpr = node.getExpression();
15
+ const name = Node.isPropertyAccessExpression(fnExpr) ? fnExpr.getName()
16
+ : Node.isIdentifier(fnExpr) ? fnExpr.getText()
17
+ : null;
18
+ if (name === _havocKey) {
19
+ return { kind: "havoc", tsType: typeToString(node.getType()) };
20
+ }
21
+ }
10
22
  // Numeric literal
11
23
  if (Node.isNumericLiteral(node)) {
12
24
  return { kind: "num", value: Number(node.getLiteralValue()) };
13
25
  }
26
+ // BigInt literal (e.g. 32n, 0xffffn) — treat as integer
27
+ if (Node.isBigIntLiteral(node)) {
28
+ const text = node.getText().replace(/n$/, '');
29
+ return { kind: "num", value: Number(text) };
30
+ }
31
+ // Template literal: `foo${x}bar` → "foo" + x + "bar"
32
+ if (Node.isTemplateExpression(node)) {
33
+ const parts = [];
34
+ const head = node.getHead().getLiteralText();
35
+ if (head)
36
+ parts.push({ kind: "str", value: head });
37
+ for (const span of node.getTemplateSpans()) {
38
+ parts.push(extractExpr(span.getExpression()));
39
+ const text = span.getLiteral().getLiteralText();
40
+ if (text)
41
+ parts.push({ kind: "str", value: text });
42
+ }
43
+ return parts.reduce((left, right) => ({ kind: "binop", op: "+", left, right }));
44
+ }
45
+ // No-substitution template literal: `hello` → "hello"
46
+ if (Node.isNoSubstitutionTemplateLiteral(node)) {
47
+ return { kind: "str", value: node.getLiteralText() };
48
+ }
14
49
  // String literal
15
50
  if (Node.isStringLiteral(node)) {
16
51
  return { kind: "str", value: node.getLiteralValue() };
@@ -24,6 +59,10 @@ function extractExpr(node) {
24
59
  if (Node.isIdentifier(node)) {
25
60
  return { kind: "var", name: node.getText() };
26
61
  }
62
+ // this
63
+ if (node.getKind() === SyntaxKind.ThisKeyword) {
64
+ return { kind: "var", name: "this" };
65
+ }
27
66
  // Property access: x.foo
28
67
  if (Node.isPropertyAccessExpression(node)) {
29
68
  return { kind: "field", obj: extractExpr(node.getExpression()), field: node.getName() };
@@ -108,6 +147,10 @@ function extractExpr(node) {
108
147
  if (Node.isSpreadAssignment(prop)) {
109
148
  spread = extractExpr(prop.getExpression());
110
149
  }
150
+ else if (Node.isShorthandPropertyAssignment(prop)) {
151
+ const name = prop.getName();
152
+ fields.push({ name, value: { kind: "var", name } });
153
+ }
111
154
  else if (Node.isPropertyAssignment(prop)) {
112
155
  const init = prop.getInitializer();
113
156
  if (init)
@@ -120,6 +163,30 @@ function extractExpr(node) {
120
163
  if (Node.isConditionalExpression(node)) {
121
164
  return { kind: "conditional", cond: extractExpr(node.getCondition()), then: extractExpr(node.getWhenTrue()), else: extractExpr(node.getWhenFalse()) };
122
165
  }
166
+ // Non-null assertion: expr!
167
+ if (Node.isNonNullExpression(node)) {
168
+ return { kind: "nonNull", expr: extractExpr(node.getExpression()) };
169
+ }
170
+ // new Map<K,V>() / new Set<T>() — with or without initializer
171
+ if (Node.isNewExpression(node)) {
172
+ const name = node.getExpression().getText();
173
+ if (name === "Map" || name === "Set") {
174
+ const typeArgs = node.getTypeArguments();
175
+ const tsType = typeArgs && typeArgs.length > 0
176
+ ? `${name}<${typeArgs.map(t => t.getText()).join(", ")}>`
177
+ : name;
178
+ const args = node.getArguments();
179
+ // new Map(arr.map(fn)) — map-from-array constructor
180
+ if (name === "Map" && args && args.length === 1) {
181
+ return { kind: "call", fn: { kind: "var", name: "__mapFromArray" }, args: [extractExpr(args[0])] };
182
+ }
183
+ return { kind: "emptyCollection", collectionType: name, tsType };
184
+ }
185
+ }
186
+ // As-expression: expr as T — strip the type assertion
187
+ if (Node.isAsExpression(node)) {
188
+ return extractExpr(node.getExpression());
189
+ }
123
190
  throw new Error(`Unsupported expression: ${node.getText()}`);
124
191
  }
125
192
  // ── Annotation parsing ───────────────────────────────────────
@@ -224,39 +291,106 @@ function findDiscriminant(members) {
224
291
  return null;
225
292
  }
226
293
  function typeToString(type) {
227
- if (type.isNumber())
294
+ if (type.isUndefined())
295
+ return "undefined";
296
+ if (type.isNumber() || type.isNumberLiteral())
228
297
  return "number";
229
- if (type.isString())
298
+ if (type.isBigInt() || type.isBigIntLiteral())
299
+ return "bigint";
300
+ if (type.isString() || type.isStringLiteral())
230
301
  return "string";
231
302
  if (type.isBoolean())
232
303
  return "boolean";
304
+ // Named type alias (e.g. Priority = "low" | "medium" | "high") — use the alias name
305
+ if (type.getAliasSymbol()) {
306
+ const name = type.getAliasSymbol().getName();
307
+ const args = type.getAliasTypeArguments();
308
+ if (args.length > 0)
309
+ return `${name}<${args.map(t => typeToString(t)).join(", ")}>`;
310
+ return name;
311
+ }
312
+ if (type.isUnion()) {
313
+ return type.getUnionTypes().map(typeToString).join(" | ");
314
+ }
233
315
  if (type.isArray()) {
234
316
  const elem = type.getArrayElementTypeOrThrow();
235
317
  return `${typeToString(elem)}[]`;
236
318
  }
237
319
  const symbol = type.getSymbol() ?? type.getAliasSymbol();
238
- if (symbol)
239
- return symbol.getName();
320
+ if (symbol) {
321
+ const name = symbol.getName();
322
+ const typeArgs = type.getTypeArguments();
323
+ if (typeArgs.length > 0) {
324
+ return `${name}<${typeArgs.map(t => typeToString(t)).join(", ")}>`;
325
+ }
326
+ return name;
327
+ }
240
328
  return type.getText();
241
329
  }
242
330
  const COMPOUND_OPS = {
243
331
  "+=": "+", "-=": "-", "*=": "*", "/=": "/", "%=": "%",
244
332
  };
245
333
  // ── Statement extraction ─────────────────────────────────────
334
+ /** Parse ghost and assert annotations from comment ranges. */
335
+ function parseSpecComments(ranges, line) {
336
+ const result = [];
337
+ for (const range of ranges) {
338
+ const text = range.getText().trim();
339
+ if (!text.startsWith(PREFIX))
340
+ continue;
341
+ const content = text.slice(PREFIX.length);
342
+ // assert expr
343
+ if (content.startsWith("assert ")) {
344
+ result.push({ kind: "assert", expr: content.slice(7).trim(), line });
345
+ continue;
346
+ }
347
+ if (!content.startsWith("ghost "))
348
+ continue;
349
+ const ghostBody = content.slice(6).trim();
350
+ // ghost let varName: type = expr OR ghost let varName = expr
351
+ const letMatch = ghostBody.match(/^let\s+(\w+)(?:\s*:\s*(\w+))?\s*=\s*(.+)$/);
352
+ if (letMatch) {
353
+ result.push({ kind: "ghostLet", name: letMatch[1], tsType: letMatch[2] ?? null, init: letMatch[3].trim(), line });
354
+ continue;
355
+ }
356
+ // ghost varName = expr
357
+ const assignMatch = ghostBody.match(/^(\w+)\s*=\s*(.+)$/);
358
+ if (assignMatch) {
359
+ result.push({ kind: "ghostAssign", target: assignMatch[1], value: assignMatch[2].trim(), line });
360
+ }
361
+ }
362
+ return result;
363
+ }
246
364
  function extractStmts(stmts) {
247
365
  const result = [];
248
366
  for (const s of stmts) {
249
367
  const line = s.getStartLineNumber();
368
+ // Ghost annotations from leading comments → inject before this statement
369
+ result.push(...parseSpecComments(s.getLeadingCommentRanges(), line));
250
370
  if (Node.isVariableStatement(s)) {
371
+ const havocMatch = s.getLeadingCommentRanges()
372
+ .map(r => r.getText().trim().match(/^\/\/@ havoc(?:\s+(\S+))?$/))
373
+ .find(m => m !== null);
374
+ const havocKey = havocMatch?.[1] ?? null;
375
+ const isHavoc = !!havocMatch;
251
376
  for (const d of s.getDeclarations()) {
252
377
  const declType = d.getType();
253
- const init = d.getInitializer();
378
+ let init;
379
+ if (isHavoc && !havocKey) {
380
+ init = { kind: "havoc", tsType: typeToString(declType) };
381
+ }
382
+ else {
383
+ const initializer = d.getInitializer();
384
+ _havocKey = havocKey;
385
+ init = initializer ? extractExpr(initializer) : { kind: "var", name: "default" };
386
+ _havocKey = null;
387
+ }
254
388
  result.push({
255
389
  kind: "let",
256
390
  name: d.getName(),
257
391
  mutable: s.getDeclarationKind() === "let",
258
392
  tsType: typeToString(declType),
259
- init: init ? extractExpr(init) : { kind: "var", name: "default" },
393
+ init,
260
394
  line,
261
395
  });
262
396
  }
@@ -279,13 +413,29 @@ function extractStmts(stmts) {
279
413
  }
280
414
  if (Node.isForOfStatement(s)) {
281
415
  const init = s.getInitializer();
282
- const varName = Node.isVariableDeclarationList(init) ? init.getDeclarations()[0]?.getName() ?? "_" : "_";
416
+ const names = [];
417
+ if (Node.isVariableDeclarationList(init)) {
418
+ const decl = init.getDeclarations()[0];
419
+ const nameNode = decl?.getNameNode();
420
+ if (nameNode && Node.isArrayBindingPattern(nameNode)) {
421
+ for (const elem of nameNode.getElements()) {
422
+ if (Node.isBindingElement(elem))
423
+ names.push(elem.getNameNode().getText());
424
+ }
425
+ }
426
+ else {
427
+ names.push(decl?.getName() ?? "_");
428
+ }
429
+ }
430
+ else {
431
+ names.push("_");
432
+ }
283
433
  const bodyNode = s.getStatement();
284
434
  const bodyStmts = Node.isBlock(bodyNode) ? bodyNode.getStatements() : [bodyNode];
285
435
  const annots = collectAnnotations(s, bodyStmts);
286
436
  result.push({
287
437
  kind: "forof",
288
- varName,
438
+ names,
289
439
  iterable: extractExpr(s.getExpression()),
290
440
  invariants: annots.filter(a => a.kind === "invariant").map(a => a.expr),
291
441
  doneWith: annots.find(a => a.kind === "done_with")?.expr ?? null,
@@ -344,8 +494,18 @@ function extractStmts(stmts) {
344
494
  }
345
495
  if (Node.isExpressionStatement(s)) {
346
496
  const expr = s.getExpression();
347
- // x = e
348
- if (Node.isBinaryExpression(expr) && expr.getOperatorToken().getText() === "=") {
497
+ // arr[i] = v → arr = arr.with(i, v)
498
+ if (Node.isBinaryExpression(expr) && expr.getOperatorToken().getText() === "=" && Node.isElementAccessExpression(expr.getLeft())) {
499
+ const left = expr.getLeft();
500
+ const obj = extractExpr(left.getExpression());
501
+ const idx = extractExpr(left.getArgumentExpression());
502
+ const val = extractExpr(expr.getRight());
503
+ const target = left.getExpression().getText();
504
+ const withCall = { kind: "call", fn: { kind: "field", obj, field: "with" }, args: [idx, val] };
505
+ result.push({ kind: "assign", target, value: withCall, line });
506
+ // x = e
507
+ }
508
+ else if (Node.isBinaryExpression(expr) && expr.getOperatorToken().getText() === "=") {
349
509
  result.push({ kind: "assign", target: expr.getLeft().getText(), value: extractExpr(expr.getRight()), line });
350
510
  // x += e, x -= e, etc.
351
511
  }
@@ -371,15 +531,47 @@ function extractStmts(stmts) {
371
531
  }
372
532
  continue;
373
533
  }
534
+ if (Node.isThrowStatement(s)) {
535
+ result.push({ kind: "throw", line });
536
+ continue;
537
+ }
374
538
  throw new Error(`Unsupported statement at line ${line}: ${s.getText().slice(0, 80)}`);
375
539
  }
540
+ // Ghost comments after the last statement (before closing brace) appear as sibling trivia nodes
541
+ if (stmts.length > 0) {
542
+ const last = stmts[stmts.length - 1];
543
+ const line = last.getStartLineNumber();
544
+ for (const sib of last.getNextSiblings()) {
545
+ const text = sib.getText().trim();
546
+ if (!text.startsWith(PREFIX))
547
+ continue;
548
+ const content = text.slice(PREFIX.length);
549
+ // assert expr
550
+ if (content.startsWith("assert ")) {
551
+ result.push({ kind: "assert", expr: content.slice(7).trim(), line });
552
+ continue;
553
+ }
554
+ if (!content.startsWith("ghost "))
555
+ continue;
556
+ const ghostBody = content.slice(6).trim();
557
+ const letMatch = ghostBody.match(/^let\s+(\w+)(?:\s*:\s*(\w+))?\s*=\s*(.+)$/);
558
+ if (letMatch) {
559
+ result.push({ kind: "ghostLet", name: letMatch[1], tsType: letMatch[2] ?? null, init: letMatch[3].trim(), line });
560
+ continue;
561
+ }
562
+ const assignMatch = ghostBody.match(/^(\w+)\s*=\s*(.+)$/);
563
+ if (assignMatch) {
564
+ result.push({ kind: "ghostAssign", target: assignMatch[1], value: assignMatch[2].trim(), line });
565
+ }
566
+ }
567
+ }
376
568
  return result;
377
569
  }
378
570
  // ── Function extraction ──────────────────────────────────────
379
571
  function extractFunction(fn) {
380
572
  const body = fn.getBody();
381
573
  if (!body || !Node.isBlock(body))
382
- throw new Error(`${fn.getName()}: function body is not a block`);
574
+ throw new Error(`${fn.getName?.() ?? "arrow"}: function body is not a block`);
383
575
  const bodyStmts = body.getStatements();
384
576
  const annots = collectAnnotations(fn, bodyStmts);
385
577
  const typeAnnotations = [];
@@ -391,7 +583,7 @@ function extractFunction(fn) {
391
583
  }
392
584
  }
393
585
  return {
394
- name: fn.getName() ?? "<anonymous>",
586
+ name: fn.getName?.() ?? "<anonymous>",
395
587
  params: fn.getParameters().map(p => ({ name: p.getName(), tsType: p.getTypeNode()?.getText() ?? "unknown" })),
396
588
  returnType: fn.getReturnTypeNode()?.getText() ?? "unknown",
397
589
  requires: annots.filter(a => a.kind === "requires").map(a => a.expr),
@@ -417,10 +609,189 @@ export function extractModule(sourceFile) {
417
609
  typeDecls.push(info);
418
610
  }
419
611
  }
612
+ // Extract module-level const declarations
613
+ const constants = [];
614
+ for (const stmt of sourceFile.getStatements()) {
615
+ if (Node.isVariableStatement(stmt)) {
616
+ for (const decl of stmt.getDeclarationList().getDeclarations()) {
617
+ if (stmt.getDeclarationList().getFlags() & 2 /* const */) {
618
+ const init = decl.getInitializer();
619
+ // Skip huge string constants — they crash the verifier and have no verification value
620
+ const initType = decl.getType();
621
+ const isHugeString = (initType.isString() || initType.isStringLiteral()) && init.getText().length > 200;
622
+ if (init && !isHugeString && !Node.isArrowFunction(init)) {
623
+ try {
624
+ constants.push({
625
+ name: decl.getName(),
626
+ tsType: typeToString(decl.getType()),
627
+ value: extractExpr(init),
628
+ });
629
+ }
630
+ catch (e) {
631
+ console.error(`WARNING: skipping const '${decl.getName()}': ${e.message}`);
632
+ }
633
+ }
634
+ }
635
+ }
636
+ }
637
+ }
638
+ // Collect all function-like declarations: function declarations + const arrow functions
639
+ const allFns = [];
640
+ for (const fn of sourceFile.getFunctions()) {
641
+ allFns.push({ name: fn.getName() ?? "<anonymous>", node: fn });
642
+ }
643
+ // const f = (...) => { ... } — treat as named function
644
+ for (const stmt of sourceFile.getStatements()) {
645
+ if (Node.isVariableStatement(stmt)) {
646
+ for (const decl of stmt.getDeclarationList().getDeclarations()) {
647
+ const init = decl.getInitializer();
648
+ if (init && Node.isArrowFunction(init)) {
649
+ allFns.push({ name: decl.getName(), node: init });
650
+ }
651
+ }
652
+ }
653
+ }
654
+ // If any function has //@ verify, only extract those (brownfield mode).
655
+ // Otherwise extract all functions (backwards-compatible with existing examples).
656
+ const hasVerifyDirective = allFns.some(f => f.node.getFullText().includes('//@ verify'));
657
+ const fnsToExtract = hasVerifyDirective
658
+ ? allFns.filter(f => f.node.getFullText().includes('//@ verify'))
659
+ : allFns;
660
+ const functions = fnsToExtract.map(f => {
661
+ const raw = extractFunction(f.node);
662
+ raw.name = f.name; // use the const name, not "<anonymous>"
663
+ return raw;
664
+ });
665
+ // In brownfield mode, filter consts to only those referenced by verified functions.
666
+ // Types are NOT filtered — they may be needed transitively (e.g. Option from T | undefined).
667
+ if (hasVerifyDirective) {
668
+ const referencedNames = new Set();
669
+ function collectNames(stmts) {
670
+ for (const s of stmts) {
671
+ if (s.kind === "let") {
672
+ collectNamesExpr(s.init);
673
+ }
674
+ if (s.kind === "assign") {
675
+ collectNamesExpr(s.value);
676
+ }
677
+ if (s.kind === "return") {
678
+ collectNamesExpr(s.value);
679
+ }
680
+ if (s.kind === "if") {
681
+ collectNamesExpr(s.cond);
682
+ collectNames(s.then);
683
+ collectNames(s.else);
684
+ }
685
+ if (s.kind === "while") {
686
+ collectNamesExpr(s.cond);
687
+ collectNames(s.body);
688
+ }
689
+ if (s.kind === "forof") {
690
+ collectNamesExpr(s.iterable);
691
+ collectNames(s.body);
692
+ }
693
+ if (s.kind === "expr") {
694
+ collectNamesExpr(s.expr);
695
+ }
696
+ }
697
+ }
698
+ function collectNamesExpr(e) {
699
+ if (e.kind === "var")
700
+ referencedNames.add(e.name);
701
+ if (e.kind === "binop") {
702
+ collectNamesExpr(e.left);
703
+ collectNamesExpr(e.right);
704
+ }
705
+ if (e.kind === "unop") {
706
+ collectNamesExpr(e.expr);
707
+ }
708
+ if (e.kind === "call") {
709
+ collectNamesExpr(e.fn);
710
+ e.args.forEach(collectNamesExpr);
711
+ }
712
+ if (e.kind === "field") {
713
+ collectNamesExpr(e.obj);
714
+ }
715
+ if (e.kind === "index") {
716
+ collectNamesExpr(e.obj);
717
+ collectNamesExpr(e.idx);
718
+ }
719
+ if (e.kind === "record") {
720
+ if (e.spread)
721
+ collectNamesExpr(e.spread);
722
+ e.fields.forEach(f => collectNamesExpr(f.value));
723
+ }
724
+ if (e.kind === "arrayLiteral") {
725
+ e.elems.forEach(collectNamesExpr);
726
+ }
727
+ if (e.kind === "conditional") {
728
+ collectNamesExpr(e.cond);
729
+ collectNamesExpr(e.then);
730
+ collectNamesExpr(e.else);
731
+ }
732
+ }
733
+ for (const fn of functions) {
734
+ for (const p of fn.params)
735
+ referencedNames.add(p.tsType);
736
+ referencedNames.add(fn.returnType);
737
+ collectNames(fn.body);
738
+ // Also scan spec annotations for identifier references
739
+ for (const spec of [...fn.requires, ...fn.ensures]) {
740
+ for (const m of spec.matchAll(/\b([a-zA-Z_]\w*)\b/g)) {
741
+ referencedNames.add(m[1]);
742
+ }
743
+ }
744
+ }
745
+ constants.splice(0, constants.length, ...constants.filter(c => referencedNames.has(c.name)));
746
+ }
747
+ // Resolve imported types: extract types referenced in function signatures but not in this file
748
+ const knownTypes = new Set(typeDecls.map(d => d.name));
749
+ const builtins = new Set(["Map", "Set", "Array", "String", "Number", "Boolean", "Promise", "Date", "RegExp", "Error"]);
750
+ function resolveType(t, locationNode) {
751
+ // Unwrap arrays and generics to find user-defined types
752
+ if (t.isArray()) {
753
+ resolveType(t.getArrayElementTypeOrThrow(), locationNode);
754
+ return;
755
+ }
756
+ for (const arg of t.getTypeArguments())
757
+ resolveType(arg, locationNode);
758
+ const sym = t.getSymbol() ?? t.getAliasSymbol();
759
+ const name = sym?.getName();
760
+ if (name && !knownTypes.has(name) && !builtins.has(name) && t.isObject()) {
761
+ const info = extractRecord(name, t, locationNode);
762
+ if (info) {
763
+ typeDecls.push(info);
764
+ knownTypes.add(name);
765
+ }
766
+ }
767
+ }
768
+ for (const f of fnsToExtract) {
769
+ for (const p of f.node.getParameters())
770
+ resolveType(p.getType(), p);
771
+ }
772
+ // Extract classes with //@ verify methods
773
+ const classes = [];
774
+ for (const cls of sourceFile.getClasses()) {
775
+ const methods = [];
776
+ for (const method of cls.getMethods()) {
777
+ if (!method.getFullText().includes('//@ verify'))
778
+ continue;
779
+ methods.push(extractFunction(method));
780
+ }
781
+ if (methods.length === 0)
782
+ continue;
783
+ const fields = [];
784
+ for (const prop of cls.getProperties()) {
785
+ fields.push({ name: prop.getName(), tsType: typeToString(prop.getType()) });
786
+ }
787
+ classes.push({ name: cls.getName() ?? "Anonymous", fields, methods });
788
+ }
420
789
  return {
421
790
  file: sourceFile.getFilePath(),
422
791
  typeDecls,
423
- functions: sourceFile.getFunctions().map(extractFunction),
792
+ constants,
793
+ functions,
794
+ classes,
424
795
  };
425
796
  }
426
797
  // ── Main ─────────────────────────────────────────────────────
@@ -430,6 +801,6 @@ if (process.argv[1]?.match(/extract\.(ts|js)$/)) {
430
801
  console.error("Usage: extract <file.ts>");
431
802
  process.exit(1);
432
803
  }
433
- const proj = new Project({ compilerOptions: { strict: true } });
804
+ const proj = new Project({ compilerOptions: { strict: true, target: ScriptTarget.ESNext, lib: ["lib.esnext.d.ts"] } });
434
805
  console.log(JSON.stringify(extractModule(proj.addSourceFileAtPath(file)), null, 2));
435
806
  }
package/tools/dist/ir.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Lean IR — the intermediate representation between transform and emit.
2
+ * IR — the intermediate representation between transform and emit.
3
3
  *
4
4
  * The transform phase produces these types.
5
- * The emit phase pretty-prints them to Lean syntax.
5
+ * The emit phase pretty-prints them to backend syntax (Lean or Dafny).
6
6
  */
7
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
+ }