gofront 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +174 -403
  2. package/package.json +16 -19
  3. package/src/{codegen → backend/js}/expressions.js +265 -77
  4. package/src/{codegen → backend/js}/index.js +120 -89
  5. package/src/{codegen → backend/js}/runtime.js +102 -0
  6. package/src/{codegen → backend/js}/source-map.js +32 -0
  7. package/src/{codegen → backend/js}/statements.js +139 -64
  8. package/src/backend/js/stdlib/bits.js +32 -0
  9. package/src/{codegen → backend/js}/stdlib/errors.js +2 -2
  10. package/src/{codegen → backend/js}/stdlib/fmt.js +26 -8
  11. package/src/{codegen → backend/js}/stdlib/index.js +6 -0
  12. package/src/{codegen → backend/js}/stdlib/maps.js +1 -1
  13. package/src/{codegen → backend/js}/stdlib/path.js +2 -2
  14. package/src/backend/js/stdlib/shared.js +17 -0
  15. package/src/{codegen → backend/js}/stdlib/slices.js +1 -1
  16. package/src/{codegen → backend/js}/stdlib/sort.js +1 -1
  17. package/src/backend/js/stdlib/strconv.js +58 -0
  18. package/src/{codegen → backend/js}/stdlib/strings.js +4 -1
  19. package/src/{codegen → backend/js}/stdlib/testing.js +2 -2
  20. package/src/{codegen → backend/js}/stdlib/time.js +3 -3
  21. package/src/{codegen → backend/js}/templ.js +3 -3
  22. package/src/backend/wasm/boundary.js +1798 -0
  23. package/src/backend/wasm/emit-builtins.js +1103 -0
  24. package/src/backend/wasm/emit-exprs.js +1908 -0
  25. package/src/backend/wasm/emit-maps.js +1863 -0
  26. package/src/backend/wasm/emit-shared.js +519 -0
  27. package/src/backend/wasm/emit-stdlib.js +3006 -0
  28. package/src/backend/wasm/emit-stmts.js +2472 -0
  29. package/src/backend/wasm/emit.js +1376 -0
  30. package/src/backend/wasm/encode.js +937 -0
  31. package/src/backend/wasm/glue.js +144 -0
  32. package/src/backend/wasm/index.js +2790 -0
  33. package/src/backend/wasm/monomorph.js +711 -0
  34. package/src/backend/wasm/optimize.js +227 -0
  35. package/src/backend/wasm/types.js +544 -0
  36. package/src/backend/wasm/wat.js +302 -0
  37. package/src/cli-core.js +323 -75
  38. package/src/compiler.js +447 -14
  39. package/src/dev-server.js +56 -15
  40. package/src/index.js +99 -120
  41. package/src/lexer.js +18 -0
  42. package/src/lower/boxing.js +35 -0
  43. package/src/lower/captures.js +172 -0
  44. package/src/lower/embedding.js +30 -0
  45. package/src/lower/escape.js +164 -0
  46. package/src/lower/functions.js +302 -0
  47. package/src/lower/globals.js +316 -0
  48. package/src/lower/index.js +118 -0
  49. package/src/lower/ownership.js +137 -0
  50. package/src/lower/range.js +67 -0
  51. package/src/lower/walk.js +25 -0
  52. package/src/minifier.js +2 -0
  53. package/src/parser/index.js +8 -3
  54. package/src/resolver.js +3 -1
  55. package/src/test-runner.js +94 -20
  56. package/src/typechecker/assignability.js +42 -1
  57. package/src/typechecker/expressions.js +182 -3
  58. package/src/typechecker/index.js +261 -5
  59. package/src/typechecker/resolve.js +35 -6
  60. package/src/typechecker/stdlib/core.js +34 -19
  61. package/src/typechecker/stdlib/extended.js +18 -3
  62. package/src/typechecker/stdlib/shared.js +53 -0
  63. package/src/typechecker/stdlib.js +2 -0
  64. package/src/typechecker/types.js +259 -6
  65. package/src/vendor.js +2 -2
  66. package/src/codegen/stdlib/strconv.js +0 -33
  67. /package/src/{codegen → backend/js}/stdlib/builder.js +0 -0
  68. /package/src/{codegen → backend/js}/stdlib/bytes.js +0 -0
  69. /package/src/{codegen → backend/js}/stdlib/gom.js +0 -0
  70. /package/src/{codegen → backend/js}/stdlib/html.js +0 -0
  71. /package/src/{codegen → backend/js}/stdlib/io.js +0 -0
  72. /package/src/{codegen → backend/js}/stdlib/math.js +0 -0
  73. /package/src/{codegen → backend/js}/stdlib/os.js +0 -0
  74. /package/src/{codegen → backend/js}/stdlib/rand.js +0 -0
  75. /package/src/{codegen → backend/js}/stdlib/regexp.js +0 -0
  76. /package/src/{codegen → backend/js}/stdlib/unicode.js +0 -0
  77. /package/src/{codegen → backend/js}/stdlib/utf8.js +0 -0
@@ -1,6 +1,6 @@
1
1
  // Code generator: walks the typed AST and emits clean JavaScript.
2
2
  //
3
- // Split into sub-modules under codegen/:
3
+ // Split into sub-modules under backend/js/:
4
4
  // source-map.js — VLQ encoder and source map builder
5
5
  // statements.js — genBlock, genStmt, genFor, genSwitch, etc.
6
6
  // expressions.js — genExpr, genCall, genCompositeLit, helpers
@@ -17,7 +17,14 @@
17
17
  // - make(map[K]V) → {}
18
18
  // - for range → for...of with .entries()
19
19
 
20
- import { isComplex, isNumeric } from "../typechecker/types.js";
20
+ import {
21
+ computeEmbeddedStubs,
22
+ hasDirectRecover,
23
+ isReferenceType,
24
+ normalizeDefers,
25
+ scanAddressTaken,
26
+ } from "../../lower/index.js";
27
+ import { isComplex, isNumeric } from "../../typechecker/types.js";
21
28
  import { expressionGenMethods } from "./expressions.js";
22
29
  import {
23
30
  HELPER_APPEND,
@@ -29,20 +36,25 @@ import {
29
36
  HELPER_IFACE_BOX,
30
37
  HELPER_INJECT_STYLES,
31
38
  HELPER_LEN,
39
+ HELPER_NIL_CALL,
40
+ HELPER_PANIC,
32
41
  HELPER_PATH_CLEAN,
33
42
  HELPER_S,
34
43
  HELPER_SCLONE,
35
44
  HELPER_SORT_SLICE,
36
45
  HELPER_SPRINTF,
46
+ HELPER_STRCONV,
37
47
  HELPER_TESTING,
38
48
  HELPER_TIME_FMT,
39
49
  HELPER_TIME_PARSE,
40
50
  } from "./runtime.js";
41
- import { buildSourceMap } from "./source-map.js";
51
+ import { buildSourceMap, buildWasmSourceMap } from "./source-map.js";
42
52
  import { statementGenMethods } from "./statements.js";
43
53
  import { stdlibGenMethods } from "./stdlib/index.js";
44
54
  import { templGenMethods } from "./templ.js";
45
55
 
56
+ export { buildSourceMap, buildWasmSourceMap };
57
+
46
58
  // Valid Go identifiers that cannot be used as JS bindings; emitted with a `$` suffix.
47
59
  const JS_RESERVED = new Set([
48
60
  "arguments",
@@ -83,6 +95,30 @@ const JS_RESERVED = new Set([
83
95
  const jsSafeName = (n) =>
84
96
  typeof n === "string" && JS_RESERVED.has(n) ? `${n}$` : n;
85
97
 
98
+ // Runtime helpers by registry name, in output order. Mark one as needed with
99
+ // `this.useHelper(name)`; `_prependHelpers` emits every marked entry.
100
+ const HELPER_MAP = [
101
+ ["len", HELPER_LEN],
102
+ ["append", HELPER_APPEND],
103
+ ["sliceGuard", HELPER_S],
104
+ ["sclone", HELPER_SCLONE],
105
+ ["ifaceBox", HELPER_IFACE_BOX],
106
+ ["equal", HELPER_EQUAL],
107
+ ["cmul", HELPER_CMUL],
108
+ ["cdiv", HELPER_CDIV],
109
+ ["panicRt", HELPER_PANIC],
110
+ ["nilCall", HELPER_NIL_CALL],
111
+ ["sprintf", HELPER_SPRINTF],
112
+ ["error", HELPER_ERROR],
113
+ ["errorIs", HELPER_ERROR_IS],
114
+ ["strconv", HELPER_STRCONV],
115
+ ["pathClean", HELPER_PATH_CLEAN],
116
+ ["sortSlice", HELPER_SORT_SLICE],
117
+ ["timeFmt", HELPER_TIME_FMT],
118
+ ["timeParse", HELPER_TIME_PARSE],
119
+ ["testing", HELPER_TESTING],
120
+ ];
121
+
86
122
  export class CodeGen {
87
123
  // jsImports: Map<importPath, string[]> — npm package imports to emit at top of file
88
124
  // bundledPackages: Set<string> — GoFront package names bundled inline;
@@ -91,8 +127,11 @@ export class CodeGen {
91
127
  checker = null,
92
128
  jsImports = new Map(),
93
129
  bundledPackages = new Set(),
130
+ options = {},
94
131
  ) {
95
132
  this.checker = checker;
133
+ this.target = options.target ?? "js";
134
+ this.strict = Boolean(options.strict || this.target === "both");
96
135
  this.out = [];
97
136
  this.indent = 0;
98
137
  this.structNames = new Set();
@@ -106,22 +145,7 @@ export class CodeGen {
106
145
  this._currentSrcFileIdx = 0; // updated as each top-level decl is generated
107
146
  this._boxedVars = new Set(); // address-taken scalar variables that need boxing
108
147
  // Runtime helper usage tracking — only emit helpers that are actually used
109
- this._usesLen = false;
110
- this._usesAppend = false;
111
- this._usesSliceGuard = false;
112
- this._usesSprintf = false;
113
- this._usesEqual = false;
114
- this._usesCmul = false;
115
- this._usesCdiv = false;
116
- this._usesError = false;
117
- this._usesErrorIs = false;
118
- this._usesPathClean = false;
119
- this._usesSortSlice = false;
120
- this._usesTimeFmt = false;
121
- this._usesTimeParse = false;
122
- this._usesTesting = false;
123
- this._usesSClone = false;
124
- this._usesIfaceBox = false;
148
+ this._helpers = new Set();
125
149
  // Per-function context for Go value semantics (see _withFnCtx)
126
150
  this._fnCtx = null;
127
151
  // Iterator (range-over-func) context
@@ -207,6 +231,10 @@ export class CodeGen {
207
231
 
208
232
  generate(program, options = {}) {
209
233
  const isTest = options.isTest ?? false;
234
+ // Defer lowering (argument/receiver hoisting, recover-target marking) is
235
+ // idempotent and owned by the backends: the wasm path runs it via
236
+ // `lower()`, the JS path here.
237
+ normalizeDefers(program);
210
238
  // Ensure _pkgName is set on all decls (generateAll sets it for
211
239
  // multi-file; for single-file compiles we derive it from the AST).
212
240
  const pkgName = program.pkg?.name;
@@ -397,28 +425,21 @@ export class CodeGen {
397
425
  return methods;
398
426
  }
399
427
 
428
+ /** Marks a runtime helper (key of HELPER_MAP) as needed by the output. */
429
+ useHelper(name) {
430
+ this._helpers.add(name);
431
+ }
432
+
400
433
  _prependHelpers(isTest = false) {
401
- const needsTesting = isTest || this._usesTesting;
402
- const HELPER_MAP = [
403
- [this.collectedCss.length > 0, HELPER_INJECT_STYLES],
404
- [this._usesLen, HELPER_LEN],
405
- [this._usesAppend, HELPER_APPEND],
406
- [this._usesSliceGuard, HELPER_S],
407
- [this._usesSClone, HELPER_SCLONE],
408
- [this._usesIfaceBox, HELPER_IFACE_BOX],
409
- [this._usesEqual, HELPER_EQUAL],
410
- [this._usesCmul, HELPER_CMUL],
411
- [this._usesCdiv, HELPER_CDIV],
412
- [this._usesSprintf || needsTesting, HELPER_SPRINTF],
413
- [this._usesError, HELPER_ERROR],
414
- [this._usesErrorIs, HELPER_ERROR_IS],
415
- [this._usesPathClean, HELPER_PATH_CLEAN],
416
- [this._usesSortSlice, HELPER_SORT_SLICE],
417
- [this._usesTimeFmt, HELPER_TIME_FMT],
418
- [this._usesTimeParse, HELPER_TIME_PARSE],
419
- [needsTesting, HELPER_TESTING],
420
- ];
421
- const helpers = HELPER_MAP.filter(([flag]) => flag).map(([, h]) => h);
434
+ if (isTest || this._helpers.has("testing")) {
435
+ this.useHelper("sprintf");
436
+ this.useHelper("testing");
437
+ }
438
+ const helpers = [];
439
+ if (this.collectedCss.length > 0) helpers.push(HELPER_INJECT_STYLES);
440
+ for (const [name, src] of HELPER_MAP) {
441
+ if (this._helpers.has(name)) helpers.push(src);
442
+ }
422
443
  if (helpers.length > 0) {
423
444
  const helperLines = helpers.flatMap((h) => h.split("\n"));
424
445
  helperLines.push("");
@@ -621,12 +642,13 @@ export class CodeGen {
621
642
  }
622
643
 
623
644
  _genEmbeddedMethodStubs(name, methodDecls) {
624
- if (!this.checker) return;
625
- const resolvedType = this.checker.types.get(name)?.underlying;
626
- if (resolvedType?.kind !== "struct" || !resolvedType._embeds) return;
627
- const declared = new Set(methodDecls.map((m) => m.name));
628
- for (const embed of resolvedType._embeds)
629
- this._genSingleEmbedStubs(embed, declared);
645
+ const stubs = computeEmbeddedStubs(name, methodDecls, this.checker);
646
+ for (const stub of stubs) {
647
+ this.blank();
648
+ this.line(
649
+ `${stub.methodName}(...__a) { return ${stub.embedName}.prototype.${stub.methodName}.call(this, ...__a); }`,
650
+ );
651
+ }
630
652
  }
631
653
 
632
654
  genMethod(decl, recvField = null) {
@@ -650,7 +672,7 @@ export class CodeGen {
650
672
  }
651
673
  }
652
674
  this._emitParamCopies(decl.params);
653
- this._withNamedReturns(decl, () => this._genBody(decl.body));
675
+ this._withNamedReturns(decl, () => this._genBody(decl.body, decl));
654
676
  }),
655
677
  );
656
678
  this._unwrappedRecv = prevUnwrapped;
@@ -707,7 +729,7 @@ export class CodeGen {
707
729
  this._withFnCtx(decl.body, () =>
708
730
  this.indented(() => {
709
731
  this._emitParamCopies(decl.params);
710
- this._withNamedReturns(decl, () => this._genBody(decl.body));
732
+ this._withNamedReturns(decl, () => this._genBody(decl.body, decl));
711
733
  }),
712
734
  );
713
735
  this._boxedVars = prevBoxed;
@@ -715,45 +737,74 @@ export class CodeGen {
715
737
  }
716
738
 
717
739
  _withNamedReturns(decl, fn) {
740
+ const prevDecl = this._currentDecl;
741
+ this._currentDecl = decl;
718
742
  const named = decl.returnType?._namedReturns;
719
743
  const prev = this.namedReturnVars;
720
744
  if (named) {
721
745
  // Emit zero-value declarations for named return vars
722
- for (const { name, type } of named) {
723
- if (name) {
724
- this._markOwnership(name, true);
725
- this.line(`let ${name} = ${this.zeroValueForTypeNode(type)};`);
726
- }
746
+ for (let i = 0; i < named.length; i++) {
747
+ const { name, type } = named[i];
748
+ const varName = !name || name === "_" ? `__ret_blank$${i}` : name;
749
+ this._markOwnership(varName, true);
750
+ this.line(`let ${varName} = ${this.zeroValueForTypeNode(type)};`);
727
751
  }
728
- this.namedReturnVars = named.map((r) => r.name).filter(Boolean);
752
+ this.namedReturnVars = named.map((r, i) =>
753
+ !r.name || r.name === "_" ? `__ret_blank$${i}` : r.name,
754
+ );
729
755
  } else {
730
756
  this.namedReturnVars = null;
731
757
  }
732
758
  fn();
733
759
  this.namedReturnVars = prev;
760
+ this._currentDecl = prevDecl;
734
761
  }
735
762
 
736
763
  // Emit a function body, wrapping in try/catch/finally for defer if needed.
737
- _genBody(body) {
764
+ // `fnNode` is the enclosing FuncDecl/MethodDecl/FuncLit; it decides whether
765
+ // the recover-arming prologue is needed (see HELPER_PANIC).
766
+ _genBody(body, fnNode = null) {
767
+ const prevRecoverOk = this._recoverOkInScope;
768
+ this._recoverOkInScope = false;
769
+ const isTarget = Boolean(fnNode?._isDeferTarget);
770
+ const forwards = isTarget && Boolean(fnNode._deferForward);
771
+ if (!forwards && (isTarget || hasDirectRecover(body))) {
772
+ this.useHelper("panicRt");
773
+ this.line(
774
+ "const __recoverOk = __gopanic.armed; __gopanic.armed = false;",
775
+ );
776
+ this._recoverOkInScope = true;
777
+ }
738
778
  if (!body._hasDefer) {
739
779
  this.genBlock(body);
780
+ this._recoverOkInScope = prevRecoverOk;
740
781
  return;
741
782
  }
783
+ this.useHelper("panicRt");
742
784
  this.line("const __defers = [];");
743
- this.line("let __panic = null;");
785
+ this.line("const __frame = { pn: null };");
744
786
  this.line("try {");
745
787
  this.indented(() => this.genBlock(body));
746
788
  this.line("} catch (__err) {");
747
- this.indented(() => this.line("__panic = __err;"));
789
+ this.indented(() => {
790
+ this.line("__frame.pn = { err: __err, recovered: false };");
791
+ this.line("__gopanic.stack.push(__frame.pn);");
792
+ });
748
793
  this.line("} finally {");
749
794
  this.indented(() => {
750
- this.line(
751
- "for (let __i = __defers.length - 1; __i >= 0; __i--) __defers[__i]();",
752
- );
753
- this.line("if (__panic !== null) throw __panic;");
795
+ // Runs defers LIFO and rethrows an unrecovered panic.
796
+ this.line("__runDefers(__defers, __frame);");
797
+ if (this.namedReturnVars?.length > 0) {
798
+ const vars = this.namedReturnVars;
799
+ this.line(
800
+ vars.length === 1
801
+ ? `return ${vars[0]};`
802
+ : `return [${vars.join(", ")}];`,
803
+ );
804
+ }
754
805
  });
755
806
  this.line("}");
756
- // If a recover() cleared __panic, execution reaches here.
807
+ // If a recover() cleared the panic, execution reaches here.
757
808
  // Return named return vars so deferred mutations are visible to the caller.
758
809
  if (this.namedReturnVars?.length > 0) {
759
810
  const vars = this.namedReturnVars;
@@ -762,41 +813,21 @@ export class CodeGen {
762
813
  ? `return ${vars[0]};`
763
814
  : `return [${vars.join(", ")}];`,
764
815
  );
816
+ } else if (this._currentDecl?.returnType) {
817
+ this.line(
818
+ `return ${this.zeroValueForTypeNode(this._currentDecl.returnType)};`,
819
+ );
765
820
  }
821
+ this._recoverOkInScope = prevRecoverOk;
766
822
  }
767
823
 
768
824
  // Scan AST node for _addressTaken idents on scalars and populate _boxedVars.
769
825
  _scanAddressTaken(node) {
770
- if (!node || typeof node !== "object") return;
771
- if (Array.isArray(node)) {
772
- for (const child of node) this._scanAddressTaken(child);
773
- return;
774
- }
775
- // &x — the operand ident will have _addressTaken set by typechecker
776
- if (node.kind === "Ident" && node._addressTaken) {
777
- // Check if the type is a scalar (needs boxing) vs reference type (no boxing)
778
- const t = node._type;
779
- if (t && !this._isReferenceType(t)) {
780
- this._boxedVars.add(node.name);
781
- }
782
- }
783
- // Recurse into FuncLit too — closures may take address of outer vars
784
- for (const key of Object.keys(node)) {
785
- if (key.startsWith("_")) continue;
786
- this._scanAddressTaken(node[key]);
787
- }
826
+ scanAddressTaken(node, this._boxedVars);
788
827
  }
789
828
 
790
829
  _isReferenceType(t) {
791
- if (!t) return false;
792
- const base = t.kind === "named" ? t.underlying : t;
793
- return (
794
- base?.kind === "struct" ||
795
- base?.kind === "slice" ||
796
- base?.kind === "map" ||
797
- base?.kind === "func" ||
798
- base?.kind === "interface"
799
- );
830
+ return isReferenceType(t);
800
831
  }
801
832
 
802
833
  // ── Variable / const declarations ────────────────────────────
@@ -55,6 +55,13 @@ var __ifp = __ifp || function(x) {
55
55
  return x;
56
56
  };`;
57
57
 
58
+ // Nil-interface method call guard (strict mode only): matches Go's panic
59
+ // message instead of a JS TypeError.
60
+ export const HELPER_NIL_CALL = `var __nilcall = __nilcall || function(x) {
61
+ if (x == null) throw new Error("runtime error: invalid memory address or nil pointer dereference");
62
+ return x;
63
+ };`;
64
+
58
65
  export const HELPER_EQUAL = `var __equal = __equal || function __equal(a, b) {
59
66
  if (a === b) return true;
60
67
  if (a === null || b === null) return false;
@@ -86,6 +93,35 @@ export const HELPER_CDIV = `var __cdiv = __cdiv || function(a, b) {
86
93
  return { re: (a.re * b.re + a.im * b.im) / d, im: (a.im * b.re - a.re * b.im) / d };
87
94
  };`;
88
95
 
96
+ // Panic/defer runtime. \`stack\` holds the in-flight panic of every frame
97
+ // currently running its defers; \`armed\` is set right before a deferred
98
+ // function is invoked and consumed by that function's prologue, so recover()
99
+ // only takes effect when called directly by the deferred function (Go spec).
100
+ export const HELPER_PANIC = `var __gopanic = __gopanic || { stack: [], armed: false };
101
+ var __runDefers = __runDefers || function(defers, frame) {
102
+ for (let i = defers.length - 1; i >= 0; i--) {
103
+ __gopanic.armed = true;
104
+ try { defers[i](); }
105
+ catch (e) {
106
+ if (frame.pn !== null) __gopanic.stack.pop();
107
+ frame.pn = { err: e, recovered: false };
108
+ __gopanic.stack.push(frame.pn);
109
+ } finally { __gopanic.armed = false; }
110
+ }
111
+ if (frame.pn !== null) {
112
+ __gopanic.stack.pop();
113
+ if (!frame.pn.recovered) throw frame.pn.err;
114
+ }
115
+ };
116
+ var __recover = __recover || function(ok) {
117
+ if (!ok) return null;
118
+ const s = __gopanic.stack;
119
+ const top = s.length > 0 ? s[s.length - 1] : null;
120
+ if (top === null || top.recovered) return null;
121
+ top.recovered = true;
122
+ return top.err?.message ?? String(top.err);
123
+ };`;
124
+
89
125
  export const HELPER_SPRINTF = `var __sprintf = __sprintf || function(f, ...a) {
90
126
  let i = 0;
91
127
  return f.replace(/%([#+\\- 0]*)([0-9]*)\\.?([0-9]*)[sdvftxXqobeEgGw%]/g, (m) => {
@@ -150,6 +186,72 @@ export const HELPER_ERROR_IS = `var __errorIs = __errorIs || function(err, targe
150
186
  return false;
151
187
  };`;
152
188
 
189
+ // Go-semantics strconv parsers shared by the JS backend and the wasm runtime
190
+ // imports (boundary.js), so both targets agree. Each parser returns
191
+ // [value, errorMessage | null]; integer values are BigInt (the wasm side
192
+ // needs i64, the JS side converts with Number()).
193
+ export const HELPER_STRCONV = `var __strconv_atoi = __strconv_atoi || function(s) {
194
+ if (!/^[+-]?[0-9]+$/.test(s)) return [0n, 'strconv.Atoi: parsing "' + s + '": invalid syntax'];
195
+ const v = BigInt(s);
196
+ if (v > 9223372036854775807n || v < -9223372036854775808n) {
197
+ return [v > 0n ? 9223372036854775807n : -9223372036854775808n, 'strconv.Atoi: parsing "' + s + '": value out of range'];
198
+ }
199
+ return [v, null];
200
+ };
201
+ var __strconv_parse_int = __strconv_parse_int || function(s, base, bits) {
202
+ let str = s, neg = false;
203
+ if (str[0] === "-" || str[0] === "+") { neg = str[0] === "-"; str = str.slice(1); }
204
+ if (base === 0) {
205
+ if (/^0[xX]/.test(str)) { base = 16; str = str.slice(2); }
206
+ else if (/^0[bB]/.test(str)) { base = 2; str = str.slice(2); }
207
+ else if (/^0[oO]/.test(str)) { base = 8; str = str.slice(2); }
208
+ else if (/^0[0-9]/.test(str)) { base = 8; str = str.slice(1); }
209
+ else base = 10;
210
+ str = str.replaceAll("_", "");
211
+ }
212
+ if (base < 2 || base > 36) return [0n, 'strconv.ParseInt: parsing "' + s + '": invalid base ' + base];
213
+ const digits = "0123456789abcdefghijklmnopqrstuvwxyz".slice(0, base);
214
+ if (str === "" || [...str.toLowerCase()].some((c) => !digits.includes(c))) {
215
+ return [0n, 'strconv.ParseInt: parsing "' + s + '": invalid syntax'];
216
+ }
217
+ let v = 0n; const B = BigInt(base);
218
+ for (const c of str.toLowerCase()) v = v * B + BigInt(digits.indexOf(c));
219
+ if (neg) v = -v;
220
+ const w = bits === 0 ? 64 : bits;
221
+ const max = (1n << BigInt(w - 1)) - 1n, min = -(1n << BigInt(w - 1));
222
+ if (v > max || v < min) return [v > max ? max : min, 'strconv.ParseInt: parsing "' + s + '": value out of range'];
223
+ return [v, null];
224
+ };
225
+ var __strconv_parse_float = __strconv_parse_float || function(s) {
226
+ const str = s.replaceAll("_", "");
227
+ if (/^[+-]?(inf|infinity)$/i.test(str)) return [str[0] === "-" ? -Infinity : Infinity, null];
228
+ if (/^[+-]?nan$/i.test(str)) return [NaN, null];
229
+ if (str === "" || !/^[+-]?(\\d+\\.?\\d*|\\.\\d+)([eE][+-]?\\d+)?$|^[+-]?0[xX][0-9a-fA-F]+$/.test(str)) {
230
+ return [0, 'strconv.ParseFloat: parsing "' + s + '": invalid syntax'];
231
+ }
232
+ return [Number(str), null];
233
+ };
234
+ var __strconv_parse_bool = __strconv_parse_bool || function(s) {
235
+ if (["1", "t", "T", "TRUE", "true", "True"].includes(s)) return [true, null];
236
+ if (["0", "f", "F", "FALSE", "false", "False"].includes(s)) return [false, null];
237
+ return [false, 'strconv.ParseBool: parsing "' + s + '": invalid syntax'];
238
+ };
239
+ var __strconv_format_float = __strconv_format_float || function(f, fmtc, prec) {
240
+ const c = String.fromCharCode(fmtc);
241
+ if (!Number.isFinite(f)) return f !== f ? "NaN" : (f > 0 ? "+Inf" : "-Inf");
242
+ if (c === "f") return prec < 0 ? String(f) : f.toFixed(prec);
243
+ if (c === "e" || c === "E") {
244
+ const r = prec < 0 ? f.toExponential() : f.toExponential(prec);
245
+ const o = r.replace(/e([+-])(\\d)$/, "e$10$2");
246
+ return c === "E" ? o.toUpperCase() : o;
247
+ }
248
+ if (c === "g" || c === "G") {
249
+ const r = prec < 0 ? String(f) : f.toPrecision(prec);
250
+ return c === "G" ? r.toUpperCase() : r;
251
+ }
252
+ return String(f);
253
+ };`;
254
+
153
255
  export const HELPER_PATH_CLEAN = `var __pathClean = __pathClean || function(p) {
154
256
  if (!p) return ".";
155
257
  const abs = p.startsWith("/");
@@ -57,3 +57,35 @@ export function buildSourceMap(sources, mappings, sourcesContent) {
57
57
  if (sourcesContent) map.sourcesContent = sourcesContent;
58
58
  return JSON.stringify(map);
59
59
  }
60
+
61
+ // WASM source maps use the convention "generated line 0, generated column =
62
+ // byte offset into the module". `mappings` is Array<{ offset, srcFileIdx,
63
+ // srcLine }> (0-based line), one segment per function body (function-level
64
+ // granularity); DevTools shows the Go file/line for each wasm function.
65
+ export function buildWasmSourceMap(sources, mappings, sourcesContent) {
66
+ const sorted = [...mappings].sort((a, b) => a.offset - b.offset);
67
+ const segments = [];
68
+ let prevOffset = 0;
69
+ let prevSrcFile = 0;
70
+ let prevSrcLine = 0;
71
+ for (const m of sorted) {
72
+ const fileIdx = m.srcFileIdx ?? 0;
73
+ segments.push(
74
+ vlqEncode(m.offset - prevOffset) +
75
+ vlqEncode(fileIdx - prevSrcFile) +
76
+ vlqEncode(m.srcLine - prevSrcLine) +
77
+ vlqEncode(0),
78
+ );
79
+ prevOffset = m.offset;
80
+ prevSrcFile = fileIdx;
81
+ prevSrcLine = m.srcLine;
82
+ }
83
+ const map = {
84
+ version: 3,
85
+ sources: sources.map((s) => s.replace(/\\/g, "/")),
86
+ names: [],
87
+ mappings: segments.join(","),
88
+ };
89
+ if (sourcesContent) map.sourcesContent = sourcesContent;
90
+ return JSON.stringify(map);
91
+ }