gofront 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +35 -39
  2. package/package.json +37 -4
  3. package/src/backend/js/expressions.js +559 -144
  4. package/src/backend/js/index.js +174 -49
  5. package/src/backend/js/runtime.js +1518 -54
  6. package/src/backend/js/statements.js +91 -73
  7. package/src/backend/js/stdlib/base64.js +12 -0
  8. package/src/backend/js/stdlib/binary.js +102 -0
  9. package/src/backend/js/stdlib/bits.js +15 -1
  10. package/src/backend/js/stdlib/builder.js +28 -9
  11. package/src/backend/js/stdlib/bytes.js +75 -18
  12. package/src/backend/js/stdlib/errors.js +25 -1
  13. package/src/backend/js/stdlib/fmt.js +70 -13
  14. package/src/backend/js/stdlib/hex.js +17 -0
  15. package/src/backend/js/stdlib/html.js +1 -1
  16. package/src/backend/js/stdlib/index.js +28 -0
  17. package/src/backend/js/stdlib/io.js +105 -3
  18. package/src/backend/js/stdlib/json.js +972 -0
  19. package/src/backend/js/stdlib/math.js +13 -1
  20. package/src/backend/js/stdlib/path.js +18 -4
  21. package/src/backend/js/stdlib/rand.js +10 -5
  22. package/src/backend/js/stdlib/sha256.js +12 -0
  23. package/src/backend/js/stdlib/simd.js +24 -0
  24. package/src/backend/js/stdlib/slices.js +52 -26
  25. package/src/backend/js/stdlib/strconv.js +21 -5
  26. package/src/backend/js/stdlib/strings.js +148 -15
  27. package/src/backend/js/stdlib/sync.js +13 -0
  28. package/src/backend/js/stdlib/time.js +90 -19
  29. package/src/backend/js/stdlib/unicode.js +2 -2
  30. package/src/backend/wasm/boundary.js +1225 -41
  31. package/src/backend/wasm/const-fold.js +205 -0
  32. package/src/backend/wasm/emit-builtins.js +220 -76
  33. package/src/backend/wasm/emit-exprs.js +475 -276
  34. package/src/backend/wasm/emit-maps.js +144 -8
  35. package/src/backend/wasm/emit-stdlib.js +12 -3005
  36. package/src/backend/wasm/emit-stmts.js +264 -270
  37. package/src/backend/wasm/emit.js +124 -87
  38. package/src/backend/wasm/encode.js +140 -29
  39. package/src/backend/wasm/index.js +1001 -32
  40. package/src/backend/wasm/optimize.js +17 -6
  41. package/src/backend/wasm/stdlib/bytes.js +5039 -0
  42. package/src/backend/wasm/stdlib/core.js +8903 -0
  43. package/src/backend/wasm/stdlib/index.js +43 -0
  44. package/src/backend/wasm/stdlib/io.js +2161 -0
  45. package/src/backend/wasm/stdlib/packages.js +2400 -0
  46. package/src/backend/wasm/stdlib/pure.js +720 -0
  47. package/src/backend/wasm/stdlib/simd.js +234 -0
  48. package/src/backend/wasm/stdlib/time.js +1200 -0
  49. package/src/backend/wasm/types.js +174 -1
  50. package/src/backend/wasm/wat.js +37 -0
  51. package/src/cli-core.js +1 -1
  52. package/src/compiler.js +18 -4
  53. package/src/dts-parser.js +150 -140
  54. package/src/index.js +2 -6
  55. package/src/lexer.js +80 -29
  56. package/src/lower/boxing.js +1 -7
  57. package/src/lower/captures.js +2 -6
  58. package/src/lower/embedding.js +2 -1
  59. package/src/lower/ownership.js +30 -9
  60. package/src/minifier.js +20 -68
  61. package/src/parser/declarations.js +3 -6
  62. package/src/parser/expressions.js +3 -8
  63. package/src/parser/index.js +18 -3
  64. package/src/parser/statements.js +25 -38
  65. package/src/parser/struct-tag.js +144 -0
  66. package/src/parser/types.js +64 -8
  67. package/src/resolver.js +2 -27
  68. package/src/templ-lexer.js +29 -63
  69. package/src/templ-parser.js +22 -45
  70. package/src/typechecker/assignability.js +126 -13
  71. package/src/typechecker/expressions.js +168 -16
  72. package/src/typechecker/index.js +35 -8
  73. package/src/typechecker/resolve.js +187 -8
  74. package/src/typechecker/statements.js +68 -9
  75. package/src/typechecker/stdlib/core.js +280 -89
  76. package/src/typechecker/stdlib/extended.js +490 -1
  77. package/src/typechecker/stdlib/simd.js +73 -0
  78. package/src/typechecker/stdlib.js +2 -0
  79. package/src/typechecker/types.js +362 -9
  80. package/src/vendor.js +86 -11
@@ -34,8 +34,22 @@
34
34
  // - `*testing.T`: the harness's JS test object, passed as an opaque
35
35
  // externref (test methods are routed back to JS via `env.testing_*`).
36
36
 
37
- import { HELPER_SPRINTF, HELPER_STRCONV } from "../js/runtime.js";
38
- import { getSharedInfo, isTestingT } from "./types.js";
37
+ import {
38
+ HELPER_BASE64,
39
+ HELPER_DURATION,
40
+ HELPER_ERROR,
41
+ HELPER_HEX,
42
+ HELPER_PATH_CLEAN,
43
+ HELPER_SHA256,
44
+ HELPER_SPRINTF,
45
+ HELPER_STRCONV,
46
+ } from "../js/runtime.js";
47
+ import {
48
+ anonIfaceName,
49
+ getInterfaceMethodList,
50
+ getSharedInfo,
51
+ isTestingT,
52
+ } from "./types.js";
39
53
 
40
54
  // ── Type classification ──────────────────────────────────────
41
55
 
@@ -94,6 +108,14 @@ function classifyType(goType, mod) {
94
108
  if (isTestingT(goType)) return { k: "extern", wType: "externref" };
95
109
 
96
110
  if (goType.kind === "TypeName" || goType.kind === "Ident") {
111
+ if (goType.name === "error") return { k: "error", wType: "anyref" };
112
+ if (goType.name === "time.Duration" || goType.name === "Duration")
113
+ return { k: "i64", unsigned: false, wType: "i64" };
114
+ if (goType.name === "time.Time" || goType.name === "Time") {
115
+ const info = mod.getStructType("time.Time");
116
+ if (info)
117
+ return { k: "time", name: "time.Time", info, wType: toRef(info) };
118
+ }
97
119
  const info = mod.getStructType(goType.name);
98
120
  if (info) return structDesc(info, false);
99
121
  const resolved = mod.checker?.types?.get(goType.name);
@@ -117,10 +139,19 @@ function classifyType(goType, mod) {
117
139
  return classifySlice(t, mod);
118
140
  case "array":
119
141
  return classifyArray(t, mod);
142
+ case "map":
143
+ return classifyMap(t, mod);
144
+ case "struct":
145
+ return classifyStruct(t, mod);
120
146
  case "func":
121
147
  return classifyFunc(t, mod);
122
148
  case "interface":
123
149
  return classifyInterface(t, mod);
150
+ case "simd":
151
+ return {
152
+ k: "unsupported",
153
+ what: "simd.F32x4 cannot cross the JS↔WASM boundary in 1.7.0 (use shared.Float32 instead)",
154
+ };
124
155
  default:
125
156
  return {
126
157
  k: "unsupported",
@@ -130,11 +161,6 @@ function classifyType(goType, mod) {
130
161
  }
131
162
 
132
163
  const UNSUPPORTED_KINDS = {
133
- map: "map",
134
- MapType: "map",
135
- struct: "anonymous struct",
136
- StructType: "anonymous struct",
137
- InterfaceType: "interface",
138
164
  ChanType: "chan",
139
165
  chan: "chan",
140
166
  };
@@ -149,13 +175,21 @@ function normalizeAstType(t) {
149
175
  return { kind: "slice", elem: t.elem };
150
176
  case "ArrayType":
151
177
  return { kind: "array", elem: t.elem, size: t.size };
178
+ case "MapType":
179
+ return { kind: "map", key: t.key, val: t.value ?? t.elem };
152
180
  case "FuncType":
153
181
  return funcSigFromAst(t);
154
182
  case "InterfaceType":
155
183
  return {
156
184
  kind: "interface",
157
185
  name: t.name,
158
- methods: new Set((t.methods ?? []).map((m) => m.name ?? m)),
186
+ methods: t.methods,
187
+ };
188
+ case "StructType":
189
+ return {
190
+ kind: "struct",
191
+ name: t.name,
192
+ fields: t.fields,
159
193
  };
160
194
  default:
161
195
  return t;
@@ -169,7 +203,7 @@ const BASIC_DESCS = {
169
203
  float64: { k: "f64", wType: "f64" },
170
204
  string: { k: "string", wType: "externref" },
171
205
  any: { k: "any", wType: "anyref" },
172
- error: { k: "unsupported", what: "error" },
206
+ error: { k: "error", wType: "anyref" },
173
207
  };
174
208
 
175
209
  function classifyBasic(n) {
@@ -182,17 +216,121 @@ function classifyBasic(n) {
182
216
  }
183
217
 
184
218
  function classifyNamed(t, mod) {
219
+ if (
220
+ t.name === "simd.F32x4" ||
221
+ t.name === "F32x4" ||
222
+ t.underlying?.kind === "simd"
223
+ ) {
224
+ return {
225
+ k: "unsupported",
226
+ what: "simd.F32x4 cannot cross the JS↔WASM boundary in 1.7.0 (use shared.Float32 instead)",
227
+ };
228
+ }
229
+ if (t.name === "error") return { k: "error", wType: "anyref" };
230
+ if (t.name === "time.Duration")
231
+ return { k: "i64", unsigned: false, wType: "i64" };
232
+ if (t.name === "time.Time") {
233
+ const info = mod.getStructType("time.Time");
234
+ if (info) return { k: "time", name: "time.Time", info, wType: toRef(info) };
235
+ }
185
236
  const info = mod.getStructType(t.name);
186
237
  if (info) return structDesc(info, false);
187
238
  if (t.underlying) return classifyType(t.underlying, mod);
188
239
  return { k: "unsupported", what: `named type '${t.name}'` };
189
240
  }
190
241
 
242
+ function classifyAnonStruct(t, mod) {
243
+ const rawFields = [];
244
+ if (t.fields instanceof Map) {
245
+ for (const [k, v] of t.fields.entries())
246
+ rawFields.push({ name: k, goType: v });
247
+ } else if (Array.isArray(t.fields)) {
248
+ for (const f of t.fields) {
249
+ const names = f.names ?? (f.name ? [f.name] : []);
250
+ for (const n of names) rawFields.push({ name: n, goType: f.type });
251
+ }
252
+ }
253
+ const info = mod.getAnonStructType(rawFields);
254
+ const fields = [];
255
+ for (const f of info.fields) {
256
+ const fd = classifyType(f.goType, mod);
257
+ if (fd.k === "unsupported") return fd;
258
+ fields.push({ name: f.name, desc: fd });
259
+ }
260
+ return {
261
+ k: "anon_struct",
262
+ name: info.name,
263
+ key: info.name,
264
+ info,
265
+ fields,
266
+ wType: toRef(info),
267
+ };
268
+ }
269
+
270
+ function classifyStruct(t, mod) {
271
+ if (t.name) {
272
+ const info = mod.getStructType(t.name);
273
+ if (info) return structDesc(info, false);
274
+ }
275
+ return classifyAnonStruct(t, mod);
276
+ }
277
+
191
278
  function classifyPointer(t, mod) {
192
279
  const base = classifyType(t.base, mod);
280
+ if (base.k === "unsupported") return base;
193
281
  if (base.k === "struct" && !base.ptr) return { ...base, ptr: true };
194
- const what = base.k === "unsupported" ? base.what : (t.base?.name ?? base.k);
195
- return { k: "unsupported", what: `pointer to ${what}` };
282
+ if (base.k === "anon_struct" && !base.ptr) return { ...base, ptr: true };
283
+ if (base.k === "time" && !base.ptr) return { ...base, ptr: true };
284
+ const boxInfo = mod.getBoxType(t.base);
285
+ const key = `${base.k}${base.unsigned ? "_u" : ""}_${boxInfo.typeIndex}`;
286
+ return {
287
+ k: "ptr_box",
288
+ base,
289
+ boxInfo,
290
+ key,
291
+ wType: { kind: "ref", nullable: true, typeIndex: boxInfo.typeIndex },
292
+ };
293
+ }
294
+
295
+ function isAllowedMapKey(kDesc) {
296
+ if (kDesc.k === "string" || kDesc.k === "bool") return true;
297
+ if (
298
+ kDesc.k === "i32" ||
299
+ kDesc.k === "i64" ||
300
+ kDesc.k === "f32" ||
301
+ kDesc.k === "f64"
302
+ )
303
+ return true;
304
+ return false;
305
+ }
306
+
307
+ function classifyMap(t, mod) {
308
+ const keyType = t.key;
309
+ const valType = t.val ?? t.value ?? t.elem;
310
+ const keyDesc = classifyType(keyType, mod);
311
+ if (keyDesc.k === "unsupported") return keyDesc;
312
+ const valDesc = classifyType(valType, mod);
313
+ if (valDesc.k === "unsupported") return valDesc;
314
+ if (!isAllowedMapKey(keyDesc)) {
315
+ return {
316
+ k: "unsupported",
317
+ what: `map with unsupported key type '${keyType?.name ?? keyDesc.k}'`,
318
+ };
319
+ }
320
+ const mapInfo = mod.getMapType(keyType, valType);
321
+ const keySliceDesc = classifySlice({ kind: "slice", elem: keyType }, mod);
322
+ const valSliceDesc = classifySlice({ kind: "slice", elem: valType }, mod);
323
+ const key = `m_${mapInfo.suffix}`;
324
+ return {
325
+ k: "map",
326
+ key,
327
+ mapInfo,
328
+ keyDesc,
329
+ valDesc,
330
+ keySliceDesc,
331
+ valSliceDesc,
332
+ wType: mapInfo.wType,
333
+ };
196
334
  }
197
335
 
198
336
  function classifyShared(t, mod) {
@@ -263,12 +401,50 @@ function classifyFunc(t, mod) {
263
401
  }
264
402
 
265
403
  function classifyInterface(t, mod) {
266
- if (t.name === "error") return { k: "unsupported", what: "error" };
267
- if (!t.methods || t.methods.size === 0) return { k: "any", wType: "anyref" };
268
- if (!t.name || !(t.methods instanceof Map))
269
- return { k: "unsupported", what: "anonymous interface" };
404
+ if (t.name === "error") return { k: "error", wType: "anyref" };
405
+ const mList = getInterfaceMethodList(t);
406
+ if (mList.length === 0) return { k: "any", wType: "anyref" };
270
407
  mod._boundaryIfaces ??= new Map();
271
- return mod._boundaryIfaces.get(t.name) ?? classifyNamedInterface(t, mod);
408
+ if (t.name && t.methods instanceof Map) {
409
+ return mod._boundaryIfaces.get(t.name) ?? classifyNamedInterface(t, mod);
410
+ }
411
+ return classifyAnonInterface(t, mod);
412
+ }
413
+
414
+ function classifyAnonInterface(t, mod) {
415
+ const key = anonIfaceName(t, mod.checker, mod);
416
+ if (mod._boundaryIfaces.has(key)) return mod._boundaryIfaces.get(key);
417
+ const mList = getInterfaceMethodList(t);
418
+ const desc = {
419
+ k: "iface",
420
+ name: key,
421
+ key,
422
+ wType: "anyref",
423
+ methods: [],
424
+ concrete: [],
425
+ isAnon: true,
426
+ };
427
+ mod._boundaryIfaces.set(key, desc);
428
+ for (const m of mList) {
429
+ const sig = classifySig(
430
+ { kind: "func", params: m.params, returns: m.returns },
431
+ mod,
432
+ );
433
+ if (sig.k === "unsupported") {
434
+ const err = {
435
+ k: "unsupported",
436
+ what: `${sig.what} (in anonymous interface method ${m.name})`,
437
+ };
438
+ mod._boundaryIfaces.set(key, err);
439
+ return err;
440
+ }
441
+ desc.methods.push({
442
+ name: m.name,
443
+ dispatch: `__dispatch_${key}_${m.name}`,
444
+ ...sig,
445
+ });
446
+ }
447
+ return desc;
272
448
  }
273
449
 
274
450
  function classifyNamedInterface(t, mod) {
@@ -325,7 +501,14 @@ function literalConst(spec, i) {
325
501
  }
326
502
  if (node?.kind !== "BasicLit") return null;
327
503
  switch (node.litKind) {
328
- case "INT":
504
+ case "INT": {
505
+ const raw = String(node.value);
506
+ const clean = /^0[0-7]+$/.test(raw) ? `0o${raw.slice(1)}` : raw;
507
+ const bi = BigInt(`${sign}${clean}`);
508
+ return bi > 9007199254740991n || bi < -9007199254740991n
509
+ ? `${sign}${clean}n`
510
+ : `${sign}${clean}`;
511
+ }
329
512
  case "FLOAT":
330
513
  return `${sign}${node.value}`;
331
514
  case "STRING":
@@ -338,6 +521,9 @@ function literalConst(spec, i) {
338
521
  }
339
522
 
340
523
  function unsupportedMsg(where, what) {
524
+ if (typeof what === "string" && what.includes("boundary")) {
525
+ return `${where}: ${what}`;
526
+ }
341
527
  return `${where}: ${what} is not yet supported across the wasm boundary (planned)`;
342
528
  }
343
529
 
@@ -394,14 +580,48 @@ function collectConstDecl(d, pkgName, meta, errors) {
394
580
  const name = spec.names[i];
395
581
  if (!isExported(name)) continue;
396
582
  const value = literalConst(spec, i);
397
- if (value !== null) meta.consts.push({ name, value });
398
- else
583
+ if (value !== null) {
584
+ meta.consts.push({ name, value, dynamic: false });
585
+ continue;
586
+ }
587
+ const folded = meta._mod.constCache?.get(name);
588
+ if (folded) {
589
+ let val = null;
590
+ if (folded.litKind === "INT") {
591
+ const s = String(folded.value);
592
+ const clean = /^0[0-7]+$/.test(s) ? `0o${s.slice(1)}` : s;
593
+ const bi = BigInt(clean);
594
+ val =
595
+ bi > 9007199254740991n || bi < -9007199254740991n
596
+ ? `${clean}n`
597
+ : clean;
598
+ } else if (folded.litKind === "FLOAT") {
599
+ val = folded.value;
600
+ } else if (folded.litKind === "STRING") {
601
+ val = JSON.stringify(String(folded.value));
602
+ } else if (folded.litKind === "BOOL") {
603
+ val = folded.value === "true" ? "true" : "false";
604
+ }
605
+ if (val !== null) {
606
+ meta.consts.push({ name, value: val, dynamic: false });
607
+ continue;
608
+ }
609
+ }
610
+ const glob = meta._mod.resolveGlobal(name);
611
+ const rawType = spec.type ?? spec.value?.[i]?._type ?? glob?.goType;
612
+ let desc = rawType ? classifyType(rawType, meta._mod) : null;
613
+ if (!desc || desc.k === "void") {
614
+ if (glob?.type) {
615
+ desc = { k: glob.type, wType: glob.type };
616
+ }
617
+ }
618
+ if (!desc || desc.k === "unsupported") {
399
619
  errors.push(
400
- unsupportedMsg(
401
- `${pkgName}.${name}`,
402
- "a constant with a non-literal value",
403
- ),
620
+ unsupportedMsg(`${pkgName}.${name}`, desc?.what ?? "unsupported"),
404
621
  );
622
+ } else {
623
+ meta.consts.push({ name, dynamic: true, desc });
624
+ }
405
625
  }
406
626
  }
407
627
  }
@@ -483,6 +703,17 @@ function visitNeed(desc, needs, errors, mod) {
483
703
  case "struct":
484
704
  visitStructNeed(desc, needs, errors, mod);
485
705
  return;
706
+ case "anon_struct":
707
+ if (needs.anonStructs.has(desc.name)) return;
708
+ needs.anonStructs.set(desc.name, desc);
709
+ for (const f of desc.fields) visitNeed(f.desc, needs, errors, mod);
710
+ return;
711
+ case "error":
712
+ needs.error = true;
713
+ return;
714
+ case "time":
715
+ needs.time = true;
716
+ return;
486
717
  case "slice":
487
718
  case "array": {
488
719
  const map = desc.k === "slice" ? needs.slices : needs.arrays;
@@ -491,6 +722,19 @@ function visitNeed(desc, needs, errors, mod) {
491
722
  visitNeed(desc.elem, needs, errors, mod);
492
723
  return;
493
724
  }
725
+ case "map":
726
+ if (needs.maps.has(desc.key)) return;
727
+ needs.maps.set(desc.key, desc);
728
+ visitNeed(desc.keyDesc, needs, errors, mod);
729
+ visitNeed(desc.valDesc, needs, errors, mod);
730
+ visitNeed(desc.keySliceDesc, needs, errors, mod);
731
+ visitNeed(desc.valSliceDesc, needs, errors, mod);
732
+ return;
733
+ case "ptr_box":
734
+ if (needs.boxes.has(desc.key)) return;
735
+ needs.boxes.set(desc.key, desc);
736
+ visitNeed(desc.base, needs, errors, mod);
737
+ return;
494
738
  case "func":
495
739
  if (needs.funcs.has(desc.key)) return;
496
740
  needs.funcs.set(desc.key, desc);
@@ -538,10 +782,15 @@ function collectNeeds(meta) {
538
782
  const mod = meta._mod;
539
783
  const needs = {
540
784
  structs: new Map(), // name -> { info, fields: [{ name, desc }] }
785
+ anonStructs: new Map(), // name -> desc
541
786
  slices: new Map(), // key -> desc
542
787
  arrays: new Map(), // key -> desc
788
+ maps: new Map(), // key -> desc
789
+ boxes: new Map(), // key -> desc
543
790
  funcs: new Map(), // key -> desc
544
791
  ifaces: new Map(), // interface name -> desc
792
+ error: false,
793
+ time: false,
545
794
  shared: false, // any shared buffer on the surface
546
795
  };
547
796
  const errors = [];
@@ -552,6 +801,9 @@ function collectNeeds(meta) {
552
801
  }
553
802
  for (const f of meta.funcs) visitSignatureNeeds(f, needs, errors, mod);
554
803
  for (const v of meta.vars ?? []) visitNeed(v.desc, needs, errors, mod);
804
+ for (const c of meta.consts ?? []) {
805
+ if (c.dynamic) visitNeed(c.desc, needs, errors, mod);
806
+ }
555
807
  if (errors.length > 0) throw new Error([...new Set(errors)].join("\n"));
556
808
  for (const desc of needs.ifaces.values())
557
809
  desc.concrete = concreteCandidates(desc, needs, meta, mod);
@@ -968,6 +1220,231 @@ export function addBoundaryHelpers(mod, meta) {
968
1220
  );
969
1221
  }
970
1222
  }
1223
+
1224
+ for (const [name, desc] of needs.anonStructs) {
1225
+ const { info } = desc;
1226
+ const ref = desc.wType;
1227
+ const ti = info.typeIndex;
1228
+ const fieldWTypes = info.fields.map((f) => f.wType);
1229
+ addFunc(
1230
+ `__new_${name}`,
1231
+ fieldWTypes.map(extType),
1232
+ [EXT],
1233
+ [
1234
+ ...fieldWTypes.flatMap((w, i) => getIn(i, w)),
1235
+ { op: "struct.new", typeIndex: ti },
1236
+ ...toExt(ref),
1237
+ ],
1238
+ );
1239
+ addFunc(
1240
+ `__zero_${name}`,
1241
+ [],
1242
+ [EXT],
1243
+ [
1244
+ ...fieldWTypes.map((w) => zeroInstr(w)),
1245
+ { op: "struct.new", typeIndex: ti },
1246
+ ...toExt(ref),
1247
+ ],
1248
+ );
1249
+ for (let i = 0; i < info.fields.length; i++) {
1250
+ const f = info.fields[i];
1251
+ addFunc(
1252
+ `__get_${name}_${f.name}`,
1253
+ [EXT],
1254
+ [extType(f.wType)],
1255
+ [
1256
+ ...getIn(0, ref),
1257
+ { op: "struct.get", typeIndex: ti, fieldIndex: i },
1258
+ ...toExt(f.wType),
1259
+ ],
1260
+ );
1261
+ addFunc(
1262
+ `__set_${name}_${f.name}`,
1263
+ [EXT, extType(f.wType)],
1264
+ [],
1265
+ [
1266
+ ...getIn(0, ref),
1267
+ ...getIn(1, f.wType),
1268
+ { op: "struct.set", typeIndex: ti, fieldIndex: i },
1269
+ ],
1270
+ );
1271
+ }
1272
+ }
1273
+
1274
+ for (const [key, desc] of needs.boxes) {
1275
+ const { boxInfo } = desc;
1276
+ const ref = desc.wType;
1277
+ const ti = boxInfo.typeIndex;
1278
+ const w = boxInfo.wType;
1279
+ addFunc(
1280
+ `__box_new_${key}`,
1281
+ [extType(w)],
1282
+ [EXT],
1283
+ [...getIn(0, w), { op: "struct.new", typeIndex: ti }, ...toExt(ref)],
1284
+ );
1285
+ addFunc(
1286
+ `__box_get_${key}`,
1287
+ [EXT],
1288
+ [extType(w)],
1289
+ [
1290
+ ...getIn(0, ref),
1291
+ { op: "struct.get", typeIndex: ti, fieldIndex: 0 },
1292
+ ...toExt(w),
1293
+ ],
1294
+ );
1295
+ addFunc(
1296
+ `__box_set_${key}`,
1297
+ [EXT, extType(w)],
1298
+ [],
1299
+ [
1300
+ ...getIn(0, ref),
1301
+ ...getIn(1, w),
1302
+ { op: "struct.set", typeIndex: ti, fieldIndex: 0 },
1303
+ ],
1304
+ );
1305
+ }
1306
+
1307
+ for (const [key, desc] of needs.maps) {
1308
+ const { mapInfo, keySliceDesc, valSliceDesc } = desc;
1309
+ const makeIdx = mod.resolveFuncIndex(mapInfo.makeFuncName);
1310
+ addFunc(
1311
+ `__map_new_${key}`,
1312
+ ["i32"],
1313
+ [EXT],
1314
+ [get(0), { op: "call", funcIndex: makeIdx }, ...toExt(desc.wType)],
1315
+ );
1316
+ const setIdx = mod.resolveFuncIndex(mapInfo.setFuncName);
1317
+ addFunc(
1318
+ `__map_set_${key}`,
1319
+ [EXT, extType(mapInfo.keyWType), extType(mapInfo.valWType)],
1320
+ [],
1321
+ [
1322
+ ...getIn(0, desc.wType),
1323
+ ...getIn(1, mapInfo.keyWType),
1324
+ ...getIn(2, mapInfo.valWType),
1325
+ { op: "call", funcIndex: setIdx },
1326
+ ],
1327
+ );
1328
+ const keysIdx = mod.resolveFuncIndex(mapInfo.keysFuncName);
1329
+ addFunc(
1330
+ `__map_keys_${key}`,
1331
+ [EXT],
1332
+ [EXT],
1333
+ [
1334
+ ...getIn(0, desc.wType),
1335
+ { op: "call", funcIndex: keysIdx },
1336
+ ...toExt(keySliceDesc.wType),
1337
+ ],
1338
+ );
1339
+ const valuesIdx = mod.resolveFuncIndex(mapInfo.valuesFuncName);
1340
+ addFunc(
1341
+ `__map_values_${key}`,
1342
+ [EXT],
1343
+ [EXT],
1344
+ [
1345
+ ...getIn(0, desc.wType),
1346
+ { op: "call", funcIndex: valuesIdx },
1347
+ ...toExt(valSliceDesc.wType),
1348
+ ],
1349
+ );
1350
+ }
1351
+
1352
+ if (needs.error) {
1353
+ const errDispatcherIdx = mod.funcMap.get("__dispatch_error_Error");
1354
+ if (errDispatcherIdx !== undefined) {
1355
+ addFunc(
1356
+ "__error_msg",
1357
+ [EXT],
1358
+ [EXT],
1359
+ [
1360
+ get(0),
1361
+ { op: "ref.is_null" },
1362
+ { op: "if", blockType: "externref" },
1363
+ { op: "ref.null", heapType: "extern" },
1364
+ { op: "else" },
1365
+ ...getIn(0, "anyref"),
1366
+ { op: "call", funcIndex: errDispatcherIdx },
1367
+ ...toExt("externref"),
1368
+ { op: "end" },
1369
+ ],
1370
+ );
1371
+ }
1372
+ const errCandidates = mod
1373
+ .findInterfaceCandidates(null, "Error")
1374
+ .map((c) => c.testTypeIndex);
1375
+ addFunc(
1376
+ "__is_error",
1377
+ [EXT],
1378
+ ["i32"],
1379
+ refTestChain(errCandidates, () => 1),
1380
+ );
1381
+ }
1382
+
1383
+ if (needs.time) {
1384
+ const timeInfo = mod.getStructType("time.Time");
1385
+ if (timeInfo) {
1386
+ const timeWType = toRef(timeInfo);
1387
+ addFunc(
1388
+ "__time_make",
1389
+ ["f64", "i32"],
1390
+ [EXT],
1391
+ [
1392
+ get(0),
1393
+ { op: "f64.const", value: 1000000 },
1394
+ { op: "f64.mul" },
1395
+ { op: "i64.trunc_sat_f64_s" },
1396
+ get(1),
1397
+ { op: "struct.new", typeIndex: timeInfo.typeIndex },
1398
+ ...toExt(timeWType),
1399
+ ],
1400
+ );
1401
+ addFunc(
1402
+ "__time_unix_milli",
1403
+ [EXT],
1404
+ ["f64"],
1405
+ [
1406
+ get(0),
1407
+ { op: "ref.is_null" },
1408
+ { op: "if", blockType: "f64" },
1409
+ { op: "f64.const", value: 0 },
1410
+ { op: "else" },
1411
+ ...getIn(0, timeWType),
1412
+ { op: "struct.get", typeIndex: timeInfo.typeIndex, fieldIndex: 0 },
1413
+ { op: "i64.const", value: 1000000n },
1414
+ { op: "i64.div_s" },
1415
+ { op: "f64.convert_i64_s" },
1416
+ { op: "end" },
1417
+ ],
1418
+ );
1419
+ addFunc(
1420
+ "__time_offset",
1421
+ [EXT],
1422
+ ["i32"],
1423
+ [
1424
+ get(0),
1425
+ { op: "ref.is_null" },
1426
+ { op: "if", blockType: "i32" },
1427
+ { op: "i32.const", value: 0 },
1428
+ { op: "else" },
1429
+ ...getIn(0, timeWType),
1430
+ { op: "struct.get", typeIndex: timeInfo.typeIndex, fieldIndex: 1 },
1431
+ { op: "end" },
1432
+ ],
1433
+ );
1434
+ }
1435
+ }
1436
+
1437
+ for (const c of meta.consts ?? []) {
1438
+ if (!c.dynamic) continue;
1439
+ const g = mod.resolveGlobal(c.name);
1440
+ if (!g) continue;
1441
+ addFunc(
1442
+ `__const_${c.name}`,
1443
+ [],
1444
+ [extType(c.desc.wType)],
1445
+ [{ op: "global.get", index: g.index }, ...toExt(c.desc.wType)],
1446
+ );
1447
+ }
971
1448
  }
972
1449
 
973
1450
  // `(externref) -> i32`: `ref.test`s param 0 against each type index and
@@ -1050,6 +1527,18 @@ function inExpr(desc, v) {
1050
1527
  return `${P}strin(${v})`;
1051
1528
  case "any":
1052
1529
  return `(${v} === undefined ? null : ${v})`;
1530
+ case "error":
1531
+ return `${P}errin(${v})`;
1532
+ case "time":
1533
+ return desc.ptr ? `${P}timein_ptr(${v})` : `${P}timein(${v})`;
1534
+ case "map":
1535
+ return `${P}mapin_${desc.key}(${v})`;
1536
+ case "ptr_box":
1537
+ return `${P}boxin_${desc.key}(${v})`;
1538
+ case "anon_struct":
1539
+ return desc.ptr
1540
+ ? `(${v} == null ? null : ${P}to_${desc.name}(${v}))`
1541
+ : `${P}to_${desc.name}(${v})`;
1053
1542
  case "struct":
1054
1543
  if (desc.info.pkgTarget === "both")
1055
1544
  return `${P}to_${desc.name}(${v}, ${desc.ptr ? "false" : "true"})`;
@@ -1082,6 +1571,18 @@ function outExpr(desc, v) {
1082
1571
  return desc.unsigned ? `${P}u64out(${v})` : `${P}i64out(${v})`;
1083
1572
  case "string":
1084
1573
  return `(${v} ?? "")`;
1574
+ case "error":
1575
+ return `${P}errout(${v})`;
1576
+ case "time":
1577
+ return desc.ptr ? `${P}timeout_ptr(${v})` : `${P}timeout(${v})`;
1578
+ case "map":
1579
+ return `${P}mapout_${desc.key}(${v})`;
1580
+ case "ptr_box":
1581
+ return `${P}boxout_${desc.key}(${v})`;
1582
+ case "anon_struct":
1583
+ return desc.ptr
1584
+ ? `(${v} == null ? null : ${P}from_${desc.name}(${v}))`
1585
+ : `${P}from_${desc.name}(${v})`;
1085
1586
  case "struct":
1086
1587
  return desc.info.pkgTarget === "both"
1087
1588
  ? `${P}from_${desc.name}(${v})`
@@ -1112,6 +1613,15 @@ function zeroExpr(desc) {
1112
1613
  return "0";
1113
1614
  case "string":
1114
1615
  return '""';
1616
+ case "error":
1617
+ case "time":
1618
+ case "map":
1619
+ case "ptr_box":
1620
+ return "null";
1621
+ case "anon_struct":
1622
+ return desc.ptr
1623
+ ? "null"
1624
+ : `{ ${desc.fields.map((f) => `"${f.name}": ${zeroExpr(f.desc)}`).join(", ")} }`;
1115
1625
  case "struct":
1116
1626
  return desc.ptr ? "null" : `new ${desc.name}()`;
1117
1627
  case "array":
@@ -1147,6 +1657,10 @@ function isBothStruct(d) {
1147
1657
  return d.k === "struct" && d.info.pkgTarget === "both";
1148
1658
  }
1149
1659
 
1660
+ function isPtrWriteBack(d) {
1661
+ return isPtrBoth(d) || d.k === "ptr_box" || (d.k === "anon_struct" && d.ptr);
1662
+ }
1663
+
1150
1664
  function returnLines(returns, resultExpr) {
1151
1665
  if (returns.length === 0) return [];
1152
1666
  if (returns.length === 1)
@@ -1161,13 +1675,21 @@ function genFuncBody(exportName, params, returns, leadArgs = []) {
1161
1675
  const names = params.map((_, i) => `a${i}`);
1162
1676
  const lines = [];
1163
1677
  const argExprs = [...leadArgs];
1164
- const writeBack = params.some(isPtrBoth);
1165
- if (writeBack) lines.push("\tconst __m = new Map();");
1678
+ const writeBack = params.some(isPtrWriteBack);
1679
+ if (params.some(isPtrBoth)) lines.push("\tconst __m = new Map();");
1166
1680
  for (let i = 0; i < params.length; i++) {
1167
1681
  const d = params[i];
1168
1682
  if (isPtrBoth(d)) {
1169
1683
  lines.push(`\tconst __p${i} = ${P}to_${d.name}(a${i}, false, __m);`);
1170
1684
  argExprs.push(`__p${i}`);
1685
+ } else if (d.k === "ptr_box") {
1686
+ lines.push(`\tconst __p${i} = ${P}boxin_${d.key}(a${i});`);
1687
+ argExprs.push(`__p${i}`);
1688
+ } else if (d.k === "anon_struct" && d.ptr) {
1689
+ lines.push(
1690
+ `\tconst __p${i} = a${i} == null ? null : ${P}to_${d.name}(a${i});`,
1691
+ );
1692
+ argExprs.push(`__p${i}`);
1171
1693
  } else {
1172
1694
  argExprs.push(inExpr(d, names[i]));
1173
1695
  }
@@ -1185,8 +1707,12 @@ function genFuncBody(exportName, params, returns, leadArgs = []) {
1185
1707
  } else {
1186
1708
  lines.push(`\t\tconst __r = ${call};`);
1187
1709
  for (let i = 0; i < params.length; i++) {
1188
- if (isPtrBoth(params[i]))
1189
- lines.push(`\t\t${P}back_${params[i].name}(__p${i}, a${i});`);
1710
+ const d = params[i];
1711
+ if (isPtrBoth(d)) lines.push(`\t\t${P}back_${d.name}(__p${i}, a${i});`);
1712
+ else if (d.k === "ptr_box")
1713
+ lines.push(`\t\t${P}boxback_${d.key}(__p${i}, a${i});`);
1714
+ else if (d.k === "anon_struct" && d.ptr)
1715
+ lines.push(`\t\t${P}back_${d.name}(__p${i}, a${i});`);
1190
1716
  }
1191
1717
  lines.push(...returnLines(returns, "__r").map((l) => `\t${l}`));
1192
1718
  }
@@ -1206,6 +1732,7 @@ export const WASM_IMPORTS_JS = `const ${P}MAX = 9007199254740991n;
1206
1732
  function ${P}imports(stringTable, extraEnv, tag, write) {
1207
1733
  let lineBuf = "";
1208
1734
  let targs = [];
1735
+ let fmtStructMap = new WeakMap();
1209
1736
  const late = { exports: null };
1210
1737
  const flush = () => { write(lineBuf); lineBuf = ""; };
1211
1738
  const print = (v) => {
@@ -1224,6 +1751,8 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
1224
1751
  flush();
1225
1752
  };
1226
1753
  const targ = (v) => { targs.push(v); };
1754
+ let utf8Encoded = null;
1755
+ let convBytes = null, convPos = 0;
1227
1756
  const env = {
1228
1757
  "panicTag": tag,
1229
1758
  "panic": (msg) => {
@@ -1276,6 +1805,25 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
1276
1805
  "str_from_i32": (n) => String(n),
1277
1806
  "str_from_f64": (f) => String(f),
1278
1807
  "is_string": (v) => (typeof v === "string" ? 1 : 0),
1808
+ "str_to_bytes": (s) => {
1809
+ utf8Encoded = new TextEncoder().encode(s ?? "");
1810
+ return utf8Encoded.length;
1811
+ },
1812
+ "str_byte_at": (i) => (utf8Encoded ? (utf8Encoded[i] ?? 0) : 0),
1813
+ "bytes_to_str_init": (len) => {
1814
+ convBytes = new Uint8Array(len);
1815
+ convPos = 0;
1816
+ },
1817
+ "bytes_to_str_push": (b) => {
1818
+ if (convBytes && convPos < convBytes.length) {
1819
+ convBytes[convPos++] = b & 0xFF;
1820
+ }
1821
+ },
1822
+ "bytes_to_str_finish": () => {
1823
+ const res = convBytes ? new TextDecoder().decode(convBytes) : "";
1824
+ convBytes = null;
1825
+ return res;
1826
+ },
1279
1827
  "print_i32": print, "print_i64": print, "print_f32": print, "print_f64": print,
1280
1828
  "print_str": print, "print_any": print,
1281
1829
  "print_bool": (b) => print(b !== 0 ? "true" : "false"),
@@ -1283,11 +1831,31 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
1283
1831
  "println_str": println, "println_any": println,
1284
1832
  "println_bool": (b) => println(b !== 0 ? "true" : "false"),
1285
1833
  "println_empty": flush,
1834
+ "fmt_new_array": () => [],
1835
+ "fmt_array_push": (arr, v) => { arr.push(v); },
1836
+ "fmt_new_map": () => new Map(),
1837
+ "fmt_map_set": (m, k, v) => { m.set(k, v); },
1838
+ "fmt_new_struct": (ref, isPtr) => {
1839
+ if (ref !== null && ref !== undefined && typeof ref === "object" && fmtStructMap.has(ref)) {
1840
+ return fmtStructMap.get(ref);
1841
+ }
1842
+ const o = {};
1843
+ Object.defineProperty(o, "__struct", { value: true, configurable: true });
1844
+ if (isPtr !== 0) Object.defineProperty(o, "__p", { value: true, configurable: true });
1845
+ if (ref !== null && ref !== undefined && typeof ref === "object") fmtStructMap.set(ref, o);
1846
+ return o;
1847
+ },
1848
+ "fmt_struct_set": (o, k, v) => { o[typeof k === "number" ? stringTable[k] : k] = v; },
1849
+ "fmt_box_i32": (v) => v,
1850
+ "fmt_box_i64": (v) => (v > ${P}MAX || v < -${P}MAX ? v : Number(v)),
1851
+ "fmt_box_f64": (v) => v,
1852
+ "fmt_box_bool": (v) => v !== 0,
1853
+ "fmt_new_box": () => ({ value: null, __p: true, __box: true }),
1286
1854
  "testing_arg_i32": targ, "testing_arg_f32": targ, "testing_arg_f64": targ,
1287
1855
  "testing_arg_str": targ, "testing_arg_any": targ,
1288
1856
  "testing_arg_i64": (v) => targ(v > ${P}MAX || v < -${P}MAX ? v : Number(v)),
1289
1857
  "testing_arg_bool": (v) => targ(v !== 0),
1290
- "testing_call": (t, i) => { const a = targs; targs = []; t[stringTable[i]](...a); },
1858
+ "testing_call": (t, i) => { const a = targs; targs = []; fmtStructMap = new WeakMap(); t[stringTable[i]](...a); },
1291
1859
  "testing_name": (t) => t.Name(),
1292
1860
  "testing_flag": (t, i) => (t[stringTable[i]]() ? 1 : 0),
1293
1861
  };
@@ -1295,7 +1863,7 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
1295
1863
  // modules that never call them do not ship it (see WASM_STDLIB_JS).
1296
1864
  if (typeof ${P}stdlib === "function") {
1297
1865
  Object.assign(env, ${P}stdlib(stringTable, {
1298
- "take": () => { const a = targs; targs = []; return a; },
1866
+ "take": () => { const a = targs; targs = []; fmtStructMap = new WeakMap(); return a; },
1299
1867
  "printf": (s) => {
1300
1868
  lineBuf += s;
1301
1869
  const nl = lineBuf.lastIndexOf("\\n");
@@ -1306,7 +1874,7 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
1306
1874
  }
1307
1875
  Object.assign(env, extraEnv);
1308
1876
  const m = {};
1309
- for (const k of ["sin", "cos", "tan", "asin", "acos", "atan", "atan2", "pow", "exp", "log", "log2", "log10", "cbrt", "hypot"]) m[k] = Math[k];
1877
+ for (const k of ["sin", "cos", "tan", "asin", "acos", "atan", "atan2", "pow", "exp", "log", "log2", "log10", "cbrt", "hypot", "random", "sinh", "cosh", "tanh"]) m[k] = Math[k];
1310
1878
  m.mod = (x, y) => x % y;
1311
1879
  // Go rounds half away from zero; Math.round rounds half toward +Infinity.
1312
1880
  m.round = (x) => Math.sign(x) * Math.round(Math.abs(x));
@@ -1316,21 +1884,169 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
1316
1884
 
1317
1885
  // JS side of the STDLIB_IMPORTS table (index.js); emitted only when the
1318
1886
  // module imports one of the packages that use it.
1319
- export const WASM_STDLIB_JS = `${HELPER_SPRINTF}
1887
+ export const WASM_STDLIB_JS = `${HELPER_ERROR}
1888
+ ${HELPER_SPRINTF}
1320
1889
  ${HELPER_STRCONV}
1890
+ ${HELPER_PATH_CLEAN}
1891
+ ${HELPER_DURATION}
1892
+ ${HELPER_SHA256}
1893
+ ${HELPER_HEX}
1894
+ ${HELPER_BASE64}
1321
1895
  function ${P}stdlib(stringTable, io) {
1322
1896
  let parts = [];
1323
- let strconvErr = null;
1897
+ let cutBefore = "", cutAfter = "";
1898
+ let strconvErr = null, strconvErrKind = 0;
1324
1899
  let decodeSize = 0;
1900
+ let replacerTarget = "", replacerPairs = [];
1901
+ let splitDir = "", splitFile = "";
1902
+ let joinParts = [];
1903
+ let matchHasErr = 0;
1904
+ let trimStart = 0, trimEnd = 0;
1905
+ let sscanTokens = [];
1906
+ let sscanHasErr = 0, sscanErrMsg = "";
1907
+ let timeParseErr = null, timeParseOffset = 0;
1908
+ let rawBytes = new Uint8Array(0);
1909
+ let rawPos = 0;
1910
+ let hexErr = null;
1911
+ let base64Err = null;
1912
+ let customBase64Encodings = [];
1325
1913
  return {
1914
+ "fmt_sscan_init": (s, mode) => {
1915
+ sscanHasErr = 0;
1916
+ sscanErrMsg = "";
1917
+ const str = s ?? "";
1918
+ sscanTokens = (mode === 1 ? str.split("\\n")[0] : str).trim().split(/\\s+/).filter(Boolean);
1919
+ return sscanTokens.length;
1920
+ },
1921
+ "fmt_sscanf_init": (s, fmt) => {
1922
+ sscanHasErr = 0;
1923
+ sscanErrMsg = "";
1924
+ const str = s ?? "";
1925
+ const fmtStr = fmt ?? "";
1926
+ let si = 0, fi = 0;
1927
+ sscanTokens = [];
1928
+ while (fi < fmtStr.length) {
1929
+ if (/\\s/.test(fmtStr[fi])) {
1930
+ while (fi < fmtStr.length && /\\s/.test(fmtStr[fi])) fi++;
1931
+ while (si < str.length && /\\s/.test(str[si])) si++;
1932
+ continue;
1933
+ }
1934
+ if (fmtStr[fi] === "%") {
1935
+ if (fi + 1 < fmtStr.length && fmtStr[fi + 1] === "%") {
1936
+ if (str[si] !== "%") {
1937
+ sscanHasErr = 1;
1938
+ sscanErrMsg = "input does not match format";
1939
+ break;
1940
+ }
1941
+ si++;
1942
+ fi += 2;
1943
+ continue;
1944
+ }
1945
+ const vm = fmtStr.slice(fi).match(/^%[vdsfqtxXbcoeEfFgG]/);
1946
+ if (vm) {
1947
+ const verb = vm[0];
1948
+ fi += verb.length;
1949
+ if (verb !== "%c") {
1950
+ while (si < str.length && /\\s/.test(str[si])) si++;
1951
+ }
1952
+ if (si >= str.length) break;
1953
+ let tokRe = /^\\S+/;
1954
+ if (verb === "%d" || verb === "%i") tokRe = /^[+-]?\\d+/;
1955
+ else if (verb === "%f" || verb === "%F" || verb === "%e" || verb === "%E" || verb === "%g" || verb === "%G") tokRe = /^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?/;
1956
+ else if (verb === "%t") tokRe = /^(?:true|false|1|0|t|f|T|F|TRUE|FALSE)/;
1957
+ else if (verb === "%x" || verb === "%X") tokRe = /^[+-]?(?:0x)?[0-9a-fA-F]+/;
1958
+ else if (verb === "%c") tokRe = /^[\\s\\S]/;
1959
+ const m = str.slice(si).match(tokRe);
1960
+ if (!m) {
1961
+ sscanHasErr = 1;
1962
+ sscanErrMsg = "input does not match format";
1963
+ break;
1964
+ }
1965
+ sscanTokens.push(m[0]);
1966
+ si += m[0].length;
1967
+ continue;
1968
+ }
1969
+ }
1970
+ if (si >= str.length) break;
1971
+ if (str[si] !== fmtStr[fi]) {
1972
+ sscanHasErr = 1;
1973
+ sscanErrMsg = "input does not match format";
1974
+ break;
1975
+ }
1976
+ si++;
1977
+ fi++;
1978
+ }
1979
+ return sscanTokens.length;
1980
+ },
1981
+ "fmt_sscan_has_err": () => sscanHasErr,
1982
+ "fmt_sscan_err_msg": () => sscanErrMsg || "syntax error",
1983
+ "fmt_sscan_token_str": (i) => sscanTokens[i] ?? "",
1984
+ "fmt_sscan_token_i64": (i) => {
1985
+ if (i >= sscanTokens.length) { sscanHasErr = 1; sscanErrMsg = "unexpected EOF"; return 0n; }
1986
+ const t = sscanTokens[i];
1987
+ if (/^[+-]?(?:0[xX])?[0-9a-fA-F]+$/.test(t)) {
1988
+ try {
1989
+ if (/^[+-]?[0-9a-fA-F]+$/.test(t) && /[a-fA-F]/.test(t)) {
1990
+ return BigInt("0x" + t);
1991
+ }
1992
+ return BigInt(t);
1993
+ } catch {}
1994
+ }
1995
+ if (t.length === 1) {
1996
+ return BigInt(t.charCodeAt(0));
1997
+ }
1998
+ sscanHasErr = 1;
1999
+ sscanErrMsg = "expected integer";
2000
+ return 0n;
2001
+ },
2002
+ "fmt_sscan_token_f64": (i) => {
2003
+ if (i >= sscanTokens.length) { sscanHasErr = 1; sscanErrMsg = "unexpected EOF"; return 0; }
2004
+ const t = sscanTokens[i];
2005
+ const n = Number(t);
2006
+ if (Number.isNaN(n) || !/^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?$/.test(t)) {
2007
+ sscanHasErr = 1;
2008
+ sscanErrMsg = "expected float";
2009
+ return 0;
2010
+ }
2011
+ return n;
2012
+ },
2013
+ "fmt_sscan_token_bool": (i) => {
2014
+ if (i >= sscanTokens.length) { sscanHasErr = 1; sscanErrMsg = "unexpected EOF"; return 0; }
2015
+ const t = sscanTokens[i]?.toLowerCase();
2016
+ if (t === "true" || t === "1" || t === "t") return 1;
2017
+ if (t === "false" || t === "0" || t === "f") return 0;
2018
+ sscanHasErr = 1;
2019
+ sscanErrMsg = "expected boolean";
2020
+ return 0;
2021
+ },
1326
2022
  "testing_run": (t, name, fn) => (t.Run(name, (sub) => { io.late.exports.__testing_run_cb(fn, sub); }) ? 1 : 0),
1327
2023
  "fmt_sprintf": (f) => __sprintf(f ?? "", ...io.take()),
1328
2024
  "fmt_printf": (f) => { io.printf(__sprintf(f ?? "", ...io.take())); },
2025
+ "fmt_format_v": (v) => { const res = __sprintf("%v", v); io.take(); return res; },
1329
2026
  "str_trim": (s, c) => ${P}trim(s ?? "", c ?? "", true, true),
1330
2027
  "str_trim_left": (s, c) => ${P}trim(s ?? "", c ?? "", true, false),
1331
2028
  "str_trim_right": (s, c) => ${P}trim(s ?? "", c ?? "", false, true),
1332
2029
  "str_trim_prefix": (s, p) => (s && p && s.startsWith(p) ? s.slice(p.length) : (s ?? "")),
1333
2030
  "str_trim_suffix": (s, p) => (s && p && s.endsWith(p) ? s.slice(0, s.length - p.length) : (s ?? "")),
2031
+ "str_cut": (s, sep) => {
2032
+ const i = (s ?? "").indexOf(sep ?? "");
2033
+ if (i < 0) { cutBefore = s ?? ""; cutAfter = ""; return 0; }
2034
+ cutBefore = (s ?? "").slice(0, i);
2035
+ cutAfter = (s ?? "").slice(i + (sep ?? "").length);
2036
+ return 1;
2037
+ },
2038
+ "str_cut_before": () => cutBefore,
2039
+ "str_cut_after": () => cutAfter,
2040
+ "str_cut_prefix": (s, p) => {
2041
+ s = s ?? ""; p = p ?? "";
2042
+ if (s.startsWith(p)) { cutAfter = s.slice(p.length); return 1; }
2043
+ cutAfter = s; return 0;
2044
+ },
2045
+ "str_cut_suffix": (s, suf) => {
2046
+ s = s ?? ""; suf = suf ?? "";
2047
+ if (s.endsWith(suf)) { cutBefore = s.slice(0, s.length - suf.length); return 1; }
2048
+ cutBefore = s; return 0;
2049
+ },
1334
2050
  "str_replace": (s, o, n, cnt) => {
1335
2051
  s = s ?? ""; o = o ?? ""; n = n ?? "";
1336
2052
  if (cnt < 0) return s.replaceAll(o, n);
@@ -1350,18 +2066,175 @@ function ${P}stdlib(stringTable, io) {
1350
2066
  "str_index_rune": (s, r) => (s ?? "").indexOf(String.fromCodePoint(r)),
1351
2067
  "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; },
1352
2068
  "str_last_index_byte": (s, b) => (s ?? "").lastIndexOf(String.fromCharCode(b)),
2069
+ "str_last_index_any": (s, cs) => {
2070
+ s = s ?? ""; let best = -1;
2071
+ for (const c of (cs ?? "")) {
2072
+ const i = s.lastIndexOf(c);
2073
+ if (i > best) best = i;
2074
+ }
2075
+ return best;
2076
+ },
1353
2077
  "str_title": (s) => (s ?? "").replace(/(^|[^\\p{L}\\p{N}_'])(\\p{Ll})/gu, (m, a, b) => a + b.toUpperCase()),
1354
2078
  "str_split": (s, sep) => { parts = (s ?? "").split(sep ?? ""); return parts.length; },
2079
+ "str_split_n": (s, sep, n) => {
2080
+ s = s ?? ""; sep = sep ?? "";
2081
+ if (n === 0) return -1;
2082
+ if (n < 0) { parts = sep === "" ? [...s] : s.split(sep); return parts.length; }
2083
+ if (sep === "") {
2084
+ const chars = [...s];
2085
+ if (chars.length <= n) { parts = chars; return parts.length; }
2086
+ const r = chars.slice(0, n - 1);
2087
+ r.push(chars.slice(n - 1).join(""));
2088
+ parts = r;
2089
+ return parts.length;
2090
+ }
2091
+ const r = [];
2092
+ let cur = s;
2093
+ for (let i = 1; i < n && cur.length; i++) {
2094
+ const j = cur.indexOf(sep);
2095
+ if (j < 0) break;
2096
+ r.push(cur.slice(0, j));
2097
+ cur = cur.slice(j + sep.length);
2098
+ }
2099
+ r.push(cur);
2100
+ parts = r;
2101
+ return parts.length;
2102
+ },
2103
+ "str_split_after": (s, sep) => {
2104
+ s = s ?? ""; sep = sep ?? "";
2105
+ if (sep === "") { parts = [...s]; return parts.length; }
2106
+ const r = [];
2107
+ let cur = s;
2108
+ while (true) {
2109
+ const j = cur.indexOf(sep);
2110
+ if (j < 0) { r.push(cur); break; }
2111
+ r.push(cur.slice(0, j + sep.length));
2112
+ cur = cur.slice(j + sep.length);
2113
+ }
2114
+ parts = r;
2115
+ return parts.length;
2116
+ },
2117
+ "str_split_after_n": (s, sep, n) => {
2118
+ s = s ?? ""; sep = sep ?? "";
2119
+ if (n === 0) return -1;
2120
+ if (sep === "") {
2121
+ const chars = [...s];
2122
+ if (n < 0 || chars.length <= n) parts = chars;
2123
+ else {
2124
+ const r = chars.slice(0, n - 1);
2125
+ r.push(chars.slice(n - 1).join(""));
2126
+ parts = r;
2127
+ }
2128
+ return parts.length;
2129
+ }
2130
+ const r = [];
2131
+ let cur = s;
2132
+ for (let i = 1; (n < 0 || i < n) && cur.length; i++) {
2133
+ const j = cur.indexOf(sep);
2134
+ if (j < 0) break;
2135
+ r.push(cur.slice(0, j + sep.length));
2136
+ cur = cur.slice(j + sep.length);
2137
+ }
2138
+ r.push(cur);
2139
+ parts = r;
2140
+ return parts.length;
2141
+ },
1355
2142
  "str_fields": (s) => { parts = (s ?? "").split(/\\s+/).filter((p) => p !== ""); return parts.length; },
1356
2143
  "str_part": (i) => parts[i] ?? "",
1357
- "strconv_atoi": (s) => { const r = __strconv_atoi(s ?? ""); strconvErr = r[1]; return r[0]; },
1358
- "strconv_parse_int": (s, base, bits) => { const r = __strconv_parse_int(s ?? "", base, bits); strconvErr = r[1]; return r[0]; },
1359
- "strconv_parse_float": (s) => { const r = __strconv_parse_float(s ?? ""); strconvErr = r[1]; return r[0]; },
1360
- "strconv_parse_bool": (s) => { const r = __strconv_parse_bool(s ?? ""); strconvErr = r[1]; return r[0] ? 1 : 0; },
2144
+ "str_replacer_init": (s) => {
2145
+ replacerTarget = s ?? "";
2146
+ replacerPairs = [];
2147
+ },
2148
+ "str_replacer_add": (o, n) => {
2149
+ replacerPairs.push([o ?? "", n ?? ""]);
2150
+ },
2151
+ "str_replacer_run": () => {
2152
+ let s = replacerTarget;
2153
+ if (!replacerPairs.length || s === "") return s;
2154
+ let out = "";
2155
+ let i = 0;
2156
+ while (i < s.length) {
2157
+ let matched = false;
2158
+ for (let k = 0; k < replacerPairs.length; k++) {
2159
+ const [oldStr, newStr] = replacerPairs[k];
2160
+ if (oldStr.length > 0 && s.startsWith(oldStr, i)) {
2161
+ out += newStr;
2162
+ i += oldStr.length;
2163
+ matched = true;
2164
+ break;
2165
+ }
2166
+ }
2167
+ if (!matched) {
2168
+ out += s[i];
2169
+ i++;
2170
+ }
2171
+ }
2172
+ return out;
2173
+ },
2174
+ "strconv_atoi": (s) => {
2175
+ const r = __strconv_atoi(s ?? "");
2176
+ strconvErr = r[1];
2177
+ strconvErrKind = r[1] ? (r[1].includes("invalid syntax") ? 1 : r[1].includes("value out of range") ? 2 : 0) : 0;
2178
+ return r[0];
2179
+ },
2180
+ "strconv_parse_int": (s, base, bits) => {
2181
+ const r = __strconv_parse_int(s ?? "", base, bits);
2182
+ strconvErr = r[1];
2183
+ strconvErrKind = r[1] ? (r[1].includes("invalid syntax") ? 1 : r[1].includes("value out of range") ? 2 : 0) : 0;
2184
+ return r[0];
2185
+ },
2186
+ "strconv_parse_float": (s) => {
2187
+ const r = __strconv_parse_float(s ?? "");
2188
+ strconvErr = r[1];
2189
+ strconvErrKind = r[1] ? (r[1].includes("invalid syntax") ? 1 : r[1].includes("value out of range") ? 2 : 0) : 0;
2190
+ return r[0];
2191
+ },
2192
+ "strconv_parse_bool": (s) => {
2193
+ const r = __strconv_parse_bool(s ?? "");
2194
+ strconvErr = r[1];
2195
+ strconvErrKind = r[1] ? (r[1].includes("invalid syntax") ? 1 : r[1].includes("value out of range") ? 2 : 0) : 0;
2196
+ return r[0] ? 1 : 0;
2197
+ },
1361
2198
  "strconv_err": () => strconvErr,
2199
+ "strconv_err_kind": () => strconvErrKind,
1362
2200
  "strconv_format_float": (f, fmtc, prec, _bits) => __strconv_format_float(f, fmtc, prec),
1363
2201
  "strconv_format_int": (n, base) => n.toString(base),
2202
+ "strconv_format_uint": (n, base) => BigInt.asUintN(64, BigInt(n)).toString(base),
2203
+ "strconv_parse_uint": (s, base, bits) => {
2204
+ const r = __strconv_parse_uint(s ?? "", base, bits);
2205
+ strconvErr = r[1];
2206
+ strconvErrKind = r[1] ? (r[1].includes("invalid syntax") ? 1 : r[1].includes("value out of range") ? 2 : 0) : 0;
2207
+ return r[0];
2208
+ },
1364
2209
  "strconv_quote": (s) => JSON.stringify(s ?? ""),
2210
+ "strconv_unquote": (s) => {
2211
+ s = s ?? "";
2212
+ if (s.startsWith("\`") && s.endsWith("\`") && s.length >= 2) {
2213
+ if (s.slice(1, -1).includes("\`")) {
2214
+ strconvErr = "invalid syntax";
2215
+ strconvErrKind = 1;
2216
+ return "";
2217
+ }
2218
+ strconvErr = null;
2219
+ strconvErrKind = 0;
2220
+ return s.slice(1, -1);
2221
+ }
2222
+ try {
2223
+ const v = JSON.parse(s);
2224
+ if (typeof v !== "string") {
2225
+ strconvErr = "invalid syntax";
2226
+ strconvErrKind = 1;
2227
+ return "";
2228
+ }
2229
+ strconvErr = null;
2230
+ strconvErrKind = 0;
2231
+ return v;
2232
+ } catch (e) {
2233
+ strconvErr = "invalid syntax";
2234
+ strconvErrKind = 1;
2235
+ return "";
2236
+ }
2237
+ },
1365
2238
  "utf8_rune_count": (s) => { let n = 0; for (const _ of (s ?? "")) n++; return n; },
1366
2239
  "utf8_rune_len": (r) => (r < 0 || r > 0x10ffff || (r >= 0xd800 && r <= 0xdfff) ? -1 : r < 0x80 ? 1 : r < 0x800 ? 2 : r < 0x10000 ? 3 : 4),
1367
2240
  "utf8_valid_string": (s) => (s ?? "").isWellFormed ? ((s ?? "").isWellFormed() ? 1 : 0) : (/[\\uD800-\\uDFFF]/.test((s ?? "").replace(/[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]/g, "")) ? 0 : 1),
@@ -1378,14 +2251,205 @@ function ${P}stdlib(stringTable, io) {
1378
2251
  },
1379
2252
  "utf8_decode_size": () => decodeSize,
1380
2253
  "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; },
2254
+ // unicode
2255
+ "unicode_is_letter": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{L}/u.test(c)) ? 1 : 0; },
2256
+ "unicode_is_digit": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{Nd}/u.test(c)) ? 1 : 0; },
2257
+ "unicode_is_space": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\s/u.test(c)) ? 1 : 0; },
2258
+ "unicode_is_upper": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{Lu}/u.test(c)) ? 1 : 0; },
2259
+ "unicode_is_lower": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{Ll}/u.test(c)) ? 1 : 0; },
2260
+ "unicode_is_punct": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{P}/u.test(c)) ? 1 : 0; },
2261
+ "unicode_is_control": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{Cc}/u.test(c)) ? 1 : 0; },
2262
+ "unicode_is_print": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && !/\\p{Cc}/u.test(c)) ? 1 : 0; },
2263
+ "unicode_is_graphic": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && !/\\p{Cc}/u.test(c)) ? 1 : 0; },
2264
+ "unicode_to_upper": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return c ? (c.toUpperCase().codePointAt(0) ?? r) : r; },
2265
+ "unicode_to_lower": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return c ? (c.toLowerCase().codePointAt(0) ?? r) : r; },
2266
+ // html
2267
+ "html_escape": (s) => (s ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&#34;").replace(/'/g, "&#39;"),
2268
+ "html_unescape": (s) => (s ?? "").replace(/&(?:quot|apos|#39|#34|gt|lt|amp|#(?:[0-9]+|[xX][0-9a-fA-F]+));/g, (m) => {
2269
+ switch (m) {
2270
+ case "&quot;": case "&#34;": return '"';
2271
+ case "&apos;": case "&#39;": return "'";
2272
+ case "&gt;": return ">";
2273
+ case "&lt;": return "<";
2274
+ case "&amp;": return "&";
2275
+ default: {
2276
+ if (m.startsWith("&#")) {
2277
+ const raw = m.slice(2, -1);
2278
+ const cp = (raw.startsWith("x") || raw.startsWith("X")) ? parseInt(raw.slice(1), 16) : parseInt(raw, 10);
2279
+ return (cp >= 0 && cp <= 0x10FFFF && !(cp >= 0xD800 && cp <= 0xDFFF)) ? String.fromCodePoint(cp) : "\\uFFFD";
2280
+ }
2281
+ return m;
2282
+ }
2283
+ }
2284
+ }),
2285
+ // path
2286
+ "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) || "/"; },
2287
+ "path_dir": (p) => { const s = p ?? ""; const i = s.lastIndexOf("/"); if (i < 0) return "."; const res = s.slice(0, i); return res === "" ? "/" : __pathClean(res); },
2288
+ "path_ext": (p) => { const s = p ?? ""; const b = s.slice(s.lastIndexOf("/") + 1); const i = b.lastIndexOf("."); return i <= 0 ? "" : b.slice(i); },
2289
+ "path_is_abs": (p) => (p ?? "").startsWith("/") ? 1 : 0,
2290
+ "path_clean": (p) => __pathClean(p ?? ""),
2291
+ "path_split": (p) => {
2292
+ const s = p ?? "";
2293
+ const i = s.lastIndexOf("/");
2294
+ if (i < 0) { splitDir = ""; splitFile = s; }
2295
+ else { splitDir = s.slice(0, i + 1); splitFile = s.slice(i + 1); }
2296
+ },
2297
+ "path_split_dir": () => splitDir,
2298
+ "path_split_file": () => splitFile,
2299
+ "path_join_init": () => { joinParts = []; },
2300
+ "path_join_push": (s) => { if (s) joinParts.push(s); },
2301
+ "path_join_run": () => {
2302
+ if (joinParts.length === 0) return "";
2303
+ const res = __pathClean(joinParts.join("/"));
2304
+ joinParts = [];
2305
+ return res;
2306
+ },
2307
+ "path_match": (pat, name) => {
2308
+ const r = __pathMatch(pat ?? "", name ?? "");
2309
+ matchHasErr = r[1] ? 1 : 0;
2310
+ return r[0] ? 1 : 0;
2311
+ },
2312
+ "path_match_has_err": () => matchHasErr,
2313
+ // bytes trim
2314
+ "bytes_trim": (s, cutset, left, right) => {
2315
+ const cs = new Set(Array.from(cutset ?? ""));
2316
+ const chars = Array.from(s ?? "");
2317
+ let a = 0, b = chars.length;
2318
+ if (left) while (a < b && cs.has(chars[a])) a++;
2319
+ if (right) while (b > a && cs.has(chars[b - 1])) b--;
2320
+ const enc = new TextEncoder();
2321
+ trimStart = a === 0 ? 0 : enc.encode(chars.slice(0, a).join("")).length;
2322
+ trimEnd = b === chars.length ? enc.encode(s ?? "").length : enc.encode(chars.slice(0, b).join("")).length;
2323
+ },
2324
+ "bytes_trim_start": () => trimStart,
2325
+ "bytes_trim_end": () => trimEnd,
2326
+ // time
2327
+ "time_now_ms": () => (typeof globalThis.__GOFRONT_CLOCK === "function" ? globalThis.__GOFRONT_CLOCK() : Date.now()),
2328
+ "time_timezone_offset": (ms) => -new Date(ms).getTimezoneOffset() * 60,
2329
+ "time_duration_string": (d) => __durationString(d),
2330
+ "time_parse": (layout, val) => {
2331
+ try {
2332
+ const d = new Date(val ?? "");
2333
+ if (isNaN(d.getTime())) throw 0;
2334
+ timeParseErr = null;
2335
+ timeParseOffset = -d.getTimezoneOffset() * 60;
2336
+ return d.getTime();
2337
+ } catch {
2338
+ timeParseErr = "parsing time: cannot parse";
2339
+ timeParseOffset = 0;
2340
+ return 0;
2341
+ }
2342
+ },
2343
+ "time_parse_err": () => timeParseErr,
2344
+ "time_parse_offset": () => timeParseOffset,
2345
+ "time_format": (y, m, d, hr, min, sec, wd, ms, offsetSec, layout) => {
2346
+ const DAYS = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];
2347
+ const MONTHS = ["January","February","March","April","May","June","July","August","September","October","November","December"];
2348
+ const pad = (n, w = 2) => String(n).padStart(w, "0");
2349
+ return (layout ?? "").replace(/2006|06|Monday|Mon|January|Jan|01|02|15|PM|pm|03|04|05|Z07:00|\\.(?:0+|9+)|1|2|3|4|5/g, t => {
2350
+ if (t.startsWith(".")) {
2351
+ const isZeroPad = t.includes("0");
2352
+ const maxLen = t.length - 1;
2353
+ let frac = pad(ms % 1000, 3);
2354
+ if (maxLen > 3) frac = frac.padEnd(maxLen, "0");
2355
+ else frac = frac.slice(0, maxLen);
2356
+ if (!isZeroPad) {
2357
+ frac = frac.replace(/0+$/, "");
2358
+ return frac.length > 0 ? "." + frac : "";
2359
+ }
2360
+ return "." + frac;
2361
+ }
2362
+ switch (t) {
2363
+ case "2006": return String(y);
2364
+ case "06": return String(y).slice(-2);
2365
+ case "Monday": return DAYS[wd];
2366
+ case "Mon": return DAYS[wd].slice(0, 3);
2367
+ case "January": return MONTHS[m - 1];
2368
+ case "Jan": return MONTHS[m - 1].slice(0, 3);
2369
+ case "01": return pad(m);
2370
+ case "1": return String(m);
2371
+ case "02": return pad(d);
2372
+ case "2": return String(d);
2373
+ case "15": return pad(hr);
2374
+ case "PM": return hr < 12 ? "AM" : "PM";
2375
+ case "pm": return hr < 12 ? "am" : "pm";
2376
+ case "03": return pad(hr % 12 || 12);
2377
+ case "3": return String(hr % 12 || 12);
2378
+ case "04": return pad(min);
2379
+ case "4": return String(min);
2380
+ case "05": return pad(sec);
2381
+ case "5": return String(sec);
2382
+ case "Z07:00": {
2383
+ const o = Math.floor(offsetSec / 60);
2384
+ return o === 0 ? "Z" : (o > 0 ? "+" : "-") + pad(Math.floor(Math.abs(o) / 60)) + ":" + pad(Math.abs(o) % 60);
2385
+ }
2386
+ default: return t;
2387
+ }
2388
+ });
2389
+ },
2390
+ "raw_bytes_init": (len) => {
2391
+ rawBytes = new Uint8Array(len);
2392
+ rawPos = 0;
2393
+ },
2394
+ "raw_bytes_push": (b) => {
2395
+ if (rawPos < rawBytes.length) {
2396
+ rawBytes[rawPos++] = b & 0xff;
2397
+ }
2398
+ },
2399
+ "raw_bytes_len": () => rawBytes.length,
2400
+ "raw_bytes_get": (i) => rawBytes[i] ?? 0,
2401
+ "crypto_rand_fill": (len) => {
2402
+ rawBytes = new Uint8Array(len);
2403
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
2404
+ const CHUNK = 65536;
2405
+ for (let offset = 0; offset < len; offset += CHUNK) {
2406
+ crypto.getRandomValues(rawBytes.subarray(offset, Math.min(offset + CHUNK, len)));
2407
+ }
2408
+ } else {
2409
+ for (let i = 0; i < len; i++) rawBytes[i] = Math.floor(Math.random() * 256);
2410
+ }
2411
+ return len;
2412
+ },
2413
+ "crypto_sha256_sum": () => {
2414
+ const d = __sha256.Sum256(rawBytes);
2415
+ rawBytes = new Uint8Array(d);
2416
+ return 32;
2417
+ },
2418
+ "hex_encode_to_string": () => {
2419
+ return __hex_encode_to_string(rawBytes);
2420
+ },
2421
+ "hex_decode_string": (s) => {
2422
+ const [res, err] = __hex_decode_string(s ?? "");
2423
+ hexErr = err ? (err.message ?? String(err)) : null;
2424
+ rawBytes = new Uint8Array(res);
2425
+ return rawBytes.length;
2426
+ },
2427
+ "hex_get_err": () => hexErr,
2428
+ "base64_new_encoding": (alphabet) => {
2429
+ const id = customBase64Encodings.length + 4;
2430
+ customBase64Encodings.push(__base64_make(alphabet ?? "", "="));
2431
+ return id;
2432
+ },
2433
+ "base64_encode_to_string": (mode) => {
2434
+ const enc = mode === 1 ? __base64_url : mode === 2 ? __base64_raw_std : mode === 3 ? __base64_raw_url : mode >= 4 ? customBase64Encodings[mode - 4] : __base64_std;
2435
+ return (enc ?? __base64_std).EncodeToString(rawBytes);
2436
+ },
2437
+ "base64_decode_string": (mode, s) => {
2438
+ const enc = mode === 1 ? __base64_url : mode === 2 ? __base64_raw_std : mode === 3 ? __base64_raw_url : mode >= 4 ? customBase64Encodings[mode - 4] : __base64_std;
2439
+ const [res, err] = (enc ?? __base64_std).DecodeString(s ?? "");
2440
+ base64Err = err ? (err.message ?? String(err)) : null;
2441
+ rawBytes = new Uint8Array(res);
2442
+ return rawBytes.length;
2443
+ },
2444
+ "base64_get_err": () => base64Err,
1381
2445
  };
1382
2446
  }
1383
2447
  function ${P}trim(s, cutset, left, right) {
1384
- const cs = Array.from(cutset);
2448
+ const cs = new Set(Array.from(cutset));
1385
2449
  const chars = Array.from(s);
1386
2450
  let a = 0, b = chars.length;
1387
- if (left) while (a < b && cs.includes(chars[a])) a++;
1388
- if (right) while (b > a && cs.includes(chars[b - 1])) b--;
2451
+ if (left) while (a < b && cs.has(chars[a])) a++;
2452
+ if (right) while (b > a && cs.has(chars[b - 1])) b--;
1389
2453
  return chars.slice(a, b).join("");
1390
2454
  }`;
1391
2455
 
@@ -1512,6 +2576,39 @@ export function generateFacade(
1512
2576
  }
1513
2577
  }
1514
2578
 
2579
+ for (const [name, desc] of needs.anonStructs) {
2580
+ out.push(`function ${P}to_${name}(o) {
2581
+ if (o == null) return __w.__zero_${name}();
2582
+ return __w.__new_${name}(${desc.fields.map((f) => inExpr(f.desc, `(o.${f.name} ?? ${zeroExpr(f.desc)})`)).join(", ")});
2583
+ }
2584
+ function ${P}from_${name}(r) {
2585
+ if (r == null) return null;
2586
+ return {
2587
+ ${desc.fields.map((f) => `\t\t"${f.name}": ${outExpr(f.desc, `__w.__get_${name}_${f.name}(r)`)}`).join(",\n")}
2588
+ };
2589
+ }
2590
+ function ${P}back_${name}(r, o) {
2591
+ if (r == null || o == null) return;
2592
+ ${desc.fields.map((f) => `\to.${f.name} = ${outExpr(f.desc, `__w.__get_${name}_${f.name}(r)`)};`).join("\n")}
2593
+ }`);
2594
+ }
2595
+
2596
+ for (const [key, desc] of needs.boxes) {
2597
+ out.push(`function ${P}boxin_${key}(b) {
2598
+ if (b == null) return null;
2599
+ const v = b.value !== undefined ? b.value : ${zeroExpr(desc.base)};
2600
+ return __w.__box_new_${key}(${inExpr(desc.base, "v")});
2601
+ }
2602
+ function ${P}boxout_${key}(r) {
2603
+ if (r == null) return null;
2604
+ return { "value": ${outExpr(desc.base, `__w.__box_get_${key}(r)`)} };
2605
+ }
2606
+ function ${P}boxback_${key}(r, b) {
2607
+ if (r == null || b == null) return;
2608
+ b.value = ${outExpr(desc.base, `__w.__box_get_${key}(r)`)};
2609
+ }`);
2610
+ }
2611
+
1515
2612
  for (const [key, desc] of needs.slices) {
1516
2613
  out.push(`function ${P}slin_${key}(arr) {
1517
2614
  if (arr == null) return __w.__slice_nil_${key}();
@@ -1530,8 +2627,34 @@ function ${P}slout_${key}(s) {
1530
2627
  function ${P}slview_${key}(s) {
1531
2628
  if (s == null || __w.__slice_is_nil_${key}(s)) return null;
1532
2629
  return ${P}idxview(__w.__slice_len_${key}(s), (i) => ${viewExpr(desc.elem, `__w.__slice_get_${key}(s, i)`)}, (i, v) => __w.__slice_set_${key}(s, i, ${inExpr(desc.elem, "v")}));
2630
+ } `);
2631
+ }
2632
+
2633
+ for (const [key, desc] of needs.maps) {
2634
+ out.push(`function ${P}mapin_${key}(m) {
2635
+ if (m == null) return null;
2636
+ const entries = m instanceof Map ? m.entries() : Object.entries(m);
2637
+ const size = m instanceof Map ? m.size : Object.keys(m).length;
2638
+ const r = __w.__map_new_${key}(size);
2639
+ for (const [k, v] of entries) {
2640
+ __w.__map_set_${key}(r, ${desc.keyDesc.k === "bool" ? `(k === "true" || k === true ? 1 : 0)` : inExpr(desc.keyDesc, "k")}, ${inExpr(desc.valDesc, "v")});
2641
+ }
2642
+ return r;
2643
+ }
2644
+ function ${P}mapout_${key}(m) {
2645
+ if (m == null) return null;
2646
+ const ks = ${P}slout_${desc.keySliceDesc.key}(__w.__map_keys_${key}(m));
2647
+ const vs = ${P}slout_${desc.valSliceDesc.key}(__w.__map_values_${key}(m));
2648
+ const out = new Map();
2649
+ if (ks) {
2650
+ for (let i = 0; i < ks.length; i++) {
2651
+ out.set(ks[i], vs[i]);
2652
+ }
2653
+ }
2654
+ return out;
1533
2655
  }`);
1534
2656
  }
2657
+
1535
2658
  for (const [key, desc] of needs.arrays) {
1536
2659
  out.push(`function ${P}arrzero_${key}(n) {
1537
2660
  const out = new Array(n);
@@ -1555,7 +2678,7 @@ function ${P}arrout_${key}(a) {
1555
2678
  function ${P}arrview_${key}(a) {
1556
2679
  if (a == null) return null;
1557
2680
  return ${P}idxview(__w.__array_len_${key}(a), (i) => ${viewExpr(desc.elem, `__w.__array_get_${key}(a, i)`)}, (i, v) => __w.__array_set_${key}(a, i, ${inExpr(desc.elem, "v")}));
1558
- }`);
2681
+ } `);
1559
2682
  }
1560
2683
 
1561
2684
  for (const [key, desc] of needs.funcs) {
@@ -1594,6 +2717,59 @@ ${indent(body, "\t")}
1594
2717
 
1595
2718
  for (const [key, desc] of needs.ifaces) genIfaceFacade(out, key, desc);
1596
2719
 
2720
+ if (needs.error) {
2721
+ out.push(`class ${P}Error extends Error {
2722
+ constructor(msg, ref) {
2723
+ super(msg);
2724
+ this.name = "GoError";
2725
+ this._msg = msg;
2726
+ this.__ref = ref;
2727
+ }
2728
+ Error() {
2729
+ return this.message;
2730
+ }
2731
+ }
2732
+ const ${P}err_w2j = new WeakMap();
2733
+ function ${P}errout(r) {
2734
+ if (r == null) return null;
2735
+ let e = ${P}err_w2j.get(r);
2736
+ if (!e) {
2737
+ const msg = __w.__error_msg ? (__w.__error_msg(r) ?? "") : "";
2738
+ e = new ${P}Error(msg, r);
2739
+ ${P}err_w2j.set(r, e);
2740
+ }
2741
+ return e;
2742
+ }
2743
+ function ${P}errin(e) {
2744
+ if (e == null) return null;
2745
+ if (e.__ref === undefined) throw new TypeError("GoFront: expected a WASM-owned error");
2746
+ if (!__w.__is_error(e.__ref)) throw new TypeError("GoFront: value does not implement error");
2747
+ return e.__ref;
2748
+ }`);
2749
+ }
2750
+
2751
+ if (needs.time) {
2752
+ out.push(`function ${P}timeout(r) {
2753
+ if (r == null) return null;
2754
+ const ms = __w.__time_unix_milli ? __w.__time_unix_milli(r) : 0;
2755
+ const off = __w.__time_offset ? __w.__time_offset(r) : 0;
2756
+ return { _d: new Date(ms), _offset: off };
2757
+ }
2758
+ function ${P}timeout_ptr(r) {
2759
+ return ${P}timeout(r);
2760
+ }
2761
+ function ${P}timein(v) {
2762
+ if (v == null) return null;
2763
+ const d = v._d ? v._d : (v instanceof Date ? v : new Date(v));
2764
+ const ms = d.getTime();
2765
+ const off = typeof v?._offset === "number" ? v._offset : -d.getTimezoneOffset() * 60;
2766
+ return __w.__time_make ? __w.__time_make(ms, off) : null;
2767
+ }
2768
+ function ${P}timein_ptr(v) {
2769
+ return ${P}timein(v);
2770
+ }`);
2771
+ }
2772
+
1597
2773
  if (needs.shared) {
1598
2774
  // Zero-copy TypedArray views over the exported memory. Memory never
1599
2775
  // grows after the start function (allocation is startup-only), but the
@@ -1615,7 +2791,15 @@ function ${P}shin(v, Ctor) {
1615
2791
  }`);
1616
2792
  }
1617
2793
 
1618
- for (const c of meta.consts) out.push(`const ${c.name} = ${c.value};`);
2794
+ for (const c of meta.consts) {
2795
+ if (c.dynamic) {
2796
+ out.push(
2797
+ `const ${c.name} = ${outExpr(c.desc, `__w.__const_${c.name}()`)};`,
2798
+ );
2799
+ } else {
2800
+ out.push(`const ${c.name} = ${c.value};`);
2801
+ }
2802
+ }
1619
2803
  for (const v of meta.vars ?? [])
1620
2804
  out.push(
1621
2805
  `const ${v.name} = ${P}shout(__w.__var_${v.name}(), ${v.desc.ctor});`,