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.
@@ -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 = typeof e.scrutinee === "string" ? e.scrutinee : r(e.scrutinee);
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" || typeof e.scrutinee === "string")
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 = (typeof m.scrutinee === "string" && m.scrutinee === e.name) ||
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 (containsVarRefExpr(arms.someArm.body, e.name) || containsVarRefExpr(arms.noneArm.body, e.name))
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" || typeof s.scrutinee === "string")
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 = (typeof s2.scrutinee === "string" && s2.scrutinee === s1.name) ||
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 (containsVarRefStmts(restStmts, s1.name))
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 = typeof e.scrutinee === "string" ? e.scrutinee : r(e.scrutinee);
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 = typeof s.scrutinee === "string" ? s.scrutinee : re(s.scrutinee);
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) };
@@ -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
- export {};
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
+ }