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