gofront 0.0.9 → 1.0.0
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 +6 -6
- package/package.json +7 -4
- package/src/codegen/expressions.js +520 -1717
- package/src/codegen/index.js +576 -0
- package/src/codegen/runtime.js +151 -0
- package/src/codegen/statements.js +305 -296
- package/src/codegen/stdlib/builder.js +36 -0
- package/src/codegen/stdlib/bytes.js +62 -0
- package/src/codegen/stdlib/errors.js +17 -0
- package/src/codegen/stdlib/fmt.js +101 -0
- package/src/codegen/stdlib/gom.js +152 -0
- package/src/codegen/stdlib/html.js +17 -0
- package/src/codegen/stdlib/index.js +95 -0
- package/src/codegen/stdlib/io.js +33 -0
- package/src/codegen/stdlib/maps.js +42 -0
- package/src/codegen/stdlib/math.js +53 -0
- package/src/codegen/stdlib/os.js +10 -0
- package/src/codegen/stdlib/path.js +31 -0
- package/src/codegen/stdlib/rand.js +24 -0
- package/src/codegen/stdlib/regexp.js +51 -0
- package/src/codegen/stdlib/slices.js +48 -0
- package/src/codegen/stdlib/sort.js +28 -0
- package/src/codegen/stdlib/strconv.js +30 -0
- package/src/codegen/stdlib/strings.js +83 -0
- package/src/codegen/stdlib/time.js +54 -0
- package/src/codegen/stdlib/unicode.js +22 -0
- package/src/codegen/stdlib/utf8.js +23 -0
- package/src/codegen/templ.js +281 -0
- package/src/compiler.js +80 -74
- package/src/dts-parser.js +355 -371
- package/src/index.js +13 -49
- package/src/lexer.js +186 -272
- package/src/minifier.js +305 -318
- package/src/parser/expressions.js +51 -34
- package/src/{parser.js → parser/index.js} +4 -4
- package/src/parser/statements.js +24 -34
- package/src/parser/types.js +31 -29
- package/src/templ-parser.js +2 -2
- package/src/tokens.js +109 -0
- package/src/typechecker/assignability.js +258 -0
- package/src/typechecker/expressions.js +559 -549
- package/src/typechecker/index.js +555 -0
- package/src/typechecker/resolve.js +421 -0
- package/src/typechecker/statements.js +267 -280
- package/src/typechecker/stdlib/core.js +592 -0
- package/src/typechecker/stdlib/extended.js +332 -0
- package/src/typechecker/stdlib.js +9 -1060
- package/src/typechecker/termination.js +83 -0
- package/src/codegen.js +0 -882
- package/src/typechecker.js +0 -1299
package/src/codegen.js
DELETED
|
@@ -1,882 +0,0 @@
|
|
|
1
|
-
// Code generator: walks the typed AST and emits clean JavaScript.
|
|
2
|
-
//
|
|
3
|
-
// Split into sub-modules under codegen/:
|
|
4
|
-
// source-map.js — VLQ encoder and source map builder
|
|
5
|
-
// statements.js — genBlock, genStmt, genFor, genSwitch, etc.
|
|
6
|
-
// expressions.js — genExpr, genCall, genCompositeLit, helpers
|
|
7
|
-
//
|
|
8
|
-
// Key design choices:
|
|
9
|
-
// - Structs → ES6 classes with a single destructured-object constructor
|
|
10
|
-
// - Methods → class instance methods
|
|
11
|
-
// - Multiple returns → JS arrays e.g. return [a, b]
|
|
12
|
-
// - Destructuring → let [a, b] = f()
|
|
13
|
-
// - nil → null
|
|
14
|
-
// - Slices → JS arrays (append → spread, len → .length)
|
|
15
|
-
// - Maps → plain JS objects (map[string]T)
|
|
16
|
-
// - make([]T, n) → new Array(n).fill(zeroOf(T))
|
|
17
|
-
// - make(map[K]V) → {}
|
|
18
|
-
// - for range → for...of with .entries()
|
|
19
|
-
|
|
20
|
-
import { expressionGenMethods } from "./codegen/expressions.js";
|
|
21
|
-
import { buildSourceMap } from "./codegen/source-map.js";
|
|
22
|
-
import { statementGenMethods } from "./codegen/statements.js";
|
|
23
|
-
import { T, Token } from "./lexer.js";
|
|
24
|
-
import { Parser } from "./parser.js";
|
|
25
|
-
import { isComplex, isNumeric } from "./typechecker/types.js";
|
|
26
|
-
|
|
27
|
-
export class CodeGen {
|
|
28
|
-
// jsImports: Map<importPath, string[]> — npm package imports to emit at top of file
|
|
29
|
-
// bundledPackages: Set<string> — GoFront package names bundled inline;
|
|
30
|
-
// SelectorExpr `pkg.Foo` → just `Foo`
|
|
31
|
-
constructor(
|
|
32
|
-
checker = null,
|
|
33
|
-
jsImports = new Map(),
|
|
34
|
-
bundledPackages = new Set(),
|
|
35
|
-
) {
|
|
36
|
-
this.checker = checker;
|
|
37
|
-
this.out = [];
|
|
38
|
-
this.indent = 0;
|
|
39
|
-
this.structNames = new Set();
|
|
40
|
-
this.namedWrapperNames = new Set();
|
|
41
|
-
this.jsImports = jsImports;
|
|
42
|
-
this.bundledPackages = bundledPackages;
|
|
43
|
-
this.namedReturnVars = null; // names of current function's named return vars
|
|
44
|
-
this._srcMappings = []; // { genLine, srcLine, srcFileIdx } for source map
|
|
45
|
-
this._currentSrcFileIdx = 0; // updated as each top-level decl is generated
|
|
46
|
-
this._boxedVars = new Set(); // address-taken scalar variables that need boxing
|
|
47
|
-
// Runtime helper usage tracking — only emit helpers that are actually used
|
|
48
|
-
this._usesLen = false;
|
|
49
|
-
this._usesAppend = false;
|
|
50
|
-
this._usesSliceGuard = false;
|
|
51
|
-
this._usesSprintf = false;
|
|
52
|
-
this._usesEqual = false;
|
|
53
|
-
this._usesCmul = false;
|
|
54
|
-
this._usesCdiv = false;
|
|
55
|
-
this._usesError = false;
|
|
56
|
-
this._usesErrorIs = false;
|
|
57
|
-
this._usesPathClean = false;
|
|
58
|
-
this._usesTimeFmt = false;
|
|
59
|
-
this._usesTimeParse = false;
|
|
60
|
-
// Iterator (range-over-func) context
|
|
61
|
-
this._inIteratorBody = false;
|
|
62
|
-
this._iterDepth = 0;
|
|
63
|
-
this._iterBreakFlag = null;
|
|
64
|
-
this._iterReturnFlag = null;
|
|
65
|
-
this._iterReturnVar = null;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// ── Output helpers ───────────────────────────────────────────
|
|
69
|
-
|
|
70
|
-
emit(s) {
|
|
71
|
-
this.out.push(s);
|
|
72
|
-
}
|
|
73
|
-
line(s = "", srcLine = null) {
|
|
74
|
-
if (srcLine != null) {
|
|
75
|
-
this._srcMappings.push({
|
|
76
|
-
genLine: this.out.length,
|
|
77
|
-
srcLine: srcLine - 1, // 0-based
|
|
78
|
-
srcFileIdx: this._currentSrcFileIdx,
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
this.out.push(" ".repeat(this.indent) + s);
|
|
82
|
-
}
|
|
83
|
-
blank() {
|
|
84
|
-
this.out.push("");
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
indented(fn) {
|
|
88
|
-
this.indent++;
|
|
89
|
-
fn();
|
|
90
|
-
this.indent--;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
generate(program) {
|
|
94
|
-
// Collect struct names first (needed by zero-value helpers and genCompositeLit)
|
|
95
|
-
for (const d of program.decls) {
|
|
96
|
-
if (d.kind === "TypeDecl" && d.type.kind === "StructType") {
|
|
97
|
-
this.structNames.add(d.name);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Build a map: typeName → [MethodDecl, ...]
|
|
102
|
-
const methods = new Map();
|
|
103
|
-
for (const d of program.decls) {
|
|
104
|
-
if (d.kind === "MethodDecl") {
|
|
105
|
-
const name = d.recvType.name;
|
|
106
|
-
if (!methods.has(name)) methods.set(name, []);
|
|
107
|
-
methods.get(name).push(d);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Collect named non-struct types that have methods (emitted as wrapper classes)
|
|
112
|
-
for (const d of program.decls) {
|
|
113
|
-
if (
|
|
114
|
-
d.kind === "TypeDecl" &&
|
|
115
|
-
d.type.kind !== "StructType" &&
|
|
116
|
-
d.type.kind !== "InterfaceType" &&
|
|
117
|
-
(methods.get(d.name) ?? []).length > 0
|
|
118
|
-
) {
|
|
119
|
-
this.namedWrapperNames.add(d.name);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Emit ESM import statements for npm packages
|
|
124
|
-
for (const [importPath, names] of this.jsImports) {
|
|
125
|
-
if (names.length === 0) continue;
|
|
126
|
-
this.line(`import { ${names.join(", ")} } from '${importPath}';`);
|
|
127
|
-
}
|
|
128
|
-
if (this.jsImports.size > 0) this.blank();
|
|
129
|
-
|
|
130
|
-
for (const d of program.decls) {
|
|
131
|
-
if (d.kind === "TypeDecl") {
|
|
132
|
-
this._currentSrcFileIdx = d._srcFileIdx ?? 0;
|
|
133
|
-
this.genTypeDeclWithMethods(d, methods.get(d.name) ?? []);
|
|
134
|
-
this.blank();
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
for (const d of program.decls) {
|
|
139
|
-
if (d.kind === "VarDecl") {
|
|
140
|
-
this._currentSrcFileIdx = d._srcFileIdx ?? 0;
|
|
141
|
-
this.genVarDecl(d);
|
|
142
|
-
this.blank();
|
|
143
|
-
}
|
|
144
|
-
if (d.kind === "ConstDecl") {
|
|
145
|
-
this._currentSrcFileIdx = d._srcFileIdx ?? 0;
|
|
146
|
-
this.genConstDecl(d);
|
|
147
|
-
this.blank();
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Emit functions and templ components
|
|
152
|
-
let initCount = 0;
|
|
153
|
-
const initNames = [];
|
|
154
|
-
for (const d of program.decls) {
|
|
155
|
-
if (d.kind === "FuncDecl") {
|
|
156
|
-
this._currentSrcFileIdx = d._srcFileIdx ?? 0;
|
|
157
|
-
if (d.name === "init") {
|
|
158
|
-
const renamed = initCount === 0 ? "init" : `init$${initCount}`;
|
|
159
|
-
initNames.push(renamed);
|
|
160
|
-
this.genFuncDecl(d, renamed);
|
|
161
|
-
initCount++;
|
|
162
|
-
} else {
|
|
163
|
-
this.genFuncDecl(d);
|
|
164
|
-
}
|
|
165
|
-
this.blank();
|
|
166
|
-
} else if (d.kind === "TemplDecl") {
|
|
167
|
-
this._currentSrcFileIdx = d._srcFileIdx ?? 0;
|
|
168
|
-
this.genTemplDecl(d);
|
|
169
|
-
this.blank();
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Auto-call init() functions, then main()
|
|
174
|
-
for (const name of initNames) this.line(`${name}();`);
|
|
175
|
-
const hasMain = program.decls.some(
|
|
176
|
-
(d) => d.kind === "FuncDecl" && d.name === "main",
|
|
177
|
-
);
|
|
178
|
-
if (hasMain) this.line("main();");
|
|
179
|
-
|
|
180
|
-
// Prepend runtime helpers that were actually used
|
|
181
|
-
const helpers = [];
|
|
182
|
-
if (this._usesLen)
|
|
183
|
-
helpers.push(
|
|
184
|
-
"var __len = __len || function(a) { if (a && typeof a === 'object' && !Array.isArray(a)) return Object.keys(a).length; return a?.length ?? 0; };",
|
|
185
|
-
);
|
|
186
|
-
if (this._usesAppend)
|
|
187
|
-
helpers.push(
|
|
188
|
-
"var __append = __append || function(a, ...b) { return a ? [...a, ...b] : b; };",
|
|
189
|
-
);
|
|
190
|
-
if (this._usesSliceGuard)
|
|
191
|
-
helpers.push("var __s = __s || function(a) { return a || []; };");
|
|
192
|
-
if (this._usesEqual)
|
|
193
|
-
helpers.push(
|
|
194
|
-
'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;};',
|
|
195
|
-
);
|
|
196
|
-
if (this._usesCmul)
|
|
197
|
-
helpers.push(
|
|
198
|
-
"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 }; };",
|
|
199
|
-
);
|
|
200
|
-
if (this._usesCdiv)
|
|
201
|
-
helpers.push(
|
|
202
|
-
"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 }; };",
|
|
203
|
-
);
|
|
204
|
-
if (this._usesSprintf)
|
|
205
|
-
helpers.push(
|
|
206
|
-
[
|
|
207
|
-
"var __sprintf = __sprintf || function(f,...a){let i=0;return f.replace(/%([#+\\- 0]*)([0-9]*)\\.?([0-9]*)[sdvftxXqobeEgGw%]/g,(m)=>{",
|
|
208
|
-
"if(m==='%%')return'%';const fl=m.slice(1,-1),verb=m.slice(-1),v=a[i++];",
|
|
209
|
-
"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;};",
|
|
210
|
-
"const [,flags,width,prec]=m.match(/^%([#+\\- 0]*)([0-9]*)\\.?([0-9]*)/)||[];",
|
|
211
|
-
"const zero=flags?.includes('0')&&!flags?.includes('-');",
|
|
212
|
-
"switch(verb){",
|
|
213
|
-
"case's':return pad(String(v==null?'<nil>':v),width,false);",
|
|
214
|
-
"case'd':return pad(String(Math.trunc(Number(v))),width,zero);",
|
|
215
|
-
"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);}",
|
|
216
|
-
"case'f':{const n=Number(v),p=prec!==''?parseInt(prec):6;return pad(n.toFixed(p),width,zero);}",
|
|
217
|
-
"case't':return pad(String(!!v),width,false);",
|
|
218
|
-
"case'x':return pad((Number(v)>>>0).toString(16),width,zero);",
|
|
219
|
-
"case'X':return pad((Number(v)>>>0).toString(16).toUpperCase(),width,zero);",
|
|
220
|
-
"case'o':return pad((Number(v)>>>0).toString(8),width,zero);",
|
|
221
|
-
"case'b':return pad((Number(v)>>>0).toString(2),width,zero);",
|
|
222
|
-
"case'q':return pad('\"'+String(v==null?'':v).replace(/\\\\/g,'\\\\\\\\').replace(/\"/g,'\\\\\"')+'\"',width,false);",
|
|
223
|
-
"case'e':case'E':{const n=Number(v),p=prec!==''?parseInt(prec):6;return pad(n.toExponential(p),width,zero);}",
|
|
224
|
-
"case'g':case'G':{const n=Number(v);return pad(prec!==''?n.toPrecision(parseInt(prec)):String(n),width,zero);}",
|
|
225
|
-
"case'w':return pad(String(v==null?'<nil>':typeof v==='object'&&v.Error?v.Error():v),width,false);",
|
|
226
|
-
"default:return m;}});",
|
|
227
|
-
"};",
|
|
228
|
-
].join(""),
|
|
229
|
-
);
|
|
230
|
-
if (this._usesError)
|
|
231
|
-
helpers.push(
|
|
232
|
-
"var __error = __error || function(msg, cause) { return { Error() { return msg; }, toString() { return msg; }, _msg: msg, _cause: cause ?? null }; };",
|
|
233
|
-
);
|
|
234
|
-
if (this._usesErrorIs)
|
|
235
|
-
helpers.push(
|
|
236
|
-
'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; };',
|
|
237
|
-
);
|
|
238
|
-
if (this._usesPathClean)
|
|
239
|
-
helpers.push(
|
|
240
|
-
'var __pathClean = __pathClean || function(p) { if (!p) return "."; const abs = p.startsWith("/"); const parts = p.split("/").reduce((acc, s) => { if (s === "" || s === ".") return acc; if (s === "..") { if (acc.length && acc[acc.length-1] !== "..") acc.pop(); else if (!abs) acc.push(".."); } else acc.push(s); return acc; }, []); return (abs ? "/" : "") + (parts.join("/") || "."); };',
|
|
241
|
-
);
|
|
242
|
-
if (this._usesTimeFmt)
|
|
243
|
-
helpers.push(
|
|
244
|
-
[
|
|
245
|
-
"var __timeFmt = __timeFmt || function(d, layout) {",
|
|
246
|
-
'const pad = (n, w=2) => String(n).padStart(w, "0");',
|
|
247
|
-
'const DAYS=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];',
|
|
248
|
-
'const MONTHS=["January","February","March","April","May","June","July","August","September","October","November","December"];',
|
|
249
|
-
"return layout.replace(/2006|06|Monday|Mon|January|Jan|01|1|02|2|15|PM|3|04|4|05|5|Z07:00|\\.000|\\.999/g, t => {",
|
|
250
|
-
"switch(t){",
|
|
251
|
-
'case "2006": return d.getFullYear();',
|
|
252
|
-
'case "06": return String(d.getFullYear()).slice(-2);',
|
|
253
|
-
'case "Monday": return DAYS[d.getDay()];',
|
|
254
|
-
'case "Mon": return DAYS[d.getDay()].slice(0,3);',
|
|
255
|
-
'case "January": return MONTHS[d.getMonth()];',
|
|
256
|
-
'case "Jan": return MONTHS[d.getMonth()].slice(0,3);',
|
|
257
|
-
'case "01": return pad(d.getMonth()+1);',
|
|
258
|
-
'case "1": return String(d.getMonth()+1);',
|
|
259
|
-
'case "02": return pad(d.getDate());',
|
|
260
|
-
'case "2": return String(d.getDate());',
|
|
261
|
-
'case "15": return pad(d.getHours());',
|
|
262
|
-
'case "PM": return d.getHours()<12?"AM":"PM";',
|
|
263
|
-
'case "3": return String(d.getHours()%12||12);',
|
|
264
|
-
'case "04": return pad(d.getMinutes());',
|
|
265
|
-
'case "4": return String(d.getMinutes());',
|
|
266
|
-
'case "05": return pad(d.getSeconds());',
|
|
267
|
-
'case "5": return String(d.getSeconds());',
|
|
268
|
-
'case "Z07:00": { const o=-d.getTimezoneOffset(); return o===0?"Z":(o>0?"+":"-")+pad(Math.floor(Math.abs(o)/60))+":"+pad(Math.abs(o)%60); }',
|
|
269
|
-
'case ".000": return "."+pad(d.getMilliseconds(),3);',
|
|
270
|
-
'case ".999": return d.getMilliseconds()>0?"."+String(d.getMilliseconds()).replace(/0+$/,""):"";',
|
|
271
|
-
"default: return t;}});",
|
|
272
|
-
"};",
|
|
273
|
-
].join(""),
|
|
274
|
-
);
|
|
275
|
-
if (this._usesTimeParse)
|
|
276
|
-
helpers.push(
|
|
277
|
-
'var __timeParse = __timeParse || function(layout, value) { try { const d = new Date(value); if (isNaN(d.getTime())) throw 0; return [{_d:d}, null]; } catch { return [{_d:new Date(0)}, "parsing time: cannot parse"]; } };',
|
|
278
|
-
);
|
|
279
|
-
|
|
280
|
-
if (helpers.length > 0) this.out.unshift(...helpers, "");
|
|
281
|
-
|
|
282
|
-
// Strip leading blank line
|
|
283
|
-
while (this.out[0] === "") this.out.shift();
|
|
284
|
-
return this.out.join("\n");
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// Generate a single bundle from multiple programs (same-package multi-file).
|
|
288
|
-
// Annotates each decl with its source file index before merging.
|
|
289
|
-
generateAll(programs) {
|
|
290
|
-
for (let i = 0; i < programs.length; i++) {
|
|
291
|
-
for (const decl of programs[i].decls) {
|
|
292
|
-
decl._srcFileIdx = i;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
const merged = { decls: programs.flatMap((p) => p.decls) };
|
|
296
|
-
return this.generate(merged);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// Returns a source map JSON string for the last generate() call.
|
|
300
|
-
// sources: string[] of source filenames (relative to the output file).
|
|
301
|
-
// sourcesContent: string[] of original file contents (embedded for DevTools breakpoints).
|
|
302
|
-
getSourceMap(sources, sourcesContent) {
|
|
303
|
-
const srcArray = Array.isArray(sources) ? sources : [sources];
|
|
304
|
-
return buildSourceMap(srcArray, this._srcMappings, sourcesContent);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// ── Type declarations ────────────────────────────────────────
|
|
308
|
-
|
|
309
|
-
genTypeDeclWithMethods(decl, methodDecls) {
|
|
310
|
-
if (decl.type.kind === "StructType") {
|
|
311
|
-
this.genStruct(decl.name, decl.type, methodDecls);
|
|
312
|
-
} else if (decl.type.kind === "InterfaceType") {
|
|
313
|
-
// Interfaces are compile-time only — no JS output needed.
|
|
314
|
-
this.line(`// interface ${decl.name} (compile-time only)`);
|
|
315
|
-
} else if (methodDecls.length > 0) {
|
|
316
|
-
// Named non-struct type with methods — emit an ES6 wrapper class.
|
|
317
|
-
this.genNamedTypeClass(decl.name, methodDecls);
|
|
318
|
-
} else {
|
|
319
|
-
this.line(`// type ${decl.name} = ${this.typeComment(decl.type)}`);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
genNamedTypeClass(name, methodDecls) {
|
|
324
|
-
const namedType = this.checker?.types.get(name);
|
|
325
|
-
const underlying = namedType?.underlying;
|
|
326
|
-
let field, ctorDefault;
|
|
327
|
-
if (underlying?.kind === "func") {
|
|
328
|
-
field = "_fn";
|
|
329
|
-
ctorDefault = "null";
|
|
330
|
-
} else if (underlying?.kind === "map") {
|
|
331
|
-
field = "_map";
|
|
332
|
-
ctorDefault = "{}";
|
|
333
|
-
} else {
|
|
334
|
-
field = "_items";
|
|
335
|
-
ctorDefault = "[]";
|
|
336
|
-
}
|
|
337
|
-
this.line(`class ${name} {`);
|
|
338
|
-
this.indented(() => {
|
|
339
|
-
this.line(
|
|
340
|
-
`constructor(${field} = ${ctorDefault}) { this.${field} = ${field}; }`,
|
|
341
|
-
);
|
|
342
|
-
for (const m of methodDecls) {
|
|
343
|
-
this.blank();
|
|
344
|
-
this.genMethod(m, field);
|
|
345
|
-
}
|
|
346
|
-
});
|
|
347
|
-
this.line("}");
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
genStruct(name, structTypeAst, methodDecls) {
|
|
351
|
-
const fields = [];
|
|
352
|
-
if (this.checker) {
|
|
353
|
-
const resolved = this.checker.types.get(name)?.underlying;
|
|
354
|
-
if (resolved?.kind === "struct") {
|
|
355
|
-
for (const [fName, fType] of resolved.fields.entries()) {
|
|
356
|
-
fields.push({ name: fName, zero: this.zeroValueForType(fType) });
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
} else {
|
|
360
|
-
// Fallback (e.g. tests without typechecker)
|
|
361
|
-
for (const f of structTypeAst.fields) {
|
|
362
|
-
if (f.embedded) continue; // Can't resolve here
|
|
363
|
-
const zero = this.zeroValueForTypeNode(f.type);
|
|
364
|
-
for (const n of f.names) fields.push({ name: n, zero });
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
this.line(`class ${name} {`);
|
|
369
|
-
this.indented(() => {
|
|
370
|
-
if (fields.length === 0) {
|
|
371
|
-
this.line("constructor() {}");
|
|
372
|
-
} else {
|
|
373
|
-
const params = fields.map((f) => `${f.name} = ${f.zero}`).join(", ");
|
|
374
|
-
this.line(`constructor({ ${params} } = {}) {`);
|
|
375
|
-
this.indented(() => {
|
|
376
|
-
for (const f of fields) this.line(`this.${f.name} = ${f.name};`);
|
|
377
|
-
});
|
|
378
|
-
this.line("}");
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// Methods
|
|
382
|
-
for (const m of methodDecls) {
|
|
383
|
-
this.blank();
|
|
384
|
-
this.genMethod(m);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
// Delegation stubs for promoted embedded methods
|
|
388
|
-
if (this.checker) {
|
|
389
|
-
const resolvedType = this.checker.types.get(name)?.underlying;
|
|
390
|
-
if (resolvedType?.kind === "struct" && resolvedType._embeds) {
|
|
391
|
-
const declared = new Set(methodDecls.map((m) => m.name));
|
|
392
|
-
for (const embed of resolvedType._embeds) {
|
|
393
|
-
const embedName = embed.kind === "named" ? embed.name : null;
|
|
394
|
-
if (!embedName) continue;
|
|
395
|
-
const embedBase = embed.kind === "named" ? embed.underlying : embed;
|
|
396
|
-
if (embedBase?.kind !== "struct" || !embedBase.methods) continue;
|
|
397
|
-
for (const [mName] of embedBase.methods.entries()) {
|
|
398
|
-
if (!declared.has(mName)) {
|
|
399
|
-
this.blank();
|
|
400
|
-
this.line(
|
|
401
|
-
`${mName}(...__a) { return ${embedName}.prototype.${mName}.call(this, ...__a); }`,
|
|
402
|
-
);
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
});
|
|
409
|
-
this.line("}");
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
genMethod(decl, recvField = null) {
|
|
413
|
-
const params = decl.params.map((p) => p.name).join(", ");
|
|
414
|
-
const asyncPrefix = decl.async ? "async " : "";
|
|
415
|
-
this.line(`${asyncPrefix}${decl.name}(${params}) {`);
|
|
416
|
-
const prevBoxed = this._boxedVars;
|
|
417
|
-
this._boxedVars = new Set();
|
|
418
|
-
this._scanAddressTaken(decl.body);
|
|
419
|
-
const prevUnwrapped = this._unwrappedRecv;
|
|
420
|
-
this.indented(() => {
|
|
421
|
-
if (decl.recvName && decl.recvName !== "_") {
|
|
422
|
-
if (recvField) {
|
|
423
|
-
this.line(`const ${decl.recvName} = this.${recvField};`);
|
|
424
|
-
this._unwrappedRecv = decl.recvName;
|
|
425
|
-
} else {
|
|
426
|
-
this.line(`const ${decl.recvName} = this;`);
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
this._withNamedReturns(decl, () => this._genBody(decl.body));
|
|
430
|
-
});
|
|
431
|
-
this._unwrappedRecv = prevUnwrapped;
|
|
432
|
-
this._boxedVars = prevBoxed;
|
|
433
|
-
this.line("}");
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// Returns the wrapper field name ("_fn", "_items", "_map") if `type` is a named
|
|
437
|
-
// non-struct type emitted as a wrapper class, or null otherwise.
|
|
438
|
-
// Pass `expr` so we can skip unwrapping when the expression is the method receiver
|
|
439
|
-
// (which was already unwrapped to `this.<field>` at the top of the method body).
|
|
440
|
-
_namedWrapperField(type, expr = null) {
|
|
441
|
-
if (type?.kind !== "named") return null;
|
|
442
|
-
if (!this.namedWrapperNames.has(type.name)) return null;
|
|
443
|
-
if (expr?.kind === "Ident" && expr.name === this._unwrappedRecv)
|
|
444
|
-
return null;
|
|
445
|
-
const u = type.underlying;
|
|
446
|
-
if (u?.kind === "func") return "_fn";
|
|
447
|
-
if (u?.kind === "map") return "_map";
|
|
448
|
-
return "_items";
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// ── Function declarations ────────────────────────────────────
|
|
452
|
-
|
|
453
|
-
genFuncDecl(decl, nameOverride) {
|
|
454
|
-
const name = nameOverride ?? decl.name;
|
|
455
|
-
const params = decl.params
|
|
456
|
-
.map((p, i) =>
|
|
457
|
-
p.variadic && i === decl.params.length - 1 ? `...${p.name}` : p.name,
|
|
458
|
-
)
|
|
459
|
-
.join(", ");
|
|
460
|
-
const asyncPrefix = decl.async ? "async " : "";
|
|
461
|
-
const srcLine = decl._line ?? null;
|
|
462
|
-
this.line(
|
|
463
|
-
`${asyncPrefix}function ${name}(${params}) {`,
|
|
464
|
-
srcLine ? srcLine - 1 : null,
|
|
465
|
-
);
|
|
466
|
-
const prevBoxed = this._boxedVars;
|
|
467
|
-
this._boxedVars = new Set();
|
|
468
|
-
this._scanAddressTaken(decl.body);
|
|
469
|
-
this.indented(() =>
|
|
470
|
-
this._withNamedReturns(decl, () => this._genBody(decl.body)),
|
|
471
|
-
);
|
|
472
|
-
this._boxedVars = prevBoxed;
|
|
473
|
-
this.line("}");
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
// ── TemplDecl code generation ────────────────────────────────
|
|
477
|
-
|
|
478
|
-
genTemplDecl(decl) {
|
|
479
|
-
const params = decl.params
|
|
480
|
-
.map((p, i) =>
|
|
481
|
-
p.variadic && i === decl.params.length - 1 ? `...${p.name}` : p.name,
|
|
482
|
-
)
|
|
483
|
-
.join(", ");
|
|
484
|
-
this.line(`function ${decl.name}(${params}) {`);
|
|
485
|
-
this.indented(() => {
|
|
486
|
-
this.line(`return {Mount(___p) {`);
|
|
487
|
-
this.indented(() => {
|
|
488
|
-
this._genTemplNodes(decl.body, "___p");
|
|
489
|
-
});
|
|
490
|
-
this.line(`}};`);
|
|
491
|
-
});
|
|
492
|
-
this.line("}");
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
// Emit mount statements for an array of TemplNodes into parent variable `p`.
|
|
496
|
-
_genTemplNodes(nodes, p) {
|
|
497
|
-
if (!nodes || nodes.length === 0) return;
|
|
498
|
-
if (nodes.length === 1) {
|
|
499
|
-
this._genTemplNodeMount(nodes[0], p);
|
|
500
|
-
return;
|
|
501
|
-
}
|
|
502
|
-
// Wrap multiple root nodes in a fragment div? No — emit each directly.
|
|
503
|
-
for (const node of nodes) {
|
|
504
|
-
this._genTemplNodeMount(node, p);
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
// Emit JS to mount a single TemplNode onto parent `p`.
|
|
509
|
-
_genTemplNodeMount(node, p) {
|
|
510
|
-
switch (node.kind) {
|
|
511
|
-
case "TemplElement":
|
|
512
|
-
return this._genTemplElement(node, p);
|
|
513
|
-
case "TemplText":
|
|
514
|
-
return this.line(
|
|
515
|
-
`${p}.appendChild(document.createTextNode(${JSON.stringify(node.value)}));`,
|
|
516
|
-
);
|
|
517
|
-
case "TemplExpr":
|
|
518
|
-
return this.line(
|
|
519
|
-
`${p}.appendChild(document.createTextNode(String(${this._genTemplTokenExpr(node.tokens)})));`,
|
|
520
|
-
);
|
|
521
|
-
case "TemplComponent": {
|
|
522
|
-
// Special case: @templ.Raw(expr) → insertAdjacentHTML
|
|
523
|
-
const toks = node.tokens;
|
|
524
|
-
if (
|
|
525
|
-
toks.length >= 4 &&
|
|
526
|
-
toks[0]?.value === "templ" &&
|
|
527
|
-
toks[1]?.type === T.DOT &&
|
|
528
|
-
toks[2]?.value === "Raw" &&
|
|
529
|
-
toks[3]?.type === T.LPAREN
|
|
530
|
-
) {
|
|
531
|
-
const argTokens = toks.slice(4, toks.length - 1);
|
|
532
|
-
const argJs = this._genTemplTokenExpr(argTokens);
|
|
533
|
-
return this.line(`${p}.insertAdjacentHTML("beforeend", ${argJs});`);
|
|
534
|
-
}
|
|
535
|
-
return this.line(
|
|
536
|
-
`(${this._genTemplTokenCallExpr(node.tokens)}).Mount(${p});`,
|
|
537
|
-
);
|
|
538
|
-
}
|
|
539
|
-
case "TemplChildren":
|
|
540
|
-
// children is a variadic gom.Node param — mount each child
|
|
541
|
-
return this.line(
|
|
542
|
-
`if(typeof children!=="undefined")(Array.isArray(children)?children:[...children]).forEach(___c=>___c?.Mount?.(${p}));`,
|
|
543
|
-
);
|
|
544
|
-
case "TemplIf":
|
|
545
|
-
return this._genTemplIf(node, p);
|
|
546
|
-
case "TemplFor":
|
|
547
|
-
return this._genTemplFor(node, p);
|
|
548
|
-
case "TemplSwitch":
|
|
549
|
-
return this._genTemplSwitch(node, p);
|
|
550
|
-
default:
|
|
551
|
-
break;
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
_genTemplElement(node, p) {
|
|
556
|
-
const { tag, attrs, children } = node;
|
|
557
|
-
const elVar = this._freshVar("e");
|
|
558
|
-
this.line(
|
|
559
|
-
`const ${elVar} = document.createElement(${JSON.stringify(tag)});`,
|
|
560
|
-
);
|
|
561
|
-
for (const attr of attrs) {
|
|
562
|
-
this._genTemplAttr(attr, elVar);
|
|
563
|
-
}
|
|
564
|
-
this._genTemplNodes(children, elVar);
|
|
565
|
-
this.line(`${p}.appendChild(${elVar});`);
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
_genTemplAttr(attr, el) {
|
|
569
|
-
const { name } = attr;
|
|
570
|
-
if (attr.kind === "static") {
|
|
571
|
-
if (name === "class") {
|
|
572
|
-
this.line(`${el}.className = ${JSON.stringify(attr.value)};`);
|
|
573
|
-
} else {
|
|
574
|
-
this.line(
|
|
575
|
-
`${el}.setAttribute(${JSON.stringify(name)}, ${JSON.stringify(attr.value)});`,
|
|
576
|
-
);
|
|
577
|
-
}
|
|
578
|
-
} else if (attr.kind === "expr") {
|
|
579
|
-
const exprJs = this._genTemplTokenExpr(attr.tokens);
|
|
580
|
-
if (name === "class") {
|
|
581
|
-
this.line(`${el}.className = ${exprJs};`);
|
|
582
|
-
} else {
|
|
583
|
-
this.line(
|
|
584
|
-
`${el}.setAttribute(${JSON.stringify(name)}, String(${exprJs}));`,
|
|
585
|
-
);
|
|
586
|
-
}
|
|
587
|
-
} else if (attr.kind === "bool") {
|
|
588
|
-
this.line(`${el}.setAttribute(${JSON.stringify(name)}, "");`);
|
|
589
|
-
} else if (attr.kind === "cond-bool") {
|
|
590
|
-
const exprJs = this._genTemplTokenExpr(attr.tokens);
|
|
591
|
-
this.line(
|
|
592
|
-
`if(${exprJs})${el}.setAttribute(${JSON.stringify(name)}, "");`,
|
|
593
|
-
);
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
_genTemplIf(node, p) {
|
|
598
|
-
const condJs = this._genTemplCondTokens(node.condTokens);
|
|
599
|
-
this.line(`if (${condJs}) {`);
|
|
600
|
-
this.indented(() => this._genTemplNodes(node.then, p));
|
|
601
|
-
this._genTemplIfElse(node.else_, p);
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
_genTemplIfElse(else_, p) {
|
|
605
|
-
if (!else_ || else_.length === 0) {
|
|
606
|
-
this.line(`}`);
|
|
607
|
-
return;
|
|
608
|
-
}
|
|
609
|
-
if (else_.length === 1 && else_[0].kind === "TemplIf") {
|
|
610
|
-
const elseIf = else_[0];
|
|
611
|
-
this.line(`} else if (${this._genTemplCondTokens(elseIf.condTokens)}) {`);
|
|
612
|
-
this.indented(() => this._genTemplNodes(elseIf.then, p));
|
|
613
|
-
this._genTemplIfElse(elseIf.else_, p);
|
|
614
|
-
} else {
|
|
615
|
-
this.line(`} else {`);
|
|
616
|
-
this.indented(() => this._genTemplNodes(else_, p));
|
|
617
|
-
this.line(`}`);
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
_genTemplSwitch(node, p) {
|
|
622
|
-
const { exprTokens, cases } = node;
|
|
623
|
-
if (cases.length === 0) return;
|
|
624
|
-
const exprJs = this._genTemplTokenExpr(exprTokens);
|
|
625
|
-
this.line(`switch (${exprJs}) {`);
|
|
626
|
-
this.indented(() => {
|
|
627
|
-
for (const c of cases) {
|
|
628
|
-
const label =
|
|
629
|
-
c.caseTokens === null
|
|
630
|
-
? "default"
|
|
631
|
-
: `case ${this._genTemplTokenExpr(c.caseTokens)}`;
|
|
632
|
-
this.line(`${label}: {`);
|
|
633
|
-
this.indented(() => this._genTemplNodes(c.body, p));
|
|
634
|
-
this.line(`break; }`);
|
|
635
|
-
}
|
|
636
|
-
});
|
|
637
|
-
this.line(`}`);
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
_genTemplFor(node, p) {
|
|
641
|
-
// stmtTokens include the `for` keyword plus the range clause (no body).
|
|
642
|
-
// Strip any trailing semicolons (inserted by the Go lexer on newlines),
|
|
643
|
-
// then add a fake empty body `{ }` so the parser can complete the ForStmt.
|
|
644
|
-
let tokens = node.stmtTokens;
|
|
645
|
-
while (tokens.length > 0 && tokens[tokens.length - 1].type === T.SEMICOLON)
|
|
646
|
-
tokens = tokens.slice(0, -1);
|
|
647
|
-
const eofTok = new Token(T.EOF, "", 1, 1);
|
|
648
|
-
const lbrace = new Token(T.LBRACE, "{", 1, 1);
|
|
649
|
-
const rbrace = new Token(T.RBRACE, "}", 1, 1);
|
|
650
|
-
const semi = new Token(T.SEMICOLON, ";", 1, 1);
|
|
651
|
-
const withFor =
|
|
652
|
-
tokens[0]?.type === T.FOR
|
|
653
|
-
? [...tokens, lbrace, rbrace, semi, eofTok]
|
|
654
|
-
: [
|
|
655
|
-
new Token(T.FOR, "for", 1, 1),
|
|
656
|
-
...tokens,
|
|
657
|
-
lbrace,
|
|
658
|
-
rbrace,
|
|
659
|
-
semi,
|
|
660
|
-
eofTok,
|
|
661
|
-
];
|
|
662
|
-
try {
|
|
663
|
-
const p2 = new Parser(withFor);
|
|
664
|
-
const forStmt = p2.parseFor();
|
|
665
|
-
this._genTemplRangeFor(forStmt, node.body, p);
|
|
666
|
-
} catch {
|
|
667
|
-
// Fallback for non-range loops: emit a comment + skip
|
|
668
|
-
this.line(`/* templ for: ${this._tokensToSource(tokens)} */`);
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
_genTemplRangeFor(forStmt, templBody, mountParent) {
|
|
673
|
-
const init = forStmt.init;
|
|
674
|
-
if (!init?.rhs?.[0] || init.rhs[0].kind !== "RangeExpr") {
|
|
675
|
-
// Plain for loop (cond-only or three-clause)
|
|
676
|
-
const condJs = forStmt.cond ? this.genExpr(forStmt.cond) : "true";
|
|
677
|
-
this.line(`while (${condJs}) {`);
|
|
678
|
-
this.indented(() => this._genTemplNodes(templBody, mountParent));
|
|
679
|
-
this.line("}");
|
|
680
|
-
return;
|
|
681
|
-
}
|
|
682
|
-
// Range for: for key, val := range iter
|
|
683
|
-
const range = init.rhs[0];
|
|
684
|
-
const iterJs = this.genExpr(range.expr);
|
|
685
|
-
const lhs = init.lhs.map((e) => e.name ?? "_");
|
|
686
|
-
const keyName = lhs[0] ?? "_";
|
|
687
|
-
const valName = lhs[1] ?? null;
|
|
688
|
-
|
|
689
|
-
let loopHeader;
|
|
690
|
-
if (valName === null || valName === undefined) {
|
|
691
|
-
loopHeader = `for (const ${keyName === "_" ? "_$" : keyName} of ${iterJs}) {`;
|
|
692
|
-
} else if (keyName === "_" && valName === "_") {
|
|
693
|
-
loopHeader = `for (const _$ of ${iterJs}) {`;
|
|
694
|
-
} else if (keyName === "_") {
|
|
695
|
-
loopHeader = `for (const ${valName} of ${iterJs}) {`;
|
|
696
|
-
} else if (valName === "_") {
|
|
697
|
-
loopHeader = `for (const [${keyName}] of Object.entries(${iterJs})) {`;
|
|
698
|
-
} else {
|
|
699
|
-
// Default to .entries() for arrays, Object.entries for objects
|
|
700
|
-
this._usesSliceGuard = true;
|
|
701
|
-
loopHeader = `for (const [${keyName}, ${valName}] of __s(${iterJs}).entries()) {`;
|
|
702
|
-
}
|
|
703
|
-
this.line(loopHeader);
|
|
704
|
-
this.indented(() => this._genTemplNodes(templBody, mountParent));
|
|
705
|
-
this.line("}");
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
// Parse a token array as a Go expression and emit JS.
|
|
709
|
-
_genTemplTokenExpr(tokens) {
|
|
710
|
-
if (!tokens || tokens.length === 0) return '""';
|
|
711
|
-
const eofTok = new Token(T.EOF, "", 1, 1);
|
|
712
|
-
const p = new Parser([...tokens, eofTok]);
|
|
713
|
-
const ast = p.parseExpr();
|
|
714
|
-
return this.genExpr(ast);
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
// Parse a token array as a Go call expression and emit JS.
|
|
718
|
-
_genTemplTokenCallExpr(tokens) {
|
|
719
|
-
if (!tokens || tokens.length === 0) return "null";
|
|
720
|
-
const eofTok = new Token(T.EOF, "", 1, 1);
|
|
721
|
-
const p = new Parser([...tokens, eofTok]);
|
|
722
|
-
const ast = p.parseExpr();
|
|
723
|
-
return this.genExpr(ast);
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
// Extract condition JS from condTokens (which may start with `if`).
|
|
727
|
-
_genTemplCondTokens(tokens) {
|
|
728
|
-
if (!tokens || tokens.length === 0) return "false";
|
|
729
|
-
let toks = tokens;
|
|
730
|
-
if (toks[0]?.type === T.IF) toks = toks.slice(1); // skip `if` keyword
|
|
731
|
-
const eofTok = new Token(T.EOF, "", 1, 1);
|
|
732
|
-
const p = new Parser([...toks, eofTok]);
|
|
733
|
-
const ast = p.parseExpr();
|
|
734
|
-
return this.genExpr(ast);
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
_tokensToSource(tokens) {
|
|
738
|
-
return tokens.map((t) => t.value).join(" ");
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
// Fresh variable name counter
|
|
742
|
-
_freshVar(prefix = "v") {
|
|
743
|
-
this._templVarCount = (this._templVarCount ?? 0) + 1;
|
|
744
|
-
return `___${prefix}${this._templVarCount}`;
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
_withNamedReturns(decl, fn) {
|
|
748
|
-
const named = decl.returnType?._namedReturns;
|
|
749
|
-
const prev = this.namedReturnVars;
|
|
750
|
-
if (named) {
|
|
751
|
-
// Emit zero-value declarations for named return vars
|
|
752
|
-
for (const { name, type } of named) {
|
|
753
|
-
if (name)
|
|
754
|
-
this.line(`let ${name} = ${this.zeroValueForTypeNode(type)};`);
|
|
755
|
-
}
|
|
756
|
-
this.namedReturnVars = named.map((r) => r.name).filter(Boolean);
|
|
757
|
-
} else {
|
|
758
|
-
this.namedReturnVars = null;
|
|
759
|
-
}
|
|
760
|
-
fn();
|
|
761
|
-
this.namedReturnVars = prev;
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
// Emit a function body, wrapping in try/catch/finally for defer if needed.
|
|
765
|
-
_genBody(body) {
|
|
766
|
-
if (!body._hasDefer) {
|
|
767
|
-
this.genBlock(body);
|
|
768
|
-
return;
|
|
769
|
-
}
|
|
770
|
-
this.line("const __defers = [];");
|
|
771
|
-
this.line("let __panic = null;");
|
|
772
|
-
this.line("try {");
|
|
773
|
-
this.indented(() => this.genBlock(body));
|
|
774
|
-
this.line("} catch (__err) {");
|
|
775
|
-
this.indented(() => this.line("__panic = __err;"));
|
|
776
|
-
this.line("} finally {");
|
|
777
|
-
this.indented(() => {
|
|
778
|
-
this.line(
|
|
779
|
-
"for (let __i = __defers.length - 1; __i >= 0; __i--) __defers[__i]();",
|
|
780
|
-
);
|
|
781
|
-
this.line("if (__panic !== null) throw __panic;");
|
|
782
|
-
});
|
|
783
|
-
this.line("}");
|
|
784
|
-
// If a recover() cleared __panic, execution reaches here.
|
|
785
|
-
// Return named return vars so deferred mutations are visible to the caller.
|
|
786
|
-
if (this.namedReturnVars?.length > 0) {
|
|
787
|
-
const vars = this.namedReturnVars;
|
|
788
|
-
this.line(
|
|
789
|
-
vars.length === 1
|
|
790
|
-
? `return ${vars[0]};`
|
|
791
|
-
: `return [${vars.join(", ")}];`,
|
|
792
|
-
);
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
// Scan AST node for _addressTaken idents on scalars and populate _boxedVars.
|
|
797
|
-
_scanAddressTaken(node) {
|
|
798
|
-
if (!node || typeof node !== "object") return;
|
|
799
|
-
if (Array.isArray(node)) {
|
|
800
|
-
for (const child of node) this._scanAddressTaken(child);
|
|
801
|
-
return;
|
|
802
|
-
}
|
|
803
|
-
// &x — the operand ident will have _addressTaken set by typechecker
|
|
804
|
-
if (node.kind === "Ident" && node._addressTaken) {
|
|
805
|
-
// Check if the type is a scalar (needs boxing) vs reference type (no boxing)
|
|
806
|
-
const t = node._type;
|
|
807
|
-
if (t && !this._isReferenceType(t)) {
|
|
808
|
-
this._boxedVars.add(node.name);
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
// Recurse into FuncLit too — closures may take address of outer vars
|
|
812
|
-
for (const key of Object.keys(node)) {
|
|
813
|
-
if (key.startsWith("_")) continue;
|
|
814
|
-
this._scanAddressTaken(node[key]);
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
_isReferenceType(t) {
|
|
819
|
-
if (!t) return false;
|
|
820
|
-
const base = t.kind === "named" ? t.underlying : t;
|
|
821
|
-
return (
|
|
822
|
-
base?.kind === "struct" ||
|
|
823
|
-
base?.kind === "slice" ||
|
|
824
|
-
base?.kind === "map" ||
|
|
825
|
-
base?.kind === "func" ||
|
|
826
|
-
base?.kind === "interface"
|
|
827
|
-
);
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
// ── Variable / const declarations ────────────────────────────
|
|
831
|
-
|
|
832
|
-
genVarDecl(decl) {
|
|
833
|
-
for (const spec of decl.decls) {
|
|
834
|
-
if (spec.value) {
|
|
835
|
-
const vals = spec.value.map((v) => {
|
|
836
|
-
const js = this.genExpr(v);
|
|
837
|
-
// Wrap numeric values assigned to complex-typed vars
|
|
838
|
-
if (
|
|
839
|
-
spec.type?.name === "complex128" ||
|
|
840
|
-
spec.type?.name === "complex64"
|
|
841
|
-
) {
|
|
842
|
-
if (
|
|
843
|
-
!isComplex(v._type) &&
|
|
844
|
-
(isNumeric(v._type) || v._type?.kind === "untyped")
|
|
845
|
-
) {
|
|
846
|
-
return `{ re: ${js}, im: 0 }`;
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
return js;
|
|
850
|
-
});
|
|
851
|
-
if (spec.names.length === 1) {
|
|
852
|
-
this.line(`let ${spec.names[0]} = ${vals[0]};`);
|
|
853
|
-
} else {
|
|
854
|
-
// let [a, b] = [v1, v2]
|
|
855
|
-
this.line(`let [${spec.names.join(", ")}] = [${vals.join(", ")}];`);
|
|
856
|
-
}
|
|
857
|
-
} else {
|
|
858
|
-
const zero = spec.type ? this.zeroValueForTypeNode(spec.type) : "null";
|
|
859
|
-
for (const name of spec.names) {
|
|
860
|
-
const val = this._boxedVars.has(name) ? `{ value: ${zero} }` : zero;
|
|
861
|
-
this.line(`let ${name} = ${val};`);
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
genConstDecl(decl) {
|
|
868
|
-
for (const spec of decl.decls) {
|
|
869
|
-
const vals = spec.value.map((v) => this.genExpr(v));
|
|
870
|
-
if (spec.names.length === 1) {
|
|
871
|
-
this.line(`const ${spec.names[0]} = ${vals[0]};`);
|
|
872
|
-
} else {
|
|
873
|
-
this.line(`const [${spec.names.join(", ")}] = [${vals.join(", ")}];`);
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
// ── Statements ───────────────────────────────────────────────
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
Object.assign(CodeGen.prototype, statementGenMethods);
|
|
882
|
-
Object.assign(CodeGen.prototype, expressionGenMethods);
|