gofront 1.6.1 → 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.
@@ -41,8 +41,10 @@ import {
41
41
  HELPER_ERROR_JOIN,
42
42
  HELPER_HEX,
43
43
  HELPER_IFACE_BOX,
44
+ HELPER_IFEQ,
44
45
  HELPER_INJECT_STYLES,
45
46
  HELPER_IO_EOF,
47
+ HELPER_JSON,
46
48
  HELPER_LEN,
47
49
  HELPER_NIL_CALL,
48
50
  HELPER_PANIC,
@@ -51,6 +53,7 @@ import {
51
53
  HELPER_S,
52
54
  HELPER_SCLONE,
53
55
  HELPER_SHA256,
56
+ HELPER_SIMD,
54
57
  HELPER_SORT_SLICE,
55
58
  HELPER_SPRINTF,
56
59
  HELPER_STRCONV,
@@ -116,6 +119,7 @@ const HELPER_MAP = [
116
119
  ["sclone", HELPER_SCLONE],
117
120
  ["ifaceBox", HELPER_IFACE_BOX],
118
121
  ["equal", HELPER_EQUAL],
122
+ ["ifeq", HELPER_IFEQ],
119
123
  ["cmul", HELPER_CMUL],
120
124
  ["cdiv", HELPER_CDIV],
121
125
  ["panicRt", HELPER_PANIC],
@@ -141,6 +145,8 @@ const HELPER_MAP = [
141
145
  ["binary", HELPER_BINARY],
142
146
  ["hex", HELPER_HEX],
143
147
  ["base64", HELPER_BASE64],
148
+ ["json", HELPER_JSON],
149
+ ["simd", HELPER_SIMD],
144
150
  ];
145
151
 
146
152
  export class CodeGen {
@@ -168,6 +174,7 @@ export class CodeGen {
168
174
  this._srcMappings = []; // { genLine, srcLine, srcFileIdx } for source map
169
175
  this._currentSrcFileIdx = 0; // updated as each top-level decl is generated
170
176
  this._boxedVars = new Set(); // address-taken scalar variables that need boxing
177
+ this._jsonCodecs = new Map(); // memoized JSON encoders and decoders
171
178
  // Runtime helper usage tracking — only emit helpers that are actually used
172
179
  this._helpers = new Set();
173
180
  // Per-function context for Go value semantics (see _withFnCtx)
@@ -273,6 +280,7 @@ export class CodeGen {
273
280
  this._emitTypeDecls(program, methods);
274
281
  this._emitVarConstDecls(program);
275
282
  const initNames = this._emitFuncDecls(program);
283
+ this._emitJsonCodecs();
276
284
  this._callInitAndMain(initNames, program, isTest);
277
285
  const prependedLines = this._prependHelpers(isTest);
278
286
  return this._finalizeOutput(prependedLines);
@@ -457,10 +465,14 @@ export class CodeGen {
457
465
  name === "pathError" ||
458
466
  name === "ioEOF" ||
459
467
  name === "base64" ||
460
- name === "hex"
468
+ name === "hex" ||
469
+ name === "json"
461
470
  ) {
462
471
  this._helpers.add("error");
463
472
  }
473
+ if (name === "json") {
474
+ this.useHelper("base64");
475
+ }
464
476
  if (name === "binary") {
465
477
  this.useHelper("ioEOF");
466
478
  this._helpers.add("error");
@@ -533,31 +545,63 @@ export class CodeGen {
533
545
 
534
546
  genNamedTypeClass(name, methodDecls) {
535
547
  const namedType = this.checker?.types.get(name);
536
- const underlying = namedType?.underlying;
537
- let field, ctorDefault;
538
- if (underlying?.kind === "func") {
539
- field = "_fn";
540
- ctorDefault = "null";
541
- } else if (underlying?.kind === "map") {
542
- field = "_map";
543
- ctorDefault = "{}";
544
- } else {
545
- field = "_items";
546
- ctorDefault = "[]";
547
- }
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.
548
552
  this.line(`class ${name} {`);
549
553
  this.indented(() => {
550
- this.line(
551
- `constructor(${field} = ${ctorDefault}) { this.${field} = ${field}; }`,
552
- );
554
+ this.line(`constructor(${field}) { this.${field} = ${field}; }`);
555
+ this.line(`__gfUnbox() { return this.${field}; }`);
553
556
  for (const m of methodDecls) {
554
557
  this.blank();
555
- 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
+ }
564
+ }
565
+ });
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
+ );
556
581
  }
557
582
  });
558
583
  this.line("}");
559
584
  }
560
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 };
603
+ }
604
+
561
605
  _collectStructFields(name, structTypeAst) {
562
606
  const fields = [];
563
607
  if (this.checker) {
@@ -663,6 +707,14 @@ export class CodeGen {
663
707
  }
664
708
  }
665
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
+
666
718
  _genSingleEmbedStubs(embed, declared) {
667
719
  const embedName = embed.kind === "named" ? embed.name : null;
668
720
  if (!embedName) return;
@@ -688,36 +740,45 @@ export class CodeGen {
688
740
  }
689
741
  }
690
742
 
691
- genMethod(decl, recvField = null) {
743
+ genMethod(decl, opts = null) {
692
744
  const prevFileIdx = this._currentSrcFileIdx;
693
745
  if (decl._srcFileIdx != null) this._currentSrcFileIdx = decl._srcFileIdx;
694
- const params = decl.params.map((p) => p.name).join(", ");
695
- const asyncPrefix = decl.async ? "async " : "";
696
- 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
+ );
697
753
  const prevBoxed = this._boxedVars;
698
754
  this._boxedVars = new Set();
699
755
  this._scanAddressTaken(decl.body);
700
- const prevUnwrapped = this._unwrappedRecv;
701
756
  this._withFnCtx(decl.body, () =>
702
757
  this.indented(() => {
703
- if (decl.recvName && decl.recvName !== "_") {
704
- if (recvField) {
705
- this.line(`const ${decl.recvName} = this.${recvField};`);
706
- this._unwrappedRecv = decl.recvName;
707
- } else {
708
- this.line(`const ${decl.recvName} = ${this._receiverValue(decl)};`);
709
- }
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} };`);
710
763
  }
711
764
  this._emitParamCopies(decl.params);
712
765
  this._withNamedReturns(decl, () => this._genBody(decl.body, decl));
713
766
  }),
714
767
  );
715
- this._unwrappedRecv = prevUnwrapped;
716
768
  this._boxedVars = prevBoxed;
717
769
  this.line("}");
718
770
  this._currentSrcFileIdx = prevFileIdx;
719
771
  }
720
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
+
721
782
  // Value receivers are copies in Go; only materialize the copy when the body mutates it.
722
783
  _receiverValue(decl) {
723
784
  const name = decl.recvName;
@@ -733,19 +794,10 @@ export class CodeGen {
733
794
  return "this";
734
795
  }
735
796
 
736
- // Returns the wrapper field name ("_fn", "_items", "_map") if `type` is a named
737
- // non-struct type emitted as a wrapper class, or null otherwise.
738
- // Pass `expr` so we can skip unwrapping when the expression is the method receiver
739
- // (which was already unwrapped to `this.<field>` at the top of the method body).
740
- _namedWrapperField(type, expr = null) {
741
- if (type?.kind !== "named") return null;
742
- if (!this.namedWrapperNames.has(type.name)) return null;
743
- if (expr?.kind === "Ident" && expr.name === this._unwrappedRecv)
744
- return null;
745
- const u = type.underlying;
746
- if (u?.kind === "func") return "_fn";
747
- if (u?.kind === "map") return "_map";
748
- 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;
749
801
  }
750
802
 
751
803
  // ── Function declarations ────────────────────────────────────
@@ -859,8 +911,35 @@ export class CodeGen {
859
911
  }
860
912
 
861
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.
862
916
  _scanAddressTaken(node) {
863
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);
864
943
  }
865
944
 
866
945
  _isReferenceType(t) {
@@ -891,12 +970,7 @@ export class CodeGen {
891
970
  }
892
971
  return js;
893
972
  });
894
- if (spec.names.length === 1) {
895
- this.line(`let ${spec.names[0]} = ${vals[0]};`);
896
- } else {
897
- // let [a, b] = [v1, v2]
898
- this.line(`let [${spec.names.join(", ")}] = [${vals.join(", ")}];`);
899
- }
973
+ this._emitVarBindings(spec.names, vals, paired);
900
974
  } else {
901
975
  const zero = spec.type ? this.zeroValueForTypeNode(spec.type) : "null";
902
976
  for (const name of spec.names) {
@@ -908,6 +982,20 @@ export class CodeGen {
908
982
  }
909
983
  }
910
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
+
911
999
  genConstDecl(decl) {
912
1000
  for (const spec of decl.decls) {
913
1001
  const vals = spec.value.map((v) => this.genExpr(v));