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
@@ -28,22 +28,37 @@ import { isComplex, isNumeric } from "../../typechecker/types.js";
28
28
  import { expressionGenMethods } from "./expressions.js";
29
29
  import {
30
30
  HELPER_APPEND,
31
+ HELPER_BASE64,
32
+ HELPER_BINARY,
31
33
  HELPER_CDIV,
32
34
  HELPER_CMUL,
35
+ HELPER_CRYPTO_RAND,
36
+ HELPER_DURATION,
33
37
  HELPER_EQUAL,
34
38
  HELPER_ERROR,
39
+ HELPER_ERROR_AS,
35
40
  HELPER_ERROR_IS,
41
+ HELPER_ERROR_JOIN,
42
+ HELPER_HEX,
36
43
  HELPER_IFACE_BOX,
44
+ HELPER_IFEQ,
37
45
  HELPER_INJECT_STYLES,
46
+ HELPER_IO_EOF,
47
+ HELPER_JSON,
38
48
  HELPER_LEN,
39
49
  HELPER_NIL_CALL,
40
50
  HELPER_PANIC,
41
51
  HELPER_PATH_CLEAN,
52
+ HELPER_PATH_ERROR,
42
53
  HELPER_S,
43
54
  HELPER_SCLONE,
55
+ HELPER_SHA256,
56
+ HELPER_SIMD,
44
57
  HELPER_SORT_SLICE,
45
58
  HELPER_SPRINTF,
46
59
  HELPER_STRCONV,
60
+ HELPER_STRCONV_ERROR,
61
+ HELPER_SYNC,
47
62
  HELPER_TESTING,
48
63
  HELPER_TIME_FMT,
49
64
  HELPER_TIME_PARSE,
@@ -104,6 +119,7 @@ const HELPER_MAP = [
104
119
  ["sclone", HELPER_SCLONE],
105
120
  ["ifaceBox", HELPER_IFACE_BOX],
106
121
  ["equal", HELPER_EQUAL],
122
+ ["ifeq", HELPER_IFEQ],
107
123
  ["cmul", HELPER_CMUL],
108
124
  ["cdiv", HELPER_CDIV],
109
125
  ["panicRt", HELPER_PANIC],
@@ -111,12 +127,26 @@ const HELPER_MAP = [
111
127
  ["sprintf", HELPER_SPRINTF],
112
128
  ["error", HELPER_ERROR],
113
129
  ["errorIs", HELPER_ERROR_IS],
130
+ ["errorAs", HELPER_ERROR_AS],
131
+ ["errorJoin", HELPER_ERROR_JOIN],
132
+ ["strconvError", HELPER_STRCONV_ERROR],
133
+ ["pathError", HELPER_PATH_ERROR],
134
+ ["ioEOF", HELPER_IO_EOF],
114
135
  ["strconv", HELPER_STRCONV],
115
136
  ["pathClean", HELPER_PATH_CLEAN],
116
137
  ["sortSlice", HELPER_SORT_SLICE],
117
138
  ["timeFmt", HELPER_TIME_FMT],
118
139
  ["timeParse", HELPER_TIME_PARSE],
140
+ ["duration", HELPER_DURATION],
119
141
  ["testing", HELPER_TESTING],
142
+ ["sync", HELPER_SYNC],
143
+ ["cryptoRand", HELPER_CRYPTO_RAND],
144
+ ["sha256", HELPER_SHA256],
145
+ ["binary", HELPER_BINARY],
146
+ ["hex", HELPER_HEX],
147
+ ["base64", HELPER_BASE64],
148
+ ["json", HELPER_JSON],
149
+ ["simd", HELPER_SIMD],
120
150
  ];
121
151
 
122
152
  export class CodeGen {
@@ -144,6 +174,7 @@ export class CodeGen {
144
174
  this._srcMappings = []; // { genLine, srcLine, srcFileIdx } for source map
145
175
  this._currentSrcFileIdx = 0; // updated as each top-level decl is generated
146
176
  this._boxedVars = new Set(); // address-taken scalar variables that need boxing
177
+ this._jsonCodecs = new Map(); // memoized JSON encoders and decoders
147
178
  // Runtime helper usage tracking — only emit helpers that are actually used
148
179
  this._helpers = new Set();
149
180
  // Per-function context for Go value semantics (see _withFnCtx)
@@ -249,6 +280,7 @@ export class CodeGen {
249
280
  this._emitTypeDecls(program, methods);
250
281
  this._emitVarConstDecls(program);
251
282
  const initNames = this._emitFuncDecls(program);
283
+ this._emitJsonCodecs();
252
284
  this._callInitAndMain(initNames, program, isTest);
253
285
  const prependedLines = this._prependHelpers(isTest);
254
286
  return this._finalizeOutput(prependedLines);
@@ -428,6 +460,23 @@ export class CodeGen {
428
460
  /** Marks a runtime helper (key of HELPER_MAP) as needed by the output. */
429
461
  useHelper(name) {
430
462
  this._helpers.add(name);
463
+ if (
464
+ name === "strconvError" ||
465
+ name === "pathError" ||
466
+ name === "ioEOF" ||
467
+ name === "base64" ||
468
+ name === "hex" ||
469
+ name === "json"
470
+ ) {
471
+ this._helpers.add("error");
472
+ }
473
+ if (name === "json") {
474
+ this.useHelper("base64");
475
+ }
476
+ if (name === "binary") {
477
+ this.useHelper("ioEOF");
478
+ this._helpers.add("error");
479
+ }
431
480
  }
432
481
 
433
482
  _prependHelpers(isTest = false) {
@@ -496,29 +545,61 @@ export class CodeGen {
496
545
 
497
546
  genNamedTypeClass(name, methodDecls) {
498
547
  const namedType = this.checker?.types.get(name);
499
- const underlying = namedType?.underlying;
500
- let field, ctorDefault;
501
- if (underlying?.kind === "func") {
502
- field = "_fn";
503
- ctorDefault = "null";
504
- } else if (underlying?.kind === "map") {
505
- field = "_map";
506
- ctorDefault = "{}";
507
- } else {
508
- field = "_items";
509
- ctorDefault = "[]";
510
- }
548
+ const field = this._namedBoxField(namedType?.underlying);
549
+ // Values of the type stay raw (number, string, array, map, function); the
550
+ // class is only the box used when a value is stored in an interface.
551
+ // Methods are static (`T.$M(recv, ...)`) so raw values can call them.
511
552
  this.line(`class ${name} {`);
512
553
  this.indented(() => {
513
- this.line(
514
- `constructor(${field} = ${ctorDefault}) { this.${field} = ${field}; }`,
515
- );
554
+ this.line(`constructor(${field}) { this.${field} = ${field}; }`);
555
+ this.line(`__gfUnbox() { return this.${field}; }`);
516
556
  for (const m of methodDecls) {
517
557
  this.blank();
518
- this.genMethod(m, field);
558
+ this.genMethod(m, { staticNamed: true });
559
+ if (!m.recvPointer) {
560
+ this.line(
561
+ `${m.name}(...__a) { return ${name}.$${m.name}(this.${field}, ...__a); }`,
562
+ );
563
+ }
519
564
  }
520
565
  });
521
566
  this.line("}");
567
+ // Box for *T in an interface. It also proxies `.value` to the pointer, so
568
+ // helpers that write through a pointer argument (errors.As, json.Unmarshal)
569
+ // keep working. Pointer methods get the { value } box, value methods its value.
570
+ this.line(`class ${name}$Ptr {`);
571
+ this.indented(() => {
572
+ this.line("constructor(_p) { this._p = _p; }");
573
+ this.line("__gfUnbox() { return this._p; }");
574
+ this.line("get value() { return this._p.value; }");
575
+ this.line("set value(v) { this._p.value = v; }");
576
+ for (const m of methodDecls) {
577
+ const recv = m.recvPointer ? "this._p" : "this._p.value";
578
+ this.line(
579
+ `${m.name}(...__a) { return ${name}.$${m.name}(${recv}, ...__a); }`,
580
+ );
581
+ }
582
+ });
583
+ this.line("}");
584
+ }
585
+
586
+ _namedBoxField(underlying) {
587
+ if (underlying?.kind === "func") return "_fn";
588
+ if (underlying?.kind === "map") return "_map";
589
+ return "_items";
590
+ }
591
+
592
+ // Box/dispatch info for a named non-struct type with methods, or null.
593
+ _namedBoxInfo(type) {
594
+ if (type?.kind !== "named") return null;
595
+ const u = type.underlying;
596
+ if (!u || u.kind === "struct" || u.kind === "interface") return null;
597
+ const methods = type.methods ?? type._generic?.methods;
598
+ if (!methods || methods.size === 0) return null;
599
+ if (![...methods.values()].some((m) => Object.hasOwn(m ?? {}, "_ptrRecv")))
600
+ return null;
601
+ const cls = this._dequalify(type.name).replace(/\[.*$/, "");
602
+ return { cls, field: this._namedBoxField(u), methods };
522
603
  }
523
604
 
524
605
  _collectStructFields(name, structTypeAst) {
@@ -570,7 +651,7 @@ export class CodeGen {
570
651
  .map((f) => `${f.name}: ${binding(f)} = ${f.zero}`)
571
652
  .join(", ");
572
653
  this.line(
573
- `if (typeof ${first} === "object" && ${first} !== null && ${first}.constructor === Object) ({ ${pattern} } = ${first});`,
654
+ `if (arguments.length === 1 && typeof ${first} === "object" && ${first} !== null && ${first}.constructor === Object) ({ ${pattern} } = ${first});`,
574
655
  );
575
656
  }
576
657
  for (const f of fields) {
@@ -626,6 +707,14 @@ export class CodeGen {
626
707
  }
627
708
  }
628
709
 
710
+ _emitJsonCodecs() {
711
+ if (!this._jsonCodecs || this._jsonCodecs.size === 0) return;
712
+ for (const [, code] of this._jsonCodecs) {
713
+ this.line(code);
714
+ this.blank();
715
+ }
716
+ }
717
+
629
718
  _genSingleEmbedStubs(embed, declared) {
630
719
  const embedName = embed.kind === "named" ? embed.name : null;
631
720
  if (!embedName) return;
@@ -651,36 +740,45 @@ export class CodeGen {
651
740
  }
652
741
  }
653
742
 
654
- genMethod(decl, recvField = null) {
743
+ genMethod(decl, opts = null) {
655
744
  const prevFileIdx = this._currentSrcFileIdx;
656
745
  if (decl._srcFileIdx != null) this._currentSrcFileIdx = decl._srcFileIdx;
657
- const params = decl.params.map((p) => p.name).join(", ");
658
- const asyncPrefix = decl.async ? "async " : "";
659
- this.line(`${asyncPrefix}${decl.name}(${params}) {`, decl._line ?? null);
746
+ const staticNamed = Boolean(opts?.staticNamed);
747
+ const recvParam =
748
+ decl.recvName && decl.recvName !== "_" ? decl.recvName : "__recv";
749
+ this.line(
750
+ this._methodSignature(decl, staticNamed, recvParam),
751
+ decl._line ?? null,
752
+ );
660
753
  const prevBoxed = this._boxedVars;
661
754
  this._boxedVars = new Set();
662
755
  this._scanAddressTaken(decl.body);
663
- const prevUnwrapped = this._unwrappedRecv;
664
756
  this._withFnCtx(decl.body, () =>
665
757
  this.indented(() => {
666
- if (decl.recvName && decl.recvName !== "_") {
667
- if (recvField) {
668
- this.line(`const ${decl.recvName} = this.${recvField};`);
669
- this._unwrappedRecv = decl.recvName;
670
- } else {
671
- this.line(`const ${decl.recvName} = ${this._receiverValue(decl)};`);
672
- }
758
+ if (!staticNamed && decl.recvName && decl.recvName !== "_") {
759
+ this.line(`const ${decl.recvName} = ${this._receiverValue(decl)};`);
760
+ }
761
+ if (staticNamed && this._boxedVars.has(recvParam)) {
762
+ this.line(`${recvParam} = { value: ${recvParam} };`);
673
763
  }
674
764
  this._emitParamCopies(decl.params);
675
765
  this._withNamedReturns(decl, () => this._genBody(decl.body, decl));
676
766
  }),
677
767
  );
678
- this._unwrappedRecv = prevUnwrapped;
679
768
  this._boxedVars = prevBoxed;
680
769
  this.line("}");
681
770
  this._currentSrcFileIdx = prevFileIdx;
682
771
  }
683
772
 
773
+ // Static methods of named non-struct types take the receiver as first parameter.
774
+ _methodSignature(decl, staticNamed, recvParam) {
775
+ const params = decl.params.map((p) => p.name);
776
+ const asyncPrefix = decl.async ? "async " : "";
777
+ if (!staticNamed)
778
+ return `${asyncPrefix}${decl.name}(${params.join(", ")}) {`;
779
+ return `static ${asyncPrefix}$${decl.name}(${[recvParam, ...params].join(", ")}) {`;
780
+ }
781
+
684
782
  // Value receivers are copies in Go; only materialize the copy when the body mutates it.
685
783
  _receiverValue(decl) {
686
784
  const name = decl.recvName;
@@ -696,19 +794,10 @@ export class CodeGen {
696
794
  return "this";
697
795
  }
698
796
 
699
- // Returns the wrapper field name ("_fn", "_items", "_map") if `type` is a named
700
- // non-struct type emitted as a wrapper class, or null otherwise.
701
- // Pass `expr` so we can skip unwrapping when the expression is the method receiver
702
- // (which was already unwrapped to `this.<field>` at the top of the method body).
703
- _namedWrapperField(type, expr = null) {
704
- if (type?.kind !== "named") return null;
705
- if (!this.namedWrapperNames.has(type.name)) return null;
706
- if (expr?.kind === "Ident" && expr.name === this._unwrappedRecv)
707
- return null;
708
- const u = type.underlying;
709
- if (u?.kind === "func") return "_fn";
710
- if (u?.kind === "map") return "_map";
711
- return "_items";
797
+ // Named non-struct values are kept raw in the JS backend, so there is no
798
+ // wrapper field to unwrap any more; kept for call-site compatibility.
799
+ _namedWrapperField(_type, _expr = null) {
800
+ return null;
712
801
  }
713
802
 
714
803
  // ── Function declarations ────────────────────────────────────
@@ -822,8 +911,35 @@ export class CodeGen {
822
911
  }
823
912
 
824
913
  // Scan AST node for _addressTaken idents on scalars and populate _boxedVars.
914
+ // A pointer-receiver method call on a named non-struct local (`l.Set(5)`)
915
+ // takes the variable's address too, so the variable must be boxed.
825
916
  _scanAddressTaken(node) {
826
917
  scanAddressTaken(node, this._boxedVars);
918
+ const visit = (n) => {
919
+ if (!n || typeof n !== "object") return;
920
+ if (Array.isArray(n)) {
921
+ for (const item of n) visit(item);
922
+ return;
923
+ }
924
+ if (n.kind === "CallExpr" && n.func?.kind === "SelectorExpr") {
925
+ const recv = n.func.expr;
926
+ if (
927
+ recv?.kind === "Ident" &&
928
+ this._namedPtrMethodOn(recv._type, n.func.field)
929
+ )
930
+ this._boxedVars.add(recv.name);
931
+ }
932
+ for (const key of Object.keys(n)) {
933
+ if (key.startsWith("_") || key === "kind") continue;
934
+ visit(n[key]);
935
+ }
936
+ };
937
+ visit(node);
938
+ }
939
+
940
+ _namedPtrMethodOn(type, method) {
941
+ const info = this._namedBoxInfo(type);
942
+ return Boolean(info?.methods.get(method)?._ptrRecv);
827
943
  }
828
944
 
829
945
  _isReferenceType(t) {
@@ -854,12 +970,7 @@ export class CodeGen {
854
970
  }
855
971
  return js;
856
972
  });
857
- if (spec.names.length === 1) {
858
- this.line(`let ${spec.names[0]} = ${vals[0]};`);
859
- } else {
860
- // let [a, b] = [v1, v2]
861
- this.line(`let [${spec.names.join(", ")}] = [${vals.join(", ")}];`);
862
- }
973
+ this._emitVarBindings(spec.names, vals, paired);
863
974
  } else {
864
975
  const zero = spec.type ? this.zeroValueForTypeNode(spec.type) : "null";
865
976
  for (const name of spec.names) {
@@ -871,6 +982,20 @@ export class CodeGen {
871
982
  }
872
983
  }
873
984
 
985
+ // `var a = v` / `var a, b = v1, v2` / `var a, b = f()`, boxing address-taken names.
986
+ _emitVarBindings(names, vals, paired) {
987
+ if (names.length === 1) {
988
+ const val = this._boxedVars.has(names[0])
989
+ ? `{ value: ${vals[0]} }`
990
+ : vals[0];
991
+ this.line(`let ${names[0]} = ${val};`);
992
+ return;
993
+ }
994
+ const rhs = paired ? `[${vals.join(", ")}]` : vals[0];
995
+ this.line(`let [${names.join(", ")}] = ${rhs};`);
996
+ this._boxDeclaredNames(names);
997
+ }
998
+
874
999
  genConstDecl(decl) {
875
1000
  for (const spec of decl.decls) {
876
1001
  const vals = spec.value.map((v) => this.genExpr(v));