gofront 0.0.4 → 0.0.6

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.
@@ -16,21 +16,29 @@ function vlqEncode(value) {
16
16
  return result;
17
17
  }
18
18
 
19
- export function buildSourceMap(sourceName, mappings) {
20
- // mappings: Array<{ genLine: number, srcLine: number }> (0-based)
19
+ export function buildSourceMap(sources, mappings) {
20
+ // sources: string[]
21
+ // mappings: Array<{ genLine: number, srcLine: number, srcFileIdx?: number }> (0-based)
21
22
  // Emits one segment per generated line, column 0 → source line (delta-encoded).
22
23
  const lines = [];
23
24
  let prevSrcLine = 0;
25
+ let prevSrcFile = 0;
24
26
  const maxGen = mappings.reduce((m, e) => Math.max(m, e.genLine), -1);
25
27
  const byLine = new Map(mappings.map((e) => [e.genLine, e]));
26
28
  for (let g = 0; g <= maxGen; g++) {
27
29
  const entry = byLine.get(g);
28
30
  if (entry) {
31
+ const fileIdx = entry.srcFileIdx ?? 0;
32
+ const fileDelta = fileIdx - prevSrcFile;
29
33
  const srcDelta = entry.srcLine - prevSrcLine;
30
- // Segment: [genCol=0, srcFileIdx=0, srcLineDelta, srcCol=0]
34
+ // Segment: [genCol=0, srcFileIdxDelta, srcLineDelta, srcCol=0]
31
35
  lines.push(
32
- vlqEncode(0) + vlqEncode(0) + vlqEncode(srcDelta) + vlqEncode(0),
36
+ vlqEncode(0) +
37
+ vlqEncode(fileDelta) +
38
+ vlqEncode(srcDelta) +
39
+ vlqEncode(0),
33
40
  );
41
+ prevSrcFile = fileIdx;
34
42
  prevSrcLine = entry.srcLine;
35
43
  } else {
36
44
  lines.push(""); // no mapping for this generated line
@@ -38,7 +46,7 @@ export function buildSourceMap(sourceName, mappings) {
38
46
  }
39
47
  return JSON.stringify({
40
48
  version: 3,
41
- sources: [sourceName],
49
+ sources,
42
50
  names: [],
43
51
  mappings: lines.join(";"),
44
52
  });
@@ -1,5 +1,7 @@
1
1
  // CodeGen statement methods — installed as a mixin on CodeGen.prototype.
2
2
 
3
+ import { isComplex } from "../typechecker/types.js";
4
+
3
5
  export const statementGenMethods = {
4
6
  genBlock(block) {
5
7
  for (const stmt of block.stmts) this.genStmt(stmt);
@@ -45,10 +47,16 @@ export const statementGenMethods = {
45
47
  const redecls = stmt.lhs.map((e) => !!e._redecl);
46
48
  const anyRedecl = redecls.some((r) => r);
47
49
  if (lhs.length === 1) {
50
+ const isBoxed = this._boxedVars.has(lhs[0]);
51
+ const val = isBoxed && !redecls[0] ? `{ value: ${rhs[0]} }` : rhs[0];
48
52
  if (redecls[0]) {
49
- this.line(`${lhs[0]} = ${rhs[0]};`);
53
+ if (isBoxed) {
54
+ this.line(`${lhs[0]}.value = ${rhs[0]};`);
55
+ } else {
56
+ this.line(`${lhs[0]} = ${val};`);
57
+ }
50
58
  } else {
51
- this.line(`let ${lhs[0]} = ${rhs[0]};`);
59
+ this.line(`let ${lhs[0]} = ${val};`);
52
60
  }
53
61
  } else if (!anyRedecl) {
54
62
  // All new: let [a, b] = ...
@@ -75,6 +83,38 @@ export const statementGenMethods = {
75
83
  }
76
84
 
77
85
  case "AssignStmt": {
86
+ // Complex compound assignment: z += w → z = z + w (with complex codegen)
87
+ if (
88
+ stmt.op !== "=" &&
89
+ stmt.lhs.length === 1 &&
90
+ stmt.rhs.length === 1 &&
91
+ isComplex(stmt.lhs[0]._type)
92
+ ) {
93
+ const lhsStr = stmt.lhs[0].name ?? this.genExpr(stmt.lhs[0]);
94
+ const rhsExpr = this._genComplexOperand(stmt.rhs[0]);
95
+ const baseOp = stmt.op.slice(0, -1); // "+=" → "+"
96
+ let result;
97
+ switch (baseOp) {
98
+ case "+":
99
+ result = `{ re: ${lhsStr}.re + ${rhsExpr}.re, im: ${lhsStr}.im + ${rhsExpr}.im }`;
100
+ break;
101
+ case "-":
102
+ result = `{ re: ${lhsStr}.re - ${rhsExpr}.re, im: ${lhsStr}.im - ${rhsExpr}.im }`;
103
+ break;
104
+ case "*":
105
+ this._usesCmul = true;
106
+ result = `__cmul(${lhsStr}, ${rhsExpr})`;
107
+ break;
108
+ case "/":
109
+ this._usesCdiv = true;
110
+ result = `__cdiv(${lhsStr}, ${rhsExpr})`;
111
+ break;
112
+ default:
113
+ result = rhsExpr;
114
+ }
115
+ this.line(`${lhsStr} = ${result};`);
116
+ break;
117
+ }
78
118
  // comma-ok map index: v, ok = m["key"]
79
119
  if (stmt._commaOkMap) {
80
120
  const rhsNode = stmt.rhs[0];
@@ -113,7 +153,15 @@ export const statementGenMethods = {
113
153
  }
114
154
  const rhs = stmt.rhs.map((e) => this.genExpr(e));
115
155
  for (const e of stmt.lhs) if (e.kind === "IndexExpr") e._lvalue = true;
116
- const lhs = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
156
+ const lhs = stmt.lhs.map((e) => {
157
+ if (
158
+ e.kind === "Ident" &&
159
+ e.name !== "_" &&
160
+ this._boxedVars.has(e.name)
161
+ )
162
+ return `${e.name}.value`;
163
+ return e.name ?? this.genExpr(e);
164
+ });
117
165
  // Filter out blank identifier assignments entirely
118
166
  const pairs = lhs.map((l, i) => ({ l, r: rhs[i] ?? rhs[0] }));
119
167
  const active = pairs.filter((p) => p.l !== "_");
@@ -151,6 +199,21 @@ export const statementGenMethods = {
151
199
  break;
152
200
 
153
201
  case "ReturnStmt": {
202
+ if (this._inIteratorBody) {
203
+ if (stmt.values.length === 0 && !this.namedReturnVars?.length) {
204
+ this.line(`${this._iterReturnFlag} = true; return false;`);
205
+ } else {
206
+ const vals =
207
+ stmt.values.length > 0
208
+ ? stmt.values.map((v) => this.genExpr(v))
209
+ : (this.namedReturnVars ?? []);
210
+ const stored = vals.length === 1 ? vals[0] : `[${vals.join(", ")}]`;
211
+ this.line(
212
+ `${this._iterReturnFlag} = true; ${this._iterReturnVar} = ${stored}; return false;`,
213
+ );
214
+ }
215
+ break;
216
+ }
154
217
  if (stmt.values.length === 0) {
155
218
  // bare return — emit named return vars if present
156
219
  if (this.namedReturnVars?.length > 0) {
@@ -210,6 +273,16 @@ export const statementGenMethods = {
210
273
  break;
211
274
 
212
275
  case "BranchStmt":
276
+ if (this._inIteratorBody && !stmt.label) {
277
+ if (stmt.keyword === "break") {
278
+ this.line(`${this._iterBreakFlag} = true; return false;`);
279
+ break;
280
+ }
281
+ if (stmt.keyword === "continue") {
282
+ this.line("return true;");
283
+ break;
284
+ }
285
+ }
213
286
  if (stmt.keyword !== "fallthrough")
214
287
  this.line(
215
288
  stmt.label ? `${stmt.keyword} ${stmt.label};` : `${stmt.keyword};`,
@@ -255,14 +328,22 @@ export const statementGenMethods = {
255
328
  },
256
329
 
257
330
  genFor(stmt) {
258
- // Detect range: init is DefineStmt with rhs[0] being a RangeExpr
331
+ // Detect range: init is DefineStmt/AssignStmt with rhs[0] being a RangeExpr
259
332
  if (this.isRangeFor(stmt)) {
260
- this.genRangeFor(stmt);
333
+ if (stmt.init.rhs[0]._isIterator) {
334
+ this.genIteratorFor(stmt);
335
+ } else {
336
+ this.genRangeFor(stmt);
337
+ }
261
338
  return;
262
339
  }
263
340
 
264
- // for range N { } — no variable, just repeat N times
341
+ // for range expr — no variable; could be int range or 0-param iterator
265
342
  if (stmt.cond?.kind === "RangeExpr") {
343
+ if (stmt.cond._isIterator) {
344
+ this.genIteratorForCond(stmt);
345
+ return;
346
+ }
266
347
  const iteree = this.genExpr(stmt.cond.expr);
267
348
  this.line(`for (let _$ = 0; _$ < ${iteree}; _$++) {`);
268
349
  this.indented(() => this.genBlock(stmt.body));
@@ -361,6 +442,112 @@ export const statementGenMethods = {
361
442
  this.line("}");
362
443
  },
363
444
 
445
+ genIteratorFor(stmt) {
446
+ const range = stmt.init.rhs[0];
447
+ const lhs = stmt.init.lhs.map((e) => e.name ?? this.genExpr(e));
448
+ let iteree = this.genExpr(range.expr);
449
+ const yieldParams = range._yieldParams;
450
+
451
+ // Wrap bare function expressions to avoid "Function statements require a name"
452
+ if (range.expr.kind === "FuncLit") {
453
+ iteree = `(${iteree})`;
454
+ }
455
+
456
+ const d = this._iterDepth++;
457
+ const breakFlag = `__broke${d}`;
458
+ const retFlag = `__returned${d}`;
459
+ const retVar = `__retVal${d}`;
460
+
461
+ // Yield callback param names (blank vars get _$N to avoid JS syntax error)
462
+ const cbParams = lhs.map((n, i) => (n === "_" ? `_$${i}` : n));
463
+
464
+ this.line("{");
465
+ this.indented(() => {
466
+ this.line(`let ${breakFlag} = false;`);
467
+ this.line(`let ${retFlag} = false;`);
468
+ this.line(`let ${retVar};`);
469
+
470
+ const params = yieldParams.length === 0 ? "" : cbParams.join(", ");
471
+ this.line(`${iteree}(function(${params}) {`);
472
+ this.indented(() => {
473
+ this.line(`if (${breakFlag}) return false;`);
474
+ const prev = {
475
+ in: this._inIteratorBody,
476
+ break: this._iterBreakFlag,
477
+ ret: this._iterReturnFlag,
478
+ retVar: this._iterReturnVar,
479
+ };
480
+ this._inIteratorBody = true;
481
+ this._iterBreakFlag = breakFlag;
482
+ this._iterReturnFlag = retFlag;
483
+ this._iterReturnVar = retVar;
484
+
485
+ this.genBlock(stmt.body);
486
+
487
+ this._inIteratorBody = prev.in;
488
+ this._iterBreakFlag = prev.break;
489
+ this._iterReturnFlag = prev.ret;
490
+ this._iterReturnVar = prev.retVar;
491
+
492
+ this.line("return true;");
493
+ });
494
+ this.line("});");
495
+ this.line(`if (${retFlag}) return ${retVar};`);
496
+ });
497
+ this.line("}");
498
+ this._iterDepth--;
499
+ },
500
+
501
+ // 0-param iterator: for range iter { body }
502
+ genIteratorForCond(stmt) {
503
+ const range = stmt.cond;
504
+ let iteree = this.genExpr(range.expr);
505
+
506
+ if (range.expr.kind === "FuncLit") {
507
+ iteree = `(${iteree})`;
508
+ }
509
+
510
+ const d = this._iterDepth++;
511
+ const breakFlag = `__broke${d}`;
512
+ const retFlag = `__returned${d}`;
513
+ const retVar = `__retVal${d}`;
514
+
515
+ this.line("{");
516
+ this.indented(() => {
517
+ this.line(`let ${breakFlag} = false;`);
518
+ this.line(`let ${retFlag} = false;`);
519
+ this.line(`let ${retVar};`);
520
+
521
+ this.line(`${iteree}(function() {`);
522
+ this.indented(() => {
523
+ this.line(`if (${breakFlag}) return false;`);
524
+ const prev = {
525
+ in: this._inIteratorBody,
526
+ break: this._iterBreakFlag,
527
+ ret: this._iterReturnFlag,
528
+ retVar: this._iterReturnVar,
529
+ };
530
+ this._inIteratorBody = true;
531
+ this._iterBreakFlag = breakFlag;
532
+ this._iterReturnFlag = retFlag;
533
+ this._iterReturnVar = retVar;
534
+
535
+ this.genBlock(stmt.body);
536
+
537
+ this._inIteratorBody = prev.in;
538
+ this._iterBreakFlag = prev.break;
539
+ this._iterReturnFlag = prev.ret;
540
+ this._iterReturnVar = prev.retVar;
541
+
542
+ this.line("return true;");
543
+ });
544
+ this.line("});");
545
+ this.line(`if (${retFlag}) return ${retVar};`);
546
+ });
547
+ this.line("}");
548
+ this._iterDepth--;
549
+ },
550
+
364
551
  // Inline a statement as a for-init/post string (no semicolon)
365
552
  stmtInline(stmt) {
366
553
  switch (stmt.kind) {
package/src/codegen.js CHANGED
@@ -20,6 +20,7 @@
20
20
  import { expressionGenMethods } from "./codegen/expressions.js";
21
21
  import { buildSourceMap } from "./codegen/source-map.js";
22
22
  import { statementGenMethods } from "./codegen/statements.js";
23
+ import { isComplex, isNumeric } from "./typechecker/types.js";
23
24
 
24
25
  export class CodeGen {
25
26
  // jsImports: Map<importPath, string[]> — npm package imports to emit at top of file
@@ -37,13 +38,25 @@ export class CodeGen {
37
38
  this.jsImports = jsImports;
38
39
  this.bundledPackages = bundledPackages;
39
40
  this.namedReturnVars = null; // names of current function's named return vars
40
- this._srcMappings = []; // { genLine, srcLine } for source map
41
+ this._srcMappings = []; // { genLine, srcLine, srcFileIdx } for source map
42
+ this._currentSrcFileIdx = 0; // updated as each top-level decl is generated
43
+ this._boxedVars = new Set(); // address-taken scalar variables that need boxing
41
44
  // Runtime helper usage tracking — only emit helpers that are actually used
42
45
  this._usesLen = false;
43
46
  this._usesAppend = false;
44
47
  this._usesSliceGuard = false;
45
48
  this._usesSprintf = false;
46
49
  this._usesEqual = false;
50
+ this._usesCmul = false;
51
+ this._usesCdiv = false;
52
+ this._usesError = false;
53
+ this._usesErrorIs = false;
54
+ // Iterator (range-over-func) context
55
+ this._inIteratorBody = false;
56
+ this._iterDepth = 0;
57
+ this._iterBreakFlag = null;
58
+ this._iterReturnFlag = null;
59
+ this._iterReturnVar = null;
47
60
  }
48
61
 
49
62
  // ── Output helpers ───────────────────────────────────────────
@@ -55,8 +68,9 @@ export class CodeGen {
55
68
  if (srcLine != null) {
56
69
  this._srcMappings.push({
57
70
  genLine: this.out.length,
58
- srcLine: srcLine - 1,
59
- }); // 0-based
71
+ srcLine: srcLine - 1, // 0-based
72
+ srcFileIdx: this._currentSrcFileIdx,
73
+ });
60
74
  }
61
75
  this.out.push(" ".repeat(this.indent) + s);
62
76
  }
@@ -97,6 +111,7 @@ export class CodeGen {
97
111
 
98
112
  for (const d of program.decls) {
99
113
  if (d.kind === "TypeDecl") {
114
+ this._currentSrcFileIdx = d._srcFileIdx ?? 0;
100
115
  this.genTypeDeclWithMethods(d, methods.get(d.name) ?? []);
101
116
  this.blank();
102
117
  }
@@ -104,10 +119,12 @@ export class CodeGen {
104
119
 
105
120
  for (const d of program.decls) {
106
121
  if (d.kind === "VarDecl") {
122
+ this._currentSrcFileIdx = d._srcFileIdx ?? 0;
107
123
  this.genVarDecl(d);
108
124
  this.blank();
109
125
  }
110
126
  if (d.kind === "ConstDecl") {
127
+ this._currentSrcFileIdx = d._srcFileIdx ?? 0;
111
128
  this.genConstDecl(d);
112
129
  this.blank();
113
130
  }
@@ -118,6 +135,7 @@ export class CodeGen {
118
135
  const initNames = [];
119
136
  for (const d of program.decls) {
120
137
  if (d.kind === "FuncDecl") {
138
+ this._currentSrcFileIdx = d._srcFileIdx ?? 0;
121
139
  if (d.name === "init") {
122
140
  const renamed = initCount === 0 ? "init" : `init$${initCount}`;
123
141
  initNames.push(renamed);
@@ -140,21 +158,31 @@ export class CodeGen {
140
158
  // Prepend runtime helpers that were actually used
141
159
  const helpers = [];
142
160
  if (this._usesLen)
143
- helpers.push("function __len(a) { return a?.length ?? 0; }");
161
+ helpers.push(
162
+ "var __len = __len || function(a) { if (a && typeof a === 'object' && !Array.isArray(a)) return Object.keys(a).length; return a?.length ?? 0; };",
163
+ );
144
164
  if (this._usesAppend)
145
165
  helpers.push(
146
- "function __append(a, ...b) { return a ? [...a, ...b] : b; }",
166
+ "var __append = __append || function(a, ...b) { return a ? [...a, ...b] : b; };",
147
167
  );
148
168
  if (this._usesSliceGuard)
149
- helpers.push("function __s(a) { return a || []; }");
169
+ helpers.push("var __s = __s || function(a) { return a || []; };");
150
170
  if (this._usesEqual)
151
171
  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;}',
172
+ 'var __equal = __equal || 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;};',
173
+ );
174
+ if (this._usesCmul)
175
+ helpers.push(
176
+ "var __cmul = __cmul || function(a, b) { return { re: a.re * b.re - a.im * b.im, im: a.re * b.im + a.im * b.re }; };",
177
+ );
178
+ if (this._usesCdiv)
179
+ helpers.push(
180
+ "var __cdiv = __cdiv || function(a, b) { const d = b.re * b.re + b.im * b.im; return { re: (a.re * b.re + a.im * b.im) / d, im: (a.im * b.re - a.re * b.im) / d }; };",
153
181
  );
154
182
  if (this._usesSprintf)
155
183
  helpers.push(
156
184
  [
157
- "function __sprintf(f,...a){let i=0;return f.replace(/%([#+\\- 0]*)([0-9]*)\\.?([0-9]*)[sdvftxXqobeEgG%]/g,(m)=>{",
185
+ "var __sprintf = __sprintf || function(f,...a){let i=0;return f.replace(/%([#+\\- 0]*)([0-9]*)\\.?([0-9]*)[sdvftxXqobeEgGw%]/g,(m)=>{",
158
186
  "if(m==='%%')return'%';const fl=m.slice(1,-1),verb=m.slice(-1),v=a[i++];",
159
187
  "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
188
  "const [,flags,width,prec]=m.match(/^%([#+\\- 0]*)([0-9]*)\\.?([0-9]*)/)||[];",
@@ -162,7 +190,7 @@ export class CodeGen {
162
190
  "switch(verb){",
163
191
  "case's':return pad(String(v==null?'<nil>':v),width,false);",
164
192
  "case'd':return pad(String(Math.trunc(Number(v))),width,zero);",
165
- "case'v':return pad(String(v==null?'<nil>':v),width,false);",
193
+ "case'v':{if(typeof v==='object'&&v!==null&&'re' in v&&'im' in v){const sign=v.im>=0?'+':'';return pad('('+v.re+sign+v.im+'i)',width,false);}return pad(String(v==null?'<nil>':v),width,false);}",
166
194
  "case'f':{const n=Number(v),p=prec!==''?parseInt(prec):6;return pad(n.toFixed(p),width,zero);}",
167
195
  "case't':return pad(String(!!v),width,false);",
168
196
  "case'x':return pad((Number(v)>>>0).toString(16),width,zero);",
@@ -172,10 +200,19 @@ export class CodeGen {
172
200
  "case'q':return pad('\"'+String(v==null?'':v).replace(/\\\\/g,'\\\\\\\\').replace(/\"/g,'\\\\\"')+'\"',width,false);",
173
201
  "case'e':case'E':{const n=Number(v),p=prec!==''?parseInt(prec):6;return pad(n.toExponential(p),width,zero);}",
174
202
  "case'g':case'G':{const n=Number(v);return pad(prec!==''?n.toPrecision(parseInt(prec)):String(n),width,zero);}",
203
+ "case'w':return pad(String(v==null?'<nil>':typeof v==='object'&&v.Error?v.Error():v),width,false);",
175
204
  "default:return m;}});",
176
- "}",
205
+ "};",
177
206
  ].join(""),
178
207
  );
208
+ if (this._usesError)
209
+ helpers.push(
210
+ "var __error = __error || function(msg, cause) { return { Error() { return msg; }, toString() { return msg; }, _msg: msg, _cause: cause ?? null }; };",
211
+ );
212
+ if (this._usesErrorIs)
213
+ helpers.push(
214
+ 'var __errorIs = __errorIs || function(err, target) { while (err !== null && err !== undefined) { if (err === target) return true; if (typeof err === "object" && typeof target === "object" && err._msg !== undefined && target._msg !== undefined && err._msg === target._msg) return true; err = err?._cause ?? null; } return false; };',
215
+ );
179
216
 
180
217
  if (helpers.length > 0) this.out.unshift(...helpers, "");
181
218
 
@@ -185,16 +222,22 @@ export class CodeGen {
185
222
  }
186
223
 
187
224
  // Generate a single bundle from multiple programs (same-package multi-file).
188
- // Merges all decls and generates as if it were one program.
225
+ // Annotates each decl with its source file index before merging.
189
226
  generateAll(programs) {
227
+ for (let i = 0; i < programs.length; i++) {
228
+ for (const decl of programs[i].decls) {
229
+ decl._srcFileIdx = i;
230
+ }
231
+ }
190
232
  const merged = { decls: programs.flatMap((p) => p.decls) };
191
233
  return this.generate(merged);
192
234
  }
193
235
 
194
236
  // Returns a source map JSON string for the last generate() call.
195
- // sourceName: the .go filename to reference in the map.
196
- getSourceMap(sourceName) {
197
- return buildSourceMap(sourceName, this._srcMappings);
237
+ // sources: string[] of source filenames (relative to the output file).
238
+ getSourceMap(sources) {
239
+ const srcArray = Array.isArray(sources) ? sources : [sources];
240
+ return buildSourceMap(srcArray, this._srcMappings);
198
241
  }
199
242
 
200
243
  // ── Type declarations ────────────────────────────────────────
@@ -278,12 +321,16 @@ export class CodeGen {
278
321
  const params = decl.params.map((p) => p.name).join(", ");
279
322
  const asyncPrefix = decl.async ? "async " : "";
280
323
  this.line(`${asyncPrefix}${decl.name}(${params}) {`);
324
+ const prevBoxed = this._boxedVars;
325
+ this._boxedVars = new Set();
326
+ this._scanAddressTaken(decl.body);
281
327
  this.indented(() => {
282
328
  if (decl.recvName && decl.recvName !== "_") {
283
329
  this.line(`const ${decl.recvName} = this;`);
284
330
  }
285
331
  this._withNamedReturns(decl, () => this._genBody(decl.body));
286
332
  });
333
+ this._boxedVars = prevBoxed;
287
334
  this.line("}");
288
335
  }
289
336
 
@@ -302,9 +349,13 @@ export class CodeGen {
302
349
  `${asyncPrefix}function ${name}(${params}) {`,
303
350
  srcLine ? srcLine - 1 : null,
304
351
  );
352
+ const prevBoxed = this._boxedVars;
353
+ this._boxedVars = new Set();
354
+ this._scanAddressTaken(decl.body);
305
355
  this.indented(() =>
306
356
  this._withNamedReturns(decl, () => this._genBody(decl.body)),
307
357
  );
358
+ this._boxedVars = prevBoxed;
308
359
  this.line("}");
309
360
  }
310
361
 
@@ -357,12 +408,61 @@ export class CodeGen {
357
408
  }
358
409
  }
359
410
 
411
+ // Scan AST node for _addressTaken idents on scalars and populate _boxedVars.
412
+ _scanAddressTaken(node) {
413
+ if (!node || typeof node !== "object") return;
414
+ if (Array.isArray(node)) {
415
+ for (const child of node) this._scanAddressTaken(child);
416
+ return;
417
+ }
418
+ // &x — the operand ident will have _addressTaken set by typechecker
419
+ if (node.kind === "Ident" && node._addressTaken) {
420
+ // Check if the type is a scalar (needs boxing) vs reference type (no boxing)
421
+ const t = node._type;
422
+ if (t && !this._isReferenceType(t)) {
423
+ this._boxedVars.add(node.name);
424
+ }
425
+ }
426
+ // Recurse into FuncLit too — closures may take address of outer vars
427
+ for (const key of Object.keys(node)) {
428
+ if (key.startsWith("_")) continue;
429
+ this._scanAddressTaken(node[key]);
430
+ }
431
+ }
432
+
433
+ _isReferenceType(t) {
434
+ if (!t) return false;
435
+ const base = t.kind === "named" ? t.underlying : t;
436
+ return (
437
+ base?.kind === "struct" ||
438
+ base?.kind === "slice" ||
439
+ base?.kind === "map" ||
440
+ base?.kind === "func" ||
441
+ base?.kind === "interface"
442
+ );
443
+ }
444
+
360
445
  // ── Variable / const declarations ────────────────────────────
361
446
 
362
447
  genVarDecl(decl) {
363
448
  for (const spec of decl.decls) {
364
449
  if (spec.value) {
365
- const vals = spec.value.map((v) => this.genExpr(v));
450
+ const vals = spec.value.map((v) => {
451
+ const js = this.genExpr(v);
452
+ // Wrap numeric values assigned to complex-typed vars
453
+ if (
454
+ spec.type?.name === "complex128" ||
455
+ spec.type?.name === "complex64"
456
+ ) {
457
+ if (
458
+ !isComplex(v._type) &&
459
+ (isNumeric(v._type) || v._type?.kind === "untyped")
460
+ ) {
461
+ return `{ re: ${js}, im: 0 }`;
462
+ }
463
+ }
464
+ return js;
465
+ });
366
466
  if (spec.names.length === 1) {
367
467
  this.line(`let ${spec.names[0]} = ${vals[0]};`);
368
468
  } else {
package/src/compiler.js CHANGED
@@ -16,7 +16,7 @@
16
16
  // de-qualifies it because the dependency is inlined.
17
17
 
18
18
  import { readdirSync, readFileSync } from "node:fs";
19
- import { basename, dirname, join, resolve } from "node:path";
19
+ import { basename, dirname, join, relative, resolve } from "node:path";
20
20
  import { CodeGen } from "./codegen.js";
21
21
  import { parseDts } from "./dts-parser.js";
22
22
  import { Lexer } from "./lexer.js";
@@ -165,8 +165,15 @@ export function compileFiles(files, options = {}) {
165
165
  const codegen = new CodeGen(checker, jsImports, bundledPackages);
166
166
  const mainJs = codegen.generateAll(programs);
167
167
 
168
- const js =
169
- preambles.length > 0 ? `${preambles.join("\n")}\n${mainJs}` : mainJs;
168
+ let js = preambles.length > 0 ? `${preambles.join("\n")}\n${mainJs}` : mainJs;
169
+
170
+ if (options.sourceMap) {
171
+ const outputDir = options.outputDir ?? fromDir;
172
+ const sourceFiles = files.map((f) => relative(outputDir, f));
173
+ const map = codegen.getSourceMap(sourceFiles);
174
+ const b64 = Buffer.from(map).toString("base64");
175
+ js += `\n//# sourceMappingURL=data:application/json;base64,${b64}`;
176
+ }
170
177
 
171
178
  return {
172
179
  pkgName,
@@ -0,0 +1,83 @@
1
+ // Minimal dev server for gofront --serve watch mode.
2
+ // Serves static files and pushes a reload event via SSE after each build.
3
+
4
+ import { existsSync, readFileSync } from "node:fs";
5
+ import { createServer } from "node:http";
6
+ import { extname, join } from "node:path";
7
+
8
+ const MIME = {
9
+ ".html": "text/html; charset=utf-8",
10
+ ".js": "application/javascript; charset=utf-8",
11
+ ".css": "text/css; charset=utf-8",
12
+ ".json": "application/json; charset=utf-8",
13
+ ".svg": "image/svg+xml",
14
+ ".png": "image/png",
15
+ ".jpg": "image/jpeg",
16
+ ".ico": "image/x-icon",
17
+ ".woff2": "font/woff2",
18
+ ".woff": "font/woff",
19
+ };
20
+
21
+ // Injected at the bottom of compiled JS in serve mode.
22
+ export const liveReloadClient = `(function(){var es=new EventSource('/_gofront/events');es.addEventListener('reload',function(){location.reload();});})();`;
23
+
24
+ export function createDevServer(serveDir, port) {
25
+ const clients = new Set();
26
+
27
+ const server = createServer((req, res) => {
28
+ // SSE endpoint — browser connects here to receive reload events
29
+ if (req.url === "/_gofront/events") {
30
+ res.writeHead(200, {
31
+ "Content-Type": "text/event-stream",
32
+ "Cache-Control": "no-cache",
33
+ Connection: "keep-alive",
34
+ });
35
+ res.write(": connected\n\n");
36
+ clients.add(res);
37
+ req.on("close", () => clients.delete(res));
38
+ return;
39
+ }
40
+
41
+ let urlPath = req.url.split("?")[0];
42
+ if (urlPath === "/" || urlPath === "") urlPath = "/index.html";
43
+
44
+ const filePath = join(serveDir, urlPath);
45
+ if (!existsSync(filePath)) {
46
+ res.writeHead(404, { "Content-Type": "text/plain" });
47
+ res.end("Not found");
48
+ return;
49
+ }
50
+
51
+ try {
52
+ const data = readFileSync(filePath);
53
+ const mime = MIME[extname(filePath)] ?? "application/octet-stream";
54
+ res.writeHead(200, { "Content-Type": mime });
55
+ res.end(data);
56
+ } catch {
57
+ res.writeHead(500, { "Content-Type": "text/plain" });
58
+ res.end("Server error");
59
+ }
60
+ });
61
+
62
+ server.on("error", (err) => {
63
+ if (err.code === "EADDRINUSE") {
64
+ console.error(
65
+ `gofront: port ${port} already in use — try --port <number>`,
66
+ );
67
+ } else {
68
+ console.error(`gofront: dev server error: ${err.message}`);
69
+ }
70
+ process.exit(1);
71
+ });
72
+ server.listen(port, () => {
73
+ console.error(`gofront: dev server → http://localhost:${port}`);
74
+ });
75
+
76
+ function notify() {
77
+ for (const client of clients) {
78
+ client.write("event: reload\ndata: {}\n\n");
79
+ }
80
+ }
81
+
82
+ return { notify };
83
+ }
package/src/dts-parser.js CHANGED
@@ -74,7 +74,7 @@ export class DtsParser {
74
74
  this.pos += 2;
75
75
  while (!this.eof() && !(this.ch() === "*" && this.ch1() === "/"))
76
76
  this.pos++;
77
- this.pos += 2;
77
+ if (!this.eof()) this.pos += 2;
78
78
  continue;
79
79
  }
80
80
  break;