gofront 1.8.0 → 1.9.1
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 +6 -5
- package/src/backend/js/expressions.js +1157 -408
- package/src/backend/js/index.js +327 -153
- package/src/backend/js/natives.js +383 -0
- package/src/backend/js/runtime.js +559 -2552
- package/src/backend/js/statements.js +670 -158
- package/src/backend/js/stdlib/index.js +45 -93
- package/src/backend/js/templ.js +32 -45
- package/src/backend/js/type-info.js +298 -19
- package/src/backend/shared/fixed-float.js +20 -0
- package/src/backend/shared/fmt-types.js +91 -170
- package/src/backend/shared/host-unicode.js +36 -0
- package/src/backend/wasm/boundary.js +490 -965
- package/src/backend/wasm/emit-builtins.js +884 -256
- package/src/backend/wasm/emit-exprs.js +185 -77
- package/src/backend/wasm/emit-maps.js +17 -367
- package/src/backend/wasm/emit-shared.js +4 -1
- package/src/backend/wasm/emit-stmts.js +524 -240
- package/src/backend/wasm/emit.js +176 -38
- package/src/backend/wasm/globals.js +48 -8
- package/src/backend/wasm/glue.js +2 -4
- package/src/backend/wasm/index.js +44 -530
- package/src/backend/wasm/method-expr.js +12 -1
- package/src/backend/wasm/module-emitter.js +299 -770
- package/src/backend/wasm/monomorph.js +139 -8
- package/src/backend/wasm/natives.js +600 -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 +97 -20
- package/src/backend/wasm/variadic.js +9 -4
- package/src/cli/cli-core.js +5 -1
- package/src/cli/test-runner.js +115 -342
- package/src/compiler.js +113 -130
- package/src/lexer/lexer.js +106 -11
- package/src/lexer/templ-lexer.js +61 -18
- package/src/lexer/tokens.js +1 -0
- package/src/link/stdlib.js +355 -132
- package/src/link/type-names.js +204 -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 +62 -5
- package/src/lower/errors-as.js +196 -0
- package/src/lower/fmt-formatters.js +929 -0
- package/src/lower/functions.js +225 -7
- package/src/lower/gen-types.js +293 -0
- package/src/lower/index.js +14 -2
- package/src/lower/json-codecs.js +334 -245
- 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 +30 -5
- package/src/parser/expressions.js +111 -57
- package/src/parser/statements.js +108 -0
- package/src/parser/templ.js +44 -5
- package/src/parser/types.js +12 -0
- package/src/resolver.js +1 -2
- package/src/typechecker/assignability.js +214 -19
- package/src/typechecker/const-fold.js +70 -27
- package/src/typechecker/constants.js +156 -8
- package/src/typechecker/expressions.js +490 -108
- package/src/typechecker/index.js +346 -42
- package/src/typechecker/init-order.js +136 -40
- package/src/typechecker/operand-text.js +41 -0
- package/src/typechecker/resolve.js +490 -65
- package/src/typechecker/statements.js +288 -27
- 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 +287 -575
- 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,16 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
2
|
bodyRedeclares,
|
|
3
3
|
forEachChild,
|
|
4
|
+
hasCallOrReceive,
|
|
4
5
|
isIntRangeType,
|
|
5
6
|
isMapRangeType,
|
|
6
7
|
isRangeFor,
|
|
7
8
|
isSimpleIntRange,
|
|
8
9
|
isStringRangeType,
|
|
9
10
|
loopVarsCapturedInLoop,
|
|
10
|
-
nodeAssigns,
|
|
11
11
|
someChild,
|
|
12
12
|
} from "../../lower/index.js";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
is64BitInt,
|
|
15
|
+
isComplex,
|
|
16
|
+
isComplex64,
|
|
17
|
+
isTypedArraySlice,
|
|
18
|
+
} from "../../typechecker/types.js";
|
|
14
19
|
|
|
15
20
|
/** @typedef {import('./index.js').CodeGen} CodeGen */
|
|
16
21
|
|
|
@@ -124,6 +129,20 @@ const isReusableIndex = (e, vars, boxed) => {
|
|
|
124
129
|
}
|
|
125
130
|
};
|
|
126
131
|
|
|
132
|
+
// Adds the name of every identifier under `n` to `into`.
|
|
133
|
+
const collectIdentNames = (n, into) => {
|
|
134
|
+
if (Array.isArray(n)) {
|
|
135
|
+
for (const c of n) collectIdentNames(c, into);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (!n || typeof n !== "object") return;
|
|
139
|
+
if (n.kind === "Ident") into.add(n.name);
|
|
140
|
+
someChild(n, (c) => {
|
|
141
|
+
collectIdentNames(c, into);
|
|
142
|
+
return false;
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
|
|
127
146
|
// Method-name dispatch for genStmt — string values add no static call edges.
|
|
128
147
|
const STMT_GEN_DELEGATE = {
|
|
129
148
|
VarDecl: "genVarDecl",
|
|
@@ -135,19 +154,27 @@ const STMT_GEN_DELEGATE = {
|
|
|
135
154
|
SwitchStmt: "genSwitch",
|
|
136
155
|
TypeSwitchStmt: "genTypeSwitch",
|
|
137
156
|
BranchStmt: "_genBranchStmt",
|
|
157
|
+
TemplMount: "_genTemplMountStmt",
|
|
138
158
|
};
|
|
139
159
|
|
|
140
160
|
/** @type {ThisType<CodeGen>} */
|
|
141
161
|
export const statementGenMethods = {
|
|
142
162
|
genBlock(block) {
|
|
143
163
|
let nested = 0;
|
|
164
|
+
const earlier = new Set();
|
|
144
165
|
for (const stmt of block.stmts) {
|
|
145
166
|
if (this._definesShadowedRead(stmt)) {
|
|
146
167
|
this._genShadowingDefine(stmt);
|
|
147
168
|
nested++;
|
|
169
|
+
} else if (this._definesAfterOuterUse(stmt, earlier)) {
|
|
170
|
+
this.line("{");
|
|
171
|
+
this.indent++;
|
|
172
|
+
nested++;
|
|
173
|
+
this.genStmt(stmt);
|
|
148
174
|
} else {
|
|
149
175
|
this.genStmt(stmt);
|
|
150
176
|
}
|
|
177
|
+
collectIdentNames(stmt, earlier);
|
|
151
178
|
}
|
|
152
179
|
for (; nested > 0; nested--) {
|
|
153
180
|
this.indent--;
|
|
@@ -155,11 +182,20 @@ export const statementGenMethods = {
|
|
|
155
182
|
}
|
|
156
183
|
},
|
|
157
184
|
|
|
185
|
+
// `err = nil; _, err := g()` where `err` is an outer variable used earlier
|
|
186
|
+
// in this block: JS `let` would put those uses in its temporal dead zone,
|
|
187
|
+
// so the rest of the block moves into a nested block.
|
|
188
|
+
_definesAfterOuterUse(stmt, earlier) {
|
|
189
|
+
if (stmt.kind !== "DefineStmt" || earlier.size === 0) return false;
|
|
190
|
+
return stmt.lhs.some(
|
|
191
|
+
(l) => !l._redecl && l.name !== "_" && earlier.has(l.name),
|
|
192
|
+
);
|
|
193
|
+
},
|
|
194
|
+
|
|
158
195
|
// `x := f(x)` declaring a new `x` that its own right-hand side reads (the
|
|
159
196
|
// outer `x`): JS `let` would put the read in its temporal dead zone.
|
|
160
197
|
_definesShadowedRead(stmt) {
|
|
161
|
-
if (stmt.kind !== "DefineStmt"
|
|
162
|
-
return false;
|
|
198
|
+
if (stmt.kind !== "DefineStmt") return false;
|
|
163
199
|
const names = new Set(
|
|
164
200
|
stmt.lhs.filter((l) => !l._redecl && l.name !== "_").map((l) => l.name),
|
|
165
201
|
);
|
|
@@ -173,9 +209,35 @@ export const statementGenMethods = {
|
|
|
173
209
|
return stmt.rhs.some(reads);
|
|
174
210
|
},
|
|
175
211
|
|
|
212
|
+
// Emits an `if`/`switch` init statement, then `body`; an init that reads the
|
|
213
|
+
// name it declares goes through `_genShadowingDefine`.
|
|
214
|
+
_genScopedInit(init, body) {
|
|
215
|
+
const shadowing = this._definesShadowedRead(init);
|
|
216
|
+
if (shadowing) this._genShadowingDefine(init);
|
|
217
|
+
else this.genStmt(init);
|
|
218
|
+
body();
|
|
219
|
+
if (shadowing) {
|
|
220
|
+
this.indent--;
|
|
221
|
+
this.line("}");
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
|
|
176
225
|
// Evaluates the right-hand side in the enclosing scope, then opens a nested
|
|
177
226
|
// block (closed at the end of the enclosing block) that declares the names.
|
|
178
227
|
_genShadowingDefine(stmt) {
|
|
228
|
+
if (this._isCommaOkMapExpr(stmt, stmt.rhs[0])) {
|
|
229
|
+
const node = stmt.rhs[0];
|
|
230
|
+
this._tmpCounter = (this._tmpCounter ?? 0) + 1;
|
|
231
|
+
const m = `__sm${this._tmpCounter}`;
|
|
232
|
+
const k = `__sk${this._tmpCounter}`;
|
|
233
|
+
this.line(
|
|
234
|
+
`const ${m} = ${this.genExpr(node.expr)}, ${k} = ${this.genExpr(node.index)};`,
|
|
235
|
+
);
|
|
236
|
+
this.line("{");
|
|
237
|
+
this.indent++;
|
|
238
|
+
this._genCommaOkMapIndex(stmt, node, true, { map: m, key: k });
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
179
241
|
const rhs = this._defineRhs(stmt);
|
|
180
242
|
this._tmpCounter = (this._tmpCounter ?? 0) + 1;
|
|
181
243
|
const tmp = `__t${this._tmpCounter}`;
|
|
@@ -189,24 +251,44 @@ export const statementGenMethods = {
|
|
|
189
251
|
);
|
|
190
252
|
},
|
|
191
253
|
|
|
254
|
+
// `m[k]` of a non-strict comma-ok: a missing key reads as the scalar zero
|
|
255
|
+
// value, not `undefined`.
|
|
256
|
+
_nonStrictMapRead(mapExpr, keyExpr, valType, copyVal) {
|
|
257
|
+
const zero = valType ? this.zeroValueForType(valType) : "";
|
|
258
|
+
const read = `${mapExpr}[${keyExpr}]`;
|
|
259
|
+
if (/^(0n?|""|false)$/.test(zero)) return `(${read} ?? ${zero})`;
|
|
260
|
+
return copyVal(
|
|
261
|
+
this._aggregateBase(valType) ? `(${read} ?? ${zero})` : read,
|
|
262
|
+
);
|
|
263
|
+
},
|
|
264
|
+
|
|
192
265
|
// Shared helper for comma-ok map index in both DefineStmt and AssignStmt.
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
266
|
+
// `pre` carries the map and key already evaluated (a shadowing declaration
|
|
267
|
+
// reads them before its names exist).
|
|
268
|
+
_genCommaOkMapIndex(stmt, rhsNode, isDefine, pre = null) {
|
|
269
|
+
const mapExpr = pre?.map ?? this.genExpr(rhsNode.expr);
|
|
270
|
+
const keyExpr = pre?.key ?? this.genExpr(rhsNode.index);
|
|
196
271
|
const [vName, okName] = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
|
|
197
|
-
|
|
198
|
-
//
|
|
272
|
+
// A name `:=` reuses is assigned, not declared again; an address-taken
|
|
273
|
+
// variable lives in a `{ value }` box.
|
|
274
|
+
const bind = (l, name, js) => {
|
|
275
|
+
const boxed = this._boxedVars.has(name);
|
|
276
|
+
if (isDefine && !l._redecl)
|
|
277
|
+
return `let ${name} = ${boxed ? `{ value: ${js} }` : js};`;
|
|
278
|
+
return `${name}${boxed ? ".value" : ""} = ${js};`;
|
|
279
|
+
};
|
|
280
|
+
const [vL, okL] = stmt.lhs;
|
|
281
|
+
// A struct or array map value is a copy, not the stored value.
|
|
199
282
|
const valType = rhsNode._mapValueType;
|
|
200
283
|
const copyVal = (js) =>
|
|
201
|
-
this._aggregateBase(valType)
|
|
202
|
-
? this._cloneJs(valType, js)
|
|
203
|
-
: js;
|
|
284
|
+
this._aggregateBase(valType) ? this._cloneJs(valType, js) : js;
|
|
204
285
|
if (isDefine && !this.strict) {
|
|
205
286
|
if (vName !== "_") {
|
|
206
|
-
this.
|
|
287
|
+
const val = this._nonStrictMapRead(mapExpr, keyExpr, valType, copyVal);
|
|
288
|
+
this.line(bind(vL, vName, val));
|
|
207
289
|
}
|
|
208
290
|
if (okName !== "_") {
|
|
209
|
-
this.line(
|
|
291
|
+
this.line(bind(okL, okName, `(${keyExpr}) in ${mapExpr}`));
|
|
210
292
|
}
|
|
211
293
|
return;
|
|
212
294
|
}
|
|
@@ -219,11 +301,15 @@ export const statementGenMethods = {
|
|
|
219
301
|
this.line(`const ${mTmp} = ${mapExpr}, ${kTmp} = ${keyExpr};`);
|
|
220
302
|
if (vName !== "_") {
|
|
221
303
|
this.line(
|
|
222
|
-
|
|
304
|
+
bind(
|
|
305
|
+
vL,
|
|
306
|
+
vName,
|
|
307
|
+
`(${mTmp} && (${kTmp} in ${mTmp})) ? ${copyVal(`${mTmp}[${kTmp}]`)} : ${zero}`,
|
|
308
|
+
),
|
|
223
309
|
);
|
|
224
310
|
}
|
|
225
311
|
if (okName !== "_") {
|
|
226
|
-
this.line(
|
|
312
|
+
this.line(bind(okL, okName, `Boolean(${mTmp} && (${kTmp} in ${mTmp}))`));
|
|
227
313
|
}
|
|
228
314
|
},
|
|
229
315
|
|
|
@@ -240,8 +326,11 @@ export const statementGenMethods = {
|
|
|
240
326
|
: null;
|
|
241
327
|
switch (stmt.kind) {
|
|
242
328
|
case "TypeDecl":
|
|
243
|
-
|
|
244
|
-
|
|
329
|
+
{
|
|
330
|
+
const start = this.out.length;
|
|
331
|
+
this.genTypeDeclWithMethods(stmt, []);
|
|
332
|
+
this._hoistLocalClass(stmt, start);
|
|
333
|
+
}
|
|
245
334
|
break;
|
|
246
335
|
case "IncDecStmt": {
|
|
247
336
|
this._markIndexLvalues([stmt.expr]);
|
|
@@ -257,28 +346,47 @@ export const statementGenMethods = {
|
|
|
257
346
|
: this.genExpr(stmt.expr.expr);
|
|
258
347
|
const idx = this.genExpr(stmt.expr.index);
|
|
259
348
|
const op = stmt.op === "++" ? "+" : "-";
|
|
349
|
+
const is64Val = is64BitInt(stmt.expr._mapValueType);
|
|
350
|
+
const delta = is64Val ? "1n" : "1";
|
|
260
351
|
if (
|
|
261
352
|
this._hasCallExpr(stmt.expr.expr) ||
|
|
262
353
|
this._hasCallExpr(stmt.expr.index)
|
|
263
354
|
) {
|
|
355
|
+
const rhs = is64Val
|
|
356
|
+
? this._gen64BitBinary(
|
|
357
|
+
op,
|
|
358
|
+
`(__m[__k] ?? ${zero})`,
|
|
359
|
+
delta,
|
|
360
|
+
stmt.expr._mapValueType,
|
|
361
|
+
)
|
|
362
|
+
: `(__m[__k] ?? ${zero}) ${op} 1`;
|
|
264
363
|
this.line(
|
|
265
|
-
`((__m, __k) => { ${this._genMapWriteBase("__m")}[__k] =
|
|
364
|
+
`((__m, __k) => { ${this._genMapWriteBase("__m")}[__k] = ${rhs}; })(${base}, ${idx});`,
|
|
266
365
|
srcLine,
|
|
267
366
|
);
|
|
268
367
|
} else {
|
|
368
|
+
const rhs = is64Val
|
|
369
|
+
? this._gen64BitBinary(
|
|
370
|
+
op,
|
|
371
|
+
`(${base}[${idx}] ?? ${zero})`,
|
|
372
|
+
delta,
|
|
373
|
+
stmt.expr._mapValueType,
|
|
374
|
+
)
|
|
375
|
+
: `(${base}[${idx}] ?? ${zero}) ${op} 1`;
|
|
269
376
|
this.line(
|
|
270
|
-
`${this._genMapWriteBase(base)}[${idx}] =
|
|
377
|
+
`${this._genMapWriteBase(base)}[${idx}] = ${rhs};`,
|
|
271
378
|
srcLine,
|
|
272
379
|
);
|
|
273
380
|
}
|
|
274
381
|
break;
|
|
275
382
|
}
|
|
383
|
+
const is64 = is64BitInt(stmt.expr._type);
|
|
276
384
|
if (
|
|
277
|
-
this.strict &&
|
|
385
|
+
(this.strict || is64 || this._isFloat32(stmt.expr._type)) &&
|
|
278
386
|
this._genStrictCompound(
|
|
279
387
|
stmt.expr,
|
|
280
388
|
stmt.op === "++" ? "+" : "-",
|
|
281
|
-
"1",
|
|
389
|
+
is64 ? "1n" : "1",
|
|
282
390
|
srcLine,
|
|
283
391
|
)
|
|
284
392
|
)
|
|
@@ -293,14 +401,7 @@ export const statementGenMethods = {
|
|
|
293
401
|
if (stmt.init) {
|
|
294
402
|
this.line("{");
|
|
295
403
|
this.indented(() => {
|
|
296
|
-
|
|
297
|
-
if (shadowing) this._genShadowingDefine(stmt.init);
|
|
298
|
-
else this.genStmt(stmt.init);
|
|
299
|
-
this._genIf(stmt);
|
|
300
|
-
if (shadowing) {
|
|
301
|
-
this.indent--;
|
|
302
|
-
this.line("}");
|
|
303
|
-
}
|
|
404
|
+
this._genScopedInit(stmt.init, () => this._genIf(stmt));
|
|
304
405
|
});
|
|
305
406
|
this.line("}");
|
|
306
407
|
} else {
|
|
@@ -308,15 +409,41 @@ export const statementGenMethods = {
|
|
|
308
409
|
}
|
|
309
410
|
break;
|
|
310
411
|
case "DeferStmt":
|
|
311
|
-
|
|
412
|
+
// A deferred native call runs the Go body.
|
|
413
|
+
this._withoutNatives(() =>
|
|
414
|
+
this.line(`__defers.push(() => { ${this.genExpr(stmt.call)}; });`),
|
|
415
|
+
);
|
|
312
416
|
break;
|
|
313
|
-
case "LabeledStmt":
|
|
314
|
-
|
|
315
|
-
|
|
417
|
+
case "LabeledStmt": {
|
|
418
|
+
// Stacked labels (`a: b: for ...`) all stay attached to the loop.
|
|
419
|
+
const labels = [stmt.label];
|
|
420
|
+
let body = stmt.body;
|
|
421
|
+
while (body?.kind === "LabeledStmt") {
|
|
422
|
+
labels.push(body.label);
|
|
423
|
+
body = body.body;
|
|
316
424
|
}
|
|
317
|
-
|
|
318
|
-
|
|
425
|
+
if (body?.kind === "ForStmt") {
|
|
426
|
+
body.label = labels[labels.length - 1];
|
|
427
|
+
body.labels = labels;
|
|
428
|
+
}
|
|
429
|
+
const hoisted = this._hoistForInit(body);
|
|
430
|
+
for (const label of labels) this.line(`${label}:`);
|
|
431
|
+
// Labels in scope of the JS function being emitted (see
|
|
432
|
+
// `_emitIterTargetDispatch`).
|
|
433
|
+
const prevLabels = this._jsLabels ?? [];
|
|
434
|
+
const loop = body?.kind === "ForStmt";
|
|
435
|
+
this._jsLabels = [
|
|
436
|
+
...prevLabels,
|
|
437
|
+
...labels.map((name) => ({ name, loop })),
|
|
438
|
+
];
|
|
439
|
+
try {
|
|
440
|
+
this.genStmt(body);
|
|
441
|
+
} finally {
|
|
442
|
+
this._jsLabels = prevLabels;
|
|
443
|
+
}
|
|
444
|
+
if (hoisted) this._closeHoistedForInit();
|
|
319
445
|
break;
|
|
446
|
+
}
|
|
320
447
|
case "Block":
|
|
321
448
|
this.line("{");
|
|
322
449
|
this.indented(() => this.genBlock(stmt));
|
|
@@ -656,6 +783,14 @@ export const statementGenMethods = {
|
|
|
656
783
|
if (syncs) for (const s of syncs) this.line(`${s};`);
|
|
657
784
|
},
|
|
658
785
|
|
|
786
|
+
// An untyped constant operand of a 64-bit compound assignment is a Number
|
|
787
|
+
// in the output; the operator needs it as a BigInt.
|
|
788
|
+
_untypedTo64(rhsVal, rhsNode) {
|
|
789
|
+
if (rhsNode?._type?.kind !== "untyped" || rhsVal.endsWith("n"))
|
|
790
|
+
return rhsVal;
|
|
791
|
+
return /^-?\d+$/.test(rhsVal) ? `${rhsVal}n` : `BigInt(${rhsVal})`;
|
|
792
|
+
},
|
|
793
|
+
|
|
659
794
|
_genAssignStmt(stmt) {
|
|
660
795
|
this._markIndexLvalues(stmt.lhs);
|
|
661
796
|
if (stmt.op !== "=") for (const l of stmt.lhs) this._markDerefOpTarget(l);
|
|
@@ -695,6 +830,10 @@ export const statementGenMethods = {
|
|
|
695
830
|
this._genStableWholeAssign(stmt);
|
|
696
831
|
return;
|
|
697
832
|
}
|
|
833
|
+
if (this._isStableTupleAssign(stmt)) {
|
|
834
|
+
this._genStableTupleAssign(stmt);
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
698
837
|
const paired = stmt.rhs.length === stmt.lhs.length && stmt.op === "=";
|
|
699
838
|
const rhs = stmt.rhs.map((e, i) =>
|
|
700
839
|
paired && stmt.lhs[i].name !== "_"
|
|
@@ -724,29 +863,51 @@ export const statementGenMethods = {
|
|
|
724
863
|
: this.genExpr(stmt.lhs[0].expr);
|
|
725
864
|
const idx = this.genExpr(stmt.lhs[0].index);
|
|
726
865
|
const binOp = stmt.op.slice(0, -1);
|
|
866
|
+
const is64Val = is64BitInt(stmt.lhs[0]._mapValueType);
|
|
867
|
+
let rhsVal = active[0].r;
|
|
868
|
+
if (is64Val) rhsVal = this._untypedTo64(rhsVal, stmt.rhs[0]);
|
|
869
|
+
const rhsExpr = is64Val
|
|
870
|
+
? this._gen64BitBinary(
|
|
871
|
+
binOp,
|
|
872
|
+
`(__m[__k] ?? ${zero})`,
|
|
873
|
+
rhsVal,
|
|
874
|
+
stmt.lhs[0]._mapValueType,
|
|
875
|
+
)
|
|
876
|
+
: `(__m[__k] ?? ${zero}) ${binOp === "&^" ? "& ~" : binOp} (${rhsVal})`;
|
|
877
|
+
const rhsExprDirect = is64Val
|
|
878
|
+
? this._gen64BitBinary(
|
|
879
|
+
binOp,
|
|
880
|
+
`(${base}[${idx}] ?? ${zero})`,
|
|
881
|
+
rhsVal,
|
|
882
|
+
stmt.lhs[0]._mapValueType,
|
|
883
|
+
)
|
|
884
|
+
: `(${base}[${idx}] ?? ${zero}) ${binOp === "&^" ? "& ~" : binOp} (${rhsVal})`;
|
|
727
885
|
if (
|
|
728
886
|
this._hasCallExpr(stmt.lhs[0].expr) ||
|
|
729
887
|
this._hasCallExpr(stmt.lhs[0].index)
|
|
730
888
|
) {
|
|
731
889
|
this.line(
|
|
732
|
-
`((__m, __k) => { ${this._genMapWriteBase("__m")}[__k] =
|
|
890
|
+
`((__m, __k) => { ${this._genMapWriteBase("__m")}[__k] = ${rhsExpr}; })(${base}, ${idx});`,
|
|
733
891
|
);
|
|
734
892
|
} else {
|
|
735
893
|
this.line(
|
|
736
|
-
`${this._genMapWriteBase(base)}[${idx}] =
|
|
894
|
+
`${this._genMapWriteBase(base)}[${idx}] = ${rhsExprDirect};`,
|
|
737
895
|
);
|
|
738
896
|
}
|
|
739
897
|
return;
|
|
740
898
|
}
|
|
899
|
+
const is64 = is64BitInt(stmt.lhs[0]._type);
|
|
900
|
+
let rhsVal = active[0].r;
|
|
901
|
+
if (is64) rhsVal = this._untypedTo64(rhsVal, stmt.rhs[0]);
|
|
741
902
|
if (
|
|
742
|
-
this.
|
|
743
|
-
stmt.op !== "=" &&
|
|
903
|
+
this._needsStrictCompoundStmt(stmt, is64) &&
|
|
744
904
|
this._genStrictCompound(
|
|
745
905
|
stmt.lhs[0],
|
|
746
906
|
stmt.op.slice(0, -1),
|
|
747
|
-
|
|
907
|
+
rhsVal,
|
|
748
908
|
null,
|
|
749
909
|
active[0].l,
|
|
910
|
+
stmt.rhs[0]?._type,
|
|
750
911
|
)
|
|
751
912
|
) {
|
|
752
913
|
return;
|
|
@@ -755,7 +916,8 @@ export const statementGenMethods = {
|
|
|
755
916
|
active[0].l.startsWith("{") && !active[0].l.startsWith("({")
|
|
756
917
|
? `(${active[0].l})`
|
|
757
918
|
: active[0].l;
|
|
758
|
-
this.line(`${targetL}
|
|
919
|
+
if (stmt.op === "&^=") this.line(`${targetL} &= ~(${rhsVal});`);
|
|
920
|
+
else this.line(`${targetL} ${stmt.op} ${rhsVal};`);
|
|
759
921
|
} else if (hoisted) {
|
|
760
922
|
this.line("{");
|
|
761
923
|
this.indented(() => {
|
|
@@ -786,23 +948,28 @@ export const statementGenMethods = {
|
|
|
786
948
|
((n.kind === "Ident" && assigned.has(n.name)) || someChild(n, mentions));
|
|
787
949
|
const lines = [];
|
|
788
950
|
const undo = [];
|
|
789
|
-
|
|
951
|
+
const visit = (l) => {
|
|
790
952
|
let keys;
|
|
791
953
|
if (l.kind === "IndexExpr") keys = ["expr", "index"];
|
|
792
954
|
else if (l.kind === "SelectorExpr" && !l._isMethodExpr) keys = ["expr"];
|
|
793
|
-
else
|
|
955
|
+
else return;
|
|
794
956
|
for (const key of keys) {
|
|
795
957
|
const operand = l[key];
|
|
796
958
|
if (!mentions(operand)) continue;
|
|
797
|
-
// A struct or array value names the variable's storage in Go
|
|
798
|
-
|
|
959
|
+
// A struct or array value names the variable's storage in Go: hoist
|
|
960
|
+
// the operands inside it (`ps[n].A`) instead of the value.
|
|
961
|
+
if (key === "expr" && this._aggregateBase(operand._type)) {
|
|
962
|
+
visit(operand);
|
|
963
|
+
continue;
|
|
964
|
+
}
|
|
799
965
|
this._tmpCounter = (this._tmpCounter ?? 0) + 1;
|
|
800
966
|
const name = `__h${this._tmpCounter}`;
|
|
801
967
|
lines.push(`const ${name} = ${this.genExpr(operand)};`);
|
|
802
968
|
undo.push([l, key, operand]);
|
|
803
969
|
l[key] = { kind: "Ident", name, _type: operand._type };
|
|
804
970
|
}
|
|
805
|
-
}
|
|
971
|
+
};
|
|
972
|
+
for (const l of stmt.lhs) visit(l);
|
|
806
973
|
if (lines.length === 0) return null;
|
|
807
974
|
return {
|
|
808
975
|
lines,
|
|
@@ -814,7 +981,7 @@ export const statementGenMethods = {
|
|
|
814
981
|
|
|
815
982
|
_genCommaOkTypeAssert(stmt) {
|
|
816
983
|
const val = this.genExpr(stmt.rhs[0]);
|
|
817
|
-
const [vName, okName] = stmt.lhs.map((e) =>
|
|
984
|
+
const [vName, okName] = stmt.lhs.map((e) => this._genAssignLhsExpr(e));
|
|
818
985
|
this._tmpCounter = (this._tmpCounter ?? 0) + 1;
|
|
819
986
|
const tmp = `__ta${this._tmpCounter}`;
|
|
820
987
|
this.line(`const ${tmp} = ${val};`);
|
|
@@ -828,7 +995,13 @@ export const statementGenMethods = {
|
|
|
828
995
|
_isStableWholeAssign(stmt) {
|
|
829
996
|
if (stmt.op !== "=" || stmt.lhs.length !== 1 || stmt.rhs.length !== 1)
|
|
830
997
|
return false;
|
|
831
|
-
|
|
998
|
+
return this._isStableTarget(stmt.lhs[0], stmt.rhs[0]);
|
|
999
|
+
},
|
|
1000
|
+
|
|
1001
|
+
// `l = rhs` writes into the existing storage of `l` (see above). When the
|
|
1002
|
+
// program takes `&xs[i]` anywhere, an element of a slice does too: any
|
|
1003
|
+
// callee may hold the pointer.
|
|
1004
|
+
_isStableTarget(l, rhs) {
|
|
832
1005
|
if (l.kind === "IndexExpr" && l._mapValueType) return false;
|
|
833
1006
|
if (
|
|
834
1007
|
l.kind !== "Ident" &&
|
|
@@ -837,15 +1010,59 @@ export const statementGenMethods = {
|
|
|
837
1010
|
)
|
|
838
1011
|
return false;
|
|
839
1012
|
if (l.kind === "Ident" && l.name === "_") return false;
|
|
840
|
-
if (
|
|
841
|
-
!this._aggregateBase(l._type) ||
|
|
842
|
-
!this._aggregateBase(stmt.rhs[0]._type)
|
|
843
|
-
)
|
|
1013
|
+
if (!this._aggregateBase(l._type) || !this._aggregateBase(rhs._type))
|
|
844
1014
|
return false;
|
|
1015
|
+
if (this._elemAddrTaken && this._isSliceElemTarget(l)) return true;
|
|
845
1016
|
const root = this._rootIdentName(l);
|
|
846
1017
|
return root !== null && this._fnMutates(root, true);
|
|
847
1018
|
},
|
|
848
1019
|
|
|
1020
|
+
// `xs[i]` on a plain slice (not a typed array, array or map).
|
|
1021
|
+
_isSliceElemTarget(l) {
|
|
1022
|
+
if (l.kind !== "IndexExpr" || l._mapValueType) return false;
|
|
1023
|
+
const u =
|
|
1024
|
+
l.expr._type?.kind === "named" ? l.expr._type.underlying : l.expr._type;
|
|
1025
|
+
return u?.kind === "slice" && !isTypedArraySlice(l.expr._type);
|
|
1026
|
+
},
|
|
1027
|
+
|
|
1028
|
+
// `a, b = x, y` where a target is stored in place: every right-hand value
|
|
1029
|
+
// is evaluated (and copied) first, then each target is assigned in order.
|
|
1030
|
+
_isStableTupleAssign(stmt) {
|
|
1031
|
+
return (
|
|
1032
|
+
stmt.op === "=" &&
|
|
1033
|
+
stmt.lhs.length > 1 &&
|
|
1034
|
+
stmt.rhs.length === stmt.lhs.length &&
|
|
1035
|
+
stmt.lhs.some((l, i) => this._isStableTarget(l, stmt.rhs[i]))
|
|
1036
|
+
);
|
|
1037
|
+
},
|
|
1038
|
+
|
|
1039
|
+
_genStableTupleAssign(stmt) {
|
|
1040
|
+
const hoisted = this._hoistLhsOperands(stmt);
|
|
1041
|
+
const stable = stmt.lhs.map((l, i) => this._isStableTarget(l, stmt.rhs[i]));
|
|
1042
|
+
this.line("{");
|
|
1043
|
+
this.indented(() => {
|
|
1044
|
+
for (const l of hoisted?.lines ?? []) this.line(l);
|
|
1045
|
+
const base = (this._tmpCounter ?? 0) + 1;
|
|
1046
|
+
this._tmpCounter = base + stmt.lhs.length;
|
|
1047
|
+
const val = (i) => `__s${base + i}`;
|
|
1048
|
+
const dst = (i) => `__d${base + i}`;
|
|
1049
|
+
stmt.rhs.forEach((r, i) => {
|
|
1050
|
+
this.line(`const ${val(i)} = ${this.genValueExpr(r)};`);
|
|
1051
|
+
});
|
|
1052
|
+
stmt.lhs.forEach((l, i) => {
|
|
1053
|
+
if (stable[i])
|
|
1054
|
+
this.line(`const ${dst(i)} = ${this._genAssignLhsExpr(l)};`);
|
|
1055
|
+
});
|
|
1056
|
+
stmt.lhs.forEach((l, i) => {
|
|
1057
|
+
if (stable[i]) this._emitCopyInto(dst(i), val(i), l._type);
|
|
1058
|
+
else if (!(l.kind === "Ident" && l.name === "_"))
|
|
1059
|
+
this.line(`${this._genAssignLhsExpr(l)} = ${val(i)};`);
|
|
1060
|
+
});
|
|
1061
|
+
});
|
|
1062
|
+
this.line("}");
|
|
1063
|
+
hoisted?.restore();
|
|
1064
|
+
},
|
|
1065
|
+
|
|
849
1066
|
_genStableWholeAssign(stmt) {
|
|
850
1067
|
let target = this._genAssignLhsExpr(stmt.lhs[0]);
|
|
851
1068
|
const type = stmt.lhs[0]._type;
|
|
@@ -927,27 +1144,16 @@ export const statementGenMethods = {
|
|
|
927
1144
|
}
|
|
928
1145
|
const rhsExpr = this._genComplexOperand(stmt.rhs[0]);
|
|
929
1146
|
const baseOp = stmt.op.slice(0, -1); // "+=" → "+"
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
result = `__cmul(${lhsRead}, ${rhsExpr})`;
|
|
941
|
-
break;
|
|
942
|
-
case "/":
|
|
943
|
-
this.useHelper("cdiv");
|
|
944
|
-
result = `__cdiv(${lhsRead}, ${rhsExpr})`;
|
|
945
|
-
break;
|
|
946
|
-
default:
|
|
947
|
-
throw new Error(
|
|
948
|
-
`unsupported complex compound assignment operator: ${stmt.op}`,
|
|
949
|
-
);
|
|
950
|
-
}
|
|
1147
|
+
if (!["+", "-", "*", "/"].includes(baseOp))
|
|
1148
|
+
throw new Error(
|
|
1149
|
+
`unsupported complex compound assignment operator: ${stmt.op}`,
|
|
1150
|
+
);
|
|
1151
|
+
const result = this._genComplexArith(
|
|
1152
|
+
baseOp,
|
|
1153
|
+
lhsRead,
|
|
1154
|
+
rhsExpr,
|
|
1155
|
+
isComplex64(l._type),
|
|
1156
|
+
);
|
|
951
1157
|
this.line(`${lhsTarget} = ${result};`);
|
|
952
1158
|
},
|
|
953
1159
|
|
|
@@ -956,10 +1162,21 @@ export const statementGenMethods = {
|
|
|
956
1162
|
this.line(`${this._iterReturnFlag} = true; return false;`);
|
|
957
1163
|
return;
|
|
958
1164
|
}
|
|
959
|
-
|
|
1165
|
+
let vals =
|
|
960
1166
|
stmt.values.length > 0
|
|
961
1167
|
? stmt.values.map((v) => this._genReturnValue(v))
|
|
962
1168
|
: (this.namedReturnVars ?? []);
|
|
1169
|
+
const named = this.namedReturnVars ?? [];
|
|
1170
|
+
if (stmt.values.length > 0 && named.length > 0) {
|
|
1171
|
+
// Deferred calls read the named results after the loop returns.
|
|
1172
|
+
const rhs =
|
|
1173
|
+
vals.length === named.length && named.length > 1
|
|
1174
|
+
? `[${vals.join(", ")}]`
|
|
1175
|
+
: vals[0];
|
|
1176
|
+
const lhs = named.length === 1 ? named[0] : `[${named.join(", ")}]`;
|
|
1177
|
+
this.line(`${lhs} = ${rhs};`);
|
|
1178
|
+
vals = named;
|
|
1179
|
+
}
|
|
963
1180
|
const stored = vals.length === 1 ? vals[0] : `[${vals.join(", ")}]`;
|
|
964
1181
|
this.line(
|
|
965
1182
|
`${this._iterReturnFlag} = true; ${this._iterReturnVar} = ${stored}; return false;`,
|
|
@@ -1028,11 +1245,10 @@ export const statementGenMethods = {
|
|
|
1028
1245
|
this.line(`${stmt.keyword} ${stmt.label};`);
|
|
1029
1246
|
return;
|
|
1030
1247
|
}
|
|
1031
|
-
if (
|
|
1032
|
-
stmt.keyword === "continue"
|
|
1033
|
-
stmt.
|
|
1034
|
-
|
|
1035
|
-
this.line("return true;");
|
|
1248
|
+
if (this._currentIterLoopLabels?.includes(stmt.label)) {
|
|
1249
|
+
if (stmt.keyword === "continue") this.line("return true;");
|
|
1250
|
+
else if (stmt.keyword === "break")
|
|
1251
|
+
this.line(`${this._iterBreakFlag} = true; return false;`);
|
|
1036
1252
|
return;
|
|
1037
1253
|
}
|
|
1038
1254
|
this.line(
|
|
@@ -1063,7 +1279,12 @@ export const statementGenMethods = {
|
|
|
1063
1279
|
this.line("}");
|
|
1064
1280
|
return;
|
|
1065
1281
|
}
|
|
1066
|
-
if (elseBody.kind === "IfStmt") {
|
|
1282
|
+
if (elseBody.kind === "IfStmt" && elseBody.init) {
|
|
1283
|
+
// `else if init; cond`: the init runs only when this branch is taken.
|
|
1284
|
+
this.line("} else {");
|
|
1285
|
+
this.indented(() => this.genStmt(elseBody));
|
|
1286
|
+
this.line("}");
|
|
1287
|
+
} else if (elseBody.kind === "IfStmt") {
|
|
1067
1288
|
this.line(`} else if (${this.genExpr(elseBody.cond)}) {`);
|
|
1068
1289
|
this.indented(() => this.genBlock(elseBody.body));
|
|
1069
1290
|
this._genElse(elseBody.elseBody);
|
|
@@ -1076,7 +1297,8 @@ export const statementGenMethods = {
|
|
|
1076
1297
|
|
|
1077
1298
|
_rangeExprLimit(iterType, iteree) {
|
|
1078
1299
|
if (this._isIntRangeType(iterType)) return iteree;
|
|
1079
|
-
if (this._isMapRangeType(iterType))
|
|
1300
|
+
if (this._isMapRangeType(iterType))
|
|
1301
|
+
return `Object.keys(${iteree} ?? {}).length`;
|
|
1080
1302
|
return `(${iteree} ? ${iteree}.length : 0)`;
|
|
1081
1303
|
},
|
|
1082
1304
|
|
|
@@ -1085,7 +1307,7 @@ export const statementGenMethods = {
|
|
|
1085
1307
|
this.genIteratorForCond(stmt);
|
|
1086
1308
|
return;
|
|
1087
1309
|
}
|
|
1088
|
-
const iterType = stmt.cond.expr._type;
|
|
1310
|
+
const iterType = stmt.cond.expr._coreType ?? stmt.cond.expr._type;
|
|
1089
1311
|
const iteree = this.genExpr(stmt.cond.expr);
|
|
1090
1312
|
const limit = this._rangeExprLimit(iterType, iteree);
|
|
1091
1313
|
this.line(`for (let _$ = 0; _$ < ${limit}; _$++) {`);
|
|
@@ -1103,14 +1325,52 @@ export const statementGenMethods = {
|
|
|
1103
1325
|
this.line("}");
|
|
1104
1326
|
},
|
|
1105
1327
|
|
|
1328
|
+
// `for n := n; ...` declares a loop variable its own initialiser reads: the
|
|
1329
|
+
// initialiser values are evaluated into temporaries ahead of the loop.
|
|
1330
|
+
_hoistForInit(stmt) {
|
|
1331
|
+
const init = stmt.kind === "ForStmt" && stmt.init;
|
|
1332
|
+
if (
|
|
1333
|
+
!init ||
|
|
1334
|
+
stmt._initPre ||
|
|
1335
|
+
init.kind !== "DefineStmt" ||
|
|
1336
|
+
this.isRangeFor(stmt) ||
|
|
1337
|
+
stmt.cond?.kind === "RangeExpr" ||
|
|
1338
|
+
!this._definesShadowedRead(init)
|
|
1339
|
+
)
|
|
1340
|
+
return false;
|
|
1341
|
+
this._tmpCounter = (this._tmpCounter ?? 0) + 1;
|
|
1342
|
+
const values = this._defineRhs(init);
|
|
1343
|
+
const names = values.map((_, i) => `__fi${this._tmpCounter}_${i}`);
|
|
1344
|
+
this.line("{");
|
|
1345
|
+
this.indent++;
|
|
1346
|
+
this.line(
|
|
1347
|
+
`const ${values.map((v, i) => `${names[i]} = ${v}`).join(", ")};`,
|
|
1348
|
+
);
|
|
1349
|
+
stmt._initPre = names;
|
|
1350
|
+
return true;
|
|
1351
|
+
},
|
|
1352
|
+
|
|
1353
|
+
_closeHoistedForInit() {
|
|
1354
|
+
this.indent--;
|
|
1355
|
+
this.line("}");
|
|
1356
|
+
},
|
|
1357
|
+
|
|
1106
1358
|
genFor(stmt) {
|
|
1359
|
+
const hoisted = this._hoistForInit(stmt);
|
|
1360
|
+
try {
|
|
1361
|
+
this._genForLoop(stmt);
|
|
1362
|
+
} finally {
|
|
1363
|
+
delete stmt._initPre;
|
|
1364
|
+
}
|
|
1365
|
+
if (hoisted) this._closeHoistedForInit();
|
|
1366
|
+
},
|
|
1367
|
+
|
|
1368
|
+
_genForLoop(stmt) {
|
|
1107
1369
|
const isInner = this._inIteratorBody;
|
|
1108
1370
|
if (isInner) {
|
|
1109
1371
|
this._iterLoopDepth = (this._iterLoopDepth ?? 0) + 1;
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
this._innerLoopLabels.add(stmt.label);
|
|
1113
|
-
}
|
|
1372
|
+
this._innerLoopLabels = this._innerLoopLabels ?? new Set();
|
|
1373
|
+
for (const l of this._loopLabels(stmt)) this._innerLoopLabels.add(l);
|
|
1114
1374
|
}
|
|
1115
1375
|
try {
|
|
1116
1376
|
if (this.isRangeFor(stmt)) {
|
|
@@ -1126,7 +1386,7 @@ export const statementGenMethods = {
|
|
|
1126
1386
|
this._genSimpleWhileFor(stmt);
|
|
1127
1387
|
return;
|
|
1128
1388
|
}
|
|
1129
|
-
const init = stmt.init ? this.stmtInline(stmt.init) : "";
|
|
1389
|
+
const init = stmt.init ? this.stmtInline(stmt.init, stmt._initPre) : "";
|
|
1130
1390
|
const cond = stmt.cond ? this.genExpr(stmt.cond) : "";
|
|
1131
1391
|
const post = this._perIterationClones(
|
|
1132
1392
|
stmt,
|
|
@@ -1138,7 +1398,7 @@ export const statementGenMethods = {
|
|
|
1138
1398
|
} finally {
|
|
1139
1399
|
if (isInner) {
|
|
1140
1400
|
this._iterLoopDepth--;
|
|
1141
|
-
|
|
1401
|
+
for (const l of this._loopLabels(stmt)) this._innerLoopLabels.delete(l);
|
|
1142
1402
|
}
|
|
1143
1403
|
}
|
|
1144
1404
|
},
|
|
@@ -1194,10 +1454,26 @@ export const statementGenMethods = {
|
|
|
1194
1454
|
return `${this._genAssignLhsExpr(e)} = `;
|
|
1195
1455
|
},
|
|
1196
1456
|
|
|
1457
|
+
// The value a range assignment stores into an interface-typed variable,
|
|
1458
|
+
// boxed as in any conversion to an interface.
|
|
1459
|
+
_boxRangeAssigned(e, valueJs) {
|
|
1460
|
+
const box = e._rangeBox;
|
|
1461
|
+
if (!box) return valueJs;
|
|
1462
|
+
if (this._isNamedBoxKind(box))
|
|
1463
|
+
return this._genNamedIfaceBox(
|
|
1464
|
+
valueJs,
|
|
1465
|
+
box._ifaceNamedType,
|
|
1466
|
+
box._ifaceBox === "named_ptr",
|
|
1467
|
+
);
|
|
1468
|
+
this.useHelper("ifaceBox");
|
|
1469
|
+
return this._ifaceBoxWrap(valueJs, box._ifaceBox, box._ifacePtrFields);
|
|
1470
|
+
},
|
|
1471
|
+
|
|
1197
1472
|
_emitRangeVar(e, valueJs, isAssign) {
|
|
1198
1473
|
if (!e) return;
|
|
1199
1474
|
const target = this._rangeVarTarget(e, isAssign);
|
|
1200
1475
|
if (target === null) return;
|
|
1476
|
+
if (isAssign) valueJs = this._boxRangeAssigned(e, valueJs);
|
|
1201
1477
|
if (typeof target === "string") {
|
|
1202
1478
|
this.line(`${target}${valueJs};`);
|
|
1203
1479
|
return;
|
|
@@ -1217,10 +1493,14 @@ export const statementGenMethods = {
|
|
|
1217
1493
|
const iterType = iterNode._type;
|
|
1218
1494
|
const base = iterType?.kind === "named" ? iterType.underlying : iterType;
|
|
1219
1495
|
const elemType = base?.elem;
|
|
1220
|
-
if (
|
|
1221
|
-
|
|
1496
|
+
if (
|
|
1497
|
+
!valNode ||
|
|
1498
|
+
(!this._aggregateBase(elemType) && !this._typeParamClone(elemType, js))
|
|
1499
|
+
)
|
|
1500
|
+
return js;
|
|
1501
|
+
if (valNode.kind === "Ident" && valNode.name === "_") return js;
|
|
1502
|
+
if (isAssign || valNode.kind !== "Ident" || elemType?.kind === "typeParam")
|
|
1222
1503
|
return this._cloneJs(elemType, js);
|
|
1223
|
-
if (valNode.name === "_") return js;
|
|
1224
1504
|
const root = this._rootIdentName(iterNode);
|
|
1225
1505
|
const mutated =
|
|
1226
1506
|
this._fnMutates(valNode.name) ||
|
|
@@ -1311,11 +1591,6 @@ export const statementGenMethods = {
|
|
|
1311
1591
|
this._loopDepth--;
|
|
1312
1592
|
},
|
|
1313
1593
|
|
|
1314
|
-
// True when `node` contains an assignment or ++/-- targeting identifier `name`.
|
|
1315
|
-
_nodeAssigns(node, name) {
|
|
1316
|
-
return nodeAssigns(node, name);
|
|
1317
|
-
},
|
|
1318
|
-
|
|
1319
1594
|
_genStringRangeFor(stmt, iteree, isAssign) {
|
|
1320
1595
|
this._loopDepth = this._loopDepth ?? 0;
|
|
1321
1596
|
const d = this._loopDepth++;
|
|
@@ -1323,13 +1598,17 @@ export const statementGenMethods = {
|
|
|
1323
1598
|
const s = `__s${d}`;
|
|
1324
1599
|
const i = `__i${d}`;
|
|
1325
1600
|
const cp = `__cp${d}`;
|
|
1601
|
+
const w = `__w${d}`;
|
|
1602
|
+
// The index is a UTF-16 offset (an astral rune is two units) so `s[i:]`
|
|
1603
|
+
// agrees with the WebAssembly string layout.
|
|
1326
1604
|
this.line(
|
|
1327
|
-
`for (let ${i} = 0, ${s} =
|
|
1605
|
+
`for (let ${i} = 0, ${s} = ${iteree} ?? "", ${w} = 0; ${i} < ${s}.length; ${i} += ${w}) {`,
|
|
1328
1606
|
);
|
|
1329
1607
|
this.indented(() => {
|
|
1608
|
+
this.line(`const ${cp} = ${s}.codePointAt(${i});`);
|
|
1609
|
+
this.line(`${w} = ${cp} > 0xffff ? 2 : 1;`);
|
|
1330
1610
|
this._emitRangeVar(idxNode, i, isAssign);
|
|
1331
1611
|
if (valNode && valNode.name !== "_") {
|
|
1332
|
-
this.line(`const ${cp} = ${s}[${i}].codePointAt(0);`);
|
|
1333
1612
|
this._emitRangeVar(valNode, cp, isAssign);
|
|
1334
1613
|
}
|
|
1335
1614
|
const declared = isAssign
|
|
@@ -1349,26 +1628,102 @@ export const statementGenMethods = {
|
|
|
1349
1628
|
this._loopDepth--;
|
|
1350
1629
|
},
|
|
1351
1630
|
|
|
1352
|
-
|
|
1631
|
+
// `for _, v = range m` into an address-taken variable (or an element or
|
|
1632
|
+
// field) cannot destructure into the target: bind temporaries and store
|
|
1633
|
+
// through the variable's box after decoding. Fills `stores` with the
|
|
1634
|
+
// [target, temporary] pairs and returns the names to bind.
|
|
1635
|
+
_rangeAssignTemps(init, names, stores) {
|
|
1636
|
+
if (init.kind !== "AssignStmt") return names;
|
|
1637
|
+
// `let [..]` declares every name, so once one target needs a temporary
|
|
1638
|
+
// all the others go through temporaries too.
|
|
1639
|
+
const needs = (n, i) => {
|
|
1640
|
+
const e = init.lhs[i];
|
|
1641
|
+
return (
|
|
1642
|
+
n !== "_" &&
|
|
1643
|
+
(e._rangeBox || !(e.kind === "Ident" && !this._boxedVars.has(e.name)))
|
|
1644
|
+
);
|
|
1645
|
+
};
|
|
1646
|
+
const any = names.some(needs);
|
|
1647
|
+
return names.map((n, i) => {
|
|
1648
|
+
if (n === "_" || !any) return n;
|
|
1649
|
+
stores.push([init.lhs[i], `__r${i}$`]);
|
|
1650
|
+
return `__r${i}$`;
|
|
1651
|
+
});
|
|
1652
|
+
},
|
|
1653
|
+
|
|
1654
|
+
_genIterFor(init, iterType, iteree, lhsNames, body) {
|
|
1655
|
+
const isAssign = init.kind === "AssignStmt";
|
|
1656
|
+
const stores = [];
|
|
1657
|
+
const lhs = this._rangeAssignTemps(init, lhsNames, stores);
|
|
1353
1658
|
const iterExpr = this._genRangeIterExpr(iterType, iteree, lhs);
|
|
1354
1659
|
const keyDecode = this._structKeyRange(iterType);
|
|
1660
|
+
const paramKey = this._typeParamKeyRange(iterType);
|
|
1661
|
+
const is64Key = this._is64BitMapRange(iterType);
|
|
1662
|
+
const scalarKey = this._scalarKeyRange(iterType);
|
|
1663
|
+
// `for _, v = range m` assigns existing variables, where a blank is a
|
|
1664
|
+
// hole in the destructuring pattern; a lone blank still needs a binding.
|
|
1665
|
+
const blank = isAssign && lhs.length > 1 ? "" : "_$";
|
|
1355
1666
|
const binding =
|
|
1356
1667
|
lhs.length === 1
|
|
1357
1668
|
? lhs[0] === "_"
|
|
1358
1669
|
? "_$"
|
|
1359
1670
|
: lhs[0]
|
|
1360
|
-
: `[${lhs.map((n) => (n === "_" ?
|
|
1361
|
-
const kw =
|
|
1671
|
+
: `[${lhs.map((n) => (n === "_" ? blank : n)).join(", ")}]`;
|
|
1672
|
+
const kw = !isAssign || binding === "_$" || stores.length > 0 ? "let " : "";
|
|
1362
1673
|
this.line(`for (${kw}${binding} of ${iterExpr}) {`);
|
|
1363
1674
|
this.indented(() => {
|
|
1364
|
-
if (
|
|
1675
|
+
if (paramKey && lhs[0] !== "_")
|
|
1676
|
+
this.line(`${lhs[0]} = __kdt(${paramKey}, ${lhs[0]});`);
|
|
1677
|
+
else if (keyDecode && lhs[0] !== "_")
|
|
1365
1678
|
this.line(`${lhs[0]} = __kd(${lhs[0]}, true);`);
|
|
1679
|
+
else if (is64Key && lhs[0] !== "_")
|
|
1680
|
+
this.line(`${lhs[0]} = BigInt(${lhs[0]});`);
|
|
1681
|
+
else if (scalarKey && lhs[0] !== "_")
|
|
1682
|
+
this.line(
|
|
1683
|
+
scalarKey === "bool"
|
|
1684
|
+
? `${lhs[0]} = ${lhs[0]} === "true";`
|
|
1685
|
+
: `${lhs[0]} = Number(${lhs[0]});`,
|
|
1686
|
+
);
|
|
1366
1687
|
this._cloneMapRangeValue(init, iterType, lhs[1]);
|
|
1688
|
+
for (const [e, tmp] of stores) this._emitRangeVar(e, tmp, true);
|
|
1367
1689
|
this.genBlock(body);
|
|
1368
1690
|
});
|
|
1369
1691
|
this.line("}");
|
|
1370
1692
|
},
|
|
1371
1693
|
|
|
1694
|
+
_is64BitMapRange(iterType) {
|
|
1695
|
+
if (!this._isMapRangeType(iterType)) return false;
|
|
1696
|
+
const base = iterType?.kind === "named" ? iterType.underlying : iterType;
|
|
1697
|
+
const key = base?.kind === "map" ? base.key : null;
|
|
1698
|
+
return is64BitInt(key);
|
|
1699
|
+
},
|
|
1700
|
+
|
|
1701
|
+
// Ranging over a map with plain int, float or bool keys: object property
|
|
1702
|
+
// names are strings, so the key variable is converted back.
|
|
1703
|
+
_scalarKeyRange(iterType) {
|
|
1704
|
+
if (!this._isMapRangeType(iterType)) return null;
|
|
1705
|
+
const base = iterType?.kind === "named" ? iterType.underlying : iterType;
|
|
1706
|
+
const key = base?.kind === "map" ? base.key : null;
|
|
1707
|
+
if (!key || key.kind === "typeParam") return null;
|
|
1708
|
+
const u = this._underlyingOf(key);
|
|
1709
|
+
if (u?.kind !== "basic" || this._is64BitInt(key)) return null;
|
|
1710
|
+
if (u.name === "bool") return "bool";
|
|
1711
|
+
return /^(u?int\d*|uintptr|float(32|64))$/.test(u.name) ? "num" : null;
|
|
1712
|
+
},
|
|
1713
|
+
|
|
1714
|
+
// Ranging over a map whose key type is a type parameter: the key is decoded
|
|
1715
|
+
// from its property string by the instantiated key type's zero factory.
|
|
1716
|
+
_typeParamKeyRange(iterType) {
|
|
1717
|
+
if (!this._isMapRangeType(iterType)) return null;
|
|
1718
|
+
const base = iterType?.kind === "named" ? iterType.underlying : iterType;
|
|
1719
|
+
const key = base?.kind === "map" ? base.key : null;
|
|
1720
|
+
if (key?.kind !== "typeParam") return null;
|
|
1721
|
+
const factory = this._typeParamZeroFactory(key.name);
|
|
1722
|
+
if (!factory) return null;
|
|
1723
|
+
this.useHelper("structKey");
|
|
1724
|
+
return factory;
|
|
1725
|
+
},
|
|
1726
|
+
|
|
1372
1727
|
// Ranging over a map whose keys are structs: the key variable is decoded
|
|
1373
1728
|
// from its encoded string (see HELPER_STRUCT_KEY).
|
|
1374
1729
|
_structKeyRange(iterType) {
|
|
@@ -1376,7 +1731,13 @@ export const statementGenMethods = {
|
|
|
1376
1731
|
const base = iterType?.kind === "named" ? iterType.underlying : iterType;
|
|
1377
1732
|
const key = base?.kind === "map" ? base.key : null;
|
|
1378
1733
|
const keyBase = key?.kind === "named" ? key.underlying : key;
|
|
1379
|
-
if (
|
|
1734
|
+
if (
|
|
1735
|
+
keyBase?.kind !== "struct" &&
|
|
1736
|
+
keyBase?.kind !== "array" &&
|
|
1737
|
+
keyBase?.kind !== "pointer" &&
|
|
1738
|
+
!this._isIfaceType(key)
|
|
1739
|
+
)
|
|
1740
|
+
return false;
|
|
1380
1741
|
this.useHelper("structKey");
|
|
1381
1742
|
return true;
|
|
1382
1743
|
},
|
|
@@ -1396,7 +1757,7 @@ export const statementGenMethods = {
|
|
|
1396
1757
|
const init = stmt.init;
|
|
1397
1758
|
const range = init.rhs[0];
|
|
1398
1759
|
const lhs = init.lhs.map((e) => e.name ?? this.genExpr(e));
|
|
1399
|
-
const iterType = range.expr._type;
|
|
1760
|
+
const iterType = range.expr._coreType ?? range.expr._type;
|
|
1400
1761
|
const wrapField = this._namedWrapperField(iterType, range.expr);
|
|
1401
1762
|
const iteree = wrapField
|
|
1402
1763
|
? `${this.genExpr(range.expr)}.${wrapField}`
|
|
@@ -1431,9 +1792,22 @@ export const statementGenMethods = {
|
|
|
1431
1792
|
!!valNode &&
|
|
1432
1793
|
iterBase?.kind === "slice" &&
|
|
1433
1794
|
!isTypedArraySlice(iterType);
|
|
1795
|
+
// Ranging over an array for its indexes only does not evaluate the
|
|
1796
|
+
// array (`range p` on a nil array pointer is valid): the length is the
|
|
1797
|
+
// type's.
|
|
1798
|
+
const arrayLen = this._aggregateBase(iterType)?.size;
|
|
1799
|
+
const indexOnlyArray =
|
|
1800
|
+
(!valNode || valNode.name === "_") &&
|
|
1801
|
+
typeof arrayLen === "number" &&
|
|
1802
|
+
this._aggregateBase(iterType)?.kind === "array" &&
|
|
1803
|
+
!hasCallOrReceive(range.expr);
|
|
1434
1804
|
this._genSliceRangeFor(
|
|
1435
1805
|
stmt,
|
|
1436
|
-
|
|
1806
|
+
indexOnlyArray
|
|
1807
|
+
? `({ length: ${arrayLen} })`
|
|
1808
|
+
: rangesArrayValue
|
|
1809
|
+
? this._cloneJs(iterType, iteree)
|
|
1810
|
+
: iteree,
|
|
1437
1811
|
init.kind === "AssignStmt",
|
|
1438
1812
|
unwrapView,
|
|
1439
1813
|
);
|
|
@@ -1441,25 +1815,49 @@ export const statementGenMethods = {
|
|
|
1441
1815
|
|
|
1442
1816
|
_genRangeIterExpr(iterType, iteree, lhs) {
|
|
1443
1817
|
if (this._isMapRangeType(iterType)) {
|
|
1444
|
-
|
|
1445
|
-
return `Object.
|
|
1818
|
+
// A nil map is `null`; ranging over it runs zero iterations.
|
|
1819
|
+
if (lhs.length === 1) return `Object.keys(${iteree} ?? {})`;
|
|
1820
|
+
return `Object.entries(${iteree} ?? {})`;
|
|
1446
1821
|
}
|
|
1447
1822
|
this.useHelper("sliceGuard");
|
|
1448
1823
|
if (lhs.length === 1) return `__s(${iteree}).keys()`;
|
|
1449
1824
|
return `__s(${iteree}).entries()`;
|
|
1450
1825
|
},
|
|
1451
1826
|
|
|
1452
|
-
|
|
1453
|
-
|
|
1827
|
+
// Every label of a loop: stacked labels (`a: b: for ...`) all name it.
|
|
1828
|
+
_loopLabels(stmt) {
|
|
1829
|
+
return stmt.labels ?? (stmt.label ? [stmt.label] : []);
|
|
1830
|
+
},
|
|
1831
|
+
|
|
1832
|
+
_emitIterTargetDispatch(targetFlag, ownLabels = []) {
|
|
1833
|
+
// The loop's own label is handled inside the callback (`break` and
|
|
1834
|
+
// `continue` of the range-over-func loop), and a JS `continue` cannot
|
|
1835
|
+
// name the labeled block that wraps it. Only the labels of statements
|
|
1836
|
+
// enclosing this loop in the same JS function are in scope (`_jsLabels`):
|
|
1837
|
+
// inside another iterator's callback, the enclosing iterator loop's
|
|
1838
|
+
// label is handled like a branch in its body, and any other label is
|
|
1839
|
+
// passed on to the next dispatcher.
|
|
1840
|
+
const inIter = this._inIteratorBody;
|
|
1841
|
+
const labels = (this._jsLabels ?? []).filter(
|
|
1842
|
+
(l) => !ownLabels.includes(l.name),
|
|
1843
|
+
);
|
|
1454
1844
|
this.line(`if (${targetFlag}) {`);
|
|
1455
1845
|
this.indented(() => {
|
|
1456
|
-
for (const
|
|
1457
|
-
this.line(`if (${targetFlag} === "break$${
|
|
1458
|
-
|
|
1846
|
+
for (const { name, loop } of labels) {
|
|
1847
|
+
this.line(`if (${targetFlag} === "break$${name}") break ${name};`);
|
|
1848
|
+
if (loop)
|
|
1849
|
+
this.line(
|
|
1850
|
+
`if (${targetFlag} === "continue$${name}") continue ${name};`,
|
|
1851
|
+
);
|
|
1459
1852
|
}
|
|
1460
|
-
if (
|
|
1461
|
-
|
|
1853
|
+
if (!inIter) return;
|
|
1854
|
+
for (const encl of this._currentIterLoopLabels ?? []) {
|
|
1855
|
+
this.line(`if (${targetFlag} === "continue$${encl}") return true;`);
|
|
1856
|
+
this.line(
|
|
1857
|
+
`if (${targetFlag} === "break$${encl}") { ${this._iterBreakFlag} = true; return false; }`,
|
|
1858
|
+
);
|
|
1462
1859
|
}
|
|
1860
|
+
this.line(`${this._iterTargetFlag} = ${targetFlag}; return false;`);
|
|
1463
1861
|
});
|
|
1464
1862
|
this.line("}");
|
|
1465
1863
|
},
|
|
@@ -1469,7 +1867,7 @@ export const statementGenMethods = {
|
|
|
1469
1867
|
paramsStr,
|
|
1470
1868
|
body,
|
|
1471
1869
|
assignPreamble = null,
|
|
1472
|
-
|
|
1870
|
+
loopLabels = [],
|
|
1473
1871
|
) {
|
|
1474
1872
|
let iteree = this.genExpr(itereeExpr);
|
|
1475
1873
|
if (itereeExpr.kind === "FuncLit") {
|
|
@@ -1508,16 +1906,21 @@ export const statementGenMethods = {
|
|
|
1508
1906
|
ret: this._iterReturnFlag,
|
|
1509
1907
|
retVar: this._iterReturnVar,
|
|
1510
1908
|
target: this._iterTargetFlag,
|
|
1511
|
-
|
|
1909
|
+
loopLabels: this._currentIterLoopLabels,
|
|
1512
1910
|
loopDepth: this._iterLoopDepth,
|
|
1911
|
+
innerLabels: this._innerLoopLabels,
|
|
1912
|
+
jsLabels: this._jsLabels,
|
|
1513
1913
|
};
|
|
1514
1914
|
this._inIteratorBody = true;
|
|
1515
1915
|
this._iterBreakFlag = breakFlag;
|
|
1516
1916
|
this._iterReturnFlag = retFlag;
|
|
1517
1917
|
this._iterReturnVar = retVar;
|
|
1518
1918
|
this._iterTargetFlag = targetFlag;
|
|
1519
|
-
this.
|
|
1919
|
+
this._currentIterLoopLabels = loopLabels;
|
|
1520
1920
|
this._iterLoopDepth = 0;
|
|
1921
|
+
// Loops of an enclosing callback are not JS labels in this one.
|
|
1922
|
+
this._innerLoopLabels = new Set();
|
|
1923
|
+
this._jsLabels = [];
|
|
1521
1924
|
|
|
1522
1925
|
if (assignPreamble) assignPreamble();
|
|
1523
1926
|
this.genBlock(body);
|
|
@@ -1527,14 +1930,16 @@ export const statementGenMethods = {
|
|
|
1527
1930
|
this._iterReturnFlag = prev.ret;
|
|
1528
1931
|
this._iterReturnVar = prev.retVar;
|
|
1529
1932
|
this._iterTargetFlag = prev.target;
|
|
1530
|
-
this.
|
|
1933
|
+
this._currentIterLoopLabels = prev.loopLabels;
|
|
1531
1934
|
this._iterLoopDepth = prev.loopDepth;
|
|
1935
|
+
this._innerLoopLabels = prev.innerLabels;
|
|
1936
|
+
this._jsLabels = prev.jsLabels;
|
|
1532
1937
|
|
|
1533
1938
|
this.line("return true;");
|
|
1534
1939
|
});
|
|
1535
1940
|
this.line("});");
|
|
1536
1941
|
this.line(`if (${retFlag}) return ${retVar};`);
|
|
1537
|
-
this._emitIterTargetDispatch(targetFlag);
|
|
1942
|
+
this._emitIterTargetDispatch(targetFlag, loopLabels);
|
|
1538
1943
|
});
|
|
1539
1944
|
this.line("}");
|
|
1540
1945
|
this._iterDepth--;
|
|
@@ -1543,7 +1948,8 @@ export const statementGenMethods = {
|
|
|
1543
1948
|
genIteratorFor(stmt) {
|
|
1544
1949
|
const range = stmt.init.rhs[0];
|
|
1545
1950
|
const isAssign = stmt.init.kind === "AssignStmt";
|
|
1546
|
-
|
|
1951
|
+
// A non-identifier target is stored through _emitRangeVar, not by name.
|
|
1952
|
+
const lhs = stmt.init.lhs.map((e) => e.name ?? "_$target");
|
|
1547
1953
|
const yieldTypes = range._yieldParams ?? [];
|
|
1548
1954
|
if (isAssign) {
|
|
1549
1955
|
const cbParams = lhs.map((_, i) => `__p${i}`);
|
|
@@ -1555,12 +1961,9 @@ export const statementGenMethods = {
|
|
|
1555
1961
|
const isStruct =
|
|
1556
1962
|
this._isStructType(yType) ||
|
|
1557
1963
|
this._isStructType(stmt.init.lhs[i]?._type);
|
|
1558
|
-
if (isStruct)
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
} else {
|
|
1562
|
-
this.line(`${lhs[i]} = ${cbParams[i]};`);
|
|
1563
|
-
}
|
|
1964
|
+
if (isStruct) this.useHelper("sclone");
|
|
1965
|
+
const value = isStruct ? `__sclone(${cbParams[i]})` : cbParams[i];
|
|
1966
|
+
this._emitRangeVar(stmt.init.lhs[i], value, true);
|
|
1564
1967
|
}
|
|
1565
1968
|
}
|
|
1566
1969
|
};
|
|
@@ -1569,7 +1972,7 @@ export const statementGenMethods = {
|
|
|
1569
1972
|
params,
|
|
1570
1973
|
stmt.body,
|
|
1571
1974
|
assignPreamble,
|
|
1572
|
-
stmt
|
|
1975
|
+
this._loopLabels(stmt),
|
|
1573
1976
|
);
|
|
1574
1977
|
return;
|
|
1575
1978
|
}
|
|
@@ -1594,7 +1997,7 @@ export const statementGenMethods = {
|
|
|
1594
1997
|
params,
|
|
1595
1998
|
stmt.body,
|
|
1596
1999
|
clonePreamble,
|
|
1597
|
-
stmt
|
|
2000
|
+
this._loopLabels(stmt),
|
|
1598
2001
|
);
|
|
1599
2002
|
},
|
|
1600
2003
|
|
|
@@ -1605,15 +2008,15 @@ export const statementGenMethods = {
|
|
|
1605
2008
|
"",
|
|
1606
2009
|
stmt.body,
|
|
1607
2010
|
null,
|
|
1608
|
-
stmt
|
|
2011
|
+
this._loopLabels(stmt),
|
|
1609
2012
|
);
|
|
1610
2013
|
},
|
|
1611
2014
|
|
|
1612
2015
|
// Inline a statement as a for-init/post string (no semicolon)
|
|
1613
|
-
stmtInline(stmt) {
|
|
2016
|
+
stmtInline(stmt, pre = null) {
|
|
1614
2017
|
switch (stmt.kind) {
|
|
1615
2018
|
case "DefineStmt":
|
|
1616
|
-
return this._inlineDefine(stmt);
|
|
2019
|
+
return this._inlineDefine(stmt, pre);
|
|
1617
2020
|
case "AssignStmt":
|
|
1618
2021
|
return this._withInlineSyncs(
|
|
1619
2022
|
this._resliceSyncs(stmt.lhs, stmt, true),
|
|
@@ -1623,6 +2026,14 @@ export const statementGenMethods = {
|
|
|
1623
2026
|
const syncs = this._resliceSyncs([stmt.expr], stmt, true);
|
|
1624
2027
|
this._markIndexLvalues([stmt.expr]);
|
|
1625
2028
|
this._markDerefOpTarget(stmt.expr);
|
|
2029
|
+
if (is64BitInt(stmt.expr._type)) {
|
|
2030
|
+
const l = this.genExpr(stmt.expr);
|
|
2031
|
+
const binOp = stmt.op === "++" ? "+" : "-";
|
|
2032
|
+
return this._withInlineSyncs(
|
|
2033
|
+
syncs,
|
|
2034
|
+
`${l} = ${this._gen64BitBinary(binOp, l, "1n", stmt.expr._type)}`,
|
|
2035
|
+
);
|
|
2036
|
+
}
|
|
1626
2037
|
return this._withInlineSyncs(
|
|
1627
2038
|
syncs,
|
|
1628
2039
|
`${this.genExpr(stmt.expr)}${stmt.op}`,
|
|
@@ -1639,14 +2050,14 @@ export const statementGenMethods = {
|
|
|
1639
2050
|
return syncs ? `${js}, ${syncs.join(", ")}` : js;
|
|
1640
2051
|
},
|
|
1641
2052
|
|
|
1642
|
-
_inlineDefine(stmt) {
|
|
2053
|
+
_inlineDefine(stmt, pre = null) {
|
|
1643
2054
|
const boxed =
|
|
1644
2055
|
stmt.lhs.length === 1 &&
|
|
1645
2056
|
stmt.rhs.length === 1 &&
|
|
1646
2057
|
this._boxedVars.has(stmt.lhs[0].name);
|
|
1647
2058
|
if (boxed)
|
|
1648
|
-
return `let ${stmt.lhs[0].name} = { value: ${this.genValueExpr(stmt.rhs[0])} }`;
|
|
1649
|
-
const rhs = stmt.rhs.map((e) => this.genExpr(e));
|
|
2059
|
+
return `let ${stmt.lhs[0].name} = { value: ${pre ? pre[0] : this.genValueExpr(stmt.rhs[0])} }`;
|
|
2060
|
+
const rhs = pre ?? stmt.rhs.map((e) => this.genExpr(e));
|
|
1650
2061
|
const lhs = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
|
|
1651
2062
|
if (lhs.length === 1) return `let ${lhs[0]} = ${rhs[0]}`;
|
|
1652
2063
|
// `for i, j := 0, 10`: one declarator per variable.
|
|
@@ -1661,11 +2072,56 @@ export const statementGenMethods = {
|
|
|
1661
2072
|
return `let [${lhs.map((n) => (n === "_" ? "" : n)).join(", ")}] = ${rhs[0]}`;
|
|
1662
2073
|
},
|
|
1663
2074
|
|
|
2075
|
+
// Compound assignments that need more than the JS operator: strict
|
|
2076
|
+
// numeric wrapping, 64-bit and float32 arithmetic, and integer `/=`.
|
|
2077
|
+
_needsStrictCompoundStmt(stmt, is64) {
|
|
2078
|
+
if (stmt.op === "=") return false;
|
|
2079
|
+
const t = stmt.lhs[0]._type;
|
|
2080
|
+
return (
|
|
2081
|
+
this.strict ||
|
|
2082
|
+
is64 ||
|
|
2083
|
+
this._isFloat32(t) ||
|
|
2084
|
+
(stmt.op === "/=" && this.isIntType(t))
|
|
2085
|
+
);
|
|
2086
|
+
},
|
|
2087
|
+
|
|
2088
|
+
// The single-expression form of `_genStrictCompound` for `for` post
|
|
2089
|
+
// statements: the emitted statement is lifted back out of the output.
|
|
2090
|
+
_inlineStrictCompound(stmt, rhsJs) {
|
|
2091
|
+
const mark = this.out.length;
|
|
2092
|
+
const handled = this._genStrictCompound(
|
|
2093
|
+
stmt.lhs[0],
|
|
2094
|
+
stmt.op.slice(0, -1),
|
|
2095
|
+
rhsJs,
|
|
2096
|
+
null,
|
|
2097
|
+
null,
|
|
2098
|
+
stmt.rhs[0]?._type,
|
|
2099
|
+
);
|
|
2100
|
+
const emitted = this.out.splice(mark).map((l) => l.trim());
|
|
2101
|
+
if (!handled) return null;
|
|
2102
|
+
const js = emitted.join(" ").replace(/;$/, "");
|
|
2103
|
+
return js.startsWith("{") ? `(() => ${js})()` : js;
|
|
2104
|
+
},
|
|
2105
|
+
|
|
1664
2106
|
_inlineAssign(stmt) {
|
|
1665
2107
|
this._markIndexLvalues(stmt.lhs);
|
|
1666
2108
|
const rhs = stmt.rhs.map((e) => this.genExpr(e));
|
|
1667
|
-
if (stmt.lhs.length === 1)
|
|
2109
|
+
if (stmt.lhs.length === 1) {
|
|
2110
|
+
if (stmt.op !== "=" && is64BitInt(stmt.lhs[0]._type)) {
|
|
2111
|
+
const binOp = stmt.op.slice(0, -1);
|
|
2112
|
+
// Same operand coercion as the statement path (`_genAssignStmt`).
|
|
2113
|
+
const r = this._untypedTo64(rhs[0], stmt.rhs[0]);
|
|
2114
|
+
const l = this.genExpr(stmt.lhs[0]);
|
|
2115
|
+
return `${l} = ${this._gen64BitBinary(binOp, l, r, stmt.lhs[0]._type)}`;
|
|
2116
|
+
}
|
|
2117
|
+
if (this._needsStrictCompoundStmt(stmt, is64BitInt(stmt.lhs[0]._type))) {
|
|
2118
|
+
const strict = this._inlineStrictCompound(stmt, rhs[0]);
|
|
2119
|
+
if (strict) return strict;
|
|
2120
|
+
}
|
|
2121
|
+
if (stmt.op === "&^=")
|
|
2122
|
+
return `${this.genExpr(stmt.lhs[0])} &= ~(${rhs[0]})`;
|
|
1668
2123
|
return `${this.genExpr(stmt.lhs[0])} ${stmt.op} ${rhs[0]}`;
|
|
2124
|
+
}
|
|
1669
2125
|
// `i, j = i+1, j-1`: every right-hand side is evaluated first.
|
|
1670
2126
|
const lhs = stmt.lhs.map((e) =>
|
|
1671
2127
|
e.name === "_" ? "" : this._genAssignLhsExpr(e),
|
|
@@ -1674,9 +2130,56 @@ export const statementGenMethods = {
|
|
|
1674
2130
|
return `[${lhs.join(", ")}] = ${r}`;
|
|
1675
2131
|
},
|
|
1676
2132
|
|
|
2133
|
+
_isMapTag(tag) {
|
|
2134
|
+
const t = tag._type?.kind === "named" ? tag._type.underlying : tag._type;
|
|
2135
|
+
return t?.kind === "map" || tag._coreType?.kind === "map";
|
|
2136
|
+
},
|
|
2137
|
+
|
|
1677
2138
|
// Interface-typed tags may hold boxed named values; cases compare with __ifeq.
|
|
1678
2139
|
_switchTagAndCases(stmt) {
|
|
1679
2140
|
const tag = stmt.tag ? this.genExpr(stmt.tag) : "true";
|
|
2141
|
+
if (stmt.tag && this._isStructOrArrayType(stmt.tag._type)) {
|
|
2142
|
+
// Struct and array tags compare by value, not by reference.
|
|
2143
|
+
this.useHelper("equal");
|
|
2144
|
+
this._swCounter = (this._swCounter ?? 0) + 1;
|
|
2145
|
+
const tmp = `__sw${this._swCounter}`;
|
|
2146
|
+
this.line(`const ${tmp} = ${tag};`);
|
|
2147
|
+
return {
|
|
2148
|
+
tag: "true",
|
|
2149
|
+
caseExpr: (e) => `__equal(${tmp}, ${this.genExpr(e)})`,
|
|
2150
|
+
};
|
|
2151
|
+
}
|
|
2152
|
+
if (stmt.tag && isComplex(stmt.tag._type)) {
|
|
2153
|
+
// A complex tag is an object: cases compare its parts.
|
|
2154
|
+
this._swCounter = (this._swCounter ?? 0) + 1;
|
|
2155
|
+
const tmp = `__sw${this._swCounter}`;
|
|
2156
|
+
this.line(`const ${tmp} = ${tag};`);
|
|
2157
|
+
return {
|
|
2158
|
+
tag: "true",
|
|
2159
|
+
caseExpr: (e) =>
|
|
2160
|
+
this._genComplexEquality(
|
|
2161
|
+
"===",
|
|
2162
|
+
"&&",
|
|
2163
|
+
tmp,
|
|
2164
|
+
this._genComplexOperand(e),
|
|
2165
|
+
),
|
|
2166
|
+
};
|
|
2167
|
+
}
|
|
2168
|
+
if (stmt.tag && this._isMapTag(stmt.tag)) {
|
|
2169
|
+
// A nil map is a sentinel object in JS, not `null`: `case nil`
|
|
2170
|
+
// goes through `__isnm`, as `m == nil` does.
|
|
2171
|
+
this.useHelper(this.strict ? "nilMap" : "nm");
|
|
2172
|
+
this._swCounter = (this._swCounter ?? 0) + 1;
|
|
2173
|
+
const tmp = `__sw${this._swCounter}`;
|
|
2174
|
+
this.line(`const ${tmp} = ${tag};`);
|
|
2175
|
+
return {
|
|
2176
|
+
tag: "true",
|
|
2177
|
+
caseExpr: (e) =>
|
|
2178
|
+
this._isNilLiteral(e)
|
|
2179
|
+
? `__isnm(${tmp})`
|
|
2180
|
+
: `${tmp} === ${this.genExpr(e)}`,
|
|
2181
|
+
};
|
|
2182
|
+
}
|
|
1680
2183
|
if (!stmt.tag || !this._isIfaceType(stmt.tag._type))
|
|
1681
2184
|
return { tag, caseExpr: (e) => this.genExpr(e) };
|
|
1682
2185
|
this.useHelper("ifeq");
|
|
@@ -1704,7 +2207,7 @@ export const statementGenMethods = {
|
|
|
1704
2207
|
// across sibling cases (JS switch shares one block scope).
|
|
1705
2208
|
this.line("{");
|
|
1706
2209
|
this.indented(() => {
|
|
1707
|
-
|
|
2210
|
+
this.genBlock(c);
|
|
1708
2211
|
// Go doesn't fall through by default — add break unless last stmt is return/break
|
|
1709
2212
|
const last = c.stmts[c.stmts.length - 1];
|
|
1710
2213
|
if (
|
|
@@ -1723,10 +2226,20 @@ export const statementGenMethods = {
|
|
|
1723
2226
|
if (stmt.init) {
|
|
1724
2227
|
this.line("{");
|
|
1725
2228
|
this.indented(() => {
|
|
1726
|
-
this.
|
|
1727
|
-
genBody();
|
|
2229
|
+
this._genScopedInit(stmt.init, genBody);
|
|
1728
2230
|
});
|
|
1729
2231
|
this.line("}");
|
|
2232
|
+
} else if (
|
|
2233
|
+
stmt.tag &&
|
|
2234
|
+
(this._isStructOrArrayType(stmt.tag._type) ||
|
|
2235
|
+
this._isIfaceType(stmt.tag._type) ||
|
|
2236
|
+
isComplex(stmt.tag._type) ||
|
|
2237
|
+
this._isMapTag(stmt.tag))
|
|
2238
|
+
) {
|
|
2239
|
+
// The tag temporary is a `const`, which cannot directly follow a label.
|
|
2240
|
+
this.line("{");
|
|
2241
|
+
this.indented(genBody);
|
|
2242
|
+
this.line("}");
|
|
1730
2243
|
} else {
|
|
1731
2244
|
genBody();
|
|
1732
2245
|
}
|
|
@@ -1737,7 +2250,13 @@ export const statementGenMethods = {
|
|
|
1737
2250
|
this.indented(() => {
|
|
1738
2251
|
const val = this.genExpr(stmt.expr);
|
|
1739
2252
|
this.line(`const __tsw = ${val};`);
|
|
1740
|
-
|
|
2253
|
+
// The binding is declared per clause, after the switch expression
|
|
2254
|
+
// was evaluated (`switch x := any(x).(type)` reads the outer `x`).
|
|
2255
|
+
const boxedBind = stmt.assign && this._boxedVars.has(stmt.assign);
|
|
2256
|
+
const bind = (valueJs) =>
|
|
2257
|
+
this.line(
|
|
2258
|
+
`let ${stmt.assign} = ${boxedBind ? `{ value: ${valueJs} }` : valueJs};`,
|
|
2259
|
+
);
|
|
1741
2260
|
|
|
1742
2261
|
let first = true;
|
|
1743
2262
|
let hasDefault = false;
|
|
@@ -1752,24 +2271,16 @@ export const statementGenMethods = {
|
|
|
1752
2271
|
.join(" || ");
|
|
1753
2272
|
this.line(`${first ? "if" : "else if"} (${cond}) {`);
|
|
1754
2273
|
this.indented(() => {
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
this.
|
|
1762
|
-
|
|
1763
|
-
);
|
|
1764
|
-
|
|
1765
|
-
else if (
|
|
1766
|
-
stmt.assign &&
|
|
1767
|
-
c.types.length === 1 &&
|
|
1768
|
-
this._typeFromNode(c.types[0])?.kind === "array"
|
|
1769
|
-
)
|
|
1770
|
-
this.line(
|
|
1771
|
-
`let ${stmt.assign} = ${this._cloneJs(this._typeFromNode(c.types[0]), "__tsw")};`,
|
|
1772
|
-
);
|
|
2274
|
+
if (stmt.assign) {
|
|
2275
|
+
const only = c.types.length === 1 ? c.types[0] : null;
|
|
2276
|
+
// A single named non-struct case binds the unboxed value.
|
|
2277
|
+
if (only && this._assertedValueJs(only, "__tsw") !== "__tsw")
|
|
2278
|
+
bind(this._assertedValueJs(only, "__tsw"));
|
|
2279
|
+
// An array case binds a copy of the boxed value.
|
|
2280
|
+
else if (only && this._typeFromNode(only)?.kind === "array")
|
|
2281
|
+
bind(this._cloneJs(this._typeFromNode(only), "__tsw"));
|
|
2282
|
+
else bind("__tsw");
|
|
2283
|
+
}
|
|
1773
2284
|
for (const s of c.stmts) this.genStmt(s);
|
|
1774
2285
|
});
|
|
1775
2286
|
this.line("}");
|
|
@@ -1778,6 +2289,7 @@ export const statementGenMethods = {
|
|
|
1778
2289
|
if (hasDefault) {
|
|
1779
2290
|
this.line(first ? "{" : "else {");
|
|
1780
2291
|
this.indented(() => {
|
|
2292
|
+
if (stmt.assign) bind("__tsw");
|
|
1781
2293
|
for (const s of hasDefault.stmts) this.genStmt(s);
|
|
1782
2294
|
});
|
|
1783
2295
|
this.line("}");
|