gofront 0.0.2 → 0.0.4

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
@@ -77,6 +43,7 @@ export class CodeGen {
77
43
  this._usesAppend = false;
78
44
  this._usesSliceGuard = false;
79
45
  this._usesSprintf = false;
46
+ this._usesEqual = false;
80
47
  }
81
48
 
82
49
  // ── Output helpers ───────────────────────────────────────────
@@ -154,10 +121,7 @@ export class CodeGen {
154
121
  if (d.name === "init") {
155
122
  const renamed = initCount === 0 ? "init" : `init$${initCount}`;
156
123
  initNames.push(renamed);
157
- const saved = d.name;
158
- d.name = renamed;
159
- this.genFuncDecl(d);
160
- d.name = saved;
124
+ this.genFuncDecl(d, renamed);
161
125
  initCount++;
162
126
  } else {
163
127
  this.genFuncDecl(d);
@@ -183,9 +147,34 @@ export class CodeGen {
183
147
  );
184
148
  if (this._usesSliceGuard)
185
149
  helpers.push("function __s(a) { return a || []; }");
150
+ if (this._usesEqual)
151
+ helpers.push(
152
+ 'function __equal(a,b){if(a===b)return true;if(a===null||b===null)return false;if(Array.isArray(a)&&Array.isArray(b)){if(a.length!==b.length)return false;for(let i=0;i<a.length;i++)if(!__equal(a[i],b[i]))return false;return true;}if(typeof a==="object"&&typeof b==="object"){const ka=Object.keys(a),kb=Object.keys(b);if(ka.length!==kb.length)return false;for(const k of ka)if(!__equal(a[k],b[k]))return false;return true;}return false;}',
153
+ );
186
154
  if (this._usesSprintf)
187
155
  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);});}",
156
+ [
157
+ "function __sprintf(f,...a){let i=0;return f.replace(/%([#+\\- 0]*)([0-9]*)\\.?([0-9]*)[sdvftxXqobeEgG%]/g,(m)=>{",
158
+ "if(m==='%%')return'%';const fl=m.slice(1,-1),verb=m.slice(-1),v=a[i++];",
159
+ "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;};",
160
+ "const [,flags,width,prec]=m.match(/^%([#+\\- 0]*)([0-9]*)\\.?([0-9]*)/)||[];",
161
+ "const zero=flags?.includes('0')&&!flags?.includes('-');",
162
+ "switch(verb){",
163
+ "case's':return pad(String(v==null?'<nil>':v),width,false);",
164
+ "case'd':return pad(String(Math.trunc(Number(v))),width,zero);",
165
+ "case'v':return pad(String(v==null?'<nil>':v),width,false);",
166
+ "case'f':{const n=Number(v),p=prec!==''?parseInt(prec):6;return pad(n.toFixed(p),width,zero);}",
167
+ "case't':return pad(String(!!v),width,false);",
168
+ "case'x':return pad((Number(v)>>>0).toString(16),width,zero);",
169
+ "case'X':return pad((Number(v)>>>0).toString(16).toUpperCase(),width,zero);",
170
+ "case'o':return pad((Number(v)>>>0).toString(8),width,zero);",
171
+ "case'b':return pad((Number(v)>>>0).toString(2),width,zero);",
172
+ "case'q':return pad('\"'+String(v==null?'':v).replace(/\\\\/g,'\\\\\\\\').replace(/\"/g,'\\\\\"')+'\"',width,false);",
173
+ "case'e':case'E':{const n=Number(v),p=prec!==''?parseInt(prec):6;return pad(n.toExponential(p),width,zero);}",
174
+ "case'g':case'G':{const n=Number(v);return pad(prec!==''?n.toPrecision(parseInt(prec)):String(n),width,zero);}",
175
+ "default:return m;}});",
176
+ "}",
177
+ ].join(""),
189
178
  );
190
179
 
191
180
  if (helpers.length > 0) this.out.unshift(...helpers, "");
@@ -300,7 +289,8 @@ export class CodeGen {
300
289
 
301
290
  // ── Function declarations ────────────────────────────────────
302
291
 
303
- genFuncDecl(decl) {
292
+ genFuncDecl(decl, nameOverride) {
293
+ const name = nameOverride ?? decl.name;
304
294
  const params = decl.params
305
295
  .map((p, i) =>
306
296
  p.variadic && i === decl.params.length - 1 ? `...${p.name}` : p.name,
@@ -309,7 +299,7 @@ export class CodeGen {
309
299
  const asyncPrefix = decl.async ? "async " : "";
310
300
  const srcLine = decl._line ?? null;
311
301
  this.line(
312
- `${asyncPrefix}function ${decl.name}(${params}) {`,
302
+ `${asyncPrefix}function ${name}(${params}) {`,
313
303
  srcLine ? srcLine - 1 : null,
314
304
  );
315
305
  this.indented(() =>
@@ -337,7 +327,7 @@ export class CodeGen {
337
327
 
338
328
  // Emit a function body, wrapping in try/catch/finally for defer if needed.
339
329
  _genBody(body) {
340
- if (!this._hasDefer(body)) {
330
+ if (!body._hasDefer) {
341
331
  this.genBlock(body);
342
332
  return;
343
333
  }
@@ -367,27 +357,6 @@ export class CodeGen {
367
357
  }
368
358
  }
369
359
 
370
- // Recursively check if a block (or nested blocks) contains any DeferStmt.
371
- _hasDefer(block) {
372
- if (!block?.stmts) return false;
373
- for (const stmt of block.stmts) {
374
- if (stmt.kind === "DeferStmt") return true;
375
- if (stmt.kind === "Block" && this._hasDefer(stmt)) return true;
376
- if (
377
- stmt.kind === "IfStmt" &&
378
- (this._hasDefer(stmt.body) || this._hasDefer(stmt.elseBody))
379
- )
380
- return true;
381
- if (stmt.kind === "ForStmt" && this._hasDefer(stmt.body)) return true;
382
- if (
383
- stmt.kind === "SwitchStmt" &&
384
- stmt.cases.some((c) => c.stmts.some((s) => s.kind === "DeferStmt"))
385
- )
386
- return true;
387
- }
388
- return false;
389
- }
390
-
391
360
  // ── Variable / const declarations ────────────────────────────
392
361
 
393
362
  genVarDecl(decl) {
@@ -419,929 +388,7 @@ export class CodeGen {
419
388
  }
420
389
 
421
390
  // ── Statements ───────────────────────────────────────────────
422
-
423
- genBlock(block) {
424
- for (const stmt of block.stmts) this.genStmt(stmt);
425
- }
426
-
427
- genStmt(stmt) {
428
- // Record source mapping for the first line this statement produces
429
- const srcLine = stmt._line ?? null;
430
- const _line0 = this.out.length;
431
- switch (stmt.kind) {
432
- case "VarDecl":
433
- this.genVarDecl(stmt);
434
- break;
435
- case "ConstDecl":
436
- this.genConstDecl(stmt);
437
- break;
438
- case "TypeDecl":
439
- this.genTypeDeclWithMethods(stmt, []);
440
- break;
441
-
442
- case "DefineStmt": {
443
- // Map comma-ok: v, ok := m[key] → let v = m[key]; let ok = key in m;
444
- const rhsNode = stmt.rhs[0];
445
- if (
446
- stmt.rhs.length === 1 &&
447
- stmt.lhs.length === 2 &&
448
- rhsNode?.kind === "IndexExpr" &&
449
- rhsNode.expr?._type?.kind === "map"
450
- ) {
451
- const mapExpr = this.genExpr(rhsNode.expr);
452
- const keyExpr = this.genExpr(rhsNode.index);
453
- const [vName, okName] = stmt.lhs.map(
454
- (e) => e.name ?? this.genExpr(e),
455
- );
456
- if (vName !== "_")
457
- this.line(`let ${vName} = ${mapExpr}[${keyExpr}];`);
458
- if (okName !== "_")
459
- this.line(`let ${okName} = (${keyExpr}) in ${mapExpr};`);
460
- break;
461
- }
462
- const rhs = stmt.rhs.map((e) => this.genExpr(e));
463
- const lhs = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
464
- const redecls = stmt.lhs.map((e) => !!e._redecl);
465
- const anyRedecl = redecls.some((r) => r);
466
- if (lhs.length === 1) {
467
- if (redecls[0]) {
468
- this.line(`${lhs[0]} = ${rhs[0]};`);
469
- } else {
470
- this.line(`let ${lhs[0]} = ${rhs[0]};`);
471
- }
472
- } else if (!anyRedecl) {
473
- // All new: let [a, b] = ...
474
- const rhsStr = rhs.length === 1 ? rhs[0] : `[${rhs.join(", ")}]`;
475
- this.line(`let [${lhs.join(", ")}] = ${rhsStr};`);
476
- } else {
477
- // Mixed new/existing: emit separate statements
478
- // First compute the tuple into a temp if needed
479
- if (rhs.length === 1 && lhs.length > 1) {
480
- const tmp = "__t";
481
- this.line(`let ${tmp} = ${rhs[0]};`);
482
- for (let i = 0; i < lhs.length; i++) {
483
- const prefix = redecls[i] ? "" : "let ";
484
- this.line(`${prefix}${lhs[i]} = ${tmp}[${i}];`);
485
- }
486
- } else {
487
- for (let i = 0; i < lhs.length; i++) {
488
- const prefix = redecls[i] ? "" : "let ";
489
- this.line(`${prefix}${lhs[i]} = ${rhs[i]};`);
490
- }
491
- }
492
- }
493
- break;
494
- }
495
-
496
- case "AssignStmt": {
497
- const rhs = stmt.rhs.map((e) => this.genExpr(e));
498
- for (const e of stmt.lhs) if (e.kind === "IndexExpr") e._lvalue = true;
499
- const lhs = stmt.lhs.map((e) => this.genExpr(e));
500
- if (lhs.length === 1) {
501
- this.line(`${lhs[0]} ${stmt.op} ${rhs[0]};`);
502
- } else {
503
- const rhsStr = rhs.length === 1 ? rhs[0] : `[${rhs.join(", ")}]`;
504
- this.line(`[${lhs.join(", ")}] = ${rhsStr};`);
505
- }
506
- break;
507
- }
508
-
509
- case "IncDecStmt":
510
- this.line(`${this.genExpr(stmt.expr)}${stmt.op};`);
511
- break;
512
-
513
- case "ExprStmt":
514
- this.line(`${this.genExpr(stmt.expr)};`);
515
- break;
516
-
517
- case "ReturnStmt": {
518
- if (stmt.values.length === 0) {
519
- // bare return — emit named return vars if present
520
- if (this.namedReturnVars?.length > 0) {
521
- const vars = this.namedReturnVars;
522
- this.line(
523
- vars.length === 1
524
- ? `return ${vars[0]};`
525
- : `return [${vars.join(", ")}];`,
526
- );
527
- } else {
528
- this.line("return;");
529
- }
530
- } else if (stmt.values.length === 1) {
531
- this.line(`return ${this.genExpr(stmt.values[0])};`);
532
- } else {
533
- this.line(
534
- `return [${stmt.values.map((v) => this.genExpr(v)).join(", ")}];`,
535
- );
536
- }
537
- break;
538
- }
539
-
540
- case "IfStmt": {
541
- if (stmt.init) {
542
- // Wrap in a block so init variable is scoped
543
- this.line("{");
544
- this.indented(() => {
545
- this.genStmt(stmt.init);
546
- this._genIf(stmt);
547
- });
548
- this.line("}");
549
- } else {
550
- this._genIf(stmt);
551
- }
552
- break;
553
- }
554
-
555
- case "ForStmt":
556
- this.genFor(stmt);
557
- break;
558
-
559
- case "SwitchStmt":
560
- this.genSwitch(stmt);
561
- break;
562
-
563
- case "TypeSwitchStmt":
564
- this.genTypeSwitch(stmt);
565
- break;
566
-
567
- case "DeferStmt":
568
- this.line(`__defers.push(() => { ${this.genExpr(stmt.call)}; });`);
569
- break;
570
-
571
- case "LabeledStmt":
572
- this.line(`${stmt.label}:`);
573
- this.genStmt(stmt.body);
574
- break;
575
-
576
- case "BranchStmt":
577
- if (stmt.keyword !== "fallthrough")
578
- this.line(
579
- stmt.label ? `${stmt.keyword} ${stmt.label};` : `${stmt.keyword};`,
580
- );
581
- // fallthrough: omit — JS switch falls through naturally without a break
582
- break;
583
-
584
- case "Block":
585
- this.line("{");
586
- this.indented(() => this.genBlock(stmt));
587
- this.line("}");
588
- break;
589
-
590
- default:
591
- break;
592
- }
593
- // Record source mapping: first output line this statement produced
594
- if (srcLine != null && this.out.length > _line0) {
595
- this._srcMappings.push({ genLine: _line0, srcLine: srcLine - 1 });
596
- }
597
- }
598
-
599
- _genIf(stmt) {
600
- this.line(`if (${this.genExpr(stmt.cond)}) {`);
601
- this.indented(() => this.genBlock(stmt.body));
602
- this._genElse(stmt.elseBody);
603
- }
604
-
605
- _genElse(elseBody) {
606
- if (!elseBody) {
607
- this.line("}");
608
- return;
609
- }
610
- if (elseBody.kind === "IfStmt") {
611
- this.line(`} else if (${this.genExpr(elseBody.cond)}) {`);
612
- this.indented(() => this.genBlock(elseBody.body));
613
- this._genElse(elseBody.elseBody);
614
- } else {
615
- this.line("} else {");
616
- this.indented(() => this.genBlock(elseBody));
617
- this.line("}");
618
- }
619
- }
620
-
621
- genFor(stmt) {
622
- // Detect range: init is DefineStmt with rhs[0] being a RangeExpr
623
- if (this.isRangeFor(stmt)) {
624
- this.genRangeFor(stmt);
625
- return;
626
- }
627
-
628
- // for range N { } — no variable, just repeat N times
629
- if (stmt.cond?.kind === "RangeExpr") {
630
- const iteree = this.genExpr(stmt.cond.expr);
631
- this.line(`for (let _$ = 0; _$ < ${iteree}; _$++) {`);
632
- this.indented(() => this.genBlock(stmt.body));
633
- this.line("}");
634
- return;
635
- }
636
-
637
- if (!stmt.init && !stmt.post) {
638
- if (!stmt.cond) {
639
- // infinite loop
640
- this.line("while (true) {");
641
- } else {
642
- this.line(`while (${this.genExpr(stmt.cond)}) {`);
643
- }
644
- this.indented(() => this.genBlock(stmt.body));
645
- this.line("}");
646
- return;
647
- }
648
-
649
- // Standard C-style for
650
- const init = stmt.init ? this.stmtInline(stmt.init) : "";
651
- const cond = stmt.cond ? this.genExpr(stmt.cond) : "";
652
- const post = stmt.post ? this.stmtInline(stmt.post) : "";
653
- this.line(`for (${init}; ${cond}; ${post}) {`);
654
- this.indented(() => this.genBlock(stmt.body));
655
- this.line("}");
656
- }
657
-
658
- isRangeFor(stmt) {
659
- if (!stmt.init) return false;
660
- const init = stmt.init;
661
- if (init.kind !== "DefineStmt" && init.kind !== "AssignStmt") return false;
662
- return init.rhs?.[0]?.kind === "RangeExpr";
663
- }
664
-
665
- genRangeFor(stmt) {
666
- const init = stmt.init;
667
- const range = init.rhs[0];
668
- const lhs = init.lhs.map((e) => e.name ?? this.genExpr(e));
669
- const iteree = this.genExpr(range.expr);
670
- const iterType = range.expr._type;
671
-
672
- let iterExpr;
673
- if (
674
- lhs.length <= 1 &&
675
- iterType?.kind === "basic" &&
676
- (iterType.name === "int" || iterType.name === "float64")
677
- ) {
678
- // integer range (Go 1.22): for i := range n → C-style for loop
679
- const v = lhs[0] === "_" ? "_$" : lhs[0];
680
- this.line(`for (let ${v} = 0; ${v} < ${iteree}; ${v}++) {`);
681
- this.indented(() => this.genBlock(stmt.body));
682
- this.line("}");
683
- return;
684
- }
685
- if (
686
- iterType?.kind === "map" ||
687
- (iterType?.kind === "named" && iterType.underlying?.kind === "map")
688
- ) {
689
- // map: for k, v := range m → for (const [k, v] of Object.entries(m))
690
- iterExpr = `Object.entries(${iteree})`;
691
- } else if (iterType?.kind === "basic" && iterType.name === "string") {
692
- // string: for i, ch := range s → for (const [i, ch] of s.split('').entries())
693
- iterExpr =
694
- lhs.length === 1
695
- ? `Array.from(${iteree}).keys()`
696
- : `Array.from(${iteree}).entries()`;
697
- } else {
698
- // slice/array: for i, v := range arr → for (const [i, v] of __s(arr).entries())
699
- // null-guard handles nil slices (zero value) gracefully.
700
- this._usesSliceGuard = true;
701
- if (lhs.length === 1) {
702
- iterExpr = `__s(${iteree}).keys()`;
703
- } else {
704
- iterExpr = `__s(${iteree}).entries()`;
705
- }
706
- }
707
-
708
- const binding =
709
- lhs.length === 1
710
- ? lhs[0] === "_"
711
- ? "_$"
712
- : lhs[0]
713
- : `[${lhs.map((n) => (n === "_" ? "_$" : n)).join(", ")}]`;
714
-
715
- this.line(`for (const ${binding} of ${iterExpr}) {`);
716
- this.indented(() => this.genBlock(stmt.body));
717
- this.line("}");
718
- }
719
-
720
- // Inline a statement as a for-init/post string (no semicolon)
721
- stmtInline(stmt) {
722
- switch (stmt.kind) {
723
- case "DefineStmt": {
724
- const rhs = stmt.rhs.map((e) => this.genExpr(e)).join(", ");
725
- const lhs = stmt.lhs.map((e) => e.name ?? this.genExpr(e)).join(", ");
726
- return `let ${lhs} = ${rhs}`;
727
- }
728
- case "AssignStmt": {
729
- const rhs = stmt.rhs.map((e) => this.genExpr(e)).join(", ");
730
- const lhs = stmt.lhs.map((e) => this.genExpr(e)).join(", ");
731
- return `${lhs} ${stmt.op} ${rhs}`;
732
- }
733
- case "IncDecStmt":
734
- return `${this.genExpr(stmt.expr)}${stmt.op}`;
735
- case "ExprStmt":
736
- return this.genExpr(stmt.expr);
737
- default:
738
- return "";
739
- }
740
- }
741
-
742
- genSwitch(stmt) {
743
- const genBody = () => {
744
- const tag = stmt.tag ? this.genExpr(stmt.tag) : "true";
745
- this.line(`switch (${tag}) {`);
746
- this.indented(() => {
747
- for (const c of stmt.cases) {
748
- if (c.list) {
749
- for (const e of c.list) this.line(`case ${this.genExpr(e)}:`);
750
- } else {
751
- this.line("default:");
752
- }
753
- // Wrap each case body in {} so `let` declarations don't bleed
754
- // across sibling cases (JS switch shares one block scope).
755
- this.line("{");
756
- this.indented(() => {
757
- for (const s of c.stmts) this.genStmt(s);
758
- // Go doesn't fall through by default — add break unless last stmt is return/break
759
- const last = c.stmts[c.stmts.length - 1];
760
- if (
761
- !last ||
762
- (last.kind !== "ReturnStmt" && last.kind !== "BranchStmt")
763
- ) {
764
- this.line("break;");
765
- }
766
- });
767
- this.line("}");
768
- }
769
- });
770
- this.line("}");
771
- };
772
-
773
- if (stmt.init) {
774
- this.line("{");
775
- this.indented(() => {
776
- this.genStmt(stmt.init);
777
- genBody();
778
- });
779
- this.line("}");
780
- } else {
781
- genBody();
782
- }
783
- }
784
-
785
- genTypeSwitch(stmt) {
786
- this.line("{");
787
- this.indented(() => {
788
- const val = this.genExpr(stmt.expr);
789
- this.line(`const __tsw = ${val};`);
790
- if (stmt.assign) this.line(`let ${stmt.assign} = __tsw;`);
791
-
792
- let first = true;
793
- let hasDefault = false;
794
- for (const c of stmt.cases) {
795
- if (!c.types) {
796
- // default case — emit last
797
- hasDefault = c;
798
- continue;
799
- }
800
- const cond = c.types
801
- .map((t) => this._typeCheckExpr(t, "__tsw"))
802
- .join(" || ");
803
- this.line(`${first ? "if" : "else if"} (${cond}) {`);
804
- this.indented(() => {
805
- for (const s of c.stmts) this.genStmt(s);
806
- });
807
- this.line("}");
808
- first = false;
809
- }
810
- if (hasDefault) {
811
- this.line(first ? "{" : "else {");
812
- this.indented(() => {
813
- for (const s of hasDefault.stmts) this.genStmt(s);
814
- });
815
- this.line("}");
816
- }
817
- });
818
- this.line("}");
819
- }
820
-
821
- // ── Expressions ──────────────────────────────────────────────
822
-
823
- genExpr(expr) {
824
- switch (expr.kind) {
825
- case "BasicLit":
826
- if (expr.litKind === "STRING") return JSON.stringify(expr.value);
827
- return expr.value; // int, float, bool, nil(null)
828
-
829
- case "Ident":
830
- return expr.name;
831
-
832
- case "UnaryExpr": {
833
- const op = expr.op === "^" ? "~" : expr.op; // bitwise NOT
834
- // Dereference/address-of — transparent in JS
835
- if (op === "*" || op === "&") return this.genExpr(expr.operand);
836
- return `${op}${this.genExpr(expr.operand)}`;
837
- }
838
-
839
- case "BinaryExpr": {
840
- const l = this.genExpr(expr.left);
841
- const r = this.genExpr(expr.right);
842
- // Integer division: only when both sides are int
843
- if (
844
- expr.op === "/" &&
845
- this.isIntType(expr.left._type) &&
846
- this.isIntType(expr.right._type)
847
- ) {
848
- return `Math.trunc(${l} / ${r})`;
849
- }
850
- // Go == and != are strict — map to JS === / !==
851
- const op =
852
- expr.op === "==" ? "===" : expr.op === "!=" ? "!==" : expr.op;
853
- return `${l} ${op} ${r}`;
854
- }
855
-
856
- case "CallExpr":
857
- return this.genCall(expr);
858
-
859
- case "SelectorExpr": {
860
- const base = this.genExpr(expr.expr);
861
- // Bundled GoFront packages are inlined — drop the qualifier.
862
- if (this.bundledPackages.has(base)) return expr.field;
863
- // Pointer types are wrapped as { value: T }; route through .value
864
- if (expr.expr._type?.kind === "pointer" && expr.field !== "value") {
865
- return `${base}.value.${expr.field}`;
866
- }
867
- return `${base}.${expr.field}`;
868
- }
869
-
870
- case "IndexExpr": {
871
- const base = this.genExpr(expr.expr);
872
- const idx = this.genExpr(expr.index);
873
- if (expr._mapValueType && !expr._lvalue) {
874
- const zero = this.zeroValueForType(expr._mapValueType);
875
- return `(${base}[${idx}] ?? ${zero})`;
876
- }
877
- return `${base}[${idx}]`;
878
- }
879
-
880
- case "SliceExpr": {
881
- const base = this.genExpr(expr.expr);
882
- const lo = expr.low ? this.genExpr(expr.low) : "";
883
- const hi = expr.high ? this.genExpr(expr.high) : "";
884
- if (!lo && !hi) return `${base}.slice()`;
885
- if (!hi) return `${base}.slice(${lo})`;
886
- return `${base}.slice(${lo}, ${hi})`;
887
- }
888
-
889
- case "CompositeLit":
890
- return this.genCompositeLit(expr);
891
-
892
- case "FuncLit": {
893
- const params = expr.params.map((p) => p.name).join(", ");
894
- const asyncPrefix = expr.async ? "async " : "";
895
- const saved = this.out;
896
- this.out = [];
897
- this.indented(() => this._genBody(expr.body));
898
- const body = this.out.join("\n");
899
- this.out = saved;
900
- return `${asyncPrefix}function(${params}) {\n${body}\n${" ".repeat(this.indent)}}`;
901
- }
902
-
903
- case "TypeConversion": {
904
- const inner = this.genExpr(expr.expr);
905
- const t = expr.targetType;
906
-
907
- // Slice type conversions
908
- if (t?.kind === "SliceType") {
909
- const elem = t.elem?.name;
910
- // []byte(s) → UTF-8 byte array
911
- if (elem === "byte" || elem === "uint8") {
912
- return `Array.from(new TextEncoder().encode(${inner}))`;
913
- }
914
- // []rune(s) → Unicode code point array (only when source is a string)
915
- if (elem === "rune" || elem === "int32" || elem === "int") {
916
- const srcType = expr.expr._type;
917
- if (srcType?.kind === "basic" && srcType?.name === "string") {
918
- return `Array.from(${inner}, __c => __c.codePointAt(0))`;
919
- }
920
- return `Array.from(${inner})`;
921
- }
922
- return `Array.from(${inner})`;
923
- }
924
-
925
- const target = t?.name;
926
- switch (target) {
927
- case "string":
928
- return `String(${inner})`;
929
- case "int":
930
- case "byte":
931
- case "rune":
932
- return `Math.trunc(Number(${inner}))`;
933
- case "float64":
934
- return `Number(${inner})`;
935
- case "bool":
936
- return `Boolean(${inner})`;
937
- default:
938
- return inner;
939
- }
940
- }
941
-
942
- case "TypeAssertExpr": {
943
- const val = this.genExpr(expr.expr);
944
- if (!expr._commaOk) return val; // unsafe assertion — just pass value through
945
- // comma-ok: emit [value, runtimeTypeCheck]
946
- const check = this._typeCheckExpr(expr.type, val);
947
- return `[${val}, ${check}]`;
948
- }
949
-
950
- case "AwaitExpr":
951
- return `await ${this.genExpr(expr.expr)}`;
952
-
953
- case "RangeExpr":
954
- return this.genExpr(expr.expr);
955
-
956
- default:
957
- throw new Error(`CodeGen: unhandled expression kind '${expr.kind}'`);
958
- }
959
- }
960
-
961
- genCall(expr) {
962
- // Handle built-ins that need special JS translation
963
- if (expr.func.kind === "Ident") {
964
- switch (expr.func.name) {
965
- case "append":
966
- return this.genAppend(expr);
967
- case "len": {
968
- const arg = expr.args[0];
969
- const t = arg?._type;
970
- const js = this.genExpr(arg);
971
- if (t?.kind === "map") return `Object.keys(${js}).length`;
972
- this._usesLen = true;
973
- return `__len(${js})`;
974
- }
975
- case "cap":
976
- return `${this.genExpr(expr.args[0])}.length`;
977
- case "make":
978
- return this.genMake(expr);
979
- case "delete": {
980
- const [m, k] = expr.args.map((a) => this.genExpr(a));
981
- return `(delete ${m}[${k}])`;
982
- }
983
- case "copy": {
984
- const [dst, src] = expr.args.map((a) => this.genExpr(a));
985
- return `((__cd,__cs)=>{const n=Math.min(__cd.length,__cs.length);__cd.splice(0,n,...__cs.slice(0,n));return n;})(${dst},${src})`;
986
- }
987
- case "new": {
988
- const zero = this.zeroValueForExpr(expr.args[0]);
989
- return `{ value: ${zero} }`;
990
- }
991
- case "print":
992
- case "println": {
993
- const args = expr.args.map((a) => this.genExpr(a)).join(", ");
994
- return `console.log(${args})`;
995
- }
996
- case "panic": {
997
- const arg = this.genExpr(expr.args[0]);
998
- return `(() => { throw new Error(${arg}); })()`;
999
- }
1000
- case "recover":
1001
- return `(typeof __panic !== "undefined" && __panic !== null ? (() => { const __r = __panic.message ?? String(__panic); __panic = null; return __r; })() : null)`;
1002
- case "error": {
1003
- // errors are plain strings; nil (null) means no error
1004
- const arg = this.genExpr(expr.args[0]);
1005
- return arg;
1006
- }
1007
- case "min": {
1008
- const args = expr.args.map((a) => this.genExpr(a)).join(", ");
1009
- return `Math.min(${args})`;
1010
- }
1011
- case "max": {
1012
- const args = expr.args.map((a) => this.genExpr(a)).join(", ");
1013
- return `Math.max(${args})`;
1014
- }
1015
- case "clear": {
1016
- const arg = expr.args[0];
1017
- const t = arg?._type;
1018
- const js = this.genExpr(arg);
1019
- if (
1020
- t?.kind === "map" ||
1021
- (t?.kind === "named" && t.underlying?.kind === "map")
1022
- ) {
1023
- return `((__m) => { for (const __k in __m) delete __m[__k]; })(${js})`;
1024
- }
1025
- return `(${js}).length = 0`;
1026
- }
1027
- }
1028
- }
1029
-
1030
- // error.Error() → the error string itself (errors are plain strings at runtime)
1031
- if (
1032
- expr.func.kind === "SelectorExpr" &&
1033
- expr.func.field === "Error" &&
1034
- expr.func.expr._type?.name === "error"
1035
- ) {
1036
- return this.genExpr(expr.func.expr);
1037
- }
1038
-
1039
- // fmt.Sprintf / fmt.Printf / fmt.Println / fmt.Print / fmt.Errorf
1040
- if (
1041
- expr.func.kind === "SelectorExpr" &&
1042
- expr.func.expr.kind === "Ident" &&
1043
- expr.func.expr.name === "fmt"
1044
- ) {
1045
- const fmtArgs = expr.args.map((a) => this.genExpr(a)).join(", ");
1046
- switch (expr.func.field) {
1047
- case "Sprintf":
1048
- this._usesSprintf = true;
1049
- return `__sprintf(${fmtArgs})`;
1050
- case "Errorf":
1051
- this._usesSprintf = true;
1052
- return `__sprintf(${fmtArgs})`;
1053
- case "Printf":
1054
- this._usesSprintf = true;
1055
- return `process?.stdout?.write(__sprintf(${fmtArgs}))`;
1056
- case "Println":
1057
- this._usesSprintf = true;
1058
- return `console.log(__sprintf(${fmtArgs}))`;
1059
- case "Print":
1060
- this._usesSprintf = true;
1061
- return `process?.stdout?.write(__sprintf(${fmtArgs}))`;
1062
- }
1063
- }
1064
-
1065
- const rawFn = this.genExpr(expr.func);
1066
- // Wrap function literals in parens so `function(){}()` → `(function(){})()`
1067
- const fn = expr.func.kind === "FuncLit" ? `(${rawFn})` : rawFn;
1068
- const args = expr.args
1069
- .map((a) => (a._spread ? `...${this.genExpr(a)}` : this.genExpr(a)))
1070
- .join(", ");
1071
- return `${fn}(${args})`;
1072
- }
1073
-
1074
- genAppend(expr) {
1075
- const slice = this.genExpr(expr.args[0]);
1076
- const elems = expr.args
1077
- .slice(1)
1078
- .map((a) => (a._spread ? `...${this.genExpr(a)}` : this.genExpr(a)));
1079
- if (elems.length === 0) return slice;
1080
- this._usesAppend = true;
1081
- return `__append(${slice}, ${elems.join(", ")})`;
1082
- }
1083
-
1084
- genMake(expr) {
1085
- // make([]T, n) or make([]T, n, cap) → new Array(n).fill(zero)
1086
- // make(map[K]V) → {}
1087
- const typeArg = expr.args[0];
1088
- const typeNode = typeArg.kind === "TypeExpr" ? typeArg.type : typeArg;
1089
- const resolvedKind = typeArg._type?.kind;
1090
- if (typeNode.kind === "SliceType" || resolvedKind === "slice") {
1091
- const n = expr.args[1] ? this.genExpr(expr.args[1]) : "0";
1092
- // Use the proper zero value for the element type (e.g. new Point() not 0)
1093
- const elemNode = typeNode.kind === "SliceType" ? typeNode.elem : null;
1094
- const elemResolved =
1095
- typeArg._type?.kind === "slice" ? typeArg._type.elem : null;
1096
- let zero = "null";
1097
- if (elemResolved) {
1098
- zero = this.zeroValueForType(elemResolved);
1099
- } else if (elemNode) {
1100
- zero = this.zeroValueForTypeNode(elemNode);
1101
- }
1102
- // If zero value is a constructor call, use a factory function so each element is distinct
1103
- if (zero.startsWith("new ")) {
1104
- return `Array.from({length: ${n}}, () => ${zero})`;
1105
- }
1106
- return `new Array(${n}).fill(${zero})`;
1107
- }
1108
- // map or fallback
1109
- return "{}";
1110
- }
1111
-
1112
- // Render KeyValueExpr elements as a JS field list string.
1113
- // Handles embedded-spread (_isEmbedInit) and plain key: value pairs.
1114
- _genStructFields(elems) {
1115
- return elems
1116
- .filter((e) => e.kind === "KeyValueExpr")
1117
- .map((e) =>
1118
- e._isEmbedInit
1119
- ? `...${this.genExpr(e.value)}`
1120
- : `${e.key.name ?? this.genExpr(e.key)}: ${this.genExpr(e.value)}`,
1121
- )
1122
- .join(", ");
1123
- }
1124
-
1125
- genCompositeLit(expr) {
1126
- const t = expr.typeExpr;
1127
-
1128
- // Implicit composite literal: {X: 1} inside a slice/map — type inferred from context
1129
- if (t === null) {
1130
- if (expr.elems.length > 0 && expr.elems[0]?.kind === "KeyValueExpr") {
1131
- const typeName = expr._type?.name ?? expr._type?.underlying?.name;
1132
- if (typeName && this.structNames.has(typeName)) {
1133
- return `new ${typeName}({ ${this._genStructFields(expr.elems)} })`;
1134
- }
1135
- const fields = expr.elems
1136
- .map(
1137
- (e) =>
1138
- `${e.key.name ?? this.genExpr(e.key)}: ${this.genExpr(e.value)}`,
1139
- )
1140
- .join(", ");
1141
- return `{ ${fields} }`;
1142
- }
1143
- return `[${expr.elems.map((e) => this.genExpr(e)).join(", ")}]`;
1144
- }
1145
-
1146
- const typeName = this.getTypeName(t);
1147
-
1148
- // Struct: new Foo({ X: 1, Y: 2 })
1149
- if (typeName && this.structNames.has(typeName)) {
1150
- return `new ${typeName}({ ${this._genStructFields(expr.elems)} })`;
1151
- }
1152
-
1153
- // Slice/array: [1, 2, 3]
1154
- if (t?.kind === "SliceType" || t?.kind === "ArrayType") {
1155
- const elems = expr.elems
1156
- .map((e) =>
1157
- e.kind === "KeyValueExpr" ? this.genExpr(e.value) : this.genExpr(e),
1158
- )
1159
- .join(", ");
1160
- return `[${elems}]`;
1161
- }
1162
-
1163
- // Map: { key: value }
1164
- if (t?.kind === "MapType") {
1165
- const entries = expr.elems
1166
- .map((e) => {
1167
- if (e.kind === "KeyValueExpr") {
1168
- const k =
1169
- e.key.litKind === "STRING"
1170
- ? JSON.stringify(e.key.value)
1171
- : `[${this.genExpr(e.key)}]`;
1172
- return `${k}: ${this.genExpr(e.value)}`;
1173
- }
1174
- return this.genExpr(e);
1175
- })
1176
- .join(", ");
1177
- return `{ ${entries} }`;
1178
- }
1179
-
1180
- // Fallback: key-value pairs → plain object, positional → array
1181
- if (expr.elems.length > 0 && expr.elems[0]?.kind === "KeyValueExpr") {
1182
- const fields = expr.elems
1183
- .map(
1184
- (e) =>
1185
- `${e.key.name ?? this.genExpr(e.key)}: ${this.genExpr(e.value)}`,
1186
- )
1187
- .join(", ");
1188
- return `{ ${fields} }`;
1189
- }
1190
- return `[${expr.elems.map((e) => this.genExpr(e)).join(", ")}]`;
1191
- }
1192
-
1193
- // ── Helpers ───────────────────────────────────────────────────
1194
-
1195
- getTypeName(typeNode) {
1196
- if (!typeNode) return null;
1197
- if (typeNode.kind === "TypeName") return typeNode.name;
1198
- if (typeNode.kind === "Ident") return typeNode.name;
1199
- if (typeNode.kind === "SelectorExpr") return typeNode.field;
1200
- return null;
1201
- }
1202
-
1203
- isIntType(t) {
1204
- return t?.kind === "basic" && t.name === "int";
1205
- }
1206
-
1207
- // Returns the JS zero-value literal for a basic type name, or null if not a basic type.
1208
- _zeroForBasicName(name) {
1209
- switch (name) {
1210
- case "int":
1211
- case "uint":
1212
- case "int8":
1213
- case "int16":
1214
- case "int32":
1215
- case "int64":
1216
- case "uint8":
1217
- case "uint16":
1218
- case "uint32":
1219
- case "uint64":
1220
- case "uintptr":
1221
- case "float32":
1222
- case "float64":
1223
- case "byte":
1224
- case "rune":
1225
- return "0";
1226
- case "string":
1227
- return '""';
1228
- case "bool":
1229
- return "false";
1230
- default:
1231
- return null;
1232
- }
1233
- }
1234
-
1235
- zeroValueForTypeNode(typeNode) {
1236
- if (!typeNode) return "null";
1237
- switch (typeNode.kind) {
1238
- case "TypeName": {
1239
- const basic = this._zeroForBasicName(typeNode.name);
1240
- if (basic !== null) return basic;
1241
- if (this.structNames.has(typeNode.name))
1242
- return `new ${typeNode.name}()`;
1243
- return "null";
1244
- }
1245
- case "SliceType":
1246
- return "null";
1247
- case "ArrayType":
1248
- return "[]";
1249
- case "MapType":
1250
- return "{}";
1251
- case "PointerType":
1252
- return "null";
1253
- default:
1254
- return "null";
1255
- }
1256
- }
1257
-
1258
- // zeroValueForType operates on typechecker type objects (not AST type nodes).
1259
- zeroValueForType(t) {
1260
- if (!t) return "null";
1261
- switch (t.kind) {
1262
- case "basic": {
1263
- const basic = this._zeroForBasicName(t.name);
1264
- return basic !== null ? basic : "null";
1265
- }
1266
- case "slice":
1267
- return "null";
1268
- case "map":
1269
- return "{}";
1270
- case "named":
1271
- if (this.structNames.has(t.name)) return `new ${t.name}()`;
1272
- return "null";
1273
- default:
1274
- return "null";
1275
- }
1276
- }
1277
-
1278
- // Emit a JS boolean expression that checks whether `val` matches type node `t`.
1279
- _typeCheckExpr(typeNode, val) {
1280
- if (!typeNode) return "true";
1281
- if (typeNode.kind === "TypeName") {
1282
- switch (typeNode.name) {
1283
- case "int":
1284
- case "uint":
1285
- case "int8":
1286
- case "int16":
1287
- case "int32":
1288
- case "int64":
1289
- case "uint8":
1290
- case "uint16":
1291
- case "uint32":
1292
- case "uint64":
1293
- case "uintptr":
1294
- case "float32":
1295
- case "float64":
1296
- case "byte":
1297
- case "rune":
1298
- return `typeof ${val} === "number"`;
1299
- case "string":
1300
- return `typeof ${val} === "string"`;
1301
- case "bool":
1302
- return `typeof ${val} === "boolean"`;
1303
- case "nil":
1304
- return `${val} === null`;
1305
- case "error":
1306
- return `typeof ${val} === "string"`;
1307
- default:
1308
- if (this.structNames.has(typeNode.name))
1309
- return `${val} instanceof ${typeNode.name}`;
1310
- return "true"; // unknown type — can't check at runtime
1311
- }
1312
- }
1313
- return "true";
1314
- }
1315
-
1316
- zeroValueForExpr(expr) {
1317
- // For new(T) calls
1318
- if (expr.kind === "Ident") {
1319
- switch (expr.name) {
1320
- case "int":
1321
- case "float64":
1322
- return "0";
1323
- case "string":
1324
- return '""';
1325
- case "bool":
1326
- return "false";
1327
- default:
1328
- if (this.structNames.has(expr.name)) return `new ${expr.name}()`;
1329
- }
1330
- }
1331
- return "null";
1332
- }
1333
-
1334
- typeComment(typeNode) {
1335
- if (!typeNode) return "unknown";
1336
- switch (typeNode.kind) {
1337
- case "TypeName":
1338
- return typeNode.name;
1339
- case "SliceType":
1340
- return `[]${this.typeComment(typeNode.elem)}`;
1341
- case "MapType":
1342
- return `map[${this.typeComment(typeNode.key)}]${this.typeComment(typeNode.value)}`;
1343
- default:
1344
- return typeNode.kind;
1345
- }
1346
- }
1347
391
  }
392
+
393
+ Object.assign(CodeGen.prototype, statementGenMethods);
394
+ Object.assign(CodeGen.prototype, expressionGenMethods);