gofront 1.8.0 → 1.9.1

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 (178) hide show
  1. package/README.md +3 -3
  2. package/package.json +6 -5
  3. package/src/backend/js/expressions.js +1157 -408
  4. package/src/backend/js/index.js +327 -153
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +559 -2552
  7. package/src/backend/js/statements.js +670 -158
  8. package/src/backend/js/stdlib/index.js +45 -93
  9. package/src/backend/js/templ.js +32 -45
  10. package/src/backend/js/type-info.js +298 -19
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +91 -170
  13. package/src/backend/shared/host-unicode.js +36 -0
  14. package/src/backend/wasm/boundary.js +490 -965
  15. package/src/backend/wasm/emit-builtins.js +884 -256
  16. package/src/backend/wasm/emit-exprs.js +185 -77
  17. package/src/backend/wasm/emit-maps.js +17 -367
  18. package/src/backend/wasm/emit-shared.js +4 -1
  19. package/src/backend/wasm/emit-stmts.js +524 -240
  20. package/src/backend/wasm/emit.js +176 -38
  21. package/src/backend/wasm/globals.js +48 -8
  22. package/src/backend/wasm/glue.js +2 -4
  23. package/src/backend/wasm/index.js +44 -530
  24. package/src/backend/wasm/method-expr.js +12 -1
  25. package/src/backend/wasm/module-emitter.js +299 -770
  26. package/src/backend/wasm/monomorph.js +139 -8
  27. package/src/backend/wasm/natives.js +600 -0
  28. package/src/backend/wasm/prescan.js +5 -153
  29. package/src/backend/wasm/prune-funcs.js +89 -0
  30. package/src/backend/wasm/stdlib/index.js +2 -32
  31. package/src/backend/wasm/types.js +97 -20
  32. package/src/backend/wasm/variadic.js +9 -4
  33. package/src/cli/cli-core.js +5 -1
  34. package/src/cli/test-runner.js +115 -342
  35. package/src/compiler.js +113 -130
  36. package/src/lexer/lexer.js +106 -11
  37. package/src/lexer/templ-lexer.js +61 -18
  38. package/src/lexer/tokens.js +1 -0
  39. package/src/link/stdlib.js +355 -132
  40. package/src/link/type-names.js +204 -0
  41. package/src/link/wasm.js +30 -6
  42. package/src/lower/bounds.js +153 -0
  43. package/src/lower/captures.js +92 -33
  44. package/src/lower/embedding.js +62 -5
  45. package/src/lower/errors-as.js +196 -0
  46. package/src/lower/fmt-formatters.js +929 -0
  47. package/src/lower/functions.js +225 -7
  48. package/src/lower/gen-types.js +293 -0
  49. package/src/lower/index.js +14 -2
  50. package/src/lower/json-codecs.js +334 -245
  51. package/src/lower/ownership.js +25 -7
  52. package/src/lower/reachability.js +69 -51
  53. package/src/lower/walk.js +12 -0
  54. package/src/parser/declarations.js +30 -5
  55. package/src/parser/expressions.js +111 -57
  56. package/src/parser/statements.js +108 -0
  57. package/src/parser/templ.js +44 -5
  58. package/src/parser/types.js +12 -0
  59. package/src/resolver.js +1 -2
  60. package/src/typechecker/assignability.js +214 -19
  61. package/src/typechecker/const-fold.js +70 -27
  62. package/src/typechecker/constants.js +156 -8
  63. package/src/typechecker/expressions.js +490 -108
  64. package/src/typechecker/index.js +346 -42
  65. package/src/typechecker/init-order.js +136 -40
  66. package/src/typechecker/operand-text.js +41 -0
  67. package/src/typechecker/resolve.js +490 -65
  68. package/src/typechecker/statements.js +288 -27
  69. package/src/typechecker/stdlib/excluded.js +9 -5
  70. package/src/typechecker/stdlib/index.js +25 -9
  71. package/src/typechecker/termination.js +10 -1
  72. package/src/typechecker/types.js +287 -575
  73. package/stdlib/bytes/buffer.go +479 -0
  74. package/stdlib/bytes/bytes.go +1010 -0
  75. package/stdlib/bytes/reader.go +161 -0
  76. package/stdlib/context/context.go +115 -0
  77. package/stdlib/crypto/rand/rand.go +34 -0
  78. package/stdlib/crypto/sha256/sha256.go +249 -0
  79. package/stdlib/encoding/base64/base64.go +331 -0
  80. package/stdlib/encoding/binary/binary.go +332 -0
  81. package/stdlib/encoding/hex/hex.go +140 -0
  82. package/stdlib/encoding/json/json.go +62 -0
  83. package/stdlib/errors/errors.go +174 -0
  84. package/stdlib/fmt/errors.go +110 -0
  85. package/stdlib/fmt/format.go +732 -0
  86. package/stdlib/fmt/print.go +1232 -0
  87. package/stdlib/gom/gom.go +293 -0
  88. package/stdlib/html/html.go +251 -0
  89. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  90. package/stdlib/internal/jsonrt/dec.go +171 -38
  91. package/stdlib/internal/jsonrt/enc.go +186 -36
  92. package/stdlib/internal/jsonrt/scanner.go +17 -8
  93. package/stdlib/io/io.go +157 -1
  94. package/stdlib/maps/maps.go +131 -0
  95. package/stdlib/math/bits/bits.go +507 -0
  96. package/stdlib/math/extra.go +16 -14
  97. package/stdlib/math/math.go +439 -0
  98. package/stdlib/math/rand/rand.go +297 -0
  99. package/stdlib/os/os.go +93 -0
  100. package/stdlib/path/filepath/filepath.go +47 -0
  101. package/stdlib/path/path.go +394 -0
  102. package/stdlib/regexp/regexp.go +231 -0
  103. package/stdlib/slices/slices.go +526 -0
  104. package/stdlib/slices/sort.go +209 -0
  105. package/stdlib/slices/zsort_cmp.go +480 -0
  106. package/stdlib/slices/zsort_ordered.go +482 -0
  107. package/stdlib/sort/sort.go +391 -452
  108. package/stdlib/sort/zsort_func.go +480 -0
  109. package/stdlib/sort/zsort_iface.go +480 -0
  110. package/stdlib/strconv/float.go +1699 -0
  111. package/stdlib/strconv/isprint.go +752 -0
  112. package/stdlib/strconv/quote.go +235 -50
  113. package/stdlib/strconv/strconv.go +616 -0
  114. package/stdlib/strings/builder.go +95 -0
  115. package/stdlib/strings/reader.go +163 -0
  116. package/stdlib/strings/replacer.go +102 -0
  117. package/stdlib/strings/strings.go +807 -0
  118. package/stdlib/sync/sync.go +268 -0
  119. package/stdlib/testing/async_js.go +61 -0
  120. package/stdlib/testing/benchmark.go +297 -0
  121. package/stdlib/testing/testing.go +523 -0
  122. package/stdlib/time/duration.go +233 -0
  123. package/stdlib/time/format.go +745 -0
  124. package/stdlib/time/parse.go +790 -0
  125. package/stdlib/time/sleep_js.go +63 -0
  126. package/stdlib/time/time.go +834 -0
  127. package/stdlib/unicode/unicode.go +141 -3
  128. package/stdlib/unicode/utf8/utf8.go +464 -0
  129. package/src/backend/js/fmt-runtime.js +0 -573
  130. package/src/backend/js/stdlib/base64.js +0 -12
  131. package/src/backend/js/stdlib/binary.js +0 -102
  132. package/src/backend/js/stdlib/bits.js +0 -119
  133. package/src/backend/js/stdlib/builder.js +0 -98
  134. package/src/backend/js/stdlib/bytes.js +0 -368
  135. package/src/backend/js/stdlib/errors.js +0 -45
  136. package/src/backend/js/stdlib/fmt.js +0 -517
  137. package/src/backend/js/stdlib/gom.js +0 -155
  138. package/src/backend/js/stdlib/hex.js +0 -17
  139. package/src/backend/js/stdlib/html.js +0 -20
  140. package/src/backend/js/stdlib/io.js +0 -137
  141. package/src/backend/js/stdlib/json.js +0 -1167
  142. package/src/backend/js/stdlib/maps.js +0 -37
  143. package/src/backend/js/stdlib/math.js +0 -85
  144. package/src/backend/js/stdlib/os.js +0 -13
  145. package/src/backend/js/stdlib/path.js +0 -48
  146. package/src/backend/js/stdlib/rand.js +0 -32
  147. package/src/backend/js/stdlib/regexp.js +0 -54
  148. package/src/backend/js/stdlib/sha256.js +0 -12
  149. package/src/backend/js/stdlib/shared.js +0 -17
  150. package/src/backend/js/stdlib/simd.js +0 -34
  151. package/src/backend/js/stdlib/slices.js +0 -107
  152. package/src/backend/js/stdlib/sort.js +0 -30
  153. package/src/backend/js/stdlib/strconv.js +0 -79
  154. package/src/backend/js/stdlib/strings.js +0 -301
  155. package/src/backend/js/stdlib/sync.js +0 -13
  156. package/src/backend/js/stdlib/testing.js +0 -41
  157. package/src/backend/js/stdlib/time.js +0 -152
  158. package/src/backend/js/stdlib/unicode.js +0 -25
  159. package/src/backend/js/stdlib/utf8.js +0 -26
  160. package/src/backend/wasm/stdlib/bytes.js +0 -6716
  161. package/src/backend/wasm/stdlib/core.js +0 -10278
  162. package/src/backend/wasm/stdlib/io.js +0 -3578
  163. package/src/backend/wasm/stdlib/packages.js +0 -2402
  164. package/src/backend/wasm/stdlib/pure.js +0 -720
  165. package/src/backend/wasm/stdlib/time.js +0 -2229
  166. package/src/typechecker/stdlib/core.js +0 -1079
  167. package/src/typechecker/stdlib/extended.js +0 -1210
  168. package/stdlib/bytes/iter.go +0 -192
  169. package/stdlib/bytes/search.go +0 -240
  170. package/stdlib/bytes/split.go +0 -218
  171. package/stdlib/errors/overlay.go +0 -12
  172. package/stdlib/maps/iter.go +0 -65
  173. package/stdlib/slices/extra.go +0 -73
  174. package/stdlib/slices/iter.go +0 -113
  175. package/stdlib/strings/extra.go +0 -35
  176. package/stdlib/strings/iter.go +0 -122
  177. package/stdlib/sync/extra.go +0 -105
  178. package/stdlib/unicode/utf8/bytes.go +0 -239
@@ -31,26 +31,18 @@
31
31
  // module's exported linear memory; JS sees a zero-copy TypedArray over
32
32
  // `memory.buffer`. Only TypedArrays that already alias that memory (ones
33
33
  // handed out by this facade) may be passed back in.
34
- // - `*testing.T`: the harness's JS test object, passed as an opaque
35
- // externref (test methods are routed back to JS via `env.testing_*`).
36
34
 
37
35
  import { BROWSER_GLOBALS } from "../../typechecker/types.js";
36
+ import { FIXED_FLOAT_JS } from "../shared/fixed-float.js";
38
37
  import {
39
- HELPER_BASE64,
40
- HELPER_DURATION,
41
- HELPER_ERROR,
42
- HELPER_HEX,
43
- HELPER_PATH_CLEAN,
44
- HELPER_SHA256,
45
- HELPER_SPRINT,
46
- HELPER_SPRINTF,
47
- HELPER_STRCONV,
48
- } from "../js/runtime.js";
38
+ unicodeIsJs,
39
+ unicodeToLowerJs,
40
+ unicodeToUpperJs,
41
+ } from "../shared/host-unicode.js";
49
42
  import {
50
43
  anonIfaceName,
51
44
  getInterfaceMethodList,
52
45
  getSharedInfo,
53
- isTestingT,
54
46
  } from "./types.js";
55
47
 
56
48
  // ── Type classification ──────────────────────────────────────
@@ -87,8 +79,16 @@ function toRef(info) {
87
79
  return { kind: "ref", nullable: true, typeIndex: info.typeIndex };
88
80
  }
89
81
 
82
+ // The facade and its wasm export names build identifiers from a struct's name,
83
+ // so the dot of a stdlib type (`strings.Builder`) becomes `$`.
90
84
  function structDesc(info, ptr) {
91
- return { k: "struct", name: info.name, ptr, info, wType: toRef(info) };
85
+ return {
86
+ k: "struct",
87
+ name: info.name.replaceAll(".", "$"),
88
+ ptr,
89
+ info,
90
+ wType: toRef(info),
91
+ };
92
92
  }
93
93
 
94
94
  function funcSigFromAst(t) {
@@ -111,17 +111,9 @@ function funcSigFromAst(t) {
111
111
  // Returns a descriptor `{ k, ... }`; `k === "unsupported"` carries `what`.
112
112
  function classifyType(goType, mod) {
113
113
  if (!goType) return { k: "void" };
114
- if (isTestingT(goType)) return { k: "extern", wType: "externref" };
115
114
 
116
115
  if (goType.kind === "TypeName" || goType.kind === "Ident") {
117
116
  if (goType.name === "error") return { k: "error", wType: "anyref" };
118
- if (goType.name === "time.Duration" || goType.name === "Duration")
119
- return { k: "i64", unsigned: false, wType: "i64" };
120
- if (goType.name === "time.Time" || goType.name === "Time") {
121
- const info = mod.getStructType("time.Time");
122
- if (info)
123
- return { k: "time", name: "time.Time", info, wType: toRef(info) };
124
- }
125
117
  const info = mod.getStructType(goType.name);
126
118
  if (info) return structDesc(info, false);
127
119
  const resolved = mod.checker?.types?.get(goType.name);
@@ -213,7 +205,12 @@ function classifyBasic(n) {
213
205
  if (I32_INTS.has(n))
214
206
  return { k: "i32", unsigned: UNSIGNED.has(n), wType: "i32" };
215
207
  if (I64_INTS.has(n))
216
- return { k: "i64", unsigned: UNSIGNED.has(n), wType: "i64" };
208
+ return {
209
+ k: "i64",
210
+ unsigned: UNSIGNED.has(n),
211
+ is64: n === "int64" || n === "uint64",
212
+ wType: "i64",
213
+ };
217
214
  const d = BASIC_DESCS[n];
218
215
  return d ? { ...d } : { k: "unsupported", what: `type '${n}'` };
219
216
  }
@@ -229,12 +226,6 @@ function classifyNamed(t, mod) {
229
226
  return classifySimd(t.underlying ?? t, mod, t.name);
230
227
  }
231
228
  if (t.name === "error") return { k: "error", wType: "anyref" };
232
- if (t.name === "time.Duration")
233
- return { k: "i64", unsigned: false, wType: "i64" };
234
- if (t.name === "time.Time") {
235
- const info = mod.getStructType("time.Time");
236
- if (info) return { k: "time", name: "time.Time", info, wType: toRef(info) };
237
- }
238
229
  const info = mod.getStructType(t.name);
239
230
  if (info) return structDesc(info, false);
240
231
  if (t.underlying) return classifyType(t.underlying, mod);
@@ -294,10 +285,9 @@ function classifyPointer(t, mod) {
294
285
  if (base.k === "unsupported") return base;
295
286
  if (base.k === "struct" && !base.ptr) return { ...base, ptr: true };
296
287
  if (base.k === "anon_struct" && !base.ptr) return { ...base, ptr: true };
297
- if (base.k === "time" && !base.ptr) return { ...base, ptr: true };
298
288
  const boxInfo = mod.getBoxType(t.base);
299
289
  const ptrInfo = mod.getPtrType ? mod.getPtrType(t.base) : null;
300
- const key = `${base.k}${base.unsigned ? "_u" : ""}_${boxInfo.typeIndex}`;
290
+ const key = `${base.k}${base.unsigned ? "_u" : ""}${base.is64 ? "_64" : ""}_${boxInfo.typeIndex}`;
301
291
  return {
302
292
  k: "ptr_box",
303
293
  base,
@@ -379,7 +369,11 @@ function classifySlice(t, mod) {
379
369
  // so they get separate helper keys.
380
370
  key:
381
371
  wTypeKey(sliceInfo.elemWType) +
382
- (elem.k === "f32x4" || elem.k === "mask4" ? `_${elem.k}` : ""),
372
+ (elem.k === "f32x4" || elem.k === "mask4"
373
+ ? `_${elem.k}`
374
+ : elem.is64
375
+ ? "_i64"
376
+ : ""),
383
377
  wType: { kind: "ref", nullable: true, typeIndex: sliceInfo.typeIndex },
384
378
  };
385
379
  }
@@ -399,7 +393,7 @@ function classifyArray(t, mod) {
399
393
  elem,
400
394
  size: arraySize(t),
401
395
  arrInfo,
402
- key: wTypeKey(arrInfo.elemWType),
396
+ key: wTypeKey(arrInfo.elemWType) + (elem.is64 ? "_i64" : ""),
403
397
  wType: { kind: "ref", nullable: true, typeIndex: arrInfo.typeIndex },
404
398
  };
405
399
  }
@@ -574,6 +568,9 @@ export function collectBoundaryMeta(progs, funcDecls, mod) {
574
568
  for (const p of progs) {
575
569
  const pkgName = p.pkg?.name ?? "main";
576
570
  const pkgTarget = p.target ?? "wasm";
571
+ // The tests of a package run inside the module: only the generated
572
+ // driver crosses the boundary.
573
+ if (isTestSource(p._path ?? p._filename)) continue;
577
574
  for (const d of p.decls ?? []) {
578
575
  if (d.kind === "TypeDecl" && d.type?.kind === "StructType") {
579
576
  collectStructDecl(d, pkgName, pkgTarget, meta, mod);
@@ -587,6 +584,7 @@ export function collectBoundaryMeta(progs, funcDecls, mod) {
587
584
 
588
585
  for (const fn of funcDecls) {
589
586
  if (fn._isClosure || fn._pkgTarget !== "wasm") continue;
587
+ if (isTestSource(fn._srcFile) && fn.name !== TEST_DRIVER) continue;
590
588
  if (fn._isMethod) collectMethodMeta(fn, meta, errors, mod);
591
589
  else collectFuncMeta(fn, meta, errors, mod);
592
590
  }
@@ -595,12 +593,19 @@ export function collectBoundaryMeta(progs, funcDecls, mod) {
595
593
  return meta;
596
594
  }
597
595
 
596
+ // The driver function the test runner compiles in (src/cli/test-runner.js).
597
+ const TEST_DRIVER = "GofrontRunTests";
598
+
599
+ const isTestSource = (file) => Boolean(file?.endsWith("_test.go"));
600
+
598
601
  function collectStructDecl(d, pkgName, pkgTarget, meta, mod) {
599
- if (!isExported(d.name) || meta.structByName.has(d.name)) return;
602
+ if (meta.structByName.has(d.name)) return;
600
603
  const info = mod.getStructType(d.name);
601
604
  if (!info) return;
602
605
  const s = { name: d.name, pkgName, pkgTarget, info, methods: [] };
603
- meta.structs.push(s);
606
+ // An unexported struct has no class of its own, but its exported methods
607
+ // reach JS through the exported structs that embed it.
608
+ if (isExported(d.name)) meta.structs.push(s);
604
609
  meta.structByName.set(d.name, s);
605
610
  }
606
611
 
@@ -755,9 +760,6 @@ function visitNeed(desc, needs, errors, mod) {
755
760
  needs.simd = desc.arr;
756
761
  visitNeed(desc.arr, needs, errors, mod);
757
762
  return;
758
- case "time":
759
- needs.time = true;
760
- return;
761
763
  case "slice":
762
764
  case "array": {
763
765
  const map = desc.k === "slice" ? needs.slices : needs.arrays;
@@ -834,7 +836,6 @@ function collectNeeds(meta) {
834
836
  funcs: new Map(), // key -> desc
835
837
  ifaces: new Map(), // interface name -> desc
836
838
  error: false,
837
- time: false,
838
839
  simd: null, // array desc carrying vectors across the boundary
839
840
  shared: false, // any shared buffer on the surface
840
841
  };
@@ -862,11 +863,20 @@ export function scanBoundaryImports(mod, meta) {
862
863
  const needs = collectNeeds(meta);
863
864
  meta._needs = needs;
864
865
  for (const [key, desc] of needs.funcs) {
866
+ const paramDescs = desc.params ?? [];
867
+ const returnDescs = desc.returns ?? [];
865
868
  mod.getOrAddFuncImport(
866
869
  "env",
867
870
  `__invoke$${key}`,
868
- ["externref", ...desc.closureInfo.paramWTypes.map(extType)],
869
- desc.closureInfo.returnWTypes.map(extType),
871
+ [
872
+ "externref",
873
+ ...desc.closureInfo.paramWTypes.map((w, i) =>
874
+ extType(w, paramDescs[i]?.is64),
875
+ ),
876
+ ],
877
+ desc.closureInfo.returnWTypes.map((w, i) =>
878
+ extType(w, returnDescs[i]?.is64),
879
+ ),
870
880
  );
871
881
  }
872
882
  }
@@ -884,19 +894,21 @@ function isGcRef(wType) {
884
894
  // the converting helpers are registered first thing in addBoundaryHelpers.
885
895
  const v128Conv = { from: null, to: null };
886
896
 
887
- function extType(wType) {
897
+ function extType(wType, is64 = false) {
888
898
  if (wType === "v128") return "externref";
889
- if (wType === "i64") return "f64";
899
+ if (wType === "i64") return is64 ? "i64" : "f64";
890
900
  if (isGcRef(wType)) return "externref";
891
901
  return wType;
892
902
  }
893
- function needsConv(wTypes) {
894
- return wTypes.some((w) => w === "i64" || w === "v128" || isGcRef(w));
903
+ function needsConv(wTypes, descs = []) {
904
+ return wTypes.some(
905
+ (w, i) => (w === "i64" && !descs[i]?.is64) || w === "v128" || isGcRef(w),
906
+ );
895
907
  }
896
908
  // Instructions converting the value on the stack from its boundary form.
897
- function fromExt(wType) {
909
+ function fromExt(wType, is64 = false) {
898
910
  if (wType === "v128") return [{ op: "call", funcIndex: v128Conv.from }];
899
- if (wType === "i64") return [{ op: "i64.trunc_sat_f64_s" }];
911
+ if (wType === "i64") return is64 ? [] : [{ op: "i64.trunc_sat_f64_s" }];
900
912
  if (wType === "anyref") return [{ op: "any.convert_extern" }];
901
913
  if (isGcRef(wType)) {
902
914
  const out = [{ op: "any.convert_extern" }];
@@ -907,22 +919,25 @@ function fromExt(wType) {
907
919
  return [];
908
920
  }
909
921
  // Instructions converting the value on the stack to its boundary form.
910
- function toExt(wType) {
922
+ function toExt(wType, is64 = false) {
911
923
  if (wType === "v128") return [{ op: "call", funcIndex: v128Conv.to }];
912
- if (wType === "i64") return [{ op: "f64.convert_i64_s" }];
924
+ if (wType === "i64") return is64 ? [] : [{ op: "f64.convert_i64_s" }];
913
925
  if (isGcRef(wType)) return [{ op: "extern.convert_any" }];
914
926
  return [];
915
927
  }
916
928
  // Rewrites the values on top of the stack (types `wTypes`) with `conv`,
917
929
  // spilling through locals starting at `localBase` when more than one.
918
- function convertResults(wTypes, conv, localBase) {
919
- if (wTypes.length === 0 || !needsConv(wTypes)) return [];
920
- if (wTypes.length === 1) return conv(wTypes[0]);
930
+ function convertResults(wTypes, conv, localBase, descs = []) {
931
+ if (wTypes.length === 0 || !needsConv(wTypes, descs)) return [];
932
+ if (wTypes.length === 1) return conv(wTypes[0], descs[0]?.is64);
921
933
  const out = [];
922
934
  for (let i = wTypes.length - 1; i >= 0; i--)
923
935
  out.push({ op: "local.set", index: localBase + i });
924
936
  for (let i = 0; i < wTypes.length; i++)
925
- out.push({ op: "local.get", index: localBase + i }, ...conv(wTypes[i]));
937
+ out.push(
938
+ { op: "local.get", index: localBase + i },
939
+ ...conv(wTypes[i], descs[i]?.is64),
940
+ );
926
941
  return out;
927
942
  }
928
943
 
@@ -1022,19 +1037,19 @@ export function addBoundaryHelpers(mod, meta) {
1022
1037
  );
1023
1038
  }
1024
1039
  // Parameter `i` converted from its boundary form.
1025
- const getIn = (i, wType) => [get(i), ...fromExt(wType)];
1040
+ const getIn = (i, wType, is64 = false) => [get(i), ...fromExt(wType, is64)];
1026
1041
  const EXT = "externref";
1027
1042
 
1028
- for (const [name, { info }] of needs.structs) {
1043
+ for (const [name, { info, fields }] of needs.structs) {
1029
1044
  const ref = toRef(info);
1030
1045
  const ti = info.typeIndex;
1031
1046
  const fieldWTypes = info.fields.map((f) => f.wType);
1032
1047
  addFunc(
1033
1048
  `__new_${name}`,
1034
- fieldWTypes.map(extType),
1049
+ fieldWTypes.map((w, i) => extType(w, fields[i]?.desc?.is64)),
1035
1050
  [EXT],
1036
1051
  [
1037
- ...fieldWTypes.flatMap((w, i) => getIn(i, w)),
1052
+ ...fieldWTypes.flatMap((w, i) => getIn(i, w, fields[i]?.desc?.is64)),
1038
1053
  { op: "struct.new", typeIndex: ti },
1039
1054
  ...toExt(ref),
1040
1055
  ],
@@ -1064,23 +1079,24 @@ export function addBoundaryHelpers(mod, meta) {
1064
1079
  );
1065
1080
  for (let i = 0; i < info.fields.length; i++) {
1066
1081
  const f = info.fields[i];
1082
+ const is64 = Boolean(fields[i]?.desc?.is64);
1067
1083
  addFunc(
1068
1084
  `__get_${name}_${f.name}`,
1069
1085
  [EXT],
1070
- [extType(f.wType)],
1086
+ [extType(f.wType, is64)],
1071
1087
  [
1072
1088
  ...getIn(0, ref),
1073
1089
  { op: "struct.get", typeIndex: ti, fieldIndex: i },
1074
- ...toExt(f.wType),
1090
+ ...toExt(f.wType, is64),
1075
1091
  ],
1076
1092
  );
1077
1093
  addFunc(
1078
1094
  `__set_${name}_${f.name}`,
1079
- [EXT, extType(f.wType)],
1095
+ [EXT, extType(f.wType, is64)],
1080
1096
  [],
1081
1097
  [
1082
1098
  ...getIn(0, ref),
1083
- ...getIn(1, f.wType),
1099
+ ...getIn(1, f.wType, is64),
1084
1100
  { op: "struct.set", typeIndex: ti, fieldIndex: i },
1085
1101
  ],
1086
1102
  );
@@ -1106,25 +1122,26 @@ export function addBoundaryHelpers(mod, meta) {
1106
1122
  ["i32"],
1107
1123
  [...getIn(0, ref), { op: "array.len" }],
1108
1124
  );
1125
+ const is64 = Boolean(desc.elem?.is64);
1109
1126
  addFunc(
1110
1127
  `__array_get_${key}`,
1111
1128
  [EXT, "i32"],
1112
- [extType(arrInfo.elemWType)],
1129
+ [extType(arrInfo.elemWType, is64)],
1113
1130
  [
1114
1131
  ...getIn(0, ref),
1115
1132
  get(1),
1116
1133
  { op: "array.get", typeIndex: arrInfo.typeIndex },
1117
- ...toExt(arrInfo.elemWType),
1134
+ ...toExt(arrInfo.elemWType, is64),
1118
1135
  ],
1119
1136
  );
1120
1137
  addFunc(
1121
1138
  `__array_set_${key}`,
1122
- [EXT, "i32", extType(arrInfo.elemWType)],
1139
+ [EXT, "i32", extType(arrInfo.elemWType, is64)],
1123
1140
  [],
1124
1141
  [
1125
1142
  ...getIn(0, ref),
1126
1143
  get(1),
1127
- ...getIn(2, arrInfo.elemWType),
1144
+ ...getIn(2, arrInfo.elemWType, is64),
1128
1145
  { op: "array.set", typeIndex: arrInfo.typeIndex },
1129
1146
  ],
1130
1147
  );
@@ -1135,6 +1152,7 @@ export function addBoundaryHelpers(mod, meta) {
1135
1152
  const ref = desc.wType;
1136
1153
  const arrTI = sliceInfo.arrInfo.typeIndex;
1137
1154
  const sTI = sliceInfo.typeIndex;
1155
+ const is64 = Boolean(desc.elem?.is64);
1138
1156
  const elemAddr = [
1139
1157
  ...getIn(0, ref),
1140
1158
  { op: "struct.get", typeIndex: sTI, fieldIndex: 0 },
@@ -1166,11 +1184,11 @@ export function addBoundaryHelpers(mod, meta) {
1166
1184
  addFunc(
1167
1185
  `__slice_get_${key}`,
1168
1186
  [EXT, "i32"],
1169
- [extType(sliceInfo.elemWType)],
1187
+ [extType(sliceInfo.elemWType, is64)],
1170
1188
  [
1171
1189
  ...elemAddr,
1172
1190
  { op: "array.get", typeIndex: arrTI },
1173
- ...toExt(sliceInfo.elemWType),
1191
+ ...toExt(sliceInfo.elemWType, is64),
1174
1192
  ],
1175
1193
  );
1176
1194
  addFunc(
@@ -1191,11 +1209,11 @@ export function addBoundaryHelpers(mod, meta) {
1191
1209
  );
1192
1210
  addFunc(
1193
1211
  `__slice_set_${key}`,
1194
- [EXT, "i32", extType(sliceInfo.elemWType)],
1212
+ [EXT, "i32", extType(sliceInfo.elemWType, is64)],
1195
1213
  [],
1196
1214
  [
1197
1215
  ...elemAddr,
1198
- ...getIn(2, sliceInfo.elemWType),
1216
+ ...getIn(2, sliceInfo.elemWType, is64),
1199
1217
  { op: "array.set", typeIndex: arrTI },
1200
1218
  ],
1201
1219
  );
@@ -1205,6 +1223,8 @@ export function addBoundaryHelpers(mod, meta) {
1205
1223
  const ci = desc.closureInfo;
1206
1224
  const invokeIdx = mod.importCache.get(`env.__invoke$${key}`);
1207
1225
  const nParams = ci.paramWTypes.length;
1226
+ const paramDescs = desc.params ?? [];
1227
+ const returnDescs = desc.returns ?? [];
1208
1228
  // Thunk: wasm closure body calling the JS callback (boundary forms out, back in).
1209
1229
  const thunkIdx = addFunc(
1210
1230
  `__jsthunk$${key}`,
@@ -1213,12 +1233,17 @@ export function addBoundaryHelpers(mod, meta) {
1213
1233
  [
1214
1234
  get(0),
1215
1235
  { op: "extern.convert_any" },
1216
- ...ci.paramWTypes.flatMap((w, i) => [get(i + 1), ...toExt(w)]),
1236
+ ...ci.paramWTypes.flatMap((w, i) => [
1237
+ get(i + 1),
1238
+ ...toExt(w, paramDescs[i]?.is64),
1239
+ ]),
1217
1240
  { op: "call", funcIndex: invokeIdx },
1218
- ...convertResults(ci.returnWTypes, fromExt, nParams + 1),
1241
+ ...convertResults(ci.returnWTypes, fromExt, nParams + 1, returnDescs),
1219
1242
  ],
1220
1243
  false,
1221
- ci.returnWTypes.length > 1 ? ci.returnWTypes.map(extType) : [],
1244
+ ci.returnWTypes.length > 1
1245
+ ? ci.returnWTypes.map((w, i) => extType(w, returnDescs[i]?.is64))
1246
+ : [],
1222
1247
  );
1223
1248
  mod.addRefFuncElement(thunkIdx);
1224
1249
  addFunc(
@@ -1236,16 +1261,18 @@ export function addBoundaryHelpers(mod, meta) {
1236
1261
  // JS calling a wasm closure (boundary forms in, results out).
1237
1262
  addFunc(
1238
1263
  `__call_fn$${key}`,
1239
- [EXT, ...ci.paramWTypes.map(extType)],
1240
- ci.returnWTypes.map(extType),
1264
+ [EXT, ...ci.paramWTypes.map((w, i) => extType(w, paramDescs[i]?.is64))],
1265
+ ci.returnWTypes.map((w, i) => extType(w, returnDescs[i]?.is64)),
1241
1266
  [
1242
1267
  ...getIn(0, desc.wType),
1243
1268
  { op: "struct.get", typeIndex: ci.typeIndex, fieldIndex: 1 },
1244
- ...ci.paramWTypes.flatMap((w, i) => getIn(i + 1, w)),
1269
+ ...ci.paramWTypes.flatMap((w, i) =>
1270
+ getIn(i + 1, w, paramDescs[i]?.is64),
1271
+ ),
1245
1272
  ...getIn(0, desc.wType),
1246
1273
  { op: "struct.get", typeIndex: ci.typeIndex, fieldIndex: 0 },
1247
1274
  { op: "call_ref", typeIndex: ci.funcTypeIndex },
1248
- ...convertResults(ci.returnWTypes, toExt, nParams + 1),
1275
+ ...convertResults(ci.returnWTypes, toExt, nParams + 1, returnDescs),
1249
1276
  ],
1250
1277
  true,
1251
1278
  ci.returnWTypes.length > 1 ? ci.returnWTypes : [],
@@ -1263,17 +1290,20 @@ export function addBoundaryHelpers(mod, meta) {
1263
1290
  if (sig?.form !== "func") return;
1264
1291
  const paramW = sig.params;
1265
1292
  const resultW = sig.results;
1266
- if (!needsConv(paramW) && !needsConv(resultW)) return;
1293
+ const paramDescs = entry.params ?? [];
1294
+ const resultDescs = entry.returns ?? [];
1295
+ if (!needsConv(paramW, paramDescs) && !needsConv(resultW, resultDescs))
1296
+ return;
1267
1297
  // Exported names become JS identifiers: no dot of a qualified type.
1268
1298
  entry.callName = `__x_${entry.exportName}`.replaceAll(".", "_");
1269
1299
  addFunc(
1270
1300
  entry.callName,
1271
- paramW.map(extType),
1272
- resultW.map(extType),
1301
+ paramW.map((w, i) => extType(w, paramDescs[i]?.is64)),
1302
+ resultW.map((w, i) => extType(w, resultDescs[i]?.is64)),
1273
1303
  [
1274
- ...paramW.flatMap((w, i) => getIn(i, w)),
1304
+ ...paramW.flatMap((w, i) => getIn(i, w, paramDescs[i]?.is64)),
1275
1305
  { op: "call", funcIndex: fnIdx },
1276
- ...convertResults(resultW, toExt, paramW.length),
1306
+ ...convertResults(resultW, toExt, paramW.length, resultDescs),
1277
1307
  ],
1278
1308
  true,
1279
1309
  resultW.length > 1 ? resultW : [],
@@ -1318,16 +1348,16 @@ export function addBoundaryHelpers(mod, meta) {
1318
1348
  }
1319
1349
 
1320
1350
  for (const [name, desc] of needs.anonStructs) {
1321
- const { info } = desc;
1351
+ const { info, fields } = desc;
1322
1352
  const ref = desc.wType;
1323
1353
  const ti = info.typeIndex;
1324
1354
  const fieldWTypes = info.fields.map((f) => f.wType);
1325
1355
  addFunc(
1326
1356
  `__new_${name}`,
1327
- fieldWTypes.map(extType),
1357
+ fieldWTypes.map((w, i) => extType(w, fields[i]?.desc?.is64)),
1328
1358
  [EXT],
1329
1359
  [
1330
- ...fieldWTypes.flatMap((w, i) => getIn(i, w)),
1360
+ ...fieldWTypes.flatMap((w, i) => getIn(i, w, fields[i]?.desc?.is64)),
1331
1361
  { op: "struct.new", typeIndex: ti },
1332
1362
  ...toExt(ref),
1333
1363
  ],
@@ -1344,23 +1374,24 @@ export function addBoundaryHelpers(mod, meta) {
1344
1374
  );
1345
1375
  for (let i = 0; i < info.fields.length; i++) {
1346
1376
  const f = info.fields[i];
1377
+ const is64 = Boolean(fields[i]?.desc?.is64);
1347
1378
  addFunc(
1348
1379
  `__get_${name}_${f.name}`,
1349
1380
  [EXT],
1350
- [extType(f.wType)],
1381
+ [extType(f.wType, is64)],
1351
1382
  [
1352
1383
  ...getIn(0, ref),
1353
1384
  { op: "struct.get", typeIndex: ti, fieldIndex: i },
1354
- ...toExt(f.wType),
1385
+ ...toExt(f.wType, is64),
1355
1386
  ],
1356
1387
  );
1357
1388
  addFunc(
1358
1389
  `__set_${name}_${f.name}`,
1359
- [EXT, extType(f.wType)],
1390
+ [EXT, extType(f.wType, is64)],
1360
1391
  [],
1361
1392
  [
1362
1393
  ...getIn(0, ref),
1363
- ...getIn(1, f.wType),
1394
+ ...getIn(1, f.wType, is64),
1364
1395
  { op: "struct.set", typeIndex: ti, fieldIndex: i },
1365
1396
  ],
1366
1397
  );
@@ -1368,15 +1399,20 @@ export function addBoundaryHelpers(mod, meta) {
1368
1399
  }
1369
1400
 
1370
1401
  for (const [key, desc] of needs.boxes) {
1371
- const { boxInfo } = desc;
1402
+ const { boxInfo, base } = desc;
1372
1403
  const ref = desc.wType;
1373
1404
  const ti = boxInfo.typeIndex;
1374
1405
  const w = boxInfo.wType;
1406
+ const is64 = Boolean(base?.is64);
1375
1407
  addFunc(
1376
1408
  `__box_new_${key}`,
1377
- [extType(w)],
1409
+ [extType(w, is64)],
1378
1410
  [EXT],
1379
- [...getIn(0, w), { op: "struct.new", typeIndex: ti }, ...toExt(ref)],
1411
+ [
1412
+ ...getIn(0, w, is64),
1413
+ { op: "struct.new", typeIndex: ti },
1414
+ ...toExt(ref),
1415
+ ],
1380
1416
  );
1381
1417
  const cast = {
1382
1418
  op: "ref.cast",
@@ -1386,29 +1422,29 @@ export function addBoundaryHelpers(mod, meta) {
1386
1422
  addFunc(
1387
1423
  `__box_get_${key}`,
1388
1424
  [EXT],
1389
- [extType(w)],
1425
+ [extType(w, is64)],
1390
1426
  [
1391
1427
  ...getIn(0, ref),
1392
1428
  cast,
1393
1429
  { op: "struct.get", typeIndex: ti, fieldIndex: 0 },
1394
- ...toExt(w),
1430
+ ...toExt(w, is64),
1395
1431
  ],
1396
1432
  );
1397
1433
  addFunc(
1398
1434
  `__box_set_${key}`,
1399
- [EXT, extType(w)],
1435
+ [EXT, extType(w, is64)],
1400
1436
  [],
1401
1437
  [
1402
1438
  ...getIn(0, ref),
1403
1439
  cast,
1404
- ...getIn(1, w),
1440
+ ...getIn(1, w, is64),
1405
1441
  { op: "struct.set", typeIndex: ti, fieldIndex: 0 },
1406
1442
  ],
1407
1443
  );
1408
1444
  }
1409
1445
 
1410
1446
  for (const [key, desc] of needs.maps) {
1411
- const { mapInfo, keySliceDesc, valSliceDesc } = desc;
1447
+ const { mapInfo, keySliceDesc, valSliceDesc, keyDesc, valDesc } = desc;
1412
1448
  const makeIdx = mod.resolveFuncIndex(mapInfo.makeFuncName);
1413
1449
  addFunc(
1414
1450
  `__map_new_${key}`,
@@ -1419,12 +1455,16 @@ export function addBoundaryHelpers(mod, meta) {
1419
1455
  const setIdx = mod.resolveFuncIndex(mapInfo.setFuncName);
1420
1456
  addFunc(
1421
1457
  `__map_set_${key}`,
1422
- [EXT, extType(mapInfo.keyWType), extType(mapInfo.valWType)],
1458
+ [
1459
+ EXT,
1460
+ extType(mapInfo.keyWType, keyDesc?.is64),
1461
+ extType(mapInfo.valWType, valDesc?.is64),
1462
+ ],
1423
1463
  [],
1424
1464
  [
1425
1465
  ...getIn(0, desc.wType),
1426
- ...getIn(1, mapInfo.keyWType),
1427
- ...getIn(2, mapInfo.valWType),
1466
+ ...getIn(1, mapInfo.keyWType, keyDesc?.is64),
1467
+ ...getIn(2, mapInfo.valWType, valDesc?.is64),
1428
1468
  { op: "call", funcIndex: setIdx },
1429
1469
  ],
1430
1470
  );
@@ -1483,80 +1523,27 @@ export function addBoundaryHelpers(mod, meta) {
1483
1523
  );
1484
1524
  }
1485
1525
 
1486
- if (needs.time) {
1487
- const timeInfo = mod.getStructType("time.Time");
1488
- if (timeInfo) {
1489
- const timeWType = toRef(timeInfo);
1490
- addFunc(
1491
- "__time_make",
1492
- ["f64", "i32"],
1493
- [EXT],
1494
- [
1495
- get(0),
1496
- { op: "i64.trunc_sat_f64_s" },
1497
- { op: "i64.const", value: 1000000n },
1498
- { op: "i64.mul" },
1499
- get(1),
1500
- { op: "ref.null", heapType: "any" },
1501
- { op: "struct.new", typeIndex: timeInfo.typeIndex },
1502
- ...toExt(timeWType),
1503
- ],
1504
- );
1505
- addFunc(
1506
- "__time_unix_milli",
1507
- [EXT],
1508
- ["f64"],
1509
- [
1510
- get(0),
1511
- { op: "ref.is_null" },
1512
- { op: "if", blockType: "f64" },
1513
- { op: "f64.const", value: 0 },
1514
- { op: "else" },
1515
- ...getIn(0, timeWType),
1516
- { op: "struct.get", typeIndex: timeInfo.typeIndex, fieldIndex: 0 },
1517
- { op: "i64.const", value: 1000000n },
1518
- { op: "i64.div_s" },
1519
- { op: "f64.convert_i64_s" },
1520
- { op: "end" },
1521
- ],
1522
- );
1523
- addFunc(
1524
- "__time_offset",
1525
- [EXT],
1526
- ["i32"],
1527
- [
1528
- get(0),
1529
- { op: "ref.is_null" },
1530
- { op: "if", blockType: "i32" },
1531
- { op: "i32.const", value: 0 },
1532
- { op: "else" },
1533
- ...getIn(0, timeWType),
1534
- { op: "struct.get", typeIndex: timeInfo.typeIndex, fieldIndex: 1 },
1535
- { op: "end" },
1536
- ],
1537
- );
1538
- }
1539
- }
1540
-
1541
1526
  for (const c of meta.consts ?? []) {
1542
1527
  if (!c.dynamic) continue;
1543
1528
  const g = mod.resolveGlobal(c.name);
1544
1529
  if (!g) continue;
1530
+ const is64 = Boolean(c.desc?.is64);
1545
1531
  addFunc(
1546
1532
  `__const_${c.name}`,
1547
1533
  [],
1548
- [extType(c.desc.wType)],
1549
- [{ op: "global.get", index: g.index }, ...toExt(c.desc.wType)],
1534
+ [extType(c.desc.wType, is64)],
1535
+ [{ op: "global.get", index: g.index }, ...toExt(c.desc.wType, is64)],
1550
1536
  );
1551
1537
  }
1552
1538
  for (const v of meta.vars ?? []) {
1553
1539
  const g = v.shared ? null : mod.resolveGlobal(v.name);
1554
1540
  if (!g) continue;
1541
+ const is64 = Boolean(v.desc?.is64);
1555
1542
  addFunc(
1556
1543
  `__var_${v.name}`,
1557
1544
  [],
1558
- [extType(v.desc.wType)],
1559
- [{ op: "global.get", index: g.index }, ...toExt(v.desc.wType)],
1545
+ [extType(v.desc.wType, is64)],
1546
+ [{ op: "global.get", index: g.index }, ...toExt(v.desc.wType, is64)],
1560
1547
  );
1561
1548
  }
1562
1549
  }
@@ -1625,6 +1612,13 @@ function addIfaceHelpers(desc, mod, addFunc, addTrampoline) {
1625
1612
 
1626
1613
  const P = "__gfw_"; // facade helper prefix
1627
1614
 
1615
+ // Pieces of the strings host imports (written for use inside the templates
1616
+ // below, where a backslash escape must be doubled).
1617
+ const GO_SPACE =
1618
+ "[ \\t\\n\\v\\f\\r\\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]";
1619
+ const BAD_RUNE = "r < 0 || r > 0x10FFFF || (r >= 0xD800 && r <= 0xDFFF)";
1620
+ const SURROGATE = "(v >= 0xD800 && v <= 0xDFFF)";
1621
+
1628
1622
  // JS expression converting JS value `v` into the wasm representation.
1629
1623
  function inExpr(desc, v) {
1630
1624
  switch (desc.k) {
@@ -1633,7 +1627,7 @@ function inExpr(desc, v) {
1633
1627
  case "i32":
1634
1628
  return `(${v} | 0)`;
1635
1629
  case "i64":
1636
- return `${P}i64in(${v})`;
1630
+ return desc.is64 ? `BigInt(${v})` : `${P}i64in(${v})`;
1637
1631
  case "f32":
1638
1632
  case "f64":
1639
1633
  return `(+${v})`;
@@ -1647,8 +1641,6 @@ function inExpr(desc, v) {
1647
1641
  return `${P}f32x4in(${v})`;
1648
1642
  case "mask4":
1649
1643
  return `${P}mask4in(${v})`;
1650
- case "time":
1651
- return desc.ptr ? `${P}timein_ptr(${v})` : `${P}timein(${v})`;
1652
1644
  case "map":
1653
1645
  return `${P}mapin_${desc.key}(${v})`;
1654
1646
  case "ptr_box":
@@ -1686,6 +1678,10 @@ function outExpr(desc, v) {
1686
1678
  case "i32":
1687
1679
  return desc.unsigned ? `(${v} >>> 0)` : v;
1688
1680
  case "i64":
1681
+ if (desc.is64)
1682
+ return desc.unsigned
1683
+ ? `BigInt.asUintN(64, ${v})`
1684
+ : `BigInt.asIntN(64, ${v})`;
1689
1685
  return desc.unsigned ? `${P}u64out(${v})` : `${P}i64out(${v})`;
1690
1686
  case "string":
1691
1687
  return `(${v} ?? "")`;
@@ -1695,8 +1691,6 @@ function outExpr(desc, v) {
1695
1691
  return `${P}f32x4out(${v})`;
1696
1692
  case "mask4":
1697
1693
  return `${P}mask4out(${v})`;
1698
- case "time":
1699
- return desc.ptr ? `${P}timeout_ptr(${v})` : `${P}timeout(${v})`;
1700
1694
  case "map":
1701
1695
  return `${P}mapout_${desc.key}(${v})`;
1702
1696
  case "ptr_box":
@@ -1729,10 +1723,11 @@ function zeroExpr(desc) {
1729
1723
  case "bool":
1730
1724
  return "false";
1731
1725
  case "i32":
1732
- case "i64":
1733
1726
  case "f32":
1734
1727
  case "f64":
1735
1728
  return "0";
1729
+ case "i64":
1730
+ return desc.is64 ? "0n" : "0";
1736
1731
  case "string":
1737
1732
  return '""';
1738
1733
  case "f32x4":
@@ -1740,7 +1735,6 @@ function zeroExpr(desc) {
1740
1735
  case "mask4":
1741
1736
  return "[false, false, false, false]";
1742
1737
  case "error":
1743
- case "time":
1744
1738
  case "map":
1745
1739
  case "ptr_box":
1746
1740
  return "null";
@@ -1855,14 +1849,11 @@ function indent(text, prefix) {
1855
1849
  // survive the built-in mangler: no shorthand destructuring, import keys
1856
1850
  // quoted, helpers prefixed with `__`.
1857
1851
  export const WASM_IMPORTS_JS = `const ${P}MAX = 9007199254740991n;
1858
- function ${P}imports(stringTable, extraEnv, tag, write) {
1852
+ function ${P}imports(stringTable, extraEnv, tag, write, toStderr) {
1853
+ // Each instance numbers the references fmt_ptr_id is asked about from 1
1854
+ // (the table the JS body of that intrinsic uses).
1855
+ globalThis.__fmtptr = { m: new WeakMap(), n: 1 };
1859
1856
  let lineBuf = "";
1860
- let targs = [];
1861
- let fmtStructMap = new WeakMap();
1862
- // A value whose type has String()/Error(): the method already ran in WASM.
1863
- // __gfUnbox hands fmt the raw value for the other verbs.
1864
- class FmtStr { constructor(r, s) { this.r = r; this.s = s; } __gfUnbox() { return this.r; } String() { return this.s; } }
1865
- class FmtErr { constructor(r, s) { this.r = r; this.s = s; } __gfUnbox() { return this.r; } Error() { return this.s; } }
1866
1857
  const late = { exports: null };
1867
1858
  // Go separates operands with a space unconditionally, so track whether the
1868
1859
  // current line already holds an operand (an empty string leaves lineBuf empty).
@@ -1878,7 +1869,6 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
1878
1869
  print(v);
1879
1870
  flush();
1880
1871
  };
1881
- const targ = (v) => { targs.push(v); };
1882
1872
  let utf8Encoded = null;
1883
1873
  let convBytes = null, convPos = 0;
1884
1874
  const env = {
@@ -1890,7 +1880,9 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
1890
1880
  } catch {
1891
1881
  str = "[panic object]";
1892
1882
  }
1893
- throw new Error(str);
1883
+ const err = new Error(str);
1884
+ err.__gfp = true;
1885
+ throw err;
1894
1886
  },
1895
1887
  "str": (i) => stringTable[i] ?? "",
1896
1888
  "str_len": (s) => (s ? s.length : 0),
@@ -1903,7 +1895,7 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
1903
1895
  "str_ge": (a, b) => ((a ?? "") >= (b ?? "") ? 1 : 0),
1904
1896
  "str_get": (s, i) => (s ? s.charCodeAt(i) : 0),
1905
1897
  "str_slice": (s, a, b) => (s ? s.slice(a, b) : ""),
1906
- "str_from_code_point": (c) => String.fromCodePoint(c),
1898
+ "str_from_code_point": (c) => String.fromCodePoint(c >= 0 && c <= 0x10ffff && !(c >= 0xd800 && c <= 0xdfff) ? c : 0xfffd),
1907
1899
  "str_code_point_at": (s, i) => (s ? s.codePointAt(i) : 0),
1908
1900
  "str_hash": (s) => {
1909
1901
  if (!s) return 0;
@@ -1913,29 +1905,109 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
1913
1905
  }
1914
1906
  return h;
1915
1907
  },
1916
- "str_to_upper": (s) => (s ? s.toUpperCase() : ""),
1917
- "str_to_lower": (s) => (s ? s.toLowerCase() : ""),
1918
- "str_to_valid_utf8": (s, repl) => (s ? s.replace(/(?:[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF])+/g, repl ?? "") : (s ?? "")),
1919
- "str_trim_space": (s) => (s ? s.trim() : ""),
1920
- "str_contains": (s, sub) => (s && sub !== undefined ? (s.includes(sub) ? 1 : 0) : 0),
1921
- "str_has_prefix": (s, pre) => (s && pre !== undefined ? (s.startsWith(pre) ? 1 : 0) : 0),
1922
- "str_has_suffix": (s, suf) => (s && suf !== undefined ? (s.endsWith(suf) ? 1 : 0) : 0),
1923
- "str_index": (s, sub) => (s ? s.indexOf(sub) : -1),
1924
- "str_last_index": (s, sub) => (s ? s.lastIndexOf(sub) : -1),
1925
- "str_repeat": (s, n) => (s && n > 0 ? s.repeat(Number(n)) : ""),
1926
- "str_replace_all": (s, o, n) => (s ? s.replaceAll(o, n) : ""),
1927
- "str_equal_fold": (a, b) => ((a ?? "").toLowerCase() === (b ?? "").toLowerCase() ? 1 : 0),
1928
- "str_count": (s, sep) => {
1929
- if (!s) return sep === "" ? 1 : 0;
1930
- if (sep === "") return s.length + 1;
1931
- return s.split(sep).length - 1;
1908
+ "str_to_upper": (s) => ${P}mapCase(s ?? "", "toUpperCase"),
1909
+ "str_to_lower": (s) => ${P}mapCase(s ?? "", "toLowerCase"),
1910
+ "str_trim_space": (s) => {
1911
+ s = s ?? "";
1912
+ const sp = /${GO_SPACE}/;
1913
+ let i = 0, j = s.length;
1914
+ while (i < j && sp.test(s[i])) i++;
1915
+ while (j > i && sp.test(s[j - 1])) j--;
1916
+ return s.slice(i, j);
1917
+ },
1918
+ "str_contains": (s, sub) => ((s ?? "").includes(sub ?? "") ? 1 : 0),
1919
+ "str_has_prefix": (s, pre) => ((s ?? "").startsWith(pre ?? "") ? 1 : 0),
1920
+ "str_has_suffix": (s, suf) => ((s ?? "").endsWith(suf ?? "") ? 1 : 0),
1921
+ "os_write": (fd, s) => {
1922
+ if (fd === 2 && toStderr && typeof process !== "undefined" && process.stderr?.write) { process.stderr.write(s ?? ""); return; }
1923
+ lineBuf += s ?? "";
1924
+ const nl = lineBuf.lastIndexOf("\\n");
1925
+ if (nl >= 0) { write(lineBuf.slice(0, nl)); lineBuf = lineBuf.slice(nl + 1); }
1932
1926
  },
1927
+ "time_now_ms": () => (typeof globalThis.__GOFRONT_CLOCK === "function" ? globalThis.__GOFRONT_CLOCK() : Date.now()),
1928
+ "time_timezone_offset": (ms) => (-new Date(ms).getTimezoneOffset() * 60) | 0,
1929
+ "os_exit": (code) => {
1930
+ if (lineBuf !== "") { write(lineBuf); lineBuf = ""; }
1931
+ if (typeof process !== "undefined" && process.exit) process.exit(code);
1932
+ throw new Error("os.Exit(" + code + ")");
1933
+ },
1934
+ "os_getenv": (k) => (typeof process !== "undefined" && process.env ? process.env : (globalThis.__os_env ??= {}))[k ?? ""] ?? "",
1935
+ "os_setenv": (k, v) => { (typeof process !== "undefined" && process.env ? process.env : (globalThis.__os_env ??= {}))[k ?? ""] = v ?? ""; },
1936
+ "unicode_is": (r, k) => { ${unicodeIsJs("r", "k")} },
1937
+ "unicode_to_upper": (r) => { ${unicodeToUpperJs("r")} },
1938
+ "unicode_to_lower": (r) => { ${unicodeToLowerJs("r")} },
1939
+ "testing_nanotime": () => performance.now() * 1e6,
1940
+ "testing_match": (p, s) => new RegExp(p ?? "").test(s ?? "") ? 1 : 0,
1941
+ "fmt_ptr_id": (o) => {
1942
+ if (o === null || o === undefined || (typeof o !== "object" && typeof o !== "function")) return 0;
1943
+ const t = globalThis.__fmtptr;
1944
+ let id = t.m.get(o);
1945
+ if (!id) { id = t.n++; t.m.set(o, id); }
1946
+ return id;
1947
+ },
1948
+ "crypto_rand_u32": () => {
1949
+ const c = typeof crypto !== "undefined" ? crypto : globalThis.crypto;
1950
+ return c?.getRandomValues ? c.getRandomValues(new Uint32Array(1))[0] | 0 : (Math.random() * 4294967296) | 0;
1951
+ },
1952
+ "strconv_shortest": (f) => f.toExponential(),
1953
+ "strconv_fixed": ${FIXED_FLOAT_JS},
1954
+ "strconv_parse_decimal": (s) => Number((s ?? "").replaceAll("_", "")),
1955
+ "str_index": (s, sub) => (s ?? "").indexOf(sub ?? ""),
1956
+ "str_last_index": (s, sub) => (s ?? "").lastIndexOf(sub ?? ""),
1957
+ "str_index_byte": (s, b) => (s ?? "").indexOf(String.fromCharCode(b)),
1958
+ "str_last_index_byte": (s, b) => (s ?? "").lastIndexOf(String.fromCharCode(b)),
1959
+ "str_index_rune": (s, r) => (${BAD_RUNE} ? -1 : (s ?? "").indexOf(String.fromCodePoint(r))),
1960
+ "str_contains_rune": (s, r) => (!(${BAD_RUNE}) && (s ?? "").includes(String.fromCodePoint(r)) ? 1 : 0),
1961
+ "str_index_any": (s, cs) => {
1962
+ s = s ?? "";
1963
+ let m = -1;
1964
+ for (const c of (cs ?? "")) {
1965
+ const v = c.codePointAt(0);
1966
+ if (${SURROGATE}) continue;
1967
+ const i = s.indexOf(c);
1968
+ if (i >= 0 && (m < 0 || i < m)) m = i;
1969
+ }
1970
+ return m;
1971
+ },
1972
+ "str_last_index_any": (s, cs) => {
1973
+ s = s ?? "";
1974
+ let m = -1;
1975
+ for (const c of (cs ?? "")) {
1976
+ const v = c.codePointAt(0);
1977
+ if (${SURROGATE}) continue;
1978
+ const i = s.lastIndexOf(c);
1979
+ if (i > m) m = i;
1980
+ }
1981
+ return m;
1982
+ },
1983
+ "str_contains_any": (s, cs) => {
1984
+ s = s ?? "";
1985
+ for (const c of (cs ?? "")) {
1986
+ const v = c.codePointAt(0);
1987
+ if (!${SURROGATE} && s.includes(c)) return 1;
1988
+ }
1989
+ return 0;
1990
+ },
1991
+ "str_count": (s, t) => {
1992
+ s = s ?? ""; t = t ?? "";
1993
+ if (t === "") return [...s].length + 1;
1994
+ let n = 0;
1995
+ for (let i = s.indexOf(t); i !== -1; i = s.indexOf(t, i + t.length)) n++;
1996
+ return n;
1997
+ },
1998
+ "str_replace": (s, o, w, n) => ${P}replace(s ?? "", o ?? "", w ?? "", n),
1999
+ "str_replace_all": (s, o, w) => ${P}replace(s ?? "", o ?? "", w ?? "", -1),
2000
+ "str_trim": (s, c) => ${P}trim(s ?? "", c ?? "", true, true),
2001
+ "str_trim_left": (s, c) => ${P}trim(s ?? "", c ?? "", true, false),
2002
+ "str_trim_right": (s, c) => ${P}trim(s ?? "", c ?? "", false, true),
2003
+ "str_trim_prefix": (s, p) => ((s ?? "").startsWith(p ?? "") ? (s ?? "").slice((p ?? "").length) : (s ?? "")),
2004
+ "str_trim_suffix": (s, p) => ((s ?? "").endsWith(p ?? "") ? (s ?? "").slice(0, (s ?? "").length - (p ?? "").length) : (s ?? "")),
1933
2005
  "str_from_i64": (n) => String(n),
1934
2006
  "str_from_i32": (n) => String(n),
1935
2007
  "str_from_f64": (f) => (Object.is(f, -0) ? "-0" : String(f)),
1936
2008
  "is_string": (v) => (typeof v === "string" ? 1 : 0),
1937
2009
  "str_to_bytes": (s) => {
1938
- utf8Encoded = new TextEncoder().encode(s ?? "");
2010
+ utf8Encoded = ${P}u8enc(s ?? "");
1939
2011
  return utf8Encoded.length;
1940
2012
  },
1941
2013
  "str_byte_at": (i) => (utf8Encoded ? (utf8Encoded[i] ?? 0) : 0),
@@ -1955,767 +2027,127 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
1955
2027
  },
1956
2028
  "print_i32": print, "print_i64": print, "print_f32": print, "print_f64": print,
1957
2029
  "print_str": print, "print_any": print,
2030
+ "print_u64": (v) => print(BigInt.asUintN(64, v)),
2031
+ "println_u64": (v) => println(BigInt.asUintN(64, v)),
2032
+ "print_reset": () => { started = false; },
1958
2033
  "print_bool": (b) => print(b !== 0 ? "true" : "false"),
1959
2034
  "println_i32": println, "println_i64": println, "println_f32": println, "println_f64": println,
1960
2035
  "println_str": println, "println_any": println,
1961
2036
  "println_bool": (b) => println(b !== 0 ? "true" : "false"),
1962
2037
  "println_empty": flush,
1963
- "fmt_new_array": () => [],
1964
- "fmt_array_push": (arr, v) => { arr.push(v); },
1965
- "fmt_new_map": () => new Map(),
1966
- "fmt_map_set": (m, k, v) => { m.set(k, v); },
1967
- "fmt_new_struct": (ref, isPtr) => {
1968
- // One object per struct and per kind: a value and a pointer to it
1969
- // in the same call (fmt.Println(&s, s)) must stay distinct.
1970
- const slot = isPtr;
1971
- if (isPtr === 2) {
1972
- const seenBox = ref !== null && ref !== undefined && typeof ref === "object" ? fmtStructMap.get(ref) : undefined;
1973
- if (seenBox?.[2]) return seenBox[2];
1974
- const b = { value: null, __p: true, __box: true };
1975
- if (ref !== null && ref !== undefined && typeof ref === "object") {
1976
- if (seenBox) seenBox[2] = b;
1977
- else fmtStructMap.set(ref, [undefined, undefined, b]);
1978
- }
1979
- return b;
1980
- }
1981
- const isRef = ref !== null && ref !== undefined && typeof ref === "object";
1982
- const seen = isRef ? fmtStructMap.get(ref) : undefined;
1983
- if (seen?.[slot]) return seen[slot];
1984
- const o = {};
1985
- Object.defineProperty(o, "__struct", { value: true, configurable: true });
1986
- if (isPtr !== 0) Object.defineProperty(o, "__p", { value: true, configurable: true });
1987
- if (isRef) {
1988
- if (seen) seen[slot] = o;
1989
- else fmtStructMap.set(ref, slot ? [undefined, o] : [o, undefined]);
1990
- }
1991
- return o;
1992
- },
1993
- "fmt_struct_type": (o, i) => { Object.defineProperty(o, "__tn", { value: stringTable[i], configurable: true }); },
1994
- "fmt_struct_set": (o, k, v) => { o[typeof k === "number" ? stringTable[k] : k] = v; },
1995
- "fmt_box_i32": (v) => v,
1996
- "fmt_box_i64": (v) => (v > ${P}MAX || v < -${P}MAX ? v : Number(v)),
1997
- "fmt_box_u64": (v) => { v = BigInt.asUintN(64, v); return v > ${P}MAX ? v : Number(v); },
1998
- "fmt_box_f64": (v) => v,
1999
- "fmt_box_bool": (v) => v !== 0,
2000
- "fmt_new_stringer": (r, s, isErr) => (isErr !== 0 ? new FmtErr(r, s) : new FmtStr(r, s)),
2001
- "fmt_new_box": () => ({ value: null, __p: true, __box: true }),
2002
- "testing_arg_i32": targ, "testing_arg_f32": targ, "testing_arg_f64": targ,
2003
- "testing_arg_str": targ, "testing_arg_any": targ,
2004
- "testing_arg_i64": (v) => targ(v > ${P}MAX || v < -${P}MAX ? v : Number(v)),
2005
- "testing_arg_bool": (v) => targ(v !== 0),
2006
- "testing_arg_u64": (v) => { v = BigInt.asUintN(64, v); targ(v > ${P}MAX ? v : Number(v)); },
2007
- "fmt_arg_type": (i) => { if (targs.length > 0) targs[targs.length - 1] = __ft(targs[targs.length - 1], JSON.parse(stringTable[i])); },
2008
- "testing_arg_spread": (a) => { if (a) for (const v of a) targs.push(v); },
2009
- "testing_arg_err": (s) => targ({ Error() { return s; } }),
2010
- "testing_call": (t, i) => { const a = targs; targs = []; fmtStructMap = new WeakMap(); t[stringTable[i]](...a); },
2011
- "testing_name": (t) => t.Name(),
2012
- "testing_flag": (t, i) => Number(t[stringTable[i]]()),
2013
- "testing_n": (t) => BigInt(t.N ?? 0),
2014
- "testing_elapsed": (t) => BigInt(t.Elapsed()),
2015
- "testing_context": (t) => t.Context(),
2016
- "testing_output": (t) => t.Output(),
2017
2038
  };
2018
- // Stdlib imports (strings/strconv/utf8/fmt/t.Run) are a separate block so
2019
- // modules that never call them do not ship it (see WASM_STDLIB_JS).
2020
- if (typeof ${P}stdlib === "function") {
2021
- Object.assign(env, ${P}stdlib(stringTable, {
2022
- "take": () => { const a = targs; targs = []; fmtStructMap = new WeakMap(); return a; },
2023
- "printf": (s) => {
2024
- lineBuf += s;
2025
- const nl = lineBuf.lastIndexOf("\\n");
2026
- if (nl >= 0) { write(lineBuf.slice(0, nl)); lineBuf = lineBuf.slice(nl + 1); }
2027
- },
2028
- "late": late,
2029
- }));
2030
- }
2031
2039
  Object.assign(env, extraEnv);
2040
+ // A host import that throws raises a Go panic: the module's panic stack
2041
+ // gets the message (as env.panic does through __push_panic) before the
2042
+ // error unwinds, so recover() in a calling frame sees it. Errors already
2043
+ // pushed (env.panic, nested calls into the module) are marked and pass
2044
+ // through; traps and wasm exceptions are not host panics.
2045
+ const hostPanic = (e) => {
2046
+ if (e !== null && typeof e === "object") {
2047
+ if (e.__gfp === true) return;
2048
+ if (typeof WebAssembly !== "undefined" && (e instanceof WebAssembly.Exception || e instanceof WebAssembly.RuntimeError)) return;
2049
+ try { e.__gfp = true; } catch {}
2050
+ }
2051
+ const push = late.exports && late.exports.__push_panic;
2052
+ if (push) push(String(e !== null && typeof e === "object" && e.message !== undefined ? e.message : e));
2053
+ };
2054
+ for (const k of Object.keys(env)) {
2055
+ const fn = env[k];
2056
+ if (typeof fn !== "function" || k === "panic" || k === "panicTag") continue;
2057
+ env[k] = function () {
2058
+ try { return fn.apply(this, arguments); } catch (e) { hostPanic(e); throw e; }
2059
+ };
2060
+ }
2032
2061
  const m = {};
2033
2062
  for (const k of ["sin", "cos", "tan", "asin", "acos", "atan", "atan2", "pow", "exp", "log", "log2", "log10", "cbrt", "hypot", "random", "sinh", "cosh", "tanh", "log1p", "expm1"]) m[k] = Math[k];
2034
2063
  m.mod = (x, y) => x % y;
2035
2064
  // Go rounds half away from zero; Math.round rounds half toward +Infinity.
2036
2065
  m.round = (x) => Math.sign(x) * Math.round(Math.abs(x));
2037
2066
  // Exports are needed by imports that call back into the module (t.Run).
2038
- return { "env": env, "Math": m, "__bind": (ex) => { late.exports = ex; } };
2039
- }`;
2040
-
2041
- // JS side of the STDLIB_IMPORTS table (index.js); emitted only when the
2042
- // module imports one of the packages that use it.
2043
- export const WASM_STDLIB_JS = `${HELPER_ERROR}
2044
- ${HELPER_SPRINTF}
2045
- ${HELPER_SPRINT}
2046
- ${HELPER_STRCONV}
2047
- ${HELPER_PATH_CLEAN}
2048
- ${HELPER_DURATION}
2049
- ${HELPER_SHA256}
2050
- ${HELPER_HEX}
2051
- ${HELPER_BASE64}
2052
- function ${P}stdlib_fmt(stringTable, io) {
2053
- let sscanTokens = [];
2054
- let sscanHasErr = 0, sscanErrMsg = "";
2055
- return {
2056
- "fmt_sscan_init": (s, mode) => {
2057
- sscanHasErr = 0;
2058
- sscanErrMsg = "";
2059
- const str = s ?? "";
2060
- sscanTokens = (mode === 1 ? str.split("\\n")[0] : str).trim().split(/\\s+/).filter(Boolean);
2061
- return sscanTokens.length;
2062
- },
2063
- "fmt_sscanf_init": (s, fmt) => {
2064
- sscanHasErr = 0;
2065
- sscanErrMsg = "";
2066
- const str = s ?? "";
2067
- const fmtStr = fmt ?? "";
2068
- let si = 0, fi = 0;
2069
- sscanTokens = [];
2070
- while (fi < fmtStr.length) {
2071
- if (/\\s/.test(fmtStr[fi])) {
2072
- while (fi < fmtStr.length && /\\s/.test(fmtStr[fi])) fi++;
2073
- while (si < str.length && /\\s/.test(str[si])) si++;
2074
- continue;
2075
- }
2076
- if (fmtStr[fi] === "%") {
2077
- if (fi + 1 < fmtStr.length && fmtStr[fi + 1] === "%") {
2078
- if (str[si] !== "%") {
2079
- sscanHasErr = 1;
2080
- sscanErrMsg = "input does not match format";
2081
- break;
2082
- }
2083
- si++;
2084
- fi += 2;
2085
- continue;
2086
- }
2087
- const vm = fmtStr.slice(fi).match(/^%[vdsfqtxXbcoeEfFgG]/);
2088
- if (vm) {
2089
- const verb = vm[0];
2090
- fi += verb.length;
2091
- if (verb !== "%c") {
2092
- while (si < str.length && /\\s/.test(str[si])) si++;
2093
- }
2094
- if (si >= str.length) break;
2095
- let tokRe = /^\\S+/;
2096
- if (verb === "%d" || verb === "%i") tokRe = /^[+-]?\\d+/;
2097
- else if (verb === "%f" || verb === "%F" || verb === "%e" || verb === "%E" || verb === "%g" || verb === "%G") tokRe = /^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?/;
2098
- else if (verb === "%t") tokRe = /^(?:true|false|1|0|t|f|T|F|TRUE|FALSE)/;
2099
- else if (verb === "%x" || verb === "%X") tokRe = /^[+-]?(?:0x)?[0-9a-fA-F]+/;
2100
- else if (verb === "%c") tokRe = /^[\\s\\S]/;
2101
- const m = str.slice(si).match(tokRe);
2102
- if (!m) {
2103
- sscanHasErr = 1;
2104
- sscanErrMsg = "input does not match format";
2105
- break;
2106
- }
2107
- sscanTokens.push(m[0]);
2108
- si += m[0].length;
2109
- continue;
2110
- }
2111
- }
2112
- if (si >= str.length) break;
2113
- if (str[si] !== fmtStr[fi]) {
2114
- sscanHasErr = 1;
2115
- sscanErrMsg = "input does not match format";
2116
- break;
2117
- }
2118
- si++;
2119
- fi++;
2120
- }
2121
- return sscanTokens.length;
2122
- },
2123
- "fmt_sscan_has_err": () => sscanHasErr,
2124
- "fmt_sscan_err_msg": () => sscanErrMsg || "syntax error",
2125
- "fmt_sscan_token_str": (i) => sscanTokens[i] ?? "",
2126
- "fmt_sscan_token_i64": (i) => {
2127
- if (i >= sscanTokens.length) { sscanHasErr = 1; sscanErrMsg = "unexpected EOF"; return 0n; }
2128
- const t = sscanTokens[i];
2129
- if (/^[+-]?(?:0[xX])?[0-9a-fA-F]+$/.test(t)) {
2130
- try {
2131
- if (/^[+-]?[0-9a-fA-F]+$/.test(t) && /[a-fA-F]/.test(t)) {
2132
- return BigInt("0x" + t);
2133
- }
2134
- return BigInt(t);
2135
- } catch {}
2136
- }
2137
- if (t.length === 1) {
2138
- return BigInt(t.charCodeAt(0));
2139
- }
2140
- sscanHasErr = 1;
2141
- sscanErrMsg = "expected integer";
2142
- return 0n;
2143
- },
2144
- "fmt_sscan_token_f64": (i) => {
2145
- if (i >= sscanTokens.length) { sscanHasErr = 1; sscanErrMsg = "unexpected EOF"; return 0; }
2146
- const t = sscanTokens[i];
2147
- const n = Number(t);
2148
- if (Number.isNaN(n) || !/^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?$/.test(t)) {
2149
- sscanHasErr = 1;
2150
- sscanErrMsg = "expected float";
2151
- return 0;
2152
- }
2153
- return n;
2154
- },
2155
- "fmt_sscan_token_bool": (i) => {
2156
- if (i >= sscanTokens.length) { sscanHasErr = 1; sscanErrMsg = "unexpected EOF"; return 0; }
2157
- const t = sscanTokens[i]?.toLowerCase();
2158
- if (t === "true" || t === "1" || t === "t") return 1;
2159
- if (t === "false" || t === "0" || t === "f") return 0;
2160
- sscanHasErr = 1;
2161
- sscanErrMsg = "expected boolean";
2162
- return 0;
2163
- },
2164
- "testing_run": (t, name, fn) => (t.Run(name, (sub) => { io.late.exports.__testing_run_cb(fn, sub); }) ? 1 : 0),
2165
- "testing_cleanup": (t, fn) => { t.Cleanup(() => { io.late.exports.__testing_cleanup_cb(fn); }); },
2166
- "testing_n": (t) => BigInt(t.N ?? 0),
2167
- "testing_elapsed": (t) => BigInt(t.Elapsed()),
2168
- "testing_context": (t) => t.Context(),
2169
- "testing_output": (t) => t.Output(),
2170
- "fmt_sprintf": (f) => __sprintf(f ?? "", ...io.take()),
2171
- "fmt_sprint": (ln) => __sprint(ln !== 0, ...io.take()),
2172
- "fmt_print": (ln) => { io.printf(__sprint(ln !== 0, ...io.take())); },
2173
- "fmt_print_raw": (s) => { io.printf(s ?? ""); },
2174
- "fmt_printf": (f) => { io.printf(__sprintf(f ?? "", ...io.take())); },
2175
- "fmt_format_v": (v) => { const res = __sprintf("%v", v); io.take(); return res; },
2176
- };
2177
- }
2178
- function ${P}stdlib_str(stringTable, io) {
2179
- let parts = [];
2180
- let cutBefore = "", cutAfter = "";
2181
- let replacerTarget = "", replacerPairs = [];
2182
- let strconvErr = null, strconvErrKind = 0;
2183
- let decodeSize = 0;
2184
- return {
2185
- "str_trim": (s, c) => ${P}trim(s ?? "", c ?? "", true, true),
2186
- "str_trim_left": (s, c) => ${P}trim(s ?? "", c ?? "", true, false),
2187
- "str_trim_right": (s, c) => ${P}trim(s ?? "", c ?? "", false, true),
2188
- "str_trim_prefix": (s, p) => (s && p && s.startsWith(p) ? s.slice(p.length) : (s ?? "")),
2189
- "str_trim_suffix": (s, p) => (s && p && s.endsWith(p) ? s.slice(0, s.length - p.length) : (s ?? "")),
2190
- "str_cut": (s, sep) => {
2191
- const i = (s ?? "").indexOf(sep ?? "");
2192
- if (i < 0) { cutBefore = s ?? ""; cutAfter = ""; return 0; }
2193
- cutBefore = (s ?? "").slice(0, i);
2194
- cutAfter = (s ?? "").slice(i + (sep ?? "").length);
2195
- return 1;
2196
- },
2197
- "str_cut_before": () => cutBefore,
2198
- "str_cut_after": () => cutAfter,
2199
- "str_cut_prefix": (s, p) => {
2200
- s = s ?? ""; p = p ?? "";
2201
- if (s.startsWith(p)) { cutAfter = s.slice(p.length); return 1; }
2202
- cutAfter = s; return 0;
2203
- },
2204
- "str_cut_suffix": (s, suf) => {
2205
- s = s ?? ""; suf = suf ?? "";
2206
- if (s.endsWith(suf)) { cutBefore = s.slice(0, s.length - suf.length); return 1; }
2207
- cutBefore = s; return 0;
2208
- },
2209
- "str_replace": (s, o, n, cnt) => {
2210
- s = s ?? ""; o = o ?? ""; n = n ?? "";
2211
- if (cnt < 0) return s.replaceAll(o, n);
2212
- let out = "", from = 0;
2213
- for (let k = 0; k < cnt; k++) {
2214
- const at = s.indexOf(o, from);
2215
- if (at < 0) break;
2216
- out += s.slice(from, at) + n;
2217
- from = at + o.length;
2218
- if (o === "") { if (from >= s.length) break; out += s[from]; from++; }
2219
- }
2220
- return out + s.slice(from);
2221
- },
2222
- "str_contains_rune": (s, r) => ((s ?? "").includes(String.fromCodePoint(r)) ? 1 : 0),
2223
- "str_contains_any": (s, cs) => { for (const c of (cs ?? "")) if ((s ?? "").includes(c)) return 1; return 0; },
2224
- "str_index_byte": (s, b) => (s ?? "").indexOf(String.fromCharCode(b)),
2225
- "str_index_rune": (s, r) => (s ?? "").indexOf(String.fromCodePoint(r)),
2226
- "str_index_any": (s, cs) => { s = s ?? ""; let best = -1; for (const c of (cs ?? "")) { const i = s.indexOf(c); if (i >= 0 && (best < 0 || i < best)) best = i; } return best; },
2227
- "str_last_index_byte": (s, b) => (s ?? "").lastIndexOf(String.fromCharCode(b)),
2228
- "str_last_index_any": (s, cs) => {
2229
- s = s ?? ""; let best = -1;
2230
- for (const c of (cs ?? "")) {
2231
- const i = s.lastIndexOf(c);
2232
- if (i > best) best = i;
2233
- }
2234
- return best;
2235
- },
2236
- "str_title": (s) => (s ?? "").replace(/(^|[^\\p{L}\\p{N}_'])(\\p{Ll})/gu, (m, a, b) => a + b.toUpperCase()),
2237
- "str_split": (s, sep) => { parts = (s ?? "").split(sep ?? ""); return parts.length; },
2238
- "str_split_n": (s, sep, n) => {
2239
- s = s ?? ""; sep = sep ?? "";
2240
- if (n === 0) return -1;
2241
- if (n < 0) { parts = sep === "" ? [...s] : s.split(sep); return parts.length; }
2242
- if (sep === "") {
2243
- const chars = [...s];
2244
- if (chars.length <= n) { parts = chars; return parts.length; }
2245
- const r = chars.slice(0, n - 1);
2246
- r.push(chars.slice(n - 1).join(""));
2247
- parts = r;
2248
- return parts.length;
2249
- }
2250
- const r = [];
2251
- let cur = s;
2252
- for (let i = 1; i < n && cur.length; i++) {
2253
- const j = cur.indexOf(sep);
2254
- if (j < 0) break;
2255
- r.push(cur.slice(0, j));
2256
- cur = cur.slice(j + sep.length);
2257
- }
2258
- r.push(cur);
2259
- parts = r;
2260
- return parts.length;
2261
- },
2262
- "str_split_after": (s, sep) => {
2263
- s = s ?? ""; sep = sep ?? "";
2264
- if (sep === "") { parts = [...s]; return parts.length; }
2265
- const r = [];
2266
- let cur = s;
2267
- while (true) {
2268
- const j = cur.indexOf(sep);
2269
- if (j < 0) { r.push(cur); break; }
2270
- r.push(cur.slice(0, j + sep.length));
2271
- cur = cur.slice(j + sep.length);
2272
- }
2273
- parts = r;
2274
- return parts.length;
2275
- },
2276
- "str_split_after_n": (s, sep, n) => {
2277
- s = s ?? ""; sep = sep ?? "";
2278
- if (n === 0) return -1;
2279
- if (sep === "") {
2280
- const chars = [...s];
2281
- if (n < 0 || chars.length <= n) parts = chars;
2282
- else {
2283
- const r = chars.slice(0, n - 1);
2284
- r.push(chars.slice(n - 1).join(""));
2285
- parts = r;
2286
- }
2287
- return parts.length;
2288
- }
2289
- const r = [];
2290
- let cur = s;
2291
- for (let i = 1; (n < 0 || i < n) && cur.length; i++) {
2292
- const j = cur.indexOf(sep);
2293
- if (j < 0) break;
2294
- r.push(cur.slice(0, j + sep.length));
2295
- cur = cur.slice(j + sep.length);
2296
- }
2297
- r.push(cur);
2298
- parts = r;
2299
- return parts.length;
2300
- },
2301
- "str_fields": (s) => { parts = (s ?? "").split(/[ \\t\\n\\v\\f\\r\\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]+/).filter((p) => p !== ""); return parts.length; },
2302
- "str_part": (i) => parts[i] ?? "",
2303
- "str_replacer_init": (s) => {
2304
- replacerTarget = s ?? "";
2305
- replacerPairs = [];
2306
- },
2307
- "str_replacer_add": (o, n) => {
2308
- replacerPairs.push([o ?? "", n ?? ""]);
2309
- },
2310
- "str_replacer_run": () => {
2311
- let s = replacerTarget;
2312
- if (!replacerPairs.length || s === "") return s;
2313
- let out = "";
2314
- let i = 0;
2315
- while (i < s.length) {
2316
- let matched = false;
2317
- for (let k = 0; k < replacerPairs.length; k++) {
2318
- const [oldStr, newStr] = replacerPairs[k];
2319
- if (oldStr.length > 0 && s.startsWith(oldStr, i)) {
2320
- out += newStr;
2321
- i += oldStr.length;
2322
- matched = true;
2323
- break;
2324
- }
2325
- }
2326
- if (!matched) {
2327
- out += s[i];
2328
- i++;
2329
- }
2330
- }
2331
- return out;
2332
- },
2333
- "strconv_atoi": (s) => {
2334
- const r = __strconv_atoi(s ?? "");
2335
- strconvErr = r[1];
2336
- strconvErrKind = r[1] ? (r[1].includes("invalid syntax") ? 1 : r[1].includes("value out of range") ? 2 : 0) : 0;
2337
- return r[0];
2338
- },
2339
- "strconv_parse_int": (s, base, bits) => {
2340
- const r = __strconv_parse_int(s ?? "", base, bits);
2341
- strconvErr = r[1];
2342
- strconvErrKind = r[1] ? (r[1].includes("invalid syntax") ? 1 : r[1].includes("value out of range") ? 2 : 0) : 0;
2343
- return r[0];
2344
- },
2345
- "strconv_parse_float": (s) => {
2346
- const r = __strconv_parse_float(s ?? "");
2347
- strconvErr = r[1];
2348
- strconvErrKind = r[1] ? (r[1].includes("invalid syntax") ? 1 : r[1].includes("value out of range") ? 2 : 0) : 0;
2349
- return r[0];
2350
- },
2351
- "strconv_parse_bool": (s) => {
2352
- const r = __strconv_parse_bool(s ?? "");
2353
- strconvErr = r[1];
2354
- strconvErrKind = r[1] ? (r[1].includes("invalid syntax") ? 1 : r[1].includes("value out of range") ? 2 : 0) : 0;
2355
- return r[0] ? 1 : 0;
2356
- },
2357
- "strconv_err": () => strconvErr,
2358
- "strconv_err_kind": () => strconvErrKind,
2359
- "strconv_format_float": (f, fmtc, prec, _bits) => __strconv_format_float(f, fmtc, prec),
2360
- "strconv_format_int": (n, base) => n.toString(base),
2361
- "strconv_format_uint": (n, base) => BigInt.asUintN(64, BigInt(n)).toString(base),
2362
- "strconv_parse_uint": (s, base, bits) => {
2363
- const r = __strconv_parse_uint(s ?? "", base, bits);
2364
- strconvErr = r[1];
2365
- strconvErrKind = r[1] ? (r[1].includes("invalid syntax") ? 1 : r[1].includes("value out of range") ? 2 : 0) : 0;
2366
- return r[0];
2367
- },
2368
- "strconv_quote": (s) => JSON.stringify(s ?? ""),
2369
- "strconv_unquote": (s) => {
2370
- s = s ?? "";
2371
- if (s.startsWith("\`") && s.endsWith("\`") && s.length >= 2) {
2372
- if (s.slice(1, -1).includes("\`")) {
2373
- strconvErr = "invalid syntax";
2374
- strconvErrKind = 1;
2375
- return "";
2376
- }
2377
- strconvErr = null;
2378
- strconvErrKind = 0;
2379
- return s.slice(1, -1);
2380
- }
2381
- try {
2382
- const v = JSON.parse(s);
2383
- if (typeof v !== "string") {
2384
- strconvErr = "invalid syntax";
2385
- strconvErrKind = 1;
2386
- return "";
2387
- }
2388
- strconvErr = null;
2389
- strconvErrKind = 0;
2390
- return v;
2391
- } catch (e) {
2392
- strconvErr = "invalid syntax";
2393
- strconvErrKind = 1;
2394
- return "";
2395
- }
2396
- },
2397
- "utf8_rune_count": (s) => { let n = 0; for (const _ of (s ?? "")) n++; return n; },
2398
- "utf8_rune_len": (r) => (r < 0 || r > 0x10ffff || (r >= 0xd800 && r <= 0xdfff) ? -1 : r < 0x80 ? 1 : r < 0x800 ? 2 : r < 0x10000 ? 3 : 4),
2399
- "utf8_valid_string": (s) => (s ?? "").isWellFormed ? ((s ?? "").isWellFormed() ? 1 : 0) : (/[\\uD800-\\uDFFF]/.test((s ?? "").replace(/[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]/g, "")) ? 0 : 1),
2400
- "utf8_decode_rune": (s, last) => {
2401
- s = s ?? "";
2402
- if (s.length === 0) { decodeSize = 0; return 0xfffd; }
2403
- let cp;
2404
- if (last) { const tail = s.codePointAt(s.length - 1); cp = (tail >= 0xdc00 && tail <= 0xdfff && s.length >= 2) ? s.codePointAt(s.length - 2) : tail; }
2405
- else cp = s.codePointAt(0);
2406
- if (cp >= 0xd800 && cp <= 0xdfff) { decodeSize = 1; return 0xfffd; }
2407
- // Size is in UTF-16 code units, matching the JS backend (strings are JS strings).
2408
- decodeSize = cp > 0xffff ? 2 : 1;
2409
- return cp;
2410
- },
2411
- "utf8_decode_size": () => decodeSize,
2412
- "utf8_full_rune": (s) => { s = s ?? ""; if (s.length === 0) return 0; const c = s.charCodeAt(0); return (c >= 0xd800 && c <= 0xdbff && s.length < 2) ? 0 : 1; },
2413
- // unicode
2414
- "unicode_is_letter": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{L}/u.test(c)) ? 1 : 0; },
2415
- "unicode_is_digit": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{Nd}/u.test(c)) ? 1 : 0; },
2416
- "unicode_is_space": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /[ \\t\\n\\v\\f\\r\\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]/.test(c)) ? 1 : 0; },
2417
- "unicode_is_upper": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{Lu}/u.test(c)) ? 1 : 0; },
2418
- "unicode_is_lower": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{Ll}/u.test(c)) ? 1 : 0; },
2419
- "unicode_is_punct": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{P}/u.test(c)) ? 1 : 0; },
2420
- "unicode_is_control": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{Cc}/u.test(c)) ? 1 : 0; },
2421
- "unicode_is_print": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && !/\\p{Cc}/u.test(c)) ? 1 : 0; },
2422
- "unicode_is_graphic": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && !/\\p{Cc}/u.test(c)) ? 1 : 0; },
2423
- "unicode_to_upper": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return c ? (c.toUpperCase().codePointAt(0) ?? r) : r; },
2424
- "unicode_to_lower": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return c ? (c.toLowerCase().codePointAt(0) ?? r) : r; },
2067
+ // A partial last line (print / fmt.Print without a newline) is written when
2068
+ // the program ends; Node gets it without the newline console.log adds.
2069
+ const flushPartial = () => {
2070
+ if (lineBuf === "") return;
2071
+ const s = lineBuf;
2072
+ lineBuf = "";
2073
+ started = false;
2074
+ if (typeof process !== "undefined" && process?.stdout?.write) process.stdout.write(s);
2075
+ else write(s);
2425
2076
  };
2077
+ return { "env": env, "Math": m, "__bind": (ex) => { late.exports = ex; }, "__flush": flushPartial };
2426
2078
  }
2427
- function ${P}stdlib_sys(stringTable, io) {
2428
- let splitDir = "", splitFile = "";
2429
- let joinParts = [];
2430
- let matchHasErr = 0;
2431
- let trimStart = 0, trimEnd = 0;
2432
- let timeParseErr = null, timeParseOffset = 0, timeParseDurationErr = null, timeParseSubNs = 0;
2433
- let rawBytes = new Uint8Array(0);
2434
- let rawPos = 0;
2435
- let hexErr = null;
2436
- let base64Err = null;
2437
- let customBase64Encodings = [];
2438
- let bitsRem32 = 0;
2439
- let bitsRem64 = 0n;
2440
- return {
2441
- // html
2442
- "html_escape": (s) => (s ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&#34;").replace(/'/g, "&#39;"),
2443
- "html_unescape": (s) => (s ?? "").replace(/&(?:quot|apos|#39|#34|gt|lt|amp|#(?:[0-9]+|[xX][0-9a-fA-F]+));/g, (m) => {
2444
- switch (m) {
2445
- case "&quot;": case "&#34;": return '"';
2446
- case "&apos;": case "&#39;": return "'";
2447
- case "&gt;": return ">";
2448
- case "&lt;": return "<";
2449
- case "&amp;": return "&";
2450
- default: {
2451
- if (m.startsWith("&#")) {
2452
- const raw = m.slice(2, -1);
2453
- const cp = (raw.startsWith("x") || raw.startsWith("X")) ? parseInt(raw.slice(1), 16) : parseInt(raw, 10);
2454
- return (cp >= 0 && cp <= 0x10FFFF && !(cp >= 0xD800 && cp <= 0xDFFF)) ? String.fromCodePoint(cp) : "\\uFFFD";
2455
- }
2456
- return m;
2457
- }
2458
- }
2459
- }),
2460
- // path
2461
- "path_base": (p) => { if (!p) return "."; const stripped = p.replace(/\\/+$/, ""); if (!stripped) return "/"; const i = stripped.lastIndexOf("/"); return i < 0 ? stripped : stripped.slice(i + 1) || "/"; },
2462
- "path_dir": (p) => { const s = p ?? ""; const i = s.lastIndexOf("/"); if (i < 0) return "."; const res = s.slice(0, i); return res === "" ? "/" : __pathClean(res); },
2463
- "path_ext": (p) => { const s = p ?? ""; const b = s.slice(s.lastIndexOf("/") + 1); const i = b.lastIndexOf("."); return i <= 0 ? "" : b.slice(i); },
2464
- "path_is_abs": (p) => (p ?? "").startsWith("/") ? 1 : 0,
2465
- "path_clean": (p) => __pathClean(p ?? ""),
2466
- "path_split": (p) => {
2467
- const s = p ?? "";
2468
- const i = s.lastIndexOf("/");
2469
- if (i < 0) { splitDir = ""; splitFile = s; }
2470
- else { splitDir = s.slice(0, i + 1); splitFile = s.slice(i + 1); }
2471
- },
2472
- "path_split_dir": () => splitDir,
2473
- "path_split_file": () => splitFile,
2474
- "path_join_init": () => { joinParts = []; },
2475
- "path_join_push": (s) => { if (s) joinParts.push(s); },
2476
- "path_join_run": () => {
2477
- if (joinParts.length === 0) return "";
2478
- const res = __pathClean(joinParts.join("/"));
2479
- joinParts = [];
2480
- return res;
2481
- },
2482
- "path_match": (pat, name) => {
2483
- const r = __pathMatch(pat ?? "", name ?? "");
2484
- matchHasErr = r[1] ? 1 : 0;
2485
- return r[0] ? 1 : 0;
2486
- },
2487
- "path_match_has_err": () => matchHasErr,
2488
- // bytes trim
2489
- "bytes_trim": (s, cutset, left, right) => {
2490
- const cs = new Set(Array.from(cutset ?? ""));
2491
- const chars = Array.from(s ?? "");
2492
- let a = 0, b = chars.length;
2493
- if (left) while (a < b && cs.has(chars[a])) a++;
2494
- if (right) while (b > a && cs.has(chars[b - 1])) b--;
2495
- const enc = new TextEncoder();
2496
- trimStart = a === 0 ? 0 : enc.encode(chars.slice(0, a).join("")).length;
2497
- trimEnd = b === chars.length ? enc.encode(s ?? "").length : enc.encode(chars.slice(0, b).join("")).length;
2498
- },
2499
- "bytes_trim_start": () => trimStart,
2500
- "bytes_trim_end": () => trimEnd,
2501
- // time
2502
- "time_now_ms": () => (typeof globalThis.__GOFRONT_CLOCK === "function" ? globalThis.__GOFRONT_CLOCK() : Date.now()),
2503
- "time_timezone_offset": (ms) => -new Date(ms).getTimezoneOffset() * 60,
2504
- "time_duration_string": (d) => __durationString(d),
2505
- "time_parse_duration": (s) => {
2506
- const res = __timeParseDuration(s);
2507
- if (res[1]) {
2508
- timeParseDurationErr = res[1].message || String(res[1]);
2509
- return 0n;
2510
- }
2511
- timeParseDurationErr = null;
2512
- return BigInt(res[0]);
2513
- },
2514
- "time_parse_duration_err": () => timeParseDurationErr,
2515
- "time_month_string": (m) => __monthString(m),
2516
- "time_weekday_string": (w) => __weekdayString(w),
2517
- "time_parse": (layout, val) => {
2518
- try {
2519
- let s = String(val ?? "");
2520
- if (s.length >= 2 && s.startsWith('"') && s.endsWith('"')) {
2521
- s = s.slice(1, -1);
2522
- }
2523
- const d = new Date(s);
2524
- if (isNaN(d.getTime())) throw 0;
2525
- let subNs = 0;
2526
- const m = /\\.([0-9]{1,9})/.exec(s);
2527
- if (m) {
2528
- subNs = Number(m[1].padEnd(9, "0")) % 1000000;
2529
- }
2530
- timeParseErr = null;
2531
- if (s.endsWith("Z") || s.endsWith("z")) {
2532
- timeParseOffset = 0;
2533
- } else {
2534
- const tzMatch = /([+-])(\\d{2}):?(\\d{2})$/.exec(s);
2535
- if (tzMatch) {
2536
- const sign = tzMatch[1] === "+" ? 1 : -1;
2537
- timeParseOffset = sign * (Number(tzMatch[2]) * 3600 + Number(tzMatch[3]) * 60);
2538
- } else {
2539
- timeParseOffset = -d.getTimezoneOffset() * 60;
2540
- }
2541
- }
2542
- timeParseSubNs = subNs;
2543
- return d.getTime();
2544
- } catch {
2545
- timeParseErr = "parsing time: cannot parse";
2546
- timeParseOffset = 0;
2547
- timeParseSubNs = 0;
2548
- return 0;
2549
- }
2550
- },
2551
- "time_parse_err": () => timeParseErr,
2552
- "time_parse_offset": () => timeParseOffset,
2553
- "time_parse_sub_ns": () => timeParseSubNs,
2554
- "time_format": (y, m, d, hr, min, sec, wd, ms, offsetSec, layout) => {
2555
- const DAYS = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];
2556
- const MONTHS = ["January","February","March","April","May","June","July","August","September","October","November","December"];
2557
- const pad = (n, w = 2) => String(n).padStart(w, "0");
2558
- return (layout ?? "").replace(/2006|06|Monday|Mon|January|Jan|01|02|15|PM|pm|03|04|05|Z07:00|-07:00|-0700|MST|\\.(?:0+|9+)|1|2|3|4|5/g, t => {
2559
- if (t.startsWith(".")) {
2560
- const isZeroPad = t.includes("0");
2561
- const maxLen = t.length - 1;
2562
- let frac = pad(ms % 1000000000, 9);
2563
- if (maxLen <= 9) frac = frac.slice(0, maxLen);
2564
- else frac = frac.padEnd(maxLen, "0");
2565
- if (!isZeroPad) {
2566
- frac = frac.replace(/0+$/, "");
2567
- return frac.length > 0 ? "." + frac : "";
2568
- }
2569
- return "." + frac;
2570
- }
2571
- switch (t) {
2572
- case "2006": return String(y);
2573
- case "06": return String(y).slice(-2);
2574
- case "Monday": return DAYS[wd];
2575
- case "Mon": return DAYS[wd].slice(0, 3);
2576
- case "January": return MONTHS[m - 1];
2577
- case "Jan": return MONTHS[m - 1].slice(0, 3);
2578
- case "01": return pad(m);
2579
- case "1": return String(m);
2580
- case "02": return pad(d);
2581
- case "2": return String(d);
2582
- case "15": return pad(hr);
2583
- case "PM": return hr < 12 ? "AM" : "PM";
2584
- case "pm": return hr < 12 ? "am" : "pm";
2585
- case "03": return pad(hr % 12 || 12);
2586
- case "3": return String(hr % 12 || 12);
2587
- case "04": return pad(min);
2588
- case "4": return String(min);
2589
- case "05": return pad(sec);
2590
- case "5": return String(sec);
2591
- case "Z07:00": {
2592
- const o = Math.floor(offsetSec / 60);
2593
- return o === 0 ? "Z" : (o > 0 ? "+" : "-") + pad(Math.floor(Math.abs(o) / 60)) + ":" + pad(Math.abs(o) % 60);
2594
- }
2595
- case "-07:00": {
2596
- const o = Math.floor(offsetSec / 60);
2597
- return (o >= 0 ? "+" : "-") + pad(Math.floor(Math.abs(o) / 60)) + ":" + pad(Math.abs(o) % 60);
2598
- }
2599
- case "-0700": {
2600
- const o = Math.floor(offsetSec / 60);
2601
- return (o >= 0 ? "+" : "-") + pad(Math.floor(Math.abs(o) / 60)) + pad(Math.abs(o) % 60);
2602
- }
2603
- case "MST": return offsetSec === 0 ? "UTC" : "LOCAL";
2604
- default: return t;
2605
- }
2606
- });
2607
- },
2608
- "raw_bytes_init": (len) => {
2609
- rawBytes = new Uint8Array(len);
2610
- rawPos = 0;
2611
- },
2612
- "raw_bytes_push": (b) => {
2613
- if (rawPos < rawBytes.length) {
2614
- rawBytes[rawPos++] = b & 0xff;
2615
- }
2616
- },
2617
- "raw_bytes_len": () => rawBytes.length,
2618
- "raw_bytes_get": (i) => rawBytes[i] ?? 0,
2619
- "crypto_rand_fill": (len) => {
2620
- rawBytes = new Uint8Array(len);
2621
- if (typeof crypto !== "undefined" && crypto.getRandomValues) {
2622
- const CHUNK = 65536;
2623
- for (let offset = 0; offset < len; offset += CHUNK) {
2624
- crypto.getRandomValues(rawBytes.subarray(offset, Math.min(offset + CHUNK, len)));
2625
- }
2626
- } else {
2627
- for (let i = 0; i < len; i++) rawBytes[i] = Math.floor(Math.random() * 256);
2628
- }
2629
- return len;
2630
- },
2631
- "crypto_sha256_sum": () => {
2632
- const d = __sha256.Sum256(rawBytes);
2633
- rawBytes = new Uint8Array(d);
2634
- return 32;
2635
- },
2636
- "hex_encode_to_string": () => {
2637
- return __hex_encode_to_string(rawBytes);
2638
- },
2639
- "hex_decode_string": (s) => {
2640
- const [res, err] = __hex_decode_string(s ?? "");
2641
- hexErr = err ? (err.message ?? String(err)) : null;
2642
- rawBytes = new Uint8Array(res);
2643
- return rawBytes.length;
2644
- },
2645
- "hex_get_err": () => hexErr,
2646
- "base64_new_encoding": (alphabet) => {
2647
- const id = customBase64Encodings.length + 4;
2648
- customBase64Encodings.push(__base64_make(alphabet ?? "", "="));
2649
- return id;
2650
- },
2651
- "base64_encode_to_string": (mode) => {
2652
- const enc = mode === 1 ? __base64_url : mode === 2 ? __base64_raw_std : mode === 3 ? __base64_raw_url : mode >= 4 ? customBase64Encodings[mode - 4] : __base64_std;
2653
- return (enc ?? __base64_std).EncodeToString(rawBytes);
2654
- },
2655
- "base64_decode_string": (mode, s) => {
2656
- const enc = mode === 1 ? __base64_url : mode === 2 ? __base64_raw_std : mode === 3 ? __base64_raw_url : mode >= 4 ? customBase64Encodings[mode - 4] : __base64_std;
2657
- const [res, err] = (enc ?? __base64_std).DecodeString(s ?? "");
2658
- base64Err = err ? (err.message ?? String(err)) : null;
2659
- rawBytes = new Uint8Array(res);
2660
- return rawBytes.length;
2661
- },
2662
- "base64_get_err": () => base64Err,
2663
- "bits_div32": (hi, lo, y) => {
2664
- const yu = y >>> 0;
2665
- if (yu === 0) throw new Error("runtime error: integer divide by zero");
2666
- const hiu = hi >>> 0;
2667
- if (hiu >= yu) throw new Error("runtime error: integer overflow");
2668
- const z = (BigInt(hiu) << 32n) | BigInt(lo >>> 0);
2669
- const yB = BigInt(yu);
2670
- bitsRem32 = Number(z % yB);
2671
- return Number(z / yB);
2672
- },
2673
- "bits_div32_rem": () => bitsRem32,
2674
- "bits_rem32": (hi, lo, y) => {
2675
- const yu = y >>> 0;
2676
- if (yu === 0) throw new Error("runtime error: integer divide by zero");
2677
- const z = (BigInt(hi >>> 0) << 32n) | BigInt(lo >>> 0);
2678
- return Number(z % BigInt(yu));
2679
- },
2680
- "bits_div64": (hi, lo, y) => {
2681
- const yB = BigInt.asUintN(64, BigInt(y));
2682
- if (yB === 0n) throw new Error("runtime error: integer divide by zero");
2683
- const hiB = BigInt.asUintN(64, BigInt(hi));
2684
- if (hiB >= yB) throw new Error("runtime error: integer overflow");
2685
- const loB = BigInt.asUintN(64, BigInt(lo));
2686
- const z = (hiB << 64n) | loB;
2687
- bitsRem64 = z % yB;
2688
- return z / yB;
2689
- },
2690
- "bits_div64_rem": () => bitsRem64,
2691
- "bits_rem64": (hi, lo, y) => {
2692
- const yB = BigInt.asUintN(64, BigInt(y));
2693
- if (yB === 0n) throw new Error("runtime error: integer divide by zero");
2694
- const z = ((BigInt.asUintN(64, BigInt(hi)) % yB) << 64n) | BigInt.asUintN(64, BigInt(lo));
2695
- return z % yB;
2696
- },
2697
- };
2698
- }
2699
- function ${P}stdlib(stringTable, io) {
2700
- return Object.assign(
2701
- {},
2702
- ${P}stdlib_fmt(stringTable, io),
2703
- ${P}stdlib_str(stringTable, io),
2704
- ${P}stdlib_sys(stringTable, io),
2705
- );
2706
- }
2079
+ // string -> bytes: UTF-8, except that a lone surrogate U+DC80-U+DCFF (a raw
2080
+ // byte from a \\xNN escape in the source) encodes as that single byte.
2081
+ function ${P}u8enc(s) {
2082
+ const e = new TextEncoder();
2083
+ if (s.isWellFormed()) return e.encode(s);
2084
+ const out = [];
2085
+ for (let i = 0; i < s.length; i++) {
2086
+ const c = s.charCodeAt(i);
2087
+ if (c >= 0xdc80 && c <= 0xdcff && !(i > 0 && (s.charCodeAt(i - 1) & 0xfc00) === 0xd800)) { out.push(c - 0xdc00); continue; }
2088
+ const cp = s.codePointAt(i);
2089
+ if (cp > 0xffff) i++;
2090
+ for (const b of e.encode(String.fromCodePoint(cp))) out.push(b);
2091
+ }
2092
+ return Uint8Array.from(out);
2093
+ }
2094
+ // strings.ToUpper/ToLower: letter by letter through the first code point of
2095
+ // the host's mapping, as unicode.ToUpper/ToLower do; ASCII text maps whole.
2096
+ function ${P}mapCase(s, method) {
2097
+ if (/^[\\x00-\\x7f]*$/.test(s)) return s[method]();
2098
+ return Array.from(s, (c) => {
2099
+ const m = c[method]();
2100
+ const r = String.fromCodePoint(m.codePointAt(0));
2101
+ // A letter whose upper case spans several code points (ß) stays as is.
2102
+ return method === "toUpperCase" && m.length > r.length ? c : r;
2103
+ }).join("");
2104
+ }
2105
+ // Code-point-aware Trim/TrimLeft/TrimRight of strings.
2707
2106
  function ${P}trim(s, cutset, left, right) {
2708
- const cs = new Set(Array.from(cutset));
2709
- const chars = Array.from(s);
2710
- let a = 0, b = chars.length;
2711
- if (left) while (a < b && cs.has(chars[a])) a++;
2712
- if (right) while (b > a && cs.has(chars[b - 1])) b--;
2713
- return chars.slice(a, b).join("");
2107
+ if (s === "" || cutset === "") return s;
2108
+ const set = new Set(cutset);
2109
+ let i = 0, j = s.length;
2110
+ if (left) while (i < j) {
2111
+ const c = s.codePointAt(i);
2112
+ if (!set.has(String.fromCodePoint(c))) break;
2113
+ i += c > 0xFFFF ? 2 : 1;
2114
+ }
2115
+ if (right) while (j > i) {
2116
+ let k = j - 1;
2117
+ const lo = s.charCodeAt(k);
2118
+ if (lo >= 0xDC00 && lo <= 0xDFFF && k > i) {
2119
+ const hi = s.charCodeAt(k - 1);
2120
+ if (hi >= 0xD800 && hi <= 0xDBFF) k--;
2121
+ }
2122
+ if (!set.has(s.slice(k, j))) break;
2123
+ j = k;
2124
+ }
2125
+ return s.slice(i, j);
2126
+ }
2127
+ // strings.Replace: an empty old string matches before every code point and at
2128
+ // the end; the replacement is literal.
2129
+ function ${P}replace(s, o, w, n) {
2130
+ if (o === w || n === 0) return s;
2131
+ if (o !== "") {
2132
+ if (n < 0) return s.replaceAll(o, () => w);
2133
+ let r = "", i = 0;
2134
+ for (let k = n; k > 0; k--) {
2135
+ const j = s.indexOf(o, i);
2136
+ if (j < 0) break;
2137
+ r += s.slice(i, j) + w;
2138
+ i = j + o.length;
2139
+ }
2140
+ return r + s.slice(i);
2141
+ }
2142
+ const cs = [...s];
2143
+ const m = n < 0 || n > cs.length + 1 ? cs.length + 1 : n;
2144
+ let r = "";
2145
+ for (let k = 0; k < m; k++) r += (k > 0 ? cs[k - 1] : "") + w;
2146
+ return r + cs.slice(m - 1).join("");
2714
2147
  }`;
2715
2148
 
2716
2149
  // Runtime loader emitted at the top of the facade.
2717
- const wasmLoaderJs = (stdlibEnv) => `${WASM_IMPORTS_JS}
2718
- ${stdlibEnv ? WASM_STDLIB_JS : ""}
2150
+ const wasmLoaderJs = `${WASM_IMPORTS_JS}
2719
2151
  async function ${P}fetch(url) {
2720
2152
  const res = await fetch(url);
2721
2153
  if (!res.ok) throw new Error("GoFront: failed to fetch " + url + " (" + res.status + ")");
@@ -2732,17 +2164,19 @@ async function ${P}instantiate(imports) {
2732
2164
  }
2733
2165
  return WebAssembly.instantiate(await ${P}fetch(url), imports);
2734
2166
  }
2167
+ let ${P}flushOut = () => {};
2735
2168
  async function ${P}load(stringTable, extraEnv) {
2736
2169
  if (typeof WebAssembly === "undefined" || typeof WebAssembly.Tag !== "function") {
2737
2170
  throw new Error("GoFront: this runtime lacks the WebAssembly GC / exception support required by app.wasm");
2738
2171
  }
2739
2172
  const tag = new WebAssembly.Tag({ "parameters": ["externref"] });
2740
- const imports = ${P}imports(stringTable, extraEnv, tag, (s) => console.log(s));
2173
+ const imports = ${P}imports(stringTable, extraEnv, tag, (s) => console.log(s), true);
2741
2174
  // Panics arrive as plain JS Errors thrown by env.panic (also from the
2742
2175
  // start function while package-level initializers run), so exports are
2743
2176
  // returned raw: no try/catch wrapper, which keeps JS→wasm calls inlinable.
2744
2177
  const result = await ${P}instantiate(imports);
2745
2178
  imports.__bind(result.instance.exports);
2179
+ ${P}flushOut = imports.__flush;
2746
2180
  return result.instance.exports;
2747
2181
  }
2748
2182
  // Go int/int64 cross the boundary as f64 (exact within the safe-integer range).
@@ -2805,10 +2239,10 @@ function facadeName(name) {
2805
2239
 
2806
2240
  export function generateFacade(
2807
2241
  meta,
2808
- { stringTable = [], callMain = false, stdlibEnv = true } = {},
2242
+ { stringTable = [], callMain = false } = {},
2809
2243
  ) {
2810
2244
  const needs = meta._needs ?? collectNeeds(meta);
2811
- const out = [wasmLoaderJs(stdlibEnv)];
2245
+ const out = [wasmLoaderJs];
2812
2246
 
2813
2247
  // JS callback imports (registered before instantiation; bodies run after).
2814
2248
  out.push(`const ${P}env = {};`);
@@ -2852,7 +2286,7 @@ export function generateFacade(
2852
2286
  }
2853
2287
  const sig = ["fn", ...names].join(", ");
2854
2288
  out.push(
2855
- `${P}env["__invoke$${key}"] = (${sig}) => { try { ${body} } catch (__e) { if (__w && __w.__push_panic) __w.__push_panic(__e.message ?? String(__e)); throw __e; } };`,
2289
+ `${P}env["__invoke$${key}"] = (${sig}) => { try { ${body} } catch (__e) { if (__w && __w.__push_panic && !(__e && __e.__gfp === true)) { __w.__push_panic(__e.message ?? String(__e)); try { __e.__gfp = true; } catch {} } throw __e; } };`,
2856
2290
  );
2857
2291
  }
2858
2292
  out.push(
@@ -2863,9 +2297,11 @@ export function generateFacade(
2863
2297
  if (info.pkgTarget === "both") genBothStruct(out, name, fields);
2864
2298
  else {
2865
2299
  const methods = meta.structByName.get(name)?.methods ?? [];
2866
- genHandleStruct(out, name, fields, methods);
2300
+ const promoted = promotedMethods(info, fields, methods, meta);
2301
+ genHandleStruct(out, name, fields, methods, promoted);
2867
2302
  }
2868
2303
  }
2304
+ genGenericClasses(out, needs, meta._mod);
2869
2305
 
2870
2306
  for (const [name, desc] of needs.anonStructs) {
2871
2307
  out.push(`function ${P}to_${name}(o) {
@@ -2947,6 +2383,15 @@ function ${P}slview_${key}(s) {
2947
2383
  } `);
2948
2384
  }
2949
2385
 
2386
+ if (needs.maps.size > 0) {
2387
+ // GoFront JS code reads a map as a plain object (`m["a"]`, `len(m)`), so
2388
+ // each entry is also an own property of the returned Map unless that name
2389
+ // is one the Map already has.
2390
+ out.push(`function ${P}mapprop(m, k, v) {
2391
+ const p = String(k);
2392
+ if (!(p in m)) m[p] = v;
2393
+ }`);
2394
+ }
2950
2395
  for (const [key, desc] of needs.maps) {
2951
2396
  out.push(`function ${P}mapin_${key}(m) {
2952
2397
  if (m == null) return null;
@@ -2966,6 +2411,7 @@ function ${P}mapout_${key}(m) {
2966
2411
  if (ks) {
2967
2412
  for (let i = 0; i < ks.length; i++) {
2968
2413
  out.set(ks[i], vs[i]);
2414
+ ${P}mapprop(out, ks[i], vs[i]);
2969
2415
  }
2970
2416
  }
2971
2417
  return out;
@@ -3093,28 +2539,6 @@ function ${P}errin(e) {
3093
2539
  }`);
3094
2540
  }
3095
2541
 
3096
- if (needs.time) {
3097
- out.push(`function ${P}timeout(r) {
3098
- if (r == null) return null;
3099
- const ms = __w.__time_unix_milli ? __w.__time_unix_milli(r) : 0;
3100
- const off = __w.__time_offset ? __w.__time_offset(r) : 0;
3101
- return { _d: new Date(ms), _offset: off };
3102
- }
3103
- function ${P}timeout_ptr(r) {
3104
- return ${P}timeout(r);
3105
- }
3106
- function ${P}timein(v) {
3107
- if (v == null) return null;
3108
- const d = v._d ? v._d : (v instanceof Date ? v : new Date(v));
3109
- const ms = d.getTime();
3110
- const off = typeof v?._offset === "number" ? v._offset : -d.getTimezoneOffset() * 60;
3111
- return __w.__time_make ? __w.__time_make(ms, off) : null;
3112
- }
3113
- function ${P}timein_ptr(v) {
3114
- return ${P}timein(v);
3115
- }`);
3116
- }
3117
-
3118
2542
  if (needs.shared) {
3119
2543
  // Zero-copy TypedArray views over the exported memory. Memory never
3120
2544
  // grows after the start function (allocation is startup-only), but the
@@ -3163,7 +2587,7 @@ function ${P}shin(v, Ctor) {
3163
2587
  );
3164
2588
  }
3165
2589
 
3166
- if (callMain) out.push("__w.main();");
2590
+ if (callMain) out.push(`try { __w.main(); } finally { ${P}flushOut(); }`);
3167
2591
  return bindExports(out.join("\n"));
3168
2592
  }
3169
2593
 
@@ -3214,8 +2638,26 @@ function genBothStruct(out, name, fields) {
3214
2638
  const got = `__w.__get_${name}_${f.name}(this.__ref)`;
3215
2639
  return `"${f.name}": { "enumerable": true, "get"() { return ${viewExpr(f.desc, got)}; }, "set"(v) { __w.__set_${name}_${f.name}(this.__ref, ${inExpr(f.desc, "v")}); } }`;
3216
2640
  });
2641
+ // The one conversion `time.Time` has beyond the ordinary struct rules: JS
2642
+ // code may pass a Date (or an object holding one in `_d`), and a Time
2643
+ // handed to JS also reads as a Date through `_d`.
2644
+ const isTime = name === "Time$$time";
2645
+ if (isTime)
2646
+ out.push(`function ${P}time_in(o) {
2647
+ const d = o instanceof Date ? o : o?._d instanceof Date ? o._d : null;
2648
+ if (d === null) return o;
2649
+ const ms = d.getTime();
2650
+ const off = typeof o._offset === "number" ? o._offset : -d.getTimezoneOffset() * 60;
2651
+ const sec = Math.floor(ms / 1000);
2652
+ return new Time$$time(BigInt(sec) + 62135596800n, (ms - sec * 1000) * 1000000, new Location$$time("", off, 0));
2653
+ }
2654
+ function ${P}time_out(t) {
2655
+ Object.defineProperty(t, "_d", { configurable: true, get() { return new Date(Number(this.sec - 62135596800n) * 1000 + Math.floor(this.nsec / 1000000)); } });
2656
+ Object.defineProperty(t, "_offset", { configurable: true, get() { return this.loc ? this.loc.offset : 0; } });
2657
+ return t;
2658
+ }`);
3217
2659
  out.push(`function ${P}to_${name}(o, isValue, m) {
3218
- if (o == null) return isValue ? __w.__zero_${name}() : null;
2660
+ ${isTime ? `\to = ${P}time_in(o);\n` : ""} if (o == null) return isValue ? __w.__zero_${name}() : null;
3219
2661
  if (m) { const hit = m.get(o); if (hit) return hit; }
3220
2662
  const r = __w.__new_${name}(${toArgs.join(", ")});
3221
2663
  if (m) m.set(o, r);
@@ -3223,7 +2665,7 @@ function genBothStruct(out, name, fields) {
3223
2665
  }
3224
2666
  function ${P}from_${name}(r) {
3225
2667
  if (r == null) return null;
3226
- return new ${name}(${fromArgs.join(", ")});
2668
+ return ${isTime ? `${P}time_out(new ${name}(${fromArgs.join(", ")}))` : `new ${name}(${fromArgs.join(", ")})`};
3227
2669
  }
3228
2670
  // Live views share one prototype and are cached per wasm object so hot paths
3229
2671
  // (\`h.Pos.X = 1\` in a frame loop) do not allocate a wrapper per access. The
@@ -3282,7 +2724,84 @@ function ${P}ifin_${key}(v) {
3282
2724
  }`);
3283
2725
  }
3284
2726
 
3285
- function genHandleStruct(out, name, fields, methods) {
2727
+ // Methods a struct gets from its embedded structs, shallowest first (a name
2728
+ // found twice at one depth is ambiguous and left out). Each is forwarded
2729
+ // through the embedded field's getter: `{ name, via, params, variadic }`.
2730
+ function promotedMethods(info, fields, methods, meta) {
2731
+ const taken = new Set([
2732
+ ...methods.map((m) => m.name),
2733
+ ...fields.map((f) => f.name),
2734
+ ]);
2735
+ const promoted = [];
2736
+ const seen = new Set([info]);
2737
+ let level = [{ info, via: "" }];
2738
+ while (level.length > 0) {
2739
+ const next = [];
2740
+ const found = new Map();
2741
+ for (const { info: owner, via } of level) {
2742
+ for (const e of owner.embeds ?? []) {
2743
+ const typeName = e.typeName ?? e.name;
2744
+ const path = `${via}.${e.name}`;
2745
+ const inner = meta._mod.getStructType(typeName);
2746
+ const innerMethods =
2747
+ meta.structByName.get(typeName.replaceAll(".", "$"))?.methods ?? [];
2748
+ for (const m of innerMethods) {
2749
+ if (!found.has(m.name)) found.set(m.name, []);
2750
+ found.get(m.name).push({
2751
+ name: m.name,
2752
+ via: path,
2753
+ params: m.params,
2754
+ variadic: m.variadic,
2755
+ });
2756
+ }
2757
+ if (inner && !seen.has(inner)) {
2758
+ seen.add(inner);
2759
+ next.push({ info: inner, via: path });
2760
+ }
2761
+ }
2762
+ }
2763
+ for (const [n, c] of found) {
2764
+ if (!taken.has(n) && c.length === 1) promoted.push(c[0]);
2765
+ taken.add(n);
2766
+ }
2767
+ level = next;
2768
+ }
2769
+ return promoted;
2770
+ }
2771
+
2772
+ // A generic struct is built in a js main as `new Box(V, [...zeroFactories,
2773
+ // "int"])`; the facade only has the instances (`Box$int`). `Box` picks the
2774
+ // instance named by the last type-argument entry (the printed type arguments).
2775
+ function genGenericClasses(out, needs, mod) {
2776
+ const groups = new Map();
2777
+ for (const [name, { info }] of needs.structs) {
2778
+ if (info.pkgTarget === "both") continue;
2779
+ const goName = mod?.checker?.types?.get(info.name)?._goName;
2780
+ const m = goName ? /^([^[]+)\[(.*)\]$/s.exec(goName) : null;
2781
+ if (!m) continue;
2782
+ if (!groups.has(m[1])) groups.set(m[1], new Map());
2783
+ // The js caller keys instances by the unqualified printed arguments.
2784
+ groups
2785
+ .get(m[1])
2786
+ .set(m[2].replaceAll(" ", "").replace(/(?<![\w.])main\./g, ""), name);
2787
+ }
2788
+ for (const [base, instances] of groups) {
2789
+ const table = [...instances]
2790
+ .map(([id, cls]) => `${JSON.stringify(id)}: ${cls}`)
2791
+ .join(", ");
2792
+ out.push(`class ${facadeName(base)} {
2793
+ constructor(...a) {
2794
+ const tz = a.at(-1);
2795
+ const id = Array.isArray(tz) && typeof tz.at(-1) === "string" ? tz.at(-1).replaceAll(" ", "") : undefined;
2796
+ const C = id === undefined ? ${instances.size === 1 ? `${[...instances.values()][0]}` : "undefined"} : { ${table} }[id];
2797
+ if (!C) throw new TypeError("GoFront: no ${base} instance for the type arguments " + id + " in the wasm package");
2798
+ return new C(...(Array.isArray(tz) ? a.slice(0, -1) : a));
2799
+ }
2800
+ }`);
2801
+ }
2802
+ }
2803
+
2804
+ function genHandleStruct(out, name, fields, methods, promoted = []) {
3286
2805
  // `$`-suffixed params keep the mangler from renaming the matching getters.
3287
2806
  const ctorParams = fields.map((f) => `${f.name}$ = ${zeroExpr(f.desc)}`);
3288
2807
  const ctorArgs = fields.map((f) => inExpr(f.desc, `${f.name}$`));
@@ -3333,6 +2852,12 @@ class ${name} {
3333
2852
  `\t${m.name}(${restParams(names, m.variadic)}) {\n${indent(body, "\t")}\n\t}`,
3334
2853
  );
3335
2854
  }
2855
+ for (const m of promoted) {
2856
+ const names = m.params.map((_, i) => `a${i}`);
2857
+ lines.push(
2858
+ `\t${m.name}(${restParams(names, m.variadic)}) { return this${m.via}.${m.name}(${restParams(names, m.variadic)}); }`,
2859
+ );
2860
+ }
3336
2861
  lines.push("}");
3337
2862
  out.push(lines.join("\n"));
3338
2863
  }