gofront 1.5.1 → 1.6.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 (91) hide show
  1. package/README.md +174 -466
  2. package/package.json +16 -20
  3. package/src/backend/js/expressions.js +288 -128
  4. package/src/backend/js/index.js +138 -56
  5. package/src/backend/js/runtime.js +1055 -53
  6. package/src/backend/js/source-map.js +32 -0
  7. package/src/backend/js/statements.js +151 -83
  8. package/src/backend/js/stdlib/base64.js +12 -0
  9. package/src/backend/js/stdlib/binary.js +102 -0
  10. package/src/backend/js/stdlib/bits.js +15 -1
  11. package/src/backend/js/stdlib/builder.js +28 -9
  12. package/src/backend/js/stdlib/bytes.js +75 -18
  13. package/src/backend/js/stdlib/errors.js +22 -3
  14. package/src/backend/js/stdlib/fmt.js +95 -20
  15. package/src/backend/js/stdlib/hex.js +17 -0
  16. package/src/backend/js/stdlib/html.js +1 -1
  17. package/src/backend/js/stdlib/index.js +19 -0
  18. package/src/backend/js/stdlib/io.js +105 -3
  19. package/src/backend/js/stdlib/maps.js +1 -1
  20. package/src/backend/js/stdlib/math.js +13 -1
  21. package/src/backend/js/stdlib/path.js +20 -6
  22. package/src/backend/js/stdlib/rand.js +10 -5
  23. package/src/backend/js/stdlib/sha256.js +12 -0
  24. package/src/backend/js/stdlib/shared.js +17 -0
  25. package/src/backend/js/stdlib/slices.js +53 -27
  26. package/src/backend/js/stdlib/sort.js +1 -1
  27. package/src/backend/js/stdlib/strconv.js +52 -11
  28. package/src/backend/js/stdlib/strings.js +152 -16
  29. package/src/backend/js/stdlib/sync.js +13 -0
  30. package/src/backend/js/stdlib/testing.js +2 -2
  31. package/src/backend/js/stdlib/time.js +93 -22
  32. package/src/backend/js/stdlib/unicode.js +2 -2
  33. package/src/backend/js/templ.js +1 -1
  34. package/src/backend/wasm/boundary.js +1677 -58
  35. package/src/backend/wasm/const-fold.js +205 -0
  36. package/src/backend/wasm/emit-builtins.js +1230 -0
  37. package/src/backend/wasm/emit-exprs.js +2096 -0
  38. package/src/backend/wasm/emit-maps.js +1999 -0
  39. package/src/backend/wasm/emit-shared.js +519 -0
  40. package/src/backend/wasm/emit-stdlib.js +13 -0
  41. package/src/backend/wasm/emit-stmts.js +2466 -0
  42. package/src/backend/wasm/emit.js +656 -5031
  43. package/src/backend/wasm/encode.js +259 -85
  44. package/src/backend/wasm/glue.js +19 -5
  45. package/src/backend/wasm/index.js +2432 -86
  46. package/src/backend/wasm/monomorph.js +711 -0
  47. package/src/backend/wasm/optimize.js +236 -0
  48. package/src/backend/wasm/stdlib/bytes.js +5039 -0
  49. package/src/backend/wasm/stdlib/core.js +8903 -0
  50. package/src/backend/wasm/stdlib/index.js +40 -0
  51. package/src/backend/wasm/stdlib/io.js +2161 -0
  52. package/src/backend/wasm/stdlib/packages.js +2400 -0
  53. package/src/backend/wasm/stdlib/pure.js +720 -0
  54. package/src/backend/wasm/stdlib/time.js +1200 -0
  55. package/src/backend/wasm/types.js +312 -36
  56. package/src/backend/wasm/wat.js +49 -2
  57. package/src/cli-core.js +276 -73
  58. package/src/compiler.js +162 -25
  59. package/src/dts-parser.js +79 -126
  60. package/src/index.js +88 -121
  61. package/src/lexer.js +80 -29
  62. package/src/lower/boxing.js +4 -6
  63. package/src/lower/captures.js +12 -23
  64. package/src/lower/escape.js +4 -9
  65. package/src/lower/functions.js +277 -5
  66. package/src/lower/globals.js +7 -15
  67. package/src/lower/index.js +5 -2
  68. package/src/lower/ownership.js +35 -19
  69. package/src/lower/range.js +2 -5
  70. package/src/lower/walk.js +25 -0
  71. package/src/minifier.js +20 -68
  72. package/src/parser/declarations.js +3 -6
  73. package/src/parser/expressions.js +3 -8
  74. package/src/parser/index.js +18 -3
  75. package/src/parser/statements.js +25 -38
  76. package/src/parser/types.js +1 -2
  77. package/src/resolver.js +3 -27
  78. package/src/templ-lexer.js +29 -63
  79. package/src/templ-parser.js +22 -45
  80. package/src/test-runner.js +20 -0
  81. package/src/typechecker/assignability.js +123 -14
  82. package/src/typechecker/expressions.js +244 -4
  83. package/src/typechecker/index.js +27 -1
  84. package/src/typechecker/resolve.js +71 -12
  85. package/src/typechecker/statements.js +0 -7
  86. package/src/typechecker/stdlib/core.js +292 -103
  87. package/src/typechecker/stdlib/extended.js +471 -4
  88. package/src/typechecker/stdlib/shared.js +53 -0
  89. package/src/typechecker/stdlib.js +2 -0
  90. package/src/typechecker/types.js +545 -8
  91. package/src/vendor.js +2 -2
@@ -19,36 +19,53 @@
19
19
 
20
20
  import {
21
21
  computeEmbeddedStubs,
22
+ hasDirectRecover,
22
23
  isReferenceType,
24
+ normalizeDefers,
23
25
  scanAddressTaken,
24
26
  } from "../../lower/index.js";
25
27
  import { isComplex, isNumeric } from "../../typechecker/types.js";
26
28
  import { expressionGenMethods } from "./expressions.js";
27
29
  import {
28
30
  HELPER_APPEND,
31
+ HELPER_BASE64,
32
+ HELPER_BINARY,
29
33
  HELPER_CDIV,
30
34
  HELPER_CMUL,
35
+ HELPER_CRYPTO_RAND,
36
+ HELPER_DURATION,
31
37
  HELPER_EQUAL,
32
38
  HELPER_ERROR,
39
+ HELPER_ERROR_AS,
33
40
  HELPER_ERROR_IS,
41
+ HELPER_ERROR_JOIN,
42
+ HELPER_HEX,
34
43
  HELPER_IFACE_BOX,
35
44
  HELPER_INJECT_STYLES,
45
+ HELPER_IO_EOF,
36
46
  HELPER_LEN,
47
+ HELPER_NIL_CALL,
48
+ HELPER_PANIC,
37
49
  HELPER_PATH_CLEAN,
50
+ HELPER_PATH_ERROR,
38
51
  HELPER_S,
39
52
  HELPER_SCLONE,
53
+ HELPER_SHA256,
40
54
  HELPER_SORT_SLICE,
41
55
  HELPER_SPRINTF,
56
+ HELPER_STRCONV,
57
+ HELPER_STRCONV_ERROR,
58
+ HELPER_SYNC,
42
59
  HELPER_TESTING,
43
60
  HELPER_TIME_FMT,
44
61
  HELPER_TIME_PARSE,
45
62
  } from "./runtime.js";
46
- import { buildSourceMap } from "./source-map.js";
63
+ import { buildSourceMap, buildWasmSourceMap } from "./source-map.js";
47
64
  import { statementGenMethods } from "./statements.js";
48
65
  import { stdlibGenMethods } from "./stdlib/index.js";
49
66
  import { templGenMethods } from "./templ.js";
50
67
 
51
- export { buildSourceMap };
68
+ export { buildSourceMap, buildWasmSourceMap };
52
69
 
53
70
  // Valid Go identifiers that cannot be used as JS bindings; emitted with a `$` suffix.
54
71
  const JS_RESERVED = new Set([
@@ -90,6 +107,42 @@ const JS_RESERVED = new Set([
90
107
  const jsSafeName = (n) =>
91
108
  typeof n === "string" && JS_RESERVED.has(n) ? `${n}$` : n;
92
109
 
110
+ // Runtime helpers by registry name, in output order. Mark one as needed with
111
+ // `this.useHelper(name)`; `_prependHelpers` emits every marked entry.
112
+ const HELPER_MAP = [
113
+ ["len", HELPER_LEN],
114
+ ["append", HELPER_APPEND],
115
+ ["sliceGuard", HELPER_S],
116
+ ["sclone", HELPER_SCLONE],
117
+ ["ifaceBox", HELPER_IFACE_BOX],
118
+ ["equal", HELPER_EQUAL],
119
+ ["cmul", HELPER_CMUL],
120
+ ["cdiv", HELPER_CDIV],
121
+ ["panicRt", HELPER_PANIC],
122
+ ["nilCall", HELPER_NIL_CALL],
123
+ ["sprintf", HELPER_SPRINTF],
124
+ ["error", HELPER_ERROR],
125
+ ["errorIs", HELPER_ERROR_IS],
126
+ ["errorAs", HELPER_ERROR_AS],
127
+ ["errorJoin", HELPER_ERROR_JOIN],
128
+ ["strconvError", HELPER_STRCONV_ERROR],
129
+ ["pathError", HELPER_PATH_ERROR],
130
+ ["ioEOF", HELPER_IO_EOF],
131
+ ["strconv", HELPER_STRCONV],
132
+ ["pathClean", HELPER_PATH_CLEAN],
133
+ ["sortSlice", HELPER_SORT_SLICE],
134
+ ["timeFmt", HELPER_TIME_FMT],
135
+ ["timeParse", HELPER_TIME_PARSE],
136
+ ["duration", HELPER_DURATION],
137
+ ["testing", HELPER_TESTING],
138
+ ["sync", HELPER_SYNC],
139
+ ["cryptoRand", HELPER_CRYPTO_RAND],
140
+ ["sha256", HELPER_SHA256],
141
+ ["binary", HELPER_BINARY],
142
+ ["hex", HELPER_HEX],
143
+ ["base64", HELPER_BASE64],
144
+ ];
145
+
93
146
  export class CodeGen {
94
147
  // jsImports: Map<importPath, string[]> — npm package imports to emit at top of file
95
148
  // bundledPackages: Set<string> — GoFront package names bundled inline;
@@ -116,22 +169,7 @@ export class CodeGen {
116
169
  this._currentSrcFileIdx = 0; // updated as each top-level decl is generated
117
170
  this._boxedVars = new Set(); // address-taken scalar variables that need boxing
118
171
  // Runtime helper usage tracking — only emit helpers that are actually used
119
- this._usesLen = false;
120
- this._usesAppend = false;
121
- this._usesSliceGuard = false;
122
- this._usesSprintf = false;
123
- this._usesEqual = false;
124
- this._usesCmul = false;
125
- this._usesCdiv = false;
126
- this._usesError = false;
127
- this._usesErrorIs = false;
128
- this._usesPathClean = false;
129
- this._usesSortSlice = false;
130
- this._usesTimeFmt = false;
131
- this._usesTimeParse = false;
132
- this._usesTesting = false;
133
- this._usesSClone = false;
134
- this._usesIfaceBox = false;
172
+ this._helpers = new Set();
135
173
  // Per-function context for Go value semantics (see _withFnCtx)
136
174
  this._fnCtx = null;
137
175
  // Iterator (range-over-func) context
@@ -217,6 +255,10 @@ export class CodeGen {
217
255
 
218
256
  generate(program, options = {}) {
219
257
  const isTest = options.isTest ?? false;
258
+ // Defer lowering (argument/receiver hoisting, recover-target marking) is
259
+ // idempotent and owned by the backends: the wasm path runs it via
260
+ // `lower()`, the JS path here.
261
+ normalizeDefers(program);
220
262
  // Ensure _pkgName is set on all decls (generateAll sets it for
221
263
  // multi-file; for single-file compiles we derive it from the AST).
222
264
  const pkgName = program.pkg?.name;
@@ -407,28 +449,34 @@ export class CodeGen {
407
449
  return methods;
408
450
  }
409
451
 
452
+ /** Marks a runtime helper (key of HELPER_MAP) as needed by the output. */
453
+ useHelper(name) {
454
+ this._helpers.add(name);
455
+ if (
456
+ name === "strconvError" ||
457
+ name === "pathError" ||
458
+ name === "ioEOF" ||
459
+ name === "base64" ||
460
+ name === "hex"
461
+ ) {
462
+ this._helpers.add("error");
463
+ }
464
+ if (name === "binary") {
465
+ this.useHelper("ioEOF");
466
+ this._helpers.add("error");
467
+ }
468
+ }
469
+
410
470
  _prependHelpers(isTest = false) {
411
- const needsTesting = isTest || this._usesTesting;
412
- const HELPER_MAP = [
413
- [this.collectedCss.length > 0, HELPER_INJECT_STYLES],
414
- [this._usesLen, HELPER_LEN],
415
- [this._usesAppend, HELPER_APPEND],
416
- [this._usesSliceGuard, HELPER_S],
417
- [this._usesSClone, HELPER_SCLONE],
418
- [this._usesIfaceBox, HELPER_IFACE_BOX],
419
- [this._usesEqual, HELPER_EQUAL],
420
- [this._usesCmul, HELPER_CMUL],
421
- [this._usesCdiv, HELPER_CDIV],
422
- [this._usesSprintf || needsTesting, HELPER_SPRINTF],
423
- [this._usesError, HELPER_ERROR],
424
- [this._usesErrorIs, HELPER_ERROR_IS],
425
- [this._usesPathClean, HELPER_PATH_CLEAN],
426
- [this._usesSortSlice, HELPER_SORT_SLICE],
427
- [this._usesTimeFmt, HELPER_TIME_FMT],
428
- [this._usesTimeParse, HELPER_TIME_PARSE],
429
- [needsTesting, HELPER_TESTING],
430
- ];
431
- const helpers = HELPER_MAP.filter(([flag]) => flag).map(([, h]) => h);
471
+ if (isTest || this._helpers.has("testing")) {
472
+ this.useHelper("sprintf");
473
+ this.useHelper("testing");
474
+ }
475
+ const helpers = [];
476
+ if (this.collectedCss.length > 0) helpers.push(HELPER_INJECT_STYLES);
477
+ for (const [name, src] of HELPER_MAP) {
478
+ if (this._helpers.has(name)) helpers.push(src);
479
+ }
432
480
  if (helpers.length > 0) {
433
481
  const helperLines = helpers.flatMap((h) => h.split("\n"));
434
482
  helperLines.push("");
@@ -559,7 +607,7 @@ export class CodeGen {
559
607
  .map((f) => `${f.name}: ${binding(f)} = ${f.zero}`)
560
608
  .join(", ");
561
609
  this.line(
562
- `if (typeof ${first} === "object" && ${first} !== null && ${first}.constructor === Object) ({ ${pattern} } = ${first});`,
610
+ `if (arguments.length === 1 && typeof ${first} === "object" && ${first} !== null && ${first}.constructor === Object) ({ ${pattern} } = ${first});`,
563
611
  );
564
612
  }
565
613
  for (const f of fields) {
@@ -661,7 +709,7 @@ export class CodeGen {
661
709
  }
662
710
  }
663
711
  this._emitParamCopies(decl.params);
664
- this._withNamedReturns(decl, () => this._genBody(decl.body));
712
+ this._withNamedReturns(decl, () => this._genBody(decl.body, decl));
665
713
  }),
666
714
  );
667
715
  this._unwrappedRecv = prevUnwrapped;
@@ -718,7 +766,7 @@ export class CodeGen {
718
766
  this._withFnCtx(decl.body, () =>
719
767
  this.indented(() => {
720
768
  this._emitParamCopies(decl.params);
721
- this._withNamedReturns(decl, () => this._genBody(decl.body));
769
+ this._withNamedReturns(decl, () => this._genBody(decl.body, decl));
722
770
  }),
723
771
  );
724
772
  this._boxedVars = prevBoxed;
@@ -726,45 +774,74 @@ export class CodeGen {
726
774
  }
727
775
 
728
776
  _withNamedReturns(decl, fn) {
777
+ const prevDecl = this._currentDecl;
778
+ this._currentDecl = decl;
729
779
  const named = decl.returnType?._namedReturns;
730
780
  const prev = this.namedReturnVars;
731
781
  if (named) {
732
782
  // Emit zero-value declarations for named return vars
733
- for (const { name, type } of named) {
734
- if (name) {
735
- this._markOwnership(name, true);
736
- this.line(`let ${name} = ${this.zeroValueForTypeNode(type)};`);
737
- }
783
+ for (let i = 0; i < named.length; i++) {
784
+ const { name, type } = named[i];
785
+ const varName = !name || name === "_" ? `__ret_blank$${i}` : name;
786
+ this._markOwnership(varName, true);
787
+ this.line(`let ${varName} = ${this.zeroValueForTypeNode(type)};`);
738
788
  }
739
- this.namedReturnVars = named.map((r) => r.name).filter(Boolean);
789
+ this.namedReturnVars = named.map((r, i) =>
790
+ !r.name || r.name === "_" ? `__ret_blank$${i}` : r.name,
791
+ );
740
792
  } else {
741
793
  this.namedReturnVars = null;
742
794
  }
743
795
  fn();
744
796
  this.namedReturnVars = prev;
797
+ this._currentDecl = prevDecl;
745
798
  }
746
799
 
747
800
  // Emit a function body, wrapping in try/catch/finally for defer if needed.
748
- _genBody(body) {
801
+ // `fnNode` is the enclosing FuncDecl/MethodDecl/FuncLit; it decides whether
802
+ // the recover-arming prologue is needed (see HELPER_PANIC).
803
+ _genBody(body, fnNode = null) {
804
+ const prevRecoverOk = this._recoverOkInScope;
805
+ this._recoverOkInScope = false;
806
+ const isTarget = Boolean(fnNode?._isDeferTarget);
807
+ const forwards = isTarget && Boolean(fnNode._deferForward);
808
+ if (!forwards && (isTarget || hasDirectRecover(body))) {
809
+ this.useHelper("panicRt");
810
+ this.line(
811
+ "const __recoverOk = __gopanic.armed; __gopanic.armed = false;",
812
+ );
813
+ this._recoverOkInScope = true;
814
+ }
749
815
  if (!body._hasDefer) {
750
816
  this.genBlock(body);
817
+ this._recoverOkInScope = prevRecoverOk;
751
818
  return;
752
819
  }
820
+ this.useHelper("panicRt");
753
821
  this.line("const __defers = [];");
754
- this.line("let __panic = null;");
822
+ this.line("const __frame = { pn: null };");
755
823
  this.line("try {");
756
824
  this.indented(() => this.genBlock(body));
757
825
  this.line("} catch (__err) {");
758
- this.indented(() => this.line("__panic = __err;"));
826
+ this.indented(() => {
827
+ this.line("__frame.pn = { err: __err, recovered: false };");
828
+ this.line("__gopanic.stack.push(__frame.pn);");
829
+ });
759
830
  this.line("} finally {");
760
831
  this.indented(() => {
761
- this.line(
762
- "for (let __i = __defers.length - 1; __i >= 0; __i--) __defers[__i]();",
763
- );
764
- this.line("if (__panic !== null) throw __panic;");
832
+ // Runs defers LIFO and rethrows an unrecovered panic.
833
+ this.line("__runDefers(__defers, __frame);");
834
+ if (this.namedReturnVars?.length > 0) {
835
+ const vars = this.namedReturnVars;
836
+ this.line(
837
+ vars.length === 1
838
+ ? `return ${vars[0]};`
839
+ : `return [${vars.join(", ")}];`,
840
+ );
841
+ }
765
842
  });
766
843
  this.line("}");
767
- // If a recover() cleared __panic, execution reaches here.
844
+ // If a recover() cleared the panic, execution reaches here.
768
845
  // Return named return vars so deferred mutations are visible to the caller.
769
846
  if (this.namedReturnVars?.length > 0) {
770
847
  const vars = this.namedReturnVars;
@@ -773,7 +850,12 @@ export class CodeGen {
773
850
  ? `return ${vars[0]};`
774
851
  : `return [${vars.join(", ")}];`,
775
852
  );
853
+ } else if (this._currentDecl?.returnType) {
854
+ this.line(
855
+ `return ${this.zeroValueForTypeNode(this._currentDecl.returnType)};`,
856
+ );
776
857
  }
858
+ this._recoverOkInScope = prevRecoverOk;
777
859
  }
778
860
 
779
861
  // Scan AST node for _addressTaken idents on scalars and populate _boxedVars.