gofront 0.0.1 → 0.0.3

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/src/codegen.js CHANGED
@@ -1,5 +1,10 @@
1
1
  // Code generator: walks the typed AST and emits clean JavaScript.
2
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
+ //
3
8
  // Key design choices:
4
9
  // - Structs → ES6 classes with a single destructured-object constructor
5
10
  // - Methods → class instance methods
@@ -12,48 +17,9 @@
12
17
  // - make(map[K]V) → {}
13
18
  // - for range → for...of with .entries()
14
19
 
15
- // ── Minimal VLQ / source-map helpers ─────────────────────────
16
- const B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
17
-
18
- function vlqEncode(value) {
19
- // Signed VLQ: sign bit in LSB of first group, then 5-bit continuation chunks
20
- let vlq = value < 0 ? (-value << 1) | 1 : value << 1;
21
- let result = "";
22
- do {
23
- let digit = vlq & 0x1f;
24
- vlq >>>= 5;
25
- if (vlq > 0) digit |= 0x20; // continuation bit
26
- result += B64[digit];
27
- } while (vlq > 0);
28
- return result;
29
- }
30
-
31
- function buildSourceMap(sourceName, mappings) {
32
- // mappings: Array<{ genLine: number, srcLine: number }> (0-based)
33
- // Emits one segment per generated line, column 0 → source line (delta-encoded).
34
- const lines = [];
35
- let prevSrcLine = 0;
36
- const maxGen = mappings.reduce((m, e) => Math.max(m, e.genLine), -1);
37
- for (let g = 0; g <= maxGen; g++) {
38
- const entry = mappings.find((e) => e.genLine === g);
39
- if (entry) {
40
- const srcDelta = entry.srcLine - prevSrcLine;
41
- // Segment: [genCol=0, srcFileIdx=0, srcLineDelta, srcCol=0]
42
- lines.push(
43
- vlqEncode(0) + vlqEncode(0) + vlqEncode(srcDelta) + vlqEncode(0),
44
- );
45
- prevSrcLine = entry.srcLine;
46
- } else {
47
- lines.push(""); // no mapping for this generated line
48
- }
49
- }
50
- return JSON.stringify({
51
- version: 3,
52
- sources: [sourceName],
53
- names: [],
54
- mappings: lines.join(";"),
55
- });
56
- }
20
+ import { expressionGenMethods } from "./codegen/expressions.js";
21
+ import { buildSourceMap } from "./codegen/source-map.js";
22
+ import { statementGenMethods } from "./codegen/statements.js";
57
23
 
58
24
  export class CodeGen {
59
25
  // jsImports: Map<importPath, string[]> — npm package imports to emit at top of file
@@ -185,7 +151,28 @@ export class CodeGen {
185
151
  helpers.push("function __s(a) { return a || []; }");
186
152
  if (this._usesSprintf)
187
153
  helpers.push(
188
- "function __sprintf(f,...a){let i=0;return f.replace(/%[sdvf%]/g,m=>{if(m==='%%')return'%';const v=a[i++];return m==='%f'?Number(v).toString():String(v==null?'<nil>':v);});}",
154
+ [
155
+ "function __sprintf(f,...a){let i=0;return f.replace(/%([#+\\- 0]*)([0-9]*)\\.?([0-9]*)[sdvftxXqobeEgG%]/g,(m)=>{",
156
+ "if(m==='%%')return'%';const fl=m.slice(1,-1),verb=m.slice(-1),v=a[i++];",
157
+ "const pad=(s,w,z)=>{w=parseInt(w)||0;if(!w)return s;const p=(z?'0':' ').repeat(Math.max(0,w-s.length));return fl.includes('-')?s+p:p+s;};",
158
+ "const [,flags,width,prec]=m.match(/^%([#+\\- 0]*)([0-9]*)\\.?([0-9]*)/)||[];",
159
+ "const zero=flags?.includes('0')&&!flags?.includes('-');",
160
+ "switch(verb){",
161
+ "case's':return pad(String(v==null?'<nil>':v),width,false);",
162
+ "case'd':return pad(String(Math.trunc(Number(v))),width,zero);",
163
+ "case'v':return pad(String(v==null?'<nil>':v),width,false);",
164
+ "case'f':{const n=Number(v),p=prec!==''?parseInt(prec):6;return pad(n.toFixed(p),width,zero);}",
165
+ "case't':return pad(String(!!v),width,false);",
166
+ "case'x':return pad((Number(v)>>>0).toString(16),width,zero);",
167
+ "case'X':return pad((Number(v)>>>0).toString(16).toUpperCase(),width,zero);",
168
+ "case'o':return pad((Number(v)>>>0).toString(8),width,zero);",
169
+ "case'b':return pad((Number(v)>>>0).toString(2),width,zero);",
170
+ "case'q':return pad('\"'+String(v==null?'':v).replace(/\\\\/g,'\\\\\\\\').replace(/\"/g,'\\\\\"')+'\"',width,false);",
171
+ "case'e':case'E':{const n=Number(v),p=prec!==''?parseInt(prec):6;return pad(n.toExponential(p),width,zero);}",
172
+ "case'g':case'G':{const n=Number(v);return pad(prec!==''?n.toPrecision(parseInt(prec)):String(n),width,zero);}",
173
+ "default:return m;}});",
174
+ "}",
175
+ ].join(""),
189
176
  );
190
177
 
191
178
  if (helpers.length > 0) this.out.unshift(...helpers, "");
@@ -210,10 +197,6 @@ export class CodeGen {
210
197
 
211
198
  // ── Type declarations ────────────────────────────────────────
212
199
 
213
- genTypeDecl(decl) {
214
- this.genTypeDeclWithMethods(decl, []);
215
- }
216
-
217
200
  genTypeDeclWithMethods(decl, methodDecls) {
218
201
  if (decl.type.kind === "StructType") {
219
202
  this.genStruct(decl.name, decl.type, methodDecls);
@@ -339,43 +322,36 @@ export class CodeGen {
339
322
  this.namedReturnVars = prev;
340
323
  }
341
324
 
342
- // Emit a function body, wrapping in try/finally for defer if needed.
325
+ // Emit a function body, wrapping in try/catch/finally for defer if needed.
343
326
  _genBody(body) {
344
- if (!this._hasDefer(body)) {
345
- this.genBlock(body, true);
327
+ if (!body._hasDefer) {
328
+ this.genBlock(body);
346
329
  return;
347
330
  }
348
331
  this.line("const __defers = [];");
332
+ this.line("let __panic = null;");
349
333
  this.line("try {");
350
- this.indented(() => this.genBlock(body, true));
334
+ this.indented(() => this.genBlock(body));
335
+ this.line("} catch (__err) {");
336
+ this.indented(() => this.line("__panic = __err;"));
351
337
  this.line("} finally {");
352
338
  this.indented(() => {
353
339
  this.line(
354
340
  "for (let __i = __defers.length - 1; __i >= 0; __i--) __defers[__i]();",
355
341
  );
342
+ this.line("if (__panic !== null) throw __panic;");
356
343
  });
357
344
  this.line("}");
358
- }
359
-
360
- // Recursively check if a block (or nested blocks) contains any DeferStmt.
361
- _hasDefer(block) {
362
- if (!block?.stmts) return false;
363
- for (const stmt of block.stmts) {
364
- if (stmt.kind === "DeferStmt") return true;
365
- if (stmt.kind === "Block" && this._hasDefer(stmt)) return true;
366
- if (
367
- stmt.kind === "IfStmt" &&
368
- (this._hasDefer(stmt.body) || this._hasDefer(stmt.elseBody))
369
- )
370
- return true;
371
- if (stmt.kind === "ForStmt" && this._hasDefer(stmt.body)) return true;
372
- if (
373
- stmt.kind === "SwitchStmt" &&
374
- stmt.cases.some((c) => c.stmts.some((s) => s.kind === "DeferStmt"))
375
- )
376
- return true;
345
+ // If a recover() cleared __panic, execution reaches here.
346
+ // Return named return vars so deferred mutations are visible to the caller.
347
+ if (this.namedReturnVars?.length > 0) {
348
+ const vars = this.namedReturnVars;
349
+ this.line(
350
+ vars.length === 1
351
+ ? `return ${vars[0]};`
352
+ : `return [${vars.join(", ")}];`,
353
+ );
377
354
  }
378
- return false;
379
355
  }
380
356
 
381
357
  // ── Variable / const declarations ────────────────────────────
@@ -409,784 +385,7 @@ export class CodeGen {
409
385
  }
410
386
 
411
387
  // ── Statements ───────────────────────────────────────────────
412
-
413
- genBlock(block, _skipBraces = false) {
414
- for (const stmt of block.stmts) this.genStmt(stmt);
415
- }
416
-
417
- genStmt(stmt) {
418
- // Record source mapping for the first line this statement produces
419
- const srcLine = stmt._line ?? null;
420
- const _line0 = this.out.length;
421
- switch (stmt.kind) {
422
- case "VarDecl":
423
- this.genVarDecl(stmt);
424
- break;
425
- case "ConstDecl":
426
- this.genConstDecl(stmt);
427
- break;
428
- case "TypeDecl":
429
- this.genTypeDecl(stmt);
430
- break;
431
-
432
- case "DefineStmt": {
433
- // Map comma-ok: v, ok := m[key] → let v = m[key]; let ok = key in m;
434
- const rhsNode = stmt.rhs[0];
435
- if (
436
- stmt.rhs.length === 1 &&
437
- stmt.lhs.length === 2 &&
438
- rhsNode?.kind === "IndexExpr" &&
439
- rhsNode.expr?._type?.kind === "map"
440
- ) {
441
- const mapExpr = this.genExpr(rhsNode.expr);
442
- const keyExpr = this.genExpr(rhsNode.index);
443
- const [vName, okName] = stmt.lhs.map(
444
- (e) => e.name ?? this.genExpr(e),
445
- );
446
- if (vName !== "_")
447
- this.line(`let ${vName} = ${mapExpr}[${keyExpr}];`);
448
- if (okName !== "_")
449
- this.line(`let ${okName} = (${keyExpr}) in ${mapExpr};`);
450
- break;
451
- }
452
- const rhs = stmt.rhs.map((e) => this.genExpr(e));
453
- const lhs = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
454
- const redecls = stmt.lhs.map((e) => !!e._redecl);
455
- const anyRedecl = redecls.some((r) => r);
456
- if (lhs.length === 1) {
457
- if (redecls[0]) {
458
- this.line(`${lhs[0]} = ${rhs[0]};`);
459
- } else {
460
- this.line(`let ${lhs[0]} = ${rhs[0]};`);
461
- }
462
- } else if (!anyRedecl) {
463
- // All new: let [a, b] = ...
464
- const rhsStr = rhs.length === 1 ? rhs[0] : `[${rhs.join(", ")}]`;
465
- this.line(`let [${lhs.join(", ")}] = ${rhsStr};`);
466
- } else {
467
- // Mixed new/existing: emit separate statements
468
- // First compute the tuple into a temp if needed
469
- if (rhs.length === 1 && lhs.length > 1) {
470
- const tmp = "__t";
471
- this.line(`let ${tmp} = ${rhs[0]};`);
472
- for (let i = 0; i < lhs.length; i++) {
473
- const prefix = redecls[i] ? "" : "let ";
474
- this.line(`${prefix}${lhs[i]} = ${tmp}[${i}];`);
475
- }
476
- } else {
477
- for (let i = 0; i < lhs.length; i++) {
478
- const prefix = redecls[i] ? "" : "let ";
479
- this.line(`${prefix}${lhs[i]} = ${rhs[i]};`);
480
- }
481
- }
482
- }
483
- break;
484
- }
485
-
486
- case "AssignStmt": {
487
- const rhs = stmt.rhs.map((e) => this.genExpr(e));
488
- for (const e of stmt.lhs) if (e.kind === "IndexExpr") e._lvalue = true;
489
- const lhs = stmt.lhs.map((e) => this.genExpr(e));
490
- if (lhs.length === 1) {
491
- this.line(`${lhs[0]} ${stmt.op} ${rhs[0]};`);
492
- } else {
493
- const rhsStr = rhs.length === 1 ? rhs[0] : `[${rhs.join(", ")}]`;
494
- this.line(`[${lhs.join(", ")}] = ${rhsStr};`);
495
- }
496
- break;
497
- }
498
-
499
- case "IncDecStmt":
500
- this.line(`${this.genExpr(stmt.expr)}${stmt.op};`);
501
- break;
502
-
503
- case "ExprStmt":
504
- this.line(`${this.genExpr(stmt.expr)};`);
505
- break;
506
-
507
- case "ReturnStmt": {
508
- if (stmt.values.length === 0) {
509
- // bare return — emit named return vars if present
510
- if (this.namedReturnVars?.length > 0) {
511
- const vars = this.namedReturnVars;
512
- this.line(
513
- vars.length === 1
514
- ? `return ${vars[0]};`
515
- : `return [${vars.join(", ")}];`,
516
- );
517
- } else {
518
- this.line("return;");
519
- }
520
- } else if (stmt.values.length === 1) {
521
- this.line(`return ${this.genExpr(stmt.values[0])};`);
522
- } else {
523
- this.line(
524
- `return [${stmt.values.map((v) => this.genExpr(v)).join(", ")}];`,
525
- );
526
- }
527
- break;
528
- }
529
-
530
- case "IfStmt": {
531
- if (stmt.init) {
532
- // Wrap in a block so init variable is scoped
533
- this.line("{");
534
- this.indented(() => {
535
- this.genStmt(stmt.init);
536
- this._genIf(stmt);
537
- });
538
- this.line("}");
539
- } else {
540
- this._genIf(stmt);
541
- }
542
- break;
543
- }
544
-
545
- case "ForStmt":
546
- this.genFor(stmt);
547
- break;
548
-
549
- case "SwitchStmt":
550
- this.genSwitch(stmt);
551
- break;
552
-
553
- case "DeferStmt":
554
- this.line(`__defers.push(() => { ${this.genExpr(stmt.call)}; });`);
555
- break;
556
-
557
- case "BranchStmt":
558
- if (stmt.keyword !== "fallthrough") this.line(`${stmt.keyword};`);
559
- // fallthrough: omit — JS switch falls through naturally without a break
560
- break;
561
-
562
- case "Block":
563
- this.line("{");
564
- this.indented(() => this.genBlock(stmt));
565
- this.line("}");
566
- break;
567
-
568
- default:
569
- break;
570
- }
571
- // Record source mapping: first output line this statement produced
572
- if (srcLine != null && this.out.length > _line0) {
573
- this._srcMappings.push({ genLine: _line0, srcLine: srcLine - 1 });
574
- }
575
- }
576
-
577
- _genIf(stmt) {
578
- this.line(`if (${this.genExpr(stmt.cond)}) {`);
579
- this.indented(() => this.genBlock(stmt.body));
580
- this._genElse(stmt.elseBody);
581
- }
582
-
583
- _genElse(elseBody) {
584
- if (!elseBody) {
585
- this.line("}");
586
- return;
587
- }
588
- if (elseBody.kind === "IfStmt") {
589
- this.line(`} else if (${this.genExpr(elseBody.cond)}) {`);
590
- this.indented(() => this.genBlock(elseBody.body));
591
- this._genElse(elseBody.elseBody);
592
- } else {
593
- this.line("} else {");
594
- this.indented(() => this.genBlock(elseBody));
595
- this.line("}");
596
- }
597
- }
598
-
599
- genFor(stmt) {
600
- // Detect range: init is DefineStmt with rhs[0] being a RangeExpr
601
- if (this.isRangeFor(stmt)) {
602
- this.genRangeFor(stmt);
603
- return;
604
- }
605
-
606
- if (!stmt.init && !stmt.post) {
607
- if (!stmt.cond) {
608
- // infinite loop
609
- this.line("while (true) {");
610
- } else {
611
- this.line(`while (${this.genExpr(stmt.cond)}) {`);
612
- }
613
- this.indented(() => this.genBlock(stmt.body));
614
- this.line("}");
615
- return;
616
- }
617
-
618
- // Standard C-style for
619
- const init = stmt.init ? this.stmtInline(stmt.init) : "";
620
- const cond = stmt.cond ? this.genExpr(stmt.cond) : "";
621
- const post = stmt.post ? this.stmtInline(stmt.post) : "";
622
- this.line(`for (${init}; ${cond}; ${post}) {`);
623
- this.indented(() => this.genBlock(stmt.body));
624
- this.line("}");
625
- }
626
-
627
- isRangeFor(stmt) {
628
- if (!stmt.init) return false;
629
- const init = stmt.init;
630
- if (init.kind !== "DefineStmt" && init.kind !== "AssignStmt") return false;
631
- return init.rhs?.[0]?.kind === "RangeExpr";
632
- }
633
-
634
- genRangeFor(stmt) {
635
- const init = stmt.init;
636
- const range = init.rhs[0];
637
- const lhs = init.lhs.map((e) => e.name ?? this.genExpr(e));
638
- const iteree = this.genExpr(range.expr);
639
- const iterType = range.expr._type;
640
-
641
- let iterExpr;
642
- if (
643
- iterType?.kind === "map" ||
644
- (iterType?.kind === "named" && iterType.underlying?.kind === "map")
645
- ) {
646
- // map: for k, v := range m → for (const [k, v] of Object.entries(m))
647
- iterExpr = `Object.entries(${iteree})`;
648
- } else if (iterType?.kind === "basic" && iterType.name === "string") {
649
- // string: for i, ch := range s → for (const [i, ch] of s.split('').entries())
650
- iterExpr =
651
- lhs.length === 1
652
- ? `Array.from(${iteree}).keys()`
653
- : `Array.from(${iteree}).entries()`;
654
- } else {
655
- // slice/array: for i, v := range arr → for (const [i, v] of __s(arr).entries())
656
- // null-guard handles nil slices (zero value) gracefully.
657
- this._usesSliceGuard = true;
658
- if (lhs.length === 1) {
659
- iterExpr = `__s(${iteree}).keys()`;
660
- } else {
661
- iterExpr = `__s(${iteree}).entries()`;
662
- }
663
- }
664
-
665
- const binding =
666
- lhs.length === 1
667
- ? lhs[0] === "_"
668
- ? "_$"
669
- : lhs[0]
670
- : `[${lhs.map((n) => (n === "_" ? "_$" : n)).join(", ")}]`;
671
-
672
- this.line(`for (const ${binding} of ${iterExpr}) {`);
673
- this.indented(() => this.genBlock(stmt.body));
674
- this.line("}");
675
- }
676
-
677
- // Inline a statement as a for-init/post string (no semicolon)
678
- stmtInline(stmt) {
679
- switch (stmt.kind) {
680
- case "DefineStmt": {
681
- const rhs = stmt.rhs.map((e) => this.genExpr(e)).join(", ");
682
- const lhs = stmt.lhs.map((e) => e.name ?? this.genExpr(e)).join(", ");
683
- return `let ${lhs} = ${rhs}`;
684
- }
685
- case "AssignStmt": {
686
- const rhs = stmt.rhs.map((e) => this.genExpr(e)).join(", ");
687
- const lhs = stmt.lhs.map((e) => this.genExpr(e)).join(", ");
688
- return `${lhs} ${stmt.op} ${rhs}`;
689
- }
690
- case "IncDecStmt":
691
- return `${this.genExpr(stmt.expr)}${stmt.op}`;
692
- case "ExprStmt":
693
- return this.genExpr(stmt.expr);
694
- default:
695
- return "";
696
- }
697
- }
698
-
699
- genSwitch(stmt) {
700
- if (stmt.init) {
701
- this.line("{");
702
- this.indent++;
703
- this.genStmt(stmt.init);
704
- }
705
-
706
- const tag = stmt.tag ? this.genExpr(stmt.tag) : "true";
707
- this.line(`switch (${tag}) {`);
708
- this.indented(() => {
709
- for (const c of stmt.cases) {
710
- if (c.list) {
711
- for (const e of c.list) this.line(`case ${this.genExpr(e)}:`);
712
- } else {
713
- this.line("default:");
714
- }
715
- // Wrap each case body in {} so `let` declarations don't bleed
716
- // across sibling cases (JS switch shares one block scope).
717
- this.line("{");
718
- this.indented(() => {
719
- for (const s of c.stmts) this.genStmt(s);
720
- // Go doesn't fall through by default — add break unless last stmt is return/break
721
- const last = c.stmts[c.stmts.length - 1];
722
- if (
723
- !last ||
724
- (last.kind !== "ReturnStmt" && last.kind !== "BranchStmt")
725
- ) {
726
- this.line("break;");
727
- }
728
- });
729
- this.line("}");
730
- }
731
- });
732
- this.line("}");
733
-
734
- if (stmt.init) {
735
- this.indent--;
736
- this.line("}");
737
- }
738
- }
739
-
740
- // ── Expressions ──────────────────────────────────────────────
741
-
742
- genExpr(expr) {
743
- switch (expr.kind) {
744
- case "BasicLit":
745
- if (expr.litKind === "STRING") return JSON.stringify(expr.value);
746
- return expr.value; // int, float, bool, nil(null)
747
-
748
- case "Ident":
749
- return expr.name;
750
-
751
- case "UnaryExpr": {
752
- const op = expr.op === "^" ? "~" : expr.op; // bitwise NOT
753
- // Dereference/address-of — transparent in JS
754
- if (op === "*" || op === "&") return this.genExpr(expr.operand);
755
- return `${op}${this.genExpr(expr.operand)}`;
756
- }
757
-
758
- case "BinaryExpr": {
759
- const l = this.genExpr(expr.left);
760
- const r = this.genExpr(expr.right);
761
- // Integer division: only when both sides are int
762
- if (
763
- expr.op === "/" &&
764
- this.isIntType(expr.left._type) &&
765
- this.isIntType(expr.right._type)
766
- ) {
767
- return `Math.trunc(${l} / ${r})`;
768
- }
769
- return `${l} ${expr.op} ${r}`;
770
- }
771
-
772
- case "CallExpr":
773
- return this.genCall(expr);
774
-
775
- case "SelectorExpr": {
776
- const base = this.genExpr(expr.expr);
777
- // Bundled GoFront packages are inlined — drop the qualifier.
778
- if (this.bundledPackages.has(base)) return expr.field;
779
- // Pointer types are wrapped as { value: T }; route through .value
780
- if (expr.expr._type?.kind === "pointer" && expr.field !== "value") {
781
- return `${base}.value.${expr.field}`;
782
- }
783
- return `${base}.${expr.field}`;
784
- }
785
-
786
- case "IndexExpr": {
787
- const base = this.genExpr(expr.expr);
788
- const idx = this.genExpr(expr.index);
789
- if (expr._mapValueType && !expr._lvalue) {
790
- const zero = this.zeroValueForType(expr._mapValueType);
791
- return `(${base}[${idx}] ?? ${zero})`;
792
- }
793
- return `${base}[${idx}]`;
794
- }
795
-
796
- case "SliceExpr": {
797
- const base = this.genExpr(expr.expr);
798
- const lo = expr.low ? this.genExpr(expr.low) : "";
799
- const hi = expr.high ? this.genExpr(expr.high) : "";
800
- if (!lo && !hi) return `${base}.slice()`;
801
- if (!hi) return `${base}.slice(${lo})`;
802
- return `${base}.slice(${lo}, ${hi})`;
803
- }
804
-
805
- case "CompositeLit":
806
- return this.genCompositeLit(expr);
807
-
808
- case "FuncLit": {
809
- const params = expr.params.map((p) => p.name).join(", ");
810
- const asyncPrefix = expr.async ? "async " : "";
811
- const saved = this.out;
812
- this.out = [];
813
- this.indented(() => this._genBody(expr.body));
814
- const body = this.out.join("\n");
815
- this.out = saved;
816
- return `${asyncPrefix}function(${params}) {\n${body}\n${" ".repeat(this.indent)}}`;
817
- }
818
-
819
- case "TypeConversion": {
820
- const inner = this.genExpr(expr.expr);
821
- const target = expr.targetType?.name;
822
- switch (target) {
823
- case "string":
824
- return `String(${inner})`;
825
- case "int":
826
- case "byte":
827
- case "rune":
828
- return `Math.trunc(Number(${inner}))`;
829
- case "float64":
830
- return `Number(${inner})`;
831
- case "bool":
832
- return `Boolean(${inner})`;
833
- default:
834
- return inner;
835
- }
836
- }
837
-
838
- case "TypeAssertExpr": {
839
- const val = this.genExpr(expr.expr);
840
- if (!expr._commaOk) return val; // unsafe assertion — just pass value through
841
- // comma-ok: emit [value, runtimeTypeCheck]
842
- const check = this._typeCheckExpr(expr.type, val);
843
- return `[${val}, ${check}]`;
844
- }
845
-
846
- case "AwaitExpr":
847
- return `await ${this.genExpr(expr.expr)}`;
848
-
849
- case "RangeExpr":
850
- return this.genExpr(expr.expr);
851
-
852
- default:
853
- throw new Error(`CodeGen: unhandled expression kind '${expr.kind}'`);
854
- }
855
- }
856
-
857
- genCall(expr) {
858
- // Handle built-ins that need special JS translation
859
- if (expr.func.kind === "Ident") {
860
- switch (expr.func.name) {
861
- case "append":
862
- return this.genAppend(expr);
863
- case "len": {
864
- const arg = expr.args[0];
865
- const t = arg?._type;
866
- const js = this.genExpr(arg);
867
- if (t?.kind === "map") return `Object.keys(${js}).length`;
868
- this._usesLen = true;
869
- return `__len(${js})`;
870
- }
871
- case "cap":
872
- return `${this.genExpr(expr.args[0])}.length`;
873
- case "make":
874
- return this.genMake(expr);
875
- case "delete": {
876
- const [m, k] = expr.args.map((a) => this.genExpr(a));
877
- return `(delete ${m}[${k}])`;
878
- }
879
- case "copy": {
880
- const [dst, src] = expr.args.map((a) => this.genExpr(a));
881
- return `((__cd,__cs)=>{const n=Math.min(__cd.length,__cs.length);__cd.splice(0,n,...__cs.slice(0,n));return n;})(${dst},${src})`;
882
- }
883
- case "new": {
884
- const zero = this.zeroValueForExpr(expr.args[0]);
885
- return `{ value: ${zero} }`;
886
- }
887
- case "print":
888
- case "println": {
889
- const args = expr.args.map((a) => this.genExpr(a)).join(", ");
890
- return `console.log(${args})`;
891
- }
892
- case "panic": {
893
- const arg = this.genExpr(expr.args[0]);
894
- return `(() => { throw new Error(${arg}); })()`;
895
- }
896
- case "error": {
897
- // errors are plain strings; nil (null) means no error
898
- const arg = this.genExpr(expr.args[0]);
899
- return arg;
900
- }
901
- }
902
- }
903
-
904
- // error.Error() → the error string itself (errors are plain strings at runtime)
905
- if (
906
- expr.func.kind === "SelectorExpr" &&
907
- expr.func.field === "Error" &&
908
- expr.func.expr._type?.name === "error"
909
- ) {
910
- return this.genExpr(expr.func.expr);
911
- }
912
-
913
- // fmt.Sprintf / fmt.Printf / fmt.Println / fmt.Print / fmt.Errorf
914
- if (
915
- expr.func.kind === "SelectorExpr" &&
916
- expr.func.expr.kind === "Ident" &&
917
- expr.func.expr.name === "fmt"
918
- ) {
919
- const fmtArgs = expr.args.map((a) => this.genExpr(a)).join(", ");
920
- switch (expr.func.field) {
921
- case "Sprintf":
922
- this._usesSprintf = true;
923
- return `__sprintf(${fmtArgs})`;
924
- case "Errorf":
925
- this._usesSprintf = true;
926
- return `__sprintf(${fmtArgs})`;
927
- case "Printf":
928
- this._usesSprintf = true;
929
- return `process?.stdout?.write(__sprintf(${fmtArgs}))`;
930
- case "Println":
931
- this._usesSprintf = true;
932
- return `console.log(__sprintf(${fmtArgs}))`;
933
- case "Print":
934
- this._usesSprintf = true;
935
- return `process?.stdout?.write(__sprintf(${fmtArgs}))`;
936
- }
937
- }
938
-
939
- const fn = this.genExpr(expr.func);
940
- const args = expr.args.map((a) => this.genExpr(a)).join(", ");
941
- return `${fn}(${args})`;
942
- }
943
-
944
- genAppend(expr) {
945
- const slice = this.genExpr(expr.args[0]);
946
- const elems = expr.args.slice(1).map((a) => this.genExpr(a));
947
- if (elems.length === 0) return slice;
948
- this._usesAppend = true;
949
- return `__append(${slice}, ${elems.join(", ")})`;
950
- }
951
-
952
- genMake(expr) {
953
- // make([]T, n) or make([]T, n, cap) → new Array(n).fill(zero)
954
- // make(map[K]V) → {}
955
- const typeArg = expr.args[0];
956
- const typeNode = typeArg.kind === "TypeExpr" ? typeArg.type : typeArg;
957
- const resolvedKind = typeArg._type?.kind;
958
- if (typeNode.kind === "SliceType" || resolvedKind === "slice") {
959
- const n = expr.args[1] ? this.genExpr(expr.args[1]) : "0";
960
- // Use the proper zero value for the element type (e.g. new Point() not 0)
961
- const elemNode = typeNode.kind === "SliceType" ? typeNode.elem : null;
962
- const elemResolved =
963
- typeArg._type?.kind === "slice" ? typeArg._type.elem : null;
964
- let zero = "null";
965
- if (elemResolved) {
966
- zero = this.zeroValueForType(elemResolved);
967
- } else if (elemNode) {
968
- zero = this.zeroValueForTypeNode(elemNode);
969
- }
970
- // If zero value is a constructor call, use a factory function so each element is distinct
971
- if (zero.startsWith("new ")) {
972
- return `Array.from({length: ${n}}, () => ${zero})`;
973
- }
974
- return `new Array(${n}).fill(${zero})`;
975
- }
976
- if (typeNode.kind === "MapType" || resolvedKind === "map") {
977
- return "{}";
978
- }
979
- // Fallback
980
- return "{}";
981
- }
982
-
983
- // Render KeyValueExpr elements as a JS field list string.
984
- // Handles embedded-spread (_isEmbedInit) and plain key: value pairs.
985
- _genStructFields(elems) {
986
- return elems
987
- .filter((e) => e.kind === "KeyValueExpr")
988
- .map((e) =>
989
- e._isEmbedInit
990
- ? `...${this.genExpr(e.value)}`
991
- : `${e.key.name ?? this.genExpr(e.key)}: ${this.genExpr(e.value)}`,
992
- )
993
- .join(", ");
994
- }
995
-
996
- genCompositeLit(expr) {
997
- const t = expr.typeExpr;
998
-
999
- // Implicit composite literal: {X: 1} inside a slice/map — type inferred from context
1000
- if (t === null) {
1001
- if (expr.elems.length > 0 && expr.elems[0]?.kind === "KeyValueExpr") {
1002
- const typeName = expr._type?.name ?? expr._type?.underlying?.name;
1003
- if (typeName && this.structNames.has(typeName)) {
1004
- return `new ${typeName}({ ${this._genStructFields(expr.elems)} })`;
1005
- }
1006
- const fields = expr.elems
1007
- .map(
1008
- (e) =>
1009
- `${e.key.name ?? this.genExpr(e.key)}: ${this.genExpr(e.value)}`,
1010
- )
1011
- .join(", ");
1012
- return `{ ${fields} }`;
1013
- }
1014
- return `[${expr.elems.map((e) => this.genExpr(e)).join(", ")}]`;
1015
- }
1016
-
1017
- const typeName = this.getTypeName(t);
1018
-
1019
- // Struct: new Foo({ X: 1, Y: 2 })
1020
- if (typeName && this.structNames.has(typeName)) {
1021
- return `new ${typeName}({ ${this._genStructFields(expr.elems)} })`;
1022
- }
1023
-
1024
- // Slice/array: [1, 2, 3]
1025
- if (t?.kind === "SliceType" || t?.kind === "ArrayType") {
1026
- const elems = expr.elems
1027
- .map((e) =>
1028
- e.kind === "KeyValueExpr" ? this.genExpr(e.value) : this.genExpr(e),
1029
- )
1030
- .join(", ");
1031
- return `[${elems}]`;
1032
- }
1033
-
1034
- // Map: { key: value }
1035
- if (t?.kind === "MapType") {
1036
- const entries = expr.elems
1037
- .map((e) => {
1038
- if (e.kind === "KeyValueExpr") {
1039
- const k =
1040
- e.key.litKind === "STRING"
1041
- ? JSON.stringify(e.key.value)
1042
- : `[${this.genExpr(e.key)}]`;
1043
- return `${k}: ${this.genExpr(e.value)}`;
1044
- }
1045
- return this.genExpr(e);
1046
- })
1047
- .join(", ");
1048
- return `{ ${entries} }`;
1049
- }
1050
-
1051
- // Fallback: key-value pairs → plain object, positional → array
1052
- if (expr.elems.length > 0 && expr.elems[0]?.kind === "KeyValueExpr") {
1053
- const fields = expr.elems
1054
- .map(
1055
- (e) =>
1056
- `${e.key.name ?? this.genExpr(e.key)}: ${this.genExpr(e.value)}`,
1057
- )
1058
- .join(", ");
1059
- return `{ ${fields} }`;
1060
- }
1061
- return `[${expr.elems.map((e) => this.genExpr(e)).join(", ")}]`;
1062
- }
1063
-
1064
- // ── Helpers ───────────────────────────────────────────────────
1065
-
1066
- getTypeName(typeNode) {
1067
- if (!typeNode) return null;
1068
- if (typeNode.kind === "TypeName") return typeNode.name;
1069
- if (typeNode.kind === "Ident") return typeNode.name;
1070
- if (typeNode.kind === "SelectorExpr") return typeNode.field;
1071
- return null;
1072
- }
1073
-
1074
- isIntType(t) {
1075
- return t?.kind === "basic" && t.name === "int";
1076
- }
1077
-
1078
- // Returns the JS zero-value literal for a basic type name, or null if not a basic type.
1079
- _zeroForBasicName(name) {
1080
- switch (name) {
1081
- case "int":
1082
- case "float64":
1083
- case "byte":
1084
- case "rune":
1085
- return "0";
1086
- case "string":
1087
- return '""';
1088
- case "bool":
1089
- return "false";
1090
- default:
1091
- return null;
1092
- }
1093
- }
1094
-
1095
- zeroValueForTypeNode(typeNode) {
1096
- if (!typeNode) return "null";
1097
- switch (typeNode.kind) {
1098
- case "TypeName": {
1099
- const basic = this._zeroForBasicName(typeNode.name);
1100
- if (basic !== null) return basic;
1101
- if (this.structNames.has(typeNode.name))
1102
- return `new ${typeNode.name}()`;
1103
- return "null";
1104
- }
1105
- case "SliceType":
1106
- return "null";
1107
- case "ArrayType":
1108
- return "[]";
1109
- case "MapType":
1110
- return "{}";
1111
- case "PointerType":
1112
- return "null";
1113
- default:
1114
- return "null";
1115
- }
1116
- }
1117
-
1118
- // zeroValueForType operates on typechecker type objects (not AST type nodes).
1119
- zeroValueForType(t) {
1120
- if (!t) return "null";
1121
- switch (t.kind) {
1122
- case "basic": {
1123
- const basic = this._zeroForBasicName(t.name);
1124
- return basic !== null ? basic : "null";
1125
- }
1126
- case "slice":
1127
- return "null";
1128
- case "map":
1129
- return "{}";
1130
- case "named":
1131
- if (this.structNames.has(t.name)) return `new ${t.name}()`;
1132
- return "null";
1133
- default:
1134
- return "null";
1135
- }
1136
- }
1137
-
1138
- // Emit a JS boolean expression that checks whether `val` matches type node `t`.
1139
- _typeCheckExpr(typeNode, val) {
1140
- if (!typeNode) return "true";
1141
- if (typeNode.kind === "TypeName") {
1142
- switch (typeNode.name) {
1143
- case "int":
1144
- case "float64":
1145
- case "byte":
1146
- case "rune":
1147
- return `typeof ${val} === "number"`;
1148
- case "string":
1149
- return `typeof ${val} === "string"`;
1150
- case "bool":
1151
- return `typeof ${val} === "boolean"`;
1152
- default:
1153
- if (this.structNames.has(typeNode.name))
1154
- return `${val} instanceof ${typeNode.name}`;
1155
- return "true"; // unknown type — can't check at runtime
1156
- }
1157
- }
1158
- return "true";
1159
- }
1160
-
1161
- zeroValueForExpr(expr) {
1162
- // For new(T) calls
1163
- if (expr.kind === "Ident") {
1164
- switch (expr.name) {
1165
- case "int":
1166
- case "float64":
1167
- return "0";
1168
- case "string":
1169
- return '""';
1170
- case "bool":
1171
- return "false";
1172
- default:
1173
- if (this.structNames.has(expr.name)) return `new ${expr.name}()`;
1174
- }
1175
- }
1176
- return "null";
1177
- }
1178
-
1179
- typeComment(typeNode) {
1180
- if (!typeNode) return "unknown";
1181
- switch (typeNode.kind) {
1182
- case "TypeName":
1183
- return typeNode.name;
1184
- case "SliceType":
1185
- return `[]${this.typeComment(typeNode.elem)}`;
1186
- case "MapType":
1187
- return `map[${this.typeComment(typeNode.key)}]${this.typeComment(typeNode.value)}`;
1188
- default:
1189
- return typeNode.kind;
1190
- }
1191
- }
1192
388
  }
389
+
390
+ Object.assign(CodeGen.prototype, statementGenMethods);
391
+ Object.assign(CodeGen.prototype, expressionGenMethods);