gofront 1.7.0 → 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 +13 -13
- package/package.json +8 -37
- package/src/backend/js/expressions.js +1341 -148
- package/src/backend/js/fmt-runtime.js +573 -0
- package/src/backend/js/index.js +375 -57
- package/src/backend/js/runtime.js +1460 -133
- package/src/backend/js/statements.js +900 -86
- 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 +2 -1
- package/src/backend/js/stdlib/fmt.js +349 -10
- package/src/backend/js/stdlib/index.js +8 -3
- package/src/backend/js/stdlib/json.js +247 -52
- 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 +10 -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 +436 -80
- package/src/backend/wasm/emit-builtins.js +303 -13
- package/src/backend/wasm/emit-exprs.js +1397 -203
- package/src/backend/wasm/emit-maps.js +61 -13
- package/src/backend/wasm/emit-stmts.js +1359 -164
- package/src/backend/wasm/emit.js +512 -77
- package/src/backend/wasm/encode.js +43 -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/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 +0 -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 +293 -28
- package/src/backend/wasm/stdlib/time.js +1041 -12
- package/src/backend/wasm/types.js +200 -15
- package/src/backend/wasm/variadic.js +93 -0
- package/src/backend/wasm/wat.js +47 -20
- package/src/{cli-core.js → cli/cli-core.js} +236 -10
- package/src/{test-runner.js → cli/test-runner.js} +332 -39
- package/src/{vendor.js → cli/vendor.js} +3 -4
- package/src/compiler.js +107 -269
- 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 -1
- package/src/lower/captures.js +176 -32
- 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/{templ-parser.js → parser/templ.js} +138 -15
- package/src/parser/types.js +69 -9
- package/src/resolver.js +22 -1
- package/src/typechecker/assignability.js +176 -17
- package/src/typechecker/const-fold.js +356 -0
- package/src/typechecker/constants.js +256 -0
- package/src/typechecker/expressions.js +931 -122
- package/src/typechecker/index.js +459 -67
- package/src/typechecker/init-order.js +330 -0
- package/src/typechecker/resolve.js +451 -56
- package/src/typechecker/statements.js +90 -9
- package/src/typechecker/stdlib/core.js +333 -55
- package/src/typechecker/stdlib/excluded.js +898 -0
- package/src/typechecker/stdlib/extended.js +322 -26
- package/src/typechecker/{stdlib.js → stdlib/index.js} +5 -5
- package/src/typechecker/stdlib/simd.js +65 -15
- package/src/typechecker/termination.js +1 -1
- package/src/typechecker/types.js +149 -25
- 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,22 +368,14 @@ export const statementGenMethods = {
|
|
|
178
368
|
},
|
|
179
369
|
|
|
180
370
|
_genDefineMixedRedecl(lhs, rhs, redecls) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
} else {
|
|
190
|
-
for (let i = 0; i < lhs.length; i++) {
|
|
191
|
-
if (lhs[i] === "_") {
|
|
192
|
-
this.line(`${rhs[i]};`);
|
|
193
|
-
continue;
|
|
194
|
-
}
|
|
195
|
-
this._genDefineSingleVar([lhs[i]], [rhs[i]], [redecls[i]]);
|
|
196
|
-
}
|
|
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]]);
|
|
197
379
|
}
|
|
198
380
|
},
|
|
199
381
|
|
|
@@ -206,19 +388,23 @@ export const statementGenMethods = {
|
|
|
206
388
|
}
|
|
207
389
|
},
|
|
208
390
|
|
|
209
|
-
|
|
210
|
-
const rhsNode = stmt.rhs[0];
|
|
211
|
-
if (this._isCommaOkMapExpr(stmt, rhsNode)) {
|
|
212
|
-
this._genCommaOkMapIndex(stmt, rhsNode, true);
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
391
|
+
_defineRhs(stmt) {
|
|
215
392
|
const paired = stmt.rhs.length === stmt.lhs.length;
|
|
216
|
-
|
|
393
|
+
return stmt.rhs.map((e, i) => {
|
|
217
394
|
const l = paired ? stmt.lhs[i] : null;
|
|
218
395
|
if (!l || l.name === "_") return this.genExpr(e);
|
|
219
396
|
if (l._redecl) return this.genValueExpr(e);
|
|
220
397
|
return this._genDeclValue(l.name, e);
|
|
221
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);
|
|
222
408
|
const lhs = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
|
|
223
409
|
const redecls = stmt.lhs.map((e) => !!e._redecl);
|
|
224
410
|
const anyRedecl = redecls.some((r) => r);
|
|
@@ -249,6 +435,68 @@ export const statementGenMethods = {
|
|
|
249
435
|
);
|
|
250
436
|
},
|
|
251
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
|
+
|
|
252
500
|
_isStructDerefAssign(stmt) {
|
|
253
501
|
if (stmt.lhs.length !== 1 || stmt.rhs.length !== 1 || stmt.op !== "=")
|
|
254
502
|
return false;
|
|
@@ -270,6 +518,13 @@ export const statementGenMethods = {
|
|
|
270
518
|
for (const e of exprs) if (e.kind === "IndexExpr") e._lvalue = true;
|
|
271
519
|
},
|
|
272
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
|
+
|
|
273
528
|
_genAssignMulti(stmt, lhs, rhs, active, pairs) {
|
|
274
529
|
const rhsStr = rhs.length === 1 ? rhs[0] : `[${rhs.join(", ")}]`;
|
|
275
530
|
if (active.length === pairs.length) {
|
|
@@ -279,7 +534,132 @@ export const statementGenMethods = {
|
|
|
279
534
|
}
|
|
280
535
|
},
|
|
281
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
|
+
|
|
282
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);
|
|
283
663
|
if (this._isComplexCompoundAssign(stmt)) {
|
|
284
664
|
this._genComplexCompoundAssign(stmt);
|
|
285
665
|
return;
|
|
@@ -292,18 +672,38 @@ export const statementGenMethods = {
|
|
|
292
672
|
this._genCommaOkTypeAssert(stmt);
|
|
293
673
|
return;
|
|
294
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
|
+
}
|
|
295
690
|
if (this._isStructDerefAssign(stmt)) {
|
|
296
691
|
this._genStructDerefAssign(stmt);
|
|
297
692
|
return;
|
|
298
693
|
}
|
|
694
|
+
if (this._isStableWholeAssign(stmt)) {
|
|
695
|
+
this._genStableWholeAssign(stmt);
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
299
698
|
const paired = stmt.rhs.length === stmt.lhs.length && stmt.op === "=";
|
|
300
699
|
const rhs = stmt.rhs.map((e, i) =>
|
|
301
700
|
paired && stmt.lhs[i].name !== "_"
|
|
302
701
|
? this.genValueExpr(e)
|
|
303
702
|
: this.genExpr(e),
|
|
304
703
|
);
|
|
305
|
-
this.
|
|
704
|
+
const hoisted = this._hoistLhsOperands(stmt);
|
|
306
705
|
const lhs = stmt.lhs.map((e) => this._genAssignLhsExpr(e));
|
|
706
|
+
hoisted?.restore();
|
|
307
707
|
const pairs = lhs.map((l, i) => ({ l, r: rhs[i] ?? rhs[0] }));
|
|
308
708
|
const active = pairs.filter((p) => p.l !== "_");
|
|
309
709
|
if (active.length === 0) {
|
|
@@ -329,11 +729,11 @@ export const statementGenMethods = {
|
|
|
329
729
|
this._hasCallExpr(stmt.lhs[0].index)
|
|
330
730
|
) {
|
|
331
731
|
this.line(
|
|
332
|
-
`((__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});`,
|
|
333
733
|
);
|
|
334
734
|
} else {
|
|
335
735
|
this.line(
|
|
336
|
-
`${base}[${idx}] = (${base}[${idx}] ?? ${zero}) ${binOp} (${active[0].r});`,
|
|
736
|
+
`${this._genMapWriteBase(base)}[${idx}] = (${base}[${idx}] ?? ${zero}) ${binOp} (${active[0].r});`,
|
|
337
737
|
);
|
|
338
738
|
}
|
|
339
739
|
return;
|
|
@@ -351,12 +751,67 @@ export const statementGenMethods = {
|
|
|
351
751
|
) {
|
|
352
752
|
return;
|
|
353
753
|
}
|
|
354
|
-
|
|
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("}");
|
|
355
766
|
} else {
|
|
356
767
|
this._genAssignMulti(stmt, lhs, rhs, active, pairs);
|
|
357
768
|
}
|
|
358
769
|
},
|
|
359
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
|
+
|
|
360
815
|
_genCommaOkTypeAssert(stmt) {
|
|
361
816
|
const val = this.genExpr(stmt.rhs[0]);
|
|
362
817
|
const [vName, okName] = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
|
|
@@ -367,6 +822,68 @@ export const statementGenMethods = {
|
|
|
367
822
|
if (okName !== "_") this.line(`${okName} = ${tmp}[1];`);
|
|
368
823
|
},
|
|
369
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
|
+
|
|
370
887
|
_genStructDerefAssign(stmt) {
|
|
371
888
|
const target = this.genExpr(stmt.lhs[0].operand);
|
|
372
889
|
const rhsNode = stmt.rhs[0];
|
|
@@ -393,31 +910,45 @@ export const statementGenMethods = {
|
|
|
393
910
|
},
|
|
394
911
|
|
|
395
912
|
_genComplexCompoundAssign(stmt) {
|
|
396
|
-
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
|
+
}
|
|
397
928
|
const rhsExpr = this._genComplexOperand(stmt.rhs[0]);
|
|
398
929
|
const baseOp = stmt.op.slice(0, -1); // "+=" → "+"
|
|
399
930
|
let result;
|
|
400
931
|
switch (baseOp) {
|
|
401
932
|
case "+":
|
|
402
|
-
result = `{ re: ${
|
|
933
|
+
result = `{ re: ${lhsRead}.re + ${rhsExpr}.re, im: ${lhsRead}.im + ${rhsExpr}.im }`;
|
|
403
934
|
break;
|
|
404
935
|
case "-":
|
|
405
|
-
result = `{ re: ${
|
|
936
|
+
result = `{ re: ${lhsRead}.re - ${rhsExpr}.re, im: ${lhsRead}.im - ${rhsExpr}.im }`;
|
|
406
937
|
break;
|
|
407
938
|
case "*":
|
|
408
939
|
this.useHelper("cmul");
|
|
409
|
-
result = `__cmul(${
|
|
940
|
+
result = `__cmul(${lhsRead}, ${rhsExpr})`;
|
|
410
941
|
break;
|
|
411
942
|
case "/":
|
|
412
943
|
this.useHelper("cdiv");
|
|
413
|
-
result = `__cdiv(${
|
|
944
|
+
result = `__cdiv(${lhsRead}, ${rhsExpr})`;
|
|
414
945
|
break;
|
|
415
946
|
default:
|
|
416
947
|
throw new Error(
|
|
417
948
|
`unsupported complex compound assignment operator: ${stmt.op}`,
|
|
418
949
|
);
|
|
419
950
|
}
|
|
420
|
-
this.line(`${
|
|
951
|
+
this.line(`${lhsTarget} = ${result};`);
|
|
421
952
|
},
|
|
422
953
|
|
|
423
954
|
_genIteratorReturn(stmt) {
|
|
@@ -476,8 +1007,12 @@ export const statementGenMethods = {
|
|
|
476
1007
|
}
|
|
477
1008
|
},
|
|
478
1009
|
|
|
479
|
-
|
|
480
|
-
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
|
+
}
|
|
481
1016
|
if (stmt.keyword === "break") {
|
|
482
1017
|
this.line(`${this._iterBreakFlag} = true; return false;`);
|
|
483
1018
|
return;
|
|
@@ -486,6 +1021,29 @@ export const statementGenMethods = {
|
|
|
486
1021
|
this.line("return true;");
|
|
487
1022
|
return;
|
|
488
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;
|
|
489
1047
|
}
|
|
490
1048
|
if (stmt.keyword !== "fallthrough")
|
|
491
1049
|
this.line(
|
|
@@ -546,31 +1104,73 @@ export const statementGenMethods = {
|
|
|
546
1104
|
},
|
|
547
1105
|
|
|
548
1106
|
genFor(stmt) {
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
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
|
+
}
|
|
557
1114
|
}
|
|
558
|
-
|
|
559
|
-
this.
|
|
560
|
-
|
|
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
|
+
}
|
|
561
1143
|
}
|
|
562
|
-
const init = stmt.init ? this.stmtInline(stmt.init) : "";
|
|
563
|
-
const cond = stmt.cond ? this.genExpr(stmt.cond) : "";
|
|
564
|
-
const post = stmt.post ? this.stmtInline(stmt.post) : "";
|
|
565
|
-
this.line(`for (${init}; ${cond}; ${post}) {`);
|
|
566
|
-
this.indented(() => this.genBlock(stmt.body));
|
|
567
|
-
this.line("}");
|
|
568
1144
|
},
|
|
569
1145
|
|
|
570
1146
|
isRangeFor(stmt) {
|
|
571
1147
|
return isRangeFor(stmt);
|
|
572
1148
|
},
|
|
573
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
|
+
|
|
574
1174
|
_isIntRangeType(iterType) {
|
|
575
1175
|
return isIntRangeType(iterType);
|
|
576
1176
|
},
|
|
@@ -629,21 +1229,33 @@ export const statementGenMethods = {
|
|
|
629
1229
|
return mutated ? this._cloneJs(elemType, js) : js;
|
|
630
1230
|
},
|
|
631
1231
|
|
|
632
|
-
_genSliceRangeFor(stmt, iteree, isAssign) {
|
|
1232
|
+
_genSliceRangeFor(stmt, iteree, isAssign, unwrapView = false) {
|
|
633
1233
|
this._loopDepth = this._loopDepth ?? 0;
|
|
634
1234
|
const d = this._loopDepth++;
|
|
635
1235
|
const [idxNode, valNode] = stmt.init.lhs;
|
|
636
1236
|
const i = `__i${d}`;
|
|
637
1237
|
const arr = `__arr${d}`;
|
|
638
1238
|
const len = `__len${d}`;
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
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
|
+
}
|
|
642
1254
|
this.indented(() => {
|
|
643
1255
|
this._emitRangeVar(idxNode, i, isAssign);
|
|
644
1256
|
this._emitRangeVar(
|
|
645
1257
|
valNode,
|
|
646
|
-
this._rangeElemValue(stmt, valNode,
|
|
1258
|
+
this._rangeElemValue(stmt, valNode, elem, isAssign),
|
|
647
1259
|
isAssign,
|
|
648
1260
|
);
|
|
649
1261
|
const declared = isAssign
|
|
@@ -739,6 +1351,7 @@ export const statementGenMethods = {
|
|
|
739
1351
|
|
|
740
1352
|
_genIterFor(init, iterType, iteree, lhs, body) {
|
|
741
1353
|
const iterExpr = this._genRangeIterExpr(iterType, iteree, lhs);
|
|
1354
|
+
const keyDecode = this._structKeyRange(iterType);
|
|
742
1355
|
const binding =
|
|
743
1356
|
lhs.length === 1
|
|
744
1357
|
? lhs[0] === "_"
|
|
@@ -747,10 +1360,38 @@ export const statementGenMethods = {
|
|
|
747
1360
|
: `[${lhs.map((n) => (n === "_" ? "_$" : n)).join(", ")}]`;
|
|
748
1361
|
const kw = init.kind === "AssignStmt" ? "" : "let ";
|
|
749
1362
|
this.line(`for (${kw}${binding} of ${iterExpr}) {`);
|
|
750
|
-
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
|
+
});
|
|
751
1369
|
this.line("}");
|
|
752
1370
|
},
|
|
753
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
|
+
|
|
754
1395
|
genRangeFor(stmt) {
|
|
755
1396
|
const init = stmt.init;
|
|
756
1397
|
const range = init.rhs[0];
|
|
@@ -776,7 +1417,26 @@ export const statementGenMethods = {
|
|
|
776
1417
|
return;
|
|
777
1418
|
}
|
|
778
1419
|
|
|
779
|
-
|
|
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
|
+
);
|
|
780
1440
|
},
|
|
781
1441
|
|
|
782
1442
|
_genRangeIterExpr(iterType, iteree, lhs) {
|
|
@@ -789,7 +1449,28 @@ export const statementGenMethods = {
|
|
|
789
1449
|
return `__s(${iteree}).entries()`;
|
|
790
1450
|
},
|
|
791
1451
|
|
|
792
|
-
|
|
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
|
+
) {
|
|
793
1474
|
let iteree = this.genExpr(itereeExpr);
|
|
794
1475
|
if (itereeExpr.kind === "FuncLit") {
|
|
795
1476
|
iteree = `(${iteree})`;
|
|
@@ -799,38 +1480,61 @@ export const statementGenMethods = {
|
|
|
799
1480
|
const breakFlag = `__broke${d}`;
|
|
800
1481
|
const retFlag = `__returned${d}`;
|
|
801
1482
|
const retVar = `__retVal${d}`;
|
|
1483
|
+
const targetFlag = `__target${d}`;
|
|
802
1484
|
|
|
803
1485
|
this.line("{");
|
|
804
1486
|
this.indented(() => {
|
|
805
1487
|
this.line(`let ${breakFlag} = false;`);
|
|
806
1488
|
this.line(`let ${retFlag} = false;`);
|
|
807
1489
|
this.line(`let ${retVar};`);
|
|
1490
|
+
this.line(`let ${targetFlag} = null;`);
|
|
808
1491
|
|
|
809
1492
|
this.line(`${iteree}(function(${paramsStr}) {`);
|
|
810
1493
|
this.indented(() => {
|
|
811
|
-
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("}");
|
|
812
1505
|
const prev = {
|
|
813
1506
|
in: this._inIteratorBody,
|
|
814
1507
|
break: this._iterBreakFlag,
|
|
815
1508
|
ret: this._iterReturnFlag,
|
|
816
1509
|
retVar: this._iterReturnVar,
|
|
1510
|
+
target: this._iterTargetFlag,
|
|
1511
|
+
loopLabel: this._currentIterLoopLabel,
|
|
1512
|
+
loopDepth: this._iterLoopDepth,
|
|
817
1513
|
};
|
|
818
1514
|
this._inIteratorBody = true;
|
|
819
1515
|
this._iterBreakFlag = breakFlag;
|
|
820
1516
|
this._iterReturnFlag = retFlag;
|
|
821
1517
|
this._iterReturnVar = retVar;
|
|
1518
|
+
this._iterTargetFlag = targetFlag;
|
|
1519
|
+
this._currentIterLoopLabel = loopLabel;
|
|
1520
|
+
this._iterLoopDepth = 0;
|
|
822
1521
|
|
|
1522
|
+
if (assignPreamble) assignPreamble();
|
|
823
1523
|
this.genBlock(body);
|
|
824
1524
|
|
|
825
1525
|
this._inIteratorBody = prev.in;
|
|
826
1526
|
this._iterBreakFlag = prev.break;
|
|
827
1527
|
this._iterReturnFlag = prev.ret;
|
|
828
1528
|
this._iterReturnVar = prev.retVar;
|
|
1529
|
+
this._iterTargetFlag = prev.target;
|
|
1530
|
+
this._currentIterLoopLabel = prev.loopLabel;
|
|
1531
|
+
this._iterLoopDepth = prev.loopDepth;
|
|
829
1532
|
|
|
830
1533
|
this.line("return true;");
|
|
831
1534
|
});
|
|
832
1535
|
this.line("});");
|
|
833
1536
|
this.line(`if (${retFlag}) return ${retVar};`);
|
|
1537
|
+
this._emitIterTargetDispatch(targetFlag);
|
|
834
1538
|
});
|
|
835
1539
|
this.line("}");
|
|
836
1540
|
this._iterDepth--;
|
|
@@ -838,32 +1542,92 @@ export const statementGenMethods = {
|
|
|
838
1542
|
|
|
839
1543
|
genIteratorFor(stmt) {
|
|
840
1544
|
const range = stmt.init.rhs[0];
|
|
1545
|
+
const isAssign = stmt.init.kind === "AssignStmt";
|
|
841
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
|
+
}
|
|
842
1576
|
const cbParams = lhs.map((n, i) => (n === "_" ? `_$${i}` : n));
|
|
843
|
-
const params =
|
|
844
|
-
|
|
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
|
+
);
|
|
845
1599
|
},
|
|
846
1600
|
|
|
847
1601
|
// 0-param iterator: for range iter { body }
|
|
848
1602
|
genIteratorForCond(stmt) {
|
|
849
|
-
this._genIteratorLoop(
|
|
1603
|
+
this._genIteratorLoop(
|
|
1604
|
+
stmt.cond.expr,
|
|
1605
|
+
"",
|
|
1606
|
+
stmt.body,
|
|
1607
|
+
null,
|
|
1608
|
+
stmt.label ?? null,
|
|
1609
|
+
);
|
|
850
1610
|
},
|
|
851
1611
|
|
|
852
1612
|
// Inline a statement as a for-init/post string (no semicolon)
|
|
853
1613
|
stmtInline(stmt) {
|
|
854
1614
|
switch (stmt.kind) {
|
|
855
|
-
case "DefineStmt":
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
return
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
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
|
+
}
|
|
867
1631
|
case "ExprStmt":
|
|
868
1632
|
return this.genExpr(stmt.expr);
|
|
869
1633
|
default:
|
|
@@ -871,6 +1635,45 @@ export const statementGenMethods = {
|
|
|
871
1635
|
}
|
|
872
1636
|
},
|
|
873
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
|
+
|
|
874
1677
|
// Interface-typed tags may hold boxed named values; cases compare with __ifeq.
|
|
875
1678
|
_switchTagAndCases(stmt) {
|
|
876
1679
|
const tag = stmt.tag ? this.genExpr(stmt.tag) : "true";
|
|
@@ -953,9 +1756,20 @@ export const statementGenMethods = {
|
|
|
953
1756
|
if (
|
|
954
1757
|
stmt.assign &&
|
|
955
1758
|
c.types.length === 1 &&
|
|
956
|
-
this.
|
|
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"
|
|
957
1769
|
)
|
|
958
|
-
this.line(
|
|
1770
|
+
this.line(
|
|
1771
|
+
`let ${stmt.assign} = ${this._cloneJs(this._typeFromNode(c.types[0]), "__tsw")};`,
|
|
1772
|
+
);
|
|
959
1773
|
for (const s of c.stmts) this.genStmt(s);
|
|
960
1774
|
});
|
|
961
1775
|
this.line("}");
|