gofront 0.0.9 → 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.
Files changed (50) hide show
  1. package/README.md +6 -6
  2. package/package.json +7 -4
  3. package/src/codegen/expressions.js +520 -1717
  4. package/src/codegen/index.js +576 -0
  5. package/src/codegen/runtime.js +151 -0
  6. package/src/codegen/statements.js +305 -296
  7. package/src/codegen/stdlib/builder.js +36 -0
  8. package/src/codegen/stdlib/bytes.js +62 -0
  9. package/src/codegen/stdlib/errors.js +17 -0
  10. package/src/codegen/stdlib/fmt.js +101 -0
  11. package/src/codegen/stdlib/gom.js +152 -0
  12. package/src/codegen/stdlib/html.js +17 -0
  13. package/src/codegen/stdlib/index.js +95 -0
  14. package/src/codegen/stdlib/io.js +33 -0
  15. package/src/codegen/stdlib/maps.js +42 -0
  16. package/src/codegen/stdlib/math.js +53 -0
  17. package/src/codegen/stdlib/os.js +10 -0
  18. package/src/codegen/stdlib/path.js +31 -0
  19. package/src/codegen/stdlib/rand.js +24 -0
  20. package/src/codegen/stdlib/regexp.js +51 -0
  21. package/src/codegen/stdlib/slices.js +48 -0
  22. package/src/codegen/stdlib/sort.js +28 -0
  23. package/src/codegen/stdlib/strconv.js +30 -0
  24. package/src/codegen/stdlib/strings.js +83 -0
  25. package/src/codegen/stdlib/time.js +54 -0
  26. package/src/codegen/stdlib/unicode.js +22 -0
  27. package/src/codegen/stdlib/utf8.js +23 -0
  28. package/src/codegen/templ.js +281 -0
  29. package/src/compiler.js +80 -74
  30. package/src/dts-parser.js +355 -371
  31. package/src/index.js +13 -49
  32. package/src/lexer.js +186 -272
  33. package/src/minifier.js +305 -318
  34. package/src/parser/expressions.js +51 -34
  35. package/src/{parser.js → parser/index.js} +4 -4
  36. package/src/parser/statements.js +24 -34
  37. package/src/parser/types.js +31 -29
  38. package/src/templ-parser.js +2 -2
  39. package/src/tokens.js +109 -0
  40. package/src/typechecker/assignability.js +258 -0
  41. package/src/typechecker/expressions.js +559 -549
  42. package/src/typechecker/index.js +555 -0
  43. package/src/typechecker/resolve.js +421 -0
  44. package/src/typechecker/statements.js +267 -280
  45. package/src/typechecker/stdlib/core.js +592 -0
  46. package/src/typechecker/stdlib/extended.js +332 -0
  47. package/src/typechecker/stdlib.js +9 -1060
  48. package/src/typechecker/termination.js +83 -0
  49. package/src/codegen.js +0 -882
  50. package/src/typechecker.js +0 -1299
@@ -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
- default:
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
- this.genIteratorFor(stmt);
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
- if (stmt.cond._isIterator) {
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
- if (!stmt.cond) {
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
- let iterExpr;
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
- if (
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));