gofront 1.8.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/package.json +5 -4
- package/src/backend/js/expressions.js +849 -371
- package/src/backend/js/index.js +241 -147
- package/src/backend/js/natives.js +383 -0
- package/src/backend/js/runtime.js +512 -2547
- package/src/backend/js/statements.js +506 -103
- package/src/backend/js/stdlib/index.js +45 -93
- package/src/backend/js/templ.js +32 -45
- package/src/backend/js/type-info.js +177 -13
- package/src/backend/shared/fixed-float.js +20 -0
- package/src/backend/shared/fmt-types.js +63 -171
- package/src/backend/shared/host-unicode.js +36 -0
- package/src/backend/wasm/boundary.js +490 -965
- package/src/backend/wasm/emit-builtins.js +643 -256
- package/src/backend/wasm/emit-exprs.js +124 -57
- package/src/backend/wasm/emit-maps.js +17 -367
- package/src/backend/wasm/emit-stmts.js +325 -75
- package/src/backend/wasm/emit.js +104 -13
- package/src/backend/wasm/globals.js +47 -8
- package/src/backend/wasm/glue.js +2 -4
- package/src/backend/wasm/index.js +36 -530
- package/src/backend/wasm/method-expr.js +12 -1
- package/src/backend/wasm/module-emitter.js +92 -717
- package/src/backend/wasm/monomorph.js +103 -6
- package/src/backend/wasm/natives.js +596 -0
- package/src/backend/wasm/prescan.js +5 -153
- package/src/backend/wasm/prune-funcs.js +89 -0
- package/src/backend/wasm/stdlib/index.js +2 -32
- package/src/backend/wasm/types.js +68 -20
- package/src/backend/wasm/variadic.js +3 -3
- package/src/cli/cli-core.js +5 -1
- package/src/cli/test-runner.js +115 -342
- package/src/compiler.js +108 -130
- package/src/lexer/lexer.js +105 -11
- package/src/lexer/templ-lexer.js +61 -18
- package/src/lexer/tokens.js +1 -0
- package/src/link/stdlib.js +354 -132
- package/src/link/type-names.js +148 -0
- package/src/link/wasm.js +30 -6
- package/src/lower/bounds.js +153 -0
- package/src/lower/captures.js +92 -33
- package/src/lower/embedding.js +32 -3
- package/src/lower/errors-as.js +196 -0
- package/src/lower/fmt-formatters.js +905 -0
- package/src/lower/functions.js +2 -0
- package/src/lower/gen-types.js +274 -0
- package/src/lower/index.js +7 -1
- package/src/lower/json-codecs.js +228 -247
- package/src/lower/ownership.js +25 -7
- package/src/lower/reachability.js +69 -51
- package/src/lower/walk.js +12 -0
- package/src/parser/declarations.js +28 -5
- package/src/parser/expressions.js +86 -16
- package/src/parser/statements.js +1 -0
- package/src/parser/templ.js +44 -5
- package/src/parser/types.js +1 -0
- package/src/resolver.js +1 -2
- package/src/typechecker/assignability.js +124 -11
- package/src/typechecker/const-fold.js +70 -27
- package/src/typechecker/constants.js +32 -6
- package/src/typechecker/expressions.js +326 -98
- package/src/typechecker/index.js +208 -33
- package/src/typechecker/init-order.js +136 -40
- package/src/typechecker/operand-text.js +40 -0
- package/src/typechecker/resolve.js +192 -36
- package/src/typechecker/statements.js +122 -11
- package/src/typechecker/stdlib/excluded.js +9 -5
- package/src/typechecker/stdlib/index.js +25 -9
- package/src/typechecker/termination.js +10 -1
- package/src/typechecker/types.js +203 -573
- package/stdlib/bytes/buffer.go +479 -0
- package/stdlib/bytes/bytes.go +1010 -0
- package/stdlib/bytes/reader.go +161 -0
- package/stdlib/context/context.go +115 -0
- package/stdlib/crypto/rand/rand.go +34 -0
- package/stdlib/crypto/sha256/sha256.go +249 -0
- package/stdlib/encoding/base64/base64.go +331 -0
- package/stdlib/encoding/binary/binary.go +332 -0
- package/stdlib/encoding/hex/hex.go +140 -0
- package/stdlib/encoding/json/json.go +62 -0
- package/stdlib/errors/errors.go +174 -0
- package/stdlib/fmt/errors.go +110 -0
- package/stdlib/fmt/format.go +732 -0
- package/stdlib/fmt/print.go +1232 -0
- package/stdlib/gom/gom.go +293 -0
- package/stdlib/html/html.go +251 -0
- package/stdlib/internal/fmtrt/fmtrt.go +163 -0
- package/stdlib/internal/jsonrt/dec.go +171 -38
- package/stdlib/internal/jsonrt/enc.go +186 -36
- package/stdlib/internal/jsonrt/scanner.go +17 -8
- package/stdlib/io/io.go +157 -1
- package/stdlib/maps/maps.go +131 -0
- package/stdlib/math/bits/bits.go +507 -0
- package/stdlib/math/extra.go +16 -14
- package/stdlib/math/math.go +439 -0
- package/stdlib/math/rand/rand.go +297 -0
- package/stdlib/os/os.go +93 -0
- package/stdlib/path/filepath/filepath.go +47 -0
- package/stdlib/path/path.go +394 -0
- package/stdlib/regexp/regexp.go +231 -0
- package/stdlib/slices/slices.go +526 -0
- package/stdlib/slices/sort.go +209 -0
- package/stdlib/slices/zsort_cmp.go +480 -0
- package/stdlib/slices/zsort_ordered.go +482 -0
- package/stdlib/sort/sort.go +391 -452
- package/stdlib/sort/zsort_func.go +480 -0
- package/stdlib/sort/zsort_iface.go +480 -0
- package/stdlib/strconv/float.go +1699 -0
- package/stdlib/strconv/isprint.go +752 -0
- package/stdlib/strconv/quote.go +235 -50
- package/stdlib/strconv/strconv.go +616 -0
- package/stdlib/strings/builder.go +95 -0
- package/stdlib/strings/reader.go +163 -0
- package/stdlib/strings/replacer.go +102 -0
- package/stdlib/strings/strings.go +807 -0
- package/stdlib/sync/sync.go +268 -0
- package/stdlib/testing/async_js.go +61 -0
- package/stdlib/testing/benchmark.go +297 -0
- package/stdlib/testing/testing.go +523 -0
- package/stdlib/time/duration.go +233 -0
- package/stdlib/time/format.go +745 -0
- package/stdlib/time/parse.go +790 -0
- package/stdlib/time/sleep_js.go +63 -0
- package/stdlib/time/time.go +834 -0
- package/stdlib/unicode/unicode.go +141 -3
- package/stdlib/unicode/utf8/utf8.go +464 -0
- package/src/backend/js/fmt-runtime.js +0 -573
- package/src/backend/js/stdlib/base64.js +0 -12
- package/src/backend/js/stdlib/binary.js +0 -102
- package/src/backend/js/stdlib/bits.js +0 -119
- package/src/backend/js/stdlib/builder.js +0 -98
- package/src/backend/js/stdlib/bytes.js +0 -368
- package/src/backend/js/stdlib/errors.js +0 -45
- package/src/backend/js/stdlib/fmt.js +0 -517
- package/src/backend/js/stdlib/gom.js +0 -155
- package/src/backend/js/stdlib/hex.js +0 -17
- package/src/backend/js/stdlib/html.js +0 -20
- package/src/backend/js/stdlib/io.js +0 -137
- package/src/backend/js/stdlib/json.js +0 -1167
- package/src/backend/js/stdlib/maps.js +0 -37
- package/src/backend/js/stdlib/math.js +0 -85
- package/src/backend/js/stdlib/os.js +0 -13
- package/src/backend/js/stdlib/path.js +0 -48
- package/src/backend/js/stdlib/rand.js +0 -32
- package/src/backend/js/stdlib/regexp.js +0 -54
- package/src/backend/js/stdlib/sha256.js +0 -12
- package/src/backend/js/stdlib/shared.js +0 -17
- package/src/backend/js/stdlib/simd.js +0 -34
- package/src/backend/js/stdlib/slices.js +0 -107
- package/src/backend/js/stdlib/sort.js +0 -30
- package/src/backend/js/stdlib/strconv.js +0 -79
- package/src/backend/js/stdlib/strings.js +0 -301
- package/src/backend/js/stdlib/sync.js +0 -13
- package/src/backend/js/stdlib/testing.js +0 -41
- package/src/backend/js/stdlib/time.js +0 -152
- package/src/backend/js/stdlib/unicode.js +0 -25
- package/src/backend/js/stdlib/utf8.js +0 -26
- package/src/backend/wasm/stdlib/bytes.js +0 -6716
- package/src/backend/wasm/stdlib/core.js +0 -10278
- package/src/backend/wasm/stdlib/io.js +0 -3578
- package/src/backend/wasm/stdlib/packages.js +0 -2402
- package/src/backend/wasm/stdlib/pure.js +0 -720
- package/src/backend/wasm/stdlib/time.js +0 -2229
- package/src/typechecker/stdlib/core.js +0 -1079
- package/src/typechecker/stdlib/extended.js +0 -1210
- package/stdlib/bytes/iter.go +0 -192
- package/stdlib/bytes/search.go +0 -240
- package/stdlib/bytes/split.go +0 -218
- package/stdlib/errors/overlay.go +0 -12
- package/stdlib/maps/iter.go +0 -65
- package/stdlib/slices/extra.go +0 -73
- package/stdlib/slices/iter.go +0 -113
- package/stdlib/strings/extra.go +0 -35
- package/stdlib/strings/iter.go +0 -122
- package/stdlib/sync/extra.go +0 -105
- package/stdlib/unicode/utf8/bytes.go +0 -239
|
@@ -1,16 +1,20 @@
|
|
|
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
|
+
isTypedArraySlice,
|
|
17
|
+
} from "../../typechecker/types.js";
|
|
14
18
|
|
|
15
19
|
/** @typedef {import('./index.js').CodeGen} CodeGen */
|
|
16
20
|
|
|
@@ -124,6 +128,20 @@ const isReusableIndex = (e, vars, boxed) => {
|
|
|
124
128
|
}
|
|
125
129
|
};
|
|
126
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
|
+
|
|
127
145
|
// Method-name dispatch for genStmt — string values add no static call edges.
|
|
128
146
|
const STMT_GEN_DELEGATE = {
|
|
129
147
|
VarDecl: "genVarDecl",
|
|
@@ -135,19 +153,27 @@ const STMT_GEN_DELEGATE = {
|
|
|
135
153
|
SwitchStmt: "genSwitch",
|
|
136
154
|
TypeSwitchStmt: "genTypeSwitch",
|
|
137
155
|
BranchStmt: "_genBranchStmt",
|
|
156
|
+
TemplMount: "_genTemplMountStmt",
|
|
138
157
|
};
|
|
139
158
|
|
|
140
159
|
/** @type {ThisType<CodeGen>} */
|
|
141
160
|
export const statementGenMethods = {
|
|
142
161
|
genBlock(block) {
|
|
143
162
|
let nested = 0;
|
|
163
|
+
const earlier = new Set();
|
|
144
164
|
for (const stmt of block.stmts) {
|
|
145
165
|
if (this._definesShadowedRead(stmt)) {
|
|
146
166
|
this._genShadowingDefine(stmt);
|
|
147
167
|
nested++;
|
|
168
|
+
} else if (this._definesAfterOuterUse(stmt, earlier)) {
|
|
169
|
+
this.line("{");
|
|
170
|
+
this.indent++;
|
|
171
|
+
nested++;
|
|
172
|
+
this.genStmt(stmt);
|
|
148
173
|
} else {
|
|
149
174
|
this.genStmt(stmt);
|
|
150
175
|
}
|
|
176
|
+
collectIdentNames(stmt, earlier);
|
|
151
177
|
}
|
|
152
178
|
for (; nested > 0; nested--) {
|
|
153
179
|
this.indent--;
|
|
@@ -155,11 +181,20 @@ export const statementGenMethods = {
|
|
|
155
181
|
}
|
|
156
182
|
},
|
|
157
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
|
+
|
|
158
194
|
// `x := f(x)` declaring a new `x` that its own right-hand side reads (the
|
|
159
195
|
// outer `x`): JS `let` would put the read in its temporal dead zone.
|
|
160
196
|
_definesShadowedRead(stmt) {
|
|
161
|
-
if (stmt.kind !== "DefineStmt"
|
|
162
|
-
return false;
|
|
197
|
+
if (stmt.kind !== "DefineStmt") return false;
|
|
163
198
|
const names = new Set(
|
|
164
199
|
stmt.lhs.filter((l) => !l._redecl && l.name !== "_").map((l) => l.name),
|
|
165
200
|
);
|
|
@@ -173,9 +208,35 @@ export const statementGenMethods = {
|
|
|
173
208
|
return stmt.rhs.some(reads);
|
|
174
209
|
},
|
|
175
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
|
+
|
|
176
224
|
// Evaluates the right-hand side in the enclosing scope, then opens a nested
|
|
177
225
|
// block (closed at the end of the enclosing block) that declares the names.
|
|
178
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
|
+
}
|
|
179
240
|
const rhs = this._defineRhs(stmt);
|
|
180
241
|
this._tmpCounter = (this._tmpCounter ?? 0) + 1;
|
|
181
242
|
const tmp = `__t${this._tmpCounter}`;
|
|
@@ -189,24 +250,44 @@ export const statementGenMethods = {
|
|
|
189
250
|
);
|
|
190
251
|
},
|
|
191
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
|
+
);
|
|
262
|
+
},
|
|
263
|
+
|
|
192
264
|
// Shared helper for comma-ok map index in both DefineStmt and AssignStmt.
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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);
|
|
196
270
|
const [vName, okName] = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
|
|
197
|
-
|
|
198
|
-
//
|
|
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.
|
|
199
281
|
const valType = rhsNode._mapValueType;
|
|
200
282
|
const copyVal = (js) =>
|
|
201
|
-
this._aggregateBase(valType)
|
|
202
|
-
? this._cloneJs(valType, js)
|
|
203
|
-
: js;
|
|
283
|
+
this._aggregateBase(valType) ? this._cloneJs(valType, js) : js;
|
|
204
284
|
if (isDefine && !this.strict) {
|
|
205
285
|
if (vName !== "_") {
|
|
206
|
-
this.
|
|
286
|
+
const val = this._nonStrictMapRead(mapExpr, keyExpr, valType, copyVal);
|
|
287
|
+
this.line(bind(vL, vName, val));
|
|
207
288
|
}
|
|
208
289
|
if (okName !== "_") {
|
|
209
|
-
this.line(
|
|
290
|
+
this.line(bind(okL, okName, `(${keyExpr}) in ${mapExpr}`));
|
|
210
291
|
}
|
|
211
292
|
return;
|
|
212
293
|
}
|
|
@@ -219,11 +300,15 @@ export const statementGenMethods = {
|
|
|
219
300
|
this.line(`const ${mTmp} = ${mapExpr}, ${kTmp} = ${keyExpr};`);
|
|
220
301
|
if (vName !== "_") {
|
|
221
302
|
this.line(
|
|
222
|
-
|
|
303
|
+
bind(
|
|
304
|
+
vL,
|
|
305
|
+
vName,
|
|
306
|
+
`(${mTmp} && (${kTmp} in ${mTmp})) ? ${copyVal(`${mTmp}[${kTmp}]`)} : ${zero}`,
|
|
307
|
+
),
|
|
223
308
|
);
|
|
224
309
|
}
|
|
225
310
|
if (okName !== "_") {
|
|
226
|
-
this.line(
|
|
311
|
+
this.line(bind(okL, okName, `Boolean(${mTmp} && (${kTmp} in ${mTmp}))`));
|
|
227
312
|
}
|
|
228
313
|
},
|
|
229
314
|
|
|
@@ -240,8 +325,11 @@ export const statementGenMethods = {
|
|
|
240
325
|
: null;
|
|
241
326
|
switch (stmt.kind) {
|
|
242
327
|
case "TypeDecl":
|
|
243
|
-
|
|
244
|
-
|
|
328
|
+
{
|
|
329
|
+
const start = this.out.length;
|
|
330
|
+
this.genTypeDeclWithMethods(stmt, []);
|
|
331
|
+
this._hoistLocalClass(stmt, start);
|
|
332
|
+
}
|
|
245
333
|
break;
|
|
246
334
|
case "IncDecStmt": {
|
|
247
335
|
this._markIndexLvalues([stmt.expr]);
|
|
@@ -257,28 +345,47 @@ export const statementGenMethods = {
|
|
|
257
345
|
: this.genExpr(stmt.expr.expr);
|
|
258
346
|
const idx = this.genExpr(stmt.expr.index);
|
|
259
347
|
const op = stmt.op === "++" ? "+" : "-";
|
|
348
|
+
const is64Val = is64BitInt(stmt.expr._mapValueType);
|
|
349
|
+
const delta = is64Val ? "1n" : "1";
|
|
260
350
|
if (
|
|
261
351
|
this._hasCallExpr(stmt.expr.expr) ||
|
|
262
352
|
this._hasCallExpr(stmt.expr.index)
|
|
263
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`;
|
|
264
362
|
this.line(
|
|
265
|
-
`((__m, __k) => { ${this._genMapWriteBase("__m")}[__k] =
|
|
363
|
+
`((__m, __k) => { ${this._genMapWriteBase("__m")}[__k] = ${rhs}; })(${base}, ${idx});`,
|
|
266
364
|
srcLine,
|
|
267
365
|
);
|
|
268
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`;
|
|
269
375
|
this.line(
|
|
270
|
-
`${this._genMapWriteBase(base)}[${idx}] =
|
|
376
|
+
`${this._genMapWriteBase(base)}[${idx}] = ${rhs};`,
|
|
271
377
|
srcLine,
|
|
272
378
|
);
|
|
273
379
|
}
|
|
274
380
|
break;
|
|
275
381
|
}
|
|
382
|
+
const is64 = is64BitInt(stmt.expr._type);
|
|
276
383
|
if (
|
|
277
|
-
this.strict &&
|
|
384
|
+
(this.strict || is64 || this._isFloat32(stmt.expr._type)) &&
|
|
278
385
|
this._genStrictCompound(
|
|
279
386
|
stmt.expr,
|
|
280
387
|
stmt.op === "++" ? "+" : "-",
|
|
281
|
-
"1",
|
|
388
|
+
is64 ? "1n" : "1",
|
|
282
389
|
srcLine,
|
|
283
390
|
)
|
|
284
391
|
)
|
|
@@ -293,14 +400,7 @@ export const statementGenMethods = {
|
|
|
293
400
|
if (stmt.init) {
|
|
294
401
|
this.line("{");
|
|
295
402
|
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
|
-
}
|
|
403
|
+
this._genScopedInit(stmt.init, () => this._genIf(stmt));
|
|
304
404
|
});
|
|
305
405
|
this.line("}");
|
|
306
406
|
} else {
|
|
@@ -308,15 +408,26 @@ export const statementGenMethods = {
|
|
|
308
408
|
}
|
|
309
409
|
break;
|
|
310
410
|
case "DeferStmt":
|
|
311
|
-
|
|
411
|
+
// A deferred native call runs the Go body.
|
|
412
|
+
this._withoutNatives(() =>
|
|
413
|
+
this.line(`__defers.push(() => { ${this.genExpr(stmt.call)}; });`),
|
|
414
|
+
);
|
|
312
415
|
break;
|
|
313
|
-
case "LabeledStmt":
|
|
314
|
-
|
|
315
|
-
|
|
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;
|
|
316
423
|
}
|
|
317
|
-
|
|
318
|
-
this.
|
|
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();
|
|
319
429
|
break;
|
|
430
|
+
}
|
|
320
431
|
case "Block":
|
|
321
432
|
this.line("{");
|
|
322
433
|
this.indented(() => this.genBlock(stmt));
|
|
@@ -695,6 +806,10 @@ export const statementGenMethods = {
|
|
|
695
806
|
this._genStableWholeAssign(stmt);
|
|
696
807
|
return;
|
|
697
808
|
}
|
|
809
|
+
if (this._isStableTupleAssign(stmt)) {
|
|
810
|
+
this._genStableTupleAssign(stmt);
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
698
813
|
const paired = stmt.rhs.length === stmt.lhs.length && stmt.op === "=";
|
|
699
814
|
const rhs = stmt.rhs.map((e, i) =>
|
|
700
815
|
paired && stmt.lhs[i].name !== "_"
|
|
@@ -724,29 +839,65 @@ export const statementGenMethods = {
|
|
|
724
839
|
: this.genExpr(stmt.lhs[0].expr);
|
|
725
840
|
const idx = this.genExpr(stmt.lhs[0].index);
|
|
726
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})`;
|
|
727
868
|
if (
|
|
728
869
|
this._hasCallExpr(stmt.lhs[0].expr) ||
|
|
729
870
|
this._hasCallExpr(stmt.lhs[0].index)
|
|
730
871
|
) {
|
|
731
872
|
this.line(
|
|
732
|
-
`((__m, __k) => { ${this._genMapWriteBase("__m")}[__k] =
|
|
873
|
+
`((__m, __k) => { ${this._genMapWriteBase("__m")}[__k] = ${rhsExpr}; })(${base}, ${idx});`,
|
|
733
874
|
);
|
|
734
875
|
} else {
|
|
735
876
|
this.line(
|
|
736
|
-
`${this._genMapWriteBase(base)}[${idx}] =
|
|
877
|
+
`${this._genMapWriteBase(base)}[${idx}] = ${rhsExprDirect};`,
|
|
737
878
|
);
|
|
738
879
|
}
|
|
739
880
|
return;
|
|
740
881
|
}
|
|
882
|
+
const is64 = is64BitInt(stmt.lhs[0]._type);
|
|
883
|
+
let rhsVal = active[0].r;
|
|
741
884
|
if (
|
|
742
|
-
|
|
743
|
-
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) &&
|
|
744
894
|
this._genStrictCompound(
|
|
745
895
|
stmt.lhs[0],
|
|
746
896
|
stmt.op.slice(0, -1),
|
|
747
|
-
|
|
897
|
+
rhsVal,
|
|
748
898
|
null,
|
|
749
899
|
active[0].l,
|
|
900
|
+
stmt.rhs[0]?._type,
|
|
750
901
|
)
|
|
751
902
|
) {
|
|
752
903
|
return;
|
|
@@ -755,7 +906,8 @@ export const statementGenMethods = {
|
|
|
755
906
|
active[0].l.startsWith("{") && !active[0].l.startsWith("({")
|
|
756
907
|
? `(${active[0].l})`
|
|
757
908
|
: active[0].l;
|
|
758
|
-
this.line(`${targetL}
|
|
909
|
+
if (stmt.op === "&^=") this.line(`${targetL} &= ~(${rhsVal});`);
|
|
910
|
+
else this.line(`${targetL} ${stmt.op} ${rhsVal};`);
|
|
759
911
|
} else if (hoisted) {
|
|
760
912
|
this.line("{");
|
|
761
913
|
this.indented(() => {
|
|
@@ -786,23 +938,28 @@ export const statementGenMethods = {
|
|
|
786
938
|
((n.kind === "Ident" && assigned.has(n.name)) || someChild(n, mentions));
|
|
787
939
|
const lines = [];
|
|
788
940
|
const undo = [];
|
|
789
|
-
|
|
941
|
+
const visit = (l) => {
|
|
790
942
|
let keys;
|
|
791
943
|
if (l.kind === "IndexExpr") keys = ["expr", "index"];
|
|
792
944
|
else if (l.kind === "SelectorExpr" && !l._isMethodExpr) keys = ["expr"];
|
|
793
|
-
else
|
|
945
|
+
else return;
|
|
794
946
|
for (const key of keys) {
|
|
795
947
|
const operand = l[key];
|
|
796
948
|
if (!mentions(operand)) continue;
|
|
797
|
-
// A struct or array value names the variable's storage in Go
|
|
798
|
-
|
|
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
|
+
}
|
|
799
955
|
this._tmpCounter = (this._tmpCounter ?? 0) + 1;
|
|
800
956
|
const name = `__h${this._tmpCounter}`;
|
|
801
957
|
lines.push(`const ${name} = ${this.genExpr(operand)};`);
|
|
802
958
|
undo.push([l, key, operand]);
|
|
803
959
|
l[key] = { kind: "Ident", name, _type: operand._type };
|
|
804
960
|
}
|
|
805
|
-
}
|
|
961
|
+
};
|
|
962
|
+
for (const l of stmt.lhs) visit(l);
|
|
806
963
|
if (lines.length === 0) return null;
|
|
807
964
|
return {
|
|
808
965
|
lines,
|
|
@@ -814,7 +971,7 @@ export const statementGenMethods = {
|
|
|
814
971
|
|
|
815
972
|
_genCommaOkTypeAssert(stmt) {
|
|
816
973
|
const val = this.genExpr(stmt.rhs[0]);
|
|
817
|
-
const [vName, okName] = stmt.lhs.map((e) =>
|
|
974
|
+
const [vName, okName] = stmt.lhs.map((e) => this._genAssignLhsExpr(e));
|
|
818
975
|
this._tmpCounter = (this._tmpCounter ?? 0) + 1;
|
|
819
976
|
const tmp = `__ta${this._tmpCounter}`;
|
|
820
977
|
this.line(`const ${tmp} = ${val};`);
|
|
@@ -828,7 +985,13 @@ export const statementGenMethods = {
|
|
|
828
985
|
_isStableWholeAssign(stmt) {
|
|
829
986
|
if (stmt.op !== "=" || stmt.lhs.length !== 1 || stmt.rhs.length !== 1)
|
|
830
987
|
return false;
|
|
831
|
-
|
|
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) {
|
|
832
995
|
if (l.kind === "IndexExpr" && l._mapValueType) return false;
|
|
833
996
|
if (
|
|
834
997
|
l.kind !== "Ident" &&
|
|
@@ -837,15 +1000,59 @@ export const statementGenMethods = {
|
|
|
837
1000
|
)
|
|
838
1001
|
return false;
|
|
839
1002
|
if (l.kind === "Ident" && l.name === "_") return false;
|
|
840
|
-
if (
|
|
841
|
-
!this._aggregateBase(l._type) ||
|
|
842
|
-
!this._aggregateBase(stmt.rhs[0]._type)
|
|
843
|
-
)
|
|
1003
|
+
if (!this._aggregateBase(l._type) || !this._aggregateBase(rhs._type))
|
|
844
1004
|
return false;
|
|
1005
|
+
if (this._elemAddrTaken && this._isSliceElemTarget(l)) return true;
|
|
845
1006
|
const root = this._rootIdentName(l);
|
|
846
1007
|
return root !== null && this._fnMutates(root, true);
|
|
847
1008
|
},
|
|
848
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
|
+
|
|
849
1056
|
_genStableWholeAssign(stmt) {
|
|
850
1057
|
let target = this._genAssignLhsExpr(stmt.lhs[0]);
|
|
851
1058
|
const type = stmt.lhs[0]._type;
|
|
@@ -1063,7 +1270,12 @@ export const statementGenMethods = {
|
|
|
1063
1270
|
this.line("}");
|
|
1064
1271
|
return;
|
|
1065
1272
|
}
|
|
1066
|
-
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") {
|
|
1067
1279
|
this.line(`} else if (${this.genExpr(elseBody.cond)}) {`);
|
|
1068
1280
|
this.indented(() => this.genBlock(elseBody.body));
|
|
1069
1281
|
this._genElse(elseBody.elseBody);
|
|
@@ -1076,7 +1288,8 @@ export const statementGenMethods = {
|
|
|
1076
1288
|
|
|
1077
1289
|
_rangeExprLimit(iterType, iteree) {
|
|
1078
1290
|
if (this._isIntRangeType(iterType)) return iteree;
|
|
1079
|
-
if (this._isMapRangeType(iterType))
|
|
1291
|
+
if (this._isMapRangeType(iterType))
|
|
1292
|
+
return `Object.keys(${iteree} ?? {}).length`;
|
|
1080
1293
|
return `(${iteree} ? ${iteree}.length : 0)`;
|
|
1081
1294
|
},
|
|
1082
1295
|
|
|
@@ -1085,7 +1298,7 @@ export const statementGenMethods = {
|
|
|
1085
1298
|
this.genIteratorForCond(stmt);
|
|
1086
1299
|
return;
|
|
1087
1300
|
}
|
|
1088
|
-
const iterType = stmt.cond.expr._type;
|
|
1301
|
+
const iterType = stmt.cond.expr._coreType ?? stmt.cond.expr._type;
|
|
1089
1302
|
const iteree = this.genExpr(stmt.cond.expr);
|
|
1090
1303
|
const limit = this._rangeExprLimit(iterType, iteree);
|
|
1091
1304
|
this.line(`for (let _$ = 0; _$ < ${limit}; _$++) {`);
|
|
@@ -1103,7 +1316,47 @@ export const statementGenMethods = {
|
|
|
1103
1316
|
this.line("}");
|
|
1104
1317
|
},
|
|
1105
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
|
+
|
|
1106
1349
|
genFor(stmt) {
|
|
1350
|
+
const hoisted = this._hoistForInit(stmt);
|
|
1351
|
+
try {
|
|
1352
|
+
this._genForLoop(stmt);
|
|
1353
|
+
} finally {
|
|
1354
|
+
delete stmt._initPre;
|
|
1355
|
+
}
|
|
1356
|
+
if (hoisted) this._closeHoistedForInit();
|
|
1357
|
+
},
|
|
1358
|
+
|
|
1359
|
+
_genForLoop(stmt) {
|
|
1107
1360
|
const isInner = this._inIteratorBody;
|
|
1108
1361
|
if (isInner) {
|
|
1109
1362
|
this._iterLoopDepth = (this._iterLoopDepth ?? 0) + 1;
|
|
@@ -1126,7 +1379,7 @@ export const statementGenMethods = {
|
|
|
1126
1379
|
this._genSimpleWhileFor(stmt);
|
|
1127
1380
|
return;
|
|
1128
1381
|
}
|
|
1129
|
-
const init = stmt.init ? this.stmtInline(stmt.init) : "";
|
|
1382
|
+
const init = stmt.init ? this.stmtInline(stmt.init, stmt._initPre) : "";
|
|
1130
1383
|
const cond = stmt.cond ? this.genExpr(stmt.cond) : "";
|
|
1131
1384
|
const post = this._perIterationClones(
|
|
1132
1385
|
stmt,
|
|
@@ -1217,10 +1470,14 @@ export const statementGenMethods = {
|
|
|
1217
1470
|
const iterType = iterNode._type;
|
|
1218
1471
|
const base = iterType?.kind === "named" ? iterType.underlying : iterType;
|
|
1219
1472
|
const elemType = base?.elem;
|
|
1220
|
-
if (
|
|
1221
|
-
|
|
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")
|
|
1222
1480
|
return this._cloneJs(elemType, js);
|
|
1223
|
-
if (valNode.name === "_") return js;
|
|
1224
1481
|
const root = this._rootIdentName(iterNode);
|
|
1225
1482
|
const mutated =
|
|
1226
1483
|
this._fnMutates(valNode.name) ||
|
|
@@ -1311,11 +1568,6 @@ export const statementGenMethods = {
|
|
|
1311
1568
|
this._loopDepth--;
|
|
1312
1569
|
},
|
|
1313
1570
|
|
|
1314
|
-
// True when `node` contains an assignment or ++/-- targeting identifier `name`.
|
|
1315
|
-
_nodeAssigns(node, name) {
|
|
1316
|
-
return nodeAssigns(node, name);
|
|
1317
|
-
},
|
|
1318
|
-
|
|
1319
1571
|
_genStringRangeFor(stmt, iteree, isAssign) {
|
|
1320
1572
|
this._loopDepth = this._loopDepth ?? 0;
|
|
1321
1573
|
const d = this._loopDepth++;
|
|
@@ -1323,13 +1575,17 @@ export const statementGenMethods = {
|
|
|
1323
1575
|
const s = `__s${d}`;
|
|
1324
1576
|
const i = `__i${d}`;
|
|
1325
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.
|
|
1326
1581
|
this.line(
|
|
1327
|
-
`for (let ${i} = 0, ${s} =
|
|
1582
|
+
`for (let ${i} = 0, ${s} = ${iteree} ?? "", ${w} = 0; ${i} < ${s}.length; ${i} += ${w}) {`,
|
|
1328
1583
|
);
|
|
1329
1584
|
this.indented(() => {
|
|
1585
|
+
this.line(`const ${cp} = ${s}.codePointAt(${i});`);
|
|
1586
|
+
this.line(`${w} = ${cp} > 0xffff ? 2 : 1;`);
|
|
1330
1587
|
this._emitRangeVar(idxNode, i, isAssign);
|
|
1331
1588
|
if (valNode && valNode.name !== "_") {
|
|
1332
|
-
this.line(`const ${cp} = ${s}[${i}].codePointAt(0);`);
|
|
1333
1589
|
this._emitRangeVar(valNode, cp, isAssign);
|
|
1334
1590
|
}
|
|
1335
1591
|
const declared = isAssign
|
|
@@ -1352,23 +1608,73 @@ export const statementGenMethods = {
|
|
|
1352
1608
|
_genIterFor(init, iterType, iteree, lhs, body) {
|
|
1353
1609
|
const iterExpr = this._genRangeIterExpr(iterType, iteree, lhs);
|
|
1354
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 ? "" : "_$";
|
|
1355
1618
|
const binding =
|
|
1356
1619
|
lhs.length === 1
|
|
1357
1620
|
? lhs[0] === "_"
|
|
1358
1621
|
? "_$"
|
|
1359
1622
|
: lhs[0]
|
|
1360
|
-
: `[${lhs.map((n) => (n === "_" ?
|
|
1361
|
-
const kw =
|
|
1623
|
+
: `[${lhs.map((n) => (n === "_" ? blank : n)).join(", ")}]`;
|
|
1624
|
+
const kw = !isAssign || binding === "_$" ? "let " : "";
|
|
1362
1625
|
this.line(`for (${kw}${binding} of ${iterExpr}) {`);
|
|
1363
1626
|
this.indented(() => {
|
|
1364
|
-
if (
|
|
1627
|
+
if (paramKey && lhs[0] !== "_")
|
|
1628
|
+
this.line(`${lhs[0]} = __kdt(${paramKey}, ${lhs[0]});`);
|
|
1629
|
+
else if (keyDecode && lhs[0] !== "_")
|
|
1365
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
|
+
);
|
|
1366
1639
|
this._cloneMapRangeValue(init, iterType, lhs[1]);
|
|
1367
1640
|
this.genBlock(body);
|
|
1368
1641
|
});
|
|
1369
1642
|
this.line("}");
|
|
1370
1643
|
},
|
|
1371
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
|
+
|
|
1372
1678
|
// Ranging over a map whose keys are structs: the key variable is decoded
|
|
1373
1679
|
// from its encoded string (see HELPER_STRUCT_KEY).
|
|
1374
1680
|
_structKeyRange(iterType) {
|
|
@@ -1376,7 +1682,13 @@ export const statementGenMethods = {
|
|
|
1376
1682
|
const base = iterType?.kind === "named" ? iterType.underlying : iterType;
|
|
1377
1683
|
const key = base?.kind === "map" ? base.key : null;
|
|
1378
1684
|
const keyBase = key?.kind === "named" ? key.underlying : key;
|
|
1379
|
-
if (
|
|
1685
|
+
if (
|
|
1686
|
+
keyBase?.kind !== "struct" &&
|
|
1687
|
+
keyBase?.kind !== "array" &&
|
|
1688
|
+
keyBase?.kind !== "pointer" &&
|
|
1689
|
+
!this._isIfaceType(key)
|
|
1690
|
+
)
|
|
1691
|
+
return false;
|
|
1380
1692
|
this.useHelper("structKey");
|
|
1381
1693
|
return true;
|
|
1382
1694
|
},
|
|
@@ -1396,7 +1708,7 @@ export const statementGenMethods = {
|
|
|
1396
1708
|
const init = stmt.init;
|
|
1397
1709
|
const range = init.rhs[0];
|
|
1398
1710
|
const lhs = init.lhs.map((e) => e.name ?? this.genExpr(e));
|
|
1399
|
-
const iterType = range.expr._type;
|
|
1711
|
+
const iterType = range.expr._coreType ?? range.expr._type;
|
|
1400
1712
|
const wrapField = this._namedWrapperField(iterType, range.expr);
|
|
1401
1713
|
const iteree = wrapField
|
|
1402
1714
|
? `${this.genExpr(range.expr)}.${wrapField}`
|
|
@@ -1431,9 +1743,22 @@ export const statementGenMethods = {
|
|
|
1431
1743
|
!!valNode &&
|
|
1432
1744
|
iterBase?.kind === "slice" &&
|
|
1433
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);
|
|
1434
1755
|
this._genSliceRangeFor(
|
|
1435
1756
|
stmt,
|
|
1436
|
-
|
|
1757
|
+
indexOnlyArray
|
|
1758
|
+
? `({ length: ${arrayLen} })`
|
|
1759
|
+
: rangesArrayValue
|
|
1760
|
+
? this._cloneJs(iterType, iteree)
|
|
1761
|
+
: iteree,
|
|
1437
1762
|
init.kind === "AssignStmt",
|
|
1438
1763
|
unwrapView,
|
|
1439
1764
|
);
|
|
@@ -1441,8 +1766,9 @@ export const statementGenMethods = {
|
|
|
1441
1766
|
|
|
1442
1767
|
_genRangeIterExpr(iterType, iteree, lhs) {
|
|
1443
1768
|
if (this._isMapRangeType(iterType)) {
|
|
1444
|
-
|
|
1445
|
-
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} ?? {})`;
|
|
1446
1772
|
}
|
|
1447
1773
|
this.useHelper("sliceGuard");
|
|
1448
1774
|
if (lhs.length === 1) return `__s(${iteree}).keys()`;
|
|
@@ -1610,10 +1936,10 @@ export const statementGenMethods = {
|
|
|
1610
1936
|
},
|
|
1611
1937
|
|
|
1612
1938
|
// Inline a statement as a for-init/post string (no semicolon)
|
|
1613
|
-
stmtInline(stmt) {
|
|
1939
|
+
stmtInline(stmt, pre = null) {
|
|
1614
1940
|
switch (stmt.kind) {
|
|
1615
1941
|
case "DefineStmt":
|
|
1616
|
-
return this._inlineDefine(stmt);
|
|
1942
|
+
return this._inlineDefine(stmt, pre);
|
|
1617
1943
|
case "AssignStmt":
|
|
1618
1944
|
return this._withInlineSyncs(
|
|
1619
1945
|
this._resliceSyncs(stmt.lhs, stmt, true),
|
|
@@ -1623,6 +1949,14 @@ export const statementGenMethods = {
|
|
|
1623
1949
|
const syncs = this._resliceSyncs([stmt.expr], stmt, true);
|
|
1624
1950
|
this._markIndexLvalues([stmt.expr]);
|
|
1625
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
|
+
}
|
|
1626
1960
|
return this._withInlineSyncs(
|
|
1627
1961
|
syncs,
|
|
1628
1962
|
`${this.genExpr(stmt.expr)}${stmt.op}`,
|
|
@@ -1639,14 +1973,14 @@ export const statementGenMethods = {
|
|
|
1639
1973
|
return syncs ? `${js}, ${syncs.join(", ")}` : js;
|
|
1640
1974
|
},
|
|
1641
1975
|
|
|
1642
|
-
_inlineDefine(stmt) {
|
|
1976
|
+
_inlineDefine(stmt, pre = null) {
|
|
1643
1977
|
const boxed =
|
|
1644
1978
|
stmt.lhs.length === 1 &&
|
|
1645
1979
|
stmt.rhs.length === 1 &&
|
|
1646
1980
|
this._boxedVars.has(stmt.lhs[0].name);
|
|
1647
1981
|
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));
|
|
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));
|
|
1650
1984
|
const lhs = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
|
|
1651
1985
|
if (lhs.length === 1) return `let ${lhs[0]} = ${rhs[0]}`;
|
|
1652
1986
|
// `for i, j := 0, 10`: one declarator per variable.
|
|
@@ -1661,11 +1995,62 @@ export const statementGenMethods = {
|
|
|
1661
1995
|
return `let [${lhs.map((n) => (n === "_" ? "" : n)).join(", ")}] = ${rhs[0]}`;
|
|
1662
1996
|
},
|
|
1663
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
|
+
|
|
1664
2029
|
_inlineAssign(stmt) {
|
|
1665
2030
|
this._markIndexLvalues(stmt.lhs);
|
|
1666
2031
|
const rhs = stmt.rhs.map((e) => this.genExpr(e));
|
|
1667
|
-
if (stmt.lhs.length === 1)
|
|
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]})`;
|
|
1668
2052
|
return `${this.genExpr(stmt.lhs[0])} ${stmt.op} ${rhs[0]}`;
|
|
2053
|
+
}
|
|
1669
2054
|
// `i, j = i+1, j-1`: every right-hand side is evaluated first.
|
|
1670
2055
|
const lhs = stmt.lhs.map((e) =>
|
|
1671
2056
|
e.name === "_" ? "" : this._genAssignLhsExpr(e),
|
|
@@ -1677,6 +2062,17 @@ export const statementGenMethods = {
|
|
|
1677
2062
|
// Interface-typed tags may hold boxed named values; cases compare with __ifeq.
|
|
1678
2063
|
_switchTagAndCases(stmt) {
|
|
1679
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
|
+
}
|
|
1680
2076
|
if (!stmt.tag || !this._isIfaceType(stmt.tag._type))
|
|
1681
2077
|
return { tag, caseExpr: (e) => this.genExpr(e) };
|
|
1682
2078
|
this.useHelper("ifeq");
|
|
@@ -1704,7 +2100,7 @@ export const statementGenMethods = {
|
|
|
1704
2100
|
// across sibling cases (JS switch shares one block scope).
|
|
1705
2101
|
this.line("{");
|
|
1706
2102
|
this.indented(() => {
|
|
1707
|
-
|
|
2103
|
+
this.genBlock(c);
|
|
1708
2104
|
// Go doesn't fall through by default — add break unless last stmt is return/break
|
|
1709
2105
|
const last = c.stmts[c.stmts.length - 1];
|
|
1710
2106
|
if (
|
|
@@ -1723,10 +2119,18 @@ export const statementGenMethods = {
|
|
|
1723
2119
|
if (stmt.init) {
|
|
1724
2120
|
this.line("{");
|
|
1725
2121
|
this.indented(() => {
|
|
1726
|
-
this.
|
|
1727
|
-
genBody();
|
|
2122
|
+
this._genScopedInit(stmt.init, genBody);
|
|
1728
2123
|
});
|
|
1729
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("}");
|
|
1730
2134
|
} else {
|
|
1731
2135
|
genBody();
|
|
1732
2136
|
}
|
|
@@ -1737,7 +2141,13 @@ export const statementGenMethods = {
|
|
|
1737
2141
|
this.indented(() => {
|
|
1738
2142
|
const val = this.genExpr(stmt.expr);
|
|
1739
2143
|
this.line(`const __tsw = ${val};`);
|
|
1740
|
-
|
|
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
|
+
);
|
|
1741
2151
|
|
|
1742
2152
|
let first = true;
|
|
1743
2153
|
let hasDefault = false;
|
|
@@ -1752,24 +2162,16 @@ export const statementGenMethods = {
|
|
|
1752
2162
|
.join(" || ");
|
|
1753
2163
|
this.line(`${first ? "if" : "else if"} (${cond}) {`);
|
|
1754
2164
|
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
|
-
);
|
|
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
|
+
}
|
|
1773
2175
|
for (const s of c.stmts) this.genStmt(s);
|
|
1774
2176
|
});
|
|
1775
2177
|
this.line("}");
|
|
@@ -1778,6 +2180,7 @@ export const statementGenMethods = {
|
|
|
1778
2180
|
if (hasDefault) {
|
|
1779
2181
|
this.line(first ? "{" : "else {");
|
|
1780
2182
|
this.indented(() => {
|
|
2183
|
+
if (stmt.assign) bind("__tsw");
|
|
1781
2184
|
for (const s of hasDefault.stmts) this.genStmt(s);
|
|
1782
2185
|
});
|
|
1783
2186
|
this.line("}");
|