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
@@ -0,0 +1,576 @@
1
+ // Code generator: walks the typed AST and emits clean JavaScript.
2
+ //
3
+ // Split into sub-modules under codegen/:
4
+ // source-map.js — VLQ encoder and source map builder
5
+ // statements.js — genBlock, genStmt, genFor, genSwitch, etc.
6
+ // expressions.js — genExpr, genCall, genCompositeLit, helpers
7
+ //
8
+ // Key design choices:
9
+ // - Structs → ES6 classes with a single destructured-object constructor
10
+ // - Methods → class instance methods
11
+ // - Multiple returns → JS arrays e.g. return [a, b]
12
+ // - Destructuring → let [a, b] = f()
13
+ // - nil → null
14
+ // - Slices → JS arrays (append → spread, len → .length)
15
+ // - Maps → plain JS objects (map[string]T)
16
+ // - make([]T, n) → new Array(n).fill(zeroOf(T))
17
+ // - make(map[K]V) → {}
18
+ // - for range → for...of with .entries()
19
+
20
+ import { isComplex, isNumeric } from "../typechecker/types.js";
21
+ import { expressionGenMethods } from "./expressions.js";
22
+ import {
23
+ HELPER_APPEND,
24
+ HELPER_CDIV,
25
+ HELPER_CMUL,
26
+ HELPER_EQUAL,
27
+ HELPER_ERROR,
28
+ HELPER_ERROR_IS,
29
+ HELPER_LEN,
30
+ HELPER_PATH_CLEAN,
31
+ HELPER_S,
32
+ HELPER_SPRINTF,
33
+ HELPER_TIME_FMT,
34
+ HELPER_TIME_PARSE,
35
+ } from "./runtime.js";
36
+ import { buildSourceMap } from "./source-map.js";
37
+ import { statementGenMethods } from "./statements.js";
38
+ import { stdlibGenMethods } from "./stdlib/index.js";
39
+ import { templGenMethods } from "./templ.js";
40
+
41
+ export class CodeGen {
42
+ // jsImports: Map<importPath, string[]> — npm package imports to emit at top of file
43
+ // bundledPackages: Set<string> — GoFront package names bundled inline;
44
+ // SelectorExpr `pkg.Foo` → just `Foo`
45
+ constructor(
46
+ checker = null,
47
+ jsImports = new Map(),
48
+ bundledPackages = new Set(),
49
+ ) {
50
+ this.checker = checker;
51
+ this.out = [];
52
+ this.indent = 0;
53
+ this.structNames = new Set();
54
+ this.namedWrapperNames = new Set();
55
+ this.jsImports = jsImports;
56
+ this.bundledPackages = bundledPackages;
57
+ this.namedReturnVars = null; // names of current function's named return vars
58
+ this._srcMappings = []; // { genLine, srcLine, srcFileIdx } for source map
59
+ this._currentSrcFileIdx = 0; // updated as each top-level decl is generated
60
+ this._boxedVars = new Set(); // address-taken scalar variables that need boxing
61
+ // Runtime helper usage tracking — only emit helpers that are actually used
62
+ this._usesLen = false;
63
+ this._usesAppend = false;
64
+ this._usesSliceGuard = false;
65
+ this._usesSprintf = false;
66
+ this._usesEqual = false;
67
+ this._usesCmul = false;
68
+ this._usesCdiv = false;
69
+ this._usesError = false;
70
+ this._usesErrorIs = false;
71
+ this._usesPathClean = false;
72
+ this._usesTimeFmt = false;
73
+ this._usesTimeParse = false;
74
+ // Iterator (range-over-func) context
75
+ this._inIteratorBody = false;
76
+ this._iterDepth = 0;
77
+ this._iterBreakFlag = null;
78
+ this._iterReturnFlag = null;
79
+ this._iterReturnVar = null;
80
+ }
81
+
82
+ // ── Output helpers ───────────────────────────────────────────
83
+
84
+ emit(s) {
85
+ this.out.push(s);
86
+ }
87
+ line(s = "", srcLine = null) {
88
+ if (srcLine != null) {
89
+ this._srcMappings.push({
90
+ genLine: this.out.length,
91
+ srcLine: srcLine - 1, // 0-based
92
+ srcFileIdx: this._currentSrcFileIdx,
93
+ });
94
+ }
95
+ this.out.push(" ".repeat(this.indent) + s);
96
+ }
97
+ blank() {
98
+ this.out.push("");
99
+ }
100
+
101
+ indented(fn) {
102
+ this.indent++;
103
+ fn();
104
+ this.indent--;
105
+ }
106
+
107
+ _emitJsImports() {
108
+ for (const [importPath, names] of this.jsImports) {
109
+ if (names.length === 0) continue;
110
+ this.line(`import { ${names.join(", ")} } from '${importPath}';`);
111
+ }
112
+ if (this.jsImports.size > 0) this.blank();
113
+ }
114
+
115
+ _emitTypeDecls(program, methods) {
116
+ for (const d of program.decls) {
117
+ if (d.kind === "TypeDecl") {
118
+ this._currentSrcFileIdx = d._srcFileIdx ?? 0;
119
+ this.genTypeDeclWithMethods(d, methods.get(d.name) ?? []);
120
+ this.blank();
121
+ }
122
+ }
123
+ }
124
+
125
+ _emitVarConstDecls(program) {
126
+ for (const d of program.decls) {
127
+ if (d.kind === "VarDecl") {
128
+ this._currentSrcFileIdx = d._srcFileIdx ?? 0;
129
+ this.genVarDecl(d);
130
+ this.blank();
131
+ }
132
+ if (d.kind === "ConstDecl") {
133
+ this._currentSrcFileIdx = d._srcFileIdx ?? 0;
134
+ this.genConstDecl(d);
135
+ this.blank();
136
+ }
137
+ }
138
+ }
139
+
140
+ _callInitAndMain(initNames, program) {
141
+ for (const name of initNames) this.line(`${name}();`);
142
+ if (program.decls.some((d) => d.kind === "FuncDecl" && d.name === "main"))
143
+ this.line("main();");
144
+ }
145
+
146
+ generate(program) {
147
+ const methods = this._collectDecls(program);
148
+ this._emitJsImports();
149
+ this._emitTypeDecls(program, methods);
150
+ this._emitVarConstDecls(program);
151
+ const initNames = this._emitFuncDecls(program);
152
+ this._callInitAndMain(initNames, program);
153
+ this._prependHelpers();
154
+ while (this.out[0] === "") this.out.shift();
155
+ return this.out.join("\n");
156
+ }
157
+
158
+ // Emits FuncDecl and TemplDecl nodes; returns renamed init function names.
159
+ _emitFuncDecls(program) {
160
+ let initCount = 0;
161
+ const initNames = [];
162
+ for (const d of program.decls) {
163
+ if (d.kind === "FuncDecl") {
164
+ this._currentSrcFileIdx = d._srcFileIdx ?? 0;
165
+ if (d.name === "init") {
166
+ const renamed = initCount === 0 ? "init" : `init$${initCount}`;
167
+ initNames.push(renamed);
168
+ this.genFuncDecl(d, renamed);
169
+ initCount++;
170
+ } else {
171
+ this.genFuncDecl(d);
172
+ }
173
+ this.blank();
174
+ } else if (d.kind === "TemplDecl") {
175
+ this._currentSrcFileIdx = d._srcFileIdx ?? 0;
176
+ this.genTemplDecl(d);
177
+ this.blank();
178
+ }
179
+ }
180
+ return initNames;
181
+ }
182
+
183
+ // Collects struct names, method map, and named wrapper names from program decls.
184
+ // Returns the method map (typeName → MethodDecl[]).
185
+ _collectStructNames(program) {
186
+ for (const d of program.decls) {
187
+ if (d.kind === "TypeDecl" && d.type.kind === "StructType")
188
+ this.structNames.add(d.name);
189
+ }
190
+ }
191
+
192
+ _collectMethodMap(program) {
193
+ const methods = new Map();
194
+ for (const d of program.decls) {
195
+ if (d.kind === "MethodDecl") {
196
+ const name = d.recvType.name;
197
+ if (!methods.has(name)) methods.set(name, []);
198
+ methods.get(name).push(d);
199
+ }
200
+ }
201
+ return methods;
202
+ }
203
+
204
+ _collectNamedWrappers(program, methods) {
205
+ for (const d of program.decls) {
206
+ if (
207
+ d.kind === "TypeDecl" &&
208
+ d.type.kind !== "StructType" &&
209
+ d.type.kind !== "InterfaceType" &&
210
+ (methods.get(d.name) ?? []).length > 0
211
+ ) {
212
+ this.namedWrapperNames.add(d.name);
213
+ }
214
+ }
215
+ }
216
+
217
+ _collectDecls(program) {
218
+ this._collectStructNames(program);
219
+ const methods = this._collectMethodMap(program);
220
+ this._collectNamedWrappers(program, methods);
221
+ return methods;
222
+ }
223
+
224
+ _prependHelpers() {
225
+ const HELPER_MAP = [
226
+ [this._usesLen, HELPER_LEN],
227
+ [this._usesAppend, HELPER_APPEND],
228
+ [this._usesSliceGuard, HELPER_S],
229
+ [this._usesEqual, HELPER_EQUAL],
230
+ [this._usesCmul, HELPER_CMUL],
231
+ [this._usesCdiv, HELPER_CDIV],
232
+ [this._usesSprintf, HELPER_SPRINTF],
233
+ [this._usesError, HELPER_ERROR],
234
+ [this._usesErrorIs, HELPER_ERROR_IS],
235
+ [this._usesPathClean, HELPER_PATH_CLEAN],
236
+ [this._usesTimeFmt, HELPER_TIME_FMT],
237
+ [this._usesTimeParse, HELPER_TIME_PARSE],
238
+ ];
239
+ const helpers = HELPER_MAP.filter(([flag]) => flag).map(([, h]) => h);
240
+ if (helpers.length > 0) this.out.unshift(...helpers, "");
241
+ }
242
+
243
+ // Generate a single bundle from multiple programs (same-package multi-file).
244
+ // Annotates each decl with its source file index before merging.
245
+ generateAll(programs) {
246
+ for (let i = 0; i < programs.length; i++) {
247
+ for (const decl of programs[i].decls) {
248
+ decl._srcFileIdx = i;
249
+ }
250
+ }
251
+ const merged = { decls: programs.flatMap((p) => p.decls) };
252
+ return this.generate(merged);
253
+ }
254
+
255
+ // Returns a source map JSON string for the last generate() call.
256
+ // sources: string[] of source filenames (relative to the output file).
257
+ // sourcesContent: string[] of original file contents (embedded for DevTools breakpoints).
258
+ getSourceMap(sources, sourcesContent) {
259
+ const srcArray = Array.isArray(sources) ? sources : [sources];
260
+ return buildSourceMap(srcArray, this._srcMappings, sourcesContent);
261
+ }
262
+
263
+ // ── Type declarations ────────────────────────────────────────
264
+
265
+ genTypeDeclWithMethods(decl, methodDecls) {
266
+ if (decl.type.kind === "StructType") {
267
+ this.genStruct(decl.name, decl.type, methodDecls);
268
+ } else if (decl.type.kind === "InterfaceType") {
269
+ // Interfaces are compile-time only — no JS output needed.
270
+ this.line(`// interface ${decl.name} (compile-time only)`);
271
+ } else if (methodDecls.length > 0) {
272
+ // Named non-struct type with methods — emit an ES6 wrapper class.
273
+ this.genNamedTypeClass(decl.name, methodDecls);
274
+ } else {
275
+ this.line(`// type ${decl.name} = ${this.typeComment(decl.type)}`);
276
+ }
277
+ }
278
+
279
+ genNamedTypeClass(name, methodDecls) {
280
+ const namedType = this.checker?.types.get(name);
281
+ const underlying = namedType?.underlying;
282
+ let field, ctorDefault;
283
+ if (underlying?.kind === "func") {
284
+ field = "_fn";
285
+ ctorDefault = "null";
286
+ } else if (underlying?.kind === "map") {
287
+ field = "_map";
288
+ ctorDefault = "{}";
289
+ } else {
290
+ field = "_items";
291
+ ctorDefault = "[]";
292
+ }
293
+ this.line(`class ${name} {`);
294
+ this.indented(() => {
295
+ this.line(
296
+ `constructor(${field} = ${ctorDefault}) { this.${field} = ${field}; }`,
297
+ );
298
+ for (const m of methodDecls) {
299
+ this.blank();
300
+ this.genMethod(m, field);
301
+ }
302
+ });
303
+ this.line("}");
304
+ }
305
+
306
+ _collectStructFields(name, structTypeAst) {
307
+ const fields = [];
308
+ if (this.checker) {
309
+ const resolved = this.checker.types.get(name)?.underlying;
310
+ if (resolved?.kind === "struct") {
311
+ for (const [fName, fType] of resolved.fields.entries())
312
+ fields.push({ name: fName, zero: this.zeroValueForType(fType) });
313
+ }
314
+ } else {
315
+ for (const f of structTypeAst.fields) {
316
+ if (f.embedded) continue;
317
+ const zero = this.zeroValueForTypeNode(f.type);
318
+ for (const n of f.names) fields.push({ name: n, zero });
319
+ }
320
+ }
321
+ return fields;
322
+ }
323
+
324
+ _genStructConstructor(fields) {
325
+ if (fields.length === 0) {
326
+ this.line("constructor() {}");
327
+ } else {
328
+ const params = fields.map((f) => `${f.name} = ${f.zero}`).join(", ");
329
+ this.line(`constructor({ ${params} } = {}) {`);
330
+ this.indented(() => {
331
+ for (const f of fields) this.line(`this.${f.name} = ${f.name};`);
332
+ });
333
+ this.line("}");
334
+ }
335
+ }
336
+
337
+ genStruct(name, structTypeAst, methodDecls) {
338
+ const fields = this._collectStructFields(name, structTypeAst);
339
+ this.line(`class ${name} {`);
340
+ this.indented(() => {
341
+ this._genStructConstructor(fields);
342
+ for (const m of methodDecls) {
343
+ this.blank();
344
+ this.genMethod(m);
345
+ }
346
+ this._genEmbeddedMethodStubs(name, methodDecls);
347
+ });
348
+ this.line("}");
349
+ }
350
+
351
+ _genSingleEmbedStubs(embed, declared) {
352
+ const embedName = embed.kind === "named" ? embed.name : null;
353
+ if (!embedName) return;
354
+ const embedBase = embed.kind === "named" ? embed.underlying : embed;
355
+ if (embedBase?.kind !== "struct" || !embedBase.methods) return;
356
+ for (const [mName] of embedBase.methods.entries()) {
357
+ if (!declared.has(mName)) {
358
+ this.blank();
359
+ this.line(
360
+ `${mName}(...__a) { return ${embedName}.prototype.${mName}.call(this, ...__a); }`,
361
+ );
362
+ }
363
+ }
364
+ }
365
+
366
+ _genEmbeddedMethodStubs(name, methodDecls) {
367
+ if (!this.checker) return;
368
+ const resolvedType = this.checker.types.get(name)?.underlying;
369
+ if (resolvedType?.kind !== "struct" || !resolvedType._embeds) return;
370
+ const declared = new Set(methodDecls.map((m) => m.name));
371
+ for (const embed of resolvedType._embeds)
372
+ this._genSingleEmbedStubs(embed, declared);
373
+ }
374
+
375
+ genMethod(decl, recvField = null) {
376
+ const params = decl.params.map((p) => p.name).join(", ");
377
+ const asyncPrefix = decl.async ? "async " : "";
378
+ this.line(`${asyncPrefix}${decl.name}(${params}) {`);
379
+ const prevBoxed = this._boxedVars;
380
+ this._boxedVars = new Set();
381
+ this._scanAddressTaken(decl.body);
382
+ const prevUnwrapped = this._unwrappedRecv;
383
+ this.indented(() => {
384
+ if (decl.recvName && decl.recvName !== "_") {
385
+ if (recvField) {
386
+ this.line(`const ${decl.recvName} = this.${recvField};`);
387
+ this._unwrappedRecv = decl.recvName;
388
+ } else {
389
+ this.line(`const ${decl.recvName} = this;`);
390
+ }
391
+ }
392
+ this._withNamedReturns(decl, () => this._genBody(decl.body));
393
+ });
394
+ this._unwrappedRecv = prevUnwrapped;
395
+ this._boxedVars = prevBoxed;
396
+ this.line("}");
397
+ }
398
+
399
+ // Returns the wrapper field name ("_fn", "_items", "_map") if `type` is a named
400
+ // non-struct type emitted as a wrapper class, or null otherwise.
401
+ // Pass `expr` so we can skip unwrapping when the expression is the method receiver
402
+ // (which was already unwrapped to `this.<field>` at the top of the method body).
403
+ _namedWrapperField(type, expr = null) {
404
+ if (type?.kind !== "named") return null;
405
+ if (!this.namedWrapperNames.has(type.name)) return null;
406
+ if (expr?.kind === "Ident" && expr.name === this._unwrappedRecv)
407
+ return null;
408
+ const u = type.underlying;
409
+ if (u?.kind === "func") return "_fn";
410
+ if (u?.kind === "map") return "_map";
411
+ return "_items";
412
+ }
413
+
414
+ // ── Function declarations ────────────────────────────────────
415
+
416
+ genFuncDecl(decl, nameOverride) {
417
+ const name = nameOverride ?? decl.name;
418
+ const params = decl.params
419
+ .map((p, i) =>
420
+ p.variadic && i === decl.params.length - 1 ? `...${p.name}` : p.name,
421
+ )
422
+ .join(", ");
423
+ const asyncPrefix = decl.async ? "async " : "";
424
+ const srcLine = decl._line ?? null;
425
+ this.line(
426
+ `${asyncPrefix}function ${name}(${params}) {`,
427
+ srcLine ? srcLine - 1 : null,
428
+ );
429
+ const prevBoxed = this._boxedVars;
430
+ this._boxedVars = new Set();
431
+ this._scanAddressTaken(decl.body);
432
+ this.indented(() =>
433
+ this._withNamedReturns(decl, () => this._genBody(decl.body)),
434
+ );
435
+ this._boxedVars = prevBoxed;
436
+ this.line("}");
437
+ }
438
+
439
+ _withNamedReturns(decl, fn) {
440
+ const named = decl.returnType?._namedReturns;
441
+ const prev = this.namedReturnVars;
442
+ if (named) {
443
+ // Emit zero-value declarations for named return vars
444
+ for (const { name, type } of named) {
445
+ if (name)
446
+ this.line(`let ${name} = ${this.zeroValueForTypeNode(type)};`);
447
+ }
448
+ this.namedReturnVars = named.map((r) => r.name).filter(Boolean);
449
+ } else {
450
+ this.namedReturnVars = null;
451
+ }
452
+ fn();
453
+ this.namedReturnVars = prev;
454
+ }
455
+
456
+ // Emit a function body, wrapping in try/catch/finally for defer if needed.
457
+ _genBody(body) {
458
+ if (!body._hasDefer) {
459
+ this.genBlock(body);
460
+ return;
461
+ }
462
+ this.line("const __defers = [];");
463
+ this.line("let __panic = null;");
464
+ this.line("try {");
465
+ this.indented(() => this.genBlock(body));
466
+ this.line("} catch (__err) {");
467
+ this.indented(() => this.line("__panic = __err;"));
468
+ this.line("} finally {");
469
+ this.indented(() => {
470
+ this.line(
471
+ "for (let __i = __defers.length - 1; __i >= 0; __i--) __defers[__i]();",
472
+ );
473
+ this.line("if (__panic !== null) throw __panic;");
474
+ });
475
+ this.line("}");
476
+ // If a recover() cleared __panic, execution reaches here.
477
+ // Return named return vars so deferred mutations are visible to the caller.
478
+ if (this.namedReturnVars?.length > 0) {
479
+ const vars = this.namedReturnVars;
480
+ this.line(
481
+ vars.length === 1
482
+ ? `return ${vars[0]};`
483
+ : `return [${vars.join(", ")}];`,
484
+ );
485
+ }
486
+ }
487
+
488
+ // Scan AST node for _addressTaken idents on scalars and populate _boxedVars.
489
+ _scanAddressTaken(node) {
490
+ if (!node || typeof node !== "object") return;
491
+ if (Array.isArray(node)) {
492
+ for (const child of node) this._scanAddressTaken(child);
493
+ return;
494
+ }
495
+ // &x — the operand ident will have _addressTaken set by typechecker
496
+ if (node.kind === "Ident" && node._addressTaken) {
497
+ // Check if the type is a scalar (needs boxing) vs reference type (no boxing)
498
+ const t = node._type;
499
+ if (t && !this._isReferenceType(t)) {
500
+ this._boxedVars.add(node.name);
501
+ }
502
+ }
503
+ // Recurse into FuncLit too — closures may take address of outer vars
504
+ for (const key of Object.keys(node)) {
505
+ if (key.startsWith("_")) continue;
506
+ this._scanAddressTaken(node[key]);
507
+ }
508
+ }
509
+
510
+ _isReferenceType(t) {
511
+ if (!t) return false;
512
+ const base = t.kind === "named" ? t.underlying : t;
513
+ return (
514
+ base?.kind === "struct" ||
515
+ base?.kind === "slice" ||
516
+ base?.kind === "map" ||
517
+ base?.kind === "func" ||
518
+ base?.kind === "interface"
519
+ );
520
+ }
521
+
522
+ // ── Variable / const declarations ────────────────────────────
523
+
524
+ genVarDecl(decl) {
525
+ for (const spec of decl.decls) {
526
+ if (spec.value) {
527
+ const vals = spec.value.map((v) => {
528
+ const js = this.genExpr(v);
529
+ // Wrap numeric values assigned to complex-typed vars
530
+ if (
531
+ spec.type?.name === "complex128" ||
532
+ spec.type?.name === "complex64"
533
+ ) {
534
+ if (
535
+ !isComplex(v._type) &&
536
+ (isNumeric(v._type) || v._type?.kind === "untyped")
537
+ ) {
538
+ return `{ re: ${js}, im: 0 }`;
539
+ }
540
+ }
541
+ return js;
542
+ });
543
+ if (spec.names.length === 1) {
544
+ this.line(`let ${spec.names[0]} = ${vals[0]};`);
545
+ } else {
546
+ // let [a, b] = [v1, v2]
547
+ this.line(`let [${spec.names.join(", ")}] = [${vals.join(", ")}];`);
548
+ }
549
+ } else {
550
+ const zero = spec.type ? this.zeroValueForTypeNode(spec.type) : "null";
551
+ for (const name of spec.names) {
552
+ const val = this._boxedVars.has(name) ? `{ value: ${zero} }` : zero;
553
+ this.line(`let ${name} = ${val};`);
554
+ }
555
+ }
556
+ }
557
+ }
558
+
559
+ genConstDecl(decl) {
560
+ for (const spec of decl.decls) {
561
+ const vals = spec.value.map((v) => this.genExpr(v));
562
+ if (spec.names.length === 1) {
563
+ this.line(`const ${spec.names[0]} = ${vals[0]};`);
564
+ } else {
565
+ this.line(`const [${spec.names.join(", ")}] = [${vals.join(", ")}];`);
566
+ }
567
+ }
568
+ }
569
+
570
+ // ── Statements ───────────────────────────────────────────────
571
+ }
572
+
573
+ Object.assign(CodeGen.prototype, statementGenMethods);
574
+ Object.assign(CodeGen.prototype, expressionGenMethods);
575
+ Object.assign(CodeGen.prototype, stdlibGenMethods);
576
+ Object.assign(CodeGen.prototype, templGenMethods);