lemmascript 0.5.18 → 0.5.19
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/README.md +1 -1
- package/package.json +1 -1
- package/tools/dist/autohavoc.js +2 -0
- package/tools/dist/builtins.js +124 -0
- package/tools/dist/condition-facts.js +364 -0
- package/tools/dist/dafny-emit.js +162 -35
- package/tools/dist/extract.js +108 -21
- package/tools/dist/info-command.js +68 -0
- package/tools/dist/ir.js +27 -7
- package/tools/dist/lean-emit.js +27 -16
- package/tools/dist/lsc.js +53 -5
- package/tools/dist/names.js +10 -6
- package/tools/dist/narrow.js +296 -677
- package/tools/dist/peephole.js +12 -94
- package/tools/dist/rawir.js +15 -1
- package/tools/dist/resolve.js +182 -203
- package/tools/dist/specparser.js +21 -17
- package/tools/dist/transform.js +298 -108
- package/tools/dist/typedecls.js +59 -0
package/tools/dist/peephole.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { patternCtor, patternBinders } from "./ir.js";
|
|
1
|
+
import { patternCtor, patternBinders, usesName, usesNameInStmts } from "./ir.js";
|
|
2
2
|
// ── Generic walkers (same shape as transform.ts) ─────────────
|
|
3
3
|
function mapExpr(e, f) {
|
|
4
4
|
const hit = f(e);
|
|
@@ -8,6 +8,7 @@ function mapExpr(e, f) {
|
|
|
8
8
|
switch (e.kind) {
|
|
9
9
|
case "var":
|
|
10
10
|
case "num":
|
|
11
|
+
case "bigint":
|
|
11
12
|
case "bool":
|
|
12
13
|
case "str":
|
|
13
14
|
case "constructor":
|
|
@@ -31,7 +32,7 @@ function mapExpr(e, f) {
|
|
|
31
32
|
case "arrayLiteral": return { ...e, elems: e.elems.map(r) };
|
|
32
33
|
case "if": return { ...e, cond: r(e.cond), then: r(e.then), else: r(e.else) };
|
|
33
34
|
case "match": {
|
|
34
|
-
const scr =
|
|
35
|
+
const scr = r(e.scrutinee);
|
|
35
36
|
return { ...e, scrutinee: scr, arms: e.arms.map(a => ({ ...a, body: r(a.body) })) };
|
|
36
37
|
}
|
|
37
38
|
case "forall": return { ...e, body: r(e.body) };
|
|
@@ -70,7 +71,7 @@ function getSomeNoneArms(arms) {
|
|
|
70
71
|
* Bind once (let-expression) rather than substitute, so the verifier doesn't
|
|
71
72
|
* re-derive `k in m` at every use of v inside sb. */
|
|
72
73
|
function ruleMatchOnMapGetExpr(e) {
|
|
73
|
-
if (e.kind !== "match"
|
|
74
|
+
if (e.kind !== "match")
|
|
74
75
|
return null;
|
|
75
76
|
const get = isMapGet(e.scrutinee);
|
|
76
77
|
if (!get)
|
|
@@ -130,15 +131,14 @@ function ruleLetMatchOnMapGetExpr(e) {
|
|
|
130
131
|
if (e.body.kind !== "match")
|
|
131
132
|
return null;
|
|
132
133
|
const m = e.body;
|
|
133
|
-
const matchOnX =
|
|
134
|
-
(typeof m.scrutinee !== "string" && m.scrutinee.kind === "var" && m.scrutinee.name === e.name);
|
|
134
|
+
const matchOnX = m.scrutinee.kind === "var" && m.scrutinee.name === e.name;
|
|
135
135
|
if (!matchOnX)
|
|
136
136
|
return null;
|
|
137
137
|
const arms = getSomeNoneArms(m.arms);
|
|
138
138
|
if (!arms)
|
|
139
139
|
return null;
|
|
140
140
|
// x must not appear in arm bodies (otherwise the binding is needed)
|
|
141
|
-
if (
|
|
141
|
+
if (usesName(arms.someArm.body, e.name) || usesName(arms.noneArm.body, e.name))
|
|
142
142
|
return null;
|
|
143
143
|
const idx = { kind: "index", arr: get.obj, idx: get.key };
|
|
144
144
|
const someBody = arms.binder
|
|
@@ -147,19 +147,6 @@ function ruleLetMatchOnMapGetExpr(e) {
|
|
|
147
147
|
const has = { kind: "methodCall", obj: get.obj, objTy: get.objTy, method: "has", args: [get.key], monadic: false };
|
|
148
148
|
return { kind: "if", cond: has, then: someBody, else: arms.noneArm.body };
|
|
149
149
|
}
|
|
150
|
-
function containsVarRefExpr(e, name) {
|
|
151
|
-
let found = false;
|
|
152
|
-
mapExpr(e, x => {
|
|
153
|
-
if (found)
|
|
154
|
-
return x;
|
|
155
|
-
if (x.kind === "var" && x.name === name) {
|
|
156
|
-
found = true;
|
|
157
|
-
return x;
|
|
158
|
-
}
|
|
159
|
-
return null;
|
|
160
|
-
});
|
|
161
|
-
return found;
|
|
162
|
-
}
|
|
163
150
|
function isBool(e, v) {
|
|
164
151
|
return e.kind === "bool" && e.value === v;
|
|
165
152
|
}
|
|
@@ -187,7 +174,7 @@ let EXPR_RULES = [...MAP_GET_RULES, ...BOOL_RULES];
|
|
|
187
174
|
* Bind once (var declaration) rather than substitute — substituting would
|
|
188
175
|
* re-evaluate m[k] at every use, changing semantics if the body mutates m. */
|
|
189
176
|
function ruleMatchOnMapGetStmt(s) {
|
|
190
|
-
if (s.kind !== "match"
|
|
177
|
+
if (s.kind !== "match")
|
|
191
178
|
return null;
|
|
192
179
|
const get = isMapGet(s.scrutinee);
|
|
193
180
|
if (!get)
|
|
@@ -205,75 +192,6 @@ function ruleMatchOnMapGetStmt(s) {
|
|
|
205
192
|
}
|
|
206
193
|
const STMT_RULES = [ruleMatchOnMapGetStmt];
|
|
207
194
|
// ── Variable use detection ───────────────────────────────────
|
|
208
|
-
/** Conservative reference check: does any expression in this stmt mention `name`?
|
|
209
|
-
* Doesn't track shadowing — worst case, we miss an inlining opportunity.
|
|
210
|
-
* Used to gate let-inlining (only inline if the var is not used anywhere after). */
|
|
211
|
-
function containsVarRefStmt(s, name) {
|
|
212
|
-
let found = false;
|
|
213
|
-
const checkExpr = (e) => {
|
|
214
|
-
if (found)
|
|
215
|
-
return;
|
|
216
|
-
mapExpr(e, x => {
|
|
217
|
-
if (x.kind === "var" && x.name === name) {
|
|
218
|
-
found = true;
|
|
219
|
-
return x;
|
|
220
|
-
}
|
|
221
|
-
return null;
|
|
222
|
-
});
|
|
223
|
-
};
|
|
224
|
-
const walk = (st) => {
|
|
225
|
-
if (found)
|
|
226
|
-
return;
|
|
227
|
-
switch (st.kind) {
|
|
228
|
-
case "let":
|
|
229
|
-
case "assign":
|
|
230
|
-
case "bind":
|
|
231
|
-
case "let-bind":
|
|
232
|
-
case "return":
|
|
233
|
-
case "ghostLet":
|
|
234
|
-
case "ghostAssign":
|
|
235
|
-
checkExpr(st.value);
|
|
236
|
-
return;
|
|
237
|
-
case "assert":
|
|
238
|
-
checkExpr(st.expr);
|
|
239
|
-
return;
|
|
240
|
-
case "break":
|
|
241
|
-
case "continue": return;
|
|
242
|
-
case "if":
|
|
243
|
-
checkExpr(st.cond);
|
|
244
|
-
st.then.forEach(walk);
|
|
245
|
-
st.else.forEach(walk);
|
|
246
|
-
return;
|
|
247
|
-
case "match":
|
|
248
|
-
if (typeof st.scrutinee === "string") {
|
|
249
|
-
if (st.scrutinee === name) {
|
|
250
|
-
found = true;
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
else {
|
|
255
|
-
checkExpr(st.scrutinee);
|
|
256
|
-
}
|
|
257
|
-
st.arms.forEach(a => a.body.forEach(walk));
|
|
258
|
-
return;
|
|
259
|
-
case "while":
|
|
260
|
-
checkExpr(st.cond);
|
|
261
|
-
st.invariants.forEach(checkExpr);
|
|
262
|
-
st.body.forEach(walk);
|
|
263
|
-
return;
|
|
264
|
-
case "forin":
|
|
265
|
-
checkExpr(st.bound);
|
|
266
|
-
st.invariants.forEach(checkExpr);
|
|
267
|
-
st.body.forEach(walk);
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
};
|
|
271
|
-
walk(s);
|
|
272
|
-
return found;
|
|
273
|
-
}
|
|
274
|
-
function containsVarRefStmts(stmts, name) {
|
|
275
|
-
return stmts.some(s => containsVarRefStmt(s, name));
|
|
276
|
-
}
|
|
277
195
|
// ── Statement-list rules (pairs of adjacent stmts) ──────────
|
|
278
196
|
/** Pair rule: let x = m.get(k); match x { Some(v) => sb, None => nb }
|
|
279
197
|
* → if k in m { var v := m[k]; sb } else { nb }
|
|
@@ -287,14 +205,13 @@ function tryLetMatchOnMapGet(s1, s2, restStmts) {
|
|
|
287
205
|
return null;
|
|
288
206
|
if (s2.kind !== "match")
|
|
289
207
|
return null;
|
|
290
|
-
const matchOnX =
|
|
291
|
-
(typeof s2.scrutinee !== "string" && s2.scrutinee.kind === "var" && s2.scrutinee.name === s1.name);
|
|
208
|
+
const matchOnX = s2.scrutinee.kind === "var" && s2.scrutinee.name === s1.name;
|
|
292
209
|
if (!matchOnX)
|
|
293
210
|
return null;
|
|
294
211
|
const arms = getSomeNoneArms(s2.arms);
|
|
295
212
|
if (!arms)
|
|
296
213
|
return null;
|
|
297
|
-
if (
|
|
214
|
+
if (usesNameInStmts(restStmts, s1.name))
|
|
298
215
|
return null;
|
|
299
216
|
const idx = { kind: "index", arr: get.obj, idx: get.key };
|
|
300
217
|
const valTy = get.objTy.kind === "map" ? get.objTy.value : { kind: "unknown" };
|
|
@@ -354,6 +271,7 @@ function rewriteChildrenExpr(e) {
|
|
|
354
271
|
switch (e.kind) {
|
|
355
272
|
case "var":
|
|
356
273
|
case "num":
|
|
274
|
+
case "bigint":
|
|
357
275
|
case "bool":
|
|
358
276
|
case "str":
|
|
359
277
|
case "constructor":
|
|
@@ -377,7 +295,7 @@ function rewriteChildrenExpr(e) {
|
|
|
377
295
|
case "arrayLiteral": return { ...e, elems: e.elems.map(r) };
|
|
378
296
|
case "if": return { ...e, cond: r(e.cond), then: r(e.then), else: r(e.else) };
|
|
379
297
|
case "match": {
|
|
380
|
-
const scr =
|
|
298
|
+
const scr = r(e.scrutinee);
|
|
381
299
|
return { ...e, scrutinee: scr, arms: e.arms.map(a => ({ ...a, body: r(a.body) })) };
|
|
382
300
|
}
|
|
383
301
|
case "forall": return { ...e, body: r(e.body) };
|
|
@@ -418,7 +336,7 @@ function rewriteChildrenStmt(s) {
|
|
|
418
336
|
case "continue": return s;
|
|
419
337
|
case "if": return { ...s, cond: re(s.cond), then: rs(s.then), else: rs(s.else) };
|
|
420
338
|
case "match": {
|
|
421
|
-
const scr =
|
|
339
|
+
const scr = re(s.scrutinee);
|
|
422
340
|
return { ...s, scrutinee: scr, arms: s.arms.map(a => ({ ...a, body: rs(a.body) })) };
|
|
423
341
|
}
|
|
424
342
|
case "while": return { ...s, cond: re(s.cond), invariants: s.invariants.map(re), body: rs(s.body) };
|
package/tools/dist/rawir.js
CHANGED
|
@@ -7,4 +7,18 @@
|
|
|
7
7
|
* Layer 1: structured (no strings for expressions)
|
|
8
8
|
* Layer 2 (planned): add Ty to each node
|
|
9
9
|
*/
|
|
10
|
-
|
|
10
|
+
// ── Expressions ──────────────────────────────────────────────
|
|
11
|
+
/** Source text of a BigInt literal → the canonical decimal string stored in a
|
|
12
|
+
* `bigint` node. Shared by the body extractor and the spec parser so both
|
|
13
|
+
* sides of a function (code and `//@ ` annotations) agree exactly.
|
|
14
|
+
*
|
|
15
|
+
* A BigInt literal must never pass through `Number`/`parseInt`: `9007199254740993n`
|
|
16
|
+
* and `9007199254740992n` are distinct values that collapse to the same double.
|
|
17
|
+
* Hence the string payload — it also keeps the raw IR `JSON.stringify`-able,
|
|
18
|
+
* which `lsc extract` relies on. */
|
|
19
|
+
export function normalizeBigIntLiteral(text) {
|
|
20
|
+
const withoutSuffix = text.endsWith("n") ? text.slice(0, -1) : text;
|
|
21
|
+
// BigInt() understands decimal, hex, binary, and octal; numeric separators
|
|
22
|
+
// are source syntax and must be stripped first.
|
|
23
|
+
return BigInt(withoutSuffix.replace(/_/g, "")).toString(10);
|
|
24
|
+
}
|