gofront 0.0.2 → 0.0.4

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,464 @@
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
+ // comma-ok map index: v, ok = m["key"]
79
+ if (stmt._commaOkMap) {
80
+ const rhsNode = stmt.rhs[0];
81
+ const mapExpr = this.genExpr(rhsNode.expr);
82
+ const keyExpr = this.genExpr(rhsNode.index);
83
+ const [vName, okName] = stmt.lhs.map(
84
+ (e) => e.name ?? this.genExpr(e),
85
+ );
86
+ if (vName !== "_") {
87
+ const zero = rhsNode._mapValueType
88
+ ? this.zeroValueForType(rhsNode._mapValueType)
89
+ : "undefined";
90
+ this.line(
91
+ `${vName} = (${keyExpr}) in ${mapExpr} ? ${mapExpr}[${keyExpr}] : ${zero};`,
92
+ );
93
+ }
94
+ if (okName !== "_")
95
+ this.line(`${okName} = (${keyExpr}) in ${mapExpr};`);
96
+ break;
97
+ }
98
+ // comma-ok type assertion: v, ok = x.(T)
99
+ if (
100
+ stmt.lhs.length === 2 &&
101
+ stmt.rhs.length === 1 &&
102
+ stmt.rhs[0]._commaOk
103
+ ) {
104
+ const val = this.genExpr(stmt.rhs[0]);
105
+ const [vName, okName] = stmt.lhs.map(
106
+ (e) => e.name ?? this.genExpr(e),
107
+ );
108
+ const tmp = "__ta";
109
+ this.line(`let ${tmp} = ${val};`);
110
+ if (vName !== "_") this.line(`${vName} = ${tmp}[0];`);
111
+ if (okName !== "_") this.line(`${okName} = ${tmp}[1];`);
112
+ break;
113
+ }
114
+ const rhs = stmt.rhs.map((e) => this.genExpr(e));
115
+ for (const e of stmt.lhs) if (e.kind === "IndexExpr") e._lvalue = true;
116
+ const lhs = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
117
+ // Filter out blank identifier assignments entirely
118
+ const pairs = lhs.map((l, i) => ({ l, r: rhs[i] ?? rhs[0] }));
119
+ const active = pairs.filter((p) => p.l !== "_");
120
+ if (active.length === 0) {
121
+ // All _ — still need to evaluate rhs for side effects
122
+ if (rhs.length > 0) this.line(`${rhs[0]};`);
123
+ } else if (lhs.length === 1) {
124
+ this.line(`${active[0].l} ${stmt.op} ${active[0].r};`);
125
+ } else {
126
+ // multi-assign: emit only non-blank targets
127
+ const rhsStr = rhs.length === 1 ? rhs[0] : `[${rhs.join(", ")}]`;
128
+ if (active.length === pairs.length) {
129
+ this.line(`[${lhs.join(", ")}] = ${rhsStr};`);
130
+ } else {
131
+ // Some blanks — use temp and assign only non-blank
132
+ const tmp = "__t";
133
+ this.line(`let ${tmp} = ${rhsStr};`);
134
+ for (let i = 0; i < lhs.length; i++) {
135
+ if (lhs[i] !== "_") {
136
+ const src = rhs.length === 1 ? `${tmp}[${i}]` : rhs[i];
137
+ this.line(`${lhs[i]} ${stmt.op} ${src};`);
138
+ }
139
+ }
140
+ }
141
+ }
142
+ break;
143
+ }
144
+
145
+ case "IncDecStmt":
146
+ this.line(`${this.genExpr(stmt.expr)}${stmt.op};`);
147
+ break;
148
+
149
+ case "ExprStmt":
150
+ this.line(`${this.genExpr(stmt.expr)};`);
151
+ break;
152
+
153
+ case "ReturnStmt": {
154
+ if (stmt.values.length === 0) {
155
+ // bare return — emit named return vars if present
156
+ if (this.namedReturnVars?.length > 0) {
157
+ const vars = this.namedReturnVars;
158
+ this.line(
159
+ vars.length === 1
160
+ ? `return ${vars[0]};`
161
+ : `return [${vars.join(", ")}];`,
162
+ );
163
+ } else {
164
+ this.line("return;");
165
+ }
166
+ } else if (stmt.values.length === 1) {
167
+ this.line(`return ${this.genExpr(stmt.values[0])};`);
168
+ } else {
169
+ this.line(
170
+ `return [${stmt.values.map((v) => this.genExpr(v)).join(", ")}];`,
171
+ );
172
+ }
173
+ break;
174
+ }
175
+
176
+ case "IfStmt": {
177
+ if (stmt.init) {
178
+ // Wrap in a block so init variable is scoped
179
+ this.line("{");
180
+ this.indented(() => {
181
+ this.genStmt(stmt.init);
182
+ this._genIf(stmt);
183
+ });
184
+ this.line("}");
185
+ } else {
186
+ this._genIf(stmt);
187
+ }
188
+ break;
189
+ }
190
+
191
+ case "ForStmt":
192
+ this.genFor(stmt);
193
+ break;
194
+
195
+ case "SwitchStmt":
196
+ this.genSwitch(stmt);
197
+ break;
198
+
199
+ case "TypeSwitchStmt":
200
+ this.genTypeSwitch(stmt);
201
+ break;
202
+
203
+ case "DeferStmt":
204
+ this.line(`__defers.push(() => { ${this.genExpr(stmt.call)}; });`);
205
+ break;
206
+
207
+ case "LabeledStmt":
208
+ this.line(`${stmt.label}:`);
209
+ this.genStmt(stmt.body);
210
+ break;
211
+
212
+ case "BranchStmt":
213
+ if (stmt.keyword !== "fallthrough")
214
+ this.line(
215
+ stmt.label ? `${stmt.keyword} ${stmt.label};` : `${stmt.keyword};`,
216
+ );
217
+ // fallthrough: omit — JS switch falls through naturally without a break
218
+ break;
219
+
220
+ case "Block":
221
+ this.line("{");
222
+ this.indented(() => this.genBlock(stmt));
223
+ this.line("}");
224
+ break;
225
+
226
+ default:
227
+ throw new Error(`CodeGen: unhandled statement kind '${stmt.kind}'`);
228
+ }
229
+ // Record source mapping: first output line this statement produced
230
+ if (srcLine != null && this.out.length > _line0) {
231
+ this._srcMappings.push({ genLine: _line0, srcLine: srcLine - 1 });
232
+ }
233
+ },
234
+
235
+ _genIf(stmt) {
236
+ this.line(`if (${this.genExpr(stmt.cond)}) {`);
237
+ this.indented(() => this.genBlock(stmt.body));
238
+ this._genElse(stmt.elseBody);
239
+ },
240
+
241
+ _genElse(elseBody) {
242
+ if (!elseBody) {
243
+ this.line("}");
244
+ return;
245
+ }
246
+ if (elseBody.kind === "IfStmt") {
247
+ this.line(`} else if (${this.genExpr(elseBody.cond)}) {`);
248
+ this.indented(() => this.genBlock(elseBody.body));
249
+ this._genElse(elseBody.elseBody);
250
+ } else {
251
+ this.line("} else {");
252
+ this.indented(() => this.genBlock(elseBody));
253
+ this.line("}");
254
+ }
255
+ },
256
+
257
+ genFor(stmt) {
258
+ // Detect range: init is DefineStmt with rhs[0] being a RangeExpr
259
+ if (this.isRangeFor(stmt)) {
260
+ this.genRangeFor(stmt);
261
+ return;
262
+ }
263
+
264
+ // for range N { } — no variable, just repeat N times
265
+ if (stmt.cond?.kind === "RangeExpr") {
266
+ const iteree = this.genExpr(stmt.cond.expr);
267
+ this.line(`for (let _$ = 0; _$ < ${iteree}; _$++) {`);
268
+ this.indented(() => this.genBlock(stmt.body));
269
+ this.line("}");
270
+ return;
271
+ }
272
+
273
+ if (!stmt.init && !stmt.post) {
274
+ if (!stmt.cond) {
275
+ // infinite loop
276
+ this.line("while (true) {");
277
+ } else {
278
+ this.line(`while (${this.genExpr(stmt.cond)}) {`);
279
+ }
280
+ this.indented(() => this.genBlock(stmt.body));
281
+ this.line("}");
282
+ return;
283
+ }
284
+
285
+ // Standard C-style for
286
+ const init = stmt.init ? this.stmtInline(stmt.init) : "";
287
+ const cond = stmt.cond ? this.genExpr(stmt.cond) : "";
288
+ const post = stmt.post ? this.stmtInline(stmt.post) : "";
289
+ this.line(`for (${init}; ${cond}; ${post}) {`);
290
+ this.indented(() => this.genBlock(stmt.body));
291
+ this.line("}");
292
+ },
293
+
294
+ isRangeFor(stmt) {
295
+ if (!stmt.init) return false;
296
+ const init = stmt.init;
297
+ if (init.kind !== "DefineStmt" && init.kind !== "AssignStmt") return false;
298
+ return init.rhs?.[0]?.kind === "RangeExpr";
299
+ },
300
+
301
+ genRangeFor(stmt) {
302
+ const init = stmt.init;
303
+ const range = init.rhs[0];
304
+ const lhs = init.lhs.map((e) => e.name ?? this.genExpr(e));
305
+ const iteree = this.genExpr(range.expr);
306
+ const iterType = range.expr._type;
307
+
308
+ let iterExpr;
309
+ if (
310
+ lhs.length <= 1 &&
311
+ ((iterType?.kind === "basic" &&
312
+ (iterType.name === "int" || iterType.name === "float64")) ||
313
+ (iterType?.kind === "untyped" &&
314
+ (iterType.base === "int" || iterType.base === "float64")))
315
+ ) {
316
+ // integer range (Go 1.22): for i := range n → C-style for loop
317
+ const v = lhs[0] === "_" ? "_$" : lhs[0];
318
+ this.line(`for (let ${v} = 0; ${v} < ${iteree}; ${v}++) {`);
319
+ this.indented(() => this.genBlock(stmt.body));
320
+ this.line("}");
321
+ return;
322
+ }
323
+ if (
324
+ iterType?.kind === "map" ||
325
+ (iterType?.kind === "named" && iterType.underlying?.kind === "map")
326
+ ) {
327
+ // map: for k, v := range m → for (const [k, v] of Object.entries(m))
328
+ iterExpr = `Object.entries(${iteree})`;
329
+ } else if (
330
+ (iterType?.kind === "basic" && iterType.name === "string") ||
331
+ (iterType?.kind === "untyped" && iterType.base === "string")
332
+ ) {
333
+ // string: for i, r := range s → rune (code point), not JS character
334
+ // Go spec: iterating a string yields (byte-index, rune) pairs
335
+ if (lhs.length === 1) {
336
+ iterExpr = `Array.from(${iteree}).keys()`;
337
+ } else {
338
+ // Wrap code points so the value is an integer (rune), not a JS string
339
+ iterExpr = `Array.from(${iteree}, (__c, __i) => [__i, __c.codePointAt(0)])`;
340
+ }
341
+ } else {
342
+ // slice/array: for i, v := range arr → for (const [i, v] of __s(arr).entries())
343
+ // null-guard handles nil slices (zero value) gracefully.
344
+ this._usesSliceGuard = true;
345
+ if (lhs.length === 1) {
346
+ iterExpr = `__s(${iteree}).keys()`;
347
+ } else {
348
+ iterExpr = `__s(${iteree}).entries()`;
349
+ }
350
+ }
351
+
352
+ const binding =
353
+ lhs.length === 1
354
+ ? lhs[0] === "_"
355
+ ? "_$"
356
+ : lhs[0]
357
+ : `[${lhs.map((n) => (n === "_" ? "_$" : n)).join(", ")}]`;
358
+
359
+ this.line(`for (const ${binding} of ${iterExpr}) {`);
360
+ this.indented(() => this.genBlock(stmt.body));
361
+ this.line("}");
362
+ },
363
+
364
+ // Inline a statement as a for-init/post string (no semicolon)
365
+ stmtInline(stmt) {
366
+ switch (stmt.kind) {
367
+ case "DefineStmt": {
368
+ const rhs = stmt.rhs.map((e) => this.genExpr(e)).join(", ");
369
+ const lhs = stmt.lhs.map((e) => e.name ?? this.genExpr(e)).join(", ");
370
+ return `let ${lhs} = ${rhs}`;
371
+ }
372
+ case "AssignStmt": {
373
+ const rhs = stmt.rhs.map((e) => this.genExpr(e)).join(", ");
374
+ const lhs = stmt.lhs.map((e) => this.genExpr(e)).join(", ");
375
+ return `${lhs} ${stmt.op} ${rhs}`;
376
+ }
377
+ case "IncDecStmt":
378
+ return `${this.genExpr(stmt.expr)}${stmt.op}`;
379
+ case "ExprStmt":
380
+ return this.genExpr(stmt.expr);
381
+ default:
382
+ return "";
383
+ }
384
+ },
385
+
386
+ genSwitch(stmt) {
387
+ const genBody = () => {
388
+ const tag = stmt.tag ? this.genExpr(stmt.tag) : "true";
389
+ this.line(`switch (${tag}) {`);
390
+ this.indented(() => {
391
+ for (const c of stmt.cases) {
392
+ if (c.list) {
393
+ for (const e of c.list) this.line(`case ${this.genExpr(e)}:`);
394
+ } else {
395
+ this.line("default:");
396
+ }
397
+ // Wrap each case body in {} so `let` declarations don't bleed
398
+ // across sibling cases (JS switch shares one block scope).
399
+ this.line("{");
400
+ this.indented(() => {
401
+ for (const s of c.stmts) this.genStmt(s);
402
+ // Go doesn't fall through by default — add break unless last stmt is return/break
403
+ const last = c.stmts[c.stmts.length - 1];
404
+ if (
405
+ !last ||
406
+ (last.kind !== "ReturnStmt" && last.kind !== "BranchStmt")
407
+ ) {
408
+ this.line("break;");
409
+ }
410
+ });
411
+ this.line("}");
412
+ }
413
+ });
414
+ this.line("}");
415
+ };
416
+
417
+ if (stmt.init) {
418
+ this.line("{");
419
+ this.indented(() => {
420
+ this.genStmt(stmt.init);
421
+ genBody();
422
+ });
423
+ this.line("}");
424
+ } else {
425
+ genBody();
426
+ }
427
+ },
428
+
429
+ genTypeSwitch(stmt) {
430
+ this.line("{");
431
+ this.indented(() => {
432
+ const val = this.genExpr(stmt.expr);
433
+ this.line(`const __tsw = ${val};`);
434
+ if (stmt.assign) this.line(`let ${stmt.assign} = __tsw;`);
435
+
436
+ let first = true;
437
+ let hasDefault = false;
438
+ for (const c of stmt.cases) {
439
+ if (!c.types) {
440
+ // default case — emit last
441
+ hasDefault = c;
442
+ continue;
443
+ }
444
+ const cond = c.types
445
+ .map((t) => this._typeCheckExpr(t, "__tsw"))
446
+ .join(" || ");
447
+ this.line(`${first ? "if" : "else if"} (${cond}) {`);
448
+ this.indented(() => {
449
+ for (const s of c.stmts) this.genStmt(s);
450
+ });
451
+ this.line("}");
452
+ first = false;
453
+ }
454
+ if (hasDefault) {
455
+ this.line(first ? "{" : "else {");
456
+ this.indented(() => {
457
+ for (const s of hasDefault.stmts) this.genStmt(s);
458
+ });
459
+ this.line("}");
460
+ }
461
+ });
462
+ this.line("}");
463
+ },
464
+ };