lemmascript 0.0.1 → 0.1.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/README.md +101 -3
- package/package.json +30 -20
- package/tools/dist/dafny-commands.js +104 -0
- package/tools/dist/dafny-emit.js +449 -0
- package/tools/dist/emit.js +253 -0
- package/tools/dist/extract.js +435 -0
- package/tools/dist/ir.js +7 -0
- package/tools/dist/lsc.js +118 -0
- package/tools/dist/rawir.js +10 -0
- package/tools/dist/resolve.js +451 -0
- package/tools/dist/specparser.js +251 -0
- package/tools/dist/transform.js +745 -0
- package/tools/dist/typedir.js +7 -0
- package/tools/dist/types.js +38 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -4
- package/src/index.ts +0 -1
- package/tsconfig.json +0 -14
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dafny emitter — translates Lean IR to Dafny syntax on the fly.
|
|
3
|
+
*
|
|
4
|
+
* No separate Dafny IR. The shared transform produces Lean IR,
|
|
5
|
+
* and this emitter maps it to Dafny syntax.
|
|
6
|
+
*/
|
|
7
|
+
// ── Lean type → Dafny type ──────────────────────────────────
|
|
8
|
+
function leanTypeToDafny(t) {
|
|
9
|
+
// Simple mappings
|
|
10
|
+
const MAP = {
|
|
11
|
+
"Int": "int", "Nat": "nat", "Bool": "bool",
|
|
12
|
+
"String": "string", "Unit": "()", "_": "int",
|
|
13
|
+
};
|
|
14
|
+
if (MAP[t])
|
|
15
|
+
return MAP[t];
|
|
16
|
+
// Array (X Y) → seq<...>
|
|
17
|
+
const arrParenMatch = t.match(/^Array\s+\((.+)\)$/);
|
|
18
|
+
if (arrParenMatch)
|
|
19
|
+
return `seq<${leanTypeToDafny(arrParenMatch[1])}>`;
|
|
20
|
+
// Array X → seq<X>
|
|
21
|
+
const arrMatch = t.match(/^Array\s+(.+)$/);
|
|
22
|
+
if (arrMatch)
|
|
23
|
+
return `seq<${leanTypeToDafny(arrMatch[1])}>`;
|
|
24
|
+
// Strip parens: (X) → X
|
|
25
|
+
const parenMatch = t.match(/^\((.+)\)$/);
|
|
26
|
+
if (parenMatch)
|
|
27
|
+
return leanTypeToDafny(parenMatch[1]);
|
|
28
|
+
// User types pass through
|
|
29
|
+
return t;
|
|
30
|
+
}
|
|
31
|
+
// ── Dafny keyword escaping ──────────────────────────────────
|
|
32
|
+
const DAFNY_KEYWORDS = new Set([
|
|
33
|
+
"seq", "set", "map", "multiset", "iset", "imap",
|
|
34
|
+
"var", "method", "function", "predicate", "lemma",
|
|
35
|
+
"class", "trait", "module", "import", "export",
|
|
36
|
+
"if", "then", "else", "while", "for", "in",
|
|
37
|
+
"match", "case", "return", "break", "continue",
|
|
38
|
+
"requires", "ensures", "invariant", "decreases",
|
|
39
|
+
"forall", "exists", "old", "fresh", "allocated",
|
|
40
|
+
"true", "false", "null", "this", "new",
|
|
41
|
+
"datatype", "type", "const", "ghost", "static",
|
|
42
|
+
"reads", "modifies", "assert", "assume", "print",
|
|
43
|
+
"by", "calc", "reveal",
|
|
44
|
+
]);
|
|
45
|
+
function escapeName(name) {
|
|
46
|
+
if (DAFNY_KEYWORDS.has(name))
|
|
47
|
+
return `${name}_`;
|
|
48
|
+
// Dafny doesn't allow identifiers starting with _
|
|
49
|
+
if (name.startsWith("_"))
|
|
50
|
+
return `i${name}`;
|
|
51
|
+
return name;
|
|
52
|
+
}
|
|
53
|
+
/** Format a typed parameter list for Dafny: "x: int, y: seq<int>" */
|
|
54
|
+
function paramList(params) {
|
|
55
|
+
return params.map(p => `${escapeName(p.name)}: ${leanTypeToDafny(p.type)}`).join(", ");
|
|
56
|
+
}
|
|
57
|
+
// ── Lean op → Dafny op ─────────────────────────────────────
|
|
58
|
+
const OP_MAP = {
|
|
59
|
+
"=": "==", "≠": "!=", "≥": ">=", "≤": "<=",
|
|
60
|
+
"∧": "&&", "∨": "||", "¬": "!",
|
|
61
|
+
};
|
|
62
|
+
function mapOp(op) { return OP_MAP[op] ?? op; }
|
|
63
|
+
// ── Expression emission ─────────────────────────────────────
|
|
64
|
+
function emitExpr(e) {
|
|
65
|
+
switch (e.kind) {
|
|
66
|
+
case "var": return escapeName(e.name);
|
|
67
|
+
case "num": return `${e.value}`;
|
|
68
|
+
case "bool": return e.value ? "true" : "false";
|
|
69
|
+
case "str": return `"${e.value}"`;
|
|
70
|
+
case "constructor": return qualifyCtor(e.name, e.type);
|
|
71
|
+
case "arrayLiteral":
|
|
72
|
+
if (e.elems.length === 0)
|
|
73
|
+
return `[]`;
|
|
74
|
+
return `[${e.elems.map(emitExpr).join(", ")}]`;
|
|
75
|
+
case "dotCall": {
|
|
76
|
+
const obj = emitExpr(e.obj);
|
|
77
|
+
const args = e.args.map(emitExpr);
|
|
78
|
+
if (e.method === "with" && args.length === 2)
|
|
79
|
+
return `${obj}[${args[0]} := ${args[1]}]`;
|
|
80
|
+
if (e.method === "includes" && args.length === 1)
|
|
81
|
+
return `(${args[0]} in ${obj})`;
|
|
82
|
+
if (e.method === "map" && args.length === 1) {
|
|
83
|
+
needsStdCollections = true;
|
|
84
|
+
return `Seq.Map(${args[0]}, ${obj})`;
|
|
85
|
+
}
|
|
86
|
+
if (e.method === "filter" && args.length === 1) {
|
|
87
|
+
needsStdCollections = true;
|
|
88
|
+
return `Seq.Filter(${args[0]}, ${obj})`;
|
|
89
|
+
}
|
|
90
|
+
if (e.method === "every" && args.length === 1) {
|
|
91
|
+
needsStdCollections = true;
|
|
92
|
+
return `Seq.All(${obj}, ${args[0]})`;
|
|
93
|
+
}
|
|
94
|
+
if (e.method === "some" && args.length === 1 && e.args[0].kind === "lambda" &&
|
|
95
|
+
e.args[0].body.length === 1 && e.args[0].body[0].kind === "return") {
|
|
96
|
+
const lam = e.args[0];
|
|
97
|
+
const ret = lam.body[0];
|
|
98
|
+
if (ret.kind !== "return")
|
|
99
|
+
throw new Error("unreachable");
|
|
100
|
+
const p = escapeName(lam.params[0]?.name ?? "x");
|
|
101
|
+
const body = emitExpr(ret.value);
|
|
102
|
+
return `(exists ${p} :: ${p} in ${obj} && ${body})`;
|
|
103
|
+
}
|
|
104
|
+
return `${obj}.${e.method}(${args.join(", ")})`;
|
|
105
|
+
}
|
|
106
|
+
case "lambda": {
|
|
107
|
+
const ps = paramList(e.params);
|
|
108
|
+
if (e.body.length === 1 && e.body[0].kind === "return")
|
|
109
|
+
return `(${ps}) => ${emitExpr(e.body[0].value)}`;
|
|
110
|
+
throw new Error("Unsupported: multi-statement lambda in Dafny");
|
|
111
|
+
}
|
|
112
|
+
case "unop": {
|
|
113
|
+
const op = mapOp(e.op);
|
|
114
|
+
if (op === "!" && e.expr.kind !== "var" && e.expr.kind !== "bool")
|
|
115
|
+
return `!(${emitExpr(e.expr)})`;
|
|
116
|
+
if (e.op === "-" && e.expr.kind === "num")
|
|
117
|
+
return `(-(${e.expr.value}))`;
|
|
118
|
+
if (e.op === "-")
|
|
119
|
+
return `(-(${emitExpr(e.expr)}))`;
|
|
120
|
+
return `${op}(${emitExpr(e.expr)})`;
|
|
121
|
+
}
|
|
122
|
+
case "binop": {
|
|
123
|
+
// Discriminant check: x == .Ctor → x.Ctor?
|
|
124
|
+
const op = mapOp(e.op);
|
|
125
|
+
if ((op === "==" || op === "!=") && e.right.kind === "constructor") {
|
|
126
|
+
const ctorName = escapeName(e.right.name.replace(/^\./, ""));
|
|
127
|
+
const pred = `${emitExpr(e.left)}.${ctorName}?`;
|
|
128
|
+
return op === "!=" ? `(!${pred})` : pred;
|
|
129
|
+
}
|
|
130
|
+
return `(${emitExpr(e.left)} ${op} ${emitExpr(e.right)})`;
|
|
131
|
+
}
|
|
132
|
+
case "implies": {
|
|
133
|
+
const parts = [...e.premises.map(emitExpr), emitExpr(e.conclusion)];
|
|
134
|
+
return `(${parts.join(" ==> ")})`;
|
|
135
|
+
}
|
|
136
|
+
case "app": {
|
|
137
|
+
const args = e.args.map(emitExpr);
|
|
138
|
+
// Dafny built-in translations
|
|
139
|
+
if (e.fn === "StringIndexOf") {
|
|
140
|
+
needsStringIndexOf = true;
|
|
141
|
+
return `StringIndexOf(${args.join(", ")})`;
|
|
142
|
+
}
|
|
143
|
+
if (e.fn === "StringSlice")
|
|
144
|
+
return `${args[0]}[${args[1]}..${args[2]}]`;
|
|
145
|
+
if (e.fn === "SeqPush")
|
|
146
|
+
return `(${args[0]} + [${args[1]}])`;
|
|
147
|
+
if (e.fn === "JSFloorDiv")
|
|
148
|
+
needsJSFloorDiv = true;
|
|
149
|
+
return `${e.fn}(${args.join(", ")})`;
|
|
150
|
+
}
|
|
151
|
+
case "field": {
|
|
152
|
+
const obj = emitExpr(e.obj);
|
|
153
|
+
if (e.field === "size" || e.field === "length")
|
|
154
|
+
return `|${obj}|`;
|
|
155
|
+
if (e.field === "toNat")
|
|
156
|
+
return obj;
|
|
157
|
+
return `${obj}.${escapeName(e.field)}`;
|
|
158
|
+
}
|
|
159
|
+
case "toNat":
|
|
160
|
+
// Dafny doesn't need toNat — just emit the inner expression
|
|
161
|
+
return emitExpr(e.expr);
|
|
162
|
+
case "index":
|
|
163
|
+
return `${emitExpr(e.arr)}[${emitExpr(e.idx)}]`;
|
|
164
|
+
case "record": {
|
|
165
|
+
if (e.spread) {
|
|
166
|
+
const updates = e.fields.map(f => `${escapeName(f.name)} := ${emitExpr(f.value)}`);
|
|
167
|
+
return `${emitExpr(e.spread)}.(${updates.join(", ")})`;
|
|
168
|
+
}
|
|
169
|
+
const ctorName = e.fields.length > 0 ? _recordCtors.get(e.fields[0].name) : undefined;
|
|
170
|
+
const vals = e.fields.map(f => emitExpr(f.value));
|
|
171
|
+
if (ctorName)
|
|
172
|
+
return `${ctorName}(${vals.join(", ")})`;
|
|
173
|
+
return `(${vals.join(", ")})`;
|
|
174
|
+
}
|
|
175
|
+
case "if":
|
|
176
|
+
return `if ${emitExpr(e.cond)} then ${emitExpr(e.then)} else ${emitExpr(e.else)}`;
|
|
177
|
+
case "match": {
|
|
178
|
+
const arms = e.arms.map(a => `case ${a.pattern.replace(/^\./, "")} => ${emitExpr(a.body)}`);
|
|
179
|
+
return `match ${e.scrutinee} { ${arms.join(" ")} }`;
|
|
180
|
+
}
|
|
181
|
+
case "forall": return `forall ${e.var}: ${leanTypeToDafny(e.type)} :: ${emitExpr(e.body)}`;
|
|
182
|
+
case "exists": return `exists ${e.var}: ${leanTypeToDafny(e.type)} :: ${emitExpr(e.body)}`;
|
|
183
|
+
case "let": return `var ${escapeName(e.name)} := ${emitExpr(e.value)}; ${emitExpr(e.body)}`;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/** Emit a pure expression with indentation for if/match/let. */
|
|
187
|
+
function emitPureExpr(e, indent) {
|
|
188
|
+
const pad = " ".repeat(indent);
|
|
189
|
+
switch (e.kind) {
|
|
190
|
+
case "if":
|
|
191
|
+
return `${pad}if ${emitExpr(e.cond)} then\n${emitPureExpr(e.then, indent + 1)}\n${pad}else\n${emitPureExpr(e.else, indent + 1)}`;
|
|
192
|
+
case "match": {
|
|
193
|
+
const lines = [`${pad}match ${e.scrutinee} {`];
|
|
194
|
+
for (const arm of e.arms) {
|
|
195
|
+
lines.push(`${pad} case ${translatePattern(arm.pattern)} =>`);
|
|
196
|
+
lines.push(emitPureExpr(arm.body, indent + 2));
|
|
197
|
+
}
|
|
198
|
+
lines.push(`${pad}}`);
|
|
199
|
+
return lines.join("\n");
|
|
200
|
+
}
|
|
201
|
+
case "let":
|
|
202
|
+
return `${pad}var ${escapeName(e.name)} := ${emitExpr(e.value)};\n${emitPureExpr(e.body, indent)}`;
|
|
203
|
+
default:
|
|
204
|
+
return `${pad}${emitExpr(e)}`;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// ── Statement emission ──────────────────────────────────────
|
|
208
|
+
function emitStmts(stmts, indent) {
|
|
209
|
+
return stmts.map(s => emitStmt(s, indent)).join("\n");
|
|
210
|
+
}
|
|
211
|
+
function emitStmt(s, indent) {
|
|
212
|
+
const pad = " ".repeat(indent);
|
|
213
|
+
switch (s.kind) {
|
|
214
|
+
case "let":
|
|
215
|
+
return `${pad}var ${escapeName(s.name)} := ${emitExpr(s.value)};`;
|
|
216
|
+
case "assign":
|
|
217
|
+
return `${pad}${escapeName(s.target)} := ${emitExpr(s.value)};`;
|
|
218
|
+
case "bind":
|
|
219
|
+
// Monadic bind shouldn't appear in Dafny mode, emit as regular assign
|
|
220
|
+
return `${pad}${escapeName(s.target)} := ${emitExpr(s.value)};`;
|
|
221
|
+
case "let-bind":
|
|
222
|
+
// Monadic let-bind shouldn't appear in Dafny mode, emit as regular let
|
|
223
|
+
return `${pad}var ${escapeName(s.name)} := ${emitExpr(s.value)};`;
|
|
224
|
+
case "return":
|
|
225
|
+
return `${pad}return ${emitExpr(s.value)};`;
|
|
226
|
+
case "break":
|
|
227
|
+
return `${pad}break;`;
|
|
228
|
+
case "continue":
|
|
229
|
+
throw new Error("Unsupported Dafny construct: 'continue' statement");
|
|
230
|
+
case "if": {
|
|
231
|
+
let out = `${pad}if ${emitExpr(s.cond)} {\n${emitStmts(s.then, indent + 1)}\n${pad}}`;
|
|
232
|
+
if (s.else.length > 0) {
|
|
233
|
+
if (s.else.length === 1 && s.else[0].kind === "if") {
|
|
234
|
+
out += ` else ${emitStmt(s.else[0], indent).trimStart()}`;
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
out += ` else {\n${emitStmts(s.else, indent + 1)}\n${pad}}`;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return out;
|
|
241
|
+
}
|
|
242
|
+
case "match": {
|
|
243
|
+
const lines = [`${pad}match ${s.scrutinee} {`];
|
|
244
|
+
for (const arm of s.arms) {
|
|
245
|
+
lines.push(`${pad} case ${translatePattern(arm.pattern)} =>`);
|
|
246
|
+
lines.push(emitStmts(arm.body, indent + 2));
|
|
247
|
+
}
|
|
248
|
+
lines.push(`${pad}}`);
|
|
249
|
+
return lines.join("\n");
|
|
250
|
+
}
|
|
251
|
+
case "while": {
|
|
252
|
+
const lines = [`${pad}while ${emitExpr(s.cond)}`];
|
|
253
|
+
for (const inv of s.invariants)
|
|
254
|
+
lines.push(`${pad} invariant ${emitExpr(inv)}`);
|
|
255
|
+
if (s.decreasing)
|
|
256
|
+
lines.push(`${pad} decreases ${emitExpr(s.decreasing)}`);
|
|
257
|
+
lines.push(`${pad}{`);
|
|
258
|
+
lines.push(emitStmts(s.body, indent + 1));
|
|
259
|
+
lines.push(`${pad}}`);
|
|
260
|
+
return lines.join("\n");
|
|
261
|
+
}
|
|
262
|
+
case "forin": {
|
|
263
|
+
// Lean for-in → Dafny while loop over index
|
|
264
|
+
const idx = escapeName(s.idx);
|
|
265
|
+
const lines = [
|
|
266
|
+
`${pad}var ${idx} := 0;`,
|
|
267
|
+
`${pad}while ${idx} < ${emitExpr(s.bound)}`,
|
|
268
|
+
];
|
|
269
|
+
for (const inv of s.invariants)
|
|
270
|
+
lines.push(`${pad} invariant ${emitExpr(inv)}`);
|
|
271
|
+
lines.push(`${pad}{`);
|
|
272
|
+
lines.push(emitStmts(s.body, indent + 1));
|
|
273
|
+
lines.push(`${pad} ${idx} := ${idx} + 1;`);
|
|
274
|
+
lines.push(`${pad}}`);
|
|
275
|
+
return lines.join("\n");
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// ── Declaration emission ────────────────────────────────────
|
|
280
|
+
function emitDecl(d) {
|
|
281
|
+
switch (d.kind) {
|
|
282
|
+
case "inductive": {
|
|
283
|
+
const ctors = d.constructors.map(c => {
|
|
284
|
+
if (c.fields.length === 0)
|
|
285
|
+
return escapeName(c.name);
|
|
286
|
+
return `${escapeName(c.name)}(${paramList(c.fields)})`;
|
|
287
|
+
});
|
|
288
|
+
return `datatype ${d.name} = ${ctors.join(" | ")}`;
|
|
289
|
+
}
|
|
290
|
+
case "structure": {
|
|
291
|
+
return `datatype ${d.name} = ${d.name}(${paramList(d.fields)})`;
|
|
292
|
+
}
|
|
293
|
+
case "def": {
|
|
294
|
+
const lines = [`function ${d.name}(${paramList(d.params)}): ${leanTypeToDafny(d.returnType)}`];
|
|
295
|
+
for (const r of d.requires)
|
|
296
|
+
lines.push(` requires ${emitExpr(r)}`);
|
|
297
|
+
lines.push(`{`);
|
|
298
|
+
lines.push(emitPureExpr(d.body, 1));
|
|
299
|
+
lines.push(`}`);
|
|
300
|
+
// Companion lemma for ensures (proof target for LLM)
|
|
301
|
+
if (d.ensures.length > 0) {
|
|
302
|
+
lines.push("");
|
|
303
|
+
lines.push(`lemma ${d.name}_ensures(${paramList(d.params)})`);
|
|
304
|
+
for (const r of d.requires)
|
|
305
|
+
lines.push(` requires ${emitExpr(r)}`);
|
|
306
|
+
for (const e of d.ensures)
|
|
307
|
+
lines.push(` ensures ${emitExpr(e)}`);
|
|
308
|
+
lines.push(`{`);
|
|
309
|
+
lines.push(`}`);
|
|
310
|
+
}
|
|
311
|
+
return lines.join("\n");
|
|
312
|
+
}
|
|
313
|
+
case "method": {
|
|
314
|
+
const lines = [`method ${d.name}(${paramList(d.params)}) returns (res: ${leanTypeToDafny(d.returnType)})`];
|
|
315
|
+
for (const r of d.requires)
|
|
316
|
+
lines.push(` requires ${emitExpr(r)}`);
|
|
317
|
+
for (const e of d.ensures)
|
|
318
|
+
lines.push(` ensures ${emitExpr(e)}`);
|
|
319
|
+
lines.push(`{`);
|
|
320
|
+
lines.push(emitStmts(d.body, 1));
|
|
321
|
+
lines.push(`}`);
|
|
322
|
+
return lines.join("\n");
|
|
323
|
+
}
|
|
324
|
+
case "namespace": {
|
|
325
|
+
// Dafny doesn't need namespaces — flatten declarations
|
|
326
|
+
return d.decls.map(emitDecl).join("\n\n");
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
// ── File emission ───────────────────────────────────────────
|
|
331
|
+
// ── Preamble helpers ────────────────────────────────────────
|
|
332
|
+
let needsStringIndexOf = false;
|
|
333
|
+
let needsJSFloorDiv = false;
|
|
334
|
+
let needsStdCollections = false;
|
|
335
|
+
const JS_FLOOR_DIV = `function JSFloorDiv(a: int, b: int): int
|
|
336
|
+
requires b != 0
|
|
337
|
+
{
|
|
338
|
+
if b > 0 then
|
|
339
|
+
if a >= 0 then a / b
|
|
340
|
+
else -((-a - 1) / b) - 1
|
|
341
|
+
else
|
|
342
|
+
if a <= 0 then (-a) / (-b)
|
|
343
|
+
else -((a - 1) / (-b)) - 1
|
|
344
|
+
}`;
|
|
345
|
+
const STRING_INDEX_OF = `function StringIndexOf(s: string, sub: string): int
|
|
346
|
+
{
|
|
347
|
+
StringIndexOfFrom(s, sub, 0)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function StringIndexOfFrom(s: string, sub: string, from: nat): int
|
|
351
|
+
decreases |s| - from
|
|
352
|
+
{
|
|
353
|
+
if from + |sub| > |s| then -1
|
|
354
|
+
else if s[from..from + |sub|] == sub then from as int
|
|
355
|
+
else StringIndexOfFrom(s, sub, from + 1)
|
|
356
|
+
}`;
|
|
357
|
+
// ── Constructor and record helpers ───────────────────────────
|
|
358
|
+
let _recordCtors = new Map();
|
|
359
|
+
function buildRecordCtorMap(decls) {
|
|
360
|
+
_recordCtors = new Map();
|
|
361
|
+
for (const d of decls) {
|
|
362
|
+
if (d.kind === "structure" && d.fields.length > 0)
|
|
363
|
+
_recordCtors.set(d.fields[0].name, d.name);
|
|
364
|
+
if (d.kind === "namespace")
|
|
365
|
+
for (const inner of d.decls) {
|
|
366
|
+
if (inner.kind === "structure" && inner.fields.length > 0)
|
|
367
|
+
_recordCtors.set(inner.fields[0].name, inner.name);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
function qualifyCtor(name, type) {
|
|
372
|
+
const rawName = name.replace(/^\./, "");
|
|
373
|
+
if (type)
|
|
374
|
+
return `${type}.${escapeName(rawName)}`;
|
|
375
|
+
return escapeName(rawName);
|
|
376
|
+
}
|
|
377
|
+
/** Translate a Lean match pattern to Dafny syntax.
|
|
378
|
+
* ".ctorName field1 field2" → "ctorName(field1, field2)"
|
|
379
|
+
* ".ctorName" → "ctorName"
|
|
380
|
+
* "_" → "_"
|
|
381
|
+
*/
|
|
382
|
+
function translatePattern(pattern) {
|
|
383
|
+
if (pattern === "_")
|
|
384
|
+
return "_";
|
|
385
|
+
const m = pattern.match(/^\.(\w+)\s*(.*)$/);
|
|
386
|
+
if (!m)
|
|
387
|
+
return pattern;
|
|
388
|
+
const ctorName = escapeName(m[1]);
|
|
389
|
+
const fields = m[2].trim();
|
|
390
|
+
if (!fields)
|
|
391
|
+
return ctorName;
|
|
392
|
+
const fieldNames = fields.split(/\s+/).map(escapeName);
|
|
393
|
+
return `${ctorName}(${fieldNames.join(", ")})`;
|
|
394
|
+
}
|
|
395
|
+
const PREAMBLES = {
|
|
396
|
+
StringIndexOf: STRING_INDEX_OF,
|
|
397
|
+
};
|
|
398
|
+
export function emitDafnyFile(file, tsFileName) {
|
|
399
|
+
buildRecordCtorMap(file.decls);
|
|
400
|
+
needsStringIndexOf = false;
|
|
401
|
+
needsJSFloorDiv = false;
|
|
402
|
+
needsStdCollections = false;
|
|
403
|
+
// Collect pure def names so we can skip their method wrappers
|
|
404
|
+
const pureDefs = new Set();
|
|
405
|
+
for (const d of file.decls) {
|
|
406
|
+
if (d.kind === "namespace") {
|
|
407
|
+
for (const inner of d.decls)
|
|
408
|
+
if (inner.kind === "def")
|
|
409
|
+
pureDefs.add(inner.name);
|
|
410
|
+
}
|
|
411
|
+
if (d.kind === "def")
|
|
412
|
+
pureDefs.add(d.name);
|
|
413
|
+
}
|
|
414
|
+
// Emit declarations
|
|
415
|
+
const declLines = [];
|
|
416
|
+
const skipped = [];
|
|
417
|
+
for (const decl of file.decls) {
|
|
418
|
+
if (decl.kind === "method" && pureDefs.has(decl.name))
|
|
419
|
+
continue;
|
|
420
|
+
try {
|
|
421
|
+
declLines.push("");
|
|
422
|
+
declLines.push(emitDecl(decl));
|
|
423
|
+
}
|
|
424
|
+
catch (e) {
|
|
425
|
+
const name = "name" in decl ? decl.name : "unknown";
|
|
426
|
+
console.error(`WARNING: skipping '${name}': ${e.message}`);
|
|
427
|
+
skipped.push(name);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (skipped.length > 0) {
|
|
431
|
+
console.error(`WARNING: ${skipped.length} declaration(s) skipped: ${skipped.join(", ")}`);
|
|
432
|
+
}
|
|
433
|
+
// Build output with needed preambles
|
|
434
|
+
const lines = [];
|
|
435
|
+
if (tsFileName)
|
|
436
|
+
lines.push(`// Generated by lsc from ${tsFileName}`);
|
|
437
|
+
if (needsStdCollections)
|
|
438
|
+
lines.push("import Std.Collections.Seq");
|
|
439
|
+
if (needsJSFloorDiv) {
|
|
440
|
+
lines.push("");
|
|
441
|
+
lines.push(JS_FLOOR_DIV);
|
|
442
|
+
}
|
|
443
|
+
if (needsStringIndexOf) {
|
|
444
|
+
lines.push("");
|
|
445
|
+
lines.push(PREAMBLES.StringIndexOf);
|
|
446
|
+
}
|
|
447
|
+
lines.push(...declLines);
|
|
448
|
+
return lines.join("\n") + "\n";
|
|
449
|
+
}
|