gofront 0.0.3 → 0.0.5

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.
@@ -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,14 +83,109 @@ 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
+ }
118
+ // comma-ok map index: v, ok = m["key"]
119
+ if (stmt._commaOkMap) {
120
+ const rhsNode = stmt.rhs[0];
121
+ const mapExpr = this.genExpr(rhsNode.expr);
122
+ const keyExpr = this.genExpr(rhsNode.index);
123
+ const [vName, okName] = stmt.lhs.map(
124
+ (e) => e.name ?? this.genExpr(e),
125
+ );
126
+ if (vName !== "_") {
127
+ const zero = rhsNode._mapValueType
128
+ ? this.zeroValueForType(rhsNode._mapValueType)
129
+ : "undefined";
130
+ this.line(
131
+ `${vName} = (${keyExpr}) in ${mapExpr} ? ${mapExpr}[${keyExpr}] : ${zero};`,
132
+ );
133
+ }
134
+ if (okName !== "_")
135
+ this.line(`${okName} = (${keyExpr}) in ${mapExpr};`);
136
+ break;
137
+ }
138
+ // comma-ok type assertion: v, ok = x.(T)
139
+ if (
140
+ stmt.lhs.length === 2 &&
141
+ stmt.rhs.length === 1 &&
142
+ stmt.rhs[0]._commaOk
143
+ ) {
144
+ const val = this.genExpr(stmt.rhs[0]);
145
+ const [vName, okName] = stmt.lhs.map(
146
+ (e) => e.name ?? this.genExpr(e),
147
+ );
148
+ const tmp = "__ta";
149
+ this.line(`let ${tmp} = ${val};`);
150
+ if (vName !== "_") this.line(`${vName} = ${tmp}[0];`);
151
+ if (okName !== "_") this.line(`${okName} = ${tmp}[1];`);
152
+ break;
153
+ }
78
154
  const rhs = stmt.rhs.map((e) => this.genExpr(e));
79
155
  for (const e of stmt.lhs) if (e.kind === "IndexExpr") e._lvalue = true;
80
- const lhs = stmt.lhs.map((e) => this.genExpr(e));
81
- if (lhs.length === 1) {
82
- this.line(`${lhs[0]} ${stmt.op} ${rhs[0]};`);
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
+ });
165
+ // Filter out blank identifier assignments entirely
166
+ const pairs = lhs.map((l, i) => ({ l, r: rhs[i] ?? rhs[0] }));
167
+ const active = pairs.filter((p) => p.l !== "_");
168
+ if (active.length === 0) {
169
+ // All _ — still need to evaluate rhs for side effects
170
+ if (rhs.length > 0) this.line(`${rhs[0]};`);
171
+ } else if (lhs.length === 1) {
172
+ this.line(`${active[0].l} ${stmt.op} ${active[0].r};`);
83
173
  } else {
174
+ // multi-assign: emit only non-blank targets
84
175
  const rhsStr = rhs.length === 1 ? rhs[0] : `[${rhs.join(", ")}]`;
85
- this.line(`[${lhs.join(", ")}] = ${rhsStr};`);
176
+ if (active.length === pairs.length) {
177
+ this.line(`[${lhs.join(", ")}] = ${rhsStr};`);
178
+ } else {
179
+ // Some blanks — use temp and assign only non-blank
180
+ const tmp = "__t";
181
+ this.line(`let ${tmp} = ${rhsStr};`);
182
+ for (let i = 0; i < lhs.length; i++) {
183
+ if (lhs[i] !== "_") {
184
+ const src = rhs.length === 1 ? `${tmp}[${i}]` : rhs[i];
185
+ this.line(`${lhs[i]} ${stmt.op} ${src};`);
186
+ }
187
+ }
188
+ }
86
189
  }
87
190
  break;
88
191
  }
@@ -96,6 +199,21 @@ export const statementGenMethods = {
96
199
  break;
97
200
 
98
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
+ }
99
217
  if (stmt.values.length === 0) {
100
218
  // bare return — emit named return vars if present
101
219
  if (this.namedReturnVars?.length > 0) {
@@ -155,6 +273,16 @@ export const statementGenMethods = {
155
273
  break;
156
274
 
157
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
+ }
158
286
  if (stmt.keyword !== "fallthrough")
159
287
  this.line(
160
288
  stmt.label ? `${stmt.keyword} ${stmt.label};` : `${stmt.keyword};`,
@@ -200,14 +328,22 @@ export const statementGenMethods = {
200
328
  },
201
329
 
202
330
  genFor(stmt) {
203
- // Detect range: init is DefineStmt with rhs[0] being a RangeExpr
331
+ // Detect range: init is DefineStmt/AssignStmt with rhs[0] being a RangeExpr
204
332
  if (this.isRangeFor(stmt)) {
205
- this.genRangeFor(stmt);
333
+ if (stmt.init.rhs[0]._isIterator) {
334
+ this.genIteratorFor(stmt);
335
+ } else {
336
+ this.genRangeFor(stmt);
337
+ }
206
338
  return;
207
339
  }
208
340
 
209
- // for range N { } — no variable, just repeat N times
341
+ // for range expr — no variable; could be int range or 0-param iterator
210
342
  if (stmt.cond?.kind === "RangeExpr") {
343
+ if (stmt.cond._isIterator) {
344
+ this.genIteratorForCond(stmt);
345
+ return;
346
+ }
211
347
  const iteree = this.genExpr(stmt.cond.expr);
212
348
  this.line(`for (let _$ = 0; _$ < ${iteree}; _$++) {`);
213
349
  this.indented(() => this.genBlock(stmt.body));
@@ -275,11 +411,14 @@ export const statementGenMethods = {
275
411
  (iterType?.kind === "basic" && iterType.name === "string") ||
276
412
  (iterType?.kind === "untyped" && iterType.base === "string")
277
413
  ) {
278
- // string: for i, ch := range s → for (const [i, ch] of s.split('').entries())
279
- iterExpr =
280
- lhs.length === 1
281
- ? `Array.from(${iteree}).keys()`
282
- : `Array.from(${iteree}).entries()`;
414
+ // string: for i, r := range s → rune (code point), not JS character
415
+ // Go spec: iterating a string yields (byte-index, rune) pairs
416
+ if (lhs.length === 1) {
417
+ iterExpr = `Array.from(${iteree}).keys()`;
418
+ } else {
419
+ // Wrap code points so the value is an integer (rune), not a JS string
420
+ iterExpr = `Array.from(${iteree}, (__c, __i) => [__i, __c.codePointAt(0)])`;
421
+ }
283
422
  } else {
284
423
  // slice/array: for i, v := range arr → for (const [i, v] of __s(arr).entries())
285
424
  // null-guard handles nil slices (zero value) gracefully.
@@ -303,6 +442,112 @@ export const statementGenMethods = {
303
442
  this.line("}");
304
443
  },
305
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
+
306
551
  // Inline a statement as a for-init/post string (no semicolon)
307
552
  stmtInline(stmt) {
308
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
@@ -38,11 +39,23 @@ export class CodeGen {
38
39
  this.bundledPackages = bundledPackages;
39
40
  this.namedReturnVars = null; // names of current function's named return vars
40
41
  this._srcMappings = []; // { genLine, srcLine } for source map
42
+ this._boxedVars = new Set(); // address-taken scalar variables that need boxing
41
43
  // Runtime helper usage tracking — only emit helpers that are actually used
42
44
  this._usesLen = false;
43
45
  this._usesAppend = false;
44
46
  this._usesSliceGuard = false;
45
47
  this._usesSprintf = false;
48
+ this._usesEqual = false;
49
+ this._usesCmul = false;
50
+ this._usesCdiv = false;
51
+ this._usesError = false;
52
+ this._usesErrorIs = false;
53
+ // Iterator (range-over-func) context
54
+ this._inIteratorBody = false;
55
+ this._iterDepth = 0;
56
+ this._iterBreakFlag = null;
57
+ this._iterReturnFlag = null;
58
+ this._iterReturnVar = null;
46
59
  }
47
60
 
48
61
  // ── Output helpers ───────────────────────────────────────────
@@ -120,10 +133,7 @@ export class CodeGen {
120
133
  if (d.name === "init") {
121
134
  const renamed = initCount === 0 ? "init" : `init$${initCount}`;
122
135
  initNames.push(renamed);
123
- const saved = d.name;
124
- d.name = renamed;
125
- this.genFuncDecl(d);
126
- d.name = saved;
136
+ this.genFuncDecl(d, renamed);
127
137
  initCount++;
128
138
  } else {
129
139
  this.genFuncDecl(d);
@@ -149,10 +159,22 @@ export class CodeGen {
149
159
  );
150
160
  if (this._usesSliceGuard)
151
161
  helpers.push("function __s(a) { return a || []; }");
162
+ if (this._usesEqual)
163
+ helpers.push(
164
+ '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;}',
165
+ );
166
+ if (this._usesCmul)
167
+ helpers.push(
168
+ "function __cmul(a, b) { return { re: a.re * b.re - a.im * b.im, im: a.re * b.im + a.im * b.re }; }",
169
+ );
170
+ if (this._usesCdiv)
171
+ helpers.push(
172
+ "function __cdiv(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 }; }",
173
+ );
152
174
  if (this._usesSprintf)
153
175
  helpers.push(
154
176
  [
155
- "function __sprintf(f,...a){let i=0;return f.replace(/%([#+\\- 0]*)([0-9]*)\\.?([0-9]*)[sdvftxXqobeEgG%]/g,(m)=>{",
177
+ "function __sprintf(f,...a){let i=0;return f.replace(/%([#+\\- 0]*)([0-9]*)\\.?([0-9]*)[sdvftxXqobeEgGw%]/g,(m)=>{",
156
178
  "if(m==='%%')return'%';const fl=m.slice(1,-1),verb=m.slice(-1),v=a[i++];",
157
179
  "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
180
  "const [,flags,width,prec]=m.match(/^%([#+\\- 0]*)([0-9]*)\\.?([0-9]*)/)||[];",
@@ -160,7 +182,7 @@ export class CodeGen {
160
182
  "switch(verb){",
161
183
  "case's':return pad(String(v==null?'<nil>':v),width,false);",
162
184
  "case'd':return pad(String(Math.trunc(Number(v))),width,zero);",
163
- "case'v':return pad(String(v==null?'<nil>':v),width,false);",
185
+ "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);}",
164
186
  "case'f':{const n=Number(v),p=prec!==''?parseInt(prec):6;return pad(n.toFixed(p),width,zero);}",
165
187
  "case't':return pad(String(!!v),width,false);",
166
188
  "case'x':return pad((Number(v)>>>0).toString(16),width,zero);",
@@ -170,10 +192,19 @@ export class CodeGen {
170
192
  "case'q':return pad('\"'+String(v==null?'':v).replace(/\\\\/g,'\\\\\\\\').replace(/\"/g,'\\\\\"')+'\"',width,false);",
171
193
  "case'e':case'E':{const n=Number(v),p=prec!==''?parseInt(prec):6;return pad(n.toExponential(p),width,zero);}",
172
194
  "case'g':case'G':{const n=Number(v);return pad(prec!==''?n.toPrecision(parseInt(prec)):String(n),width,zero);}",
195
+ "case'w':return pad(String(v==null?'<nil>':typeof v==='object'&&v.Error?v.Error():v),width,false);",
173
196
  "default:return m;}});",
174
197
  "}",
175
198
  ].join(""),
176
199
  );
200
+ if (this._usesError)
201
+ helpers.push(
202
+ "function __error(msg, cause) { return { Error() { return msg; }, toString() { return msg; }, _msg: msg, _cause: cause ?? null }; }",
203
+ );
204
+ if (this._usesErrorIs)
205
+ helpers.push(
206
+ 'function __errorIs(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; }',
207
+ );
177
208
 
178
209
  if (helpers.length > 0) this.out.unshift(...helpers, "");
179
210
 
@@ -276,18 +307,23 @@ export class CodeGen {
276
307
  const params = decl.params.map((p) => p.name).join(", ");
277
308
  const asyncPrefix = decl.async ? "async " : "";
278
309
  this.line(`${asyncPrefix}${decl.name}(${params}) {`);
310
+ const prevBoxed = this._boxedVars;
311
+ this._boxedVars = new Set();
312
+ this._scanAddressTaken(decl.body);
279
313
  this.indented(() => {
280
314
  if (decl.recvName && decl.recvName !== "_") {
281
315
  this.line(`const ${decl.recvName} = this;`);
282
316
  }
283
317
  this._withNamedReturns(decl, () => this._genBody(decl.body));
284
318
  });
319
+ this._boxedVars = prevBoxed;
285
320
  this.line("}");
286
321
  }
287
322
 
288
323
  // ── Function declarations ────────────────────────────────────
289
324
 
290
- genFuncDecl(decl) {
325
+ genFuncDecl(decl, nameOverride) {
326
+ const name = nameOverride ?? decl.name;
291
327
  const params = decl.params
292
328
  .map((p, i) =>
293
329
  p.variadic && i === decl.params.length - 1 ? `...${p.name}` : p.name,
@@ -296,12 +332,16 @@ export class CodeGen {
296
332
  const asyncPrefix = decl.async ? "async " : "";
297
333
  const srcLine = decl._line ?? null;
298
334
  this.line(
299
- `${asyncPrefix}function ${decl.name}(${params}) {`,
335
+ `${asyncPrefix}function ${name}(${params}) {`,
300
336
  srcLine ? srcLine - 1 : null,
301
337
  );
338
+ const prevBoxed = this._boxedVars;
339
+ this._boxedVars = new Set();
340
+ this._scanAddressTaken(decl.body);
302
341
  this.indented(() =>
303
342
  this._withNamedReturns(decl, () => this._genBody(decl.body)),
304
343
  );
344
+ this._boxedVars = prevBoxed;
305
345
  this.line("}");
306
346
  }
307
347
 
@@ -354,12 +394,61 @@ export class CodeGen {
354
394
  }
355
395
  }
356
396
 
397
+ // Scan AST node for _addressTaken idents on scalars and populate _boxedVars.
398
+ _scanAddressTaken(node) {
399
+ if (!node || typeof node !== "object") return;
400
+ if (Array.isArray(node)) {
401
+ for (const child of node) this._scanAddressTaken(child);
402
+ return;
403
+ }
404
+ // &x — the operand ident will have _addressTaken set by typechecker
405
+ if (node.kind === "Ident" && node._addressTaken) {
406
+ // Check if the type is a scalar (needs boxing) vs reference type (no boxing)
407
+ const t = node._type;
408
+ if (t && !this._isReferenceType(t)) {
409
+ this._boxedVars.add(node.name);
410
+ }
411
+ }
412
+ // Recurse into FuncLit too — closures may take address of outer vars
413
+ for (const key of Object.keys(node)) {
414
+ if (key.startsWith("_")) continue;
415
+ this._scanAddressTaken(node[key]);
416
+ }
417
+ }
418
+
419
+ _isReferenceType(t) {
420
+ if (!t) return false;
421
+ const base = t.kind === "named" ? t.underlying : t;
422
+ return (
423
+ base?.kind === "struct" ||
424
+ base?.kind === "slice" ||
425
+ base?.kind === "map" ||
426
+ base?.kind === "func" ||
427
+ base?.kind === "interface"
428
+ );
429
+ }
430
+
357
431
  // ── Variable / const declarations ────────────────────────────
358
432
 
359
433
  genVarDecl(decl) {
360
434
  for (const spec of decl.decls) {
361
435
  if (spec.value) {
362
- const vals = spec.value.map((v) => this.genExpr(v));
436
+ const vals = spec.value.map((v) => {
437
+ const js = this.genExpr(v);
438
+ // Wrap numeric values assigned to complex-typed vars
439
+ if (
440
+ spec.type?.name === "complex128" ||
441
+ spec.type?.name === "complex64"
442
+ ) {
443
+ if (
444
+ !isComplex(v._type) &&
445
+ (isNumeric(v._type) || v._type?.kind === "untyped")
446
+ ) {
447
+ return `{ re: ${js}, im: 0 }`;
448
+ }
449
+ }
450
+ return js;
451
+ });
363
452
  if (spec.names.length === 1) {
364
453
  this.line(`let ${spec.names[0]} = ${vals[0]};`);
365
454
  } else {
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;
package/src/index.js CHANGED
@@ -27,11 +27,11 @@ const _require = createRequire(import.meta.url);
27
27
  const { version } = _require("../package.json");
28
28
 
29
29
  import { basename, dirname, join, resolve } from "node:path";
30
- import { minify } from "terser";
31
30
  import { CodeGen } from "./codegen.js";
32
31
  import { compileDir } from "./compiler.js";
33
32
  import { parseDts } from "./dts-parser.js";
34
33
  import { Lexer } from "./lexer.js";
34
+ import { minify } from "./minifier.js";
35
35
  import { Parser } from "./parser.js";
36
36
  import { isLocalPath, resolveAll, resolveGwDir } from "./resolver.js";
37
37
  import { TypeChecker } from "./typechecker.js";
@@ -56,7 +56,8 @@ Usage:
56
56
  gofront <input> --check Type-check only
57
57
  gofront <input> --watch Watch for changes and recompile
58
58
  gofront <input> --source-map Append inline source map to output
59
- gofront <input> --minify Minify output with terser
59
+ gofront <input> --minify Minify output
60
+ gofront <input> --minify --mangle Minify and mangle identifiers
60
61
  gofront <file.go> --ast Dump AST (debug)
61
62
  gofront <file.go> --tokens Dump tokens (debug)
62
63
  gofront init [dir] Scaffold a new GoFront project
@@ -121,6 +122,7 @@ const dumpTokens = args.includes("--tokens");
121
122
  const sourceMap = args.includes("--source-map");
122
123
  const watchMode = args.includes("--watch");
123
124
  const minifyOutput = args.includes("--minify");
125
+ const mangleOutput = args.includes("--mangle");
124
126
 
125
127
  // ── Determine input mode ─────────────────────────────────────
126
128
 
@@ -232,14 +234,13 @@ function runCompile() {
232
234
 
233
235
  // ── Minify helper ────────────────────────────────────────────
234
236
 
235
- async function maybeMinify(js) {
237
+ function maybeMinify(js) {
236
238
  if (!minifyOutput) return js;
237
- const result = await minify(js, {
238
- module: true,
239
- compress: true,
240
- mangle: true,
241
- });
242
- return result.code;
239
+ if (sourceMap) {
240
+ console.error("gofront: --source-map and --minify cannot be used together");
241
+ process.exit(1);
242
+ }
243
+ return minify(js, { mangle: mangleOutput });
243
244
  }
244
245
 
245
246
  // ── Single-shot mode ─────────────────────────────────────────
@@ -262,7 +263,7 @@ if (!watchMode) {
262
263
 
263
264
  let js;
264
265
  try {
265
- js = await maybeMinify(result.js);
266
+ js = maybeMinify(result.js);
266
267
  } catch (e) {
267
268
  console.error(`gofront: minify failed: ${e.message}`);
268
269
  process.exit(1);
@@ -290,11 +291,11 @@ function timestamp() {
290
291
  return new Date().toLocaleTimeString();
291
292
  }
292
293
 
293
- async function buildOnce() {
294
+ function buildOnce() {
294
295
  try {
295
296
  const startMs = performance.now();
296
297
  const result = runCompile();
297
- const js = await maybeMinify(result.js);
298
+ const js = maybeMinify(result.js);
298
299
  const elapsedMs = (performance.now() - startMs).toFixed(0);
299
300
  if (outputFile) {
300
301
  writeFileSync(outputFile, `${js}\n`);
package/src/lexer.js CHANGED
@@ -8,6 +8,8 @@ export const T = {
8
8
  STRING: "STRING",
9
9
  IDENT: "IDENT",
10
10
 
11
+ IMAG: "IMAG",
12
+
11
13
  // Keywords
12
14
  FUNC: "func",
13
15
  VAR: "var",
@@ -72,6 +74,7 @@ export const T = {
72
74
  PIPE: "|",
73
75
  CARET: "^",
74
76
  AND_NOT: "&^",
77
+ TILDE: "~",
75
78
  LSHIFT: "<<",
76
79
  RSHIFT: ">>",
77
80
  ELLIPSIS: "...",
@@ -97,6 +100,7 @@ const SEMI_TRIGGERS = new Set([
97
100
  T.IDENT,
98
101
  T.INT,
99
102
  T.FLOAT,
103
+ T.IMAG,
100
104
  T.STRING,
101
105
  T.RPAREN,
102
106
  T.RBRACE,
@@ -297,10 +301,20 @@ export class Lexer {
297
301
  }
298
302
 
299
303
  readRawString() {
304
+ const l = this.line,
305
+ c = this.col;
300
306
  this.advance(); // opening `
301
307
  let s = "";
302
308
  while (this.pos < this.src.length && this.peek() !== "`")
303
309
  s += this.advance();
310
+ if (this.pos >= this.src.length)
311
+ throw new LexError(
312
+ "Unterminated raw string",
313
+ l,
314
+ c,
315
+ this.filename,
316
+ this.src,
317
+ );
304
318
  this.advance(); // closing `
305
319
  return s;
306
320
  }
@@ -501,7 +515,13 @@ export class Lexer {
501
515
  // Numbers
502
516
  if (/[0-9]/.test(ch)) {
503
517
  const { n, isFloat } = this.readNumber();
504
- this.push(isFloat ? T.FLOAT : T.INT, n, l, c);
518
+ if (this.src[this.pos] === "i") {
519
+ this.pos++;
520
+ this.col++;
521
+ this.push(T.IMAG, n, l, c);
522
+ } else {
523
+ this.push(isFloat ? T.FLOAT : T.INT, n, l, c);
524
+ }
505
525
  continue;
506
526
  }
507
527
 
@@ -574,6 +594,9 @@ export class Lexer {
574
594
  if (this.match("=")) this.push(T.CARET_ASSIGN, "^=", l, c);
575
595
  else this.push(T.CARET, "^", l, c);
576
596
  break;
597
+ case "~":
598
+ this.push(T.TILDE, "~", l, c);
599
+ break;
577
600
  case ":":
578
601
  if (this.match("=")) this.push(T.DEFINE, ":=", l, c);
579
602
  else this.push(T.COLON, ":", l, c);