lemmascript 0.3.0 → 0.3.2
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/package.json +1 -1
- package/tools/dist/dafny-commands.js +3 -2
- package/tools/dist/dafny-emit.js +111 -155
- package/tools/dist/extract.js +88 -4
- package/tools/dist/lsc.js +7 -1
- package/tools/dist/resolve.js +229 -181
- package/tools/dist/transform.js +245 -147
package/package.json
CHANGED
|
@@ -31,13 +31,14 @@ export function dafnyCheckDiff(genPath, dfyPath) {
|
|
|
31
31
|
catch { /* diff not available */ }
|
|
32
32
|
return true;
|
|
33
33
|
}
|
|
34
|
-
export function dafnyVerify(dfyPath, dir, timeLimit) {
|
|
34
|
+
export function dafnyVerify(dfyPath, dir, timeLimit, extraFlags) {
|
|
35
35
|
console.log("Running dafny verify...");
|
|
36
36
|
try {
|
|
37
37
|
const content = readFileSync(dfyPath, "utf-8");
|
|
38
38
|
const stdLibFlag = content.includes("Std.") ? " --standard-libraries" : "";
|
|
39
39
|
const timeLimitFlag = timeLimit ? ` --verification-time-limit ${timeLimit}` : "";
|
|
40
|
-
|
|
40
|
+
const extra = extraFlags ? ` ${extraFlags}` : "";
|
|
41
|
+
execSync(`dafny verify${stdLibFlag}${timeLimitFlag}${extra} "${dfyPath}"`, { cwd: dir, stdio: "inherit" });
|
|
41
42
|
return true;
|
|
42
43
|
}
|
|
43
44
|
catch {
|
package/tools/dist/dafny-emit.js
CHANGED
|
@@ -14,7 +14,7 @@ function tyToDafny(ty) {
|
|
|
14
14
|
case "map": return `map<${tyToDafny(ty.key)}, ${tyToDafny(ty.value)}>`;
|
|
15
15
|
case "set": return `set<${tyToDafny(ty.elem)}>`;
|
|
16
16
|
case "optional": {
|
|
17
|
-
|
|
17
|
+
needPreamble("OptionType");
|
|
18
18
|
return `Option<${tyToDafny(ty.inner)}>`;
|
|
19
19
|
}
|
|
20
20
|
case "user": return ty.name;
|
|
@@ -55,6 +55,22 @@ const OP_MAP = {
|
|
|
55
55
|
};
|
|
56
56
|
function mapOp(op) { return OP_MAP[op] ?? op; }
|
|
57
57
|
// ── Expression emission ─────────────────────────────────────
|
|
58
|
+
/** Emit a match scrutinee — either a variable name (string) or an expression. */
|
|
59
|
+
function emitScrutinee(s) {
|
|
60
|
+
return typeof s === "string" ? escapeName(s) : emitExpr(s);
|
|
61
|
+
}
|
|
62
|
+
/** Collapse nested forall/exists into a single quantifier with multiple bound vars. */
|
|
63
|
+
function emitQuantifier(e, keyword) {
|
|
64
|
+
const vars = [];
|
|
65
|
+
let body = e;
|
|
66
|
+
while (body.kind === e.kind) {
|
|
67
|
+
const dty = tyToDafny(body.type);
|
|
68
|
+
const ann = dty === "string" ? "" : `: ${dty}`;
|
|
69
|
+
vars.push(`${body.var}${ann}`);
|
|
70
|
+
body = body.body;
|
|
71
|
+
}
|
|
72
|
+
return `${keyword} ${vars.join(", ")} :: ${emitExpr(body)}`;
|
|
73
|
+
}
|
|
58
74
|
function emitExpr(e) {
|
|
59
75
|
switch (e.kind) {
|
|
60
76
|
case "var": return e.name === "undefined" ? "None" : escapeName(e.name);
|
|
@@ -78,6 +94,10 @@ function emitExpr(e) {
|
|
|
78
94
|
return `${obj}[${args[0]} := ${args[1]}]`;
|
|
79
95
|
if (e.method === "includes")
|
|
80
96
|
return `(${args[0]} in ${obj})`;
|
|
97
|
+
if (e.method === "indexOf") {
|
|
98
|
+
needPreamble("SeqIndexOf");
|
|
99
|
+
return `SeqIndexOf(${obj}, ${args[0]})`;
|
|
100
|
+
}
|
|
81
101
|
if (e.method === "push")
|
|
82
102
|
return `(${obj} + [${args[0]}])`;
|
|
83
103
|
if (e.method === "concat")
|
|
@@ -106,25 +126,25 @@ function emitExpr(e) {
|
|
|
106
126
|
// String methods
|
|
107
127
|
if (ty === "string") {
|
|
108
128
|
if (e.method === "indexOf") {
|
|
109
|
-
|
|
129
|
+
needPreamble("StringIndexOf");
|
|
110
130
|
return `StringIndexOf(${obj}, ${args[0]})`;
|
|
111
131
|
}
|
|
112
132
|
if (e.method === "slice")
|
|
113
133
|
return `${obj}[${args[0]}..${args[1]}]`;
|
|
114
134
|
if (e.method === "trim") {
|
|
115
|
-
|
|
135
|
+
needPreamble("StringTrim");
|
|
116
136
|
return `StringTrim(${obj})`;
|
|
117
137
|
}
|
|
118
138
|
if (e.method === "toLowerCase") {
|
|
119
|
-
|
|
139
|
+
needPreamble("StringToLower");
|
|
120
140
|
return `StringToLower(${obj})`;
|
|
121
141
|
}
|
|
122
142
|
if (e.method === "toUpperCase") {
|
|
123
|
-
|
|
143
|
+
needPreamble("StringToUpper");
|
|
124
144
|
return `StringToUpper(${obj})`;
|
|
125
145
|
}
|
|
126
146
|
if (e.method === "includes") {
|
|
127
|
-
|
|
147
|
+
needPreamble("StringIndexOf");
|
|
128
148
|
return `(StringIndexOf(${obj}, ${args[0]}) >= 0)`;
|
|
129
149
|
}
|
|
130
150
|
if (e.method === "charCodeAt")
|
|
@@ -135,7 +155,7 @@ function emitExpr(e) {
|
|
|
135
155
|
if (e.method === "getDirect")
|
|
136
156
|
return `${obj}[${args[0]}]`;
|
|
137
157
|
if (e.method === "get") {
|
|
138
|
-
|
|
158
|
+
needPreamble("OptionType");
|
|
139
159
|
return `(if ${args[0]} in ${obj} then Some(${obj}[${args[0]}]) else None)`;
|
|
140
160
|
}
|
|
141
161
|
if (e.method === "set")
|
|
@@ -187,14 +207,14 @@ function emitExpr(e) {
|
|
|
187
207
|
if (e.right.kind === "num") {
|
|
188
208
|
return `(${emitExpr(e.left)} / ${Math.pow(2, e.right.value)})`;
|
|
189
209
|
}
|
|
190
|
-
|
|
210
|
+
needPreamble("Pow2");
|
|
191
211
|
return `(${emitExpr(e.left)} / Pow2(${emitExpr(e.right)}))`;
|
|
192
212
|
}
|
|
193
213
|
if (e.op === "<<") {
|
|
194
214
|
if (e.right.kind === "num") {
|
|
195
215
|
return `(${emitExpr(e.left)} * ${Math.pow(2, e.right.value)})`;
|
|
196
216
|
}
|
|
197
|
-
|
|
217
|
+
needPreamble("Pow2");
|
|
198
218
|
return `(${emitExpr(e.left)} * Pow2(${emitExpr(e.right)}))`;
|
|
199
219
|
}
|
|
200
220
|
// x & mask → x % (mask + 1) for literal masks of form 2^n - 1, else BitAnd
|
|
@@ -206,7 +226,7 @@ function emitExpr(e) {
|
|
|
206
226
|
return `(${emitExpr(e.left)} % ${modulus})`;
|
|
207
227
|
}
|
|
208
228
|
}
|
|
209
|
-
|
|
229
|
+
needPreamble("BitAnd");
|
|
210
230
|
return `BitAnd(${emitExpr(e.left)}, ${emitExpr(e.right)})`;
|
|
211
231
|
}
|
|
212
232
|
// int * real coercion: wrap int side with "as real"
|
|
@@ -228,7 +248,7 @@ function emitExpr(e) {
|
|
|
228
248
|
case "app": {
|
|
229
249
|
const args = e.args.map(emitExpr);
|
|
230
250
|
if (e.fn === "SetToSeq") {
|
|
231
|
-
|
|
251
|
+
needPreamble("SetToSeq");
|
|
232
252
|
return `SetToSeq(${args.join(", ")})`;
|
|
233
253
|
}
|
|
234
254
|
if (e.fn === "BigInt" || e.fn === "Number")
|
|
@@ -237,19 +257,19 @@ function emitExpr(e) {
|
|
|
237
257
|
if (e.fn === "SetLiteral")
|
|
238
258
|
return `{${args.join(", ")}}`;
|
|
239
259
|
if (e.fn === "JSFloorDiv")
|
|
240
|
-
|
|
260
|
+
needPreamble("JSFloorDiv");
|
|
241
261
|
if (e.fn === "CeilReal")
|
|
242
|
-
|
|
262
|
+
needPreamble("CeilReal");
|
|
243
263
|
if (e.fn === "FloorReal")
|
|
244
|
-
|
|
264
|
+
needPreamble("FloorReal");
|
|
245
265
|
if (e.fn === "NatToString")
|
|
246
|
-
|
|
266
|
+
needPreamble("NatToString");
|
|
247
267
|
if (e.fn === "MathAbs")
|
|
248
|
-
|
|
268
|
+
needPreamble("MathAbs");
|
|
249
269
|
if (e.fn === "MathMin")
|
|
250
|
-
|
|
270
|
+
needPreamble("MathMin");
|
|
251
271
|
if (e.fn === "MathMax")
|
|
252
|
-
|
|
272
|
+
needPreamble("MathMax");
|
|
253
273
|
return `${escapeName(e.fn)}(${args.join(", ")})`;
|
|
254
274
|
}
|
|
255
275
|
case "field": {
|
|
@@ -295,7 +315,7 @@ function emitExpr(e) {
|
|
|
295
315
|
if (f)
|
|
296
316
|
return emitExpr(f.value);
|
|
297
317
|
if (sf.type.kind === "optional") {
|
|
298
|
-
|
|
318
|
+
needPreamble("OptionType");
|
|
299
319
|
return "None";
|
|
300
320
|
}
|
|
301
321
|
return `/* missing: ${sf.name} */`;
|
|
@@ -311,34 +331,12 @@ function emitExpr(e) {
|
|
|
311
331
|
case "if":
|
|
312
332
|
return `if ${emitExpr(e.cond)} then ${emitExpr(e.then)} else ${emitExpr(e.else)}`;
|
|
313
333
|
case "match": {
|
|
314
|
-
const scrut =
|
|
334
|
+
const scrut = emitScrutinee(e.scrutinee);
|
|
315
335
|
const arms = e.arms.map(a => `case ${translatePattern(a.pattern)} => ${emitExpr(a.body)}`);
|
|
316
336
|
return `(match ${scrut} { ${arms.join(" ")} })`;
|
|
317
337
|
}
|
|
318
|
-
case "forall":
|
|
319
|
-
|
|
320
|
-
const vars = [];
|
|
321
|
-
let body = e;
|
|
322
|
-
while (body.kind === "forall") {
|
|
323
|
-
const dty = tyToDafny(body.type);
|
|
324
|
-
const ann = dty === "string" ? "" : `: ${dty}`;
|
|
325
|
-
vars.push(`${body.var}${ann}`);
|
|
326
|
-
body = body.body;
|
|
327
|
-
}
|
|
328
|
-
return `forall ${vars.join(", ")} :: ${emitExpr(body)}`;
|
|
329
|
-
}
|
|
330
|
-
case "exists": {
|
|
331
|
-
// Collapse nested exists: exists x :: exists y :: P → exists x, y :: P
|
|
332
|
-
const vars = [];
|
|
333
|
-
let body = e;
|
|
334
|
-
while (body.kind === "exists") {
|
|
335
|
-
const dty = tyToDafny(body.type);
|
|
336
|
-
const ann = dty === "string" ? "" : `: ${dty}`;
|
|
337
|
-
vars.push(`${body.var}${ann}`);
|
|
338
|
-
body = body.body;
|
|
339
|
-
}
|
|
340
|
-
return `exists ${vars.join(", ")} :: ${emitExpr(body)}`;
|
|
341
|
-
}
|
|
338
|
+
case "forall": return emitQuantifier(e, "forall");
|
|
339
|
+
case "exists": return emitQuantifier(e, "exists");
|
|
342
340
|
case "let": return `var ${escapeName(e.name)} := ${emitExpr(e.value)}; ${emitExpr(e.body)}`;
|
|
343
341
|
case "havoc": return "*";
|
|
344
342
|
}
|
|
@@ -350,7 +348,7 @@ function emitPureExpr(e, indent) {
|
|
|
350
348
|
case "if":
|
|
351
349
|
return `${pad}if ${emitExpr(e.cond)} then\n${emitPureExpr(e.then, indent + 1)}\n${pad}else\n${emitPureExpr(e.else, indent + 1)}`;
|
|
352
350
|
case "match": {
|
|
353
|
-
const scrut =
|
|
351
|
+
const scrut = emitScrutinee(e.scrutinee);
|
|
354
352
|
const lines = [`${pad}match ${scrut} {`];
|
|
355
353
|
for (const arm of e.arms) {
|
|
356
354
|
lines.push(`${pad} case ${translatePattern(arm.pattern)} =>`);
|
|
@@ -415,7 +413,7 @@ function emitStmt(s, indent) {
|
|
|
415
413
|
return out;
|
|
416
414
|
}
|
|
417
415
|
case "match": {
|
|
418
|
-
const scrut =
|
|
416
|
+
const scrut = emitScrutinee(s.scrutinee);
|
|
419
417
|
const lines = [`${pad}match ${scrut} {`];
|
|
420
418
|
for (const arm of s.arms) {
|
|
421
419
|
lines.push(`${pad} case ${translatePattern(arm.pattern)} =>`);
|
|
@@ -480,8 +478,10 @@ function emitDecl(d) {
|
|
|
480
478
|
lines.push(`}`);
|
|
481
479
|
// Companion lemma for ensures (proof target for LLM)
|
|
482
480
|
if (d.ensures.length > 0) {
|
|
481
|
+
// Strip constraints like (==) from type params — ghost lemmas don't need them
|
|
482
|
+
const lemmaTP = d.typeParams.length > 0 ? `<${d.typeParams.map(t => t.replace(/\(.*\)/, '')).join(", ")}>` : "";
|
|
483
483
|
lines.push("");
|
|
484
|
-
lines.push(`lemma ${d.name}_ensures${
|
|
484
|
+
lines.push(`lemma ${d.name}_ensures${lemmaTP}(${paramList(d.params)})`);
|
|
485
485
|
for (const r of d.requires)
|
|
486
486
|
lines.push(` requires ${emitExpr(r)}`);
|
|
487
487
|
for (const e of d.ensures)
|
|
@@ -534,21 +534,9 @@ function emitDecl(d) {
|
|
|
534
534
|
}
|
|
535
535
|
// ── File emission ───────────────────────────────────────────
|
|
536
536
|
// ── Preamble helpers ────────────────────────────────────────
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
let needsStringToUpper = false;
|
|
541
|
-
let needsJSFloorDiv = false;
|
|
542
|
-
let needsCeilReal = false;
|
|
543
|
-
let needsFloorReal = false;
|
|
544
|
-
let needsOptionType = false;
|
|
545
|
-
let needsSetToSeq = false;
|
|
546
|
-
let needsBitAnd = false;
|
|
547
|
-
let needsPow2 = false;
|
|
548
|
-
let needsNatToString = false;
|
|
549
|
-
let needsMathAbs = false;
|
|
550
|
-
let needsMathMin = false;
|
|
551
|
-
let needsMathMax = false;
|
|
537
|
+
/** Preamble tracking — emitters add keys via `needPreamble(key)`, emitDafnyFile emits them. */
|
|
538
|
+
const _neededPreambles = new Set();
|
|
539
|
+
function needPreamble(key) { _neededPreambles.add(key); }
|
|
552
540
|
const POW2 = `function Pow2(n: int): int
|
|
553
541
|
requires n >= 0
|
|
554
542
|
decreases n
|
|
@@ -581,6 +569,25 @@ const CEIL_REAL = `function CeilReal(x: real): int
|
|
|
581
569
|
if x == (x.Floor as real) then x.Floor
|
|
582
570
|
else x.Floor + 1
|
|
583
571
|
}`;
|
|
572
|
+
const SEQ_INDEX_OF = `function SeqIndexOf<T(==)>(s: seq<T>, x: T): int
|
|
573
|
+
ensures -1 <= SeqIndexOf(s, x) < |s|
|
|
574
|
+
ensures SeqIndexOf(s, x) >= 0 ==> s[SeqIndexOf(s, x)] == x
|
|
575
|
+
ensures SeqIndexOf(s, x) == -1 ==> x !in s
|
|
576
|
+
{
|
|
577
|
+
SeqIndexOfFrom(s, x, 0)
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function SeqIndexOfFrom<T(==)>(s: seq<T>, x: T, from: nat): int
|
|
581
|
+
requires from <= |s|
|
|
582
|
+
ensures -1 <= SeqIndexOfFrom(s, x, from) < |s|
|
|
583
|
+
ensures SeqIndexOfFrom(s, x, from) >= 0 ==> s[SeqIndexOfFrom(s, x, from)] == x
|
|
584
|
+
ensures SeqIndexOfFrom(s, x, from) == -1 ==> forall i :: from <= i < |s| ==> s[i] != x
|
|
585
|
+
decreases |s| - from
|
|
586
|
+
{
|
|
587
|
+
if from == |s| then -1
|
|
588
|
+
else if s[from] == x then from as int
|
|
589
|
+
else SeqIndexOfFrom(s, x, from + 1)
|
|
590
|
+
}`;
|
|
584
591
|
const STRING_INDEX_OF = `function StringIndexOf(s: string, sub: string): int
|
|
585
592
|
{
|
|
586
593
|
StringIndexOfFrom(s, sub, 0)
|
|
@@ -646,6 +653,42 @@ const NAT_TO_STRING = `function NatToString(n: nat): string
|
|
|
646
653
|
else NatToString(n / 10) + [digit]
|
|
647
654
|
}`;
|
|
648
655
|
const MATH_ABS = `function MathAbs(x: int): nat { if x >= 0 then x else -x }`;
|
|
656
|
+
const SET_TO_SEQ = `method SetToSeq<T>(s: set<T>) returns (res: seq<T>)
|
|
657
|
+
ensures forall x :: x in s <==> x in res
|
|
658
|
+
ensures |res| == |s|
|
|
659
|
+
{
|
|
660
|
+
var remaining := s;
|
|
661
|
+
res := [];
|
|
662
|
+
while remaining != {}
|
|
663
|
+
invariant remaining <= s
|
|
664
|
+
invariant forall x :: x in res <==> (x in s && x !in remaining)
|
|
665
|
+
invariant |res| + |remaining| == |s|
|
|
666
|
+
decreases remaining
|
|
667
|
+
{
|
|
668
|
+
var x :| x in remaining;
|
|
669
|
+
res := res + [x];
|
|
670
|
+
remaining := remaining - {x};
|
|
671
|
+
}
|
|
672
|
+
}`;
|
|
673
|
+
/** Preamble code keyed by name. Emitted in this order when needed. */
|
|
674
|
+
const PREAMBLE_CODE = [
|
|
675
|
+
["OptionType", "datatype Option<T> = None | Some(value: T)"],
|
|
676
|
+
["SetToSeq", SET_TO_SEQ],
|
|
677
|
+
["Pow2", POW2],
|
|
678
|
+
["BitAnd", BIT_AND],
|
|
679
|
+
["JSFloorDiv", JS_FLOOR_DIV],
|
|
680
|
+
["CeilReal", CEIL_REAL],
|
|
681
|
+
["FloorReal", FLOOR_REAL],
|
|
682
|
+
["SeqIndexOf", SEQ_INDEX_OF],
|
|
683
|
+
["StringIndexOf", STRING_INDEX_OF],
|
|
684
|
+
["StringTrim", STRING_TRIM],
|
|
685
|
+
["StringToLower", STRING_TO_LOWER],
|
|
686
|
+
["StringToUpper", STRING_TO_UPPER],
|
|
687
|
+
["NatToString", NAT_TO_STRING],
|
|
688
|
+
["MathAbs", MATH_ABS],
|
|
689
|
+
["MathMin", MATH_MIN],
|
|
690
|
+
["MathMax", MATH_MAX],
|
|
691
|
+
];
|
|
649
692
|
// ── Constructor and record helpers ───────────────────────────
|
|
650
693
|
let _recordCtors = new Map();
|
|
651
694
|
let _structureDecls = new Map();
|
|
@@ -714,26 +757,9 @@ function translatePattern(pattern) {
|
|
|
714
757
|
const fieldNames = fields.split(/\s+/).map(escapeName);
|
|
715
758
|
return `${ctorName}(${fieldNames.join(", ")})`;
|
|
716
759
|
}
|
|
717
|
-
const PREAMBLES = {
|
|
718
|
-
StringIndexOf: STRING_INDEX_OF,
|
|
719
|
-
};
|
|
720
760
|
export function emitDafnyFile(file, tsFileName) {
|
|
721
761
|
buildRecordCtorMap(file.decls);
|
|
722
|
-
|
|
723
|
-
needsStringTrim = false;
|
|
724
|
-
needsStringToLower = false;
|
|
725
|
-
needsStringToUpper = false;
|
|
726
|
-
needsJSFloorDiv = false;
|
|
727
|
-
needsCeilReal = false;
|
|
728
|
-
needsFloorReal = false;
|
|
729
|
-
needsOptionType = false;
|
|
730
|
-
needsSetToSeq = false;
|
|
731
|
-
needsBitAnd = false;
|
|
732
|
-
needsPow2 = false;
|
|
733
|
-
needsNatToString = false;
|
|
734
|
-
needsMathAbs = false;
|
|
735
|
-
needsMathMin = false;
|
|
736
|
-
needsMathMax = false;
|
|
762
|
+
_neededPreambles.clear();
|
|
737
763
|
// Collect pure def names so we can skip their method wrappers
|
|
738
764
|
const pureDefs = new Set();
|
|
739
765
|
for (const d of file.decls) {
|
|
@@ -770,81 +796,11 @@ export function emitDafnyFile(file, tsFileName) {
|
|
|
770
796
|
const lines = [];
|
|
771
797
|
if (tsFileName)
|
|
772
798
|
lines.push(`// Generated by lsc from ${tsFileName}`);
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
lines.push("");
|
|
779
|
-
lines.push(`method SetToSeq<T>(s: set<T>) returns (res: seq<T>)
|
|
780
|
-
ensures forall x :: x in s <==> x in res
|
|
781
|
-
ensures |res| == |s|
|
|
782
|
-
{
|
|
783
|
-
var remaining := s;
|
|
784
|
-
res := [];
|
|
785
|
-
while remaining != {}
|
|
786
|
-
invariant remaining <= s
|
|
787
|
-
invariant forall x :: x in res <==> (x in s && x !in remaining)
|
|
788
|
-
invariant |res| + |remaining| == |s|
|
|
789
|
-
decreases remaining
|
|
790
|
-
{
|
|
791
|
-
var x :| x in remaining;
|
|
792
|
-
res := res + [x];
|
|
793
|
-
remaining := remaining - {x};
|
|
794
|
-
}
|
|
795
|
-
}`);
|
|
796
|
-
}
|
|
797
|
-
if (needsPow2) {
|
|
798
|
-
lines.push("");
|
|
799
|
-
lines.push(POW2);
|
|
800
|
-
}
|
|
801
|
-
if (needsBitAnd) {
|
|
802
|
-
lines.push("");
|
|
803
|
-
lines.push(BIT_AND);
|
|
804
|
-
}
|
|
805
|
-
if (needsJSFloorDiv) {
|
|
806
|
-
lines.push("");
|
|
807
|
-
lines.push(JS_FLOOR_DIV);
|
|
808
|
-
}
|
|
809
|
-
if (needsCeilReal) {
|
|
810
|
-
lines.push("");
|
|
811
|
-
lines.push(CEIL_REAL);
|
|
812
|
-
}
|
|
813
|
-
if (needsFloorReal) {
|
|
814
|
-
lines.push("");
|
|
815
|
-
lines.push(FLOOR_REAL);
|
|
816
|
-
}
|
|
817
|
-
if (needsStringIndexOf) {
|
|
818
|
-
lines.push("");
|
|
819
|
-
lines.push(PREAMBLES.StringIndexOf);
|
|
820
|
-
}
|
|
821
|
-
if (needsStringTrim) {
|
|
822
|
-
lines.push("");
|
|
823
|
-
lines.push(STRING_TRIM);
|
|
824
|
-
}
|
|
825
|
-
if (needsStringToLower) {
|
|
826
|
-
lines.push("");
|
|
827
|
-
lines.push(STRING_TO_LOWER);
|
|
828
|
-
}
|
|
829
|
-
if (needsStringToUpper) {
|
|
830
|
-
lines.push("");
|
|
831
|
-
lines.push(STRING_TO_UPPER);
|
|
832
|
-
}
|
|
833
|
-
if (needsNatToString) {
|
|
834
|
-
lines.push("");
|
|
835
|
-
lines.push(NAT_TO_STRING);
|
|
836
|
-
}
|
|
837
|
-
if (needsMathAbs) {
|
|
838
|
-
lines.push("");
|
|
839
|
-
lines.push(MATH_ABS);
|
|
840
|
-
}
|
|
841
|
-
if (needsMathMin) {
|
|
842
|
-
lines.push("");
|
|
843
|
-
lines.push(MATH_MIN);
|
|
844
|
-
}
|
|
845
|
-
if (needsMathMax) {
|
|
846
|
-
lines.push("");
|
|
847
|
-
lines.push(MATH_MAX);
|
|
799
|
+
for (const [key, code] of PREAMBLE_CODE) {
|
|
800
|
+
if (_neededPreambles.has(key)) {
|
|
801
|
+
lines.push("");
|
|
802
|
+
lines.push(code);
|
|
803
|
+
}
|
|
848
804
|
}
|
|
849
805
|
lines.push(...declLines);
|
|
850
806
|
return lines.join("\n") + "\n";
|
package/tools/dist/extract.js
CHANGED
|
@@ -196,6 +196,7 @@ function extractExpr(node) {
|
|
|
196
196
|
if (Node.isObjectLiteralExpression(node)) {
|
|
197
197
|
let spread = null;
|
|
198
198
|
const fields = [];
|
|
199
|
+
const computedFields = [];
|
|
199
200
|
for (const prop of node.getProperties()) {
|
|
200
201
|
if (Node.isSpreadAssignment(prop)) {
|
|
201
202
|
spread = extractExpr(prop.getExpression());
|
|
@@ -205,10 +206,26 @@ function extractExpr(node) {
|
|
|
205
206
|
fields.push({ name, value: { kind: "var", name } });
|
|
206
207
|
}
|
|
207
208
|
else if (Node.isPropertyAssignment(prop)) {
|
|
209
|
+
const nameNode = prop.getNameNode();
|
|
208
210
|
const init = prop.getInitializer();
|
|
209
|
-
if (init)
|
|
211
|
+
if (init && Node.isComputedPropertyName(nameNode)) {
|
|
212
|
+
computedFields.push({ key: extractExpr(nameNode.getExpression()), value: extractExpr(init) });
|
|
213
|
+
}
|
|
214
|
+
else if (init) {
|
|
210
215
|
fields.push({ name: prop.getName(), value: extractExpr(init) });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// Desugar computed keys: { ...base, [k]: v } → base.set(k, v)
|
|
220
|
+
// No spread: { [k]: v } → {}.set(k, v) (empty map base)
|
|
221
|
+
if (computedFields.length > 0) {
|
|
222
|
+
let result = spread ?? { kind: "record", spread: null, fields: [] };
|
|
223
|
+
for (const cf of computedFields) {
|
|
224
|
+
result = { kind: "call",
|
|
225
|
+
fn: { kind: "field", obj: result, field: "set" },
|
|
226
|
+
args: [cf.key, cf.value] };
|
|
211
227
|
}
|
|
228
|
+
return result;
|
|
212
229
|
}
|
|
213
230
|
return { kind: "record", spread, fields };
|
|
214
231
|
}
|
|
@@ -234,8 +251,9 @@ function extractExpr(node) {
|
|
|
234
251
|
if (name === "Map" && args && args.length === 1) {
|
|
235
252
|
const argType = args[0].getType();
|
|
236
253
|
const argSymbol = argType.getSymbol()?.getName() ?? argType.getAliasSymbol()?.getName();
|
|
237
|
-
|
|
238
|
-
|
|
254
|
+
const argTypeText = _eraseGenerics(typeToString(argType));
|
|
255
|
+
if (argSymbol === "Map" || argTypeText.startsWith("Record<")) {
|
|
256
|
+
// new Map(existingMap) or new Map(record) — identity (Dafny maps are value types)
|
|
239
257
|
return extractExpr(args[0]);
|
|
240
258
|
}
|
|
241
259
|
// new Map(entries) — map-from-array constructor
|
|
@@ -257,6 +275,17 @@ function extractExpr(node) {
|
|
|
257
275
|
if (Node.isAsExpression(node)) {
|
|
258
276
|
return extractExpr(node.getExpression());
|
|
259
277
|
}
|
|
278
|
+
// delete obj[key] → map delete expression
|
|
279
|
+
if (Node.isDeleteExpression(node)) {
|
|
280
|
+
const expr = node.getExpression();
|
|
281
|
+
if (Node.isElementAccessExpression(expr)) {
|
|
282
|
+
return {
|
|
283
|
+
kind: "call",
|
|
284
|
+
fn: { kind: "field", obj: extractExpr(expr.getExpression()), field: "delete" },
|
|
285
|
+
args: [extractExpr(expr.getArgumentExpression())],
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
}
|
|
260
289
|
// null → undefined (both map to None in backends)
|
|
261
290
|
if (Node.isNullLiteral(node)) {
|
|
262
291
|
return { kind: "var", name: "undefined" };
|
|
@@ -511,6 +540,38 @@ function extractStmts(stmts) {
|
|
|
511
540
|
}
|
|
512
541
|
continue;
|
|
513
542
|
}
|
|
543
|
+
// Destructuring rest: const { [k]: _, ...rest } = map → let rest = map.delete(k)
|
|
544
|
+
if (!isHavoc && Node.isObjectBindingPattern(nameNode)) {
|
|
545
|
+
const elements = nameNode.getElements();
|
|
546
|
+
const restEl = elements.find(el => el.getDotDotDotToken());
|
|
547
|
+
const computedEls = elements.filter(el => {
|
|
548
|
+
const pn = el.getPropertyNameNode();
|
|
549
|
+
return pn && Node.isComputedPropertyName(pn);
|
|
550
|
+
});
|
|
551
|
+
if (restEl && computedEls.length > 0) {
|
|
552
|
+
const initializer = d.getInitializer();
|
|
553
|
+
if (initializer) {
|
|
554
|
+
let deleteInit = extractExpr(initializer);
|
|
555
|
+
for (const cel of computedEls) {
|
|
556
|
+
const pn = cel.getPropertyNameNode();
|
|
557
|
+
const keyExpr = extractExpr(pn.getExpression());
|
|
558
|
+
deleteInit = { kind: "call",
|
|
559
|
+
fn: { kind: "field", obj: deleteInit, field: "delete" },
|
|
560
|
+
args: [keyExpr] };
|
|
561
|
+
}
|
|
562
|
+
const declType = d.getType();
|
|
563
|
+
result.push({
|
|
564
|
+
kind: "let",
|
|
565
|
+
name: restEl.getName(),
|
|
566
|
+
mutable: s.getDeclarationKind() === "let",
|
|
567
|
+
tsType: _eraseGenerics(typeToString(declType)),
|
|
568
|
+
init: deleteInit,
|
|
569
|
+
line,
|
|
570
|
+
});
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
514
575
|
const declType = d.getType();
|
|
515
576
|
let init;
|
|
516
577
|
if (isHavoc && !havocKey) {
|
|
@@ -558,7 +619,9 @@ function extractStmts(stmts) {
|
|
|
558
619
|
const nameNode = decl?.getNameNode();
|
|
559
620
|
if (nameNode && Node.isArrayBindingPattern(nameNode)) {
|
|
560
621
|
for (const elem of nameNode.getElements()) {
|
|
561
|
-
if (Node.
|
|
622
|
+
if (Node.isOmittedExpression(elem))
|
|
623
|
+
names.push("_");
|
|
624
|
+
else if (Node.isBindingElement(elem))
|
|
562
625
|
names.push(elem.getNameNode().getText());
|
|
563
626
|
}
|
|
564
627
|
}
|
|
@@ -583,6 +646,27 @@ function extractStmts(stmts) {
|
|
|
583
646
|
});
|
|
584
647
|
continue;
|
|
585
648
|
}
|
|
649
|
+
// for...in: for (const k in obj) → treat as forof with single key name
|
|
650
|
+
if (Node.isForInStatement(s)) {
|
|
651
|
+
const init = s.getInitializer();
|
|
652
|
+
let name = "_";
|
|
653
|
+
if (Node.isVariableDeclarationList(init)) {
|
|
654
|
+
name = init.getDeclarations()[0]?.getName() ?? "_";
|
|
655
|
+
}
|
|
656
|
+
const bodyNode = s.getStatement();
|
|
657
|
+
const bodyStmts = Node.isBlock(bodyNode) ? bodyNode.getStatements() : [bodyNode];
|
|
658
|
+
const annots = collectAnnotations(s, bodyStmts);
|
|
659
|
+
result.push({
|
|
660
|
+
kind: "forof",
|
|
661
|
+
names: [name],
|
|
662
|
+
iterable: extractExpr(s.getExpression()),
|
|
663
|
+
invariants: annots.filter(a => a.kind === "invariant").map(a => a.expr),
|
|
664
|
+
doneWith: annots.find(a => a.kind === "done_with")?.expr ?? null,
|
|
665
|
+
body: extractStmts(bodyStmts),
|
|
666
|
+
line,
|
|
667
|
+
});
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
586
670
|
if (Node.isIfStatement(s)) {
|
|
587
671
|
const thenNode = s.getThenStatement();
|
|
588
672
|
const elseNode = s.getElseStatement();
|
package/tools/dist/lsc.js
CHANGED
|
@@ -33,6 +33,12 @@ function main() {
|
|
|
33
33
|
timeLimit = parseInt(args[timeLimitIdx].split("=")[1]);
|
|
34
34
|
args.splice(timeLimitIdx, 1);
|
|
35
35
|
}
|
|
36
|
+
const extraFlagsIdx = args.findIndex(a => a.startsWith("--extra-flags="));
|
|
37
|
+
let extraFlags;
|
|
38
|
+
if (extraFlagsIdx >= 0) {
|
|
39
|
+
extraFlags = args[extraFlagsIdx].split("=").slice(1).join("=");
|
|
40
|
+
args.splice(extraFlagsIdx, 1);
|
|
41
|
+
}
|
|
36
42
|
const [cmd, filePath] = args;
|
|
37
43
|
if (!cmd || !filePath) {
|
|
38
44
|
console.error("Usage: lsc <gen|check|regen|extract> [--backend=lean|dafny] <file.ts>");
|
|
@@ -101,7 +107,7 @@ function main() {
|
|
|
101
107
|
dafnyGen(genPath, dfyPath, text);
|
|
102
108
|
if (!dafnyCheckDiff(genPath, dfyPath))
|
|
103
109
|
process.exit(1);
|
|
104
|
-
if (!dafnyVerify(dfyPath, dir, timeLimit))
|
|
110
|
+
if (!dafnyVerify(dfyPath, dir, timeLimit, extraFlags))
|
|
105
111
|
process.exit(1);
|
|
106
112
|
return;
|
|
107
113
|
}
|