lemmascript 0.3.1 → 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-emit.js +24 -0
- package/tools/dist/extract.js +85 -3
- package/tools/dist/resolve.js +11 -3
- package/tools/dist/transform.js +28 -0
package/package.json
CHANGED
package/tools/dist/dafny-emit.js
CHANGED
|
@@ -94,6 +94,10 @@ function emitExpr(e) {
|
|
|
94
94
|
return `${obj}[${args[0]} := ${args[1]}]`;
|
|
95
95
|
if (e.method === "includes")
|
|
96
96
|
return `(${args[0]} in ${obj})`;
|
|
97
|
+
if (e.method === "indexOf") {
|
|
98
|
+
needPreamble("SeqIndexOf");
|
|
99
|
+
return `SeqIndexOf(${obj}, ${args[0]})`;
|
|
100
|
+
}
|
|
97
101
|
if (e.method === "push")
|
|
98
102
|
return `(${obj} + [${args[0]}])`;
|
|
99
103
|
if (e.method === "concat")
|
|
@@ -565,6 +569,25 @@ const CEIL_REAL = `function CeilReal(x: real): int
|
|
|
565
569
|
if x == (x.Floor as real) then x.Floor
|
|
566
570
|
else x.Floor + 1
|
|
567
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
|
+
}`;
|
|
568
591
|
const STRING_INDEX_OF = `function StringIndexOf(s: string, sub: string): int
|
|
569
592
|
{
|
|
570
593
|
StringIndexOfFrom(s, sub, 0)
|
|
@@ -656,6 +679,7 @@ const PREAMBLE_CODE = [
|
|
|
656
679
|
["JSFloorDiv", JS_FLOOR_DIV],
|
|
657
680
|
["CeilReal", CEIL_REAL],
|
|
658
681
|
["FloorReal", FLOOR_REAL],
|
|
682
|
+
["SeqIndexOf", SEQ_INDEX_OF],
|
|
659
683
|
["StringIndexOf", STRING_INDEX_OF],
|
|
660
684
|
["StringTrim", STRING_TRIM],
|
|
661
685
|
["StringToLower", STRING_TO_LOWER],
|
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) {
|
|
@@ -585,6 +646,27 @@ function extractStmts(stmts) {
|
|
|
585
646
|
});
|
|
586
647
|
continue;
|
|
587
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
|
+
}
|
|
588
670
|
if (Node.isIfStatement(s)) {
|
|
589
671
|
const thenNode = s.getThenStatement();
|
|
590
672
|
const elseNode = s.getElseStatement();
|
package/tools/dist/resolve.js
CHANGED
|
@@ -293,6 +293,8 @@ function inferMethodReturnTy(fn, args, ctx) {
|
|
|
293
293
|
else if (objTy.kind === "array") {
|
|
294
294
|
if (fn.field === "includes")
|
|
295
295
|
return { kind: "bool" };
|
|
296
|
+
if (fn.field === "indexOf")
|
|
297
|
+
return { kind: "int" };
|
|
296
298
|
if (fn.field === "shift")
|
|
297
299
|
return objTy.elem;
|
|
298
300
|
if (fn.field === "push" || fn.field === "concat")
|
|
@@ -395,7 +397,7 @@ function resolveExpr(e, ctx) {
|
|
|
395
397
|
const obj = resolveExpr(e.obj, ctx);
|
|
396
398
|
const idx = resolveExpr(e.idx, ctx);
|
|
397
399
|
const idxTy = obj.ty.kind === "array" ? obj.ty.elem
|
|
398
|
-
: obj.ty.kind === "map" ? obj.ty.value
|
|
400
|
+
: obj.ty.kind === "map" ? { kind: "optional", inner: obj.ty.value }
|
|
399
401
|
: { kind: "unknown" };
|
|
400
402
|
return { kind: "index", obj, idx, ty: idxTy };
|
|
401
403
|
}
|
|
@@ -445,6 +447,10 @@ function resolveExpr(e, ctx) {
|
|
|
445
447
|
if (fieldDecl) {
|
|
446
448
|
const declTy = fieldDecl.type;
|
|
447
449
|
value = coerceStr(value, declTy);
|
|
450
|
+
// Empty {} for map-typed fields → empty map (arrayLiteral with map type → emptyMap in transform)
|
|
451
|
+
if (value.kind === "record" && value.fields.length === 0 && !value.spread && declTy.kind === "map") {
|
|
452
|
+
value = { kind: "arrayLiteral", elems: [], ty: declTy };
|
|
453
|
+
}
|
|
448
454
|
// Coerce non-optional to optional: wrap in Some (only when value type is concrete)
|
|
449
455
|
if (declTy.kind === "optional" && value.ty.kind !== "optional" && value.ty.kind !== "void" && value.ty.kind !== "unknown") {
|
|
450
456
|
value = wrapSome(value, declTy);
|
|
@@ -593,8 +599,10 @@ function resolveBlock(stmts, ctx) {
|
|
|
593
599
|
function resolveStmt(s, ctx) {
|
|
594
600
|
switch (s.kind) {
|
|
595
601
|
case "let": {
|
|
596
|
-
const
|
|
597
|
-
const init = coerceStr(resolveExpr(s.init, ctx),
|
|
602
|
+
const declTy = resolveTsType(s.tsType, ctx.overrides, s.name);
|
|
603
|
+
const init = coerceStr(resolveExpr(s.init, ctx), declTy);
|
|
604
|
+
// Map indexing: TS says T, but access can fail → use Optional<T> from init
|
|
605
|
+
const ty = (declTy.kind !== "optional" && init.ty.kind === "optional") ? init.ty : declTy;
|
|
598
606
|
// const collections are mutable in value-semantics world (TS mutates in place, Dafny/Lean reassign)
|
|
599
607
|
const mutable = s.mutable || isRefMutableInTS(ty);
|
|
600
608
|
return [{ kind: "let", name: s.name, ty, mutable, init }, extend(ctx.env, s.name, ty)];
|
package/tools/dist/transform.js
CHANGED
|
@@ -350,6 +350,9 @@ function lowerExpr(e, binds) {
|
|
|
350
350
|
return { kind: "field", obj: transformExpr(e.obj), field: e.field };
|
|
351
351
|
case "index": {
|
|
352
352
|
const idx = transformExpr(e.idx);
|
|
353
|
+
if (e.obj.ty.kind === "map") {
|
|
354
|
+
return { kind: "methodCall", obj: transformExpr(e.obj), objTy: e.obj.ty, method: "get", args: [idx], monadic: false };
|
|
355
|
+
}
|
|
353
356
|
const wrappedIdx = isArray(e.obj.ty) && !isNat(e.idx.ty) ? { kind: "toNat", expr: idx } : idx;
|
|
354
357
|
return { kind: "index", arr: transformExpr(e.obj), idx: wrappedIdx };
|
|
355
358
|
}
|
|
@@ -637,6 +640,31 @@ function transformStmts(stmts, typeDecls) {
|
|
|
637
640
|
const varName = s.names[0];
|
|
638
641
|
const varTy = s.nameTypes[0] ?? { kind: "unknown" };
|
|
639
642
|
let iterExpr = transformExpr(s.iterable);
|
|
643
|
+
// Map key-only iteration: for (const k in record) → iterate keys only
|
|
644
|
+
if (s.names.length === 1 && s.iterable.ty.kind === "map") {
|
|
645
|
+
const keyName = s.names[0];
|
|
646
|
+
const keyTy = s.nameTypes[0] ?? s.iterable.ty.key ?? { kind: "unknown" };
|
|
647
|
+
const keysSeqName = `_${keyName}_keys`;
|
|
648
|
+
const convExpr = { kind: "app", fn: "SetToSeq", args: [{ kind: "field", obj: iterExpr, field: "keys" }] };
|
|
649
|
+
result.push({ kind: "let", name: keysSeqName, type: { kind: "array", elem: keyTy }, mutable: false, value: convExpr });
|
|
650
|
+
const keysVar = { kind: "var", name: keysSeqName };
|
|
651
|
+
const count = _forofCounters.get(keyName) ?? 0;
|
|
652
|
+
_forofCounters.set(keyName, count + 1);
|
|
653
|
+
const suffix = count === 0 ? "" : `${count + 1}`;
|
|
654
|
+
const idxName = `_${keyName}_idx${suffix}`;
|
|
655
|
+
const idx = { kind: "var", name: idxName };
|
|
656
|
+
const arrSize = { kind: "field", obj: keysVar, field: "size" };
|
|
657
|
+
const bodyStmts = transformStmts(s.body, typeDecls);
|
|
658
|
+
const letKey = { kind: "let", name: keyName, type: keyTy, mutable: false, value: { kind: "index", arr: keysVar, idx } };
|
|
659
|
+
const boundInv = { kind: "binop", op: "≤", left: idx, right: arrSize };
|
|
660
|
+
result.push({
|
|
661
|
+
kind: "forin", idx: idxName, bound: arrSize,
|
|
662
|
+
invariants: [boundInv, ...s.invariants.map(transformExpr)],
|
|
663
|
+
body: [letKey, ...bodyStmts],
|
|
664
|
+
});
|
|
665
|
+
i++;
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
640
668
|
// Map iteration: for (const [k, v] of map) → iterate keys, look up values
|
|
641
669
|
if (s.names.length >= 2 && s.iterable.ty.kind === "map") {
|
|
642
670
|
const keyName = s.names[0], valueName = s.names[1];
|