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.
Files changed (53) hide show
  1. package/README.md +5 -5
  2. package/package.json +7 -4
  3. package/src/cli-core.js +65 -0
  4. package/src/codegen/expressions.js +523 -1717
  5. package/src/codegen/index.js +576 -0
  6. package/src/codegen/runtime.js +151 -0
  7. package/src/codegen/statements.js +308 -296
  8. package/src/codegen/stdlib/builder.js +39 -0
  9. package/src/codegen/stdlib/bytes.js +65 -0
  10. package/src/codegen/stdlib/errors.js +20 -0
  11. package/src/codegen/stdlib/fmt.js +104 -0
  12. package/src/codegen/stdlib/gom.js +155 -0
  13. package/src/codegen/stdlib/html.js +20 -0
  14. package/src/codegen/stdlib/index.js +98 -0
  15. package/src/codegen/stdlib/io.js +36 -0
  16. package/src/codegen/stdlib/maps.js +45 -0
  17. package/src/codegen/stdlib/math.js +56 -0
  18. package/src/codegen/stdlib/os.js +13 -0
  19. package/src/codegen/stdlib/path.js +34 -0
  20. package/src/codegen/stdlib/rand.js +27 -0
  21. package/src/codegen/stdlib/regexp.js +54 -0
  22. package/src/codegen/stdlib/slices.js +51 -0
  23. package/src/codegen/stdlib/sort.js +31 -0
  24. package/src/codegen/stdlib/strconv.js +33 -0
  25. package/src/codegen/stdlib/strings.js +86 -0
  26. package/src/codegen/stdlib/time.js +57 -0
  27. package/src/codegen/stdlib/unicode.js +25 -0
  28. package/src/codegen/stdlib/utf8.js +26 -0
  29. package/src/codegen/templ.js +284 -0
  30. package/src/compiler.js +142 -77
  31. package/src/dts-parser.js +355 -371
  32. package/src/index.js +32 -162
  33. package/src/lexer.js +186 -272
  34. package/src/minifier.js +305 -318
  35. package/src/{parser.js → parser/declarations.js} +18 -175
  36. package/src/parser/expressions.js +51 -34
  37. package/src/parser/index.js +161 -0
  38. package/src/parser/statements.js +24 -34
  39. package/src/parser/types.js +31 -29
  40. package/src/templ-parser.js +2 -2
  41. package/src/tokens.js +109 -0
  42. package/src/typechecker/assignability.js +261 -0
  43. package/src/typechecker/expressions.js +568 -549
  44. package/src/typechecker/index.js +556 -0
  45. package/src/typechecker/resolve.js +428 -0
  46. package/src/typechecker/statements.js +270 -280
  47. package/src/typechecker/stdlib/core.js +592 -0
  48. package/src/typechecker/stdlib/extended.js +332 -0
  49. package/src/typechecker/stdlib.js +9 -1060
  50. package/src/typechecker/termination.js +86 -0
  51. package/src/typechecker/types.js +1 -0
  52. package/src/codegen.js +0 -882
  53. 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
- default:
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
- this.genIteratorFor(stmt);
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
- 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("}");
376
+ this._genRangeExprFor(stmt);
352
377
  return;
353
378
  }
354
-
355
379
  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("}");
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
- 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
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
- 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
-
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));