lemmascript 0.5.21 → 0.6.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/package.json +1 -1
- package/tools/dist/dafny-emit.js +10 -18
- package/tools/dist/extract.js +26 -9
- package/tools/dist/lsc.js +7 -1
- package/tools/dist/specparser.js +10 -8
- package/tools/dist/transform.js +3 -2
package/package.json
CHANGED
package/tools/dist/dafny-emit.js
CHANGED
|
@@ -1583,10 +1583,8 @@ export function emitDafnyFile(file, tsFileName, opts) {
|
|
|
1583
1583
|
// skipped when the corresponding pure def was actually emitted.
|
|
1584
1584
|
const emittedPureDefs = new Set();
|
|
1585
1585
|
// Emit a decl, rolling back any preamble requirements it registered if it
|
|
1586
|
-
// throws
|
|
1587
|
-
//
|
|
1588
|
-
// preamble (e.g. `type Unknown` from a skipped const whose head is
|
|
1589
|
-
// `unknown`-typed but whose value expr is unsupported).
|
|
1586
|
+
// throws, so callers that catch an emission error never observe partial
|
|
1587
|
+
// emitter state.
|
|
1590
1588
|
const emitDeclTx = (d) => {
|
|
1591
1589
|
const saved = new Set(_neededPreambles);
|
|
1592
1590
|
try {
|
|
@@ -1601,13 +1599,12 @@ export function emitDafnyFile(file, tsFileName, opts) {
|
|
|
1601
1599
|
};
|
|
1602
1600
|
// Emit declarations
|
|
1603
1601
|
const declLines = [];
|
|
1604
|
-
const skipped = [];
|
|
1605
1602
|
for (const decl of file.decls) {
|
|
1606
1603
|
if (decl.kind === "method" && emittedPureDefs.has(decl.name))
|
|
1607
1604
|
continue;
|
|
1608
1605
|
if (decl.kind === "namespace") {
|
|
1609
|
-
// Emit each inner
|
|
1610
|
-
//
|
|
1606
|
+
// Emit each inner declaration separately so failures name the exact
|
|
1607
|
+
// pure declaration that could not be translated.
|
|
1611
1608
|
for (const inner of decl.decls) {
|
|
1612
1609
|
try {
|
|
1613
1610
|
declLines.push("");
|
|
@@ -1617,10 +1614,9 @@ export function emitDafnyFile(file, tsFileName, opts) {
|
|
|
1617
1614
|
}
|
|
1618
1615
|
catch (e) {
|
|
1619
1616
|
const name = "name" in inner ? inner.name : "unknown";
|
|
1620
|
-
const
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
skipped.push(name);
|
|
1617
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
1618
|
+
const source = tsFileName ? ` in ${tsFileName}` : "";
|
|
1619
|
+
throw new Error(`Dafny emission failed for '${name}'${source}: ${reason}`);
|
|
1624
1620
|
}
|
|
1625
1621
|
}
|
|
1626
1622
|
continue;
|
|
@@ -1633,15 +1629,11 @@ export function emitDafnyFile(file, tsFileName, opts) {
|
|
|
1633
1629
|
}
|
|
1634
1630
|
catch (e) {
|
|
1635
1631
|
const name = "name" in decl ? decl.name : "unknown";
|
|
1636
|
-
const
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
skipped.push(name);
|
|
1632
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
1633
|
+
const source = tsFileName ? ` in ${tsFileName}` : "";
|
|
1634
|
+
throw new Error(`Dafny emission failed for '${name}'${source}: ${reason}`);
|
|
1640
1635
|
}
|
|
1641
1636
|
}
|
|
1642
|
-
if (skipped.length > 0) {
|
|
1643
|
-
console.error(`WARNING: ${skipped.length} declaration(s) skipped: ${skipped.join(", ")}`);
|
|
1644
|
-
}
|
|
1645
1637
|
// Build output with needed preambles
|
|
1646
1638
|
const lines = [];
|
|
1647
1639
|
if (tsFileName)
|
package/tools/dist/extract.js
CHANGED
|
@@ -2028,6 +2028,17 @@ export function extractModule(sourceFile) {
|
|
|
2028
2028
|
// here), deduped by qualified name.
|
|
2029
2029
|
_externs.clear();
|
|
2030
2030
|
_externSigTypes.length = 0;
|
|
2031
|
+
// Match a `//@ <kw>` directive only as the first non-whitespace on a line,
|
|
2032
|
+
// so prose mentioning an annotation does not activate it.
|
|
2033
|
+
function hasLineDirective(text, kw) {
|
|
2034
|
+
return new RegExp(String.raw `^[ \t]*//@ ${kw}\b`, "m").test(text);
|
|
2035
|
+
}
|
|
2036
|
+
// Declaration-level directives must be attached as leading comments. Do
|
|
2037
|
+
// not scan a function's whole body: a statement-level `//@ skip` inside it
|
|
2038
|
+
// must not omit the enclosing function.
|
|
2039
|
+
function hasLeadingDirective(node, kw) {
|
|
2040
|
+
return node.getLeadingCommentRanges().some(r => hasLineDirective(r.getText(), kw));
|
|
2041
|
+
}
|
|
2031
2042
|
// Share the module's ts-morph Project with parseTsType (scratch source file
|
|
2032
2043
|
// for type-string parsing). Done before declare-type parsing so any
|
|
2033
2044
|
// parseTsType call downstream uses the same Project.
|
|
@@ -2124,6 +2135,8 @@ export function extractModule(sourceFile) {
|
|
|
2124
2135
|
const constants = [];
|
|
2125
2136
|
for (const stmt of sourceFile.getStatements()) {
|
|
2126
2137
|
if (Node.isVariableStatement(stmt)) {
|
|
2138
|
+
if (hasLeadingDirective(stmt, "skip"))
|
|
2139
|
+
continue;
|
|
2127
2140
|
for (const decl of stmt.getDeclarationList().getDeclarations()) {
|
|
2128
2141
|
if (stmt.getDeclarationList().getFlags() & 2 /* const */) {
|
|
2129
2142
|
const init = decl.getInitializer();
|
|
@@ -2145,7 +2158,8 @@ export function extractModule(sourceFile) {
|
|
|
2145
2158
|
});
|
|
2146
2159
|
}
|
|
2147
2160
|
catch (e) {
|
|
2148
|
-
|
|
2161
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
2162
|
+
throw new Error(`Failed to extract const '${decl.getName()}' at ${sourceFile.getBaseName()}:${decl.getStartLineNumber()}: ${reason}`);
|
|
2149
2163
|
}
|
|
2150
2164
|
}
|
|
2151
2165
|
}
|
|
@@ -2206,11 +2220,9 @@ export function extractModule(sourceFile) {
|
|
|
2206
2220
|
// regex — but its callers should still be verifiable against an
|
|
2207
2221
|
// uninterpreted predicate. Parallel to auto-extern for cross-file calls,
|
|
2208
2222
|
// and emitted the same way (`function {:axiom} foo(...)` in Dafny).
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
function hasLineDirective(text, kw) {
|
|
2213
|
-
return new RegExp(String.raw `^[ \t]*//@ ${kw}\b`, "m").test(text);
|
|
2223
|
+
function hasSkip(f) {
|
|
2224
|
+
return hasLeadingDirective(f.parentStmt ?? f.node, "skip")
|
|
2225
|
+
|| (!!f.parentStmt && hasLeadingDirective(f.node, "skip"));
|
|
2214
2226
|
}
|
|
2215
2227
|
function hasExtern(f) {
|
|
2216
2228
|
if (hasLineDirective(f.node.getFullText(), "extern"))
|
|
@@ -2243,7 +2255,7 @@ export function extractModule(sourceFile) {
|
|
|
2243
2255
|
return null;
|
|
2244
2256
|
}
|
|
2245
2257
|
for (const f of allFns) {
|
|
2246
|
-
if (!hasExtern(f))
|
|
2258
|
+
if (hasSkip(f) || !hasExtern(f))
|
|
2247
2259
|
continue;
|
|
2248
2260
|
const qualified = externName(f) ?? f.name;
|
|
2249
2261
|
const flat = qualified.replace(/\./g, "_");
|
|
@@ -2280,8 +2292,9 @@ export function extractModule(sourceFile) {
|
|
|
2280
2292
|
}
|
|
2281
2293
|
return false;
|
|
2282
2294
|
}
|
|
2283
|
-
const
|
|
2284
|
-
const
|
|
2295
|
+
const nonExternFns = allFns.filter(f => !hasSkip(f) && !hasExtern(f));
|
|
2296
|
+
const hasVerifiedClassMethod = sourceFile.getClasses().some(cls => !hasLeadingDirective(cls, "skip") && cls.getMethods().some(method => !hasLeadingDirective(method, "skip") && hasLineDirective(method.getFullText(), "verify")));
|
|
2297
|
+
const hasVerifyDirective = nonExternFns.some(hasVerify) || hasVerifiedClassMethod;
|
|
2285
2298
|
const fnsToExtract = hasVerifyDirective ? nonExternFns.filter(hasVerify) : nonExternFns;
|
|
2286
2299
|
// `//@ autohavoc` — enable the auto-havoc abstraction (see autohavoc.ts).
|
|
2287
2300
|
// File-level: a directive at column 0 (top of file) enables it for every
|
|
@@ -2795,8 +2808,12 @@ export function extractModule(sourceFile) {
|
|
|
2795
2808
|
// Extract classes with //@ verify methods
|
|
2796
2809
|
const classes = [];
|
|
2797
2810
|
for (const cls of sourceFile.getClasses()) {
|
|
2811
|
+
if (hasLeadingDirective(cls, "skip"))
|
|
2812
|
+
continue;
|
|
2798
2813
|
const methods = [];
|
|
2799
2814
|
for (const method of cls.getMethods()) {
|
|
2815
|
+
if (hasLeadingDirective(method, "skip"))
|
|
2816
|
+
continue;
|
|
2800
2817
|
if (!method.getFullText().includes('//@ verify'))
|
|
2801
2818
|
continue;
|
|
2802
2819
|
methods.push(extractFunction(method));
|
package/tools/dist/lsc.js
CHANGED
|
@@ -337,4 +337,10 @@ function runFile(cmd, filePath, backend, timeLimit, extraFlags, noVerify = false
|
|
|
337
337
|
console.error(`Unknown command: ${cmd}`);
|
|
338
338
|
process.exit(1);
|
|
339
339
|
}
|
|
340
|
-
|
|
340
|
+
try {
|
|
341
|
+
main();
|
|
342
|
+
}
|
|
343
|
+
catch (e) {
|
|
344
|
+
console.error(`ERROR: ${e instanceof Error ? e.message : String(e)}`);
|
|
345
|
+
process.exitCode = 1;
|
|
346
|
+
}
|
package/tools/dist/specparser.js
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { normalizeBigIntLiteral } from "./rawir.js";
|
|
6
6
|
const MULTI_OPS = ["<==>", "==>", "===", "!==", "==", "!=", ">=", "<=", "&&", "||"];
|
|
7
|
-
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
|
|
11
|
-
const
|
|
7
|
+
/** Numeric literals accepted in specs. BigInts are recognized first so their
|
|
8
|
+
* exact value never passes through Number; ordinary numbers additionally
|
|
9
|
+
* allow TypeScript's fractional and exponent forms. */
|
|
10
|
+
const BIGINT_LITERAL = /^(?:(?:0[xX][0-9a-fA-F](?:_?[0-9a-fA-F])*)|(?:0[bB][01](?:_?[01])*)|(?:0[oO][0-7](?:_?[0-7])*)|(?:[0-9](?:_?[0-9])*))n/;
|
|
11
|
+
const NUMBER_LITERAL = /^(?:(?:0[xX][0-9a-fA-F](?:_?[0-9a-fA-F])*)|(?:0[bB][01](?:_?[01])*)|(?:0[oO][0-7](?:_?[0-7])*)|(?:(?:[0-9](?:_?[0-9])*)(?:\.(?:[0-9](?:_?[0-9])*)?)?|\.(?:[0-9](?:_?[0-9])*))(?:[eE][+-]?(?:[0-9](?:_?[0-9])*))?)/;
|
|
12
12
|
function tokenize(input) {
|
|
13
13
|
const tokens = [];
|
|
14
14
|
let i = 0;
|
|
@@ -47,15 +47,17 @@ function tokenize(input) {
|
|
|
47
47
|
tokens.push({ type: "str", value: s });
|
|
48
48
|
continue;
|
|
49
49
|
}
|
|
50
|
-
if (/[0-9]/.test(input[i])) {
|
|
51
|
-
const
|
|
50
|
+
if (/[0-9]/.test(input[i]) || (input[i] === "." && /[0-9]/.test(input[i + 1]))) {
|
|
51
|
+
const rest = input.slice(i);
|
|
52
|
+
const bigintMatch = rest.match(BIGINT_LITERAL);
|
|
53
|
+
const match = bigintMatch ?? rest.match(NUMBER_LITERAL);
|
|
52
54
|
if (!match)
|
|
53
55
|
throw new Error(`Invalid numeric literal at ${i} in: ${input}`);
|
|
54
56
|
const text = match[0];
|
|
55
57
|
i += text.length;
|
|
56
58
|
// The `n` suffix is meaningful, not noise: a BigInt keeps its exact value
|
|
57
59
|
// as a decimal string instead of being rounded into a double.
|
|
58
|
-
if (
|
|
60
|
+
if (bigintMatch)
|
|
59
61
|
tokens.push({ type: "bigint", value: normalizeBigIntLiteral(text) });
|
|
60
62
|
else
|
|
61
63
|
tokens.push({ type: "num", value: Number(text.replace(/_/g, "")) });
|
package/tools/dist/transform.js
CHANGED
|
@@ -2525,7 +2525,8 @@ export function transformModule(mod, specImport, moduleBaseOverride) {
|
|
|
2525
2525
|
};
|
|
2526
2526
|
});
|
|
2527
2527
|
// Def file: Velvet methods
|
|
2528
|
-
// Pure functions get a thin wrapper
|
|
2528
|
+
// Pure functions get a thin wrapper. Lean keeps pure definitions in the
|
|
2529
|
+
// `Pure` namespace; Dafny flattens that namespace, so its call is unqualified.
|
|
2529
2530
|
// def-by-method functions also skip their method wrappers
|
|
2530
2531
|
const pureDefNames = new Set([...pureDefs.map(d => d.name), ...defByMethods.map(d => d.name)]);
|
|
2531
2532
|
const methods = mod.functions.map(fn => {
|
|
@@ -2539,7 +2540,7 @@ export function transformModule(mod, specImport, moduleBaseOverride) {
|
|
|
2539
2540
|
}
|
|
2540
2541
|
_forofCounters.clear();
|
|
2541
2542
|
let body = pureDefNames.has(fn.name)
|
|
2542
|
-
? [{ kind: "return", value: { kind: "app", fn: `Pure.${fn.name}
|
|
2543
|
+
? [{ kind: "return", value: { kind: "app", fn: _opts.backend === "lean" ? `Pure.${fn.name}` : fn.name, args: fn.params.map(p => ({ kind: "var", name: p.name })) } }]
|
|
2543
2544
|
: promoteAssignedLets(transformStmts(fn.body, mod.typeDecls));
|
|
2544
2545
|
// Lean-only method-body rewrites (Velvet can't WP-synthesize monadic matches
|
|
2545
2546
|
// and forbids `return` in loops):
|