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.
- package/README.md +123 -139
- package/package.json +2 -3
- package/src/codegen/expressions.js +778 -253
- package/src/codegen/source-map.js +13 -5
- package/src/codegen/statements.js +193 -6
- package/src/codegen.js +115 -15
- package/src/compiler.js +10 -3
- package/src/dev-server.js +83 -0
- package/src/dts-parser.js +1 -1
- package/src/index.js +47 -16
- 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 +698 -16
|
@@ -16,21 +16,29 @@ function vlqEncode(value) {
|
|
|
16
16
|
return result;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export function buildSourceMap(
|
|
20
|
-
//
|
|
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,
|
|
34
|
+
// Segment: [genCol=0, srcFileIdxDelta, srcLineDelta, srcCol=0]
|
|
31
35
|
lines.push(
|
|
32
|
-
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
|
|
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
|
-
|
|
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
|
|
@@ -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
|
-
|
|
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(
|
|
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
|
-
"
|
|
166
|
+
"var __append = __append || function(a, ...b) { return a ? [...a, ...b] : b; };",
|
|
147
167
|
);
|
|
148
168
|
if (this._usesSliceGuard)
|
|
149
|
-
helpers.push("
|
|
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
|
-
"
|
|
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
|
-
//
|
|
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
|
-
//
|
|
196
|
-
getSourceMap(
|
|
197
|
-
|
|
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) =>
|
|
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
|
-
|
|
169
|
-
|
|
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
|
+
}
|