gofront 1.6.1 → 1.8.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 +45 -49
- package/package.json +12 -8
- package/src/backend/js/expressions.js +1627 -148
- package/src/backend/js/fmt-runtime.js +573 -0
- package/src/backend/js/index.js +508 -102
- package/src/backend/js/runtime.js +1997 -106
- package/src/backend/js/statements.js +931 -87
- package/src/backend/js/stdlib/bits.js +87 -14
- package/src/backend/js/stdlib/builder.js +41 -6
- package/src/backend/js/stdlib/bytes.js +253 -7
- package/src/backend/js/stdlib/errors.js +7 -1
- package/src/backend/js/stdlib/fmt.js +349 -10
- package/src/backend/js/stdlib/index.js +19 -2
- package/src/backend/js/stdlib/json.js +1167 -0
- package/src/backend/js/stdlib/maps.js +0 -8
- package/src/backend/js/stdlib/math.js +17 -1
- package/src/backend/js/stdlib/simd.js +34 -0
- package/src/backend/js/stdlib/slices.js +36 -6
- package/src/backend/js/stdlib/strconv.js +10 -5
- package/src/backend/js/stdlib/strings.js +83 -4
- package/src/backend/js/stdlib/testing.js +7 -0
- package/src/backend/js/stdlib/time.js +79 -55
- package/src/backend/js/stdlib/unicode.js +1 -1
- package/src/backend/js/stdlib/utf8.js +1 -1
- package/src/backend/js/templ.js +38 -76
- package/src/backend/js/type-info.js +272 -0
- package/src/backend/shared/fmt-types.js +188 -0
- package/src/backend/wasm/boundary.js +446 -72
- package/src/backend/wasm/emit-builtins.js +320 -13
- package/src/backend/wasm/emit-exprs.js +1404 -199
- package/src/backend/wasm/emit-maps.js +61 -13
- package/src/backend/wasm/emit-stmts.js +1359 -164
- package/src/backend/wasm/emit.js +518 -74
- package/src/backend/wasm/encode.js +128 -13
- package/src/backend/wasm/globals.js +452 -0
- package/src/backend/wasm/index.js +194 -2489
- package/src/backend/wasm/method-expr.js +381 -0
- package/src/backend/wasm/module-emitter.js +2240 -0
- package/src/backend/wasm/monomorph.js +62 -4
- package/src/backend/wasm/optimize.js +4 -2
- package/src/backend/wasm/prescan.js +397 -0
- package/src/backend/wasm/stdlib/bytes.js +1804 -127
- package/src/backend/wasm/stdlib/core.js +1574 -199
- package/src/backend/wasm/stdlib/index.js +3 -5
- package/src/backend/wasm/stdlib/io.js +1567 -150
- package/src/backend/wasm/stdlib/packages.js +2 -0
- package/src/backend/wasm/stdlib/simd.js +499 -0
- package/src/backend/wasm/stdlib/time.js +1041 -12
- package/src/backend/wasm/types.js +221 -15
- package/src/backend/wasm/variadic.js +93 -0
- package/src/backend/wasm/wat.js +84 -20
- package/src/{cli-core.js → cli/cli-core.js} +236 -10
- package/src/{test-runner.js → cli/test-runner.js} +332 -39
- package/src/{vendor.js → cli/vendor.js} +89 -15
- package/src/compiler.js +107 -269
- package/src/dts-parser.js +71 -14
- package/src/index.js +15 -169
- package/src/{lexer.js → lexer/lexer.js} +102 -55
- package/src/{tokens.js → lexer/tokens.js} +1 -0
- package/src/link/sourcemap.js +97 -0
- package/src/link/stdlib.js +515 -0
- package/src/link/wasm.js +294 -0
- package/src/lower/bounds.js +394 -0
- package/src/lower/boxing.js +1 -6
- package/src/lower/captures.js +176 -32
- package/src/lower/embedding.js +2 -1
- package/src/lower/index.js +26 -18
- package/src/lower/json-codecs.js +1398 -0
- package/src/lower/ownership.js +22 -1
- package/src/lower/range.js +86 -1
- package/src/lower/reachability.js +132 -0
- package/src/parser/declarations.js +58 -13
- package/src/parser/expressions.js +29 -7
- package/src/parser/index.js +2 -1
- package/src/parser/statements.js +37 -22
- package/src/parser/struct-tag.js +144 -0
- package/src/{templ-parser.js → parser/templ.js} +138 -15
- package/src/parser/types.js +130 -13
- package/src/resolver.js +22 -1
- package/src/typechecker/assignability.js +215 -11
- package/src/typechecker/const-fold.js +356 -0
- package/src/typechecker/constants.js +256 -0
- package/src/typechecker/expressions.js +960 -99
- package/src/typechecker/index.js +491 -72
- package/src/typechecker/init-order.js +330 -0
- package/src/typechecker/resolve.js +596 -52
- package/src/typechecker/statements.js +157 -17
- package/src/typechecker/stdlib/core.js +333 -55
- package/src/typechecker/stdlib/excluded.js +898 -0
- package/src/typechecker/stdlib/extended.js +359 -26
- package/src/typechecker/{stdlib.js → stdlib/index.js} +6 -4
- package/src/typechecker/stdlib/simd.js +123 -0
- package/src/typechecker/termination.js +1 -1
- package/src/typechecker/types.js +165 -26
- package/stdlib/bytes/iter.go +192 -0
- package/stdlib/bytes/search.go +240 -0
- package/stdlib/bytes/split.go +218 -0
- package/stdlib/cmp/cmp.go +65 -0
- package/stdlib/encoding/json/errors.go +31 -0
- package/stdlib/encoding/json/indent.go +71 -0
- package/stdlib/encoding/json/number.go +12 -0
- package/stdlib/encoding/json/stream.go +173 -0
- package/stdlib/errors/overlay.go +12 -0
- package/stdlib/hash/hash.go +53 -0
- package/stdlib/internal/jsonrt/dec.go +738 -0
- package/stdlib/internal/jsonrt/enc.go +366 -0
- package/stdlib/internal/jsonrt/indent.go +135 -0
- package/stdlib/internal/jsonrt/number.go +27 -0
- package/stdlib/internal/jsonrt/reader.go +73 -0
- package/stdlib/internal/jsonrt/scanner.go +649 -0
- package/stdlib/io/io.go +346 -0
- package/stdlib/iter/iter.go +18 -0
- package/stdlib/maps/iter.go +65 -0
- package/stdlib/math/extra.go +270 -0
- package/stdlib/slices/extra.go +73 -0
- package/stdlib/slices/iter.go +113 -0
- package/stdlib/sort/sort.go +561 -0
- package/stdlib/strconv/quote.go +341 -0
- package/stdlib/strings/extra.go +35 -0
- package/stdlib/strings/iter.go +122 -0
- package/stdlib/sync/extra.go +105 -0
- package/stdlib/unicode/unicode.go +112 -0
- package/stdlib/unicode/utf8/bytes.go +239 -0
- package/src/backend/wasm/const-fold.js +0 -205
- package/src/backend/wasm/emit-stdlib.js +0 -13
- /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
- /package/src/{colors.js → cli/colors.js} +0 -0
- /package/src/{dev-server.js → cli/dev-server.js} +0 -0
- /package/src/{minifier.js → cli/minifier.js} +0 -0
- /package/src/{project-config.js → cli/project-config.js} +0 -0
- /package/src/{pwa.js → cli/pwa.js} +0 -0
- /package/src/{templ-lexer.js → lexer/templ-lexer.js} +0 -0
|
@@ -1,16 +1,129 @@
|
|
|
1
1
|
import {
|
|
2
2
|
bodyRedeclares,
|
|
3
|
+
forEachChild,
|
|
3
4
|
isIntRangeType,
|
|
4
5
|
isMapRangeType,
|
|
5
6
|
isRangeFor,
|
|
6
7
|
isSimpleIntRange,
|
|
7
8
|
isStringRangeType,
|
|
9
|
+
loopVarsCapturedInLoop,
|
|
8
10
|
nodeAssigns,
|
|
11
|
+
someChild,
|
|
9
12
|
} from "../../lower/index.js";
|
|
10
|
-
import { isComplex } from "../../typechecker/types.js";
|
|
13
|
+
import { isComplex, isTypedArraySlice } from "../../typechecker/types.js";
|
|
11
14
|
|
|
12
15
|
/** @typedef {import('./index.js').CodeGen} CodeGen */
|
|
13
16
|
|
|
17
|
+
// The same variable or field chain (`x`, `n.Children`) on both sides.
|
|
18
|
+
const sameChain = (a, b) =>
|
|
19
|
+
a.kind === b.kind &&
|
|
20
|
+
(a.kind === "Ident"
|
|
21
|
+
? a.name === b.name && a.name !== "_"
|
|
22
|
+
: a.kind === "SelectorExpr" &&
|
|
23
|
+
a.field === b.field &&
|
|
24
|
+
sameChain(a.expr, b.expr));
|
|
25
|
+
|
|
26
|
+
// `x = x[lo:hi]` (no max) with the same chain on both sides.
|
|
27
|
+
const isSelfResliceShape = (stmt) =>
|
|
28
|
+
stmt.op === "=" &&
|
|
29
|
+
stmt.lhs.length === 1 &&
|
|
30
|
+
stmt.rhs.length === 1 &&
|
|
31
|
+
stmt.rhs[0].kind === "SliceExpr" &&
|
|
32
|
+
!stmt.rhs[0].max &&
|
|
33
|
+
sameChain(stmt.lhs[0], stmt.rhs[0].expr);
|
|
34
|
+
|
|
35
|
+
// Pure expressions (the slice chain of a `__rcs` sync) that read the same
|
|
36
|
+
// value after the write as before it: literals, variables and fields not
|
|
37
|
+
// assigned by the statement, `len` and `cap`, and arithmetic over those.
|
|
38
|
+
const isStableIndex = (e, assigned) => {
|
|
39
|
+
switch (e?.kind) {
|
|
40
|
+
case "BasicLit":
|
|
41
|
+
return true;
|
|
42
|
+
case "Ident":
|
|
43
|
+
return !assigned.has(e.name);
|
|
44
|
+
case "SelectorExpr":
|
|
45
|
+
return !e._isMethodValue && isStableIndex(e.expr, assigned);
|
|
46
|
+
case "ParenExpr":
|
|
47
|
+
return isStableIndex(e.expr, assigned);
|
|
48
|
+
case "UnaryExpr":
|
|
49
|
+
return e.op !== "<-" && isStableIndex(e.operand, assigned);
|
|
50
|
+
case "BinaryExpr":
|
|
51
|
+
return (
|
|
52
|
+
isStableIndex(e.left, assigned) && isStableIndex(e.right, assigned)
|
|
53
|
+
);
|
|
54
|
+
case "CallExpr":
|
|
55
|
+
return (
|
|
56
|
+
e.func?.kind === "Ident" &&
|
|
57
|
+
(e.func.name === "len" || e.func.name === "cap") &&
|
|
58
|
+
e.args.every((a) => isStableIndex(a, assigned))
|
|
59
|
+
);
|
|
60
|
+
default:
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const isLenCap = (e) =>
|
|
66
|
+
e.kind === "CallExpr" &&
|
|
67
|
+
e.func?.kind === "Ident" &&
|
|
68
|
+
(e.func.name === "len" || e.func.name === "cap");
|
|
69
|
+
|
|
70
|
+
// A call (other than `len`/`cap`), receive or await in `n` (not inside a
|
|
71
|
+
// function literal): it may change variables and fields.
|
|
72
|
+
const hasCall = (n) => {
|
|
73
|
+
if (Array.isArray(n)) return n.some(hasCall);
|
|
74
|
+
if (!n || typeof n !== "object" || typeof n.kind !== "string") return false;
|
|
75
|
+
if (n.kind === "FuncLit") return false;
|
|
76
|
+
if (n.kind === "AwaitExpr" || (n.kind === "CallExpr" && !isLenCap(n)))
|
|
77
|
+
return true;
|
|
78
|
+
if (n.kind === "UnaryExpr" && n.op === "<-") return true;
|
|
79
|
+
return someChild(n, hasCall);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// A call in a left-hand index or on the right-hand side of the statement.
|
|
83
|
+
const stmtHasCall = (lhs, stmt) =>
|
|
84
|
+
lhs.some((e) => hasCall(e.index)) ||
|
|
85
|
+
(stmt.kind === "AssignStmt" && hasCall(stmt.rhs));
|
|
86
|
+
|
|
87
|
+
// A multi-assign with a field or `*p` left-hand side: it may reassign the
|
|
88
|
+
// slice chain of an index write, whose sync must then use the old slice.
|
|
89
|
+
const reassignsChains = (lhs, stmt) =>
|
|
90
|
+
stmt.kind === "AssignStmt" &&
|
|
91
|
+
lhs.length > 1 &&
|
|
92
|
+
lhs.some(
|
|
93
|
+
(e) =>
|
|
94
|
+
e.kind === "SelectorExpr" || (e.kind === "UnaryExpr" && e.op === "*"),
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
// Index of a `__rcs` sync that can be evaluated again after the write:
|
|
98
|
+
// constants, and, when `vars` (a single-LHS statement without calls), unboxed
|
|
99
|
+
// variables, with `len`/`cap` and arithmetic over those. Any other index
|
|
100
|
+
// is bound to a temporary first, so the write and the sync use one value.
|
|
101
|
+
const isReusableIndex = (e, vars, boxed) => {
|
|
102
|
+
if (e?._const || e?.kind === "BasicLit") return true;
|
|
103
|
+
switch (e?.kind) {
|
|
104
|
+
case "Ident":
|
|
105
|
+
return vars && !boxed.has(e.name);
|
|
106
|
+
case "ParenExpr":
|
|
107
|
+
return isReusableIndex(e.expr, vars, boxed);
|
|
108
|
+
case "UnaryExpr":
|
|
109
|
+
return (
|
|
110
|
+
(e.op === "-" || e.op === "+" || e.op === "^") &&
|
|
111
|
+
isReusableIndex(e.operand, vars, boxed)
|
|
112
|
+
);
|
|
113
|
+
case "BinaryExpr":
|
|
114
|
+
return (
|
|
115
|
+
isReusableIndex(e.left, vars, boxed) &&
|
|
116
|
+
isReusableIndex(e.right, vars, boxed)
|
|
117
|
+
);
|
|
118
|
+
case "CallExpr":
|
|
119
|
+
return (
|
|
120
|
+
isLenCap(e) && e.args.every((a) => isReusableIndex(a, vars, boxed))
|
|
121
|
+
);
|
|
122
|
+
default:
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
14
127
|
// Method-name dispatch for genStmt — string values add no static call edges.
|
|
15
128
|
const STMT_GEN_DELEGATE = {
|
|
16
129
|
VarDecl: "genVarDecl",
|
|
@@ -27,7 +140,53 @@ const STMT_GEN_DELEGATE = {
|
|
|
27
140
|
/** @type {ThisType<CodeGen>} */
|
|
28
141
|
export const statementGenMethods = {
|
|
29
142
|
genBlock(block) {
|
|
30
|
-
|
|
143
|
+
let nested = 0;
|
|
144
|
+
for (const stmt of block.stmts) {
|
|
145
|
+
if (this._definesShadowedRead(stmt)) {
|
|
146
|
+
this._genShadowingDefine(stmt);
|
|
147
|
+
nested++;
|
|
148
|
+
} else {
|
|
149
|
+
this.genStmt(stmt);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
for (; nested > 0; nested--) {
|
|
153
|
+
this.indent--;
|
|
154
|
+
this.line("}");
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
// `x := f(x)` declaring a new `x` that its own right-hand side reads (the
|
|
159
|
+
// outer `x`): JS `let` would put the read in its temporal dead zone.
|
|
160
|
+
_definesShadowedRead(stmt) {
|
|
161
|
+
if (stmt.kind !== "DefineStmt" || this._isCommaOkMapExpr(stmt, stmt.rhs[0]))
|
|
162
|
+
return false;
|
|
163
|
+
const names = new Set(
|
|
164
|
+
stmt.lhs.filter((l) => !l._redecl && l.name !== "_").map((l) => l.name),
|
|
165
|
+
);
|
|
166
|
+
if (names.size === 0) return false;
|
|
167
|
+
const reads = (n) => {
|
|
168
|
+
if (Array.isArray(n)) return n.some(reads);
|
|
169
|
+
if (!n || typeof n !== "object") return false;
|
|
170
|
+
if (n.kind === "Ident" && names.has(n.name)) return true;
|
|
171
|
+
return someChild(n, reads);
|
|
172
|
+
};
|
|
173
|
+
return stmt.rhs.some(reads);
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
// Evaluates the right-hand side in the enclosing scope, then opens a nested
|
|
177
|
+
// block (closed at the end of the enclosing block) that declares the names.
|
|
178
|
+
_genShadowingDefine(stmt) {
|
|
179
|
+
const rhs = this._defineRhs(stmt);
|
|
180
|
+
this._tmpCounter = (this._tmpCounter ?? 0) + 1;
|
|
181
|
+
const tmp = `__t${this._tmpCounter}`;
|
|
182
|
+
const all = rhs.length === 1 ? rhs[0] : `[${rhs.join(", ")}]`;
|
|
183
|
+
this.line(`const ${tmp} = ${all};`);
|
|
184
|
+
this.line("{");
|
|
185
|
+
this.indent++;
|
|
186
|
+
this._genDefineStmt(
|
|
187
|
+
stmt,
|
|
188
|
+
rhs.length === 1 ? [tmp] : rhs.map((_, i) => `${tmp}[${i}]`),
|
|
189
|
+
);
|
|
31
190
|
},
|
|
32
191
|
|
|
33
192
|
// Shared helper for comma-ok map index in both DefineStmt and AssignStmt.
|
|
@@ -36,9 +195,15 @@ export const statementGenMethods = {
|
|
|
36
195
|
const keyExpr = this.genExpr(rhsNode.index);
|
|
37
196
|
const [vName, okName] = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
|
|
38
197
|
const decl = isDefine ? "let " : "";
|
|
198
|
+
// An array map value is a copy, not the stored array.
|
|
199
|
+
const valType = rhsNode._mapValueType;
|
|
200
|
+
const copyVal = (js) =>
|
|
201
|
+
this._aggregateBase(valType)?.kind === "array"
|
|
202
|
+
? this._cloneJs(valType, js)
|
|
203
|
+
: js;
|
|
39
204
|
if (isDefine && !this.strict) {
|
|
40
205
|
if (vName !== "_") {
|
|
41
|
-
this.line(`let ${vName} = ${mapExpr}[${keyExpr}];`);
|
|
206
|
+
this.line(`let ${vName} = ${copyVal(`${mapExpr}[${keyExpr}]`)};`);
|
|
42
207
|
}
|
|
43
208
|
if (okName !== "_") {
|
|
44
209
|
this.line(`let ${okName} = (${keyExpr}) in ${mapExpr};`);
|
|
@@ -54,7 +219,7 @@ export const statementGenMethods = {
|
|
|
54
219
|
this.line(`const ${mTmp} = ${mapExpr}, ${kTmp} = ${keyExpr};`);
|
|
55
220
|
if (vName !== "_") {
|
|
56
221
|
this.line(
|
|
57
|
-
`${decl}${vName} = (${mTmp} && (${kTmp} in ${mTmp})) ? ${mTmp}[${kTmp}] : ${zero};`,
|
|
222
|
+
`${decl}${vName} = (${mTmp} && (${kTmp} in ${mTmp})) ? ${copyVal(`${mTmp}[${kTmp}]`)} : ${zero};`,
|
|
58
223
|
);
|
|
59
224
|
}
|
|
60
225
|
if (okName !== "_") {
|
|
@@ -66,11 +231,21 @@ export const statementGenMethods = {
|
|
|
66
231
|
// Record source mapping for the first line this statement produces
|
|
67
232
|
const srcLine = stmt._line ?? null;
|
|
68
233
|
const _line0 = this.out.length;
|
|
234
|
+
const restore = [];
|
|
235
|
+
const syncs =
|
|
236
|
+
stmt.kind === "AssignStmt"
|
|
237
|
+
? this._resliceSyncs(stmt.lhs, stmt, false, restore)
|
|
238
|
+
: stmt.kind === "IncDecStmt"
|
|
239
|
+
? this._resliceSyncs([stmt.expr], stmt, false, restore)
|
|
240
|
+
: null;
|
|
69
241
|
switch (stmt.kind) {
|
|
70
242
|
case "TypeDecl":
|
|
71
243
|
this.genTypeDeclWithMethods(stmt, []);
|
|
244
|
+
this._noteLocalTypeDecl(stmt);
|
|
72
245
|
break;
|
|
73
246
|
case "IncDecStmt": {
|
|
247
|
+
this._markIndexLvalues([stmt.expr]);
|
|
248
|
+
this._markDerefOpTarget(stmt.expr);
|
|
74
249
|
if (stmt.expr.kind === "IndexExpr" && stmt.expr._mapValueType) {
|
|
75
250
|
const zero = this.zeroValueForType(stmt.expr._mapValueType);
|
|
76
251
|
const wrapField = this._namedWrapperField(
|
|
@@ -87,12 +262,12 @@ export const statementGenMethods = {
|
|
|
87
262
|
this._hasCallExpr(stmt.expr.index)
|
|
88
263
|
) {
|
|
89
264
|
this.line(
|
|
90
|
-
`((__m, __k) => { __m[__k] = (__m[__k] ?? ${zero}) ${op} 1; })(${base}, ${idx});`,
|
|
265
|
+
`((__m, __k) => { ${this._genMapWriteBase("__m")}[__k] = (__m[__k] ?? ${zero}) ${op} 1; })(${base}, ${idx});`,
|
|
91
266
|
srcLine,
|
|
92
267
|
);
|
|
93
268
|
} else {
|
|
94
269
|
this.line(
|
|
95
|
-
`${base}[${idx}] = (${base}[${idx}] ?? ${zero}) ${op} 1;`,
|
|
270
|
+
`${this._genMapWriteBase(base)}[${idx}] = (${base}[${idx}] ?? ${zero}) ${op} 1;`,
|
|
96
271
|
srcLine,
|
|
97
272
|
);
|
|
98
273
|
}
|
|
@@ -118,8 +293,14 @@ export const statementGenMethods = {
|
|
|
118
293
|
if (stmt.init) {
|
|
119
294
|
this.line("{");
|
|
120
295
|
this.indented(() => {
|
|
121
|
-
this.
|
|
296
|
+
const shadowing = this._definesShadowedRead(stmt.init);
|
|
297
|
+
if (shadowing) this._genShadowingDefine(stmt.init);
|
|
298
|
+
else this.genStmt(stmt.init);
|
|
122
299
|
this._genIf(stmt);
|
|
300
|
+
if (shadowing) {
|
|
301
|
+
this.indent--;
|
|
302
|
+
this.line("}");
|
|
303
|
+
}
|
|
123
304
|
});
|
|
124
305
|
this.line("}");
|
|
125
306
|
} else {
|
|
@@ -130,6 +311,9 @@ export const statementGenMethods = {
|
|
|
130
311
|
this.line(`__defers.push(() => { ${this.genExpr(stmt.call)}; });`);
|
|
131
312
|
break;
|
|
132
313
|
case "LabeledStmt":
|
|
314
|
+
if (stmt.body && stmt.body.kind === "ForStmt") {
|
|
315
|
+
stmt.body.label = stmt.label;
|
|
316
|
+
}
|
|
133
317
|
this.line(`${stmt.label}:`);
|
|
134
318
|
this.genStmt(stmt.body);
|
|
135
319
|
break;
|
|
@@ -147,6 +331,9 @@ export const statementGenMethods = {
|
|
|
147
331
|
throw new Error(`CodeGen: unhandled statement kind '${stmt.kind}'`);
|
|
148
332
|
}
|
|
149
333
|
}
|
|
334
|
+
this._emitResliceSyncs(syncs);
|
|
335
|
+
// The AST is shared with the WASM backend: undo the temporaries.
|
|
336
|
+
for (const [node, key, v] of restore) node[key] = v;
|
|
150
337
|
// Record source mapping: first output line this statement produced
|
|
151
338
|
if (srcLine != null && this.out.length > _line0) {
|
|
152
339
|
this._srcMappings.push({
|
|
@@ -158,11 +345,14 @@ export const statementGenMethods = {
|
|
|
158
345
|
},
|
|
159
346
|
|
|
160
347
|
_isCommaOkMapExpr(stmt, rhsNode) {
|
|
348
|
+
if (stmt._commaOkMap) return true;
|
|
349
|
+
const t = rhsNode?.expr?._type;
|
|
350
|
+
const underlying = t?.kind === "named" ? t.underlying : t;
|
|
161
351
|
return (
|
|
162
352
|
stmt.rhs.length === 1 &&
|
|
163
353
|
stmt.lhs.length === 2 &&
|
|
164
354
|
rhsNode?.kind === "IndexExpr" &&
|
|
165
|
-
|
|
355
|
+
underlying?.kind === "map"
|
|
166
356
|
);
|
|
167
357
|
},
|
|
168
358
|
|
|
@@ -178,40 +368,43 @@ export const statementGenMethods = {
|
|
|
178
368
|
},
|
|
179
369
|
|
|
180
370
|
_genDefineMixedRedecl(lhs, rhs, redecls) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
} else {
|
|
191
|
-
for (let i = 0; i < lhs.length; i++) {
|
|
192
|
-
if (lhs[i] === "_") {
|
|
193
|
-
this.line(`${rhs[i]};`);
|
|
194
|
-
continue;
|
|
195
|
-
}
|
|
196
|
-
const prefix = redecls[i] ? "" : "let ";
|
|
197
|
-
this.line(`${prefix}${lhs[i]} = ${rhs[i]};`);
|
|
198
|
-
}
|
|
371
|
+
// `x, y := 2, x` reads the old `x`: evaluate every value first.
|
|
372
|
+
this._tmpCounter = (this._tmpCounter ?? 0) + 1;
|
|
373
|
+
const tmp = `__t${this._tmpCounter}`;
|
|
374
|
+
const all = rhs.length === 1 ? rhs[0] : `[${rhs.join(", ")}]`;
|
|
375
|
+
this.line(`const ${tmp} = ${all};`);
|
|
376
|
+
for (let i = 0; i < lhs.length; i++) {
|
|
377
|
+
if (lhs[i] === "_") continue;
|
|
378
|
+
this._genDefineSingleVar([lhs[i]], [`${tmp}[${i}]`], [redecls[i]]);
|
|
199
379
|
}
|
|
200
380
|
},
|
|
201
381
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
382
|
+
// Address-taken variables introduced by a destructuring declaration are
|
|
383
|
+
// re-bound to their { value } box.
|
|
384
|
+
_boxDeclaredNames(names) {
|
|
385
|
+
for (const n of names) {
|
|
386
|
+
if (n && n !== "_" && this._boxedVars.has(n))
|
|
387
|
+
this.line(`${n} = { value: ${n} };`);
|
|
207
388
|
}
|
|
389
|
+
},
|
|
390
|
+
|
|
391
|
+
_defineRhs(stmt) {
|
|
208
392
|
const paired = stmt.rhs.length === stmt.lhs.length;
|
|
209
|
-
|
|
393
|
+
return stmt.rhs.map((e, i) => {
|
|
210
394
|
const l = paired ? stmt.lhs[i] : null;
|
|
211
395
|
if (!l || l.name === "_") return this.genExpr(e);
|
|
212
396
|
if (l._redecl) return this.genValueExpr(e);
|
|
213
397
|
return this._genDeclValue(l.name, e);
|
|
214
398
|
});
|
|
399
|
+
},
|
|
400
|
+
|
|
401
|
+
_genDefineStmt(stmt, preEvaluated = null) {
|
|
402
|
+
const rhsNode = stmt.rhs[0];
|
|
403
|
+
if (this._isCommaOkMapExpr(stmt, rhsNode)) {
|
|
404
|
+
this._genCommaOkMapIndex(stmt, rhsNode, true);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
const rhs = preEvaluated ?? this._defineRhs(stmt);
|
|
215
408
|
const lhs = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
|
|
216
409
|
const redecls = stmt.lhs.map((e) => !!e._redecl);
|
|
217
410
|
const anyRedecl = redecls.some((r) => r);
|
|
@@ -221,6 +414,7 @@ export const statementGenMethods = {
|
|
|
221
414
|
const rhsStr = rhs.length === 1 ? rhs[0] : `[${rhs.join(", ")}]`;
|
|
222
415
|
const names = lhs.map((n) => (n === "_" ? "" : n));
|
|
223
416
|
this.line(`let [${names.join(", ")}] = ${rhsStr};`);
|
|
417
|
+
this._boxDeclaredNames(names);
|
|
224
418
|
} else {
|
|
225
419
|
this._genDefineMixedRedecl(lhs, rhs, redecls);
|
|
226
420
|
}
|
|
@@ -241,6 +435,68 @@ export const statementGenMethods = {
|
|
|
241
435
|
);
|
|
242
436
|
},
|
|
243
437
|
|
|
438
|
+
// `*p = v` where `p` points at a type parameter: erased, so the pointer is
|
|
439
|
+
// either a struct (copy into it) or a get/set wrapper.
|
|
440
|
+
_isTypeParamDerefAssign(stmt) {
|
|
441
|
+
if (stmt.lhs.length !== 1 || stmt.rhs.length !== 1 || stmt.op !== "=")
|
|
442
|
+
return false;
|
|
443
|
+
const l = stmt.lhs[0];
|
|
444
|
+
return (
|
|
445
|
+
l.kind === "UnaryExpr" &&
|
|
446
|
+
l.op === "*" &&
|
|
447
|
+
l.operand?._type?.base?.kind === "typeParam"
|
|
448
|
+
);
|
|
449
|
+
},
|
|
450
|
+
|
|
451
|
+
_isTypeParamDerefTarget(l) {
|
|
452
|
+
return (
|
|
453
|
+
l.kind === "UnaryExpr" &&
|
|
454
|
+
l.op === "*" &&
|
|
455
|
+
l.operand?._type?.base?.kind === "typeParam"
|
|
456
|
+
);
|
|
457
|
+
},
|
|
458
|
+
|
|
459
|
+
// `*a, *b = *b, *a` with type-parameter pointers: `*p` is not a settable
|
|
460
|
+
// JS destructuring target, so evaluate the right-hand side into temporaries
|
|
461
|
+
// and assign each target on its own.
|
|
462
|
+
_hasTypeParamDerefTarget(stmt) {
|
|
463
|
+
return (
|
|
464
|
+
stmt.op === "=" &&
|
|
465
|
+
stmt.lhs.length > 1 &&
|
|
466
|
+
stmt.lhs.some((l) => this._isTypeParamDerefTarget(l))
|
|
467
|
+
);
|
|
468
|
+
},
|
|
469
|
+
|
|
470
|
+
_genTypeParamDerefMultiAssign(stmt) {
|
|
471
|
+
const n = stmt.lhs.length;
|
|
472
|
+
const names = stmt.lhs.map((_, i) => `__t${i}`);
|
|
473
|
+
this.line("{");
|
|
474
|
+
this.indented(() => {
|
|
475
|
+
if (stmt.rhs.length === n) {
|
|
476
|
+
const vals = stmt.rhs.map((e, i) =>
|
|
477
|
+
stmt.lhs[i].name !== "_" ? this.genValueExpr(e) : this.genExpr(e),
|
|
478
|
+
);
|
|
479
|
+
this.line(`const [${names.join(", ")}] = [${vals.join(", ")}];`);
|
|
480
|
+
} else {
|
|
481
|
+
this.line(
|
|
482
|
+
`const [${names.join(", ")}] = ${this.genExpr(stmt.rhs[0])};`,
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
stmt.lhs.forEach((l, i) => {
|
|
486
|
+
if (l.name === "_") return;
|
|
487
|
+
if (this._isTypeParamDerefTarget(l)) {
|
|
488
|
+
const isStruct = this._typeParamIsStructJs(l.operand._type.base.name);
|
|
489
|
+
this.line(
|
|
490
|
+
`((__p, __v) => { if (${isStruct}) Object.assign(__p, __v); else __p.value = __v; })(${this.genExpr(l.operand)}, ${names[i]});`,
|
|
491
|
+
);
|
|
492
|
+
} else {
|
|
493
|
+
this.line(`${this._genAssignLhsExpr(l)} = ${names[i]};`);
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
});
|
|
497
|
+
this.line("}");
|
|
498
|
+
},
|
|
499
|
+
|
|
244
500
|
_isStructDerefAssign(stmt) {
|
|
245
501
|
if (stmt.lhs.length !== 1 || stmt.rhs.length !== 1 || stmt.op !== "=")
|
|
246
502
|
return false;
|
|
@@ -262,6 +518,13 @@ export const statementGenMethods = {
|
|
|
262
518
|
for (const e of exprs) if (e.kind === "IndexExpr") e._lvalue = true;
|
|
263
519
|
},
|
|
264
520
|
|
|
521
|
+
// Op-assign / inc-dec through a type-parameter pointer needs a settable
|
|
522
|
+
// target: the erased pointer is a get/set wrapper (struct instantiations
|
|
523
|
+
// cannot be op-assigned), so target `.value` directly.
|
|
524
|
+
_markDerefOpTarget(e) {
|
|
525
|
+
if (this._isTypeParamDerefTarget(e)) e._derefOpTarget = true;
|
|
526
|
+
},
|
|
527
|
+
|
|
265
528
|
_genAssignMulti(stmt, lhs, rhs, active, pairs) {
|
|
266
529
|
const rhsStr = rhs.length === 1 ? rhs[0] : `[${rhs.join(", ")}]`;
|
|
267
530
|
if (active.length === pairs.length) {
|
|
@@ -271,7 +534,132 @@ export const statementGenMethods = {
|
|
|
271
534
|
}
|
|
272
535
|
},
|
|
273
536
|
|
|
537
|
+
// `x = x[lo:hi]` with the same variable or field chain on both sides:
|
|
538
|
+
// the old value of `x` is replaced, so `_genSliceExpr` may copy instead
|
|
539
|
+
// of building a slice view (`__reslice`).
|
|
540
|
+
_markSelfReslice(stmt) {
|
|
541
|
+
if (stmt.op !== "=" || stmt.lhs.length !== 1 || stmt.rhs.length !== 1)
|
|
542
|
+
return;
|
|
543
|
+
const r = stmt.rhs[0];
|
|
544
|
+
if (r.kind !== "SliceExpr" || r.max) return;
|
|
545
|
+
// A parameter or named result may alias the caller's slice: keep the
|
|
546
|
+
// view. Field chains (`n.Children = n.Children[:k]`) still copy.
|
|
547
|
+
const lhs = stmt.lhs[0];
|
|
548
|
+
if (lhs.kind === "Ident" && this._fnCtx?.params.has(lhs.name)) return;
|
|
549
|
+
if (sameChain(stmt.lhs[0], r.expr)) r._selfReslice = true;
|
|
550
|
+
},
|
|
551
|
+
|
|
552
|
+
// Names of the variables and fields (`.F`) that some `x = x[lo:hi]` in the
|
|
553
|
+
// program may turn into a `__reslice` copy (`_resliceSyncs`).
|
|
554
|
+
_collectResliceNames(program) {
|
|
555
|
+
const names = new Set();
|
|
556
|
+
const visit = (n) => {
|
|
557
|
+
if (Array.isArray(n)) {
|
|
558
|
+
for (const c of n) visit(c);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
if (!n || typeof n !== "object" || typeof n.kind !== "string") return;
|
|
562
|
+
if (n.kind === "AssignStmt" && isSelfResliceShape(n)) {
|
|
563
|
+
const l = n.lhs[0];
|
|
564
|
+
names.add(l.kind === "Ident" ? l.name : `.${l.field}`);
|
|
565
|
+
}
|
|
566
|
+
forEachChild(n, visit);
|
|
567
|
+
};
|
|
568
|
+
visit(program.decls);
|
|
569
|
+
this._resliceNames = names;
|
|
570
|
+
},
|
|
571
|
+
|
|
572
|
+
// Index writes in `lhs` whose slice may be a `__reslice` copy: each is
|
|
573
|
+
// followed by `__rcs(x, i)` (`_emitResliceSyncs`), which writes the element
|
|
574
|
+
// through to the copy's old array, so writes through the copy and through
|
|
575
|
+
// other slices of that array keep Go's order. An index that the statement
|
|
576
|
+
// could change (`isReusableIndex`) is first bound to a temporary (not in
|
|
577
|
+
// `inline` for-clause statements, which skip the sync: the write-back on
|
|
578
|
+
// extension still applies). Not covered: writes through other references to the copy.
|
|
579
|
+
// Once one index is bound, every left-hand index with a call is bound too,
|
|
580
|
+
// in source order and each after its operand, and so is the slice operand
|
|
581
|
+
// of a synced write in a statement with a call or one that may reassign
|
|
582
|
+
// the chain through a field or `*p` (`reassignsChains`; inline statements
|
|
583
|
+
// skip that sync) — Go evaluates both before the right-hand side.
|
|
584
|
+
// `restore` collects the replaced nodes for the caller to put back.
|
|
585
|
+
_resliceSyncs(lhs, stmt, inline = false, restore = []) {
|
|
586
|
+
if (!this._resliceNames?.size) return null;
|
|
587
|
+
const targets = this._resliceTargets(lhs, stmt);
|
|
588
|
+
if (targets.size === 0) return null;
|
|
589
|
+
const called = stmtHasCall(lhs, stmt);
|
|
590
|
+
const moved = reassignsChains(lhs, stmt);
|
|
591
|
+
const vars = lhs.length === 1 && !called;
|
|
592
|
+
const reusable = (e) => isReusableIndex(e.index, vars, this._boxedVars);
|
|
593
|
+
const out = [...targets].filter((e) => !inline || (!moved && reusable(e)));
|
|
594
|
+
const operands = called || moved;
|
|
595
|
+
if (!inline && (operands || out.some((e) => !reusable(e))))
|
|
596
|
+
this._bindResliceOperands(lhs, targets, operands, reusable, restore);
|
|
597
|
+
if (out.length === 0) return null;
|
|
598
|
+
this.useHelper("slice");
|
|
599
|
+
return out.map((e) => {
|
|
600
|
+
const wrapField = this._namedWrapperField(e.expr._type, e.expr);
|
|
601
|
+
const base = wrapField
|
|
602
|
+
? `${this.genExpr(e.expr)}.${wrapField}`
|
|
603
|
+
: this.genExpr(e.expr);
|
|
604
|
+
return `__rcs(${base}, ${this.genExpr(e.index)})`;
|
|
605
|
+
});
|
|
606
|
+
},
|
|
607
|
+
|
|
608
|
+
// The `_isResliceSyncTarget` writes in `lhs`.
|
|
609
|
+
_resliceTargets(lhs, stmt) {
|
|
610
|
+
const assigned = new Set();
|
|
611
|
+
if (stmt.kind === "AssignStmt" && stmt.lhs.length > 1)
|
|
612
|
+
for (const e of stmt.lhs) if (e.kind === "Ident") assigned.add(e.name);
|
|
613
|
+
return new Set(lhs.filter((e) => this._isResliceSyncTarget(e, assigned)));
|
|
614
|
+
},
|
|
615
|
+
|
|
616
|
+
// Binds the left-hand operands that `_resliceSyncs` must not evaluate
|
|
617
|
+
// twice to temporaries, in source order: an index after its operand, and
|
|
618
|
+
// the operand of every synced write when `operands`.
|
|
619
|
+
_bindResliceOperands(lhs, targets, operands, reusable, restore) {
|
|
620
|
+
const bind = (e, key) => {
|
|
621
|
+
this._tmpCounter = (this._tmpCounter ?? 0) + 1;
|
|
622
|
+
const tmp = `__rci${this._tmpCounter}`;
|
|
623
|
+
this.line(`const ${tmp} = ${this.genExpr(e[key])};`);
|
|
624
|
+
restore.push([e, key, e[key]]);
|
|
625
|
+
e[key] = { kind: "Ident", name: tmp, _type: e[key]._type };
|
|
626
|
+
};
|
|
627
|
+
for (const e of lhs) {
|
|
628
|
+
if (e.kind !== "IndexExpr") continue;
|
|
629
|
+
const t = targets.has(e);
|
|
630
|
+
const index = (t && !reusable(e)) || hasCall(e.index);
|
|
631
|
+
if ((t && operands) || index) bind(e, "expr");
|
|
632
|
+
if (index) bind(e, "index");
|
|
633
|
+
}
|
|
634
|
+
},
|
|
635
|
+
|
|
636
|
+
// `x[i]` on a plain slice held by a chain in `_resliceNames` whose root
|
|
637
|
+
// variable the statement does not reassign.
|
|
638
|
+
_isResliceSyncTarget(e, assigned) {
|
|
639
|
+
if (e.kind !== "IndexExpr" || e._mapValueType) return false;
|
|
640
|
+
const c = e.expr;
|
|
641
|
+
if (c.kind !== "Ident" && c.kind !== "SelectorExpr") return false;
|
|
642
|
+
if (!this._resliceNames.has(c.kind === "Ident" ? c.name : `.${c.field}`))
|
|
643
|
+
return false;
|
|
644
|
+
const u = c._type?.kind === "named" ? c._type.underlying : c._type;
|
|
645
|
+
if (u?.kind !== "slice" || isTypedArraySlice(c._type)) return false;
|
|
646
|
+
let root = c;
|
|
647
|
+
while (root.kind === "SelectorExpr") root = root.expr;
|
|
648
|
+
return (
|
|
649
|
+
root.kind === "Ident" &&
|
|
650
|
+
!assigned.has(root.name) &&
|
|
651
|
+
isStableIndex(c, assigned)
|
|
652
|
+
);
|
|
653
|
+
},
|
|
654
|
+
|
|
655
|
+
_emitResliceSyncs(syncs) {
|
|
656
|
+
if (syncs) for (const s of syncs) this.line(`${s};`);
|
|
657
|
+
},
|
|
658
|
+
|
|
274
659
|
_genAssignStmt(stmt) {
|
|
660
|
+
this._markIndexLvalues(stmt.lhs);
|
|
661
|
+
if (stmt.op !== "=") for (const l of stmt.lhs) this._markDerefOpTarget(l);
|
|
662
|
+
this._markSelfReslice(stmt);
|
|
275
663
|
if (this._isComplexCompoundAssign(stmt)) {
|
|
276
664
|
this._genComplexCompoundAssign(stmt);
|
|
277
665
|
return;
|
|
@@ -284,18 +672,38 @@ export const statementGenMethods = {
|
|
|
284
672
|
this._genCommaOkTypeAssert(stmt);
|
|
285
673
|
return;
|
|
286
674
|
}
|
|
675
|
+
if (this._isTypeParamDerefAssign(stmt)) {
|
|
676
|
+
const ptr = this.genExpr(stmt.lhs[0].operand);
|
|
677
|
+
const val = this.genValueExpr(stmt.rhs[0]);
|
|
678
|
+
const isStruct = this._typeParamIsStructJs(
|
|
679
|
+
stmt.lhs[0].operand._type.base.name,
|
|
680
|
+
);
|
|
681
|
+
this.line(
|
|
682
|
+
`((__p, __v) => { if (${isStruct}) Object.assign(__p, __v); else __p.value = __v; })(${ptr}, ${val});`,
|
|
683
|
+
);
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
if (this._hasTypeParamDerefTarget(stmt)) {
|
|
687
|
+
this._genTypeParamDerefMultiAssign(stmt);
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
287
690
|
if (this._isStructDerefAssign(stmt)) {
|
|
288
691
|
this._genStructDerefAssign(stmt);
|
|
289
692
|
return;
|
|
290
693
|
}
|
|
694
|
+
if (this._isStableWholeAssign(stmt)) {
|
|
695
|
+
this._genStableWholeAssign(stmt);
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
291
698
|
const paired = stmt.rhs.length === stmt.lhs.length && stmt.op === "=";
|
|
292
699
|
const rhs = stmt.rhs.map((e, i) =>
|
|
293
700
|
paired && stmt.lhs[i].name !== "_"
|
|
294
701
|
? this.genValueExpr(e)
|
|
295
702
|
: this.genExpr(e),
|
|
296
703
|
);
|
|
297
|
-
this.
|
|
704
|
+
const hoisted = this._hoistLhsOperands(stmt);
|
|
298
705
|
const lhs = stmt.lhs.map((e) => this._genAssignLhsExpr(e));
|
|
706
|
+
hoisted?.restore();
|
|
299
707
|
const pairs = lhs.map((l, i) => ({ l, r: rhs[i] ?? rhs[0] }));
|
|
300
708
|
const active = pairs.filter((p) => p.l !== "_");
|
|
301
709
|
if (active.length === 0) {
|
|
@@ -321,11 +729,11 @@ export const statementGenMethods = {
|
|
|
321
729
|
this._hasCallExpr(stmt.lhs[0].index)
|
|
322
730
|
) {
|
|
323
731
|
this.line(
|
|
324
|
-
`((__m, __k) => { __m[__k] = (__m[__k] ?? ${zero}) ${binOp} (${active[0].r}); })(${base}, ${idx});`,
|
|
732
|
+
`((__m, __k) => { ${this._genMapWriteBase("__m")}[__k] = (__m[__k] ?? ${zero}) ${binOp} (${active[0].r}); })(${base}, ${idx});`,
|
|
325
733
|
);
|
|
326
734
|
} else {
|
|
327
735
|
this.line(
|
|
328
|
-
`${base}[${idx}] = (${base}[${idx}] ?? ${zero}) ${binOp} (${active[0].r});`,
|
|
736
|
+
`${this._genMapWriteBase(base)}[${idx}] = (${base}[${idx}] ?? ${zero}) ${binOp} (${active[0].r});`,
|
|
329
737
|
);
|
|
330
738
|
}
|
|
331
739
|
return;
|
|
@@ -343,12 +751,67 @@ export const statementGenMethods = {
|
|
|
343
751
|
) {
|
|
344
752
|
return;
|
|
345
753
|
}
|
|
346
|
-
|
|
754
|
+
const targetL =
|
|
755
|
+
active[0].l.startsWith("{") && !active[0].l.startsWith("({")
|
|
756
|
+
? `(${active[0].l})`
|
|
757
|
+
: active[0].l;
|
|
758
|
+
this.line(`${targetL} ${stmt.op} ${active[0].r};`);
|
|
759
|
+
} else if (hoisted) {
|
|
760
|
+
this.line("{");
|
|
761
|
+
this.indented(() => {
|
|
762
|
+
for (const l of hoisted.lines) this.line(l);
|
|
763
|
+
this._genAssignMulti(stmt, lhs, rhs, active, pairs);
|
|
764
|
+
});
|
|
765
|
+
this.line("}");
|
|
347
766
|
} else {
|
|
348
767
|
this._genAssignMulti(stmt, lhs, rhs, active, pairs);
|
|
349
768
|
}
|
|
350
769
|
},
|
|
351
770
|
|
|
771
|
+
// `k, xs[k] = 1, 5`: Go evaluates the operands of every index and field
|
|
772
|
+
// target before any assignment, but a JS destructuring target reads its
|
|
773
|
+
// operands after the earlier ones are stored. Operands that mention a
|
|
774
|
+
// variable assigned by the same statement are bound to temporaries first.
|
|
775
|
+
_hoistLhsOperands(stmt) {
|
|
776
|
+
if (stmt.op !== "=" || stmt.lhs.length < 2) return null;
|
|
777
|
+
const assigned = new Set(
|
|
778
|
+
stmt.lhs
|
|
779
|
+
.filter((l) => l.kind === "Ident" && l.name !== "_")
|
|
780
|
+
.map((l) => l.name),
|
|
781
|
+
);
|
|
782
|
+
if (assigned.size === 0) return null;
|
|
783
|
+
const mentions = (n) =>
|
|
784
|
+
!!n &&
|
|
785
|
+
typeof n === "object" &&
|
|
786
|
+
((n.kind === "Ident" && assigned.has(n.name)) || someChild(n, mentions));
|
|
787
|
+
const lines = [];
|
|
788
|
+
const undo = [];
|
|
789
|
+
for (const l of stmt.lhs) {
|
|
790
|
+
let keys;
|
|
791
|
+
if (l.kind === "IndexExpr") keys = ["expr", "index"];
|
|
792
|
+
else if (l.kind === "SelectorExpr" && !l._isMethodExpr) keys = ["expr"];
|
|
793
|
+
else continue;
|
|
794
|
+
for (const key of keys) {
|
|
795
|
+
const operand = l[key];
|
|
796
|
+
if (!mentions(operand)) continue;
|
|
797
|
+
// A struct or array value names the variable's storage in Go.
|
|
798
|
+
if (key === "expr" && this._aggregateBase(operand._type)) continue;
|
|
799
|
+
this._tmpCounter = (this._tmpCounter ?? 0) + 1;
|
|
800
|
+
const name = `__h${this._tmpCounter}`;
|
|
801
|
+
lines.push(`const ${name} = ${this.genExpr(operand)};`);
|
|
802
|
+
undo.push([l, key, operand]);
|
|
803
|
+
l[key] = { kind: "Ident", name, _type: operand._type };
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
if (lines.length === 0) return null;
|
|
807
|
+
return {
|
|
808
|
+
lines,
|
|
809
|
+
restore: () => {
|
|
810
|
+
for (const [node, key, v] of undo) node[key] = v;
|
|
811
|
+
},
|
|
812
|
+
};
|
|
813
|
+
},
|
|
814
|
+
|
|
352
815
|
_genCommaOkTypeAssert(stmt) {
|
|
353
816
|
const val = this.genExpr(stmt.rhs[0]);
|
|
354
817
|
const [vName, okName] = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
|
|
@@ -359,6 +822,68 @@ export const statementGenMethods = {
|
|
|
359
822
|
if (okName !== "_") this.line(`${okName} = ${tmp}[1];`);
|
|
360
823
|
},
|
|
361
824
|
|
|
825
|
+
// `a = [3]int{...}` / `s = S{...}` on a variable whose address (or an
|
|
826
|
+
// element's or field's address) is taken must write into the existing
|
|
827
|
+
// storage so those pointers see the new value (Task 0.33).
|
|
828
|
+
_isStableWholeAssign(stmt) {
|
|
829
|
+
if (stmt.op !== "=" || stmt.lhs.length !== 1 || stmt.rhs.length !== 1)
|
|
830
|
+
return false;
|
|
831
|
+
const l = stmt.lhs[0];
|
|
832
|
+
if (l.kind === "IndexExpr" && l._mapValueType) return false;
|
|
833
|
+
if (
|
|
834
|
+
l.kind !== "Ident" &&
|
|
835
|
+
l.kind !== "SelectorExpr" &&
|
|
836
|
+
l.kind !== "IndexExpr"
|
|
837
|
+
)
|
|
838
|
+
return false;
|
|
839
|
+
if (l.kind === "Ident" && l.name === "_") return false;
|
|
840
|
+
if (
|
|
841
|
+
!this._aggregateBase(l._type) ||
|
|
842
|
+
!this._aggregateBase(stmt.rhs[0]._type)
|
|
843
|
+
)
|
|
844
|
+
return false;
|
|
845
|
+
const root = this._rootIdentName(l);
|
|
846
|
+
return root !== null && this._fnMutates(root, true);
|
|
847
|
+
},
|
|
848
|
+
|
|
849
|
+
_genStableWholeAssign(stmt) {
|
|
850
|
+
let target = this._genAssignLhsExpr(stmt.lhs[0]);
|
|
851
|
+
const type = stmt.lhs[0]._type;
|
|
852
|
+
this._tmpCounter = (this._tmpCounter ?? 0) + 1;
|
|
853
|
+
const src = `__s${this._tmpCounter}`;
|
|
854
|
+
this.line(`{ const ${src} = ${this.genExpr(stmt.rhs[0])};`);
|
|
855
|
+
if (stmt.lhs[0].kind === "IndexExpr") {
|
|
856
|
+
// Evaluate the element reference once.
|
|
857
|
+
target = `__d${this._tmpCounter}`;
|
|
858
|
+
this.line(`const ${target} = ${this._genAssignLhsExpr(stmt.lhs[0])};`);
|
|
859
|
+
}
|
|
860
|
+
this._emitCopyInto(target, src, type);
|
|
861
|
+
this.line("}");
|
|
862
|
+
},
|
|
863
|
+
|
|
864
|
+
// Copies aggregate `src` into the storage `dst` element by element, going
|
|
865
|
+
// into nested arrays and structs so pointers to inner parts stay valid.
|
|
866
|
+
_emitCopyInto(dst, src, type) {
|
|
867
|
+
const base = this._aggregateBase(type);
|
|
868
|
+
if (base.kind === "struct") {
|
|
869
|
+
for (const [name, ft] of base.fields) {
|
|
870
|
+
if (name === "_") continue;
|
|
871
|
+
const f = `.${name}`;
|
|
872
|
+
if (this._aggregateBase(ft))
|
|
873
|
+
this._emitCopyInto(`${dst}${f}`, `${src}${f}`, ft);
|
|
874
|
+
else this.line(`${dst}${f} = ${src}${f};`);
|
|
875
|
+
}
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
this._tmpCounter = (this._tmpCounter ?? 0) + 1;
|
|
879
|
+
const k = `__k${this._tmpCounter}`;
|
|
880
|
+
this.line(`for (let ${k} = 0; ${k} < ${src}.length; ${k}++) {`);
|
|
881
|
+
if (this._aggregateBase(base.elem))
|
|
882
|
+
this._emitCopyInto(`${dst}[${k}]`, `${src}[${k}]`, base.elem);
|
|
883
|
+
else this.line(`${dst}[${k}] = ${src}[${k}];`);
|
|
884
|
+
this.line("}");
|
|
885
|
+
},
|
|
886
|
+
|
|
362
887
|
_genStructDerefAssign(stmt) {
|
|
363
888
|
const target = this.genExpr(stmt.lhs[0].operand);
|
|
364
889
|
const rhsNode = stmt.rhs[0];
|
|
@@ -385,31 +910,45 @@ export const statementGenMethods = {
|
|
|
385
910
|
},
|
|
386
911
|
|
|
387
912
|
_genComplexCompoundAssign(stmt) {
|
|
388
|
-
const
|
|
913
|
+
const l = stmt.lhs[0];
|
|
914
|
+
let lhsTarget;
|
|
915
|
+
let lhsRead;
|
|
916
|
+
if (l.kind === "IndexExpr" && l._mapValueType) {
|
|
917
|
+
const wrapField = this._namedWrapperField(l.expr._type, l.expr);
|
|
918
|
+
const base = wrapField
|
|
919
|
+
? `${this.genExpr(l.expr)}.${wrapField}`
|
|
920
|
+
: this.genExpr(l.expr);
|
|
921
|
+
const idx = this.genExpr(l.index);
|
|
922
|
+
lhsTarget = `${this._genMapWriteBase(base)}[${idx}]`;
|
|
923
|
+
lhsRead = `(${base}[${idx}] ?? { re: 0, im: 0 })`;
|
|
924
|
+
} else {
|
|
925
|
+
lhsTarget = l.name ?? this.genExpr(l);
|
|
926
|
+
lhsRead = lhsTarget;
|
|
927
|
+
}
|
|
389
928
|
const rhsExpr = this._genComplexOperand(stmt.rhs[0]);
|
|
390
929
|
const baseOp = stmt.op.slice(0, -1); // "+=" → "+"
|
|
391
930
|
let result;
|
|
392
931
|
switch (baseOp) {
|
|
393
932
|
case "+":
|
|
394
|
-
result = `{ re: ${
|
|
933
|
+
result = `{ re: ${lhsRead}.re + ${rhsExpr}.re, im: ${lhsRead}.im + ${rhsExpr}.im }`;
|
|
395
934
|
break;
|
|
396
935
|
case "-":
|
|
397
|
-
result = `{ re: ${
|
|
936
|
+
result = `{ re: ${lhsRead}.re - ${rhsExpr}.re, im: ${lhsRead}.im - ${rhsExpr}.im }`;
|
|
398
937
|
break;
|
|
399
938
|
case "*":
|
|
400
939
|
this.useHelper("cmul");
|
|
401
|
-
result = `__cmul(${
|
|
940
|
+
result = `__cmul(${lhsRead}, ${rhsExpr})`;
|
|
402
941
|
break;
|
|
403
942
|
case "/":
|
|
404
943
|
this.useHelper("cdiv");
|
|
405
|
-
result = `__cdiv(${
|
|
944
|
+
result = `__cdiv(${lhsRead}, ${rhsExpr})`;
|
|
406
945
|
break;
|
|
407
946
|
default:
|
|
408
947
|
throw new Error(
|
|
409
948
|
`unsupported complex compound assignment operator: ${stmt.op}`,
|
|
410
949
|
);
|
|
411
950
|
}
|
|
412
|
-
this.line(`${
|
|
951
|
+
this.line(`${lhsTarget} = ${result};`);
|
|
413
952
|
},
|
|
414
953
|
|
|
415
954
|
_genIteratorReturn(stmt) {
|
|
@@ -468,8 +1007,12 @@ export const statementGenMethods = {
|
|
|
468
1007
|
}
|
|
469
1008
|
},
|
|
470
1009
|
|
|
471
|
-
|
|
472
|
-
if (
|
|
1010
|
+
_genIteratorBranchStmt(stmt) {
|
|
1011
|
+
if (!stmt.label) {
|
|
1012
|
+
if (this._iterLoopDepth > 0) {
|
|
1013
|
+
if (stmt.keyword !== "fallthrough") this.line(`${stmt.keyword};`);
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
473
1016
|
if (stmt.keyword === "break") {
|
|
474
1017
|
this.line(`${this._iterBreakFlag} = true; return false;`);
|
|
475
1018
|
return;
|
|
@@ -478,6 +1021,29 @@ export const statementGenMethods = {
|
|
|
478
1021
|
this.line("return true;");
|
|
479
1022
|
return;
|
|
480
1023
|
}
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
if (this._innerLoopLabels?.has(stmt.label)) {
|
|
1027
|
+
if (stmt.keyword !== "fallthrough")
|
|
1028
|
+
this.line(`${stmt.keyword} ${stmt.label};`);
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
if (
|
|
1032
|
+
stmt.keyword === "continue" &&
|
|
1033
|
+
stmt.label === this._currentIterLoopLabel
|
|
1034
|
+
) {
|
|
1035
|
+
this.line("return true;");
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
this.line(
|
|
1039
|
+
`${this._iterTargetFlag} = "${stmt.keyword}$${stmt.label}"; return false;`,
|
|
1040
|
+
);
|
|
1041
|
+
},
|
|
1042
|
+
|
|
1043
|
+
_genBranchStmt(stmt) {
|
|
1044
|
+
if (this._inIteratorBody) {
|
|
1045
|
+
this._genIteratorBranchStmt(stmt);
|
|
1046
|
+
return;
|
|
481
1047
|
}
|
|
482
1048
|
if (stmt.keyword !== "fallthrough")
|
|
483
1049
|
this.line(
|
|
@@ -538,31 +1104,73 @@ export const statementGenMethods = {
|
|
|
538
1104
|
},
|
|
539
1105
|
|
|
540
1106
|
genFor(stmt) {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
return;
|
|
1107
|
+
const isInner = this._inIteratorBody;
|
|
1108
|
+
if (isInner) {
|
|
1109
|
+
this._iterLoopDepth = (this._iterLoopDepth ?? 0) + 1;
|
|
1110
|
+
if (stmt.label) {
|
|
1111
|
+
this._innerLoopLabels = this._innerLoopLabels ?? new Set();
|
|
1112
|
+
this._innerLoopLabels.add(stmt.label);
|
|
1113
|
+
}
|
|
549
1114
|
}
|
|
550
|
-
|
|
551
|
-
this.
|
|
552
|
-
|
|
1115
|
+
try {
|
|
1116
|
+
if (this.isRangeFor(stmt)) {
|
|
1117
|
+
if (stmt.init.rhs[0]._isIterator) this.genIteratorFor(stmt);
|
|
1118
|
+
else this.genRangeFor(stmt);
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
if (stmt.cond?.kind === "RangeExpr") {
|
|
1122
|
+
this._genRangeExprFor(stmt);
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
if (!stmt.init && !stmt.post) {
|
|
1126
|
+
this._genSimpleWhileFor(stmt);
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
const init = stmt.init ? this.stmtInline(stmt.init) : "";
|
|
1130
|
+
const cond = stmt.cond ? this.genExpr(stmt.cond) : "";
|
|
1131
|
+
const post = this._perIterationClones(
|
|
1132
|
+
stmt,
|
|
1133
|
+
stmt.post ? this.stmtInline(stmt.post) : "",
|
|
1134
|
+
);
|
|
1135
|
+
this.line(`for (${init}; ${cond}; ${post}) {`);
|
|
1136
|
+
this.indented(() => this.genBlock(stmt.body));
|
|
1137
|
+
this.line("}");
|
|
1138
|
+
} finally {
|
|
1139
|
+
if (isInner) {
|
|
1140
|
+
this._iterLoopDepth--;
|
|
1141
|
+
if (stmt.label) this._innerLoopLabels.delete(stmt.label);
|
|
1142
|
+
}
|
|
553
1143
|
}
|
|
554
|
-
const init = stmt.init ? this.stmtInline(stmt.init) : "";
|
|
555
|
-
const cond = stmt.cond ? this.genExpr(stmt.cond) : "";
|
|
556
|
-
const post = stmt.post ? this.stmtInline(stmt.post) : "";
|
|
557
|
-
this.line(`for (${init}; ${cond}; ${post}) {`);
|
|
558
|
-
this.indented(() => this.genBlock(stmt.body));
|
|
559
|
-
this.line("}");
|
|
560
1144
|
},
|
|
561
1145
|
|
|
562
1146
|
isRangeFor(stmt) {
|
|
563
1147
|
return isRangeFor(stmt);
|
|
564
1148
|
},
|
|
565
1149
|
|
|
1150
|
+
// Go 1.22 per-iteration loop variables. `let` gives each iteration its
|
|
1151
|
+
// own binding, but a struct or array variable is a reference and an
|
|
1152
|
+
// address-taken scalar is a `{ value }` box: replace it at the start of
|
|
1153
|
+
// the post step (which runs in the next iteration's binding), so closures
|
|
1154
|
+
// and pointers from the finished iteration keep their own variable.
|
|
1155
|
+
_perIterationClones(stmt, post) {
|
|
1156
|
+
const clones = [];
|
|
1157
|
+
for (const name of loopVarsCapturedInLoop(stmt)) {
|
|
1158
|
+
const i = stmt.init.lhs.findIndex((e) => e.name === name);
|
|
1159
|
+
const t = stmt.init.lhs[i]?._type ?? stmt.init.rhs?.[i]?._type;
|
|
1160
|
+
if (this._boxedVars?.has(name)) {
|
|
1161
|
+
const v = `${name}.value`;
|
|
1162
|
+
const copy = this._aggregateBase(t) ? this._cloneJs(t, v) : v;
|
|
1163
|
+
clones.push(`${name} = { value: ${copy} }`);
|
|
1164
|
+
continue;
|
|
1165
|
+
}
|
|
1166
|
+
if (!this._aggregateBase(t)) continue;
|
|
1167
|
+
clones.push(`${name} = ${this._cloneJs(t, name)}`);
|
|
1168
|
+
}
|
|
1169
|
+
if (clones.length === 0) return post;
|
|
1170
|
+
const tail = post.replace(/;\s*$/, "");
|
|
1171
|
+
return tail ? `${clones.join(", ")}, ${tail}` : clones.join(", ");
|
|
1172
|
+
},
|
|
1173
|
+
|
|
566
1174
|
_isIntRangeType(iterType) {
|
|
567
1175
|
return isIntRangeType(iterType);
|
|
568
1176
|
},
|
|
@@ -621,21 +1229,33 @@ export const statementGenMethods = {
|
|
|
621
1229
|
return mutated ? this._cloneJs(elemType, js) : js;
|
|
622
1230
|
},
|
|
623
1231
|
|
|
624
|
-
_genSliceRangeFor(stmt, iteree, isAssign) {
|
|
1232
|
+
_genSliceRangeFor(stmt, iteree, isAssign, unwrapView = false) {
|
|
625
1233
|
this._loopDepth = this._loopDepth ?? 0;
|
|
626
1234
|
const d = this._loopDepth++;
|
|
627
1235
|
const [idxNode, valNode] = stmt.init.lhs;
|
|
628
1236
|
const i = `__i${d}`;
|
|
629
1237
|
const arr = `__arr${d}`;
|
|
630
1238
|
const len = `__len${d}`;
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
1239
|
+
let elem = `${arr}[${i}]`;
|
|
1240
|
+
if (unwrapView) {
|
|
1241
|
+
// A partial view of a plain array is a Proxy (`__slice`): read the
|
|
1242
|
+
// elements from its backing array `b` at offset `o` instead.
|
|
1243
|
+
this.useHelper("sv");
|
|
1244
|
+
const [v, b, o] = [`__v${d}`, `__b${d}`, `__o${d}`];
|
|
1245
|
+
this.line(
|
|
1246
|
+
`for (let ${i} = 0, ${arr} = ${iteree}, ${v} = ${arr}?.[__sv], ${b} = ${v} === undefined ? ${arr} : ${v}.b, ${o} = ${v} === undefined ? 0 : ${v}.o, ${len} = ${v} !== undefined ? ${v}.n : ${arr} ? ${arr}.length : 0; ${i} < ${len}; ${i}++) {`,
|
|
1247
|
+
);
|
|
1248
|
+
elem = `${b}[${o} + ${i}]`;
|
|
1249
|
+
} else {
|
|
1250
|
+
this.line(
|
|
1251
|
+
`for (let ${i} = 0, ${arr} = ${iteree}, ${len} = ${arr} ? ${arr}.length : 0; ${i} < ${len}; ${i}++) {`,
|
|
1252
|
+
);
|
|
1253
|
+
}
|
|
634
1254
|
this.indented(() => {
|
|
635
1255
|
this._emitRangeVar(idxNode, i, isAssign);
|
|
636
1256
|
this._emitRangeVar(
|
|
637
1257
|
valNode,
|
|
638
|
-
this._rangeElemValue(stmt, valNode,
|
|
1258
|
+
this._rangeElemValue(stmt, valNode, elem, isAssign),
|
|
639
1259
|
isAssign,
|
|
640
1260
|
);
|
|
641
1261
|
const declared = isAssign
|
|
@@ -731,6 +1351,7 @@ export const statementGenMethods = {
|
|
|
731
1351
|
|
|
732
1352
|
_genIterFor(init, iterType, iteree, lhs, body) {
|
|
733
1353
|
const iterExpr = this._genRangeIterExpr(iterType, iteree, lhs);
|
|
1354
|
+
const keyDecode = this._structKeyRange(iterType);
|
|
734
1355
|
const binding =
|
|
735
1356
|
lhs.length === 1
|
|
736
1357
|
? lhs[0] === "_"
|
|
@@ -739,10 +1360,38 @@ export const statementGenMethods = {
|
|
|
739
1360
|
: `[${lhs.map((n) => (n === "_" ? "_$" : n)).join(", ")}]`;
|
|
740
1361
|
const kw = init.kind === "AssignStmt" ? "" : "let ";
|
|
741
1362
|
this.line(`for (${kw}${binding} of ${iterExpr}) {`);
|
|
742
|
-
this.indented(() =>
|
|
1363
|
+
this.indented(() => {
|
|
1364
|
+
if (keyDecode && lhs[0] !== "_")
|
|
1365
|
+
this.line(`${lhs[0]} = __kd(${lhs[0]}, true);`);
|
|
1366
|
+
this._cloneMapRangeValue(init, iterType, lhs[1]);
|
|
1367
|
+
this.genBlock(body);
|
|
1368
|
+
});
|
|
743
1369
|
this.line("}");
|
|
744
1370
|
},
|
|
745
1371
|
|
|
1372
|
+
// Ranging over a map whose keys are structs: the key variable is decoded
|
|
1373
|
+
// from its encoded string (see HELPER_STRUCT_KEY).
|
|
1374
|
+
_structKeyRange(iterType) {
|
|
1375
|
+
if (!this._isMapRangeType(iterType)) return false;
|
|
1376
|
+
const base = iterType?.kind === "named" ? iterType.underlying : iterType;
|
|
1377
|
+
const key = base?.kind === "map" ? base.key : null;
|
|
1378
|
+
const keyBase = key?.kind === "named" ? key.underlying : key;
|
|
1379
|
+
if (keyBase?.kind !== "struct" && !this._isIfaceType(key)) return false;
|
|
1380
|
+
this.useHelper("structKey");
|
|
1381
|
+
return true;
|
|
1382
|
+
},
|
|
1383
|
+
|
|
1384
|
+
// A map range value of array or struct type is a copy: clone it when the
|
|
1385
|
+
// body may mutate it (it otherwise aliases the stored value).
|
|
1386
|
+
_cloneMapRangeValue(init, iterType, name) {
|
|
1387
|
+
if (!name || name === "_" || !this._isMapRangeType(iterType)) return;
|
|
1388
|
+
const base = iterType?.kind === "named" ? iterType.underlying : iterType;
|
|
1389
|
+
const valType = base?.value ?? base?.elem;
|
|
1390
|
+
if (!this._aggregateBase(valType)) return;
|
|
1391
|
+
if (init.kind !== "AssignStmt" && !this._fnMutates(name)) return;
|
|
1392
|
+
this.line(`${name} = ${this._cloneJs(valType, name)};`);
|
|
1393
|
+
},
|
|
1394
|
+
|
|
746
1395
|
genRangeFor(stmt) {
|
|
747
1396
|
const init = stmt.init;
|
|
748
1397
|
const range = init.rhs[0];
|
|
@@ -768,7 +1417,26 @@ export const statementGenMethods = {
|
|
|
768
1417
|
return;
|
|
769
1418
|
}
|
|
770
1419
|
|
|
771
|
-
|
|
1420
|
+
// Go ranges over a copy of an array: the range expression is evaluated
|
|
1421
|
+
// once, so writes to the array inside the body do not reach `v`.
|
|
1422
|
+
const valNode = init.lhs[1];
|
|
1423
|
+
const rangesArrayValue =
|
|
1424
|
+
valNode &&
|
|
1425
|
+
valNode.name !== "_" &&
|
|
1426
|
+
this._aggregateBase(iterType)?.kind === "array";
|
|
1427
|
+
const iterBase =
|
|
1428
|
+
iterType?.kind === "named" ? iterType.underlying : iterType;
|
|
1429
|
+
const unwrapView =
|
|
1430
|
+
valNode?.name !== "_" &&
|
|
1431
|
+
!!valNode &&
|
|
1432
|
+
iterBase?.kind === "slice" &&
|
|
1433
|
+
!isTypedArraySlice(iterType);
|
|
1434
|
+
this._genSliceRangeFor(
|
|
1435
|
+
stmt,
|
|
1436
|
+
rangesArrayValue ? this._cloneJs(iterType, iteree) : iteree,
|
|
1437
|
+
init.kind === "AssignStmt",
|
|
1438
|
+
unwrapView,
|
|
1439
|
+
);
|
|
772
1440
|
},
|
|
773
1441
|
|
|
774
1442
|
_genRangeIterExpr(iterType, iteree, lhs) {
|
|
@@ -781,7 +1449,28 @@ export const statementGenMethods = {
|
|
|
781
1449
|
return `__s(${iteree}).entries()`;
|
|
782
1450
|
},
|
|
783
1451
|
|
|
784
|
-
|
|
1452
|
+
_emitIterTargetDispatch(targetFlag) {
|
|
1453
|
+
const labels = this._functionLabels ?? [];
|
|
1454
|
+
this.line(`if (${targetFlag}) {`);
|
|
1455
|
+
this.indented(() => {
|
|
1456
|
+
for (const lbl of labels) {
|
|
1457
|
+
this.line(`if (${targetFlag} === "break$${lbl}") break ${lbl};`);
|
|
1458
|
+
this.line(`if (${targetFlag} === "continue$${lbl}") continue ${lbl};`);
|
|
1459
|
+
}
|
|
1460
|
+
if (this._inIteratorBody) {
|
|
1461
|
+
this.line(`${this._iterTargetFlag} = ${targetFlag}; return false;`);
|
|
1462
|
+
}
|
|
1463
|
+
});
|
|
1464
|
+
this.line("}");
|
|
1465
|
+
},
|
|
1466
|
+
|
|
1467
|
+
_genIteratorLoop(
|
|
1468
|
+
itereeExpr,
|
|
1469
|
+
paramsStr,
|
|
1470
|
+
body,
|
|
1471
|
+
assignPreamble = null,
|
|
1472
|
+
loopLabel = null,
|
|
1473
|
+
) {
|
|
785
1474
|
let iteree = this.genExpr(itereeExpr);
|
|
786
1475
|
if (itereeExpr.kind === "FuncLit") {
|
|
787
1476
|
iteree = `(${iteree})`;
|
|
@@ -791,38 +1480,61 @@ export const statementGenMethods = {
|
|
|
791
1480
|
const breakFlag = `__broke${d}`;
|
|
792
1481
|
const retFlag = `__returned${d}`;
|
|
793
1482
|
const retVar = `__retVal${d}`;
|
|
1483
|
+
const targetFlag = `__target${d}`;
|
|
794
1484
|
|
|
795
1485
|
this.line("{");
|
|
796
1486
|
this.indented(() => {
|
|
797
1487
|
this.line(`let ${breakFlag} = false;`);
|
|
798
1488
|
this.line(`let ${retFlag} = false;`);
|
|
799
1489
|
this.line(`let ${retVar};`);
|
|
1490
|
+
this.line(`let ${targetFlag} = null;`);
|
|
800
1491
|
|
|
801
1492
|
this.line(`${iteree}(function(${paramsStr}) {`);
|
|
802
1493
|
this.indented(() => {
|
|
803
|
-
this.line(`if (${breakFlag}
|
|
1494
|
+
this.line(`if (${breakFlag} || ${retFlag} || ${targetFlag}) {`);
|
|
1495
|
+
this.indented(() => {
|
|
1496
|
+
if (this.strict) {
|
|
1497
|
+
this.line(
|
|
1498
|
+
`throw new Error("range function continued iteration after function for loop body returned false");`,
|
|
1499
|
+
);
|
|
1500
|
+
} else {
|
|
1501
|
+
this.line("return false;");
|
|
1502
|
+
}
|
|
1503
|
+
});
|
|
1504
|
+
this.line("}");
|
|
804
1505
|
const prev = {
|
|
805
1506
|
in: this._inIteratorBody,
|
|
806
1507
|
break: this._iterBreakFlag,
|
|
807
1508
|
ret: this._iterReturnFlag,
|
|
808
1509
|
retVar: this._iterReturnVar,
|
|
1510
|
+
target: this._iterTargetFlag,
|
|
1511
|
+
loopLabel: this._currentIterLoopLabel,
|
|
1512
|
+
loopDepth: this._iterLoopDepth,
|
|
809
1513
|
};
|
|
810
1514
|
this._inIteratorBody = true;
|
|
811
1515
|
this._iterBreakFlag = breakFlag;
|
|
812
1516
|
this._iterReturnFlag = retFlag;
|
|
813
1517
|
this._iterReturnVar = retVar;
|
|
1518
|
+
this._iterTargetFlag = targetFlag;
|
|
1519
|
+
this._currentIterLoopLabel = loopLabel;
|
|
1520
|
+
this._iterLoopDepth = 0;
|
|
814
1521
|
|
|
1522
|
+
if (assignPreamble) assignPreamble();
|
|
815
1523
|
this.genBlock(body);
|
|
816
1524
|
|
|
817
1525
|
this._inIteratorBody = prev.in;
|
|
818
1526
|
this._iterBreakFlag = prev.break;
|
|
819
1527
|
this._iterReturnFlag = prev.ret;
|
|
820
1528
|
this._iterReturnVar = prev.retVar;
|
|
1529
|
+
this._iterTargetFlag = prev.target;
|
|
1530
|
+
this._currentIterLoopLabel = prev.loopLabel;
|
|
1531
|
+
this._iterLoopDepth = prev.loopDepth;
|
|
821
1532
|
|
|
822
1533
|
this.line("return true;");
|
|
823
1534
|
});
|
|
824
1535
|
this.line("});");
|
|
825
1536
|
this.line(`if (${retFlag}) return ${retVar};`);
|
|
1537
|
+
this._emitIterTargetDispatch(targetFlag);
|
|
826
1538
|
});
|
|
827
1539
|
this.line("}");
|
|
828
1540
|
this._iterDepth--;
|
|
@@ -830,32 +1542,92 @@ export const statementGenMethods = {
|
|
|
830
1542
|
|
|
831
1543
|
genIteratorFor(stmt) {
|
|
832
1544
|
const range = stmt.init.rhs[0];
|
|
1545
|
+
const isAssign = stmt.init.kind === "AssignStmt";
|
|
833
1546
|
const lhs = stmt.init.lhs.map((e) => e.name ?? this.genExpr(e));
|
|
1547
|
+
const yieldTypes = range._yieldParams ?? [];
|
|
1548
|
+
if (isAssign) {
|
|
1549
|
+
const cbParams = lhs.map((_, i) => `__p${i}`);
|
|
1550
|
+
const params = yieldTypes.length === 0 ? "" : cbParams.join(", ");
|
|
1551
|
+
const assignPreamble = () => {
|
|
1552
|
+
for (let i = 0; i < lhs.length; i++) {
|
|
1553
|
+
if (lhs[i] !== "_") {
|
|
1554
|
+
const yType = yieldTypes[i];
|
|
1555
|
+
const isStruct =
|
|
1556
|
+
this._isStructType(yType) ||
|
|
1557
|
+
this._isStructType(stmt.init.lhs[i]?._type);
|
|
1558
|
+
if (isStruct) {
|
|
1559
|
+
this.useHelper("sclone");
|
|
1560
|
+
this.line(`${lhs[i]} = __sclone(${cbParams[i]});`);
|
|
1561
|
+
} else {
|
|
1562
|
+
this.line(`${lhs[i]} = ${cbParams[i]};`);
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
};
|
|
1567
|
+
this._genIteratorLoop(
|
|
1568
|
+
range.expr,
|
|
1569
|
+
params,
|
|
1570
|
+
stmt.body,
|
|
1571
|
+
assignPreamble,
|
|
1572
|
+
stmt.label ?? null,
|
|
1573
|
+
);
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
834
1576
|
const cbParams = lhs.map((n, i) => (n === "_" ? `_$${i}` : n));
|
|
835
|
-
const params =
|
|
836
|
-
|
|
1577
|
+
const params = yieldTypes.length === 0 ? "" : cbParams.join(", ");
|
|
1578
|
+
const clonePreamble = () => {
|
|
1579
|
+
for (let i = 0; i < lhs.length; i++) {
|
|
1580
|
+
if (lhs[i] !== "_") {
|
|
1581
|
+
const yType = yieldTypes[i];
|
|
1582
|
+
const isStruct =
|
|
1583
|
+
this._isStructType(yType) ||
|
|
1584
|
+
this._isStructType(stmt.init.lhs[i]?._type);
|
|
1585
|
+
if (isStruct) {
|
|
1586
|
+
this.useHelper("sclone");
|
|
1587
|
+
this.line(`${cbParams[i]} = __sclone(${cbParams[i]});`);
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
};
|
|
1592
|
+
this._genIteratorLoop(
|
|
1593
|
+
range.expr,
|
|
1594
|
+
params,
|
|
1595
|
+
stmt.body,
|
|
1596
|
+
clonePreamble,
|
|
1597
|
+
stmt.label ?? null,
|
|
1598
|
+
);
|
|
837
1599
|
},
|
|
838
1600
|
|
|
839
1601
|
// 0-param iterator: for range iter { body }
|
|
840
1602
|
genIteratorForCond(stmt) {
|
|
841
|
-
this._genIteratorLoop(
|
|
1603
|
+
this._genIteratorLoop(
|
|
1604
|
+
stmt.cond.expr,
|
|
1605
|
+
"",
|
|
1606
|
+
stmt.body,
|
|
1607
|
+
null,
|
|
1608
|
+
stmt.label ?? null,
|
|
1609
|
+
);
|
|
842
1610
|
},
|
|
843
1611
|
|
|
844
1612
|
// Inline a statement as a for-init/post string (no semicolon)
|
|
845
1613
|
stmtInline(stmt) {
|
|
846
1614
|
switch (stmt.kind) {
|
|
847
|
-
case "DefineStmt":
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
return
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
return
|
|
1615
|
+
case "DefineStmt":
|
|
1616
|
+
return this._inlineDefine(stmt);
|
|
1617
|
+
case "AssignStmt":
|
|
1618
|
+
return this._withInlineSyncs(
|
|
1619
|
+
this._resliceSyncs(stmt.lhs, stmt, true),
|
|
1620
|
+
this._inlineAssign(stmt),
|
|
1621
|
+
);
|
|
1622
|
+
case "IncDecStmt": {
|
|
1623
|
+
const syncs = this._resliceSyncs([stmt.expr], stmt, true);
|
|
1624
|
+
this._markIndexLvalues([stmt.expr]);
|
|
1625
|
+
this._markDerefOpTarget(stmt.expr);
|
|
1626
|
+
return this._withInlineSyncs(
|
|
1627
|
+
syncs,
|
|
1628
|
+
`${this.genExpr(stmt.expr)}${stmt.op}`,
|
|
1629
|
+
);
|
|
1630
|
+
}
|
|
859
1631
|
case "ExprStmt":
|
|
860
1632
|
return this.genExpr(stmt.expr);
|
|
861
1633
|
default:
|
|
@@ -863,14 +1635,68 @@ export const statementGenMethods = {
|
|
|
863
1635
|
}
|
|
864
1636
|
},
|
|
865
1637
|
|
|
1638
|
+
_withInlineSyncs(syncs, js) {
|
|
1639
|
+
return syncs ? `${js}, ${syncs.join(", ")}` : js;
|
|
1640
|
+
},
|
|
1641
|
+
|
|
1642
|
+
_inlineDefine(stmt) {
|
|
1643
|
+
const boxed =
|
|
1644
|
+
stmt.lhs.length === 1 &&
|
|
1645
|
+
stmt.rhs.length === 1 &&
|
|
1646
|
+
this._boxedVars.has(stmt.lhs[0].name);
|
|
1647
|
+
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));
|
|
1650
|
+
const lhs = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
|
|
1651
|
+
if (lhs.length === 1) return `let ${lhs[0]} = ${rhs[0]}`;
|
|
1652
|
+
// `for i, j := 0, 10`: one declarator per variable.
|
|
1653
|
+
if (rhs.length === lhs.length) {
|
|
1654
|
+
const decls = lhs.map((n, i) =>
|
|
1655
|
+
this._boxedVars.has(n)
|
|
1656
|
+
? `${n} = { value: ${rhs[i]} }`
|
|
1657
|
+
: `${n} = ${rhs[i]}`,
|
|
1658
|
+
);
|
|
1659
|
+
return `let ${decls.join(", ")}`;
|
|
1660
|
+
}
|
|
1661
|
+
return `let [${lhs.map((n) => (n === "_" ? "" : n)).join(", ")}] = ${rhs[0]}`;
|
|
1662
|
+
},
|
|
1663
|
+
|
|
1664
|
+
_inlineAssign(stmt) {
|
|
1665
|
+
this._markIndexLvalues(stmt.lhs);
|
|
1666
|
+
const rhs = stmt.rhs.map((e) => this.genExpr(e));
|
|
1667
|
+
if (stmt.lhs.length === 1)
|
|
1668
|
+
return `${this.genExpr(stmt.lhs[0])} ${stmt.op} ${rhs[0]}`;
|
|
1669
|
+
// `i, j = i+1, j-1`: every right-hand side is evaluated first.
|
|
1670
|
+
const lhs = stmt.lhs.map((e) =>
|
|
1671
|
+
e.name === "_" ? "" : this._genAssignLhsExpr(e),
|
|
1672
|
+
);
|
|
1673
|
+
const r = rhs.length === 1 ? rhs[0] : `[${rhs.join(", ")}]`;
|
|
1674
|
+
return `[${lhs.join(", ")}] = ${r}`;
|
|
1675
|
+
},
|
|
1676
|
+
|
|
1677
|
+
// Interface-typed tags may hold boxed named values; cases compare with __ifeq.
|
|
1678
|
+
_switchTagAndCases(stmt) {
|
|
1679
|
+
const tag = stmt.tag ? this.genExpr(stmt.tag) : "true";
|
|
1680
|
+
if (!stmt.tag || !this._isIfaceType(stmt.tag._type))
|
|
1681
|
+
return { tag, caseExpr: (e) => this.genExpr(e) };
|
|
1682
|
+
this.useHelper("ifeq");
|
|
1683
|
+
this._swCounter = (this._swCounter ?? 0) + 1;
|
|
1684
|
+
const tmp = `__sw${this._swCounter}`;
|
|
1685
|
+
this.line(`const ${tmp} = ${tag};`);
|
|
1686
|
+
return {
|
|
1687
|
+
tag: "true",
|
|
1688
|
+
caseExpr: (e) => `__ifeq(${tmp}, ${this._genIfaceOperand(e)})`,
|
|
1689
|
+
};
|
|
1690
|
+
},
|
|
1691
|
+
|
|
866
1692
|
genSwitch(stmt) {
|
|
867
1693
|
const genBody = () => {
|
|
868
|
-
const tag
|
|
1694
|
+
const { tag, caseExpr } = this._switchTagAndCases(stmt);
|
|
869
1695
|
this.line(`switch (${tag}) {`);
|
|
870
1696
|
this.indented(() => {
|
|
871
1697
|
for (const c of stmt.cases) {
|
|
872
1698
|
if (c.list) {
|
|
873
|
-
for (const e of c.list) this.line(`case ${
|
|
1699
|
+
for (const e of c.list) this.line(`case ${caseExpr(e)}:`);
|
|
874
1700
|
} else {
|
|
875
1701
|
this.line("default:");
|
|
876
1702
|
}
|
|
@@ -926,6 +1752,24 @@ export const statementGenMethods = {
|
|
|
926
1752
|
.join(" || ");
|
|
927
1753
|
this.line(`${first ? "if" : "else if"} (${cond}) {`);
|
|
928
1754
|
this.indented(() => {
|
|
1755
|
+
// A single named non-struct case binds the unboxed value.
|
|
1756
|
+
if (
|
|
1757
|
+
stmt.assign &&
|
|
1758
|
+
c.types.length === 1 &&
|
|
1759
|
+
this._assertedValueJs(c.types[0], "__tsw") !== "__tsw"
|
|
1760
|
+
)
|
|
1761
|
+
this.line(
|
|
1762
|
+
`const ${stmt.assign} = ${this._assertedValueJs(c.types[0], "__tsw")};`,
|
|
1763
|
+
);
|
|
1764
|
+
// An array case binds a copy of the boxed value.
|
|
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
|
+
);
|
|
929
1773
|
for (const s of c.stmts) this.genStmt(s);
|
|
930
1774
|
});
|
|
931
1775
|
this.line("}");
|