gofront 1.7.0 → 1.8.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.
Files changed (128) hide show
  1. package/README.md +13 -13
  2. package/package.json +8 -37
  3. package/src/backend/js/expressions.js +1341 -148
  4. package/src/backend/js/fmt-runtime.js +573 -0
  5. package/src/backend/js/index.js +375 -57
  6. package/src/backend/js/runtime.js +1460 -133
  7. package/src/backend/js/statements.js +900 -86
  8. package/src/backend/js/stdlib/bits.js +87 -14
  9. package/src/backend/js/stdlib/builder.js +41 -6
  10. package/src/backend/js/stdlib/bytes.js +253 -7
  11. package/src/backend/js/stdlib/errors.js +2 -1
  12. package/src/backend/js/stdlib/fmt.js +349 -10
  13. package/src/backend/js/stdlib/index.js +8 -3
  14. package/src/backend/js/stdlib/json.js +247 -52
  15. package/src/backend/js/stdlib/maps.js +0 -8
  16. package/src/backend/js/stdlib/math.js +17 -1
  17. package/src/backend/js/stdlib/simd.js +10 -0
  18. package/src/backend/js/stdlib/slices.js +36 -6
  19. package/src/backend/js/stdlib/strconv.js +10 -5
  20. package/src/backend/js/stdlib/strings.js +83 -4
  21. package/src/backend/js/stdlib/testing.js +7 -0
  22. package/src/backend/js/stdlib/time.js +79 -55
  23. package/src/backend/js/stdlib/unicode.js +1 -1
  24. package/src/backend/js/stdlib/utf8.js +1 -1
  25. package/src/backend/js/templ.js +38 -76
  26. package/src/backend/js/type-info.js +272 -0
  27. package/src/backend/shared/fmt-types.js +188 -0
  28. package/src/backend/wasm/boundary.js +436 -80
  29. package/src/backend/wasm/emit-builtins.js +303 -13
  30. package/src/backend/wasm/emit-exprs.js +1397 -203
  31. package/src/backend/wasm/emit-maps.js +61 -13
  32. package/src/backend/wasm/emit-stmts.js +1359 -164
  33. package/src/backend/wasm/emit.js +512 -77
  34. package/src/backend/wasm/encode.js +43 -13
  35. package/src/backend/wasm/globals.js +452 -0
  36. package/src/backend/wasm/index.js +194 -2489
  37. package/src/backend/wasm/method-expr.js +381 -0
  38. package/src/backend/wasm/module-emitter.js +2240 -0
  39. package/src/backend/wasm/monomorph.js +62 -4
  40. package/src/backend/wasm/prescan.js +397 -0
  41. package/src/backend/wasm/stdlib/bytes.js +1804 -127
  42. package/src/backend/wasm/stdlib/core.js +1574 -199
  43. package/src/backend/wasm/stdlib/index.js +0 -5
  44. package/src/backend/wasm/stdlib/io.js +1567 -150
  45. package/src/backend/wasm/stdlib/packages.js +2 -0
  46. package/src/backend/wasm/stdlib/simd.js +293 -28
  47. package/src/backend/wasm/stdlib/time.js +1041 -12
  48. package/src/backend/wasm/types.js +200 -15
  49. package/src/backend/wasm/variadic.js +93 -0
  50. package/src/backend/wasm/wat.js +47 -20
  51. package/src/{cli-core.js → cli/cli-core.js} +236 -10
  52. package/src/{test-runner.js → cli/test-runner.js} +332 -39
  53. package/src/{vendor.js → cli/vendor.js} +3 -4
  54. package/src/compiler.js +107 -269
  55. package/src/index.js +15 -169
  56. package/src/{lexer.js → lexer/lexer.js} +102 -55
  57. package/src/{tokens.js → lexer/tokens.js} +1 -0
  58. package/src/link/sourcemap.js +97 -0
  59. package/src/link/stdlib.js +515 -0
  60. package/src/link/wasm.js +294 -0
  61. package/src/lower/bounds.js +394 -0
  62. package/src/lower/boxing.js +1 -1
  63. package/src/lower/captures.js +176 -32
  64. package/src/lower/index.js +26 -18
  65. package/src/lower/json-codecs.js +1398 -0
  66. package/src/lower/ownership.js +22 -1
  67. package/src/lower/range.js +86 -1
  68. package/src/lower/reachability.js +132 -0
  69. package/src/parser/declarations.js +58 -13
  70. package/src/parser/expressions.js +29 -7
  71. package/src/parser/index.js +2 -1
  72. package/src/parser/statements.js +37 -22
  73. package/src/{templ-parser.js → parser/templ.js} +138 -15
  74. package/src/parser/types.js +69 -9
  75. package/src/resolver.js +22 -1
  76. package/src/typechecker/assignability.js +176 -17
  77. package/src/typechecker/const-fold.js +356 -0
  78. package/src/typechecker/constants.js +256 -0
  79. package/src/typechecker/expressions.js +931 -122
  80. package/src/typechecker/index.js +459 -67
  81. package/src/typechecker/init-order.js +330 -0
  82. package/src/typechecker/resolve.js +451 -56
  83. package/src/typechecker/statements.js +90 -9
  84. package/src/typechecker/stdlib/core.js +333 -55
  85. package/src/typechecker/stdlib/excluded.js +898 -0
  86. package/src/typechecker/stdlib/extended.js +322 -26
  87. package/src/typechecker/{stdlib.js → stdlib/index.js} +5 -5
  88. package/src/typechecker/stdlib/simd.js +65 -15
  89. package/src/typechecker/termination.js +1 -1
  90. package/src/typechecker/types.js +149 -25
  91. package/stdlib/bytes/iter.go +192 -0
  92. package/stdlib/bytes/search.go +240 -0
  93. package/stdlib/bytes/split.go +218 -0
  94. package/stdlib/cmp/cmp.go +65 -0
  95. package/stdlib/encoding/json/errors.go +31 -0
  96. package/stdlib/encoding/json/indent.go +71 -0
  97. package/stdlib/encoding/json/number.go +12 -0
  98. package/stdlib/encoding/json/stream.go +173 -0
  99. package/stdlib/errors/overlay.go +12 -0
  100. package/stdlib/hash/hash.go +53 -0
  101. package/stdlib/internal/jsonrt/dec.go +738 -0
  102. package/stdlib/internal/jsonrt/enc.go +366 -0
  103. package/stdlib/internal/jsonrt/indent.go +135 -0
  104. package/stdlib/internal/jsonrt/number.go +27 -0
  105. package/stdlib/internal/jsonrt/reader.go +73 -0
  106. package/stdlib/internal/jsonrt/scanner.go +649 -0
  107. package/stdlib/io/io.go +346 -0
  108. package/stdlib/iter/iter.go +18 -0
  109. package/stdlib/maps/iter.go +65 -0
  110. package/stdlib/math/extra.go +270 -0
  111. package/stdlib/slices/extra.go +73 -0
  112. package/stdlib/slices/iter.go +113 -0
  113. package/stdlib/sort/sort.go +561 -0
  114. package/stdlib/strconv/quote.go +341 -0
  115. package/stdlib/strings/extra.go +35 -0
  116. package/stdlib/strings/iter.go +122 -0
  117. package/stdlib/sync/extra.go +105 -0
  118. package/stdlib/unicode/unicode.go +112 -0
  119. package/stdlib/unicode/utf8/bytes.go +239 -0
  120. package/src/backend/wasm/const-fold.js +0 -205
  121. package/src/backend/wasm/emit-stdlib.js +0 -13
  122. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  123. /package/src/{colors.js → cli/colors.js} +0 -0
  124. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  125. /package/src/{minifier.js → cli/minifier.js} +0 -0
  126. /package/src/{project-config.js → cli/project-config.js} +0 -0
  127. /package/src/{pwa.js → cli/pwa.js} +0 -0
  128. /package/src/{templ-lexer.js → lexer/templ-lexer.js} +0 -0
@@ -1,6 +1,7 @@
1
1
  // Assignability, binary result types, and interface satisfaction.
2
2
  // Installed as a mixin on TypeChecker.prototype.
3
3
 
4
+ import { constIntValue } from "./const-fold.js";
4
5
  import { sharedInfo } from "./stdlib/shared.js";
5
6
  import {
6
7
  ANY,
@@ -10,7 +11,9 @@ import {
10
11
  FLOAT64,
11
12
  isAny,
12
13
  isComplex,
14
+ isComplex64,
13
15
  isComplexOrNumeric,
16
+ isInteger,
14
17
  isNil,
15
18
  isNumeric,
16
19
  isPointer,
@@ -79,6 +82,8 @@ export const assignabilityMethods = {
79
82
  if (isAny(lt) || isAny(rt)) return ANY;
80
83
  if (isComplex(lt) || isComplex(rt))
81
84
  return this._binaryResultTypeComplex(op, lt, rt, node);
85
+ if ((op === "<<" || op === ">>") && isNumeric(lt) && isNumeric(rt))
86
+ return this._binaryResultTypeShift(op, lt, rt, node);
82
87
  if (isNumeric(lt) && isNumeric(rt))
83
88
  return this._binaryResultTypeNumeric(lt, rt);
84
89
  if (isString(lt) && isString(rt) && op === "+")
@@ -88,6 +93,37 @@ export const assignabilityMethods = {
88
93
  return ANY;
89
94
  },
90
95
 
96
+ // A shift has the type of its left operand; the count may be any integer
97
+ // type or an untyped constant representable as uint.
98
+ _binaryResultTypeShift(_op, lt, rt, node) {
99
+ if (node) this.checkShiftCount(rt, node.right);
100
+ if (node && lt.kind !== "untyped" && !isInteger(lt))
101
+ this.err(`shifted operand ${typeStr(lt)} must be integer`, node.left);
102
+ return lt;
103
+ },
104
+
105
+ // Reports a shift count that is not an integer or is a negative constant.
106
+ checkShiftCount(rt, countExpr) {
107
+ const v = this._constValue(countExpr);
108
+ if (v) {
109
+ const n = constIntValue(v);
110
+ if (n === null) {
111
+ this.err(
112
+ `invalid shift count ${typeStr(rt)} constant: not an integer`,
113
+ countExpr,
114
+ );
115
+ } else if (n < 0n) {
116
+ this.err(`invalid shift count: ${n} is negative`, countExpr);
117
+ }
118
+ return;
119
+ }
120
+ if (!isInteger(rt))
121
+ this.err(
122
+ `invalid shift count: ${typeStr(rt)} is not an integer type`,
123
+ countExpr,
124
+ );
125
+ },
126
+
91
127
  _binaryResultTypeString(lt, rt) {
92
128
  if (lt.kind === "untyped" && rt.kind === "untyped") return lt;
93
129
  if (lt.kind === "untyped") return rt;
@@ -111,6 +147,9 @@ export const assignabilityMethods = {
111
147
  return ANY;
112
148
  }
113
149
  if (lt.kind === "untyped" && rt.kind === "untyped") return UNTYPED_COMPLEX;
150
+ if (lt.kind === "untyped") return rt;
151
+ if (rt.kind === "untyped") return lt;
152
+ if (isComplex64(lt) && isComplex64(rt)) return lt;
114
153
  return COMPLEX128;
115
154
  },
116
155
 
@@ -165,8 +204,9 @@ export const assignabilityMethods = {
165
204
 
166
205
  // ── Assignability ─────────────────────────────────────────────────
167
206
 
168
- assertAssignable(target, source, node) {
207
+ assertAssignable(target, source, node, ctx = "assignment") {
169
208
  if (!target || !source) return;
209
+ if (this._checkConstOverflow(target, source, node, ctx)) return;
170
210
  target = this.resolveType(target);
171
211
  source = this.resolveType(source);
172
212
  this._markIfaceBox(target, source, node);
@@ -181,7 +221,14 @@ export const assignabilityMethods = {
181
221
  return;
182
222
  }
183
223
  if (isUntyped(source) && this._isUntypedAssignable(target, source)) return;
184
- if (this._isNumericCoercible(target, source)) return;
224
+ if (this._isNumericCoercible(target, source)) {
225
+ if (!this._isGenericCallResult(node)) return;
226
+ this.err(
227
+ `mismatched types ${typeStr(source)} and ${typeStr(target)} in ${ctx}`,
228
+ node,
229
+ );
230
+ return;
231
+ }
185
232
  if (this._checkArrayAssignable(target, source, node)) return;
186
233
  if (this._checkTypedArrayAssignable(target, source, node)) return;
187
234
  if (this._checkFuncAssignable(target, source, node)) return;
@@ -201,23 +248,32 @@ export const assignabilityMethods = {
201
248
  )
202
249
  return;
203
250
  if (target.kind === "typeParam") return;
204
- const tBase = this.resolveType(
205
- target.kind === "named" ? target.underlying : target,
206
- );
251
+ let tBase = this.resolveType(target);
252
+ if (tBase?.kind === "named") tBase = this.resolveType(tBase.underlying);
207
253
  if (!isAny(target) && tBase?.kind !== "interface") return;
254
+ // The static type behind the interface value: codegen derives the
255
+ // fmt metadata (Stringer elements of slices and maps) from it.
256
+ node._ifaceSrc = source;
257
+ const meta = this._ifaceBoxMeta(source);
258
+ if (!meta) return;
259
+ node._ifaceBox = meta.box;
260
+ if (meta.type) node._ifaceNamedType = meta.type;
261
+ if (meta.ptrFields) node._ifacePtrFields = meta.ptrFields;
262
+ },
263
+
264
+ // How a value of static type `source` is boxed on entering an interface:
265
+ // { box, type? (named kinds), ptrFields? }, or null when it stays raw.
266
+ // Shared by _markIfaceBox and the JS backend's multi-value fmt operands.
267
+ _ifaceBoxMeta(source) {
208
268
  const named = this._namedMethodBoxSource(source);
209
- if (named) {
210
- node._ifaceBox = named.ptr ? "named_ptr" : "named";
211
- node._ifaceNamedType = named.type;
212
- return;
213
- }
269
+ if (named)
270
+ return { box: named.ptr ? "named_ptr" : "named", type: named.type };
214
271
  const sBase = this.resolveType(
215
272
  source.kind === "named" ? source.underlying : source,
216
273
  );
217
274
  const info = this._classifyIfaceBox(sBase);
218
- if (!info) return;
219
- node._ifaceBox = info.box;
220
- if (info.ptrFields) node._ifacePtrFields = info.ptrFields;
275
+ if (!info) return null;
276
+ return { box: info.box, ptrFields: info.ptrFields };
221
277
  },
222
278
 
223
279
  // A named non-struct type with methods (or a pointer to one) entering an
@@ -225,11 +281,23 @@ export const assignabilityMethods = {
225
281
  // dynamic method dispatch works.
226
282
  _namedMethodBoxSource(source) {
227
283
  const isPtr = source?.kind === "pointer";
228
- const t = isPtr ? source.base : source;
284
+ let t = isPtr ? source.base : source;
285
+ if (t?.kind === "TypeName" || t?.kind === "Ident") t = this.resolveType(t);
229
286
  if (t?.kind !== "named") return null;
230
287
  const u = this.resolveType(t.underlying);
231
288
  if (!u || u.kind === "struct" || u.kind === "interface") return null;
232
289
  const methods = t.methods ?? t._generic?.methods;
290
+ // A user-declared named basic type without methods is boxed as well, so
291
+ // a type assertion to its underlying type fails like Go.
292
+ if (
293
+ u.kind === "basic" &&
294
+ !t._generic &&
295
+ t.methods instanceof Map &&
296
+ t.methods.size === 0 &&
297
+ t._target !== undefined &&
298
+ t._target !== "wasm"
299
+ )
300
+ return { type: t, ptr: isPtr };
233
301
  if (!methods || methods.size === 0) return null;
234
302
  // Only user-declared types (methods carry _ptrRecv); stdlib named types
235
303
  // such as time.Duration have no box class.
@@ -351,7 +419,45 @@ export const assignabilityMethods = {
351
419
  return;
352
420
  }
353
421
  if (this._identicalUnderlyingLiteral(target, source, tBase, sBase)) return;
354
- this.err(`Cannot assign ${typeStr(source)} to ${typeStr(target)}`, node);
422
+ this.err(
423
+ `Cannot assign ${typeStr(source)} to ${typeStr(target)}`,
424
+ node,
425
+ this._seqToSliceHint(source, tBase, node),
426
+ );
427
+ },
428
+
429
+ // maps.Keys/Values (and every iter.Seq) no longer return a slice: point the
430
+ // slice-expecting caller at slices.Collect.
431
+ _seqToSliceHint(source, tBase, node) {
432
+ if (tBase?.kind !== "slice" || !this._isIterSeq(source)) return null;
433
+ return this._seqCollectHint(node);
434
+ },
435
+
436
+ _isIterSeq(t) {
437
+ return t?.kind === "named" && /^iter\.Seq\d*\[/.test(typeStr(t));
438
+ },
439
+
440
+ _seqCollectHint(node) {
441
+ const f = node?.kind === "CallExpr" ? node.func : null;
442
+ if (
443
+ f?.kind === "SelectorExpr" &&
444
+ f.expr?.kind === "Ident" &&
445
+ f.expr.name === "maps" &&
446
+ (f.field === "Keys" || f.field === "Values")
447
+ )
448
+ return `use slices.Collect(maps.${f.field}(m)) for a slice`;
449
+ return "use slices.Collect(seq) for a slice";
450
+ },
451
+
452
+ // len/cap, spread and slice conversion are lenient on func types, and an
453
+ // iter.Seq value is one: reject it there instead of miscompiling.
454
+ _rejectSeqAsSlice(type, node, use) {
455
+ if (!this._isIterSeq(type)) return;
456
+ this.err(
457
+ `cannot use ${typeStr(type)} in ${use}`,
458
+ node,
459
+ this._seqCollectHint(node),
460
+ );
355
461
  },
356
462
 
357
463
  // Identical underlying types are assignable when one side is an unnamed
@@ -377,6 +483,20 @@ export const assignabilityMethods = {
377
483
  );
378
484
  },
379
485
 
486
+ // A value computed from a generic call (`3 + Sum[float64](1, 2)`) has the
487
+ // exact instantiated type, so the loose int/float64 promotion that plain
488
+ // code enjoys does not apply to it.
489
+ _isGenericCallResult(node) {
490
+ if (!node || typeof node !== "object") return false;
491
+ if (node.kind === "ParenExpr") return this._isGenericCallResult(node.expr);
492
+ if (node.kind === "BinaryExpr")
493
+ return (
494
+ this._isGenericCallResult(node.left) ||
495
+ this._isGenericCallResult(node.right)
496
+ );
497
+ return node.kind === "CallExpr" && Boolean(node.func?._genericFnType);
498
+ },
499
+
380
500
  _isNumericCoercible(target, source) {
381
501
  if (target.kind !== "basic" || source.kind !== "basic") return false;
382
502
  const pair = `${target.name}:${source.name}`;
@@ -465,10 +585,47 @@ export const assignabilityMethods = {
465
585
 
466
586
  // ── Interface satisfaction ────────────────────────────────────────
467
587
 
588
+ // `interface{}` and `any` are the same type, but print differently.
589
+ _sigTypeStr(t) {
590
+ switch (t?.kind) {
591
+ case "interface":
592
+ if (
593
+ !t.name &&
594
+ t.methods.size === 0 &&
595
+ !t.unionConstraint &&
596
+ !t._typeSetEmbeds
597
+ )
598
+ return "any";
599
+ return typeStr(t);
600
+ case "slice":
601
+ return `[]${this._sigTypeStr(t.elem)}`;
602
+ case "array":
603
+ return `[${t.size ?? "..."}]${this._sigTypeStr(t.elem)}`;
604
+ case "pointer":
605
+ return `*${this._sigTypeStr(t.base)}`;
606
+ case "map":
607
+ return `map[${this._sigTypeStr(t.key)}]${this._sigTypeStr(t.value)}`;
608
+ case "tuple":
609
+ return `(${t.types.map((x) => this._sigTypeStr(x)).join(", ")})`;
610
+ case "func": {
611
+ const ret =
612
+ t.returns.length > 1
613
+ ? `(${t.returns.map((x) => this._sigTypeStr(x)).join(", ")})`
614
+ : t.returns[0]
615
+ ? this._sigTypeStr(t.returns[0])
616
+ : "void";
617
+ return `func(${t.params.map((x) => this._sigTypeStr(x)).join(", ")}) ${ret}`;
618
+ }
619
+ default:
620
+ return typeStr(t);
621
+ }
622
+ },
623
+
468
624
  _sigParamsMatch(reqList, actList) {
469
625
  if (reqList.length !== actList.length) return false;
470
626
  for (let i = 0; i < reqList.length; i++) {
471
- if (typeStr(reqList[i]) !== typeStr(actList[i])) return false;
627
+ if (this._sigTypeStr(reqList[i]) !== this._sigTypeStr(actList[i]))
628
+ return false;
472
629
  }
473
630
  return true;
474
631
  },
@@ -511,7 +668,9 @@ export const assignabilityMethods = {
511
668
  }
512
669
  const methodMap =
513
670
  base?.kind === "struct"
514
- ? base.methods || srcType.methods
671
+ ? base.methods?.size
672
+ ? base.methods
673
+ : srcType.methods || base.methods
515
674
  : srcType.kind === "named"
516
675
  ? srcType.methods
517
676
  : null;
@@ -0,0 +1,356 @@
1
+ // Backend-independent evaluation of Go constant expressions.
2
+ //
3
+ // A constant value is `{ k, v, t, i }`:
4
+ // k — "INT" | "FLOAT" | "STRING" | "BOOL"
5
+ // v — BigInt (INT), Number (FLOAT), string or boolean
6
+ // t — basic type name of a typed constant ("int8", "float64", …); absent
7
+ // for untyped constants, which are exact (BigInt) and may exceed every
8
+ // Go type until they are converted or assigned
9
+ // i — for an untyped integral FLOAT, its exact BigInt value, so that
10
+ // `1e18 + 1` stays exact when it later becomes an integer
11
+ //
12
+ // The checker evaluates constant expressions with these helpers and attaches
13
+ // the result to the AST node (`_const`); both backends emit that value.
14
+ // `foldConstExpr` folds an AST directly (the WASM backend uses it for
15
+ // package-level constants).
16
+
17
+ const INT = "INT";
18
+ const FLOAT = "FLOAT";
19
+ const STRING = "STRING";
20
+ const BOOL = "BOOL";
21
+
22
+ // Shift counts beyond this are left to the runtime (Go allows larger shifts
23
+ // of untyped constants, but no program needs a 4096-bit intermediate).
24
+ const MAX_SHIFT = 4096n;
25
+
26
+ // Value ranges of the integer types.
27
+ export const INT_RANGES = {
28
+ int8: [-(2n ** 7n), 2n ** 7n - 1n],
29
+ int16: [-(2n ** 15n), 2n ** 15n - 1n],
30
+ int32: [-(2n ** 31n), 2n ** 31n - 1n],
31
+ rune: [-(2n ** 31n), 2n ** 31n - 1n],
32
+ int64: [-(2n ** 63n), 2n ** 63n - 1n],
33
+ int: [-(2n ** 63n), 2n ** 63n - 1n],
34
+ uint8: [0n, 2n ** 8n - 1n],
35
+ byte: [0n, 2n ** 8n - 1n],
36
+ uint16: [0n, 2n ** 16n - 1n],
37
+ uint32: [0n, 2n ** 32n - 1n],
38
+ uint64: [0n, 2n ** 64n - 1n],
39
+ uint: [0n, 2n ** 64n - 1n],
40
+ uintptr: [0n, 2n ** 64n - 1n],
41
+ };
42
+
43
+ const FLOAT_TYPES = new Set(["float32", "float64"]);
44
+
45
+ // Untyped integer constants of package `math` (Go declares them untyped).
46
+ // `MaxInt`, `MinInt` and `MaxUint` are left out: the JS backend maps them to
47
+ // the safe-integer range on purpose.
48
+ export const MATH_INT_CONSTS = {
49
+ MaxInt8: 2n ** 7n - 1n,
50
+ MinInt8: -(2n ** 7n),
51
+ MaxInt16: 2n ** 15n - 1n,
52
+ MinInt16: -(2n ** 15n),
53
+ MaxInt32: 2n ** 31n - 1n,
54
+ MinInt32: -(2n ** 31n),
55
+ MaxInt64: 2n ** 63n - 1n,
56
+ MinInt64: -(2n ** 63n),
57
+ MaxUint8: 2n ** 8n - 1n,
58
+ MaxUint16: 2n ** 16n - 1n,
59
+ MaxUint32: 2n ** 32n - 1n,
60
+ MaxUint64: 2n ** 64n - 1n,
61
+ };
62
+
63
+ export const intConst = (v) => ({ k: INT, v });
64
+
65
+ // Exact integer value of a decimal float literal (`1e18`, `2.50e1`), or null.
66
+ function exactIntOfFloatText(text) {
67
+ const m = /^(\d*)(?:\.(\d*))?(?:[eE]([+-]?\d+))?$/.exec(text);
68
+ if (!m) return null;
69
+ const frac = m[2] ?? "";
70
+ const digits = `${m[1]}${frac}` || "0";
71
+ const exp = Number(m[3] ?? 0) - frac.length;
72
+ if (exp > 400) return null;
73
+ if (exp >= 0) return BigInt(digits) * 10n ** BigInt(exp);
74
+ const cut = digits.length + exp;
75
+ if (!/^0*$/.test(digits.slice(Math.max(cut, 0)))) return null;
76
+ return cut > 0 ? BigInt(digits.slice(0, cut)) : 0n;
77
+ }
78
+
79
+ export function constFromLit(lit) {
80
+ switch (lit.litKind) {
81
+ case INT: {
82
+ const s = String(lit.value).replaceAll("_", "");
83
+ const clean = /^0[0-7]+$/.test(s) ? `0o${s.slice(1)}` : s;
84
+ return { k: INT, v: BigInt(clean) };
85
+ }
86
+ case FLOAT: {
87
+ const s = String(lit.value).replaceAll("_", "");
88
+ const n = Number(s);
89
+ if (!Number.isFinite(n)) return null;
90
+ const i = exactIntOfFloatText(s);
91
+ return i === null ? { k: FLOAT, v: n } : { k: FLOAT, v: n, i };
92
+ }
93
+ case STRING:
94
+ return { k: STRING, v: String(lit.value ?? "") };
95
+ case BOOL:
96
+ return { k: BOOL, v: lit.value === "true" || lit.value === true };
97
+ default:
98
+ return null;
99
+ }
100
+ }
101
+
102
+ export function constToLit(val) {
103
+ switch (val.k) {
104
+ case INT:
105
+ return { kind: "BasicLit", litKind: INT, value: val.v.toString() };
106
+ case FLOAT:
107
+ return {
108
+ kind: "BasicLit",
109
+ litKind: FLOAT,
110
+ value: Object.is(val.v, -0) ? "-0" : String(val.v),
111
+ };
112
+ case STRING:
113
+ return { kind: "BasicLit", litKind: STRING, value: val.v };
114
+ default:
115
+ return {
116
+ kind: "BasicLit",
117
+ litKind: BOOL,
118
+ value: val.v ? "true" : "false",
119
+ };
120
+ }
121
+ }
122
+
123
+ // Exact integer value of an INT or integral FLOAT, else null.
124
+ export function constIntValue(val) {
125
+ if (val?.k === INT) return val.v;
126
+ if (val?.k !== FLOAT) return null;
127
+ if (val.i !== undefined) return val.i;
128
+ return Number.isInteger(val.v) ? BigInt(val.v) : null;
129
+ }
130
+
131
+ function asFloat(val) {
132
+ return val.k === INT ? Number(val.v) : val.v;
133
+ }
134
+
135
+ // An integer result typed `t` that does not fit is an overflow.
136
+ function typedInt(v, t) {
137
+ const range = t ? INT_RANGES[t] : null;
138
+ if (range && (v < range[0] || v > range[1]))
139
+ return { k: INT, v, t, overflow: true };
140
+ return t ? { k: INT, v, t } : { k: INT, v };
141
+ }
142
+
143
+ function typedFloat(v, t) {
144
+ if (!Number.isFinite(v)) return null;
145
+ const r = t === "float32" ? Math.fround(v) : v;
146
+ return t ? { k: FLOAT, v: r, t } : { k: FLOAT, v: r };
147
+ }
148
+
149
+ export function foldUnary(op, x) {
150
+ if (op === "+" && (x.k === INT || x.k === FLOAT)) return x;
151
+ if (op === "!" && x.k === BOOL) return { ...x, v: !x.v };
152
+ if (op === "-" && x.k === INT) return typedInt(-x.v, x.t);
153
+ if (op === "-" && x.k === FLOAT) {
154
+ const r = typedFloat(-x.v, x.t);
155
+ return r && x.i !== undefined ? { ...r, i: -x.i } : r;
156
+ }
157
+ if (op === "^" && x.k === INT) {
158
+ // Unsigned: flip the bits of the type's width; otherwise -x-1.
159
+ const range = x.t ? INT_RANGES[x.t] : null;
160
+ if (range && range[0] === 0n) return { k: INT, v: range[1] ^ x.v, t: x.t };
161
+ return typedInt(~x.v, x.t);
162
+ }
163
+ return null;
164
+ }
165
+
166
+ function compare(op, a, b) {
167
+ switch (op) {
168
+ case "==":
169
+ return a === b;
170
+ case "!=":
171
+ return a !== b;
172
+ case "<":
173
+ return a < b;
174
+ case "<=":
175
+ return a <= b;
176
+ case ">":
177
+ return a > b;
178
+ case ">=":
179
+ return a >= b;
180
+ default:
181
+ return null;
182
+ }
183
+ }
184
+
185
+ function foldInts(op, a, b, t) {
186
+ switch (op) {
187
+ case "+":
188
+ return typedInt(a + b, t);
189
+ case "-":
190
+ return typedInt(a - b, t);
191
+ case "*":
192
+ return typedInt(a * b, t);
193
+ case "/":
194
+ if (b === 0n) throw new Error("invalid operation: division by zero");
195
+ return typedInt(a / b, t);
196
+ case "%":
197
+ if (b === 0n) throw new Error("invalid operation: division by zero");
198
+ return typedInt(a % b, t);
199
+ case "&":
200
+ return typedInt(a & b, t);
201
+ case "|":
202
+ return typedInt(a | b, t);
203
+ case "^":
204
+ return typedInt(a ^ b, t);
205
+ case "&^":
206
+ return typedInt(a & ~b, t);
207
+ default: {
208
+ const r = compare(op, a, b);
209
+ return r === null ? null : { k: BOOL, v: r };
210
+ }
211
+ }
212
+ }
213
+
214
+ function foldFloats(op, a, b, t) {
215
+ // Untyped integral operands stay exact for + - * (`1e18 + 1`).
216
+ const ai = t ? null : constIntValue(a);
217
+ const bi = t ? null : constIntValue(b);
218
+ if (ai !== null && bi !== null && (op === "+" || op === "-" || op === "*")) {
219
+ const i = op === "+" ? ai + bi : op === "-" ? ai - bi : ai * bi;
220
+ return { k: FLOAT, v: Number(i), i };
221
+ }
222
+ const x = asFloat(a);
223
+ const y = asFloat(b);
224
+ switch (op) {
225
+ case "+":
226
+ return typedFloat(x + y, t);
227
+ case "-":
228
+ return typedFloat(x - y, t);
229
+ case "*":
230
+ return typedFloat(x * y, t);
231
+ case "/":
232
+ if (y === 0) throw new Error("invalid operation: division by zero");
233
+ return typedFloat(x / y, t);
234
+ default: {
235
+ const r = compare(op, x, y);
236
+ return r === null ? null : { k: BOOL, v: r };
237
+ }
238
+ }
239
+ }
240
+
241
+ function foldShift(op, a, b) {
242
+ const n = constIntValue(b);
243
+ const x = constIntValue(a);
244
+ if (n === null || x === null) return null;
245
+ if (n < 0n) throw new Error("invalid shift count: negative shift count");
246
+ if (n > MAX_SHIFT) return null;
247
+ return typedInt(op === "<<" ? x << n : x >> n, a.t);
248
+ }
249
+
250
+ function foldBools(op, a, b) {
251
+ if (op === "&&") return { k: BOOL, v: a.v && b.v };
252
+ if (op === "||") return { k: BOOL, v: a.v || b.v };
253
+ const r = op === "==" || op === "!=" ? compare(op, a.v, b.v) : null;
254
+ return r === null ? null : { k: BOOL, v: r };
255
+ }
256
+
257
+ function foldStrings(op, a, b) {
258
+ if (op === "+") return { k: STRING, v: a.v + b.v, t: a.t ?? b.t };
259
+ const r = compare(op, a.v, b.v);
260
+ return r === null ? null : { k: BOOL, v: r };
261
+ }
262
+
263
+ export function foldBinary(op, a, b) {
264
+ if (op === "<<" || op === ">>") return foldShift(op, a, b);
265
+ if (a.k === BOOL && b.k === BOOL) return foldBools(op, a, b);
266
+ if (a.k === STRING && b.k === STRING) return foldStrings(op, a, b);
267
+ const t = a.t ?? b.t;
268
+ const numeric = (x) => x.k === INT || x.k === FLOAT;
269
+ if (!numeric(a) || !numeric(b)) return null;
270
+ if (FLOAT_TYPES.has(t) || (!t && (a.k === FLOAT || b.k === FLOAT)))
271
+ return foldFloats(op, a, b, t);
272
+ const ai = constIntValue(a);
273
+ const bi = constIntValue(b);
274
+ if (ai === null || bi === null) return foldFloats(op, a, b, t);
275
+ return foldInts(op, ai, bi, t);
276
+ }
277
+
278
+ // Converts `val` to the basic type `name`. Returns the typed value, a value
279
+ // with `overflow: true` when an integer does not fit, or null when the
280
+ // conversion is not a constant one this folder handles.
281
+ export function convertConst(val, name) {
282
+ if (INT_RANGES[name]) {
283
+ const i = constIntValue(val);
284
+ return i === null ? null : typedInt(i, name);
285
+ }
286
+ if (FLOAT_TYPES.has(name)) {
287
+ if (val.k !== INT && val.k !== FLOAT) return null;
288
+ return typedFloat(asFloat(val), name);
289
+ }
290
+ if (name === "string") {
291
+ if (val.k === STRING) return { k: STRING, v: val.v, t: name };
292
+ const i = constIntValue(val);
293
+ if (val.k !== INT || i === null) return null;
294
+ const valid = i >= 0n && i <= 0x10ffffn && !(i >= 0xd800n && i <= 0xdfffn);
295
+ return {
296
+ k: STRING,
297
+ v: valid ? String.fromCodePoint(Number(i)) : "�",
298
+ t: name,
299
+ };
300
+ }
301
+ if (name === "bool" && val.k === BOOL) return { k: BOOL, v: val.v, t: name };
302
+ return null;
303
+ }
304
+
305
+ // min/max over constants of one kind.
306
+ export function foldMinMax(isMin, vals) {
307
+ let best = vals[0];
308
+ for (const x of vals.slice(1)) {
309
+ const less = foldBinary("<", x, best);
310
+ if (!less) return null;
311
+ if (less.v === isMin) best = x;
312
+ }
313
+ return best;
314
+ }
315
+
316
+ function foldExpr(expr, resolve) {
317
+ switch (expr?.kind) {
318
+ case "BasicLit":
319
+ return constFromLit(expr);
320
+ case "Ident": {
321
+ if (expr.name === "true" || expr.name === "false")
322
+ return { k: BOOL, v: expr.name === "true" };
323
+ const lit = resolve(expr.name);
324
+ return lit ? constFromLit(lit) : null;
325
+ }
326
+ case "ParenExpr":
327
+ return foldExpr(expr.expr, resolve);
328
+ case "UnaryExpr": {
329
+ const x = foldExpr(expr.operand, resolve);
330
+ return x ? foldUnary(expr.op, x) : null;
331
+ }
332
+ case "BinaryExpr": {
333
+ const a = foldExpr(expr.left, resolve);
334
+ const b = a ? foldExpr(expr.right, resolve) : null;
335
+ return a && b ? foldBinary(expr.op, a, b) : null;
336
+ }
337
+ case "CallExpr": {
338
+ if (expr.func?.kind !== "Ident" || expr.func.name !== "len") return null;
339
+ if (expr.args?.length !== 1) return null;
340
+ const s = foldExpr(expr.args[0], resolve);
341
+ if (s?.k !== STRING) return null;
342
+ // Gofront strings across JS and WASM runtime use UTF-16 code units (s.length).
343
+ return { k: INT, v: BigInt(s.v.length) };
344
+ }
345
+ default:
346
+ return null;
347
+ }
348
+ }
349
+
350
+ // Folds `expr` into a BasicLit, or returns null when it needs the runtime.
351
+ // `resolve(name)` yields the BasicLit of an already-folded constant, if any.
352
+ // Throws on a constant division by zero or a negative shift count.
353
+ export function foldConstExpr(expr, resolve) {
354
+ const val = foldExpr(expr, resolve);
355
+ return val && !val.overflow ? constToLit(val) : null;
356
+ }