gofront 0.0.2 → 0.0.3

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.
@@ -0,0 +1,45 @@
1
+ // Minimal VLQ / source-map helpers for GoFront code generation.
2
+
3
+ // ── Minimal VLQ / source-map helpers ─────────────────────────
4
+ const B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
5
+
6
+ function vlqEncode(value) {
7
+ // Signed VLQ: sign bit in LSB of first group, then 5-bit continuation chunks
8
+ let vlq = value < 0 ? (-value << 1) | 1 : value << 1;
9
+ let result = "";
10
+ do {
11
+ let digit = vlq & 0x1f;
12
+ vlq >>>= 5;
13
+ if (vlq > 0) digit |= 0x20; // continuation bit
14
+ result += B64[digit];
15
+ } while (vlq > 0);
16
+ return result;
17
+ }
18
+
19
+ export function buildSourceMap(sourceName, mappings) {
20
+ // mappings: Array<{ genLine: number, srcLine: number }> (0-based)
21
+ // Emits one segment per generated line, column 0 → source line (delta-encoded).
22
+ const lines = [];
23
+ let prevSrcLine = 0;
24
+ const maxGen = mappings.reduce((m, e) => Math.max(m, e.genLine), -1);
25
+ const byLine = new Map(mappings.map((e) => [e.genLine, e]));
26
+ for (let g = 0; g <= maxGen; g++) {
27
+ const entry = byLine.get(g);
28
+ if (entry) {
29
+ const srcDelta = entry.srcLine - prevSrcLine;
30
+ // Segment: [genCol=0, srcFileIdx=0, srcLineDelta, srcCol=0]
31
+ lines.push(
32
+ vlqEncode(0) + vlqEncode(0) + vlqEncode(srcDelta) + vlqEncode(0),
33
+ );
34
+ prevSrcLine = entry.srcLine;
35
+ } else {
36
+ lines.push(""); // no mapping for this generated line
37
+ }
38
+ }
39
+ return JSON.stringify({
40
+ version: 3,
41
+ sources: [sourceName],
42
+ names: [],
43
+ mappings: lines.join(";"),
44
+ });
45
+ }
@@ -0,0 +1,406 @@
1
+ // CodeGen statement methods — installed as a mixin on CodeGen.prototype.
2
+
3
+ export const statementGenMethods = {
4
+ genBlock(block) {
5
+ for (const stmt of block.stmts) this.genStmt(stmt);
6
+ },
7
+
8
+ genStmt(stmt) {
9
+ // Record source mapping for the first line this statement produces
10
+ const srcLine = stmt._line ?? null;
11
+ const _line0 = this.out.length;
12
+ switch (stmt.kind) {
13
+ case "VarDecl":
14
+ this.genVarDecl(stmt);
15
+ break;
16
+ case "ConstDecl":
17
+ this.genConstDecl(stmt);
18
+ break;
19
+ case "TypeDecl":
20
+ this.genTypeDeclWithMethods(stmt, []);
21
+ break;
22
+
23
+ case "DefineStmt": {
24
+ // Map comma-ok: v, ok := m[key] → let v = m[key]; let ok = key in m;
25
+ const rhsNode = stmt.rhs[0];
26
+ if (
27
+ stmt.rhs.length === 1 &&
28
+ stmt.lhs.length === 2 &&
29
+ rhsNode?.kind === "IndexExpr" &&
30
+ rhsNode.expr?._type?.kind === "map"
31
+ ) {
32
+ const mapExpr = this.genExpr(rhsNode.expr);
33
+ const keyExpr = this.genExpr(rhsNode.index);
34
+ const [vName, okName] = stmt.lhs.map(
35
+ (e) => e.name ?? this.genExpr(e),
36
+ );
37
+ if (vName !== "_")
38
+ this.line(`let ${vName} = ${mapExpr}[${keyExpr}];`);
39
+ if (okName !== "_")
40
+ this.line(`let ${okName} = (${keyExpr}) in ${mapExpr};`);
41
+ break;
42
+ }
43
+ const rhs = stmt.rhs.map((e) => this.genExpr(e));
44
+ const lhs = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
45
+ const redecls = stmt.lhs.map((e) => !!e._redecl);
46
+ const anyRedecl = redecls.some((r) => r);
47
+ if (lhs.length === 1) {
48
+ if (redecls[0]) {
49
+ this.line(`${lhs[0]} = ${rhs[0]};`);
50
+ } else {
51
+ this.line(`let ${lhs[0]} = ${rhs[0]};`);
52
+ }
53
+ } else if (!anyRedecl) {
54
+ // All new: let [a, b] = ...
55
+ const rhsStr = rhs.length === 1 ? rhs[0] : `[${rhs.join(", ")}]`;
56
+ this.line(`let [${lhs.join(", ")}] = ${rhsStr};`);
57
+ } else {
58
+ // Mixed new/existing: emit separate statements
59
+ // First compute the tuple into a temp if needed
60
+ if (rhs.length === 1 && lhs.length > 1) {
61
+ const tmp = "__t";
62
+ this.line(`let ${tmp} = ${rhs[0]};`);
63
+ for (let i = 0; i < lhs.length; i++) {
64
+ const prefix = redecls[i] ? "" : "let ";
65
+ this.line(`${prefix}${lhs[i]} = ${tmp}[${i}];`);
66
+ }
67
+ } else {
68
+ for (let i = 0; i < lhs.length; i++) {
69
+ const prefix = redecls[i] ? "" : "let ";
70
+ this.line(`${prefix}${lhs[i]} = ${rhs[i]};`);
71
+ }
72
+ }
73
+ }
74
+ break;
75
+ }
76
+
77
+ case "AssignStmt": {
78
+ const rhs = stmt.rhs.map((e) => this.genExpr(e));
79
+ for (const e of stmt.lhs) if (e.kind === "IndexExpr") e._lvalue = true;
80
+ const lhs = stmt.lhs.map((e) => this.genExpr(e));
81
+ if (lhs.length === 1) {
82
+ this.line(`${lhs[0]} ${stmt.op} ${rhs[0]};`);
83
+ } else {
84
+ const rhsStr = rhs.length === 1 ? rhs[0] : `[${rhs.join(", ")}]`;
85
+ this.line(`[${lhs.join(", ")}] = ${rhsStr};`);
86
+ }
87
+ break;
88
+ }
89
+
90
+ case "IncDecStmt":
91
+ this.line(`${this.genExpr(stmt.expr)}${stmt.op};`);
92
+ break;
93
+
94
+ case "ExprStmt":
95
+ this.line(`${this.genExpr(stmt.expr)};`);
96
+ break;
97
+
98
+ case "ReturnStmt": {
99
+ if (stmt.values.length === 0) {
100
+ // bare return — emit named return vars if present
101
+ if (this.namedReturnVars?.length > 0) {
102
+ const vars = this.namedReturnVars;
103
+ this.line(
104
+ vars.length === 1
105
+ ? `return ${vars[0]};`
106
+ : `return [${vars.join(", ")}];`,
107
+ );
108
+ } else {
109
+ this.line("return;");
110
+ }
111
+ } else if (stmt.values.length === 1) {
112
+ this.line(`return ${this.genExpr(stmt.values[0])};`);
113
+ } else {
114
+ this.line(
115
+ `return [${stmt.values.map((v) => this.genExpr(v)).join(", ")}];`,
116
+ );
117
+ }
118
+ break;
119
+ }
120
+
121
+ case "IfStmt": {
122
+ if (stmt.init) {
123
+ // Wrap in a block so init variable is scoped
124
+ this.line("{");
125
+ this.indented(() => {
126
+ this.genStmt(stmt.init);
127
+ this._genIf(stmt);
128
+ });
129
+ this.line("}");
130
+ } else {
131
+ this._genIf(stmt);
132
+ }
133
+ break;
134
+ }
135
+
136
+ case "ForStmt":
137
+ this.genFor(stmt);
138
+ break;
139
+
140
+ case "SwitchStmt":
141
+ this.genSwitch(stmt);
142
+ break;
143
+
144
+ case "TypeSwitchStmt":
145
+ this.genTypeSwitch(stmt);
146
+ break;
147
+
148
+ case "DeferStmt":
149
+ this.line(`__defers.push(() => { ${this.genExpr(stmt.call)}; });`);
150
+ break;
151
+
152
+ case "LabeledStmt":
153
+ this.line(`${stmt.label}:`);
154
+ this.genStmt(stmt.body);
155
+ break;
156
+
157
+ case "BranchStmt":
158
+ if (stmt.keyword !== "fallthrough")
159
+ this.line(
160
+ stmt.label ? `${stmt.keyword} ${stmt.label};` : `${stmt.keyword};`,
161
+ );
162
+ // fallthrough: omit — JS switch falls through naturally without a break
163
+ break;
164
+
165
+ case "Block":
166
+ this.line("{");
167
+ this.indented(() => this.genBlock(stmt));
168
+ this.line("}");
169
+ break;
170
+
171
+ default:
172
+ throw new Error(`CodeGen: unhandled statement kind '${stmt.kind}'`);
173
+ }
174
+ // Record source mapping: first output line this statement produced
175
+ if (srcLine != null && this.out.length > _line0) {
176
+ this._srcMappings.push({ genLine: _line0, srcLine: srcLine - 1 });
177
+ }
178
+ },
179
+
180
+ _genIf(stmt) {
181
+ this.line(`if (${this.genExpr(stmt.cond)}) {`);
182
+ this.indented(() => this.genBlock(stmt.body));
183
+ this._genElse(stmt.elseBody);
184
+ },
185
+
186
+ _genElse(elseBody) {
187
+ if (!elseBody) {
188
+ this.line("}");
189
+ return;
190
+ }
191
+ if (elseBody.kind === "IfStmt") {
192
+ this.line(`} else if (${this.genExpr(elseBody.cond)}) {`);
193
+ this.indented(() => this.genBlock(elseBody.body));
194
+ this._genElse(elseBody.elseBody);
195
+ } else {
196
+ this.line("} else {");
197
+ this.indented(() => this.genBlock(elseBody));
198
+ this.line("}");
199
+ }
200
+ },
201
+
202
+ genFor(stmt) {
203
+ // Detect range: init is DefineStmt with rhs[0] being a RangeExpr
204
+ if (this.isRangeFor(stmt)) {
205
+ this.genRangeFor(stmt);
206
+ return;
207
+ }
208
+
209
+ // for range N { } — no variable, just repeat N times
210
+ if (stmt.cond?.kind === "RangeExpr") {
211
+ const iteree = this.genExpr(stmt.cond.expr);
212
+ this.line(`for (let _$ = 0; _$ < ${iteree}; _$++) {`);
213
+ this.indented(() => this.genBlock(stmt.body));
214
+ this.line("}");
215
+ return;
216
+ }
217
+
218
+ if (!stmt.init && !stmt.post) {
219
+ if (!stmt.cond) {
220
+ // infinite loop
221
+ this.line("while (true) {");
222
+ } else {
223
+ this.line(`while (${this.genExpr(stmt.cond)}) {`);
224
+ }
225
+ this.indented(() => this.genBlock(stmt.body));
226
+ this.line("}");
227
+ return;
228
+ }
229
+
230
+ // Standard C-style for
231
+ const init = stmt.init ? this.stmtInline(stmt.init) : "";
232
+ const cond = stmt.cond ? this.genExpr(stmt.cond) : "";
233
+ const post = stmt.post ? this.stmtInline(stmt.post) : "";
234
+ this.line(`for (${init}; ${cond}; ${post}) {`);
235
+ this.indented(() => this.genBlock(stmt.body));
236
+ this.line("}");
237
+ },
238
+
239
+ isRangeFor(stmt) {
240
+ if (!stmt.init) return false;
241
+ const init = stmt.init;
242
+ if (init.kind !== "DefineStmt" && init.kind !== "AssignStmt") return false;
243
+ return init.rhs?.[0]?.kind === "RangeExpr";
244
+ },
245
+
246
+ genRangeFor(stmt) {
247
+ const init = stmt.init;
248
+ const range = init.rhs[0];
249
+ const lhs = init.lhs.map((e) => e.name ?? this.genExpr(e));
250
+ const iteree = this.genExpr(range.expr);
251
+ const iterType = range.expr._type;
252
+
253
+ let iterExpr;
254
+ if (
255
+ lhs.length <= 1 &&
256
+ ((iterType?.kind === "basic" &&
257
+ (iterType.name === "int" || iterType.name === "float64")) ||
258
+ (iterType?.kind === "untyped" &&
259
+ (iterType.base === "int" || iterType.base === "float64")))
260
+ ) {
261
+ // integer range (Go 1.22): for i := range n → C-style for loop
262
+ const v = lhs[0] === "_" ? "_$" : lhs[0];
263
+ this.line(`for (let ${v} = 0; ${v} < ${iteree}; ${v}++) {`);
264
+ this.indented(() => this.genBlock(stmt.body));
265
+ this.line("}");
266
+ return;
267
+ }
268
+ if (
269
+ iterType?.kind === "map" ||
270
+ (iterType?.kind === "named" && iterType.underlying?.kind === "map")
271
+ ) {
272
+ // map: for k, v := range m → for (const [k, v] of Object.entries(m))
273
+ iterExpr = `Object.entries(${iteree})`;
274
+ } else if (
275
+ (iterType?.kind === "basic" && iterType.name === "string") ||
276
+ (iterType?.kind === "untyped" && iterType.base === "string")
277
+ ) {
278
+ // string: for i, ch := range s → for (const [i, ch] of s.split('').entries())
279
+ iterExpr =
280
+ lhs.length === 1
281
+ ? `Array.from(${iteree}).keys()`
282
+ : `Array.from(${iteree}).entries()`;
283
+ } else {
284
+ // slice/array: for i, v := range arr → for (const [i, v] of __s(arr).entries())
285
+ // null-guard handles nil slices (zero value) gracefully.
286
+ this._usesSliceGuard = true;
287
+ if (lhs.length === 1) {
288
+ iterExpr = `__s(${iteree}).keys()`;
289
+ } else {
290
+ iterExpr = `__s(${iteree}).entries()`;
291
+ }
292
+ }
293
+
294
+ const binding =
295
+ lhs.length === 1
296
+ ? lhs[0] === "_"
297
+ ? "_$"
298
+ : lhs[0]
299
+ : `[${lhs.map((n) => (n === "_" ? "_$" : n)).join(", ")}]`;
300
+
301
+ this.line(`for (const ${binding} of ${iterExpr}) {`);
302
+ this.indented(() => this.genBlock(stmt.body));
303
+ this.line("}");
304
+ },
305
+
306
+ // Inline a statement as a for-init/post string (no semicolon)
307
+ stmtInline(stmt) {
308
+ switch (stmt.kind) {
309
+ case "DefineStmt": {
310
+ const rhs = stmt.rhs.map((e) => this.genExpr(e)).join(", ");
311
+ const lhs = stmt.lhs.map((e) => e.name ?? this.genExpr(e)).join(", ");
312
+ return `let ${lhs} = ${rhs}`;
313
+ }
314
+ case "AssignStmt": {
315
+ const rhs = stmt.rhs.map((e) => this.genExpr(e)).join(", ");
316
+ const lhs = stmt.lhs.map((e) => this.genExpr(e)).join(", ");
317
+ return `${lhs} ${stmt.op} ${rhs}`;
318
+ }
319
+ case "IncDecStmt":
320
+ return `${this.genExpr(stmt.expr)}${stmt.op}`;
321
+ case "ExprStmt":
322
+ return this.genExpr(stmt.expr);
323
+ default:
324
+ return "";
325
+ }
326
+ },
327
+
328
+ genSwitch(stmt) {
329
+ const genBody = () => {
330
+ const tag = stmt.tag ? this.genExpr(stmt.tag) : "true";
331
+ this.line(`switch (${tag}) {`);
332
+ this.indented(() => {
333
+ for (const c of stmt.cases) {
334
+ if (c.list) {
335
+ for (const e of c.list) this.line(`case ${this.genExpr(e)}:`);
336
+ } else {
337
+ this.line("default:");
338
+ }
339
+ // Wrap each case body in {} so `let` declarations don't bleed
340
+ // across sibling cases (JS switch shares one block scope).
341
+ this.line("{");
342
+ this.indented(() => {
343
+ for (const s of c.stmts) this.genStmt(s);
344
+ // Go doesn't fall through by default — add break unless last stmt is return/break
345
+ const last = c.stmts[c.stmts.length - 1];
346
+ if (
347
+ !last ||
348
+ (last.kind !== "ReturnStmt" && last.kind !== "BranchStmt")
349
+ ) {
350
+ this.line("break;");
351
+ }
352
+ });
353
+ this.line("}");
354
+ }
355
+ });
356
+ this.line("}");
357
+ };
358
+
359
+ if (stmt.init) {
360
+ this.line("{");
361
+ this.indented(() => {
362
+ this.genStmt(stmt.init);
363
+ genBody();
364
+ });
365
+ this.line("}");
366
+ } else {
367
+ genBody();
368
+ }
369
+ },
370
+
371
+ genTypeSwitch(stmt) {
372
+ this.line("{");
373
+ this.indented(() => {
374
+ const val = this.genExpr(stmt.expr);
375
+ this.line(`const __tsw = ${val};`);
376
+ if (stmt.assign) this.line(`let ${stmt.assign} = __tsw;`);
377
+
378
+ let first = true;
379
+ let hasDefault = false;
380
+ for (const c of stmt.cases) {
381
+ if (!c.types) {
382
+ // default case — emit last
383
+ hasDefault = c;
384
+ continue;
385
+ }
386
+ const cond = c.types
387
+ .map((t) => this._typeCheckExpr(t, "__tsw"))
388
+ .join(" || ");
389
+ this.line(`${first ? "if" : "else if"} (${cond}) {`);
390
+ this.indented(() => {
391
+ for (const s of c.stmts) this.genStmt(s);
392
+ });
393
+ this.line("}");
394
+ first = false;
395
+ }
396
+ if (hasDefault) {
397
+ this.line(first ? "{" : "else {");
398
+ this.indented(() => {
399
+ for (const s of hasDefault.stmts) this.genStmt(s);
400
+ });
401
+ this.line("}");
402
+ }
403
+ });
404
+ this.line("}");
405
+ },
406
+ };