gofront 1.7.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 +14 -14
- package/package.json +10 -38
- package/src/backend/js/expressions.js +2060 -389
- package/src/backend/js/index.js +586 -174
- package/src/backend/js/natives.js +383 -0
- package/src/backend/js/runtime.js +936 -1644
- package/src/backend/js/statements.js +1349 -132
- package/src/backend/js/stdlib/index.js +51 -94
- package/src/backend/js/templ.js +67 -118
- package/src/backend/js/type-info.js +436 -0
- package/src/backend/shared/fixed-float.js +20 -0
- package/src/backend/shared/fmt-types.js +80 -0
- package/src/backend/shared/host-unicode.js +36 -0
- package/src/backend/wasm/boundary.js +753 -872
- package/src/backend/wasm/emit-builtins.js +914 -237
- package/src/backend/wasm/emit-exprs.js +1456 -195
- package/src/backend/wasm/emit-maps.js +78 -380
- package/src/backend/wasm/emit-stmts.js +1659 -214
- package/src/backend/wasm/emit.js +614 -88
- package/src/backend/wasm/encode.js +43 -13
- package/src/backend/wasm/globals.js +491 -0
- package/src/backend/wasm/glue.js +2 -4
- package/src/backend/wasm/index.js +406 -3195
- package/src/backend/wasm/method-expr.js +392 -0
- package/src/backend/wasm/module-emitter.js +1615 -0
- package/src/backend/wasm/monomorph.js +164 -9
- package/src/backend/wasm/natives.js +596 -0
- package/src/backend/wasm/prescan.js +249 -0
- package/src/backend/wasm/prune-funcs.js +89 -0
- package/src/backend/wasm/stdlib/index.js +2 -37
- package/src/backend/wasm/stdlib/simd.js +293 -28
- package/src/backend/wasm/types.js +260 -27
- package/src/backend/wasm/variadic.js +93 -0
- package/src/backend/wasm/wat.js +47 -20
- package/src/{cli-core.js → cli/cli-core.js} +241 -11
- package/src/{test-runner.js → cli/test-runner.js} +188 -122
- package/src/{vendor.js → cli/vendor.js} +3 -4
- package/src/compiler.js +202 -386
- package/src/index.js +15 -169
- package/src/{lexer.js → lexer/lexer.js} +203 -62
- package/src/{templ-lexer.js → lexer/templ-lexer.js} +61 -18
- package/src/{tokens.js → lexer/tokens.js} +2 -0
- package/src/link/sourcemap.js +97 -0
- package/src/link/stdlib.js +737 -0
- package/src/link/type-names.js +148 -0
- package/src/link/wasm.js +318 -0
- package/src/lower/bounds.js +547 -0
- package/src/lower/boxing.js +1 -1
- package/src/lower/captures.js +267 -64
- 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 +33 -19
- package/src/lower/json-codecs.js +1379 -0
- package/src/lower/ownership.js +47 -8
- package/src/lower/range.js +86 -1
- package/src/lower/reachability.js +150 -0
- package/src/lower/walk.js +12 -0
- package/src/parser/declarations.js +83 -15
- package/src/parser/expressions.js +110 -18
- package/src/parser/index.js +2 -1
- package/src/parser/statements.js +38 -22
- package/src/{templ-parser.js → parser/templ.js} +179 -17
- package/src/parser/types.js +70 -9
- package/src/resolver.js +21 -1
- package/src/typechecker/assignability.js +291 -19
- package/src/typechecker/const-fold.js +399 -0
- package/src/typechecker/constants.js +282 -0
- package/src/typechecker/expressions.js +1220 -183
- package/src/typechecker/index.js +659 -92
- package/src/typechecker/init-order.js +426 -0
- package/src/typechecker/operand-text.js +40 -0
- package/src/typechecker/resolve.js +613 -62
- package/src/typechecker/statements.js +212 -20
- package/src/typechecker/stdlib/excluded.js +902 -0
- package/src/typechecker/stdlib/index.js +38 -0
- package/src/typechecker/stdlib/simd.js +65 -15
- package/src/typechecker/termination.js +11 -2
- package/src/typechecker/types.js +268 -514
- package/stdlib/bytes/buffer.go +479 -0
- package/stdlib/bytes/bytes.go +1010 -0
- package/stdlib/bytes/reader.go +161 -0
- package/stdlib/cmp/cmp.go +65 -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/errors.go +31 -0
- package/stdlib/encoding/json/indent.go +71 -0
- package/stdlib/encoding/json/json.go +62 -0
- package/stdlib/encoding/json/number.go +12 -0
- package/stdlib/encoding/json/stream.go +173 -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/hash/hash.go +53 -0
- package/stdlib/html/html.go +251 -0
- package/stdlib/internal/fmtrt/fmtrt.go +163 -0
- package/stdlib/internal/jsonrt/dec.go +871 -0
- package/stdlib/internal/jsonrt/enc.go +516 -0
- package/stdlib/internal/jsonrt/indent.go +135 -0
- package/stdlib/internal/jsonrt/number.go +27 -0
- package/stdlib/internal/jsonrt/reader.go +73 -0
- package/stdlib/internal/jsonrt/scanner.go +658 -0
- package/stdlib/io/io.go +502 -0
- package/stdlib/iter/iter.go +18 -0
- package/stdlib/maps/maps.go +131 -0
- package/stdlib/math/bits/bits.go +507 -0
- package/stdlib/math/extra.go +272 -0
- 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 +500 -0
- 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 +526 -0
- 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 +250 -0
- package/stdlib/unicode/utf8/utf8.go +464 -0
- 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 -46
- package/src/backend/js/stdlib/builder.js +0 -63
- package/src/backend/js/stdlib/bytes.js +0 -122
- package/src/backend/js/stdlib/errors.js +0 -44
- package/src/backend/js/stdlib/fmt.js +0 -178
- 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 -972
- package/src/backend/js/stdlib/maps.js +0 -45
- package/src/backend/js/stdlib/math.js +0 -69
- 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 -24
- package/src/backend/js/stdlib/slices.js +0 -77
- package/src/backend/js/stdlib/sort.js +0 -30
- package/src/backend/js/stdlib/strconv.js +0 -74
- package/src/backend/js/stdlib/strings.js +0 -222
- package/src/backend/js/stdlib/sync.js +0 -13
- package/src/backend/js/stdlib/testing.js +0 -34
- package/src/backend/js/stdlib/time.js +0 -128
- package/src/backend/js/stdlib/unicode.js +0 -25
- package/src/backend/js/stdlib/utf8.js +0 -26
- package/src/backend/wasm/const-fold.js +0 -205
- package/src/backend/wasm/emit-stdlib.js +0 -13
- package/src/backend/wasm/stdlib/bytes.js +0 -5039
- package/src/backend/wasm/stdlib/core.js +0 -8903
- package/src/backend/wasm/stdlib/io.js +0 -2161
- package/src/backend/wasm/stdlib/packages.js +0 -2400
- package/src/backend/wasm/stdlib/pure.js +0 -720
- package/src/backend/wasm/stdlib/time.js +0 -1200
- package/src/typechecker/stdlib/core.js +0 -801
- package/src/typechecker/stdlib/extended.js +0 -914
- package/src/typechecker/stdlib.js +0 -22
- /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
- /package/src/{colors.js → cli/colors.js} +0 -0
- /package/src/{dev-server.js → cli/dev-server.js} +0 -0
- /package/src/{minifier.js → cli/minifier.js} +0 -0
- /package/src/{project-config.js → cli/project-config.js} +0 -0
- /package/src/{pwa.js → cli/pwa.js} +0 -0
|
@@ -1,16 +1,147 @@
|
|
|
1
1
|
import {
|
|
2
2
|
bodyRedeclares,
|
|
3
|
+
forEachChild,
|
|
4
|
+
hasCallOrReceive,
|
|
3
5
|
isIntRangeType,
|
|
4
6
|
isMapRangeType,
|
|
5
7
|
isRangeFor,
|
|
6
8
|
isSimpleIntRange,
|
|
7
9
|
isStringRangeType,
|
|
8
|
-
|
|
10
|
+
loopVarsCapturedInLoop,
|
|
11
|
+
someChild,
|
|
9
12
|
} from "../../lower/index.js";
|
|
10
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
is64BitInt,
|
|
15
|
+
isComplex,
|
|
16
|
+
isTypedArraySlice,
|
|
17
|
+
} from "../../typechecker/types.js";
|
|
11
18
|
|
|
12
19
|
/** @typedef {import('./index.js').CodeGen} CodeGen */
|
|
13
20
|
|
|
21
|
+
// The same variable or field chain (`x`, `n.Children`) on both sides.
|
|
22
|
+
const sameChain = (a, b) =>
|
|
23
|
+
a.kind === b.kind &&
|
|
24
|
+
(a.kind === "Ident"
|
|
25
|
+
? a.name === b.name && a.name !== "_"
|
|
26
|
+
: a.kind === "SelectorExpr" &&
|
|
27
|
+
a.field === b.field &&
|
|
28
|
+
sameChain(a.expr, b.expr));
|
|
29
|
+
|
|
30
|
+
// `x = x[lo:hi]` (no max) with the same chain on both sides.
|
|
31
|
+
const isSelfResliceShape = (stmt) =>
|
|
32
|
+
stmt.op === "=" &&
|
|
33
|
+
stmt.lhs.length === 1 &&
|
|
34
|
+
stmt.rhs.length === 1 &&
|
|
35
|
+
stmt.rhs[0].kind === "SliceExpr" &&
|
|
36
|
+
!stmt.rhs[0].max &&
|
|
37
|
+
sameChain(stmt.lhs[0], stmt.rhs[0].expr);
|
|
38
|
+
|
|
39
|
+
// Pure expressions (the slice chain of a `__rcs` sync) that read the same
|
|
40
|
+
// value after the write as before it: literals, variables and fields not
|
|
41
|
+
// assigned by the statement, `len` and `cap`, and arithmetic over those.
|
|
42
|
+
const isStableIndex = (e, assigned) => {
|
|
43
|
+
switch (e?.kind) {
|
|
44
|
+
case "BasicLit":
|
|
45
|
+
return true;
|
|
46
|
+
case "Ident":
|
|
47
|
+
return !assigned.has(e.name);
|
|
48
|
+
case "SelectorExpr":
|
|
49
|
+
return !e._isMethodValue && isStableIndex(e.expr, assigned);
|
|
50
|
+
case "ParenExpr":
|
|
51
|
+
return isStableIndex(e.expr, assigned);
|
|
52
|
+
case "UnaryExpr":
|
|
53
|
+
return e.op !== "<-" && isStableIndex(e.operand, assigned);
|
|
54
|
+
case "BinaryExpr":
|
|
55
|
+
return (
|
|
56
|
+
isStableIndex(e.left, assigned) && isStableIndex(e.right, assigned)
|
|
57
|
+
);
|
|
58
|
+
case "CallExpr":
|
|
59
|
+
return (
|
|
60
|
+
e.func?.kind === "Ident" &&
|
|
61
|
+
(e.func.name === "len" || e.func.name === "cap") &&
|
|
62
|
+
e.args.every((a) => isStableIndex(a, assigned))
|
|
63
|
+
);
|
|
64
|
+
default:
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const isLenCap = (e) =>
|
|
70
|
+
e.kind === "CallExpr" &&
|
|
71
|
+
e.func?.kind === "Ident" &&
|
|
72
|
+
(e.func.name === "len" || e.func.name === "cap");
|
|
73
|
+
|
|
74
|
+
// A call (other than `len`/`cap`), receive or await in `n` (not inside a
|
|
75
|
+
// function literal): it may change variables and fields.
|
|
76
|
+
const hasCall = (n) => {
|
|
77
|
+
if (Array.isArray(n)) return n.some(hasCall);
|
|
78
|
+
if (!n || typeof n !== "object" || typeof n.kind !== "string") return false;
|
|
79
|
+
if (n.kind === "FuncLit") return false;
|
|
80
|
+
if (n.kind === "AwaitExpr" || (n.kind === "CallExpr" && !isLenCap(n)))
|
|
81
|
+
return true;
|
|
82
|
+
if (n.kind === "UnaryExpr" && n.op === "<-") return true;
|
|
83
|
+
return someChild(n, hasCall);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// A call in a left-hand index or on the right-hand side of the statement.
|
|
87
|
+
const stmtHasCall = (lhs, stmt) =>
|
|
88
|
+
lhs.some((e) => hasCall(e.index)) ||
|
|
89
|
+
(stmt.kind === "AssignStmt" && hasCall(stmt.rhs));
|
|
90
|
+
|
|
91
|
+
// A multi-assign with a field or `*p` left-hand side: it may reassign the
|
|
92
|
+
// slice chain of an index write, whose sync must then use the old slice.
|
|
93
|
+
const reassignsChains = (lhs, stmt) =>
|
|
94
|
+
stmt.kind === "AssignStmt" &&
|
|
95
|
+
lhs.length > 1 &&
|
|
96
|
+
lhs.some(
|
|
97
|
+
(e) =>
|
|
98
|
+
e.kind === "SelectorExpr" || (e.kind === "UnaryExpr" && e.op === "*"),
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// Index of a `__rcs` sync that can be evaluated again after the write:
|
|
102
|
+
// constants, and, when `vars` (a single-LHS statement without calls), unboxed
|
|
103
|
+
// variables, with `len`/`cap` and arithmetic over those. Any other index
|
|
104
|
+
// is bound to a temporary first, so the write and the sync use one value.
|
|
105
|
+
const isReusableIndex = (e, vars, boxed) => {
|
|
106
|
+
if (e?._const || e?.kind === "BasicLit") return true;
|
|
107
|
+
switch (e?.kind) {
|
|
108
|
+
case "Ident":
|
|
109
|
+
return vars && !boxed.has(e.name);
|
|
110
|
+
case "ParenExpr":
|
|
111
|
+
return isReusableIndex(e.expr, vars, boxed);
|
|
112
|
+
case "UnaryExpr":
|
|
113
|
+
return (
|
|
114
|
+
(e.op === "-" || e.op === "+" || e.op === "^") &&
|
|
115
|
+
isReusableIndex(e.operand, vars, boxed)
|
|
116
|
+
);
|
|
117
|
+
case "BinaryExpr":
|
|
118
|
+
return (
|
|
119
|
+
isReusableIndex(e.left, vars, boxed) &&
|
|
120
|
+
isReusableIndex(e.right, vars, boxed)
|
|
121
|
+
);
|
|
122
|
+
case "CallExpr":
|
|
123
|
+
return (
|
|
124
|
+
isLenCap(e) && e.args.every((a) => isReusableIndex(a, vars, boxed))
|
|
125
|
+
);
|
|
126
|
+
default:
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// Adds the name of every identifier under `n` to `into`.
|
|
132
|
+
const collectIdentNames = (n, into) => {
|
|
133
|
+
if (Array.isArray(n)) {
|
|
134
|
+
for (const c of n) collectIdentNames(c, into);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (!n || typeof n !== "object") return;
|
|
138
|
+
if (n.kind === "Ident") into.add(n.name);
|
|
139
|
+
someChild(n, (c) => {
|
|
140
|
+
collectIdentNames(c, into);
|
|
141
|
+
return false;
|
|
142
|
+
});
|
|
143
|
+
};
|
|
144
|
+
|
|
14
145
|
// Method-name dispatch for genStmt — string values add no static call edges.
|
|
15
146
|
const STMT_GEN_DELEGATE = {
|
|
16
147
|
VarDecl: "genVarDecl",
|
|
@@ -22,26 +153,141 @@ const STMT_GEN_DELEGATE = {
|
|
|
22
153
|
SwitchStmt: "genSwitch",
|
|
23
154
|
TypeSwitchStmt: "genTypeSwitch",
|
|
24
155
|
BranchStmt: "_genBranchStmt",
|
|
156
|
+
TemplMount: "_genTemplMountStmt",
|
|
25
157
|
};
|
|
26
158
|
|
|
27
159
|
/** @type {ThisType<CodeGen>} */
|
|
28
160
|
export const statementGenMethods = {
|
|
29
161
|
genBlock(block) {
|
|
30
|
-
|
|
162
|
+
let nested = 0;
|
|
163
|
+
const earlier = new Set();
|
|
164
|
+
for (const stmt of block.stmts) {
|
|
165
|
+
if (this._definesShadowedRead(stmt)) {
|
|
166
|
+
this._genShadowingDefine(stmt);
|
|
167
|
+
nested++;
|
|
168
|
+
} else if (this._definesAfterOuterUse(stmt, earlier)) {
|
|
169
|
+
this.line("{");
|
|
170
|
+
this.indent++;
|
|
171
|
+
nested++;
|
|
172
|
+
this.genStmt(stmt);
|
|
173
|
+
} else {
|
|
174
|
+
this.genStmt(stmt);
|
|
175
|
+
}
|
|
176
|
+
collectIdentNames(stmt, earlier);
|
|
177
|
+
}
|
|
178
|
+
for (; nested > 0; nested--) {
|
|
179
|
+
this.indent--;
|
|
180
|
+
this.line("}");
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
// `err = nil; _, err := g()` where `err` is an outer variable used earlier
|
|
185
|
+
// in this block: JS `let` would put those uses in its temporal dead zone,
|
|
186
|
+
// so the rest of the block moves into a nested block.
|
|
187
|
+
_definesAfterOuterUse(stmt, earlier) {
|
|
188
|
+
if (stmt.kind !== "DefineStmt" || earlier.size === 0) return false;
|
|
189
|
+
return stmt.lhs.some(
|
|
190
|
+
(l) => !l._redecl && l.name !== "_" && earlier.has(l.name),
|
|
191
|
+
);
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
// `x := f(x)` declaring a new `x` that its own right-hand side reads (the
|
|
195
|
+
// outer `x`): JS `let` would put the read in its temporal dead zone.
|
|
196
|
+
_definesShadowedRead(stmt) {
|
|
197
|
+
if (stmt.kind !== "DefineStmt") return false;
|
|
198
|
+
const names = new Set(
|
|
199
|
+
stmt.lhs.filter((l) => !l._redecl && l.name !== "_").map((l) => l.name),
|
|
200
|
+
);
|
|
201
|
+
if (names.size === 0) return false;
|
|
202
|
+
const reads = (n) => {
|
|
203
|
+
if (Array.isArray(n)) return n.some(reads);
|
|
204
|
+
if (!n || typeof n !== "object") return false;
|
|
205
|
+
if (n.kind === "Ident" && names.has(n.name)) return true;
|
|
206
|
+
return someChild(n, reads);
|
|
207
|
+
};
|
|
208
|
+
return stmt.rhs.some(reads);
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
// Emits an `if`/`switch` init statement, then `body`; an init that reads the
|
|
212
|
+
// name it declares goes through `_genShadowingDefine`.
|
|
213
|
+
_genScopedInit(init, body) {
|
|
214
|
+
const shadowing = this._definesShadowedRead(init);
|
|
215
|
+
if (shadowing) this._genShadowingDefine(init);
|
|
216
|
+
else this.genStmt(init);
|
|
217
|
+
body();
|
|
218
|
+
if (shadowing) {
|
|
219
|
+
this.indent--;
|
|
220
|
+
this.line("}");
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
// Evaluates the right-hand side in the enclosing scope, then opens a nested
|
|
225
|
+
// block (closed at the end of the enclosing block) that declares the names.
|
|
226
|
+
_genShadowingDefine(stmt) {
|
|
227
|
+
if (this._isCommaOkMapExpr(stmt, stmt.rhs[0])) {
|
|
228
|
+
const node = stmt.rhs[0];
|
|
229
|
+
this._tmpCounter = (this._tmpCounter ?? 0) + 1;
|
|
230
|
+
const m = `__sm${this._tmpCounter}`;
|
|
231
|
+
const k = `__sk${this._tmpCounter}`;
|
|
232
|
+
this.line(
|
|
233
|
+
`const ${m} = ${this.genExpr(node.expr)}, ${k} = ${this.genExpr(node.index)};`,
|
|
234
|
+
);
|
|
235
|
+
this.line("{");
|
|
236
|
+
this.indent++;
|
|
237
|
+
this._genCommaOkMapIndex(stmt, node, true, { map: m, key: k });
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const rhs = this._defineRhs(stmt);
|
|
241
|
+
this._tmpCounter = (this._tmpCounter ?? 0) + 1;
|
|
242
|
+
const tmp = `__t${this._tmpCounter}`;
|
|
243
|
+
const all = rhs.length === 1 ? rhs[0] : `[${rhs.join(", ")}]`;
|
|
244
|
+
this.line(`const ${tmp} = ${all};`);
|
|
245
|
+
this.line("{");
|
|
246
|
+
this.indent++;
|
|
247
|
+
this._genDefineStmt(
|
|
248
|
+
stmt,
|
|
249
|
+
rhs.length === 1 ? [tmp] : rhs.map((_, i) => `${tmp}[${i}]`),
|
|
250
|
+
);
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
// `m[k]` of a non-strict comma-ok: a missing key reads as the scalar zero
|
|
254
|
+
// value, not `undefined`.
|
|
255
|
+
_nonStrictMapRead(mapExpr, keyExpr, valType, copyVal) {
|
|
256
|
+
const zero = valType ? this.zeroValueForType(valType) : "";
|
|
257
|
+
const read = `${mapExpr}[${keyExpr}]`;
|
|
258
|
+
if (/^(0n?|""|false)$/.test(zero)) return `(${read} ?? ${zero})`;
|
|
259
|
+
return copyVal(
|
|
260
|
+
this._aggregateBase(valType) ? `(${read} ?? ${zero})` : read,
|
|
261
|
+
);
|
|
31
262
|
},
|
|
32
263
|
|
|
33
264
|
// Shared helper for comma-ok map index in both DefineStmt and AssignStmt.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
265
|
+
// `pre` carries the map and key already evaluated (a shadowing declaration
|
|
266
|
+
// reads them before its names exist).
|
|
267
|
+
_genCommaOkMapIndex(stmt, rhsNode, isDefine, pre = null) {
|
|
268
|
+
const mapExpr = pre?.map ?? this.genExpr(rhsNode.expr);
|
|
269
|
+
const keyExpr = pre?.key ?? this.genExpr(rhsNode.index);
|
|
37
270
|
const [vName, okName] = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
|
|
38
|
-
|
|
271
|
+
// A name `:=` reuses is assigned, not declared again; an address-taken
|
|
272
|
+
// variable lives in a `{ value }` box.
|
|
273
|
+
const bind = (l, name, js) => {
|
|
274
|
+
const boxed = this._boxedVars.has(name);
|
|
275
|
+
if (isDefine && !l._redecl)
|
|
276
|
+
return `let ${name} = ${boxed ? `{ value: ${js} }` : js};`;
|
|
277
|
+
return `${name}${boxed ? ".value" : ""} = ${js};`;
|
|
278
|
+
};
|
|
279
|
+
const [vL, okL] = stmt.lhs;
|
|
280
|
+
// A struct or array map value is a copy, not the stored value.
|
|
281
|
+
const valType = rhsNode._mapValueType;
|
|
282
|
+
const copyVal = (js) =>
|
|
283
|
+
this._aggregateBase(valType) ? this._cloneJs(valType, js) : js;
|
|
39
284
|
if (isDefine && !this.strict) {
|
|
40
285
|
if (vName !== "_") {
|
|
41
|
-
this.
|
|
286
|
+
const val = this._nonStrictMapRead(mapExpr, keyExpr, valType, copyVal);
|
|
287
|
+
this.line(bind(vL, vName, val));
|
|
42
288
|
}
|
|
43
289
|
if (okName !== "_") {
|
|
44
|
-
this.line(
|
|
290
|
+
this.line(bind(okL, okName, `(${keyExpr}) in ${mapExpr}`));
|
|
45
291
|
}
|
|
46
292
|
return;
|
|
47
293
|
}
|
|
@@ -54,11 +300,15 @@ export const statementGenMethods = {
|
|
|
54
300
|
this.line(`const ${mTmp} = ${mapExpr}, ${kTmp} = ${keyExpr};`);
|
|
55
301
|
if (vName !== "_") {
|
|
56
302
|
this.line(
|
|
57
|
-
|
|
303
|
+
bind(
|
|
304
|
+
vL,
|
|
305
|
+
vName,
|
|
306
|
+
`(${mTmp} && (${kTmp} in ${mTmp})) ? ${copyVal(`${mTmp}[${kTmp}]`)} : ${zero}`,
|
|
307
|
+
),
|
|
58
308
|
);
|
|
59
309
|
}
|
|
60
310
|
if (okName !== "_") {
|
|
61
|
-
this.line(
|
|
311
|
+
this.line(bind(okL, okName, `Boolean(${mTmp} && (${kTmp} in ${mTmp}))`));
|
|
62
312
|
}
|
|
63
313
|
},
|
|
64
314
|
|
|
@@ -66,11 +316,24 @@ export const statementGenMethods = {
|
|
|
66
316
|
// Record source mapping for the first line this statement produces
|
|
67
317
|
const srcLine = stmt._line ?? null;
|
|
68
318
|
const _line0 = this.out.length;
|
|
319
|
+
const restore = [];
|
|
320
|
+
const syncs =
|
|
321
|
+
stmt.kind === "AssignStmt"
|
|
322
|
+
? this._resliceSyncs(stmt.lhs, stmt, false, restore)
|
|
323
|
+
: stmt.kind === "IncDecStmt"
|
|
324
|
+
? this._resliceSyncs([stmt.expr], stmt, false, restore)
|
|
325
|
+
: null;
|
|
69
326
|
switch (stmt.kind) {
|
|
70
327
|
case "TypeDecl":
|
|
71
|
-
|
|
328
|
+
{
|
|
329
|
+
const start = this.out.length;
|
|
330
|
+
this.genTypeDeclWithMethods(stmt, []);
|
|
331
|
+
this._hoistLocalClass(stmt, start);
|
|
332
|
+
}
|
|
72
333
|
break;
|
|
73
334
|
case "IncDecStmt": {
|
|
335
|
+
this._markIndexLvalues([stmt.expr]);
|
|
336
|
+
this._markDerefOpTarget(stmt.expr);
|
|
74
337
|
if (stmt.expr.kind === "IndexExpr" && stmt.expr._mapValueType) {
|
|
75
338
|
const zero = this.zeroValueForType(stmt.expr._mapValueType);
|
|
76
339
|
const wrapField = this._namedWrapperField(
|
|
@@ -82,28 +345,47 @@ export const statementGenMethods = {
|
|
|
82
345
|
: this.genExpr(stmt.expr.expr);
|
|
83
346
|
const idx = this.genExpr(stmt.expr.index);
|
|
84
347
|
const op = stmt.op === "++" ? "+" : "-";
|
|
348
|
+
const is64Val = is64BitInt(stmt.expr._mapValueType);
|
|
349
|
+
const delta = is64Val ? "1n" : "1";
|
|
85
350
|
if (
|
|
86
351
|
this._hasCallExpr(stmt.expr.expr) ||
|
|
87
352
|
this._hasCallExpr(stmt.expr.index)
|
|
88
353
|
) {
|
|
354
|
+
const rhs = is64Val
|
|
355
|
+
? this._gen64BitBinary(
|
|
356
|
+
op,
|
|
357
|
+
`(__m[__k] ?? ${zero})`,
|
|
358
|
+
delta,
|
|
359
|
+
stmt.expr._mapValueType,
|
|
360
|
+
)
|
|
361
|
+
: `(__m[__k] ?? ${zero}) ${op} 1`;
|
|
89
362
|
this.line(
|
|
90
|
-
`((__m, __k) => { __m[__k] =
|
|
363
|
+
`((__m, __k) => { ${this._genMapWriteBase("__m")}[__k] = ${rhs}; })(${base}, ${idx});`,
|
|
91
364
|
srcLine,
|
|
92
365
|
);
|
|
93
366
|
} else {
|
|
367
|
+
const rhs = is64Val
|
|
368
|
+
? this._gen64BitBinary(
|
|
369
|
+
op,
|
|
370
|
+
`(${base}[${idx}] ?? ${zero})`,
|
|
371
|
+
delta,
|
|
372
|
+
stmt.expr._mapValueType,
|
|
373
|
+
)
|
|
374
|
+
: `(${base}[${idx}] ?? ${zero}) ${op} 1`;
|
|
94
375
|
this.line(
|
|
95
|
-
`${base}[${idx}] =
|
|
376
|
+
`${this._genMapWriteBase(base)}[${idx}] = ${rhs};`,
|
|
96
377
|
srcLine,
|
|
97
378
|
);
|
|
98
379
|
}
|
|
99
380
|
break;
|
|
100
381
|
}
|
|
382
|
+
const is64 = is64BitInt(stmt.expr._type);
|
|
101
383
|
if (
|
|
102
|
-
this.strict &&
|
|
384
|
+
(this.strict || is64 || this._isFloat32(stmt.expr._type)) &&
|
|
103
385
|
this._genStrictCompound(
|
|
104
386
|
stmt.expr,
|
|
105
387
|
stmt.op === "++" ? "+" : "-",
|
|
106
|
-
"1",
|
|
388
|
+
is64 ? "1n" : "1",
|
|
107
389
|
srcLine,
|
|
108
390
|
)
|
|
109
391
|
)
|
|
@@ -118,8 +400,7 @@ export const statementGenMethods = {
|
|
|
118
400
|
if (stmt.init) {
|
|
119
401
|
this.line("{");
|
|
120
402
|
this.indented(() => {
|
|
121
|
-
this.
|
|
122
|
-
this._genIf(stmt);
|
|
403
|
+
this._genScopedInit(stmt.init, () => this._genIf(stmt));
|
|
123
404
|
});
|
|
124
405
|
this.line("}");
|
|
125
406
|
} else {
|
|
@@ -127,12 +408,26 @@ export const statementGenMethods = {
|
|
|
127
408
|
}
|
|
128
409
|
break;
|
|
129
410
|
case "DeferStmt":
|
|
130
|
-
|
|
411
|
+
// A deferred native call runs the Go body.
|
|
412
|
+
this._withoutNatives(() =>
|
|
413
|
+
this.line(`__defers.push(() => { ${this.genExpr(stmt.call)}; });`),
|
|
414
|
+
);
|
|
131
415
|
break;
|
|
132
|
-
case "LabeledStmt":
|
|
133
|
-
|
|
134
|
-
|
|
416
|
+
case "LabeledStmt": {
|
|
417
|
+
// Stacked labels (`a: b: for ...`) all stay attached to the loop.
|
|
418
|
+
const labels = [stmt.label];
|
|
419
|
+
let body = stmt.body;
|
|
420
|
+
while (body?.kind === "LabeledStmt") {
|
|
421
|
+
labels.push(body.label);
|
|
422
|
+
body = body.body;
|
|
423
|
+
}
|
|
424
|
+
if (body?.kind === "ForStmt") body.label = labels[labels.length - 1];
|
|
425
|
+
const hoisted = this._hoistForInit(body);
|
|
426
|
+
for (const label of labels) this.line(`${label}:`);
|
|
427
|
+
this.genStmt(body);
|
|
428
|
+
if (hoisted) this._closeHoistedForInit();
|
|
135
429
|
break;
|
|
430
|
+
}
|
|
136
431
|
case "Block":
|
|
137
432
|
this.line("{");
|
|
138
433
|
this.indented(() => this.genBlock(stmt));
|
|
@@ -147,6 +442,9 @@ export const statementGenMethods = {
|
|
|
147
442
|
throw new Error(`CodeGen: unhandled statement kind '${stmt.kind}'`);
|
|
148
443
|
}
|
|
149
444
|
}
|
|
445
|
+
this._emitResliceSyncs(syncs);
|
|
446
|
+
// The AST is shared with the WASM backend: undo the temporaries.
|
|
447
|
+
for (const [node, key, v] of restore) node[key] = v;
|
|
150
448
|
// Record source mapping: first output line this statement produced
|
|
151
449
|
if (srcLine != null && this.out.length > _line0) {
|
|
152
450
|
this._srcMappings.push({
|
|
@@ -158,11 +456,14 @@ export const statementGenMethods = {
|
|
|
158
456
|
},
|
|
159
457
|
|
|
160
458
|
_isCommaOkMapExpr(stmt, rhsNode) {
|
|
459
|
+
if (stmt._commaOkMap) return true;
|
|
460
|
+
const t = rhsNode?.expr?._type;
|
|
461
|
+
const underlying = t?.kind === "named" ? t.underlying : t;
|
|
161
462
|
return (
|
|
162
463
|
stmt.rhs.length === 1 &&
|
|
163
464
|
stmt.lhs.length === 2 &&
|
|
164
465
|
rhsNode?.kind === "IndexExpr" &&
|
|
165
|
-
|
|
466
|
+
underlying?.kind === "map"
|
|
166
467
|
);
|
|
167
468
|
},
|
|
168
469
|
|
|
@@ -178,22 +479,14 @@ export const statementGenMethods = {
|
|
|
178
479
|
},
|
|
179
480
|
|
|
180
481
|
_genDefineMixedRedecl(lhs, rhs, redecls) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
} else {
|
|
190
|
-
for (let i = 0; i < lhs.length; i++) {
|
|
191
|
-
if (lhs[i] === "_") {
|
|
192
|
-
this.line(`${rhs[i]};`);
|
|
193
|
-
continue;
|
|
194
|
-
}
|
|
195
|
-
this._genDefineSingleVar([lhs[i]], [rhs[i]], [redecls[i]]);
|
|
196
|
-
}
|
|
482
|
+
// `x, y := 2, x` reads the old `x`: evaluate every value first.
|
|
483
|
+
this._tmpCounter = (this._tmpCounter ?? 0) + 1;
|
|
484
|
+
const tmp = `__t${this._tmpCounter}`;
|
|
485
|
+
const all = rhs.length === 1 ? rhs[0] : `[${rhs.join(", ")}]`;
|
|
486
|
+
this.line(`const ${tmp} = ${all};`);
|
|
487
|
+
for (let i = 0; i < lhs.length; i++) {
|
|
488
|
+
if (lhs[i] === "_") continue;
|
|
489
|
+
this._genDefineSingleVar([lhs[i]], [`${tmp}[${i}]`], [redecls[i]]);
|
|
197
490
|
}
|
|
198
491
|
},
|
|
199
492
|
|
|
@@ -206,19 +499,23 @@ export const statementGenMethods = {
|
|
|
206
499
|
}
|
|
207
500
|
},
|
|
208
501
|
|
|
209
|
-
|
|
210
|
-
const rhsNode = stmt.rhs[0];
|
|
211
|
-
if (this._isCommaOkMapExpr(stmt, rhsNode)) {
|
|
212
|
-
this._genCommaOkMapIndex(stmt, rhsNode, true);
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
502
|
+
_defineRhs(stmt) {
|
|
215
503
|
const paired = stmt.rhs.length === stmt.lhs.length;
|
|
216
|
-
|
|
504
|
+
return stmt.rhs.map((e, i) => {
|
|
217
505
|
const l = paired ? stmt.lhs[i] : null;
|
|
218
506
|
if (!l || l.name === "_") return this.genExpr(e);
|
|
219
507
|
if (l._redecl) return this.genValueExpr(e);
|
|
220
508
|
return this._genDeclValue(l.name, e);
|
|
221
509
|
});
|
|
510
|
+
},
|
|
511
|
+
|
|
512
|
+
_genDefineStmt(stmt, preEvaluated = null) {
|
|
513
|
+
const rhsNode = stmt.rhs[0];
|
|
514
|
+
if (this._isCommaOkMapExpr(stmt, rhsNode)) {
|
|
515
|
+
this._genCommaOkMapIndex(stmt, rhsNode, true);
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
const rhs = preEvaluated ?? this._defineRhs(stmt);
|
|
222
519
|
const lhs = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
|
|
223
520
|
const redecls = stmt.lhs.map((e) => !!e._redecl);
|
|
224
521
|
const anyRedecl = redecls.some((r) => r);
|
|
@@ -249,6 +546,68 @@ export const statementGenMethods = {
|
|
|
249
546
|
);
|
|
250
547
|
},
|
|
251
548
|
|
|
549
|
+
// `*p = v` where `p` points at a type parameter: erased, so the pointer is
|
|
550
|
+
// either a struct (copy into it) or a get/set wrapper.
|
|
551
|
+
_isTypeParamDerefAssign(stmt) {
|
|
552
|
+
if (stmt.lhs.length !== 1 || stmt.rhs.length !== 1 || stmt.op !== "=")
|
|
553
|
+
return false;
|
|
554
|
+
const l = stmt.lhs[0];
|
|
555
|
+
return (
|
|
556
|
+
l.kind === "UnaryExpr" &&
|
|
557
|
+
l.op === "*" &&
|
|
558
|
+
l.operand?._type?.base?.kind === "typeParam"
|
|
559
|
+
);
|
|
560
|
+
},
|
|
561
|
+
|
|
562
|
+
_isTypeParamDerefTarget(l) {
|
|
563
|
+
return (
|
|
564
|
+
l.kind === "UnaryExpr" &&
|
|
565
|
+
l.op === "*" &&
|
|
566
|
+
l.operand?._type?.base?.kind === "typeParam"
|
|
567
|
+
);
|
|
568
|
+
},
|
|
569
|
+
|
|
570
|
+
// `*a, *b = *b, *a` with type-parameter pointers: `*p` is not a settable
|
|
571
|
+
// JS destructuring target, so evaluate the right-hand side into temporaries
|
|
572
|
+
// and assign each target on its own.
|
|
573
|
+
_hasTypeParamDerefTarget(stmt) {
|
|
574
|
+
return (
|
|
575
|
+
stmt.op === "=" &&
|
|
576
|
+
stmt.lhs.length > 1 &&
|
|
577
|
+
stmt.lhs.some((l) => this._isTypeParamDerefTarget(l))
|
|
578
|
+
);
|
|
579
|
+
},
|
|
580
|
+
|
|
581
|
+
_genTypeParamDerefMultiAssign(stmt) {
|
|
582
|
+
const n = stmt.lhs.length;
|
|
583
|
+
const names = stmt.lhs.map((_, i) => `__t${i}`);
|
|
584
|
+
this.line("{");
|
|
585
|
+
this.indented(() => {
|
|
586
|
+
if (stmt.rhs.length === n) {
|
|
587
|
+
const vals = stmt.rhs.map((e, i) =>
|
|
588
|
+
stmt.lhs[i].name !== "_" ? this.genValueExpr(e) : this.genExpr(e),
|
|
589
|
+
);
|
|
590
|
+
this.line(`const [${names.join(", ")}] = [${vals.join(", ")}];`);
|
|
591
|
+
} else {
|
|
592
|
+
this.line(
|
|
593
|
+
`const [${names.join(", ")}] = ${this.genExpr(stmt.rhs[0])};`,
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
stmt.lhs.forEach((l, i) => {
|
|
597
|
+
if (l.name === "_") return;
|
|
598
|
+
if (this._isTypeParamDerefTarget(l)) {
|
|
599
|
+
const isStruct = this._typeParamIsStructJs(l.operand._type.base.name);
|
|
600
|
+
this.line(
|
|
601
|
+
`((__p, __v) => { if (${isStruct}) Object.assign(__p, __v); else __p.value = __v; })(${this.genExpr(l.operand)}, ${names[i]});`,
|
|
602
|
+
);
|
|
603
|
+
} else {
|
|
604
|
+
this.line(`${this._genAssignLhsExpr(l)} = ${names[i]};`);
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
});
|
|
608
|
+
this.line("}");
|
|
609
|
+
},
|
|
610
|
+
|
|
252
611
|
_isStructDerefAssign(stmt) {
|
|
253
612
|
if (stmt.lhs.length !== 1 || stmt.rhs.length !== 1 || stmt.op !== "=")
|
|
254
613
|
return false;
|
|
@@ -270,6 +629,13 @@ export const statementGenMethods = {
|
|
|
270
629
|
for (const e of exprs) if (e.kind === "IndexExpr") e._lvalue = true;
|
|
271
630
|
},
|
|
272
631
|
|
|
632
|
+
// Op-assign / inc-dec through a type-parameter pointer needs a settable
|
|
633
|
+
// target: the erased pointer is a get/set wrapper (struct instantiations
|
|
634
|
+
// cannot be op-assigned), so target `.value` directly.
|
|
635
|
+
_markDerefOpTarget(e) {
|
|
636
|
+
if (this._isTypeParamDerefTarget(e)) e._derefOpTarget = true;
|
|
637
|
+
},
|
|
638
|
+
|
|
273
639
|
_genAssignMulti(stmt, lhs, rhs, active, pairs) {
|
|
274
640
|
const rhsStr = rhs.length === 1 ? rhs[0] : `[${rhs.join(", ")}]`;
|
|
275
641
|
if (active.length === pairs.length) {
|
|
@@ -279,7 +645,132 @@ export const statementGenMethods = {
|
|
|
279
645
|
}
|
|
280
646
|
},
|
|
281
647
|
|
|
648
|
+
// `x = x[lo:hi]` with the same variable or field chain on both sides:
|
|
649
|
+
// the old value of `x` is replaced, so `_genSliceExpr` may copy instead
|
|
650
|
+
// of building a slice view (`__reslice`).
|
|
651
|
+
_markSelfReslice(stmt) {
|
|
652
|
+
if (stmt.op !== "=" || stmt.lhs.length !== 1 || stmt.rhs.length !== 1)
|
|
653
|
+
return;
|
|
654
|
+
const r = stmt.rhs[0];
|
|
655
|
+
if (r.kind !== "SliceExpr" || r.max) return;
|
|
656
|
+
// A parameter or named result may alias the caller's slice: keep the
|
|
657
|
+
// view. Field chains (`n.Children = n.Children[:k]`) still copy.
|
|
658
|
+
const lhs = stmt.lhs[0];
|
|
659
|
+
if (lhs.kind === "Ident" && this._fnCtx?.params.has(lhs.name)) return;
|
|
660
|
+
if (sameChain(stmt.lhs[0], r.expr)) r._selfReslice = true;
|
|
661
|
+
},
|
|
662
|
+
|
|
663
|
+
// Names of the variables and fields (`.F`) that some `x = x[lo:hi]` in the
|
|
664
|
+
// program may turn into a `__reslice` copy (`_resliceSyncs`).
|
|
665
|
+
_collectResliceNames(program) {
|
|
666
|
+
const names = new Set();
|
|
667
|
+
const visit = (n) => {
|
|
668
|
+
if (Array.isArray(n)) {
|
|
669
|
+
for (const c of n) visit(c);
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
if (!n || typeof n !== "object" || typeof n.kind !== "string") return;
|
|
673
|
+
if (n.kind === "AssignStmt" && isSelfResliceShape(n)) {
|
|
674
|
+
const l = n.lhs[0];
|
|
675
|
+
names.add(l.kind === "Ident" ? l.name : `.${l.field}`);
|
|
676
|
+
}
|
|
677
|
+
forEachChild(n, visit);
|
|
678
|
+
};
|
|
679
|
+
visit(program.decls);
|
|
680
|
+
this._resliceNames = names;
|
|
681
|
+
},
|
|
682
|
+
|
|
683
|
+
// Index writes in `lhs` whose slice may be a `__reslice` copy: each is
|
|
684
|
+
// followed by `__rcs(x, i)` (`_emitResliceSyncs`), which writes the element
|
|
685
|
+
// through to the copy's old array, so writes through the copy and through
|
|
686
|
+
// other slices of that array keep Go's order. An index that the statement
|
|
687
|
+
// could change (`isReusableIndex`) is first bound to a temporary (not in
|
|
688
|
+
// `inline` for-clause statements, which skip the sync: the write-back on
|
|
689
|
+
// extension still applies). Not covered: writes through other references to the copy.
|
|
690
|
+
// Once one index is bound, every left-hand index with a call is bound too,
|
|
691
|
+
// in source order and each after its operand, and so is the slice operand
|
|
692
|
+
// of a synced write in a statement with a call or one that may reassign
|
|
693
|
+
// the chain through a field or `*p` (`reassignsChains`; inline statements
|
|
694
|
+
// skip that sync) — Go evaluates both before the right-hand side.
|
|
695
|
+
// `restore` collects the replaced nodes for the caller to put back.
|
|
696
|
+
_resliceSyncs(lhs, stmt, inline = false, restore = []) {
|
|
697
|
+
if (!this._resliceNames?.size) return null;
|
|
698
|
+
const targets = this._resliceTargets(lhs, stmt);
|
|
699
|
+
if (targets.size === 0) return null;
|
|
700
|
+
const called = stmtHasCall(lhs, stmt);
|
|
701
|
+
const moved = reassignsChains(lhs, stmt);
|
|
702
|
+
const vars = lhs.length === 1 && !called;
|
|
703
|
+
const reusable = (e) => isReusableIndex(e.index, vars, this._boxedVars);
|
|
704
|
+
const out = [...targets].filter((e) => !inline || (!moved && reusable(e)));
|
|
705
|
+
const operands = called || moved;
|
|
706
|
+
if (!inline && (operands || out.some((e) => !reusable(e))))
|
|
707
|
+
this._bindResliceOperands(lhs, targets, operands, reusable, restore);
|
|
708
|
+
if (out.length === 0) return null;
|
|
709
|
+
this.useHelper("slice");
|
|
710
|
+
return out.map((e) => {
|
|
711
|
+
const wrapField = this._namedWrapperField(e.expr._type, e.expr);
|
|
712
|
+
const base = wrapField
|
|
713
|
+
? `${this.genExpr(e.expr)}.${wrapField}`
|
|
714
|
+
: this.genExpr(e.expr);
|
|
715
|
+
return `__rcs(${base}, ${this.genExpr(e.index)})`;
|
|
716
|
+
});
|
|
717
|
+
},
|
|
718
|
+
|
|
719
|
+
// The `_isResliceSyncTarget` writes in `lhs`.
|
|
720
|
+
_resliceTargets(lhs, stmt) {
|
|
721
|
+
const assigned = new Set();
|
|
722
|
+
if (stmt.kind === "AssignStmt" && stmt.lhs.length > 1)
|
|
723
|
+
for (const e of stmt.lhs) if (e.kind === "Ident") assigned.add(e.name);
|
|
724
|
+
return new Set(lhs.filter((e) => this._isResliceSyncTarget(e, assigned)));
|
|
725
|
+
},
|
|
726
|
+
|
|
727
|
+
// Binds the left-hand operands that `_resliceSyncs` must not evaluate
|
|
728
|
+
// twice to temporaries, in source order: an index after its operand, and
|
|
729
|
+
// the operand of every synced write when `operands`.
|
|
730
|
+
_bindResliceOperands(lhs, targets, operands, reusable, restore) {
|
|
731
|
+
const bind = (e, key) => {
|
|
732
|
+
this._tmpCounter = (this._tmpCounter ?? 0) + 1;
|
|
733
|
+
const tmp = `__rci${this._tmpCounter}`;
|
|
734
|
+
this.line(`const ${tmp} = ${this.genExpr(e[key])};`);
|
|
735
|
+
restore.push([e, key, e[key]]);
|
|
736
|
+
e[key] = { kind: "Ident", name: tmp, _type: e[key]._type };
|
|
737
|
+
};
|
|
738
|
+
for (const e of lhs) {
|
|
739
|
+
if (e.kind !== "IndexExpr") continue;
|
|
740
|
+
const t = targets.has(e);
|
|
741
|
+
const index = (t && !reusable(e)) || hasCall(e.index);
|
|
742
|
+
if ((t && operands) || index) bind(e, "expr");
|
|
743
|
+
if (index) bind(e, "index");
|
|
744
|
+
}
|
|
745
|
+
},
|
|
746
|
+
|
|
747
|
+
// `x[i]` on a plain slice held by a chain in `_resliceNames` whose root
|
|
748
|
+
// variable the statement does not reassign.
|
|
749
|
+
_isResliceSyncTarget(e, assigned) {
|
|
750
|
+
if (e.kind !== "IndexExpr" || e._mapValueType) return false;
|
|
751
|
+
const c = e.expr;
|
|
752
|
+
if (c.kind !== "Ident" && c.kind !== "SelectorExpr") return false;
|
|
753
|
+
if (!this._resliceNames.has(c.kind === "Ident" ? c.name : `.${c.field}`))
|
|
754
|
+
return false;
|
|
755
|
+
const u = c._type?.kind === "named" ? c._type.underlying : c._type;
|
|
756
|
+
if (u?.kind !== "slice" || isTypedArraySlice(c._type)) return false;
|
|
757
|
+
let root = c;
|
|
758
|
+
while (root.kind === "SelectorExpr") root = root.expr;
|
|
759
|
+
return (
|
|
760
|
+
root.kind === "Ident" &&
|
|
761
|
+
!assigned.has(root.name) &&
|
|
762
|
+
isStableIndex(c, assigned)
|
|
763
|
+
);
|
|
764
|
+
},
|
|
765
|
+
|
|
766
|
+
_emitResliceSyncs(syncs) {
|
|
767
|
+
if (syncs) for (const s of syncs) this.line(`${s};`);
|
|
768
|
+
},
|
|
769
|
+
|
|
282
770
|
_genAssignStmt(stmt) {
|
|
771
|
+
this._markIndexLvalues(stmt.lhs);
|
|
772
|
+
if (stmt.op !== "=") for (const l of stmt.lhs) this._markDerefOpTarget(l);
|
|
773
|
+
this._markSelfReslice(stmt);
|
|
283
774
|
if (this._isComplexCompoundAssign(stmt)) {
|
|
284
775
|
this._genComplexCompoundAssign(stmt);
|
|
285
776
|
return;
|
|
@@ -292,18 +783,42 @@ export const statementGenMethods = {
|
|
|
292
783
|
this._genCommaOkTypeAssert(stmt);
|
|
293
784
|
return;
|
|
294
785
|
}
|
|
786
|
+
if (this._isTypeParamDerefAssign(stmt)) {
|
|
787
|
+
const ptr = this.genExpr(stmt.lhs[0].operand);
|
|
788
|
+
const val = this.genValueExpr(stmt.rhs[0]);
|
|
789
|
+
const isStruct = this._typeParamIsStructJs(
|
|
790
|
+
stmt.lhs[0].operand._type.base.name,
|
|
791
|
+
);
|
|
792
|
+
this.line(
|
|
793
|
+
`((__p, __v) => { if (${isStruct}) Object.assign(__p, __v); else __p.value = __v; })(${ptr}, ${val});`,
|
|
794
|
+
);
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
if (this._hasTypeParamDerefTarget(stmt)) {
|
|
798
|
+
this._genTypeParamDerefMultiAssign(stmt);
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
295
801
|
if (this._isStructDerefAssign(stmt)) {
|
|
296
802
|
this._genStructDerefAssign(stmt);
|
|
297
803
|
return;
|
|
298
804
|
}
|
|
805
|
+
if (this._isStableWholeAssign(stmt)) {
|
|
806
|
+
this._genStableWholeAssign(stmt);
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
if (this._isStableTupleAssign(stmt)) {
|
|
810
|
+
this._genStableTupleAssign(stmt);
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
299
813
|
const paired = stmt.rhs.length === stmt.lhs.length && stmt.op === "=";
|
|
300
814
|
const rhs = stmt.rhs.map((e, i) =>
|
|
301
815
|
paired && stmt.lhs[i].name !== "_"
|
|
302
816
|
? this.genValueExpr(e)
|
|
303
817
|
: this.genExpr(e),
|
|
304
818
|
);
|
|
305
|
-
this.
|
|
819
|
+
const hoisted = this._hoistLhsOperands(stmt);
|
|
306
820
|
const lhs = stmt.lhs.map((e) => this._genAssignLhsExpr(e));
|
|
821
|
+
hoisted?.restore();
|
|
307
822
|
const pairs = lhs.map((l, i) => ({ l, r: rhs[i] ?? rhs[0] }));
|
|
308
823
|
const active = pairs.filter((p) => p.l !== "_");
|
|
309
824
|
if (active.length === 0) {
|
|
@@ -324,42 +839,139 @@ export const statementGenMethods = {
|
|
|
324
839
|
: this.genExpr(stmt.lhs[0].expr);
|
|
325
840
|
const idx = this.genExpr(stmt.lhs[0].index);
|
|
326
841
|
const binOp = stmt.op.slice(0, -1);
|
|
842
|
+
const is64Val = is64BitInt(stmt.lhs[0]._mapValueType);
|
|
843
|
+
let rhsVal = active[0].r;
|
|
844
|
+
if (
|
|
845
|
+
is64Val &&
|
|
846
|
+
stmt.rhs[0]?._type?.kind === "untyped" &&
|
|
847
|
+
!rhsVal.endsWith("n") &&
|
|
848
|
+
/^-?\d+$/.test(rhsVal)
|
|
849
|
+
) {
|
|
850
|
+
rhsVal = `${rhsVal}n`;
|
|
851
|
+
}
|
|
852
|
+
const rhsExpr = is64Val
|
|
853
|
+
? this._gen64BitBinary(
|
|
854
|
+
binOp,
|
|
855
|
+
`(__m[__k] ?? ${zero})`,
|
|
856
|
+
rhsVal,
|
|
857
|
+
stmt.lhs[0]._mapValueType,
|
|
858
|
+
)
|
|
859
|
+
: `(__m[__k] ?? ${zero}) ${binOp === "&^" ? "& ~" : binOp} (${rhsVal})`;
|
|
860
|
+
const rhsExprDirect = is64Val
|
|
861
|
+
? this._gen64BitBinary(
|
|
862
|
+
binOp,
|
|
863
|
+
`(${base}[${idx}] ?? ${zero})`,
|
|
864
|
+
rhsVal,
|
|
865
|
+
stmt.lhs[0]._mapValueType,
|
|
866
|
+
)
|
|
867
|
+
: `(${base}[${idx}] ?? ${zero}) ${binOp === "&^" ? "& ~" : binOp} (${rhsVal})`;
|
|
327
868
|
if (
|
|
328
869
|
this._hasCallExpr(stmt.lhs[0].expr) ||
|
|
329
870
|
this._hasCallExpr(stmt.lhs[0].index)
|
|
330
871
|
) {
|
|
331
872
|
this.line(
|
|
332
|
-
`((__m, __k) => { __m[__k] =
|
|
873
|
+
`((__m, __k) => { ${this._genMapWriteBase("__m")}[__k] = ${rhsExpr}; })(${base}, ${idx});`,
|
|
333
874
|
);
|
|
334
875
|
} else {
|
|
335
876
|
this.line(
|
|
336
|
-
`${base}[${idx}] =
|
|
877
|
+
`${this._genMapWriteBase(base)}[${idx}] = ${rhsExprDirect};`,
|
|
337
878
|
);
|
|
338
879
|
}
|
|
339
880
|
return;
|
|
340
881
|
}
|
|
882
|
+
const is64 = is64BitInt(stmt.lhs[0]._type);
|
|
883
|
+
let rhsVal = active[0].r;
|
|
341
884
|
if (
|
|
342
|
-
|
|
343
|
-
stmt.
|
|
885
|
+
is64 &&
|
|
886
|
+
stmt.rhs[0]?._type?.kind === "untyped" &&
|
|
887
|
+
!rhsVal.endsWith("n") &&
|
|
888
|
+
/^-?\d+$/.test(rhsVal)
|
|
889
|
+
) {
|
|
890
|
+
rhsVal = `${rhsVal}n`;
|
|
891
|
+
}
|
|
892
|
+
if (
|
|
893
|
+
this._needsStrictCompoundStmt(stmt, is64) &&
|
|
344
894
|
this._genStrictCompound(
|
|
345
895
|
stmt.lhs[0],
|
|
346
896
|
stmt.op.slice(0, -1),
|
|
347
|
-
|
|
897
|
+
rhsVal,
|
|
348
898
|
null,
|
|
349
899
|
active[0].l,
|
|
900
|
+
stmt.rhs[0]?._type,
|
|
350
901
|
)
|
|
351
902
|
) {
|
|
352
903
|
return;
|
|
353
904
|
}
|
|
354
|
-
|
|
905
|
+
const targetL =
|
|
906
|
+
active[0].l.startsWith("{") && !active[0].l.startsWith("({")
|
|
907
|
+
? `(${active[0].l})`
|
|
908
|
+
: active[0].l;
|
|
909
|
+
if (stmt.op === "&^=") this.line(`${targetL} &= ~(${rhsVal});`);
|
|
910
|
+
else this.line(`${targetL} ${stmt.op} ${rhsVal};`);
|
|
911
|
+
} else if (hoisted) {
|
|
912
|
+
this.line("{");
|
|
913
|
+
this.indented(() => {
|
|
914
|
+
for (const l of hoisted.lines) this.line(l);
|
|
915
|
+
this._genAssignMulti(stmt, lhs, rhs, active, pairs);
|
|
916
|
+
});
|
|
917
|
+
this.line("}");
|
|
355
918
|
} else {
|
|
356
919
|
this._genAssignMulti(stmt, lhs, rhs, active, pairs);
|
|
357
920
|
}
|
|
358
921
|
},
|
|
359
922
|
|
|
923
|
+
// `k, xs[k] = 1, 5`: Go evaluates the operands of every index and field
|
|
924
|
+
// target before any assignment, but a JS destructuring target reads its
|
|
925
|
+
// operands after the earlier ones are stored. Operands that mention a
|
|
926
|
+
// variable assigned by the same statement are bound to temporaries first.
|
|
927
|
+
_hoistLhsOperands(stmt) {
|
|
928
|
+
if (stmt.op !== "=" || stmt.lhs.length < 2) return null;
|
|
929
|
+
const assigned = new Set(
|
|
930
|
+
stmt.lhs
|
|
931
|
+
.filter((l) => l.kind === "Ident" && l.name !== "_")
|
|
932
|
+
.map((l) => l.name),
|
|
933
|
+
);
|
|
934
|
+
if (assigned.size === 0) return null;
|
|
935
|
+
const mentions = (n) =>
|
|
936
|
+
!!n &&
|
|
937
|
+
typeof n === "object" &&
|
|
938
|
+
((n.kind === "Ident" && assigned.has(n.name)) || someChild(n, mentions));
|
|
939
|
+
const lines = [];
|
|
940
|
+
const undo = [];
|
|
941
|
+
const visit = (l) => {
|
|
942
|
+
let keys;
|
|
943
|
+
if (l.kind === "IndexExpr") keys = ["expr", "index"];
|
|
944
|
+
else if (l.kind === "SelectorExpr" && !l._isMethodExpr) keys = ["expr"];
|
|
945
|
+
else return;
|
|
946
|
+
for (const key of keys) {
|
|
947
|
+
const operand = l[key];
|
|
948
|
+
if (!mentions(operand)) continue;
|
|
949
|
+
// A struct or array value names the variable's storage in Go: hoist
|
|
950
|
+
// the operands inside it (`ps[n].A`) instead of the value.
|
|
951
|
+
if (key === "expr" && this._aggregateBase(operand._type)) {
|
|
952
|
+
visit(operand);
|
|
953
|
+
continue;
|
|
954
|
+
}
|
|
955
|
+
this._tmpCounter = (this._tmpCounter ?? 0) + 1;
|
|
956
|
+
const name = `__h${this._tmpCounter}`;
|
|
957
|
+
lines.push(`const ${name} = ${this.genExpr(operand)};`);
|
|
958
|
+
undo.push([l, key, operand]);
|
|
959
|
+
l[key] = { kind: "Ident", name, _type: operand._type };
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
for (const l of stmt.lhs) visit(l);
|
|
963
|
+
if (lines.length === 0) return null;
|
|
964
|
+
return {
|
|
965
|
+
lines,
|
|
966
|
+
restore: () => {
|
|
967
|
+
for (const [node, key, v] of undo) node[key] = v;
|
|
968
|
+
},
|
|
969
|
+
};
|
|
970
|
+
},
|
|
971
|
+
|
|
360
972
|
_genCommaOkTypeAssert(stmt) {
|
|
361
973
|
const val = this.genExpr(stmt.rhs[0]);
|
|
362
|
-
const [vName, okName] = stmt.lhs.map((e) =>
|
|
974
|
+
const [vName, okName] = stmt.lhs.map((e) => this._genAssignLhsExpr(e));
|
|
363
975
|
this._tmpCounter = (this._tmpCounter ?? 0) + 1;
|
|
364
976
|
const tmp = `__ta${this._tmpCounter}`;
|
|
365
977
|
this.line(`const ${tmp} = ${val};`);
|
|
@@ -367,6 +979,118 @@ export const statementGenMethods = {
|
|
|
367
979
|
if (okName !== "_") this.line(`${okName} = ${tmp}[1];`);
|
|
368
980
|
},
|
|
369
981
|
|
|
982
|
+
// `a = [3]int{...}` / `s = S{...}` on a variable whose address (or an
|
|
983
|
+
// element's or field's address) is taken must write into the existing
|
|
984
|
+
// storage so those pointers see the new value (Task 0.33).
|
|
985
|
+
_isStableWholeAssign(stmt) {
|
|
986
|
+
if (stmt.op !== "=" || stmt.lhs.length !== 1 || stmt.rhs.length !== 1)
|
|
987
|
+
return false;
|
|
988
|
+
return this._isStableTarget(stmt.lhs[0], stmt.rhs[0]);
|
|
989
|
+
},
|
|
990
|
+
|
|
991
|
+
// `l = rhs` writes into the existing storage of `l` (see above). When the
|
|
992
|
+
// program takes `&xs[i]` anywhere, an element of a slice does too: any
|
|
993
|
+
// callee may hold the pointer.
|
|
994
|
+
_isStableTarget(l, rhs) {
|
|
995
|
+
if (l.kind === "IndexExpr" && l._mapValueType) return false;
|
|
996
|
+
if (
|
|
997
|
+
l.kind !== "Ident" &&
|
|
998
|
+
l.kind !== "SelectorExpr" &&
|
|
999
|
+
l.kind !== "IndexExpr"
|
|
1000
|
+
)
|
|
1001
|
+
return false;
|
|
1002
|
+
if (l.kind === "Ident" && l.name === "_") return false;
|
|
1003
|
+
if (!this._aggregateBase(l._type) || !this._aggregateBase(rhs._type))
|
|
1004
|
+
return false;
|
|
1005
|
+
if (this._elemAddrTaken && this._isSliceElemTarget(l)) return true;
|
|
1006
|
+
const root = this._rootIdentName(l);
|
|
1007
|
+
return root !== null && this._fnMutates(root, true);
|
|
1008
|
+
},
|
|
1009
|
+
|
|
1010
|
+
// `xs[i]` on a plain slice (not a typed array, array or map).
|
|
1011
|
+
_isSliceElemTarget(l) {
|
|
1012
|
+
if (l.kind !== "IndexExpr" || l._mapValueType) return false;
|
|
1013
|
+
const u =
|
|
1014
|
+
l.expr._type?.kind === "named" ? l.expr._type.underlying : l.expr._type;
|
|
1015
|
+
return u?.kind === "slice" && !isTypedArraySlice(l.expr._type);
|
|
1016
|
+
},
|
|
1017
|
+
|
|
1018
|
+
// `a, b = x, y` where a target is stored in place: every right-hand value
|
|
1019
|
+
// is evaluated (and copied) first, then each target is assigned in order.
|
|
1020
|
+
_isStableTupleAssign(stmt) {
|
|
1021
|
+
return (
|
|
1022
|
+
stmt.op === "=" &&
|
|
1023
|
+
stmt.lhs.length > 1 &&
|
|
1024
|
+
stmt.rhs.length === stmt.lhs.length &&
|
|
1025
|
+
stmt.lhs.some((l, i) => this._isStableTarget(l, stmt.rhs[i]))
|
|
1026
|
+
);
|
|
1027
|
+
},
|
|
1028
|
+
|
|
1029
|
+
_genStableTupleAssign(stmt) {
|
|
1030
|
+
const hoisted = this._hoistLhsOperands(stmt);
|
|
1031
|
+
const stable = stmt.lhs.map((l, i) => this._isStableTarget(l, stmt.rhs[i]));
|
|
1032
|
+
this.line("{");
|
|
1033
|
+
this.indented(() => {
|
|
1034
|
+
for (const l of hoisted?.lines ?? []) this.line(l);
|
|
1035
|
+
const base = (this._tmpCounter ?? 0) + 1;
|
|
1036
|
+
this._tmpCounter = base + stmt.lhs.length;
|
|
1037
|
+
const val = (i) => `__s${base + i}`;
|
|
1038
|
+
const dst = (i) => `__d${base + i}`;
|
|
1039
|
+
stmt.rhs.forEach((r, i) => {
|
|
1040
|
+
this.line(`const ${val(i)} = ${this.genValueExpr(r)};`);
|
|
1041
|
+
});
|
|
1042
|
+
stmt.lhs.forEach((l, i) => {
|
|
1043
|
+
if (stable[i])
|
|
1044
|
+
this.line(`const ${dst(i)} = ${this._genAssignLhsExpr(l)};`);
|
|
1045
|
+
});
|
|
1046
|
+
stmt.lhs.forEach((l, i) => {
|
|
1047
|
+
if (stable[i]) this._emitCopyInto(dst(i), val(i), l._type);
|
|
1048
|
+
else if (!(l.kind === "Ident" && l.name === "_"))
|
|
1049
|
+
this.line(`${this._genAssignLhsExpr(l)} = ${val(i)};`);
|
|
1050
|
+
});
|
|
1051
|
+
});
|
|
1052
|
+
this.line("}");
|
|
1053
|
+
hoisted?.restore();
|
|
1054
|
+
},
|
|
1055
|
+
|
|
1056
|
+
_genStableWholeAssign(stmt) {
|
|
1057
|
+
let target = this._genAssignLhsExpr(stmt.lhs[0]);
|
|
1058
|
+
const type = stmt.lhs[0]._type;
|
|
1059
|
+
this._tmpCounter = (this._tmpCounter ?? 0) + 1;
|
|
1060
|
+
const src = `__s${this._tmpCounter}`;
|
|
1061
|
+
this.line(`{ const ${src} = ${this.genExpr(stmt.rhs[0])};`);
|
|
1062
|
+
if (stmt.lhs[0].kind === "IndexExpr") {
|
|
1063
|
+
// Evaluate the element reference once.
|
|
1064
|
+
target = `__d${this._tmpCounter}`;
|
|
1065
|
+
this.line(`const ${target} = ${this._genAssignLhsExpr(stmt.lhs[0])};`);
|
|
1066
|
+
}
|
|
1067
|
+
this._emitCopyInto(target, src, type);
|
|
1068
|
+
this.line("}");
|
|
1069
|
+
},
|
|
1070
|
+
|
|
1071
|
+
// Copies aggregate `src` into the storage `dst` element by element, going
|
|
1072
|
+
// into nested arrays and structs so pointers to inner parts stay valid.
|
|
1073
|
+
_emitCopyInto(dst, src, type) {
|
|
1074
|
+
const base = this._aggregateBase(type);
|
|
1075
|
+
if (base.kind === "struct") {
|
|
1076
|
+
for (const [name, ft] of base.fields) {
|
|
1077
|
+
if (name === "_") continue;
|
|
1078
|
+
const f = `.${name}`;
|
|
1079
|
+
if (this._aggregateBase(ft))
|
|
1080
|
+
this._emitCopyInto(`${dst}${f}`, `${src}${f}`, ft);
|
|
1081
|
+
else this.line(`${dst}${f} = ${src}${f};`);
|
|
1082
|
+
}
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
this._tmpCounter = (this._tmpCounter ?? 0) + 1;
|
|
1086
|
+
const k = `__k${this._tmpCounter}`;
|
|
1087
|
+
this.line(`for (let ${k} = 0; ${k} < ${src}.length; ${k}++) {`);
|
|
1088
|
+
if (this._aggregateBase(base.elem))
|
|
1089
|
+
this._emitCopyInto(`${dst}[${k}]`, `${src}[${k}]`, base.elem);
|
|
1090
|
+
else this.line(`${dst}[${k}] = ${src}[${k}];`);
|
|
1091
|
+
this.line("}");
|
|
1092
|
+
},
|
|
1093
|
+
|
|
370
1094
|
_genStructDerefAssign(stmt) {
|
|
371
1095
|
const target = this.genExpr(stmt.lhs[0].operand);
|
|
372
1096
|
const rhsNode = stmt.rhs[0];
|
|
@@ -393,31 +1117,45 @@ export const statementGenMethods = {
|
|
|
393
1117
|
},
|
|
394
1118
|
|
|
395
1119
|
_genComplexCompoundAssign(stmt) {
|
|
396
|
-
const
|
|
1120
|
+
const l = stmt.lhs[0];
|
|
1121
|
+
let lhsTarget;
|
|
1122
|
+
let lhsRead;
|
|
1123
|
+
if (l.kind === "IndexExpr" && l._mapValueType) {
|
|
1124
|
+
const wrapField = this._namedWrapperField(l.expr._type, l.expr);
|
|
1125
|
+
const base = wrapField
|
|
1126
|
+
? `${this.genExpr(l.expr)}.${wrapField}`
|
|
1127
|
+
: this.genExpr(l.expr);
|
|
1128
|
+
const idx = this.genExpr(l.index);
|
|
1129
|
+
lhsTarget = `${this._genMapWriteBase(base)}[${idx}]`;
|
|
1130
|
+
lhsRead = `(${base}[${idx}] ?? { re: 0, im: 0 })`;
|
|
1131
|
+
} else {
|
|
1132
|
+
lhsTarget = l.name ?? this.genExpr(l);
|
|
1133
|
+
lhsRead = lhsTarget;
|
|
1134
|
+
}
|
|
397
1135
|
const rhsExpr = this._genComplexOperand(stmt.rhs[0]);
|
|
398
1136
|
const baseOp = stmt.op.slice(0, -1); // "+=" → "+"
|
|
399
1137
|
let result;
|
|
400
1138
|
switch (baseOp) {
|
|
401
1139
|
case "+":
|
|
402
|
-
result = `{ re: ${
|
|
1140
|
+
result = `{ re: ${lhsRead}.re + ${rhsExpr}.re, im: ${lhsRead}.im + ${rhsExpr}.im }`;
|
|
403
1141
|
break;
|
|
404
1142
|
case "-":
|
|
405
|
-
result = `{ re: ${
|
|
1143
|
+
result = `{ re: ${lhsRead}.re - ${rhsExpr}.re, im: ${lhsRead}.im - ${rhsExpr}.im }`;
|
|
406
1144
|
break;
|
|
407
1145
|
case "*":
|
|
408
1146
|
this.useHelper("cmul");
|
|
409
|
-
result = `__cmul(${
|
|
1147
|
+
result = `__cmul(${lhsRead}, ${rhsExpr})`;
|
|
410
1148
|
break;
|
|
411
1149
|
case "/":
|
|
412
1150
|
this.useHelper("cdiv");
|
|
413
|
-
result = `__cdiv(${
|
|
1151
|
+
result = `__cdiv(${lhsRead}, ${rhsExpr})`;
|
|
414
1152
|
break;
|
|
415
1153
|
default:
|
|
416
1154
|
throw new Error(
|
|
417
1155
|
`unsupported complex compound assignment operator: ${stmt.op}`,
|
|
418
1156
|
);
|
|
419
1157
|
}
|
|
420
|
-
this.line(`${
|
|
1158
|
+
this.line(`${lhsTarget} = ${result};`);
|
|
421
1159
|
},
|
|
422
1160
|
|
|
423
1161
|
_genIteratorReturn(stmt) {
|
|
@@ -476,8 +1214,12 @@ export const statementGenMethods = {
|
|
|
476
1214
|
}
|
|
477
1215
|
},
|
|
478
1216
|
|
|
479
|
-
|
|
480
|
-
if (
|
|
1217
|
+
_genIteratorBranchStmt(stmt) {
|
|
1218
|
+
if (!stmt.label) {
|
|
1219
|
+
if (this._iterLoopDepth > 0) {
|
|
1220
|
+
if (stmt.keyword !== "fallthrough") this.line(`${stmt.keyword};`);
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
481
1223
|
if (stmt.keyword === "break") {
|
|
482
1224
|
this.line(`${this._iterBreakFlag} = true; return false;`);
|
|
483
1225
|
return;
|
|
@@ -486,6 +1228,29 @@ export const statementGenMethods = {
|
|
|
486
1228
|
this.line("return true;");
|
|
487
1229
|
return;
|
|
488
1230
|
}
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
if (this._innerLoopLabels?.has(stmt.label)) {
|
|
1234
|
+
if (stmt.keyword !== "fallthrough")
|
|
1235
|
+
this.line(`${stmt.keyword} ${stmt.label};`);
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
if (
|
|
1239
|
+
stmt.keyword === "continue" &&
|
|
1240
|
+
stmt.label === this._currentIterLoopLabel
|
|
1241
|
+
) {
|
|
1242
|
+
this.line("return true;");
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
this.line(
|
|
1246
|
+
`${this._iterTargetFlag} = "${stmt.keyword}$${stmt.label}"; return false;`,
|
|
1247
|
+
);
|
|
1248
|
+
},
|
|
1249
|
+
|
|
1250
|
+
_genBranchStmt(stmt) {
|
|
1251
|
+
if (this._inIteratorBody) {
|
|
1252
|
+
this._genIteratorBranchStmt(stmt);
|
|
1253
|
+
return;
|
|
489
1254
|
}
|
|
490
1255
|
if (stmt.keyword !== "fallthrough")
|
|
491
1256
|
this.line(
|
|
@@ -505,7 +1270,12 @@ export const statementGenMethods = {
|
|
|
505
1270
|
this.line("}");
|
|
506
1271
|
return;
|
|
507
1272
|
}
|
|
508
|
-
if (elseBody.kind === "IfStmt") {
|
|
1273
|
+
if (elseBody.kind === "IfStmt" && elseBody.init) {
|
|
1274
|
+
// `else if init; cond`: the init runs only when this branch is taken.
|
|
1275
|
+
this.line("} else {");
|
|
1276
|
+
this.indented(() => this.genStmt(elseBody));
|
|
1277
|
+
this.line("}");
|
|
1278
|
+
} else if (elseBody.kind === "IfStmt") {
|
|
509
1279
|
this.line(`} else if (${this.genExpr(elseBody.cond)}) {`);
|
|
510
1280
|
this.indented(() => this.genBlock(elseBody.body));
|
|
511
1281
|
this._genElse(elseBody.elseBody);
|
|
@@ -518,7 +1288,8 @@ export const statementGenMethods = {
|
|
|
518
1288
|
|
|
519
1289
|
_rangeExprLimit(iterType, iteree) {
|
|
520
1290
|
if (this._isIntRangeType(iterType)) return iteree;
|
|
521
|
-
if (this._isMapRangeType(iterType))
|
|
1291
|
+
if (this._isMapRangeType(iterType))
|
|
1292
|
+
return `Object.keys(${iteree} ?? {}).length`;
|
|
522
1293
|
return `(${iteree} ? ${iteree}.length : 0)`;
|
|
523
1294
|
},
|
|
524
1295
|
|
|
@@ -527,7 +1298,7 @@ export const statementGenMethods = {
|
|
|
527
1298
|
this.genIteratorForCond(stmt);
|
|
528
1299
|
return;
|
|
529
1300
|
}
|
|
530
|
-
const iterType = stmt.cond.expr._type;
|
|
1301
|
+
const iterType = stmt.cond.expr._coreType ?? stmt.cond.expr._type;
|
|
531
1302
|
const iteree = this.genExpr(stmt.cond.expr);
|
|
532
1303
|
const limit = this._rangeExprLimit(iterType, iteree);
|
|
533
1304
|
this.line(`for (let _$ = 0; _$ < ${limit}; _$++) {`);
|
|
@@ -545,32 +1316,114 @@ export const statementGenMethods = {
|
|
|
545
1316
|
this.line("}");
|
|
546
1317
|
},
|
|
547
1318
|
|
|
1319
|
+
// `for n := n; ...` declares a loop variable its own initialiser reads: the
|
|
1320
|
+
// initialiser values are evaluated into temporaries ahead of the loop.
|
|
1321
|
+
_hoistForInit(stmt) {
|
|
1322
|
+
const init = stmt.kind === "ForStmt" && stmt.init;
|
|
1323
|
+
if (
|
|
1324
|
+
!init ||
|
|
1325
|
+
stmt._initPre ||
|
|
1326
|
+
init.kind !== "DefineStmt" ||
|
|
1327
|
+
this.isRangeFor(stmt) ||
|
|
1328
|
+
stmt.cond?.kind === "RangeExpr" ||
|
|
1329
|
+
!this._definesShadowedRead(init)
|
|
1330
|
+
)
|
|
1331
|
+
return false;
|
|
1332
|
+
this._tmpCounter = (this._tmpCounter ?? 0) + 1;
|
|
1333
|
+
const values = this._defineRhs(init);
|
|
1334
|
+
const names = values.map((_, i) => `__fi${this._tmpCounter}_${i}`);
|
|
1335
|
+
this.line("{");
|
|
1336
|
+
this.indent++;
|
|
1337
|
+
this.line(
|
|
1338
|
+
`const ${values.map((v, i) => `${names[i]} = ${v}`).join(", ")};`,
|
|
1339
|
+
);
|
|
1340
|
+
stmt._initPre = names;
|
|
1341
|
+
return true;
|
|
1342
|
+
},
|
|
1343
|
+
|
|
1344
|
+
_closeHoistedForInit() {
|
|
1345
|
+
this.indent--;
|
|
1346
|
+
this.line("}");
|
|
1347
|
+
},
|
|
1348
|
+
|
|
548
1349
|
genFor(stmt) {
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
1350
|
+
const hoisted = this._hoistForInit(stmt);
|
|
1351
|
+
try {
|
|
1352
|
+
this._genForLoop(stmt);
|
|
1353
|
+
} finally {
|
|
1354
|
+
delete stmt._initPre;
|
|
553
1355
|
}
|
|
554
|
-
if (
|
|
555
|
-
|
|
556
|
-
|
|
1356
|
+
if (hoisted) this._closeHoistedForInit();
|
|
1357
|
+
},
|
|
1358
|
+
|
|
1359
|
+
_genForLoop(stmt) {
|
|
1360
|
+
const isInner = this._inIteratorBody;
|
|
1361
|
+
if (isInner) {
|
|
1362
|
+
this._iterLoopDepth = (this._iterLoopDepth ?? 0) + 1;
|
|
1363
|
+
if (stmt.label) {
|
|
1364
|
+
this._innerLoopLabels = this._innerLoopLabels ?? new Set();
|
|
1365
|
+
this._innerLoopLabels.add(stmt.label);
|
|
1366
|
+
}
|
|
557
1367
|
}
|
|
558
|
-
|
|
559
|
-
this.
|
|
560
|
-
|
|
1368
|
+
try {
|
|
1369
|
+
if (this.isRangeFor(stmt)) {
|
|
1370
|
+
if (stmt.init.rhs[0]._isIterator) this.genIteratorFor(stmt);
|
|
1371
|
+
else this.genRangeFor(stmt);
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
if (stmt.cond?.kind === "RangeExpr") {
|
|
1375
|
+
this._genRangeExprFor(stmt);
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
if (!stmt.init && !stmt.post) {
|
|
1379
|
+
this._genSimpleWhileFor(stmt);
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
const init = stmt.init ? this.stmtInline(stmt.init, stmt._initPre) : "";
|
|
1383
|
+
const cond = stmt.cond ? this.genExpr(stmt.cond) : "";
|
|
1384
|
+
const post = this._perIterationClones(
|
|
1385
|
+
stmt,
|
|
1386
|
+
stmt.post ? this.stmtInline(stmt.post) : "",
|
|
1387
|
+
);
|
|
1388
|
+
this.line(`for (${init}; ${cond}; ${post}) {`);
|
|
1389
|
+
this.indented(() => this.genBlock(stmt.body));
|
|
1390
|
+
this.line("}");
|
|
1391
|
+
} finally {
|
|
1392
|
+
if (isInner) {
|
|
1393
|
+
this._iterLoopDepth--;
|
|
1394
|
+
if (stmt.label) this._innerLoopLabels.delete(stmt.label);
|
|
1395
|
+
}
|
|
561
1396
|
}
|
|
562
|
-
const init = stmt.init ? this.stmtInline(stmt.init) : "";
|
|
563
|
-
const cond = stmt.cond ? this.genExpr(stmt.cond) : "";
|
|
564
|
-
const post = stmt.post ? this.stmtInline(stmt.post) : "";
|
|
565
|
-
this.line(`for (${init}; ${cond}; ${post}) {`);
|
|
566
|
-
this.indented(() => this.genBlock(stmt.body));
|
|
567
|
-
this.line("}");
|
|
568
1397
|
},
|
|
569
1398
|
|
|
570
1399
|
isRangeFor(stmt) {
|
|
571
1400
|
return isRangeFor(stmt);
|
|
572
1401
|
},
|
|
573
1402
|
|
|
1403
|
+
// Go 1.22 per-iteration loop variables. `let` gives each iteration its
|
|
1404
|
+
// own binding, but a struct or array variable is a reference and an
|
|
1405
|
+
// address-taken scalar is a `{ value }` box: replace it at the start of
|
|
1406
|
+
// the post step (which runs in the next iteration's binding), so closures
|
|
1407
|
+
// and pointers from the finished iteration keep their own variable.
|
|
1408
|
+
_perIterationClones(stmt, post) {
|
|
1409
|
+
const clones = [];
|
|
1410
|
+
for (const name of loopVarsCapturedInLoop(stmt)) {
|
|
1411
|
+
const i = stmt.init.lhs.findIndex((e) => e.name === name);
|
|
1412
|
+
const t = stmt.init.lhs[i]?._type ?? stmt.init.rhs?.[i]?._type;
|
|
1413
|
+
if (this._boxedVars?.has(name)) {
|
|
1414
|
+
const v = `${name}.value`;
|
|
1415
|
+
const copy = this._aggregateBase(t) ? this._cloneJs(t, v) : v;
|
|
1416
|
+
clones.push(`${name} = { value: ${copy} }`);
|
|
1417
|
+
continue;
|
|
1418
|
+
}
|
|
1419
|
+
if (!this._aggregateBase(t)) continue;
|
|
1420
|
+
clones.push(`${name} = ${this._cloneJs(t, name)}`);
|
|
1421
|
+
}
|
|
1422
|
+
if (clones.length === 0) return post;
|
|
1423
|
+
const tail = post.replace(/;\s*$/, "");
|
|
1424
|
+
return tail ? `${clones.join(", ")}, ${tail}` : clones.join(", ");
|
|
1425
|
+
},
|
|
1426
|
+
|
|
574
1427
|
_isIntRangeType(iterType) {
|
|
575
1428
|
return isIntRangeType(iterType);
|
|
576
1429
|
},
|
|
@@ -617,10 +1470,14 @@ export const statementGenMethods = {
|
|
|
617
1470
|
const iterType = iterNode._type;
|
|
618
1471
|
const base = iterType?.kind === "named" ? iterType.underlying : iterType;
|
|
619
1472
|
const elemType = base?.elem;
|
|
620
|
-
if (
|
|
621
|
-
|
|
1473
|
+
if (
|
|
1474
|
+
!valNode ||
|
|
1475
|
+
(!this._aggregateBase(elemType) && !this._typeParamClone(elemType, js))
|
|
1476
|
+
)
|
|
1477
|
+
return js;
|
|
1478
|
+
if (valNode.kind === "Ident" && valNode.name === "_") return js;
|
|
1479
|
+
if (isAssign || valNode.kind !== "Ident" || elemType?.kind === "typeParam")
|
|
622
1480
|
return this._cloneJs(elemType, js);
|
|
623
|
-
if (valNode.name === "_") return js;
|
|
624
1481
|
const root = this._rootIdentName(iterNode);
|
|
625
1482
|
const mutated =
|
|
626
1483
|
this._fnMutates(valNode.name) ||
|
|
@@ -629,21 +1486,33 @@ export const statementGenMethods = {
|
|
|
629
1486
|
return mutated ? this._cloneJs(elemType, js) : js;
|
|
630
1487
|
},
|
|
631
1488
|
|
|
632
|
-
_genSliceRangeFor(stmt, iteree, isAssign) {
|
|
1489
|
+
_genSliceRangeFor(stmt, iteree, isAssign, unwrapView = false) {
|
|
633
1490
|
this._loopDepth = this._loopDepth ?? 0;
|
|
634
1491
|
const d = this._loopDepth++;
|
|
635
1492
|
const [idxNode, valNode] = stmt.init.lhs;
|
|
636
1493
|
const i = `__i${d}`;
|
|
637
1494
|
const arr = `__arr${d}`;
|
|
638
1495
|
const len = `__len${d}`;
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
1496
|
+
let elem = `${arr}[${i}]`;
|
|
1497
|
+
if (unwrapView) {
|
|
1498
|
+
// A partial view of a plain array is a Proxy (`__slice`): read the
|
|
1499
|
+
// elements from its backing array `b` at offset `o` instead.
|
|
1500
|
+
this.useHelper("sv");
|
|
1501
|
+
const [v, b, o] = [`__v${d}`, `__b${d}`, `__o${d}`];
|
|
1502
|
+
this.line(
|
|
1503
|
+
`for (let ${i} = 0, ${arr} = ${iteree}, ${v} = ${arr}?.[__sv], ${b} = ${v} === undefined ? ${arr} : ${v}.b, ${o} = ${v} === undefined ? 0 : ${v}.o, ${len} = ${v} !== undefined ? ${v}.n : ${arr} ? ${arr}.length : 0; ${i} < ${len}; ${i}++) {`,
|
|
1504
|
+
);
|
|
1505
|
+
elem = `${b}[${o} + ${i}]`;
|
|
1506
|
+
} else {
|
|
1507
|
+
this.line(
|
|
1508
|
+
`for (let ${i} = 0, ${arr} = ${iteree}, ${len} = ${arr} ? ${arr}.length : 0; ${i} < ${len}; ${i}++) {`,
|
|
1509
|
+
);
|
|
1510
|
+
}
|
|
642
1511
|
this.indented(() => {
|
|
643
1512
|
this._emitRangeVar(idxNode, i, isAssign);
|
|
644
1513
|
this._emitRangeVar(
|
|
645
1514
|
valNode,
|
|
646
|
-
this._rangeElemValue(stmt, valNode,
|
|
1515
|
+
this._rangeElemValue(stmt, valNode, elem, isAssign),
|
|
647
1516
|
isAssign,
|
|
648
1517
|
);
|
|
649
1518
|
const declared = isAssign
|
|
@@ -699,11 +1568,6 @@ export const statementGenMethods = {
|
|
|
699
1568
|
this._loopDepth--;
|
|
700
1569
|
},
|
|
701
1570
|
|
|
702
|
-
// True when `node` contains an assignment or ++/-- targeting identifier `name`.
|
|
703
|
-
_nodeAssigns(node, name) {
|
|
704
|
-
return nodeAssigns(node, name);
|
|
705
|
-
},
|
|
706
|
-
|
|
707
1571
|
_genStringRangeFor(stmt, iteree, isAssign) {
|
|
708
1572
|
this._loopDepth = this._loopDepth ?? 0;
|
|
709
1573
|
const d = this._loopDepth++;
|
|
@@ -711,13 +1575,17 @@ export const statementGenMethods = {
|
|
|
711
1575
|
const s = `__s${d}`;
|
|
712
1576
|
const i = `__i${d}`;
|
|
713
1577
|
const cp = `__cp${d}`;
|
|
1578
|
+
const w = `__w${d}`;
|
|
1579
|
+
// The index is a UTF-16 offset (an astral rune is two units) so `s[i:]`
|
|
1580
|
+
// agrees with the WebAssembly string layout.
|
|
714
1581
|
this.line(
|
|
715
|
-
`for (let ${i} = 0, ${s} =
|
|
1582
|
+
`for (let ${i} = 0, ${s} = ${iteree} ?? "", ${w} = 0; ${i} < ${s}.length; ${i} += ${w}) {`,
|
|
716
1583
|
);
|
|
717
1584
|
this.indented(() => {
|
|
1585
|
+
this.line(`const ${cp} = ${s}.codePointAt(${i});`);
|
|
1586
|
+
this.line(`${w} = ${cp} > 0xffff ? 2 : 1;`);
|
|
718
1587
|
this._emitRangeVar(idxNode, i, isAssign);
|
|
719
1588
|
if (valNode && valNode.name !== "_") {
|
|
720
|
-
this.line(`const ${cp} = ${s}[${i}].codePointAt(0);`);
|
|
721
1589
|
this._emitRangeVar(valNode, cp, isAssign);
|
|
722
1590
|
}
|
|
723
1591
|
const declared = isAssign
|
|
@@ -739,23 +1607,108 @@ export const statementGenMethods = {
|
|
|
739
1607
|
|
|
740
1608
|
_genIterFor(init, iterType, iteree, lhs, body) {
|
|
741
1609
|
const iterExpr = this._genRangeIterExpr(iterType, iteree, lhs);
|
|
1610
|
+
const keyDecode = this._structKeyRange(iterType);
|
|
1611
|
+
const paramKey = this._typeParamKeyRange(iterType);
|
|
1612
|
+
const is64Key = this._is64BitMapRange(iterType);
|
|
1613
|
+
const scalarKey = this._scalarKeyRange(iterType);
|
|
1614
|
+
const isAssign = init.kind === "AssignStmt";
|
|
1615
|
+
// `for _, v = range m` assigns existing variables, where a blank is a
|
|
1616
|
+
// hole in the destructuring pattern; a lone blank still needs a binding.
|
|
1617
|
+
const blank = isAssign && lhs.length > 1 ? "" : "_$";
|
|
742
1618
|
const binding =
|
|
743
1619
|
lhs.length === 1
|
|
744
1620
|
? lhs[0] === "_"
|
|
745
1621
|
? "_$"
|
|
746
1622
|
: lhs[0]
|
|
747
|
-
: `[${lhs.map((n) => (n === "_" ?
|
|
748
|
-
const kw =
|
|
1623
|
+
: `[${lhs.map((n) => (n === "_" ? blank : n)).join(", ")}]`;
|
|
1624
|
+
const kw = !isAssign || binding === "_$" ? "let " : "";
|
|
749
1625
|
this.line(`for (${kw}${binding} of ${iterExpr}) {`);
|
|
750
|
-
this.indented(() =>
|
|
1626
|
+
this.indented(() => {
|
|
1627
|
+
if (paramKey && lhs[0] !== "_")
|
|
1628
|
+
this.line(`${lhs[0]} = __kdt(${paramKey}, ${lhs[0]});`);
|
|
1629
|
+
else if (keyDecode && lhs[0] !== "_")
|
|
1630
|
+
this.line(`${lhs[0]} = __kd(${lhs[0]}, true);`);
|
|
1631
|
+
else if (is64Key && lhs[0] !== "_")
|
|
1632
|
+
this.line(`${lhs[0]} = BigInt(${lhs[0]});`);
|
|
1633
|
+
else if (scalarKey && lhs[0] !== "_")
|
|
1634
|
+
this.line(
|
|
1635
|
+
scalarKey === "bool"
|
|
1636
|
+
? `${lhs[0]} = ${lhs[0]} === "true";`
|
|
1637
|
+
: `${lhs[0]} = Number(${lhs[0]});`,
|
|
1638
|
+
);
|
|
1639
|
+
this._cloneMapRangeValue(init, iterType, lhs[1]);
|
|
1640
|
+
this.genBlock(body);
|
|
1641
|
+
});
|
|
751
1642
|
this.line("}");
|
|
752
1643
|
},
|
|
753
1644
|
|
|
1645
|
+
_is64BitMapRange(iterType) {
|
|
1646
|
+
if (!this._isMapRangeType(iterType)) return false;
|
|
1647
|
+
const base = iterType?.kind === "named" ? iterType.underlying : iterType;
|
|
1648
|
+
const key = base?.kind === "map" ? base.key : null;
|
|
1649
|
+
return is64BitInt(key);
|
|
1650
|
+
},
|
|
1651
|
+
|
|
1652
|
+
// Ranging over a map with plain int, float or bool keys: object property
|
|
1653
|
+
// names are strings, so the key variable is converted back.
|
|
1654
|
+
_scalarKeyRange(iterType) {
|
|
1655
|
+
if (!this._isMapRangeType(iterType)) return null;
|
|
1656
|
+
const base = iterType?.kind === "named" ? iterType.underlying : iterType;
|
|
1657
|
+
const key = base?.kind === "map" ? base.key : null;
|
|
1658
|
+
if (!key || key.kind === "typeParam") return null;
|
|
1659
|
+
const u = this._underlyingOf(key);
|
|
1660
|
+
if (u?.kind !== "basic" || this._is64BitInt(key)) return null;
|
|
1661
|
+
if (u.name === "bool") return "bool";
|
|
1662
|
+
return /^(u?int\d*|uintptr|float(32|64))$/.test(u.name) ? "num" : null;
|
|
1663
|
+
},
|
|
1664
|
+
|
|
1665
|
+
// Ranging over a map whose key type is a type parameter: the key is decoded
|
|
1666
|
+
// from its property string by the instantiated key type's zero factory.
|
|
1667
|
+
_typeParamKeyRange(iterType) {
|
|
1668
|
+
if (!this._isMapRangeType(iterType)) return null;
|
|
1669
|
+
const base = iterType?.kind === "named" ? iterType.underlying : iterType;
|
|
1670
|
+
const key = base?.kind === "map" ? base.key : null;
|
|
1671
|
+
if (key?.kind !== "typeParam") return null;
|
|
1672
|
+
const factory = this._typeParamZeroFactory(key.name);
|
|
1673
|
+
if (!factory) return null;
|
|
1674
|
+
this.useHelper("structKey");
|
|
1675
|
+
return factory;
|
|
1676
|
+
},
|
|
1677
|
+
|
|
1678
|
+
// Ranging over a map whose keys are structs: the key variable is decoded
|
|
1679
|
+
// from its encoded string (see HELPER_STRUCT_KEY).
|
|
1680
|
+
_structKeyRange(iterType) {
|
|
1681
|
+
if (!this._isMapRangeType(iterType)) return false;
|
|
1682
|
+
const base = iterType?.kind === "named" ? iterType.underlying : iterType;
|
|
1683
|
+
const key = base?.kind === "map" ? base.key : null;
|
|
1684
|
+
const keyBase = key?.kind === "named" ? key.underlying : key;
|
|
1685
|
+
if (
|
|
1686
|
+
keyBase?.kind !== "struct" &&
|
|
1687
|
+
keyBase?.kind !== "array" &&
|
|
1688
|
+
keyBase?.kind !== "pointer" &&
|
|
1689
|
+
!this._isIfaceType(key)
|
|
1690
|
+
)
|
|
1691
|
+
return false;
|
|
1692
|
+
this.useHelper("structKey");
|
|
1693
|
+
return true;
|
|
1694
|
+
},
|
|
1695
|
+
|
|
1696
|
+
// A map range value of array or struct type is a copy: clone it when the
|
|
1697
|
+
// body may mutate it (it otherwise aliases the stored value).
|
|
1698
|
+
_cloneMapRangeValue(init, iterType, name) {
|
|
1699
|
+
if (!name || name === "_" || !this._isMapRangeType(iterType)) return;
|
|
1700
|
+
const base = iterType?.kind === "named" ? iterType.underlying : iterType;
|
|
1701
|
+
const valType = base?.value ?? base?.elem;
|
|
1702
|
+
if (!this._aggregateBase(valType)) return;
|
|
1703
|
+
if (init.kind !== "AssignStmt" && !this._fnMutates(name)) return;
|
|
1704
|
+
this.line(`${name} = ${this._cloneJs(valType, name)};`);
|
|
1705
|
+
},
|
|
1706
|
+
|
|
754
1707
|
genRangeFor(stmt) {
|
|
755
1708
|
const init = stmt.init;
|
|
756
1709
|
const range = init.rhs[0];
|
|
757
1710
|
const lhs = init.lhs.map((e) => e.name ?? this.genExpr(e));
|
|
758
|
-
const iterType = range.expr._type;
|
|
1711
|
+
const iterType = range.expr._coreType ?? range.expr._type;
|
|
759
1712
|
const wrapField = this._namedWrapperField(iterType, range.expr);
|
|
760
1713
|
const iteree = wrapField
|
|
761
1714
|
? `${this.genExpr(range.expr)}.${wrapField}`
|
|
@@ -776,20 +1729,74 @@ export const statementGenMethods = {
|
|
|
776
1729
|
return;
|
|
777
1730
|
}
|
|
778
1731
|
|
|
779
|
-
|
|
1732
|
+
// Go ranges over a copy of an array: the range expression is evaluated
|
|
1733
|
+
// once, so writes to the array inside the body do not reach `v`.
|
|
1734
|
+
const valNode = init.lhs[1];
|
|
1735
|
+
const rangesArrayValue =
|
|
1736
|
+
valNode &&
|
|
1737
|
+
valNode.name !== "_" &&
|
|
1738
|
+
this._aggregateBase(iterType)?.kind === "array";
|
|
1739
|
+
const iterBase =
|
|
1740
|
+
iterType?.kind === "named" ? iterType.underlying : iterType;
|
|
1741
|
+
const unwrapView =
|
|
1742
|
+
valNode?.name !== "_" &&
|
|
1743
|
+
!!valNode &&
|
|
1744
|
+
iterBase?.kind === "slice" &&
|
|
1745
|
+
!isTypedArraySlice(iterType);
|
|
1746
|
+
// Ranging over an array for its indexes only does not evaluate the
|
|
1747
|
+
// array (`range p` on a nil array pointer is valid): the length is the
|
|
1748
|
+
// type's.
|
|
1749
|
+
const arrayLen = this._aggregateBase(iterType)?.size;
|
|
1750
|
+
const indexOnlyArray =
|
|
1751
|
+
(!valNode || valNode.name === "_") &&
|
|
1752
|
+
typeof arrayLen === "number" &&
|
|
1753
|
+
this._aggregateBase(iterType)?.kind === "array" &&
|
|
1754
|
+
!hasCallOrReceive(range.expr);
|
|
1755
|
+
this._genSliceRangeFor(
|
|
1756
|
+
stmt,
|
|
1757
|
+
indexOnlyArray
|
|
1758
|
+
? `({ length: ${arrayLen} })`
|
|
1759
|
+
: rangesArrayValue
|
|
1760
|
+
? this._cloneJs(iterType, iteree)
|
|
1761
|
+
: iteree,
|
|
1762
|
+
init.kind === "AssignStmt",
|
|
1763
|
+
unwrapView,
|
|
1764
|
+
);
|
|
780
1765
|
},
|
|
781
1766
|
|
|
782
1767
|
_genRangeIterExpr(iterType, iteree, lhs) {
|
|
783
1768
|
if (this._isMapRangeType(iterType)) {
|
|
784
|
-
|
|
785
|
-
return `Object.
|
|
1769
|
+
// A nil map is `null`; ranging over it runs zero iterations.
|
|
1770
|
+
if (lhs.length === 1) return `Object.keys(${iteree} ?? {})`;
|
|
1771
|
+
return `Object.entries(${iteree} ?? {})`;
|
|
786
1772
|
}
|
|
787
1773
|
this.useHelper("sliceGuard");
|
|
788
1774
|
if (lhs.length === 1) return `__s(${iteree}).keys()`;
|
|
789
1775
|
return `__s(${iteree}).entries()`;
|
|
790
1776
|
},
|
|
791
1777
|
|
|
792
|
-
|
|
1778
|
+
_emitIterTargetDispatch(targetFlag) {
|
|
1779
|
+
const labels = this._functionLabels ?? [];
|
|
1780
|
+
this.line(`if (${targetFlag}) {`);
|
|
1781
|
+
this.indented(() => {
|
|
1782
|
+
for (const lbl of labels) {
|
|
1783
|
+
this.line(`if (${targetFlag} === "break$${lbl}") break ${lbl};`);
|
|
1784
|
+
this.line(`if (${targetFlag} === "continue$${lbl}") continue ${lbl};`);
|
|
1785
|
+
}
|
|
1786
|
+
if (this._inIteratorBody) {
|
|
1787
|
+
this.line(`${this._iterTargetFlag} = ${targetFlag}; return false;`);
|
|
1788
|
+
}
|
|
1789
|
+
});
|
|
1790
|
+
this.line("}");
|
|
1791
|
+
},
|
|
1792
|
+
|
|
1793
|
+
_genIteratorLoop(
|
|
1794
|
+
itereeExpr,
|
|
1795
|
+
paramsStr,
|
|
1796
|
+
body,
|
|
1797
|
+
assignPreamble = null,
|
|
1798
|
+
loopLabel = null,
|
|
1799
|
+
) {
|
|
793
1800
|
let iteree = this.genExpr(itereeExpr);
|
|
794
1801
|
if (itereeExpr.kind === "FuncLit") {
|
|
795
1802
|
iteree = `(${iteree})`;
|
|
@@ -799,38 +1806,61 @@ export const statementGenMethods = {
|
|
|
799
1806
|
const breakFlag = `__broke${d}`;
|
|
800
1807
|
const retFlag = `__returned${d}`;
|
|
801
1808
|
const retVar = `__retVal${d}`;
|
|
1809
|
+
const targetFlag = `__target${d}`;
|
|
802
1810
|
|
|
803
1811
|
this.line("{");
|
|
804
1812
|
this.indented(() => {
|
|
805
1813
|
this.line(`let ${breakFlag} = false;`);
|
|
806
1814
|
this.line(`let ${retFlag} = false;`);
|
|
807
1815
|
this.line(`let ${retVar};`);
|
|
1816
|
+
this.line(`let ${targetFlag} = null;`);
|
|
808
1817
|
|
|
809
1818
|
this.line(`${iteree}(function(${paramsStr}) {`);
|
|
810
1819
|
this.indented(() => {
|
|
811
|
-
this.line(`if (${breakFlag}
|
|
1820
|
+
this.line(`if (${breakFlag} || ${retFlag} || ${targetFlag}) {`);
|
|
1821
|
+
this.indented(() => {
|
|
1822
|
+
if (this.strict) {
|
|
1823
|
+
this.line(
|
|
1824
|
+
`throw new Error("range function continued iteration after function for loop body returned false");`,
|
|
1825
|
+
);
|
|
1826
|
+
} else {
|
|
1827
|
+
this.line("return false;");
|
|
1828
|
+
}
|
|
1829
|
+
});
|
|
1830
|
+
this.line("}");
|
|
812
1831
|
const prev = {
|
|
813
1832
|
in: this._inIteratorBody,
|
|
814
1833
|
break: this._iterBreakFlag,
|
|
815
1834
|
ret: this._iterReturnFlag,
|
|
816
1835
|
retVar: this._iterReturnVar,
|
|
1836
|
+
target: this._iterTargetFlag,
|
|
1837
|
+
loopLabel: this._currentIterLoopLabel,
|
|
1838
|
+
loopDepth: this._iterLoopDepth,
|
|
817
1839
|
};
|
|
818
1840
|
this._inIteratorBody = true;
|
|
819
1841
|
this._iterBreakFlag = breakFlag;
|
|
820
1842
|
this._iterReturnFlag = retFlag;
|
|
821
1843
|
this._iterReturnVar = retVar;
|
|
1844
|
+
this._iterTargetFlag = targetFlag;
|
|
1845
|
+
this._currentIterLoopLabel = loopLabel;
|
|
1846
|
+
this._iterLoopDepth = 0;
|
|
822
1847
|
|
|
1848
|
+
if (assignPreamble) assignPreamble();
|
|
823
1849
|
this.genBlock(body);
|
|
824
1850
|
|
|
825
1851
|
this._inIteratorBody = prev.in;
|
|
826
1852
|
this._iterBreakFlag = prev.break;
|
|
827
1853
|
this._iterReturnFlag = prev.ret;
|
|
828
1854
|
this._iterReturnVar = prev.retVar;
|
|
1855
|
+
this._iterTargetFlag = prev.target;
|
|
1856
|
+
this._currentIterLoopLabel = prev.loopLabel;
|
|
1857
|
+
this._iterLoopDepth = prev.loopDepth;
|
|
829
1858
|
|
|
830
1859
|
this.line("return true;");
|
|
831
1860
|
});
|
|
832
1861
|
this.line("});");
|
|
833
1862
|
this.line(`if (${retFlag}) return ${retVar};`);
|
|
1863
|
+
this._emitIterTargetDispatch(targetFlag);
|
|
834
1864
|
});
|
|
835
1865
|
this.line("}");
|
|
836
1866
|
this._iterDepth--;
|
|
@@ -838,32 +1868,100 @@ export const statementGenMethods = {
|
|
|
838
1868
|
|
|
839
1869
|
genIteratorFor(stmt) {
|
|
840
1870
|
const range = stmt.init.rhs[0];
|
|
1871
|
+
const isAssign = stmt.init.kind === "AssignStmt";
|
|
841
1872
|
const lhs = stmt.init.lhs.map((e) => e.name ?? this.genExpr(e));
|
|
1873
|
+
const yieldTypes = range._yieldParams ?? [];
|
|
1874
|
+
if (isAssign) {
|
|
1875
|
+
const cbParams = lhs.map((_, i) => `__p${i}`);
|
|
1876
|
+
const params = yieldTypes.length === 0 ? "" : cbParams.join(", ");
|
|
1877
|
+
const assignPreamble = () => {
|
|
1878
|
+
for (let i = 0; i < lhs.length; i++) {
|
|
1879
|
+
if (lhs[i] !== "_") {
|
|
1880
|
+
const yType = yieldTypes[i];
|
|
1881
|
+
const isStruct =
|
|
1882
|
+
this._isStructType(yType) ||
|
|
1883
|
+
this._isStructType(stmt.init.lhs[i]?._type);
|
|
1884
|
+
if (isStruct) {
|
|
1885
|
+
this.useHelper("sclone");
|
|
1886
|
+
this.line(`${lhs[i]} = __sclone(${cbParams[i]});`);
|
|
1887
|
+
} else {
|
|
1888
|
+
this.line(`${lhs[i]} = ${cbParams[i]};`);
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
};
|
|
1893
|
+
this._genIteratorLoop(
|
|
1894
|
+
range.expr,
|
|
1895
|
+
params,
|
|
1896
|
+
stmt.body,
|
|
1897
|
+
assignPreamble,
|
|
1898
|
+
stmt.label ?? null,
|
|
1899
|
+
);
|
|
1900
|
+
return;
|
|
1901
|
+
}
|
|
842
1902
|
const cbParams = lhs.map((n, i) => (n === "_" ? `_$${i}` : n));
|
|
843
|
-
const params =
|
|
844
|
-
|
|
1903
|
+
const params = yieldTypes.length === 0 ? "" : cbParams.join(", ");
|
|
1904
|
+
const clonePreamble = () => {
|
|
1905
|
+
for (let i = 0; i < lhs.length; i++) {
|
|
1906
|
+
if (lhs[i] !== "_") {
|
|
1907
|
+
const yType = yieldTypes[i];
|
|
1908
|
+
const isStruct =
|
|
1909
|
+
this._isStructType(yType) ||
|
|
1910
|
+
this._isStructType(stmt.init.lhs[i]?._type);
|
|
1911
|
+
if (isStruct) {
|
|
1912
|
+
this.useHelper("sclone");
|
|
1913
|
+
this.line(`${cbParams[i]} = __sclone(${cbParams[i]});`);
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
};
|
|
1918
|
+
this._genIteratorLoop(
|
|
1919
|
+
range.expr,
|
|
1920
|
+
params,
|
|
1921
|
+
stmt.body,
|
|
1922
|
+
clonePreamble,
|
|
1923
|
+
stmt.label ?? null,
|
|
1924
|
+
);
|
|
845
1925
|
},
|
|
846
1926
|
|
|
847
1927
|
// 0-param iterator: for range iter { body }
|
|
848
1928
|
genIteratorForCond(stmt) {
|
|
849
|
-
this._genIteratorLoop(
|
|
1929
|
+
this._genIteratorLoop(
|
|
1930
|
+
stmt.cond.expr,
|
|
1931
|
+
"",
|
|
1932
|
+
stmt.body,
|
|
1933
|
+
null,
|
|
1934
|
+
stmt.label ?? null,
|
|
1935
|
+
);
|
|
850
1936
|
},
|
|
851
1937
|
|
|
852
1938
|
// Inline a statement as a for-init/post string (no semicolon)
|
|
853
|
-
stmtInline(stmt) {
|
|
1939
|
+
stmtInline(stmt, pre = null) {
|
|
854
1940
|
switch (stmt.kind) {
|
|
855
|
-
case "DefineStmt":
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
return
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
1941
|
+
case "DefineStmt":
|
|
1942
|
+
return this._inlineDefine(stmt, pre);
|
|
1943
|
+
case "AssignStmt":
|
|
1944
|
+
return this._withInlineSyncs(
|
|
1945
|
+
this._resliceSyncs(stmt.lhs, stmt, true),
|
|
1946
|
+
this._inlineAssign(stmt),
|
|
1947
|
+
);
|
|
1948
|
+
case "IncDecStmt": {
|
|
1949
|
+
const syncs = this._resliceSyncs([stmt.expr], stmt, true);
|
|
1950
|
+
this._markIndexLvalues([stmt.expr]);
|
|
1951
|
+
this._markDerefOpTarget(stmt.expr);
|
|
1952
|
+
if (is64BitInt(stmt.expr._type)) {
|
|
1953
|
+
const l = this.genExpr(stmt.expr);
|
|
1954
|
+
const binOp = stmt.op === "++" ? "+" : "-";
|
|
1955
|
+
return this._withInlineSyncs(
|
|
1956
|
+
syncs,
|
|
1957
|
+
`${l} = ${this._gen64BitBinary(binOp, l, "1n", stmt.expr._type)}`,
|
|
1958
|
+
);
|
|
1959
|
+
}
|
|
1960
|
+
return this._withInlineSyncs(
|
|
1961
|
+
syncs,
|
|
1962
|
+
`${this.genExpr(stmt.expr)}${stmt.op}`,
|
|
1963
|
+
);
|
|
1964
|
+
}
|
|
867
1965
|
case "ExprStmt":
|
|
868
1966
|
return this.genExpr(stmt.expr);
|
|
869
1967
|
default:
|
|
@@ -871,9 +1969,110 @@ export const statementGenMethods = {
|
|
|
871
1969
|
}
|
|
872
1970
|
},
|
|
873
1971
|
|
|
1972
|
+
_withInlineSyncs(syncs, js) {
|
|
1973
|
+
return syncs ? `${js}, ${syncs.join(", ")}` : js;
|
|
1974
|
+
},
|
|
1975
|
+
|
|
1976
|
+
_inlineDefine(stmt, pre = null) {
|
|
1977
|
+
const boxed =
|
|
1978
|
+
stmt.lhs.length === 1 &&
|
|
1979
|
+
stmt.rhs.length === 1 &&
|
|
1980
|
+
this._boxedVars.has(stmt.lhs[0].name);
|
|
1981
|
+
if (boxed)
|
|
1982
|
+
return `let ${stmt.lhs[0].name} = { value: ${pre ? pre[0] : this.genValueExpr(stmt.rhs[0])} }`;
|
|
1983
|
+
const rhs = pre ?? stmt.rhs.map((e) => this.genExpr(e));
|
|
1984
|
+
const lhs = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
|
|
1985
|
+
if (lhs.length === 1) return `let ${lhs[0]} = ${rhs[0]}`;
|
|
1986
|
+
// `for i, j := 0, 10`: one declarator per variable.
|
|
1987
|
+
if (rhs.length === lhs.length) {
|
|
1988
|
+
const decls = lhs.map((n, i) =>
|
|
1989
|
+
this._boxedVars.has(n)
|
|
1990
|
+
? `${n} = { value: ${rhs[i]} }`
|
|
1991
|
+
: `${n} = ${rhs[i]}`,
|
|
1992
|
+
);
|
|
1993
|
+
return `let ${decls.join(", ")}`;
|
|
1994
|
+
}
|
|
1995
|
+
return `let [${lhs.map((n) => (n === "_" ? "" : n)).join(", ")}] = ${rhs[0]}`;
|
|
1996
|
+
},
|
|
1997
|
+
|
|
1998
|
+
// Compound assignments that need more than the JS operator: strict
|
|
1999
|
+
// numeric wrapping, 64-bit and float32 arithmetic, and integer `/=`.
|
|
2000
|
+
_needsStrictCompoundStmt(stmt, is64) {
|
|
2001
|
+
if (stmt.op === "=") return false;
|
|
2002
|
+
const t = stmt.lhs[0]._type;
|
|
2003
|
+
return (
|
|
2004
|
+
this.strict ||
|
|
2005
|
+
is64 ||
|
|
2006
|
+
this._isFloat32(t) ||
|
|
2007
|
+
(stmt.op === "/=" && this.isIntType(t))
|
|
2008
|
+
);
|
|
2009
|
+
},
|
|
2010
|
+
|
|
2011
|
+
// The single-expression form of `_genStrictCompound` for `for` post
|
|
2012
|
+
// statements: the emitted statement is lifted back out of the output.
|
|
2013
|
+
_inlineStrictCompound(stmt, rhsJs) {
|
|
2014
|
+
const mark = this.out.length;
|
|
2015
|
+
const handled = this._genStrictCompound(
|
|
2016
|
+
stmt.lhs[0],
|
|
2017
|
+
stmt.op.slice(0, -1),
|
|
2018
|
+
rhsJs,
|
|
2019
|
+
null,
|
|
2020
|
+
null,
|
|
2021
|
+
stmt.rhs[0]?._type,
|
|
2022
|
+
);
|
|
2023
|
+
const emitted = this.out.splice(mark).map((l) => l.trim());
|
|
2024
|
+
if (!handled) return null;
|
|
2025
|
+
const js = emitted.join(" ").replace(/;$/, "");
|
|
2026
|
+
return js.startsWith("{") ? `(() => ${js})()` : js;
|
|
2027
|
+
},
|
|
2028
|
+
|
|
2029
|
+
_inlineAssign(stmt) {
|
|
2030
|
+
this._markIndexLvalues(stmt.lhs);
|
|
2031
|
+
const rhs = stmt.rhs.map((e) => this.genExpr(e));
|
|
2032
|
+
if (stmt.lhs.length === 1) {
|
|
2033
|
+
if (stmt.op !== "=" && is64BitInt(stmt.lhs[0]._type)) {
|
|
2034
|
+
const binOp = stmt.op.slice(0, -1);
|
|
2035
|
+
let r = rhs[0];
|
|
2036
|
+
if (
|
|
2037
|
+
stmt.rhs[0]?._type?.kind === "untyped" &&
|
|
2038
|
+
!r.endsWith("n") &&
|
|
2039
|
+
/^-?\d+$/.test(r)
|
|
2040
|
+
) {
|
|
2041
|
+
r = `${r}n`;
|
|
2042
|
+
}
|
|
2043
|
+
const l = this.genExpr(stmt.lhs[0]);
|
|
2044
|
+
return `${l} = ${this._gen64BitBinary(binOp, l, r, stmt.lhs[0]._type)}`;
|
|
2045
|
+
}
|
|
2046
|
+
if (this._needsStrictCompoundStmt(stmt, is64BitInt(stmt.lhs[0]._type))) {
|
|
2047
|
+
const strict = this._inlineStrictCompound(stmt, rhs[0]);
|
|
2048
|
+
if (strict) return strict;
|
|
2049
|
+
}
|
|
2050
|
+
if (stmt.op === "&^=")
|
|
2051
|
+
return `${this.genExpr(stmt.lhs[0])} &= ~(${rhs[0]})`;
|
|
2052
|
+
return `${this.genExpr(stmt.lhs[0])} ${stmt.op} ${rhs[0]}`;
|
|
2053
|
+
}
|
|
2054
|
+
// `i, j = i+1, j-1`: every right-hand side is evaluated first.
|
|
2055
|
+
const lhs = stmt.lhs.map((e) =>
|
|
2056
|
+
e.name === "_" ? "" : this._genAssignLhsExpr(e),
|
|
2057
|
+
);
|
|
2058
|
+
const r = rhs.length === 1 ? rhs[0] : `[${rhs.join(", ")}]`;
|
|
2059
|
+
return `[${lhs.join(", ")}] = ${r}`;
|
|
2060
|
+
},
|
|
2061
|
+
|
|
874
2062
|
// Interface-typed tags may hold boxed named values; cases compare with __ifeq.
|
|
875
2063
|
_switchTagAndCases(stmt) {
|
|
876
2064
|
const tag = stmt.tag ? this.genExpr(stmt.tag) : "true";
|
|
2065
|
+
if (stmt.tag && this._isStructOrArrayType(stmt.tag._type)) {
|
|
2066
|
+
// Struct and array tags compare by value, not by reference.
|
|
2067
|
+
this.useHelper("equal");
|
|
2068
|
+
this._swCounter = (this._swCounter ?? 0) + 1;
|
|
2069
|
+
const tmp = `__sw${this._swCounter}`;
|
|
2070
|
+
this.line(`const ${tmp} = ${tag};`);
|
|
2071
|
+
return {
|
|
2072
|
+
tag: "true",
|
|
2073
|
+
caseExpr: (e) => `__equal(${tmp}, ${this.genExpr(e)})`,
|
|
2074
|
+
};
|
|
2075
|
+
}
|
|
877
2076
|
if (!stmt.tag || !this._isIfaceType(stmt.tag._type))
|
|
878
2077
|
return { tag, caseExpr: (e) => this.genExpr(e) };
|
|
879
2078
|
this.useHelper("ifeq");
|
|
@@ -901,7 +2100,7 @@ export const statementGenMethods = {
|
|
|
901
2100
|
// across sibling cases (JS switch shares one block scope).
|
|
902
2101
|
this.line("{");
|
|
903
2102
|
this.indented(() => {
|
|
904
|
-
|
|
2103
|
+
this.genBlock(c);
|
|
905
2104
|
// Go doesn't fall through by default — add break unless last stmt is return/break
|
|
906
2105
|
const last = c.stmts[c.stmts.length - 1];
|
|
907
2106
|
if (
|
|
@@ -920,10 +2119,18 @@ export const statementGenMethods = {
|
|
|
920
2119
|
if (stmt.init) {
|
|
921
2120
|
this.line("{");
|
|
922
2121
|
this.indented(() => {
|
|
923
|
-
this.
|
|
924
|
-
genBody();
|
|
2122
|
+
this._genScopedInit(stmt.init, genBody);
|
|
925
2123
|
});
|
|
926
2124
|
this.line("}");
|
|
2125
|
+
} else if (
|
|
2126
|
+
stmt.tag &&
|
|
2127
|
+
(this._isStructOrArrayType(stmt.tag._type) ||
|
|
2128
|
+
this._isIfaceType(stmt.tag._type))
|
|
2129
|
+
) {
|
|
2130
|
+
// The tag temporary is a `const`, which cannot directly follow a label.
|
|
2131
|
+
this.line("{");
|
|
2132
|
+
this.indented(genBody);
|
|
2133
|
+
this.line("}");
|
|
927
2134
|
} else {
|
|
928
2135
|
genBody();
|
|
929
2136
|
}
|
|
@@ -934,7 +2141,13 @@ export const statementGenMethods = {
|
|
|
934
2141
|
this.indented(() => {
|
|
935
2142
|
const val = this.genExpr(stmt.expr);
|
|
936
2143
|
this.line(`const __tsw = ${val};`);
|
|
937
|
-
|
|
2144
|
+
// The binding is declared per clause, after the switch expression
|
|
2145
|
+
// was evaluated (`switch x := any(x).(type)` reads the outer `x`).
|
|
2146
|
+
const boxedBind = stmt.assign && this._boxedVars.has(stmt.assign);
|
|
2147
|
+
const bind = (valueJs) =>
|
|
2148
|
+
this.line(
|
|
2149
|
+
`let ${stmt.assign} = ${boxedBind ? `{ value: ${valueJs} }` : valueJs};`,
|
|
2150
|
+
);
|
|
938
2151
|
|
|
939
2152
|
let first = true;
|
|
940
2153
|
let hasDefault = false;
|
|
@@ -949,13 +2162,16 @@ export const statementGenMethods = {
|
|
|
949
2162
|
.join(" || ");
|
|
950
2163
|
this.line(`${first ? "if" : "else if"} (${cond}) {`);
|
|
951
2164
|
this.indented(() => {
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
2165
|
+
if (stmt.assign) {
|
|
2166
|
+
const only = c.types.length === 1 ? c.types[0] : null;
|
|
2167
|
+
// A single named non-struct case binds the unboxed value.
|
|
2168
|
+
if (only && this._assertedValueJs(only, "__tsw") !== "__tsw")
|
|
2169
|
+
bind(this._assertedValueJs(only, "__tsw"));
|
|
2170
|
+
// An array case binds a copy of the boxed value.
|
|
2171
|
+
else if (only && this._typeFromNode(only)?.kind === "array")
|
|
2172
|
+
bind(this._cloneJs(this._typeFromNode(only), "__tsw"));
|
|
2173
|
+
else bind("__tsw");
|
|
2174
|
+
}
|
|
959
2175
|
for (const s of c.stmts) this.genStmt(s);
|
|
960
2176
|
});
|
|
961
2177
|
this.line("}");
|
|
@@ -964,6 +2180,7 @@ export const statementGenMethods = {
|
|
|
964
2180
|
if (hasDefault) {
|
|
965
2181
|
this.line(first ? "{" : "else {");
|
|
966
2182
|
this.indented(() => {
|
|
2183
|
+
if (stmt.assign) bind("__tsw");
|
|
967
2184
|
for (const s of hasDefault.stmts) this.genStmt(s);
|
|
968
2185
|
});
|
|
969
2186
|
this.line("}");
|