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.
- package/README.md +35 -39
- package/package.json +37 -4
- package/src/backend/js/expressions.js +323 -37
- package/src/backend/js/index.js +137 -49
- package/src/backend/js/runtime.js +570 -6
- package/src/backend/js/statements.js +36 -6
- package/src/backend/js/stdlib/errors.js +5 -0
- package/src/backend/js/stdlib/index.js +12 -0
- package/src/backend/js/stdlib/json.js +972 -0
- package/src/backend/js/stdlib/simd.js +24 -0
- package/src/backend/wasm/boundary.js +18 -0
- package/src/backend/wasm/emit-builtins.js +17 -0
- package/src/backend/wasm/emit-exprs.js +11 -0
- package/src/backend/wasm/emit.js +9 -0
- package/src/backend/wasm/encode.js +85 -0
- package/src/backend/wasm/optimize.js +4 -2
- package/src/backend/wasm/stdlib/index.js +3 -0
- package/src/backend/wasm/stdlib/simd.js +234 -0
- package/src/backend/wasm/types.js +21 -0
- package/src/backend/wasm/wat.js +37 -0
- package/src/dts-parser.js +71 -14
- package/src/lower/boxing.js +1 -6
- package/src/lower/embedding.js +2 -1
- package/src/parser/struct-tag.js +144 -0
- package/src/parser/types.js +63 -6
- package/src/typechecker/assignability.js +45 -0
- package/src/typechecker/expressions.js +66 -14
- package/src/typechecker/index.js +35 -8
- package/src/typechecker/resolve.js +151 -2
- package/src/typechecker/statements.js +68 -9
- package/src/typechecker/stdlib/extended.js +37 -0
- package/src/typechecker/stdlib/simd.js +73 -0
- package/src/typechecker/stdlib.js +2 -0
- package/src/typechecker/types.js +18 -3
- package/src/vendor.js +86 -11
package/src/backend/js/index.js
CHANGED
|
@@ -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
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
743
|
+
genMethod(decl, opts = null) {
|
|
692
744
|
const prevFileIdx = this._currentSrcFileIdx;
|
|
693
745
|
if (decl._srcFileIdx != null) this._currentSrcFileIdx = decl._srcFileIdx;
|
|
694
|
-
const
|
|
695
|
-
const
|
|
696
|
-
|
|
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
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
}
|
|
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
|
-
//
|
|
737
|
-
//
|
|
738
|
-
|
|
739
|
-
|
|
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
|
-
|
|
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));
|