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.
- package/README.md +110 -135
- package/package.json +2 -3
- package/src/codegen/expressions.js +529 -234
- package/src/codegen/statements.js +259 -14
- package/src/codegen.js +98 -9
- 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 +93 -18
- package/src/typechecker/expressions.js +370 -13
- package/src/typechecker/statements.js +103 -5
- package/src/typechecker/types.js +96 -28
- package/src/typechecker.js +658 -22
|
@@ -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,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) =>
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
-
|
|
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]*)[
|
|
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 ${
|
|
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) =>
|
|
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
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);
|