gofront 1.8.0 → 1.9.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 +3 -3
- package/package.json +5 -4
- package/src/backend/js/expressions.js +849 -371
- package/src/backend/js/index.js +241 -147
- package/src/backend/js/natives.js +383 -0
- package/src/backend/js/runtime.js +512 -2547
- package/src/backend/js/statements.js +506 -103
- package/src/backend/js/stdlib/index.js +45 -93
- package/src/backend/js/templ.js +32 -45
- package/src/backend/js/type-info.js +177 -13
- package/src/backend/shared/fixed-float.js +20 -0
- package/src/backend/shared/fmt-types.js +63 -171
- package/src/backend/shared/host-unicode.js +36 -0
- package/src/backend/wasm/boundary.js +490 -965
- package/src/backend/wasm/emit-builtins.js +643 -256
- package/src/backend/wasm/emit-exprs.js +124 -57
- package/src/backend/wasm/emit-maps.js +17 -367
- package/src/backend/wasm/emit-stmts.js +325 -75
- package/src/backend/wasm/emit.js +104 -13
- package/src/backend/wasm/globals.js +47 -8
- package/src/backend/wasm/glue.js +2 -4
- package/src/backend/wasm/index.js +36 -530
- package/src/backend/wasm/method-expr.js +12 -1
- package/src/backend/wasm/module-emitter.js +92 -717
- package/src/backend/wasm/monomorph.js +103 -6
- package/src/backend/wasm/natives.js +596 -0
- package/src/backend/wasm/prescan.js +5 -153
- package/src/backend/wasm/prune-funcs.js +89 -0
- package/src/backend/wasm/stdlib/index.js +2 -32
- package/src/backend/wasm/types.js +68 -20
- package/src/backend/wasm/variadic.js +3 -3
- package/src/cli/cli-core.js +5 -1
- package/src/cli/test-runner.js +115 -342
- package/src/compiler.js +108 -130
- package/src/lexer/lexer.js +105 -11
- package/src/lexer/templ-lexer.js +61 -18
- package/src/lexer/tokens.js +1 -0
- package/src/link/stdlib.js +354 -132
- package/src/link/type-names.js +148 -0
- package/src/link/wasm.js +30 -6
- package/src/lower/bounds.js +153 -0
- package/src/lower/captures.js +92 -33
- package/src/lower/embedding.js +32 -3
- package/src/lower/errors-as.js +196 -0
- package/src/lower/fmt-formatters.js +905 -0
- package/src/lower/functions.js +2 -0
- package/src/lower/gen-types.js +274 -0
- package/src/lower/index.js +7 -1
- package/src/lower/json-codecs.js +228 -247
- package/src/lower/ownership.js +25 -7
- package/src/lower/reachability.js +69 -51
- package/src/lower/walk.js +12 -0
- package/src/parser/declarations.js +28 -5
- package/src/parser/expressions.js +86 -16
- package/src/parser/statements.js +1 -0
- package/src/parser/templ.js +44 -5
- package/src/parser/types.js +1 -0
- package/src/resolver.js +1 -2
- package/src/typechecker/assignability.js +124 -11
- package/src/typechecker/const-fold.js +70 -27
- package/src/typechecker/constants.js +32 -6
- package/src/typechecker/expressions.js +326 -98
- package/src/typechecker/index.js +208 -33
- package/src/typechecker/init-order.js +136 -40
- package/src/typechecker/operand-text.js +40 -0
- package/src/typechecker/resolve.js +192 -36
- package/src/typechecker/statements.js +122 -11
- package/src/typechecker/stdlib/excluded.js +9 -5
- package/src/typechecker/stdlib/index.js +25 -9
- package/src/typechecker/termination.js +10 -1
- package/src/typechecker/types.js +203 -573
- package/stdlib/bytes/buffer.go +479 -0
- package/stdlib/bytes/bytes.go +1010 -0
- package/stdlib/bytes/reader.go +161 -0
- package/stdlib/context/context.go +115 -0
- package/stdlib/crypto/rand/rand.go +34 -0
- package/stdlib/crypto/sha256/sha256.go +249 -0
- package/stdlib/encoding/base64/base64.go +331 -0
- package/stdlib/encoding/binary/binary.go +332 -0
- package/stdlib/encoding/hex/hex.go +140 -0
- package/stdlib/encoding/json/json.go +62 -0
- package/stdlib/errors/errors.go +174 -0
- package/stdlib/fmt/errors.go +110 -0
- package/stdlib/fmt/format.go +732 -0
- package/stdlib/fmt/print.go +1232 -0
- package/stdlib/gom/gom.go +293 -0
- package/stdlib/html/html.go +251 -0
- package/stdlib/internal/fmtrt/fmtrt.go +163 -0
- package/stdlib/internal/jsonrt/dec.go +171 -38
- package/stdlib/internal/jsonrt/enc.go +186 -36
- package/stdlib/internal/jsonrt/scanner.go +17 -8
- package/stdlib/io/io.go +157 -1
- package/stdlib/maps/maps.go +131 -0
- package/stdlib/math/bits/bits.go +507 -0
- package/stdlib/math/extra.go +16 -14
- package/stdlib/math/math.go +439 -0
- package/stdlib/math/rand/rand.go +297 -0
- package/stdlib/os/os.go +93 -0
- package/stdlib/path/filepath/filepath.go +47 -0
- package/stdlib/path/path.go +394 -0
- package/stdlib/regexp/regexp.go +231 -0
- package/stdlib/slices/slices.go +526 -0
- package/stdlib/slices/sort.go +209 -0
- package/stdlib/slices/zsort_cmp.go +480 -0
- package/stdlib/slices/zsort_ordered.go +482 -0
- package/stdlib/sort/sort.go +391 -452
- package/stdlib/sort/zsort_func.go +480 -0
- package/stdlib/sort/zsort_iface.go +480 -0
- package/stdlib/strconv/float.go +1699 -0
- package/stdlib/strconv/isprint.go +752 -0
- package/stdlib/strconv/quote.go +235 -50
- package/stdlib/strconv/strconv.go +616 -0
- package/stdlib/strings/builder.go +95 -0
- package/stdlib/strings/reader.go +163 -0
- package/stdlib/strings/replacer.go +102 -0
- package/stdlib/strings/strings.go +807 -0
- package/stdlib/sync/sync.go +268 -0
- package/stdlib/testing/async_js.go +61 -0
- package/stdlib/testing/benchmark.go +297 -0
- package/stdlib/testing/testing.go +523 -0
- package/stdlib/time/duration.go +233 -0
- package/stdlib/time/format.go +745 -0
- package/stdlib/time/parse.go +790 -0
- package/stdlib/time/sleep_js.go +63 -0
- package/stdlib/time/time.go +834 -0
- package/stdlib/unicode/unicode.go +141 -3
- package/stdlib/unicode/utf8/utf8.go +464 -0
- package/src/backend/js/fmt-runtime.js +0 -573
- package/src/backend/js/stdlib/base64.js +0 -12
- package/src/backend/js/stdlib/binary.js +0 -102
- package/src/backend/js/stdlib/bits.js +0 -119
- package/src/backend/js/stdlib/builder.js +0 -98
- package/src/backend/js/stdlib/bytes.js +0 -368
- package/src/backend/js/stdlib/errors.js +0 -45
- package/src/backend/js/stdlib/fmt.js +0 -517
- package/src/backend/js/stdlib/gom.js +0 -155
- package/src/backend/js/stdlib/hex.js +0 -17
- package/src/backend/js/stdlib/html.js +0 -20
- package/src/backend/js/stdlib/io.js +0 -137
- package/src/backend/js/stdlib/json.js +0 -1167
- package/src/backend/js/stdlib/maps.js +0 -37
- package/src/backend/js/stdlib/math.js +0 -85
- package/src/backend/js/stdlib/os.js +0 -13
- package/src/backend/js/stdlib/path.js +0 -48
- package/src/backend/js/stdlib/rand.js +0 -32
- package/src/backend/js/stdlib/regexp.js +0 -54
- package/src/backend/js/stdlib/sha256.js +0 -12
- package/src/backend/js/stdlib/shared.js +0 -17
- package/src/backend/js/stdlib/simd.js +0 -34
- package/src/backend/js/stdlib/slices.js +0 -107
- package/src/backend/js/stdlib/sort.js +0 -30
- package/src/backend/js/stdlib/strconv.js +0 -79
- package/src/backend/js/stdlib/strings.js +0 -301
- package/src/backend/js/stdlib/sync.js +0 -13
- package/src/backend/js/stdlib/testing.js +0 -41
- package/src/backend/js/stdlib/time.js +0 -152
- package/src/backend/js/stdlib/unicode.js +0 -25
- package/src/backend/js/stdlib/utf8.js +0 -26
- package/src/backend/wasm/stdlib/bytes.js +0 -6716
- package/src/backend/wasm/stdlib/core.js +0 -10278
- package/src/backend/wasm/stdlib/io.js +0 -3578
- package/src/backend/wasm/stdlib/packages.js +0 -2402
- package/src/backend/wasm/stdlib/pure.js +0 -720
- package/src/backend/wasm/stdlib/time.js +0 -2229
- package/src/typechecker/stdlib/core.js +0 -1079
- package/src/typechecker/stdlib/extended.js +0 -1210
- package/stdlib/bytes/iter.go +0 -192
- package/stdlib/bytes/search.go +0 -240
- package/stdlib/bytes/split.go +0 -218
- package/stdlib/errors/overlay.go +0 -12
- package/stdlib/maps/iter.go +0 -65
- package/stdlib/slices/extra.go +0 -73
- package/stdlib/slices/iter.go +0 -113
- package/stdlib/strings/extra.go +0 -35
- package/stdlib/strings/iter.go +0 -122
- package/stdlib/sync/extra.go +0 -105
- package/stdlib/unicode/utf8/bytes.go +0 -239
|
@@ -1,74 +1,56 @@
|
|
|
1
1
|
// CodeGen stdlib call generation — installed as a mixin on CodeGen.prototype.
|
|
2
|
-
//
|
|
2
|
+
// Dispatches `gofront/shared` and `gofront/simd` calls to inline JS.
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import { binaryMethods } from "./binary.js";
|
|
6
|
-
import { bitsMethods } from "./bits.js";
|
|
7
|
-
import { builderMethods } from "./builder.js";
|
|
8
|
-
import { bytesMethods } from "./bytes.js";
|
|
9
|
-
import { errorsMethods } from "./errors.js";
|
|
10
|
-
import { fmtMethods } from "./fmt.js";
|
|
11
|
-
import { gomMethods } from "./gom.js";
|
|
12
|
-
import { hexMethods } from "./hex.js";
|
|
13
|
-
import { htmlMethods } from "./html.js";
|
|
14
|
-
import { ioMethods } from "./io.js";
|
|
15
|
-
import { jsonMethods } from "./json.js";
|
|
16
|
-
import { mapsMethods } from "./maps.js";
|
|
17
|
-
import { mathMethods } from "./math.js";
|
|
18
|
-
import { osMethods } from "./os.js";
|
|
19
|
-
import { pathMethods } from "./path.js";
|
|
20
|
-
import { randMethods } from "./rand.js";
|
|
21
|
-
import { regexpMethods } from "./regexp.js";
|
|
22
|
-
import { sha256Methods } from "./sha256.js";
|
|
23
|
-
import { sharedMethods } from "./shared.js";
|
|
24
|
-
import { simdMethods } from "./simd.js";
|
|
25
|
-
import { slicesMethods } from "./slices.js";
|
|
26
|
-
import { sortMethods } from "./sort.js";
|
|
27
|
-
import { strconvMethods } from "./strconv.js";
|
|
28
|
-
import { stringsMethods } from "./strings.js";
|
|
29
|
-
import { syncMethods } from "./sync.js";
|
|
30
|
-
import { testingMethods } from "./testing.js";
|
|
31
|
-
import { timeMethods } from "./time.js";
|
|
32
|
-
import { unicodeMethods } from "./unicode.js";
|
|
33
|
-
import { utf8Methods } from "./utf8.js";
|
|
4
|
+
import { SHARED_KINDS } from "../../../typechecker/stdlib/shared.js";
|
|
34
5
|
|
|
35
6
|
/** @typedef {import('../index.js').CodeGen} CodeGen */
|
|
36
7
|
|
|
37
8
|
// Stdlib namespace → handler method name. Used by _genStdlibCall to dispatch
|
|
38
9
|
// dynamically without creating static call edges.
|
|
39
10
|
const STDLIB_METHOD_MAP = {
|
|
40
|
-
fmt: "_genFmt",
|
|
41
|
-
strings: "_genStrings",
|
|
42
|
-
bytes: "_genBytes",
|
|
43
|
-
strconv: "_genStrconv",
|
|
44
|
-
sort: "_genSort",
|
|
45
|
-
math: "_genMath",
|
|
46
|
-
bits: "_genBits",
|
|
47
|
-
unicode: "_genUnicode",
|
|
48
|
-
os: "_genOs",
|
|
49
|
-
errors: "_genErrors",
|
|
50
|
-
time: "_genTime",
|
|
51
|
-
regexp: "_genRegexp",
|
|
52
|
-
slices: "_genSlices",
|
|
53
|
-
maps: "_genMaps",
|
|
54
|
-
html: "_genHtml",
|
|
55
|
-
io: "_genIo",
|
|
56
|
-
rand: "_genRand",
|
|
57
|
-
utf8: "_genUtf8",
|
|
58
|
-
path: "_genPath",
|
|
59
|
-
filepath: "_genPath",
|
|
60
|
-
testing: "_genTesting",
|
|
61
11
|
shared: "_genShared",
|
|
62
|
-
sync: "_genSync",
|
|
63
|
-
sha256: "_genSha256",
|
|
64
|
-
binary: "_genBinary",
|
|
65
|
-
hex: "_genHex",
|
|
66
|
-
base64: "_genBase64",
|
|
67
|
-
json: "_genJson",
|
|
68
12
|
simd: "_genSimd",
|
|
69
13
|
};
|
|
70
14
|
|
|
71
15
|
const dispatchMethods = {
|
|
16
|
+
// `gofront/shared`: in js packages a shared buffer is just a TypedArray (no
|
|
17
|
+
// linear memory is involved); in wasm packages the facade hands out
|
|
18
|
+
// zero-copy views onto the module memory.
|
|
19
|
+
_genShared(fn, a) {
|
|
20
|
+
if (!fn.startsWith("New")) return undefined;
|
|
21
|
+
const info = SHARED_KINDS[fn.slice(3)];
|
|
22
|
+
if (!info) return undefined;
|
|
23
|
+
return `new ${info.ctor}(${a()[0]})`;
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
// `gofront/simd` in JS packages.
|
|
27
|
+
_genSimd(fn, a) {
|
|
28
|
+
this.useHelper("simd");
|
|
29
|
+
const args = a();
|
|
30
|
+
switch (fn) {
|
|
31
|
+
case "Splat":
|
|
32
|
+
return `__simd_splat(${args[0]})`;
|
|
33
|
+
case "NewF32x4":
|
|
34
|
+
return `__simd_f32x4(${args[0]}, ${args[1]}, ${args[2]}, ${args[3]})`;
|
|
35
|
+
case "Select":
|
|
36
|
+
return `__simd_select(${args[0]}, ${args[1]}, ${args[2]})`;
|
|
37
|
+
case "Shuffle":
|
|
38
|
+
return `__simd_shuffle(${args.join(", ")})`;
|
|
39
|
+
case "LoadF32x4":
|
|
40
|
+
return `__simd_load(${args[0]}, ${args[1]})`;
|
|
41
|
+
case "StoreF32x4":
|
|
42
|
+
return `__simd_store(${args[0]}, ${args[1]}, ${args[2]})`;
|
|
43
|
+
case "LoadF32x4Slice":
|
|
44
|
+
return `__simd_load_slice(${args[0]}, ${args[1]})`;
|
|
45
|
+
case "StoreF32x4Slice":
|
|
46
|
+
return `__simd_store_slice(${args[0]}, ${args[1]}, ${args[2]})`;
|
|
47
|
+
case "Pad4":
|
|
48
|
+
return `(Math.floor((${args[0]} + 3) / 4) * 4)`;
|
|
49
|
+
default:
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
|
|
72
54
|
typeComment(typeNode) {
|
|
73
55
|
if (!typeNode) return "unknown";
|
|
74
56
|
switch (typeNode.kind) {
|
|
@@ -86,18 +68,18 @@ const dispatchMethods = {
|
|
|
86
68
|
// Dispatch table for stdlib namespace calls (pkg.Func → inline JS).
|
|
87
69
|
// Returns the generated JS string, or undefined if not handled.
|
|
88
70
|
_genStdlibCall(ns, fn, expr) {
|
|
89
|
-
if (ns === "gom") return this._genGom(fn, expr);
|
|
90
71
|
const method = STDLIB_METHOD_MAP[ns];
|
|
91
72
|
if (!method) return undefined;
|
|
92
|
-
//
|
|
93
|
-
|
|
73
|
+
// A member written in Go is an ordinary call of its mangled name.
|
|
74
|
+
if (expr.func?._stdName) return undefined;
|
|
75
|
+
// Stdlib shims take raw values, not the interface box of a named value.
|
|
94
76
|
const isNamespace = expr.func?.expr?._type?.kind === "namespace";
|
|
95
|
-
if (isNamespace
|
|
77
|
+
if (isNamespace) {
|
|
96
78
|
for (const e of expr.args) if (e) e._noNamedBox = true;
|
|
97
79
|
}
|
|
98
80
|
const a = () => expr.args.map((e) => this.genExpr(e));
|
|
99
81
|
const res = this[method](fn, a, expr);
|
|
100
|
-
if (res === undefined && isNamespace
|
|
82
|
+
if (res === undefined && isNamespace) {
|
|
101
83
|
for (const e of expr.args) if (e) delete e._noNamedBox;
|
|
102
84
|
}
|
|
103
85
|
return res;
|
|
@@ -106,35 +88,5 @@ const dispatchMethods = {
|
|
|
106
88
|
|
|
107
89
|
/** @type {ThisType<CodeGen>} */
|
|
108
90
|
export const stdlibGenMethods = {
|
|
109
|
-
...bitsMethods,
|
|
110
|
-
...builderMethods,
|
|
111
|
-
...bytesMethods,
|
|
112
|
-
...errorsMethods,
|
|
113
|
-
...fmtMethods,
|
|
114
|
-
...gomMethods,
|
|
115
|
-
...htmlMethods,
|
|
116
|
-
...ioMethods,
|
|
117
|
-
...mapsMethods,
|
|
118
|
-
...mathMethods,
|
|
119
|
-
...osMethods,
|
|
120
|
-
...pathMethods,
|
|
121
|
-
...randMethods,
|
|
122
|
-
...regexpMethods,
|
|
123
|
-
...sharedMethods,
|
|
124
|
-
...simdMethods,
|
|
125
|
-
...slicesMethods,
|
|
126
|
-
...sortMethods,
|
|
127
|
-
...strconvMethods,
|
|
128
|
-
...stringsMethods,
|
|
129
|
-
...timeMethods,
|
|
130
|
-
...unicodeMethods,
|
|
131
|
-
...utf8Methods,
|
|
132
|
-
...testingMethods,
|
|
133
|
-
...syncMethods,
|
|
134
|
-
...sha256Methods,
|
|
135
|
-
...binaryMethods,
|
|
136
|
-
...hexMethods,
|
|
137
|
-
...base64Methods,
|
|
138
|
-
...jsonMethods,
|
|
139
91
|
...dispatchMethods,
|
|
140
92
|
};
|
package/src/backend/js/templ.js
CHANGED
|
@@ -13,6 +13,8 @@ export const templGenMethods = {
|
|
|
13
13
|
)
|
|
14
14
|
.join(", ");
|
|
15
15
|
this.line(`function ${decl.name}(${params}) {`);
|
|
16
|
+
const prevBoxed = this._boxedVars;
|
|
17
|
+
this._boxedVars = new Set(this._boxedGlobals);
|
|
16
18
|
this.indented(() => {
|
|
17
19
|
// `@W(xs...)` passes `{ __sp: xs }` like any Go variadic call.
|
|
18
20
|
const last = decl.params[decl.params.length - 1];
|
|
@@ -26,6 +28,7 @@ export const templGenMethods = {
|
|
|
26
28
|
});
|
|
27
29
|
this.line(`}};`);
|
|
28
30
|
});
|
|
31
|
+
this._boxedVars = prevBoxed;
|
|
29
32
|
this.line("}");
|
|
30
33
|
},
|
|
31
34
|
|
|
@@ -86,11 +89,17 @@ export const templGenMethods = {
|
|
|
86
89
|
);
|
|
87
90
|
case "TemplComponent":
|
|
88
91
|
return this._genTemplComponentMount(node, p);
|
|
89
|
-
case "TemplChildren":
|
|
90
|
-
//
|
|
92
|
+
case "TemplChildren": {
|
|
93
|
+
// `{ name... }`: mount each node of a variadic gom.Node parameter
|
|
94
|
+
// (nil mounts nothing). `children` may also be absent altogether.
|
|
95
|
+
const slot =
|
|
96
|
+
node.name === "children"
|
|
97
|
+
? `(typeof children!=="undefined"?children:null)`
|
|
98
|
+
: this._genTemplExpr(node, "null");
|
|
91
99
|
return this.line(
|
|
92
|
-
`
|
|
100
|
+
`for (const ___c of ${slot} ?? []) ___c?.Mount?.(${p});`,
|
|
93
101
|
);
|
|
102
|
+
}
|
|
94
103
|
case "TemplIf":
|
|
95
104
|
return this._genTemplIf(node, p);
|
|
96
105
|
case "TemplFor":
|
|
@@ -200,15 +209,18 @@ export const templGenMethods = {
|
|
|
200
209
|
_genTemplSwitch(node, p) {
|
|
201
210
|
const { cases } = node;
|
|
202
211
|
if (cases.length === 0) return;
|
|
203
|
-
|
|
212
|
+
// `switch { case cond: }` compares each condition with `true`.
|
|
213
|
+
const exprJs = this._genTemplExpr(node, "true");
|
|
204
214
|
this.line(`switch (${exprJs}) {`);
|
|
205
215
|
this.indented(() => {
|
|
206
216
|
for (const c of cases) {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
217
|
+
if (c.caseTokens === null) this.line("default: {");
|
|
218
|
+
else {
|
|
219
|
+
// One JS label per value of `case a, b:`.
|
|
220
|
+
if (c.exprError) throw c.exprError;
|
|
221
|
+
const labels = (c.exprs ?? []).map((e) => `case ${this.genExpr(e)}:`);
|
|
222
|
+
this.line(`${labels.join(" ") || 'case "":'} {`);
|
|
223
|
+
}
|
|
212
224
|
this.indented(() => this._genTemplNodes(c.body, p));
|
|
213
225
|
this.line(`break; }`);
|
|
214
226
|
}
|
|
@@ -216,48 +228,23 @@ export const templGenMethods = {
|
|
|
216
228
|
this.line(`}`);
|
|
217
229
|
},
|
|
218
230
|
|
|
231
|
+
// The header is a parsed Go `for` statement, so it goes through the regular
|
|
232
|
+
// loop generator; its body is a statement that mounts the templ nodes.
|
|
219
233
|
_genTemplFor(node, p) {
|
|
220
234
|
if (node.forError) throw node.forError;
|
|
221
235
|
if (!node.forStmt)
|
|
222
236
|
throw new Error("templ for header was not parsed by TemplParser");
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
const condJs = forStmt.cond ? this.genExpr(forStmt.cond) : "true";
|
|
231
|
-
this.line(`while (${condJs}) {`);
|
|
232
|
-
this.indented(() => this._genTemplNodes(templBody, mountParent));
|
|
233
|
-
this.line("}");
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
// Range for: for key, val := range iter
|
|
237
|
-
const range = init.rhs[0];
|
|
238
|
-
const iterJs = this.genExpr(range.expr);
|
|
239
|
-
const lhs = init.lhs.map((e) => e.name ?? "_");
|
|
240
|
-
const keyName = lhs[0] ?? "_";
|
|
241
|
-
const valName = lhs[1] ?? null;
|
|
242
|
-
|
|
243
|
-
const loopHeader = this._genTemplRangeHeader(keyName, valName, iterJs);
|
|
244
|
-
this.line(loopHeader);
|
|
245
|
-
this.indented(() => this._genTemplNodes(templBody, mountParent));
|
|
246
|
-
this.line("}");
|
|
237
|
+
const mount = { kind: "TemplMount", parent: p };
|
|
238
|
+
// Hidden from the generic AST walkers: they know no templ node kinds.
|
|
239
|
+
Object.defineProperty(mount, "nodes", { value: node.body });
|
|
240
|
+
this.genFor({
|
|
241
|
+
...node.forStmt,
|
|
242
|
+
body: { kind: "Block", stmts: [mount] },
|
|
243
|
+
});
|
|
247
244
|
},
|
|
248
245
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
return `for (const ${keyName === "_" ? "_$" : keyName} of ${iterJs}) {`;
|
|
252
|
-
}
|
|
253
|
-
if (keyName === "_" && valName === "_")
|
|
254
|
-
return `for (const _$ of ${iterJs}) {`;
|
|
255
|
-
if (keyName === "_") return `for (const ${valName} of ${iterJs}) {`;
|
|
256
|
-
if (valName === "_")
|
|
257
|
-
return `for (const [${keyName}] of Object.entries(${iterJs})) {`;
|
|
258
|
-
// Default to .entries() for arrays, Object.entries for objects
|
|
259
|
-
this.useHelper("sliceGuard");
|
|
260
|
-
return `for (const [${keyName}, ${valName}] of __s(${iterJs}).entries()) {`;
|
|
246
|
+
_genTemplMountStmt(stmt) {
|
|
247
|
+
this._genTemplNodes(stmt.nodes, stmt.parent);
|
|
261
248
|
},
|
|
262
249
|
|
|
263
250
|
// Emit JS for the Go expression TemplParser parsed onto `node`. `emptyJs` is
|
|
@@ -7,7 +7,12 @@
|
|
|
7
7
|
// type's kind, identity and a type test (see `__ti` in HELPER_GENERICS).
|
|
8
8
|
|
|
9
9
|
import { forEachChild } from "../../lower/index.js";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
isAny,
|
|
12
|
+
isTypedArraySlice,
|
|
13
|
+
typedArrayConstructorForElem,
|
|
14
|
+
typeStr,
|
|
15
|
+
} from "../../typechecker/types.js";
|
|
11
16
|
import { mentionsTypeParam } from "./expressions.js";
|
|
12
17
|
|
|
13
18
|
// True when type node `node` names one of the type parameters `names`.
|
|
@@ -68,8 +73,22 @@ export const typeInfoGenMethods = {
|
|
|
68
73
|
return found;
|
|
69
74
|
},
|
|
70
75
|
|
|
76
|
+
// `x.M(...)` where x has a type parameter type: a raw value of a named
|
|
77
|
+
// non-struct type argument must be boxed to reach its methods.
|
|
78
|
+
_isTypeParamMethodCall(call, names) {
|
|
79
|
+
const t = call.func?.expr?._type;
|
|
80
|
+
return (
|
|
81
|
+
call.func?.kind === "SelectorExpr" &&
|
|
82
|
+
!call.func._isMethodExpr &&
|
|
83
|
+
t?.kind === "typeParam" &&
|
|
84
|
+
(!names || names.has(t.name))
|
|
85
|
+
);
|
|
86
|
+
},
|
|
87
|
+
|
|
71
88
|
_nodeUsesTypeInfo(node, names) {
|
|
72
89
|
if (node._ifaceKeyDyn && names.has(node._ifaceKeyParam)) return true;
|
|
90
|
+
if (node._ifaceSrc?.kind === "typeParam" && names.has(node._ifaceSrc.name))
|
|
91
|
+
return true;
|
|
73
92
|
switch (node.kind) {
|
|
74
93
|
case "TypeAssertExpr":
|
|
75
94
|
return typeNodeMentions(node.type, names);
|
|
@@ -79,11 +98,23 @@ export const typeInfoGenMethods = {
|
|
|
79
98
|
return typeNodeMentions(node.targetType, names);
|
|
80
99
|
case "BinaryExpr":
|
|
81
100
|
return (
|
|
82
|
-
(node.op === "==" || node.op === "!=") &&
|
|
83
|
-
|
|
101
|
+
((node.op === "==" || node.op === "!=") &&
|
|
102
|
+
node.left._type?.kind === "typeParam") ||
|
|
103
|
+
(node._type?.kind === "typeParam" && names.has(node._type.name))
|
|
104
|
+
);
|
|
105
|
+
case "AssignStmt":
|
|
106
|
+
return (
|
|
107
|
+
node.op !== "=" &&
|
|
108
|
+
node.lhs?.[0]?._type?.kind === "typeParam" &&
|
|
109
|
+
names.has(node.lhs[0]._type.name)
|
|
110
|
+
);
|
|
111
|
+
case "IncDecStmt":
|
|
112
|
+
return (
|
|
113
|
+
node.expr?._type?.kind === "typeParam" &&
|
|
114
|
+
names.has(node.expr._type.name)
|
|
84
115
|
);
|
|
85
116
|
case "CallExpr":
|
|
86
|
-
return this._callUsesTypeInfo(node);
|
|
117
|
+
return this._callUsesTypeInfo(node, names);
|
|
87
118
|
case "CompositeLit":
|
|
88
119
|
case "GenericTypeName":
|
|
89
120
|
return typeArgsMention(node);
|
|
@@ -92,7 +123,8 @@ export const typeInfoGenMethods = {
|
|
|
92
123
|
}
|
|
93
124
|
},
|
|
94
125
|
|
|
95
|
-
_callUsesTypeInfo(call) {
|
|
126
|
+
_callUsesTypeInfo(call, names) {
|
|
127
|
+
if (this._isTypeParamMethodCall(call, names)) return true;
|
|
96
128
|
if (call._isTypeConversion)
|
|
97
129
|
return call._conversionTargetType?.kind === "typeParam";
|
|
98
130
|
// A fmt operand of a type parameter type is formatted by its instantiated
|
|
@@ -146,7 +178,7 @@ export const typeInfoGenMethods = {
|
|
|
146
178
|
const check = this._typeCheckForType(t, "__v");
|
|
147
179
|
const is = check ? `(__v) => ${check}` : "null";
|
|
148
180
|
const unbox = this._boxesInInterface(t) ? "true" : "false";
|
|
149
|
-
const box = this._ifaceKeyBox(t);
|
|
181
|
+
const box = this._ifaceKeyBox(t) ?? this._ptrMethodBox(t);
|
|
150
182
|
const fmtBox = this._fmtBoxFor(t);
|
|
151
183
|
const tail = fmtBox
|
|
152
184
|
? `, ${box ?? "null"}, ${fmtBox}`
|
|
@@ -156,6 +188,22 @@ export const typeInfoGenMethods = {
|
|
|
156
188
|
return `__ti(${base}, ${JSON.stringify(k)}, ${JSON.stringify(typeStr(t))}, ${is}, ${unbox}${tail})`;
|
|
157
189
|
},
|
|
158
190
|
|
|
191
|
+
// How `__ik` tells the dynamic type of an interface map key apart where
|
|
192
|
+
// the JS value cannot: the numeric type name for types sharing a JS
|
|
193
|
+
// representation with `int`/`float64`/`int64`, `*` for pointers (keyed by
|
|
194
|
+
// identity) and `!type` for types Go cannot hash.
|
|
195
|
+
_ifaceKeyTag(t) {
|
|
196
|
+
if (!t || t.kind === "named" || mentionsTypeParam(t)) return null;
|
|
197
|
+
if (t.kind === "pointer") return "*";
|
|
198
|
+
if (t.kind === "slice" || t.kind === "map" || t.kind === "func")
|
|
199
|
+
return `!${this._dynTypeName(t)}`;
|
|
200
|
+
if (t.kind !== "basic") return null;
|
|
201
|
+
const name = this._dynTypeName(t);
|
|
202
|
+
if (["int", "float64", "int64", "string", "bool"].includes(name))
|
|
203
|
+
return null;
|
|
204
|
+
return /^(u?int\d*|uintptr|float32)$/.test(name) ? name : null;
|
|
205
|
+
},
|
|
206
|
+
|
|
159
207
|
// A function boxing a plain value of named basic type `t`, as converting
|
|
160
208
|
// it to an interface does; null when `t` is not boxed.
|
|
161
209
|
_ifaceKeyBox(t) {
|
|
@@ -163,9 +211,27 @@ export const typeInfoGenMethods = {
|
|
|
163
211
|
return `(__v) => ${this._genNamedIfaceBox("__v", t, false)}`;
|
|
164
212
|
},
|
|
165
213
|
|
|
214
|
+
// A function boxing a pointer to a named non-struct type with methods, as
|
|
215
|
+
// converting it to an interface does; null for any other type.
|
|
216
|
+
_ptrMethodBox(t) {
|
|
217
|
+
if (t?.kind !== "pointer" || !this._boxesInInterface(t)) return null;
|
|
218
|
+
return `(__v) => ${this._genNamedIfaceBox("__v", t.base, true)}`;
|
|
219
|
+
},
|
|
220
|
+
|
|
166
221
|
// A function giving a plain value of type `t` the type information fmt
|
|
167
|
-
// needs once it is an `any` operand
|
|
222
|
+
// needs once it is an `any` operand; null when it needs none.
|
|
168
223
|
_fmtBoxFor(t) {
|
|
224
|
+
const base = this._fmtBoxBase(t);
|
|
225
|
+
// An unnamed slice, map or pointer also carries its type tag, as
|
|
226
|
+
// converting it to an `any` gives it, so a type switch tells it apart.
|
|
227
|
+
const tag = this._dynTypeTag(t);
|
|
228
|
+
if (!tag) return base;
|
|
229
|
+
this.useHelper("ifaceBox");
|
|
230
|
+
const ptr = t.kind === "pointer" ? ", true" : "";
|
|
231
|
+
return `(__v) => __ity(${base ? `(${base})(__v)` : "__v"}, ${JSON.stringify(tag)}${ptr})`;
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
_fmtBoxBase(t) {
|
|
169
235
|
if (!t || t.kind === "typeParam" || mentionsTypeParam(t)) return null;
|
|
170
236
|
if (this._ifaceKeyBox(t)) return null; // boxed by the key box already
|
|
171
237
|
const u = this._underlyingOf(t);
|
|
@@ -173,15 +239,113 @@ export const typeInfoGenMethods = {
|
|
|
173
239
|
this.useHelper("ifaceBox");
|
|
174
240
|
return "(__v) => __ifp(__v)";
|
|
175
241
|
}
|
|
176
|
-
const desc = this._fmtDesc(t);
|
|
177
|
-
if (desc) this.useHelper("ifd");
|
|
178
|
-
const tagged = (v) => (desc ? `__ifd(${v}, ${desc})` : v);
|
|
179
242
|
if (u?.kind === "map") {
|
|
180
243
|
this.useHelper("ifaceBox");
|
|
181
|
-
return
|
|
244
|
+
return "(__v) => __ifm(__v)";
|
|
245
|
+
}
|
|
246
|
+
if (u?.kind === "slice") return "(__v) => (__v ?? [])";
|
|
247
|
+
return null;
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
// The run-time type tag of an unnamed slice, map or non-struct pointer
|
|
251
|
+
// entering an interface, or null when the value needs none. The tag lets
|
|
252
|
+
// assertions and type switches tell `[]int32` from `[]uint8`, `*int` from
|
|
253
|
+
// `*string`, and a nil `*int` from the nil interface.
|
|
254
|
+
_dynTypeTag(t) {
|
|
255
|
+
if (!t || mentionsTypeParam(t)) return null;
|
|
256
|
+
if (
|
|
257
|
+
t.kind === "slice" ||
|
|
258
|
+
t.kind === "map" ||
|
|
259
|
+
t.kind === "array" ||
|
|
260
|
+
t.kind === "struct" ||
|
|
261
|
+
t.kind === "func"
|
|
262
|
+
)
|
|
263
|
+
return this._dynTypeName(t);
|
|
264
|
+
if (this._isNamedComposite(t)) return t.name;
|
|
265
|
+
if (t.kind === "pointer") {
|
|
266
|
+
const base = t.base;
|
|
267
|
+
if (base?.kind === "struct" && !base.name) return this._dynTypeName(t);
|
|
268
|
+
if (this._underlyingOf(base)?.kind === "struct") return null;
|
|
269
|
+
if (this._boxesInInterface(t)) return null;
|
|
270
|
+
return this._dynTypeName(t);
|
|
271
|
+
}
|
|
272
|
+
return null;
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
// A named slice, array or map type: its values are plain JS arrays and
|
|
276
|
+
// objects, told apart in an interface by a tag carrying the type's name.
|
|
277
|
+
_isNamedComposite(t) {
|
|
278
|
+
if (t?.kind !== "named" || this._namedBoxInfo(t)) return false;
|
|
279
|
+
const kind = this._underlyingOf(t)?.kind;
|
|
280
|
+
return kind === "slice" || kind === "array" || kind === "map";
|
|
281
|
+
},
|
|
282
|
+
|
|
283
|
+
// `typeStr` with the spellings of identical types unified.
|
|
284
|
+
_dynTypeName(t) {
|
|
285
|
+
return this._dynTypeStr(t)
|
|
286
|
+
.replace(/\bbyte\b/g, "uint8")
|
|
287
|
+
.replace(/\brune\b/g, "int32")
|
|
288
|
+
.replace(/\bany\b/g, "interface {}")
|
|
289
|
+
.replace(/interface\{\.\.\.\}/g, "interface {}");
|
|
290
|
+
},
|
|
291
|
+
|
|
292
|
+
// `typeStr`, but an unnamed struct lists its fields, so two anonymous
|
|
293
|
+
// structs do not share a tag.
|
|
294
|
+
_dynTypeStr(t) {
|
|
295
|
+
const s = (x) => this._dynTypeStr(x);
|
|
296
|
+
switch (t?.kind) {
|
|
297
|
+
case "slice":
|
|
298
|
+
return `[]${s(t.elem)}`;
|
|
299
|
+
case "array":
|
|
300
|
+
return `[${t.size?.value ?? t.size ?? "..."}]${s(t.elem)}`;
|
|
301
|
+
case "map":
|
|
302
|
+
return `map[${s(t.key)}]${s(t.value)}`;
|
|
303
|
+
case "pointer":
|
|
304
|
+
return `*${s(t.base)}`;
|
|
305
|
+
case "struct":
|
|
306
|
+
if (t.name || !t._structFields) return typeStr(t);
|
|
307
|
+
return `struct{${t._structFields
|
|
308
|
+
.map(
|
|
309
|
+
(f) =>
|
|
310
|
+
`${(f.names ?? []).join(",")} ${s(f.type)}${f.tag ? ` ${JSON.stringify(f.tag)}` : ""}${f.embedded ? " embedded" : ""}`,
|
|
311
|
+
)
|
|
312
|
+
.join("; ")}}`;
|
|
313
|
+
default:
|
|
314
|
+
return typeStr(t);
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
|
|
318
|
+
// Run-time test that `val` holds a value of the unnamed composite type
|
|
319
|
+
// (or named func type) of assertion target `typeNode`; null when the
|
|
320
|
+
// checker recorded no type for it.
|
|
321
|
+
_dynTypeCheck(typeNode, val) {
|
|
322
|
+
const t = typeNode?._assertType;
|
|
323
|
+
if (!t) return null;
|
|
324
|
+
const u = this._underlyingOf(t);
|
|
325
|
+
const shape = mentionsTypeParam(t);
|
|
326
|
+
const tag = JSON.stringify(shape ? "" : this._dynTypeName(t));
|
|
327
|
+
this.useHelper("ifaceBox");
|
|
328
|
+
const args = (kind, ctor = "null", n = "0") =>
|
|
329
|
+
`__isty(${val}, ${tag}, "${kind}", ${ctor}, ${n})`;
|
|
330
|
+
if (u?.kind === "func") return args("func");
|
|
331
|
+
if (t.kind === "named") return null;
|
|
332
|
+
if (u?.kind === "map") return args("map");
|
|
333
|
+
if (u?.kind === "struct") return args("struct");
|
|
334
|
+
if (u?.kind === "array")
|
|
335
|
+
return args("array", "null", String(u.size?.value ?? u.size ?? 0));
|
|
336
|
+
if (u?.kind === "slice") {
|
|
337
|
+
const ctor = isTypedArraySlice(u)
|
|
338
|
+
? typedArrayConstructorForElem(u.elem)
|
|
339
|
+
: null;
|
|
340
|
+
return args("slice", ctor ?? "null");
|
|
341
|
+
}
|
|
342
|
+
if (u?.kind === "pointer") {
|
|
343
|
+
if (u.base?.kind === "struct" && !u.base.name) return args("struct");
|
|
344
|
+
if (this._underlyingOf(u.base)?.kind === "struct") return null;
|
|
345
|
+
if (this._boxesInInterface(u)) return null;
|
|
346
|
+
return args("ptr");
|
|
182
347
|
}
|
|
183
|
-
|
|
184
|
-
return desc ? `(__v) => ${tagged("__v")}` : null;
|
|
348
|
+
return null;
|
|
185
349
|
},
|
|
186
350
|
|
|
187
351
|
_underlyingOf(t) {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// The host half of strconv's fixed-point float formatting: a JS function,
|
|
2
|
+
// as source text, that both backends embed (src/backend/js/natives.js and the
|
|
3
|
+
// WASM host imports in src/backend/wasm/boundary.js).
|
|
4
|
+
//
|
|
5
|
+
// `toFixed` is exact except that it rounds a tie (a value whose decimal
|
|
6
|
+
// expansion ends in a 5 right after the digits kept) up, where Go rounds it
|
|
7
|
+
// to even; the extra digits of a second call tell the two apart. A tie is a
|
|
8
|
+
// multiple of 2^-(p+1), so any other value needs no second call. "" declines.
|
|
9
|
+
export const FIXED_FLOAT_JS = `(x, p) => {
|
|
10
|
+
if (!(x < 1e21)) return "";
|
|
11
|
+
const s = x.toFixed(p);
|
|
12
|
+
if (x % 1 === 0 || (x * 2 ** (p + 1)) % 1 !== 0) return s;
|
|
13
|
+
const t = x.toFixed(p + 30);
|
|
14
|
+
const dot = t.indexOf(".");
|
|
15
|
+
if (/^50*$/.test(t.slice(dot + 1 + p))) {
|
|
16
|
+
const down = t.slice(0, p === 0 ? dot : dot + 1 + p);
|
|
17
|
+
return (down.charCodeAt(down.length - 1) - 48) % 2 === 0 ? down : s;
|
|
18
|
+
}
|
|
19
|
+
return s;
|
|
20
|
+
}`;
|