gofront 0.0.4 → 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.
- package/README.md +104 -126
- package/package.json +2 -3
- package/src/codegen/expressions.js +448 -256
- package/src/codegen/statements.js +193 -6
- package/src/codegen.js +89 -3
- package/src/dts-parser.js +1 -1
- package/src/index.js +13 -12
- package/src/lexer.js +24 -1
- package/src/minifier.js +698 -0
- package/src/parser/expressions.js +115 -15
- package/src/parser/statements.js +4 -4
- package/src/parser/types.js +108 -3
- package/src/parser.js +57 -4
- package/src/typechecker/expressions.js +279 -10
- package/src/typechecker/statements.js +64 -3
- package/src/typechecker/types.js +96 -10
- package/src/typechecker.js +482 -16
|
@@ -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
|
-
|
|
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]} = ${
|
|
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) =>
|
|
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
|
-
|
|
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
|
|
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
|
|
@@ -38,12 +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;
|
|
46
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;
|
|
47
59
|
}
|
|
48
60
|
|
|
49
61
|
// ── Output helpers ───────────────────────────────────────────
|
|
@@ -151,10 +163,18 @@ export class CodeGen {
|
|
|
151
163
|
helpers.push(
|
|
152
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;}',
|
|
153
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
|
+
);
|
|
154
174
|
if (this._usesSprintf)
|
|
155
175
|
helpers.push(
|
|
156
176
|
[
|
|
157
|
-
"function __sprintf(f,...a){let i=0;return f.replace(/%([#+\\- 0]*)([0-9]*)\\.?([0-9]*)[
|
|
177
|
+
"function __sprintf(f,...a){let i=0;return f.replace(/%([#+\\- 0]*)([0-9]*)\\.?([0-9]*)[sdvftxXqobeEgGw%]/g,(m)=>{",
|
|
158
178
|
"if(m==='%%')return'%';const fl=m.slice(1,-1),verb=m.slice(-1),v=a[i++];",
|
|
159
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;};",
|
|
160
180
|
"const [,flags,width,prec]=m.match(/^%([#+\\- 0]*)([0-9]*)\\.?([0-9]*)/)||[];",
|
|
@@ -162,7 +182,7 @@ export class CodeGen {
|
|
|
162
182
|
"switch(verb){",
|
|
163
183
|
"case's':return pad(String(v==null?'<nil>':v),width,false);",
|
|
164
184
|
"case'd':return pad(String(Math.trunc(Number(v))),width,zero);",
|
|
165
|
-
"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);}",
|
|
166
186
|
"case'f':{const n=Number(v),p=prec!==''?parseInt(prec):6;return pad(n.toFixed(p),width,zero);}",
|
|
167
187
|
"case't':return pad(String(!!v),width,false);",
|
|
168
188
|
"case'x':return pad((Number(v)>>>0).toString(16),width,zero);",
|
|
@@ -172,10 +192,19 @@ export class CodeGen {
|
|
|
172
192
|
"case'q':return pad('\"'+String(v==null?'':v).replace(/\\\\/g,'\\\\\\\\').replace(/\"/g,'\\\\\"')+'\"',width,false);",
|
|
173
193
|
"case'e':case'E':{const n=Number(v),p=prec!==''?parseInt(prec):6;return pad(n.toExponential(p),width,zero);}",
|
|
174
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);",
|
|
175
196
|
"default:return m;}});",
|
|
176
197
|
"}",
|
|
177
198
|
].join(""),
|
|
178
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
|
+
);
|
|
179
208
|
|
|
180
209
|
if (helpers.length > 0) this.out.unshift(...helpers, "");
|
|
181
210
|
|
|
@@ -278,12 +307,16 @@ export class CodeGen {
|
|
|
278
307
|
const params = decl.params.map((p) => p.name).join(", ");
|
|
279
308
|
const asyncPrefix = decl.async ? "async " : "";
|
|
280
309
|
this.line(`${asyncPrefix}${decl.name}(${params}) {`);
|
|
310
|
+
const prevBoxed = this._boxedVars;
|
|
311
|
+
this._boxedVars = new Set();
|
|
312
|
+
this._scanAddressTaken(decl.body);
|
|
281
313
|
this.indented(() => {
|
|
282
314
|
if (decl.recvName && decl.recvName !== "_") {
|
|
283
315
|
this.line(`const ${decl.recvName} = this;`);
|
|
284
316
|
}
|
|
285
317
|
this._withNamedReturns(decl, () => this._genBody(decl.body));
|
|
286
318
|
});
|
|
319
|
+
this._boxedVars = prevBoxed;
|
|
287
320
|
this.line("}");
|
|
288
321
|
}
|
|
289
322
|
|
|
@@ -302,9 +335,13 @@ export class CodeGen {
|
|
|
302
335
|
`${asyncPrefix}function ${name}(${params}) {`,
|
|
303
336
|
srcLine ? srcLine - 1 : null,
|
|
304
337
|
);
|
|
338
|
+
const prevBoxed = this._boxedVars;
|
|
339
|
+
this._boxedVars = new Set();
|
|
340
|
+
this._scanAddressTaken(decl.body);
|
|
305
341
|
this.indented(() =>
|
|
306
342
|
this._withNamedReturns(decl, () => this._genBody(decl.body)),
|
|
307
343
|
);
|
|
344
|
+
this._boxedVars = prevBoxed;
|
|
308
345
|
this.line("}");
|
|
309
346
|
}
|
|
310
347
|
|
|
@@ -357,12 +394,61 @@ export class CodeGen {
|
|
|
357
394
|
}
|
|
358
395
|
}
|
|
359
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
|
+
|
|
360
431
|
// ── Variable / const declarations ────────────────────────────
|
|
361
432
|
|
|
362
433
|
genVarDecl(decl) {
|
|
363
434
|
for (const spec of decl.decls) {
|
|
364
435
|
if (spec.value) {
|
|
365
|
-
const vals = spec.value.map((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
|
+
});
|
|
366
452
|
if (spec.names.length === 1) {
|
|
367
453
|
this.line(`let ${spec.names[0]} = ${vals[0]};`);
|
|
368
454
|
} else {
|
package/src/dts-parser.js
CHANGED
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
|
|
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
|
-
|
|
237
|
+
function maybeMinify(js) {
|
|
236
238
|
if (!minifyOutput) return js;
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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 =
|
|
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
|
-
|
|
294
|
+
function buildOnce() {
|
|
294
295
|
try {
|
|
295
296
|
const startMs = performance.now();
|
|
296
297
|
const result = runCompile();
|
|
297
|
-
const 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.
|
|
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);
|