gofront 0.0.9 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/package.json +7 -4
- package/src/cli-core.js +65 -0
- package/src/codegen/expressions.js +523 -1717
- package/src/codegen/index.js +576 -0
- package/src/codegen/runtime.js +151 -0
- package/src/codegen/statements.js +308 -296
- package/src/codegen/stdlib/builder.js +39 -0
- package/src/codegen/stdlib/bytes.js +65 -0
- package/src/codegen/stdlib/errors.js +20 -0
- package/src/codegen/stdlib/fmt.js +104 -0
- package/src/codegen/stdlib/gom.js +155 -0
- package/src/codegen/stdlib/html.js +20 -0
- package/src/codegen/stdlib/index.js +98 -0
- package/src/codegen/stdlib/io.js +36 -0
- package/src/codegen/stdlib/maps.js +45 -0
- package/src/codegen/stdlib/math.js +56 -0
- package/src/codegen/stdlib/os.js +13 -0
- package/src/codegen/stdlib/path.js +34 -0
- package/src/codegen/stdlib/rand.js +27 -0
- package/src/codegen/stdlib/regexp.js +54 -0
- package/src/codegen/stdlib/slices.js +51 -0
- package/src/codegen/stdlib/sort.js +31 -0
- package/src/codegen/stdlib/strconv.js +33 -0
- package/src/codegen/stdlib/strings.js +86 -0
- package/src/codegen/stdlib/time.js +57 -0
- package/src/codegen/stdlib/unicode.js +25 -0
- package/src/codegen/stdlib/utf8.js +26 -0
- package/src/codegen/templ.js +284 -0
- package/src/compiler.js +142 -77
- package/src/dts-parser.js +355 -371
- package/src/index.js +32 -162
- package/src/lexer.js +186 -272
- package/src/minifier.js +305 -318
- package/src/{parser.js → parser/declarations.js} +18 -175
- package/src/parser/expressions.js +51 -34
- package/src/parser/index.js +161 -0
- package/src/parser/statements.js +24 -34
- package/src/parser/types.js +31 -29
- package/src/templ-parser.js +2 -2
- package/src/tokens.js +109 -0
- package/src/typechecker/assignability.js +261 -0
- package/src/typechecker/expressions.js +568 -549
- package/src/typechecker/index.js +556 -0
- package/src/typechecker/resolve.js +428 -0
- package/src/typechecker/statements.js +270 -280
- package/src/typechecker/stdlib/core.js +592 -0
- package/src/typechecker/stdlib/extended.js +332 -0
- package/src/typechecker/stdlib.js +9 -1060
- package/src/typechecker/termination.js +86 -0
- package/src/typechecker/types.js +1 -0
- package/src/codegen.js +0 -882
- package/src/typechecker.js +0 -1299
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
import { isComplex } from "../typechecker/types.js";
|
|
4
4
|
|
|
5
|
+
/** @typedef {import('./index.js').CodeGen} CodeGen */
|
|
6
|
+
|
|
7
|
+
// Method-name dispatch for genStmt — string values add no static call edges.
|
|
8
|
+
const STMT_GEN_DELEGATE = {
|
|
9
|
+
VarDecl: "genVarDecl",
|
|
10
|
+
ConstDecl: "genConstDecl",
|
|
11
|
+
DefineStmt: "_genDefineStmt",
|
|
12
|
+
AssignStmt: "_genAssignStmt",
|
|
13
|
+
ReturnStmt: "_genReturnStmt",
|
|
14
|
+
ForStmt: "genFor",
|
|
15
|
+
SwitchStmt: "genSwitch",
|
|
16
|
+
TypeSwitchStmt: "genTypeSwitch",
|
|
17
|
+
BranchStmt: "_genBranchStmt",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/** @type {ThisType<CodeGen>} */
|
|
5
21
|
export const statementGenMethods = {
|
|
6
22
|
genBlock(block) {
|
|
7
23
|
for (const stmt of block.stmts) this.genStmt(stmt);
|
|
@@ -36,210 +52,17 @@ export const statementGenMethods = {
|
|
|
36
52
|
const srcLine = stmt._line ?? null;
|
|
37
53
|
const _line0 = this.out.length;
|
|
38
54
|
switch (stmt.kind) {
|
|
39
|
-
case "VarDecl":
|
|
40
|
-
this.genVarDecl(stmt);
|
|
41
|
-
break;
|
|
42
|
-
case "ConstDecl":
|
|
43
|
-
this.genConstDecl(stmt);
|
|
44
|
-
break;
|
|
45
55
|
case "TypeDecl":
|
|
46
56
|
this.genTypeDeclWithMethods(stmt, []);
|
|
47
57
|
break;
|
|
48
|
-
|
|
49
|
-
case "DefineStmt": {
|
|
50
|
-
// Map comma-ok: v, ok := m[key] → let v = m[key]; let ok = key in m;
|
|
51
|
-
const rhsNode = stmt.rhs[0];
|
|
52
|
-
if (
|
|
53
|
-
stmt.rhs.length === 1 &&
|
|
54
|
-
stmt.lhs.length === 2 &&
|
|
55
|
-
rhsNode?.kind === "IndexExpr" &&
|
|
56
|
-
rhsNode.expr?._type?.kind === "map"
|
|
57
|
-
) {
|
|
58
|
-
this._genCommaOkMapIndex(stmt, rhsNode, true);
|
|
59
|
-
break;
|
|
60
|
-
}
|
|
61
|
-
const rhs = stmt.rhs.map((e) => this.genExpr(e));
|
|
62
|
-
const lhs = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
|
|
63
|
-
const redecls = stmt.lhs.map((e) => !!e._redecl);
|
|
64
|
-
const anyRedecl = redecls.some((r) => r);
|
|
65
|
-
if (lhs.length === 1) {
|
|
66
|
-
const isBoxed = this._boxedVars.has(lhs[0]);
|
|
67
|
-
const val = isBoxed && !redecls[0] ? `{ value: ${rhs[0]} }` : rhs[0];
|
|
68
|
-
if (redecls[0]) {
|
|
69
|
-
if (isBoxed) {
|
|
70
|
-
this.line(`${lhs[0]}.value = ${rhs[0]};`);
|
|
71
|
-
} else {
|
|
72
|
-
this.line(`${lhs[0]} = ${val};`);
|
|
73
|
-
}
|
|
74
|
-
} else {
|
|
75
|
-
this.line(`let ${lhs[0]} = ${val};`);
|
|
76
|
-
}
|
|
77
|
-
} else if (!anyRedecl) {
|
|
78
|
-
// All new: let [a, b] = ...
|
|
79
|
-
const rhsStr = rhs.length === 1 ? rhs[0] : `[${rhs.join(", ")}]`;
|
|
80
|
-
this.line(`let [${lhs.join(", ")}] = ${rhsStr};`);
|
|
81
|
-
} else {
|
|
82
|
-
// Mixed new/existing: emit separate statements
|
|
83
|
-
// First compute the tuple into a temp if needed
|
|
84
|
-
if (rhs.length === 1 && lhs.length > 1) {
|
|
85
|
-
const tmp = "__t";
|
|
86
|
-
this.line(`let ${tmp} = ${rhs[0]};`);
|
|
87
|
-
for (let i = 0; i < lhs.length; i++) {
|
|
88
|
-
const prefix = redecls[i] ? "" : "let ";
|
|
89
|
-
this.line(`${prefix}${lhs[i]} = ${tmp}[${i}];`);
|
|
90
|
-
}
|
|
91
|
-
} else {
|
|
92
|
-
for (let i = 0; i < lhs.length; i++) {
|
|
93
|
-
const prefix = redecls[i] ? "" : "let ";
|
|
94
|
-
this.line(`${prefix}${lhs[i]} = ${rhs[i]};`);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
break;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
case "AssignStmt": {
|
|
102
|
-
// Complex compound assignment: z += w → z = z + w (with complex codegen)
|
|
103
|
-
if (
|
|
104
|
-
stmt.op !== "=" &&
|
|
105
|
-
stmt.lhs.length === 1 &&
|
|
106
|
-
stmt.rhs.length === 1 &&
|
|
107
|
-
isComplex(stmt.lhs[0]._type)
|
|
108
|
-
) {
|
|
109
|
-
const lhsStr = stmt.lhs[0].name ?? this.genExpr(stmt.lhs[0]);
|
|
110
|
-
const rhsExpr = this._genComplexOperand(stmt.rhs[0]);
|
|
111
|
-
const baseOp = stmt.op.slice(0, -1); // "+=" → "+"
|
|
112
|
-
let result;
|
|
113
|
-
switch (baseOp) {
|
|
114
|
-
case "+":
|
|
115
|
-
result = `{ re: ${lhsStr}.re + ${rhsExpr}.re, im: ${lhsStr}.im + ${rhsExpr}.im }`;
|
|
116
|
-
break;
|
|
117
|
-
case "-":
|
|
118
|
-
result = `{ re: ${lhsStr}.re - ${rhsExpr}.re, im: ${lhsStr}.im - ${rhsExpr}.im }`;
|
|
119
|
-
break;
|
|
120
|
-
case "*":
|
|
121
|
-
this._usesCmul = true;
|
|
122
|
-
result = `__cmul(${lhsStr}, ${rhsExpr})`;
|
|
123
|
-
break;
|
|
124
|
-
case "/":
|
|
125
|
-
this._usesCdiv = true;
|
|
126
|
-
result = `__cdiv(${lhsStr}, ${rhsExpr})`;
|
|
127
|
-
break;
|
|
128
|
-
default:
|
|
129
|
-
result = rhsExpr;
|
|
130
|
-
}
|
|
131
|
-
this.line(`${lhsStr} = ${result};`);
|
|
132
|
-
break;
|
|
133
|
-
}
|
|
134
|
-
// comma-ok map index: v, ok = m["key"]
|
|
135
|
-
if (stmt._commaOkMap) {
|
|
136
|
-
this._genCommaOkMapIndex(stmt, stmt.rhs[0], false);
|
|
137
|
-
break;
|
|
138
|
-
}
|
|
139
|
-
// comma-ok type assertion: v, ok = x.(T)
|
|
140
|
-
if (
|
|
141
|
-
stmt.lhs.length === 2 &&
|
|
142
|
-
stmt.rhs.length === 1 &&
|
|
143
|
-
stmt.rhs[0]._commaOk
|
|
144
|
-
) {
|
|
145
|
-
const val = this.genExpr(stmt.rhs[0]);
|
|
146
|
-
const [vName, okName] = stmt.lhs.map(
|
|
147
|
-
(e) => e.name ?? this.genExpr(e),
|
|
148
|
-
);
|
|
149
|
-
const tmp = "__ta";
|
|
150
|
-
this.line(`let ${tmp} = ${val};`);
|
|
151
|
-
if (vName !== "_") this.line(`${vName} = ${tmp}[0];`);
|
|
152
|
-
if (okName !== "_") this.line(`${okName} = ${tmp}[1];`);
|
|
153
|
-
break;
|
|
154
|
-
}
|
|
155
|
-
const rhs = stmt.rhs.map((e) => this.genExpr(e));
|
|
156
|
-
for (const e of stmt.lhs) if (e.kind === "IndexExpr") e._lvalue = true;
|
|
157
|
-
const lhs = stmt.lhs.map((e) => {
|
|
158
|
-
if (
|
|
159
|
-
e.kind === "Ident" &&
|
|
160
|
-
e.name !== "_" &&
|
|
161
|
-
this._boxedVars.has(e.name)
|
|
162
|
-
)
|
|
163
|
-
return `${e.name}.value`;
|
|
164
|
-
return e.name ?? this.genExpr(e);
|
|
165
|
-
});
|
|
166
|
-
// Filter out blank identifier assignments entirely
|
|
167
|
-
const pairs = lhs.map((l, i) => ({ l, r: rhs[i] ?? rhs[0] }));
|
|
168
|
-
const active = pairs.filter((p) => p.l !== "_");
|
|
169
|
-
if (active.length === 0) {
|
|
170
|
-
// All _ — still need to evaluate rhs for side effects
|
|
171
|
-
if (rhs.length > 0) this.line(`${rhs[0]};`);
|
|
172
|
-
} else if (lhs.length === 1) {
|
|
173
|
-
this.line(`${active[0].l} ${stmt.op} ${active[0].r};`);
|
|
174
|
-
} else {
|
|
175
|
-
// multi-assign: emit only non-blank targets
|
|
176
|
-
const rhsStr = rhs.length === 1 ? rhs[0] : `[${rhs.join(", ")}]`;
|
|
177
|
-
if (active.length === pairs.length) {
|
|
178
|
-
this.line(`[${lhs.join(", ")}] = ${rhsStr};`);
|
|
179
|
-
} else {
|
|
180
|
-
// Some blanks — use temp and assign only non-blank
|
|
181
|
-
const tmp = "__t";
|
|
182
|
-
this.line(`let ${tmp} = ${rhsStr};`);
|
|
183
|
-
for (let i = 0; i < lhs.length; i++) {
|
|
184
|
-
if (lhs[i] !== "_") {
|
|
185
|
-
const src = rhs.length === 1 ? `${tmp}[${i}]` : rhs[i];
|
|
186
|
-
this.line(`${lhs[i]} ${stmt.op} ${src};`);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
break;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
58
|
case "IncDecStmt":
|
|
195
59
|
this.line(`${this.genExpr(stmt.expr)}${stmt.op};`);
|
|
196
60
|
break;
|
|
197
|
-
|
|
198
61
|
case "ExprStmt":
|
|
199
62
|
this.line(`${this.genExpr(stmt.expr)};`);
|
|
200
63
|
break;
|
|
201
|
-
|
|
202
|
-
case "ReturnStmt": {
|
|
203
|
-
if (this._inIteratorBody) {
|
|
204
|
-
if (stmt.values.length === 0 && !this.namedReturnVars?.length) {
|
|
205
|
-
this.line(`${this._iterReturnFlag} = true; return false;`);
|
|
206
|
-
} else {
|
|
207
|
-
const vals =
|
|
208
|
-
stmt.values.length > 0
|
|
209
|
-
? stmt.values.map((v) => this.genExpr(v))
|
|
210
|
-
: (this.namedReturnVars ?? []);
|
|
211
|
-
const stored = vals.length === 1 ? vals[0] : `[${vals.join(", ")}]`;
|
|
212
|
-
this.line(
|
|
213
|
-
`${this._iterReturnFlag} = true; ${this._iterReturnVar} = ${stored}; return false;`,
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
break;
|
|
217
|
-
}
|
|
218
|
-
if (stmt.values.length === 0) {
|
|
219
|
-
// bare return — emit named return vars if present
|
|
220
|
-
if (this.namedReturnVars?.length > 0) {
|
|
221
|
-
const vars = this.namedReturnVars;
|
|
222
|
-
this.line(
|
|
223
|
-
vars.length === 1
|
|
224
|
-
? `return ${vars[0]};`
|
|
225
|
-
: `return [${vars.join(", ")}];`,
|
|
226
|
-
);
|
|
227
|
-
} else {
|
|
228
|
-
this.line("return;");
|
|
229
|
-
}
|
|
230
|
-
} else if (stmt.values.length === 1) {
|
|
231
|
-
this.line(`return ${this.genExpr(stmt.values[0])};`);
|
|
232
|
-
} else {
|
|
233
|
-
this.line(
|
|
234
|
-
`return [${stmt.values.map((v) => this.genExpr(v)).join(", ")}];`,
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
break;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
case "IfStmt": {
|
|
64
|
+
case "IfStmt":
|
|
241
65
|
if (stmt.init) {
|
|
242
|
-
// Wrap in a block so init variable is scoped
|
|
243
66
|
this.line("{");
|
|
244
67
|
this.indented(() => {
|
|
245
68
|
this.genStmt(stmt.init);
|
|
@@ -250,55 +73,26 @@ export const statementGenMethods = {
|
|
|
250
73
|
this._genIf(stmt);
|
|
251
74
|
}
|
|
252
75
|
break;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
case "ForStmt":
|
|
256
|
-
this.genFor(stmt);
|
|
257
|
-
break;
|
|
258
|
-
|
|
259
|
-
case "SwitchStmt":
|
|
260
|
-
this.genSwitch(stmt);
|
|
261
|
-
break;
|
|
262
|
-
|
|
263
|
-
case "TypeSwitchStmt":
|
|
264
|
-
this.genTypeSwitch(stmt);
|
|
265
|
-
break;
|
|
266
|
-
|
|
267
76
|
case "DeferStmt":
|
|
268
77
|
this.line(`__defers.push(() => { ${this.genExpr(stmt.call)}; });`);
|
|
269
78
|
break;
|
|
270
|
-
|
|
271
79
|
case "LabeledStmt":
|
|
272
80
|
this.line(`${stmt.label}:`);
|
|
273
81
|
this.genStmt(stmt.body);
|
|
274
82
|
break;
|
|
275
|
-
|
|
276
|
-
case "BranchStmt":
|
|
277
|
-
if (this._inIteratorBody && !stmt.label) {
|
|
278
|
-
if (stmt.keyword === "break") {
|
|
279
|
-
this.line(`${this._iterBreakFlag} = true; return false;`);
|
|
280
|
-
break;
|
|
281
|
-
}
|
|
282
|
-
if (stmt.keyword === "continue") {
|
|
283
|
-
this.line("return true;");
|
|
284
|
-
break;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
if (stmt.keyword !== "fallthrough")
|
|
288
|
-
this.line(
|
|
289
|
-
stmt.label ? `${stmt.keyword} ${stmt.label};` : `${stmt.keyword};`,
|
|
290
|
-
);
|
|
291
|
-
// fallthrough: omit — JS switch falls through naturally without a break
|
|
292
|
-
break;
|
|
293
|
-
|
|
294
83
|
case "Block":
|
|
295
84
|
this.line("{");
|
|
296
85
|
this.indented(() => this.genBlock(stmt));
|
|
297
86
|
this.line("}");
|
|
298
87
|
break;
|
|
299
|
-
|
|
300
|
-
|
|
88
|
+
default: {
|
|
89
|
+
const m = STMT_GEN_DELEGATE[stmt.kind];
|
|
90
|
+
if (m) {
|
|
91
|
+
this[m](stmt);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
301
94
|
throw new Error(`CodeGen: unhandled statement kind '${stmt.kind}'`);
|
|
95
|
+
}
|
|
302
96
|
}
|
|
303
97
|
// Record source mapping: first output line this statement produced
|
|
304
98
|
if (srcLine != null && this.out.length > _line0) {
|
|
@@ -306,6 +100,229 @@ export const statementGenMethods = {
|
|
|
306
100
|
}
|
|
307
101
|
},
|
|
308
102
|
|
|
103
|
+
_isCommaOkMapExpr(stmt, rhsNode) {
|
|
104
|
+
return (
|
|
105
|
+
stmt.rhs.length === 1 &&
|
|
106
|
+
stmt.lhs.length === 2 &&
|
|
107
|
+
rhsNode?.kind === "IndexExpr" &&
|
|
108
|
+
rhsNode.expr?._type?.kind === "map"
|
|
109
|
+
);
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
_genDefineSingleVar(lhs, rhs, redecls) {
|
|
113
|
+
const isBoxed = this._boxedVars.has(lhs[0]);
|
|
114
|
+
const val = isBoxed && !redecls[0] ? `{ value: ${rhs[0]} }` : rhs[0];
|
|
115
|
+
if (redecls[0]) {
|
|
116
|
+
if (isBoxed) this.line(`${lhs[0]}.value = ${rhs[0]};`);
|
|
117
|
+
else this.line(`${lhs[0]} = ${val};`);
|
|
118
|
+
} else {
|
|
119
|
+
this.line(`let ${lhs[0]} = ${val};`);
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
_genDefineMixedRedecl(lhs, rhs, redecls) {
|
|
124
|
+
if (rhs.length === 1 && lhs.length > 1) {
|
|
125
|
+
const tmp = "__t";
|
|
126
|
+
this.line(`let ${tmp} = ${rhs[0]};`);
|
|
127
|
+
for (let i = 0; i < lhs.length; i++) {
|
|
128
|
+
const prefix = redecls[i] ? "" : "let ";
|
|
129
|
+
this.line(`${prefix}${lhs[i]} = ${tmp}[${i}];`);
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
for (let i = 0; i < lhs.length; i++) {
|
|
133
|
+
const prefix = redecls[i] ? "" : "let ";
|
|
134
|
+
this.line(`${prefix}${lhs[i]} = ${rhs[i]};`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
_genDefineStmt(stmt) {
|
|
140
|
+
const rhsNode = stmt.rhs[0];
|
|
141
|
+
if (this._isCommaOkMapExpr(stmt, rhsNode)) {
|
|
142
|
+
this._genCommaOkMapIndex(stmt, rhsNode, true);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const rhs = stmt.rhs.map((e) => this.genExpr(e));
|
|
146
|
+
const lhs = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
|
|
147
|
+
const redecls = stmt.lhs.map((e) => !!e._redecl);
|
|
148
|
+
const anyRedecl = redecls.some((r) => r);
|
|
149
|
+
if (lhs.length === 1) {
|
|
150
|
+
this._genDefineSingleVar(lhs, rhs, redecls);
|
|
151
|
+
} else if (!anyRedecl) {
|
|
152
|
+
const rhsStr = rhs.length === 1 ? rhs[0] : `[${rhs.join(", ")}]`;
|
|
153
|
+
this.line(`let [${lhs.join(", ")}] = ${rhsStr};`);
|
|
154
|
+
} else {
|
|
155
|
+
this._genDefineMixedRedecl(lhs, rhs, redecls);
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
_isComplexCompoundAssign(stmt) {
|
|
160
|
+
return (
|
|
161
|
+
stmt.op !== "=" &&
|
|
162
|
+
stmt.lhs.length === 1 &&
|
|
163
|
+
stmt.rhs.length === 1 &&
|
|
164
|
+
isComplex(stmt.lhs[0]._type)
|
|
165
|
+
);
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
_isCommaOkAssertStmt(stmt) {
|
|
169
|
+
return (
|
|
170
|
+
stmt.lhs.length === 2 && stmt.rhs.length === 1 && stmt.rhs[0]._commaOk
|
|
171
|
+
);
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
_genAssignLhsExpr(e) {
|
|
175
|
+
if (e.kind === "Ident" && e.name !== "_" && this._boxedVars.has(e.name))
|
|
176
|
+
return `${e.name}.value`;
|
|
177
|
+
return e.name ?? this.genExpr(e);
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
_markIndexLvalues(exprs) {
|
|
181
|
+
for (const e of exprs) if (e.kind === "IndexExpr") e._lvalue = true;
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
_genAssignMulti(stmt, lhs, rhs, active, pairs) {
|
|
185
|
+
const rhsStr = rhs.length === 1 ? rhs[0] : `[${rhs.join(", ")}]`;
|
|
186
|
+
if (active.length === pairs.length) {
|
|
187
|
+
this.line(`[${lhs.join(", ")}] = ${rhsStr};`);
|
|
188
|
+
} else {
|
|
189
|
+
this._genMultiAssignWithBlanks(stmt, lhs, rhs, rhsStr);
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
_genAssignStmt(stmt) {
|
|
194
|
+
if (this._isComplexCompoundAssign(stmt)) {
|
|
195
|
+
this._genComplexCompoundAssign(stmt);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (stmt._commaOkMap) {
|
|
199
|
+
this._genCommaOkMapIndex(stmt, stmt.rhs[0], false);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (this._isCommaOkAssertStmt(stmt)) {
|
|
203
|
+
this._genCommaOkTypeAssert(stmt);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const rhs = stmt.rhs.map((e) => this.genExpr(e));
|
|
207
|
+
this._markIndexLvalues(stmt.lhs);
|
|
208
|
+
const lhs = stmt.lhs.map((e) => this._genAssignLhsExpr(e));
|
|
209
|
+
const pairs = lhs.map((l, i) => ({ l, r: rhs[i] ?? rhs[0] }));
|
|
210
|
+
const active = pairs.filter((p) => p.l !== "_");
|
|
211
|
+
if (active.length === 0) {
|
|
212
|
+
if (rhs.length > 0) this.line(`${rhs[0]};`);
|
|
213
|
+
} else if (lhs.length === 1) {
|
|
214
|
+
this.line(`${active[0].l} ${stmt.op} ${active[0].r};`);
|
|
215
|
+
} else {
|
|
216
|
+
this._genAssignMulti(stmt, lhs, rhs, active, pairs);
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
_genCommaOkTypeAssert(stmt) {
|
|
221
|
+
const val = this.genExpr(stmt.rhs[0]);
|
|
222
|
+
const [vName, okName] = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
|
|
223
|
+
const tmp = "__ta";
|
|
224
|
+
this.line(`let ${tmp} = ${val};`);
|
|
225
|
+
if (vName !== "_") this.line(`${vName} = ${tmp}[0];`);
|
|
226
|
+
if (okName !== "_") this.line(`${okName} = ${tmp}[1];`);
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
_genMultiAssignWithBlanks(stmt, lhs, rhs, rhsStr) {
|
|
230
|
+
const tmp = "__t";
|
|
231
|
+
this.line(`let ${tmp} = ${rhsStr};`);
|
|
232
|
+
for (let i = 0; i < lhs.length; i++) {
|
|
233
|
+
if (lhs[i] !== "_") {
|
|
234
|
+
const src = rhs.length === 1 ? `${tmp}[${i}]` : rhs[i];
|
|
235
|
+
this.line(`${lhs[i]} ${stmt.op} ${src};`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
_genComplexCompoundAssign(stmt) {
|
|
241
|
+
const lhsStr = stmt.lhs[0].name ?? this.genExpr(stmt.lhs[0]);
|
|
242
|
+
const rhsExpr = this._genComplexOperand(stmt.rhs[0]);
|
|
243
|
+
const baseOp = stmt.op.slice(0, -1); // "+=" → "+"
|
|
244
|
+
let result;
|
|
245
|
+
switch (baseOp) {
|
|
246
|
+
case "+":
|
|
247
|
+
result = `{ re: ${lhsStr}.re + ${rhsExpr}.re, im: ${lhsStr}.im + ${rhsExpr}.im }`;
|
|
248
|
+
break;
|
|
249
|
+
case "-":
|
|
250
|
+
result = `{ re: ${lhsStr}.re - ${rhsExpr}.re, im: ${lhsStr}.im - ${rhsExpr}.im }`;
|
|
251
|
+
break;
|
|
252
|
+
case "*":
|
|
253
|
+
this._usesCmul = true;
|
|
254
|
+
result = `__cmul(${lhsStr}, ${rhsExpr})`;
|
|
255
|
+
break;
|
|
256
|
+
case "/":
|
|
257
|
+
this._usesCdiv = true;
|
|
258
|
+
result = `__cdiv(${lhsStr}, ${rhsExpr})`;
|
|
259
|
+
break;
|
|
260
|
+
default:
|
|
261
|
+
throw new Error(
|
|
262
|
+
`unsupported complex compound assignment operator: ${stmt.op}`,
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
this.line(`${lhsStr} = ${result};`);
|
|
266
|
+
},
|
|
267
|
+
|
|
268
|
+
_genIteratorReturn(stmt) {
|
|
269
|
+
if (stmt.values.length === 0 && !this.namedReturnVars?.length) {
|
|
270
|
+
this.line(`${this._iterReturnFlag} = true; return false;`);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const vals =
|
|
274
|
+
stmt.values.length > 0
|
|
275
|
+
? stmt.values.map((v) => this.genExpr(v))
|
|
276
|
+
: (this.namedReturnVars ?? []);
|
|
277
|
+
const stored = vals.length === 1 ? vals[0] : `[${vals.join(", ")}]`;
|
|
278
|
+
this.line(
|
|
279
|
+
`${this._iterReturnFlag} = true; ${this._iterReturnVar} = ${stored}; return false;`,
|
|
280
|
+
);
|
|
281
|
+
},
|
|
282
|
+
|
|
283
|
+
_genReturnStmt(stmt) {
|
|
284
|
+
if (this._inIteratorBody) {
|
|
285
|
+
this._genIteratorReturn(stmt);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (stmt.values.length === 0) {
|
|
289
|
+
if (this.namedReturnVars?.length > 0) {
|
|
290
|
+
const vars = this.namedReturnVars;
|
|
291
|
+
this.line(
|
|
292
|
+
vars.length === 1
|
|
293
|
+
? `return ${vars[0]};`
|
|
294
|
+
: `return [${vars.join(", ")}];`,
|
|
295
|
+
);
|
|
296
|
+
} else {
|
|
297
|
+
this.line("return;");
|
|
298
|
+
}
|
|
299
|
+
} else if (stmt.values.length === 1) {
|
|
300
|
+
this.line(`return ${this.genExpr(stmt.values[0])};`);
|
|
301
|
+
} else {
|
|
302
|
+
this.line(
|
|
303
|
+
`return [${stmt.values.map((v) => this.genExpr(v)).join(", ")}];`,
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
_genBranchStmt(stmt) {
|
|
309
|
+
if (this._inIteratorBody && !stmt.label) {
|
|
310
|
+
if (stmt.keyword === "break") {
|
|
311
|
+
this.line(`${this._iterBreakFlag} = true; return false;`);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (stmt.keyword === "continue") {
|
|
315
|
+
this.line("return true;");
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (stmt.keyword !== "fallthrough")
|
|
320
|
+
this.line(
|
|
321
|
+
stmt.label ? `${stmt.keyword} ${stmt.label};` : `${stmt.keyword};`,
|
|
322
|
+
);
|
|
323
|
+
// fallthrough: omit — JS switch falls through naturally without a break
|
|
324
|
+
},
|
|
325
|
+
|
|
309
326
|
_genIf(stmt) {
|
|
310
327
|
this.line(`if (${this.genExpr(stmt.cond)}) {`);
|
|
311
328
|
this.indented(() => this.genBlock(stmt.body));
|
|
@@ -328,43 +345,41 @@ export const statementGenMethods = {
|
|
|
328
345
|
}
|
|
329
346
|
},
|
|
330
347
|
|
|
348
|
+
_genRangeExprFor(stmt) {
|
|
349
|
+
if (stmt.cond._isIterator) {
|
|
350
|
+
this.genIteratorForCond(stmt);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
const iteree = this.genExpr(stmt.cond.expr);
|
|
354
|
+
this.line(`for (let _$ = 0; _$ < ${iteree}; _$++) {`);
|
|
355
|
+
this.indented(() => this.genBlock(stmt.body));
|
|
356
|
+
this.line("}");
|
|
357
|
+
},
|
|
358
|
+
|
|
359
|
+
_genSimpleWhileFor(stmt) {
|
|
360
|
+
if (!stmt.cond) {
|
|
361
|
+
this.line("while (true) {");
|
|
362
|
+
} else {
|
|
363
|
+
this.line(`while (${this.genExpr(stmt.cond)}) {`);
|
|
364
|
+
}
|
|
365
|
+
this.indented(() => this.genBlock(stmt.body));
|
|
366
|
+
this.line("}");
|
|
367
|
+
},
|
|
368
|
+
|
|
331
369
|
genFor(stmt) {
|
|
332
|
-
// Detect range: init is DefineStmt/AssignStmt with rhs[0] being a RangeExpr
|
|
333
370
|
if (this.isRangeFor(stmt)) {
|
|
334
|
-
if (stmt.init.rhs[0]._isIterator)
|
|
335
|
-
|
|
336
|
-
} else {
|
|
337
|
-
this.genRangeFor(stmt);
|
|
338
|
-
}
|
|
371
|
+
if (stmt.init.rhs[0]._isIterator) this.genIteratorFor(stmt);
|
|
372
|
+
else this.genRangeFor(stmt);
|
|
339
373
|
return;
|
|
340
374
|
}
|
|
341
|
-
|
|
342
|
-
// for range expr — no variable; could be int range or 0-param iterator
|
|
343
375
|
if (stmt.cond?.kind === "RangeExpr") {
|
|
344
|
-
|
|
345
|
-
this.genIteratorForCond(stmt);
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
const iteree = this.genExpr(stmt.cond.expr);
|
|
349
|
-
this.line(`for (let _$ = 0; _$ < ${iteree}; _$++) {`);
|
|
350
|
-
this.indented(() => this.genBlock(stmt.body));
|
|
351
|
-
this.line("}");
|
|
376
|
+
this._genRangeExprFor(stmt);
|
|
352
377
|
return;
|
|
353
378
|
}
|
|
354
|
-
|
|
355
379
|
if (!stmt.init && !stmt.post) {
|
|
356
|
-
|
|
357
|
-
// infinite loop
|
|
358
|
-
this.line("while (true) {");
|
|
359
|
-
} else {
|
|
360
|
-
this.line(`while (${this.genExpr(stmt.cond)}) {`);
|
|
361
|
-
}
|
|
362
|
-
this.indented(() => this.genBlock(stmt.body));
|
|
363
|
-
this.line("}");
|
|
380
|
+
this._genSimpleWhileFor(stmt);
|
|
364
381
|
return;
|
|
365
382
|
}
|
|
366
|
-
|
|
367
|
-
// Standard C-style for
|
|
368
383
|
const init = stmt.init ? this.stmtInline(stmt.init) : "";
|
|
369
384
|
const cond = stmt.cond ? this.genExpr(stmt.cond) : "";
|
|
370
385
|
const post = stmt.post ? this.stmtInline(stmt.post) : "";
|
|
@@ -380,6 +395,29 @@ export const statementGenMethods = {
|
|
|
380
395
|
return init.rhs?.[0]?.kind === "RangeExpr";
|
|
381
396
|
},
|
|
382
397
|
|
|
398
|
+
_isIntRangeType(iterType) {
|
|
399
|
+
return (
|
|
400
|
+
(iterType?.kind === "basic" &&
|
|
401
|
+
(iterType.name === "int" || iterType.name === "float64")) ||
|
|
402
|
+
(iterType?.kind === "untyped" &&
|
|
403
|
+
(iterType.base === "int" || iterType.base === "float64"))
|
|
404
|
+
);
|
|
405
|
+
},
|
|
406
|
+
|
|
407
|
+
_isMapRangeType(iterType) {
|
|
408
|
+
return (
|
|
409
|
+
iterType?.kind === "map" ||
|
|
410
|
+
(iterType?.kind === "named" && iterType.underlying?.kind === "map")
|
|
411
|
+
);
|
|
412
|
+
},
|
|
413
|
+
|
|
414
|
+
_isStringRangeType(iterType) {
|
|
415
|
+
return (
|
|
416
|
+
(iterType?.kind === "basic" && iterType.name === "string") ||
|
|
417
|
+
(iterType?.kind === "untyped" && iterType.base === "string")
|
|
418
|
+
);
|
|
419
|
+
},
|
|
420
|
+
|
|
383
421
|
genRangeFor(stmt) {
|
|
384
422
|
const init = stmt.init;
|
|
385
423
|
const range = init.rhs[0];
|
|
@@ -390,62 +428,36 @@ export const statementGenMethods = {
|
|
|
390
428
|
? `${this.genExpr(range.expr)}.${wrapField}`
|
|
391
429
|
: this.genExpr(range.expr);
|
|
392
430
|
|
|
393
|
-
|
|
394
|
-
if (
|
|
395
|
-
lhs.length <= 1 &&
|
|
396
|
-
((iterType?.kind === "basic" &&
|
|
397
|
-
(iterType.name === "int" || iterType.name === "float64")) ||
|
|
398
|
-
(iterType?.kind === "untyped" &&
|
|
399
|
-
(iterType.base === "int" || iterType.base === "float64")))
|
|
400
|
-
) {
|
|
401
|
-
// integer range (Go 1.22): for i := range n → C-style for loop
|
|
431
|
+
if (lhs.length <= 1 && this._isIntRangeType(iterType)) {
|
|
402
432
|
const v = lhs[0] === "_" ? "_$" : lhs[0];
|
|
403
433
|
this.line(`for (let ${v} = 0; ${v} < ${iteree}; ${v}++) {`);
|
|
404
434
|
this.indented(() => this.genBlock(stmt.body));
|
|
405
435
|
this.line("}");
|
|
406
436
|
return;
|
|
407
437
|
}
|
|
408
|
-
|
|
409
|
-
iterType?.kind === "map" ||
|
|
410
|
-
(iterType?.kind === "named" && iterType.underlying?.kind === "map")
|
|
411
|
-
) {
|
|
412
|
-
// map: for k, v := range m → for (const [k, v] of Object.entries(m))
|
|
413
|
-
iterExpr = `Object.entries(${iteree})`;
|
|
414
|
-
} else if (
|
|
415
|
-
(iterType?.kind === "basic" && iterType.name === "string") ||
|
|
416
|
-
(iterType?.kind === "untyped" && iterType.base === "string")
|
|
417
|
-
) {
|
|
418
|
-
// string: for i, r := range s → rune (code point), not JS character
|
|
419
|
-
// Go spec: iterating a string yields (byte-index, rune) pairs
|
|
420
|
-
if (lhs.length === 1) {
|
|
421
|
-
iterExpr = `Array.from(${iteree}).keys()`;
|
|
422
|
-
} else {
|
|
423
|
-
// Wrap code points so the value is an integer (rune), not a JS string
|
|
424
|
-
iterExpr = `Array.from(${iteree}, (__c, __i) => [__i, __c.codePointAt(0)])`;
|
|
425
|
-
}
|
|
426
|
-
} else {
|
|
427
|
-
// slice/array: for i, v := range arr → for (const [i, v] of __s(arr).entries())
|
|
428
|
-
// null-guard handles nil slices (zero value) gracefully.
|
|
429
|
-
this._usesSliceGuard = true;
|
|
430
|
-
if (lhs.length === 1) {
|
|
431
|
-
iterExpr = `__s(${iteree}).keys()`;
|
|
432
|
-
} else {
|
|
433
|
-
iterExpr = `__s(${iteree}).entries()`;
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
|
|
438
|
+
const iterExpr = this._genRangeIterExpr(iterType, iteree, lhs);
|
|
437
439
|
const binding =
|
|
438
440
|
lhs.length === 1
|
|
439
441
|
? lhs[0] === "_"
|
|
440
442
|
? "_$"
|
|
441
443
|
: lhs[0]
|
|
442
444
|
: `[${lhs.map((n) => (n === "_" ? "_$" : n)).join(", ")}]`;
|
|
443
|
-
|
|
444
445
|
this.line(`for (const ${binding} of ${iterExpr}) {`);
|
|
445
446
|
this.indented(() => this.genBlock(stmt.body));
|
|
446
447
|
this.line("}");
|
|
447
448
|
},
|
|
448
449
|
|
|
450
|
+
_genRangeIterExpr(iterType, iteree, lhs) {
|
|
451
|
+
if (this._isMapRangeType(iterType)) return `Object.entries(${iteree})`;
|
|
452
|
+
if (this._isStringRangeType(iterType)) {
|
|
453
|
+
if (lhs.length === 1) return `Array.from(${iteree}).keys()`;
|
|
454
|
+
return `Array.from(${iteree}, (__c, __i) => [__i, __c.codePointAt(0)])`;
|
|
455
|
+
}
|
|
456
|
+
this._usesSliceGuard = true;
|
|
457
|
+
if (lhs.length === 1) return `__s(${iteree}).keys()`;
|
|
458
|
+
return `__s(${iteree}).entries()`;
|
|
459
|
+
},
|
|
460
|
+
|
|
449
461
|
genIteratorFor(stmt) {
|
|
450
462
|
const range = stmt.init.rhs[0];
|
|
451
463
|
const lhs = stmt.init.lhs.map((e) => e.name ?? this.genExpr(e));
|