lemmascript 0.5.14 → 0.5.15

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lemmascript",
3
- "version": "0.5.14",
3
+ "version": "0.5.15",
4
4
  "description": "A verification toolchain for TypeScript — generates Lean 4 or Dafny from annotated TS",
5
5
  "type": "module",
6
6
  "engines": {
@@ -1083,22 +1083,39 @@ const SEQ_SORT_BY = `function {:axiom} SeqSortBy<T(==,!new)>(s: seq<T>, cmp: (T,
1083
1083
  ensures multiset(SeqSortBy(s, cmp)) == multiset(s)
1084
1084
  ensures |SeqSortBy(s, cmp)| == |s|
1085
1085
  ensures forall i: int, j: int :: 0 <= i <= j < |SeqSortBy(s, cmp)| ==> cmp(SeqSortBy(s, cmp)[i], SeqSortBy(s, cmp)[j]) <= 0`;
1086
- const STRING_TRIM = `function StringTrimLeft(s: string): string
1086
+ // TS/JS String.prototype.trim() strips ECMAScript WhiteSpace ∪ LineTerminator:
1087
+ // Unicode general-category Zs plus TAB/VT/FF/CR/LF, LS/PS, and the BOM — NOT just
1088
+ // U+0020, and NOT U+0085 (NEL, which is Cc). See
1089
+ // https://tc39.es/ecma262/#sec-white-space and
1090
+ // https://tc39.es/ecma262/#sec-line-terminators.
1091
+ // `\\U{..}` are Dafny char escapes (not JS: the string
1092
+ // is emitted verbatim), so the enumeration below is Dafny source, not decoded.
1093
+ const STRING_TRIM = `predicate IsJSWhitespace(c: char)
1094
+ {
1095
+ c == '\\U{0009}' || c == '\\U{000A}' || c == '\\U{000B}' || c == '\\U{000C}' || c == '\\U{000D}' ||
1096
+ c == '\\U{0020}' || c == '\\U{00A0}' || c == '\\U{1680}' ||
1097
+ ('\\U{2000}' <= c <= '\\U{200A}') ||
1098
+ c == '\\U{2028}' || c == '\\U{2029}' || c == '\\U{202F}' || c == '\\U{205F}' ||
1099
+ c == '\\U{3000}' || c == '\\U{FEFF}'
1100
+ }
1101
+
1102
+ function StringTrimLeft(s: string): string
1087
1103
  ensures |StringTrimLeft(s)| <= |s|
1088
- ensures StringTrimLeft(s) == "" || (|StringTrimLeft(s)| > 0 && StringTrimLeft(s)[0] != ' ')
1104
+ ensures StringTrimLeft(s) == "" || (|StringTrimLeft(s)| > 0 && !IsJSWhitespace(StringTrimLeft(s)[0]))
1089
1105
  decreases |s|
1090
1106
  {
1091
1107
  if |s| == 0 then ""
1092
- else if s[0] == ' ' then StringTrimLeft(s[1..])
1108
+ else if IsJSWhitespace(s[0]) then StringTrimLeft(s[1..])
1093
1109
  else s
1094
1110
  }
1095
1111
 
1096
1112
  function StringTrimRight(s: string): string
1097
1113
  ensures |StringTrimRight(s)| <= |s|
1114
+ ensures StringTrimRight(s) == "" || (|StringTrimRight(s)| > 0 && !IsJSWhitespace(StringTrimRight(s)[|StringTrimRight(s)|-1]))
1098
1115
  decreases |s|
1099
1116
  {
1100
1117
  if |s| == 0 then ""
1101
- else if s[|s|-1] == ' ' then StringTrimRight(s[..|s|-1])
1118
+ else if IsJSWhitespace(s[|s|-1]) then StringTrimRight(s[..|s|-1])
1102
1119
  else s
1103
1120
  }
1104
1121
 
@@ -1,6 +1,8 @@
1
1
  /**
2
- * Lean emitter — IR → Lean text.
3
- * No logic, no type decisions — just serialization.
2
+ * Lean emitter — IR → Lean text. Beyond serialization it makes type-driven
3
+ * decisions: Bool-vs-Prop connectives, dropping Repr/DecidableEq for
4
+ * opaque-tainted types, discriminator/destructor lowering, method dispatch,
5
+ * and support-import selection.
4
6
  */
5
7
  import { anyExpr, usesNameInDecl, patternBinders } from "./ir.js";
6
8
  import { freshName } from "./names.js";
@@ -760,6 +762,20 @@ function emitPureExpr(e, indent) {
760
762
  }
761
763
  }
762
764
  // ── File emission ────────────────────────────────────────────
765
+ /** Reset per-module emitter state. Call once per module before the types file
766
+ * (not between types and def — the def file reads the types file's registries). */
767
+ export function resetLeanModule() {
768
+ _resultName = "res";
769
+ _unionCtors.clear();
770
+ _opaqueNames.clear();
771
+ _opaqueNames.add("Unknown");
772
+ _typeRefs.clear();
773
+ _taintedTypes.clear();
774
+ _needsJSString = false;
775
+ _needsUnknown = false;
776
+ _unknownEmitted = false;
777
+ _boolCtx = false;
778
+ }
763
779
  export function emitLeanFile(file) {
764
780
  _needsJSString = false;
765
781
  _needsUnknown = false;
package/tools/dist/lsc.js CHANGED
@@ -15,7 +15,7 @@ import { narrowModule } from "./narrow.js";
15
15
  import { autoHavocModule } from "./autohavoc.js";
16
16
  import { transformModuleLean, transformModuleDafny } from "./transform.js";
17
17
  import { peepholeModule } from "./peephole.js";
18
- import { emitLeanFile } from "./lean-emit.js";
18
+ import { emitLeanFile, resetLeanModule } from "./lean-emit.js";
19
19
  import { emitDafnyFile } from "./dafny-emit.js";
20
20
  import { dafnyGen, dafnyCheckDiff, dafnyVerify, dafnyRegen } from "./dafny-commands.js";
21
21
  import { leanGen, leanCheck } from "./lean-commands.js";
@@ -261,6 +261,7 @@ function runFile(cmd, filePath, backend, timeLimit, extraFlags) {
261
261
  if (typesFile)
262
262
  typesFile = peepholeModule(typesFile, "lean");
263
263
  defFile = peepholeModule(defFile, "lean");
264
+ resetLeanModule(); // clear per-module emitter state so batch mode doesn't leak into this module
264
265
  const typesPath = typesFile ? path.join(dir, `${leanBase}.types.lean`) : null;
265
266
  const typesText = typesFile ? emitLeanFile(typesFile) : null;
266
267
  const defPath = path.join(dir, `${leanBase}.def.lean`);