gofront 0.0.1

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/src/parser.js ADDED
@@ -0,0 +1,1120 @@
1
+ // Recursive-descent parser. Produces an AST from a GoFront token stream.
2
+ //
3
+ // Node kinds used throughout the compiler:
4
+ // Program, PackageDecl, ImportDecl
5
+ // FuncDecl, MethodDecl, VarDecl, ConstDecl, TypeDecl
6
+ // StructType, InterfaceType, SliceType, MapType, FuncType, PointerType
7
+ // Block, IfStmt, ForStmt, RangeStmt, ReturnStmt, BranchStmt
8
+ // AssignStmt, DefineStmt, IncDecStmt, ExprStmt
9
+ // SwitchStmt, CaseClause
10
+ // Ident, BasicLit, UnaryExpr, BinaryExpr
11
+ // CallExpr, SelectorExpr, IndexExpr, SliceExpr
12
+ // CompositeLit, KeyValueExpr, FuncLit, AwaitExpr
13
+
14
+ import { T } from "./lexer.js";
15
+
16
+ export class ParseError extends Error {
17
+ constructor(msg, token, filename, sourceCode) {
18
+ const loc = filename
19
+ ? `${filename}:${token.line}:${token.col}`
20
+ : `${token.line}:${token.col}`;
21
+ let lineContext = "";
22
+ if (token.line && sourceCode) {
23
+ const lines = sourceCode.split("\n");
24
+ const lineStr = lines[token.line - 1];
25
+ if (lineStr !== undefined) {
26
+ lineContext = `\n ${token.line} | ${lineStr}`;
27
+ }
28
+ }
29
+ super(
30
+ `Parse error at ${loc}: ${msg} (got ${token.type} "${token.value}")${lineContext}`,
31
+ );
32
+ this.token = token;
33
+ }
34
+ }
35
+
36
+ // Binary operator precedence (higher = binds tighter, matching Go spec)
37
+ const PREC = {
38
+ [T.OR]: 1,
39
+ [T.AND]: 2,
40
+ [T.EQ]: 3,
41
+ [T.NEQ]: 3,
42
+ [T.LT]: 3,
43
+ [T.GT]: 3,
44
+ [T.LTE]: 3,
45
+ [T.GTE]: 3,
46
+ [T.PLUS]: 4,
47
+ [T.MINUS]: 4,
48
+ [T.PIPE]: 4,
49
+ [T.CARET]: 4,
50
+ [T.STAR]: 5,
51
+ [T.SLASH]: 5,
52
+ [T.PERCENT]: 5,
53
+ [T.LSHIFT]: 5,
54
+ [T.RSHIFT]: 5,
55
+ [T.AMP]: 5,
56
+ };
57
+
58
+ export class Parser {
59
+ constructor(tokens, filename = null, source = null) {
60
+ this.tokens = tokens;
61
+ this.pos = 0;
62
+ this.filename = filename;
63
+ this.source = source;
64
+ }
65
+
66
+ // ── Primitives ───────────────────────────────────────────────
67
+
68
+ peek() {
69
+ return this.tokens[this.pos];
70
+ }
71
+ peek2() {
72
+ return this.tokens[this.pos + 1];
73
+ }
74
+
75
+ advance() {
76
+ const t = this.tokens[this.pos];
77
+ if (t.type !== T.EOF) this.pos++;
78
+ return t;
79
+ }
80
+
81
+ check(type) {
82
+ return this.peek().type === type;
83
+ }
84
+ check2(type) {
85
+ return this.peek2().type === type;
86
+ }
87
+
88
+ match(...types) {
89
+ for (const type of types) {
90
+ if (this.check(type)) {
91
+ this.advance();
92
+ return true;
93
+ }
94
+ }
95
+ return false;
96
+ }
97
+
98
+ expect(type) {
99
+ if (!this.check(type))
100
+ throw new ParseError(`expected '${type}'`, this.peek());
101
+ return this.advance();
102
+ }
103
+
104
+ semi() {
105
+ if (this.check(T.SEMICOLON)) this.advance();
106
+ }
107
+
108
+ err(msg) {
109
+ throw new ParseError(msg, this.peek(), this.filename, this.source);
110
+ }
111
+
112
+ // ── Entry point ──────────────────────────────────────────────
113
+
114
+ parse() {
115
+ const pkg = this.parsePackage();
116
+ const imports = [];
117
+ while (this.check(T.IMPORT)) imports.push(this.parseImport());
118
+ const decls = [];
119
+ while (!this.check(T.EOF)) decls.push(this.parseTopDecl());
120
+ return { kind: "Program", pkg, imports, decls, _filename: this.filename };
121
+ }
122
+
123
+ parsePackage() {
124
+ this.expect(T.PACKAGE);
125
+ const name = this.expect(T.IDENT).value;
126
+ this.semi();
127
+ return { kind: "PackageDecl", name };
128
+ }
129
+
130
+ parseImport() {
131
+ this.expect(T.IMPORT);
132
+ const paths = [];
133
+ if (this.match(T.LPAREN)) {
134
+ while (!this.check(T.RPAREN) && !this.check(T.EOF)) {
135
+ paths.push(this.expect(T.STRING).value);
136
+ this.semi();
137
+ }
138
+ this.expect(T.RPAREN);
139
+ } else {
140
+ paths.push(this.expect(T.STRING).value);
141
+ }
142
+ this.semi();
143
+ return { kind: "ImportDecl", paths };
144
+ }
145
+
146
+ // ── Top-level declarations ───────────────────────────────────
147
+
148
+ parseTopDecl() {
149
+ const t = this.peek();
150
+ switch (t.type) {
151
+ case T.ASYNC:
152
+ return this.parseAsyncFuncOrMethod();
153
+ case T.FUNC:
154
+ return this.parseFuncOrMethod();
155
+ case T.TYPE:
156
+ return this.parseTypeDecl();
157
+ case T.VAR:
158
+ return this.parseVarDecl(true);
159
+ case T.CONST:
160
+ return this.parseConstDecl();
161
+ default:
162
+ this.err("Expected top-level declaration");
163
+ }
164
+ }
165
+
166
+ parseFuncOrMethod() {
167
+ const _line = this.peek().line;
168
+ this.expect(T.FUNC);
169
+ // method: func (recv Type) Name(...)
170
+ if (this.check(T.LPAREN)) {
171
+ this.expect(T.LPAREN);
172
+ const recvName =
173
+ this.check(T.IDENT) && !this.isTypeName(this.peek2())
174
+ ? this.advance().value
175
+ : "_";
176
+ this.match(T.STAR); // pointer receiver: (c *Counter) — strip *, treat same as value receiver
177
+ const recvType = this.parseTypeName();
178
+ this.expect(T.RPAREN);
179
+ const name = this.expect(T.IDENT).value;
180
+ const { params, returnType } = this.parseSignature();
181
+ const body = this.parseBlock();
182
+ this.semi();
183
+ return {
184
+ kind: "MethodDecl",
185
+ recvName,
186
+ recvType,
187
+ name,
188
+ params,
189
+ returnType,
190
+ body,
191
+ _line,
192
+ };
193
+ }
194
+ // plain function
195
+ const name = this.expect(T.IDENT).value;
196
+ const { params, returnType } = this.parseSignature();
197
+ const body = this.parseBlock();
198
+ this.semi();
199
+ return { kind: "FuncDecl", name, params, returnType, body, _line };
200
+ }
201
+
202
+ parseAsyncFuncOrMethod() {
203
+ this.expect(T.ASYNC);
204
+ const decl = this.parseFuncOrMethod();
205
+ decl.async = true;
206
+ return decl;
207
+ }
208
+
209
+ // Returns true if tok looks like a type name token (for receiver parsing)
210
+ isTypeName(tok) {
211
+ return (
212
+ tok &&
213
+ (tok.type === T.RPAREN ||
214
+ tok.type === T.COMMA ||
215
+ tok.type === T.SEMICOLON ||
216
+ tok.type === T.EOF)
217
+ );
218
+ }
219
+
220
+ parseSignature() {
221
+ const params = this.parseParamList();
222
+ const returnType = this.parseReturnType();
223
+ return { params, returnType };
224
+ }
225
+
226
+ // Parses (a, b int, c string) style param list
227
+ parseParamList() {
228
+ this.expect(T.LPAREN);
229
+ const params = [];
230
+ if (!this.check(T.RPAREN)) {
231
+ do {
232
+ if (this.check(T.RPAREN)) break;
233
+ // Collect names, then type
234
+ const names = [];
235
+ if (this.check(T.IDENT) && this.isParamType(this.peek2())) {
236
+ // named: a, b int or ns ...int
237
+ names.push(this.advance().value);
238
+ while (
239
+ this.match(T.COMMA) &&
240
+ this.check(T.IDENT) &&
241
+ this.isParamType(this.peek2())
242
+ ) {
243
+ names.push(this.advance().value);
244
+ }
245
+ // variadic: name ...T
246
+ const variadic = this.match(T.ELLIPSIS);
247
+ const type = this.parseType();
248
+ for (const n of names) params.push({ name: n, type, variadic });
249
+ } else {
250
+ // unnamed: just a type (possibly variadic: ...T)
251
+ const variadic = this.match(T.ELLIPSIS);
252
+ const type = this.parseType();
253
+ params.push({ name: "_", type, variadic });
254
+ }
255
+ } while (this.match(T.COMMA));
256
+ }
257
+ this.expect(T.RPAREN);
258
+ return params;
259
+ }
260
+
261
+ // Heuristic: peek2 is a param-name separator if it's a comma, rparen, or a type token
262
+ isParamType(tok) {
263
+ if (!tok) return false;
264
+ // T.RPAREN is intentionally excluded: when peek2 is ")", the current IDENT
265
+ // is the type of an unnamed param (e.g. func(int)), not a param name.
266
+ return (
267
+ tok.type === T.COMMA || tok.type === T.ELLIPSIS || this.looksLikeType(tok)
268
+ );
269
+ }
270
+
271
+ looksLikeType(tok) {
272
+ return (
273
+ tok.type === T.LBRACKET ||
274
+ tok.type === T.STAR ||
275
+ tok.type === T.MAP ||
276
+ tok.type === T.FUNC ||
277
+ tok.type === T.INTERFACE ||
278
+ tok.type === T.STRUCT ||
279
+ (tok.type === T.IDENT && tok.value !== "_") ||
280
+ [
281
+ "int",
282
+ "float64",
283
+ "string",
284
+ "bool",
285
+ "any",
286
+ "byte",
287
+ "rune",
288
+ "error",
289
+ ].includes(tok.value)
290
+ );
291
+ }
292
+
293
+ parseReturnType(inTypeExpr = false) {
294
+ // No return type
295
+ if (this.check(T.LBRACE) || this.check(T.SEMICOLON) || this.check(T.EOF))
296
+ return null;
297
+ // Inside a type expression (e.g. func type in a tuple), `,` and `)` also end the type
298
+ if (inTypeExpr && (this.check(T.COMMA) || this.check(T.RPAREN)))
299
+ return null;
300
+ // Multiple return values: (int, string) or named (result int, ok bool)
301
+ if (this.check(T.LPAREN)) {
302
+ this.advance();
303
+ const types = [];
304
+ const names = []; // non-null if named returns
305
+ let isNamed = false;
306
+ do {
307
+ // Named returns: only treat IDENT as a name if peek2 is an actual type token
308
+ if (this.check(T.IDENT) && this.looksLikeType(this.peek2())) {
309
+ names.push(this.advance().value);
310
+ isNamed = true;
311
+ } else {
312
+ names.push(null);
313
+ }
314
+ types.push(this.parseType());
315
+ } while (this.match(T.COMMA));
316
+ this.expect(T.RPAREN);
317
+ const typeNode =
318
+ types.length === 1 ? types[0] : { kind: "TupleType", types };
319
+ // Attach named return info so typechecker can inject them into scope
320
+ if (isNamed)
321
+ typeNode._namedReturns = names.map((n, i) => ({
322
+ name: n,
323
+ type: types[i],
324
+ }));
325
+ return typeNode;
326
+ }
327
+ return this.parseType();
328
+ }
329
+
330
+ parseTypeDecl() {
331
+ this.expect(T.TYPE);
332
+ const name = this.expect(T.IDENT).value;
333
+ const type = this.parseType();
334
+ this.semi();
335
+ return { kind: "TypeDecl", name, type };
336
+ }
337
+
338
+ parseVarDecl(_topLevel = false) {
339
+ this.expect(T.VAR);
340
+ const decls = [];
341
+ if (this.match(T.LPAREN)) {
342
+ while (!this.check(T.RPAREN) && !this.check(T.EOF)) {
343
+ decls.push(this.parseVarSpec());
344
+ this.semi();
345
+ }
346
+ this.expect(T.RPAREN);
347
+ } else {
348
+ decls.push(this.parseVarSpec());
349
+ }
350
+ this.semi();
351
+ return { kind: "VarDecl", decls };
352
+ }
353
+
354
+ parseVarSpec() {
355
+ const names = [this.expect(T.IDENT).value];
356
+ while (this.match(T.COMMA)) names.push(this.expect(T.IDENT).value);
357
+ let type = null;
358
+ let value = null;
359
+ if (
360
+ !this.check(T.ASSIGN) &&
361
+ !this.check(T.SEMICOLON) &&
362
+ !this.check(T.RPAREN)
363
+ ) {
364
+ type = this.parseType();
365
+ }
366
+ if (this.match(T.ASSIGN)) value = this.parseExprList();
367
+ return { names, type, value };
368
+ }
369
+
370
+ parseConstDecl() {
371
+ this.expect(T.CONST);
372
+ const decls = [];
373
+ if (this.match(T.LPAREN)) {
374
+ let iotaVal = 0;
375
+ while (!this.check(T.RPAREN) && !this.check(T.EOF)) {
376
+ const names = [this.expect(T.IDENT).value];
377
+ while (this.match(T.COMMA)) names.push(this.expect(T.IDENT).value);
378
+ let type = null;
379
+ let value;
380
+ if (this.check(T.ASSIGN)) {
381
+ this.advance();
382
+ value = this.parseExprList();
383
+ } else if (!this.check(T.SEMICOLON) && !this.check(T.RPAREN)) {
384
+ // optional type annotation before =
385
+ type = this.parseType();
386
+ this.expect(T.ASSIGN);
387
+ value = this.parseExprList();
388
+ } else {
389
+ // implicit iota
390
+ value = [
391
+ { kind: "BasicLit", litKind: "INT", value: String(iotaVal) },
392
+ ];
393
+ }
394
+ // Replace iota identifier with current counter value
395
+ value = value.map((v) =>
396
+ v.kind === "Ident" && v.name === "iota"
397
+ ? { kind: "BasicLit", litKind: "INT", value: String(iotaVal) }
398
+ : v,
399
+ );
400
+ decls.push({ names, type, value });
401
+ iotaVal++;
402
+ this.semi();
403
+ }
404
+ this.expect(T.RPAREN);
405
+ } else {
406
+ const names = [this.expect(T.IDENT).value];
407
+ while (this.match(T.COMMA)) names.push(this.expect(T.IDENT).value);
408
+ let type = null;
409
+ if (!this.check(T.ASSIGN)) type = this.parseType();
410
+ this.expect(T.ASSIGN);
411
+ const value = this.parseExprList();
412
+ decls.push({ names, type, value });
413
+ }
414
+ this.semi();
415
+ return { kind: "ConstDecl", decls };
416
+ }
417
+
418
+ // ── Types ────────────────────────────────────────────────────
419
+
420
+ parseType() {
421
+ const t = this.peek();
422
+ switch (t.type) {
423
+ case T.LBRACKET:
424
+ return this.parseSliceOrArrayType();
425
+ case T.MAP:
426
+ return this.parseMapType();
427
+ case T.FUNC:
428
+ return this.parseFuncType();
429
+ case T.STRUCT:
430
+ return this.parseStructType();
431
+ case T.INTERFACE:
432
+ return this.parseInterfaceType();
433
+ case T.STAR:
434
+ this.advance();
435
+ return { kind: "PointerType", base: this.parseType() };
436
+ case T.IDENT:
437
+ case "int":
438
+ case "float64":
439
+ case "string":
440
+ case "bool":
441
+ case "any":
442
+ case "byte":
443
+ case "rune":
444
+ case "error":
445
+ return this.parseTypeName();
446
+ default:
447
+ this.err(`Expected type, got '${t.value}'`);
448
+ }
449
+ }
450
+
451
+ parseTypeName() {
452
+ const t = this.advance();
453
+ let name = t.value;
454
+ // qualified: pkg.Type
455
+ if (this.check(T.DOT)) {
456
+ this.advance();
457
+ name += `.${this.expect(T.IDENT).value}`;
458
+ }
459
+ return { kind: "TypeName", name };
460
+ }
461
+
462
+ parseSliceOrArrayType() {
463
+ this.expect(T.LBRACKET);
464
+ if (this.check(T.RBRACKET)) {
465
+ // slice type: []T
466
+ this.advance();
467
+ return { kind: "SliceType", elem: this.parseType() };
468
+ }
469
+ // fixed array: [n]T — treated as slice for JS purposes
470
+ const size = this.parseExpr();
471
+ this.expect(T.RBRACKET);
472
+ return { kind: "ArrayType", size, elem: this.parseType() };
473
+ }
474
+
475
+ parseMapType() {
476
+ this.expect(T.MAP);
477
+ this.expect(T.LBRACKET);
478
+ const key = this.parseType();
479
+ this.expect(T.RBRACKET);
480
+ const value = this.parseType();
481
+ return { kind: "MapType", key, value };
482
+ }
483
+
484
+ parseFuncType() {
485
+ this.expect(T.FUNC);
486
+ const params = this.parseParamList();
487
+ const returnType = this.parseReturnType(true);
488
+ return { kind: "FuncType", params, returnType };
489
+ }
490
+
491
+ parseStructType() {
492
+ this.expect(T.STRUCT);
493
+ this.expect(T.LBRACE);
494
+ const fields = [];
495
+ while (!this.check(T.RBRACE) && !this.check(T.EOF)) {
496
+ const names = [this.expect(T.IDENT).value];
497
+ while (this.match(T.COMMA)) names.push(this.expect(T.IDENT).value);
498
+ // Check if this is an embedded field (just a type name)
499
+ // e.g., type T struct { Base }
500
+ if (this.check(T.SEMICOLON) || this.check(T.RBRACE)) {
501
+ const typeName = names[0]; // assuming no comma list for embedded
502
+ fields.push({
503
+ names: [],
504
+ type: { kind: "TypeName", name: typeName },
505
+ embedded: true,
506
+ });
507
+ this.semi();
508
+ continue;
509
+ }
510
+ const type = this.parseType();
511
+ fields.push({ names, type });
512
+ this.semi();
513
+ }
514
+ this.expect(T.RBRACE);
515
+ return { kind: "StructType", fields };
516
+ }
517
+
518
+ parseInterfaceType() {
519
+ this.expect(T.INTERFACE);
520
+ this.expect(T.LBRACE);
521
+ const methods = [];
522
+ while (!this.check(T.RBRACE) && !this.check(T.EOF)) {
523
+ const name = this.expect(T.IDENT).value;
524
+ const params = this.parseParamList();
525
+ const returnType = this.parseReturnType();
526
+ methods.push({ name, params, returnType });
527
+ this.semi();
528
+ }
529
+ this.expect(T.RBRACE);
530
+ return { kind: "InterfaceType", methods };
531
+ }
532
+
533
+ // ── Statements ───────────────────────────────────────────────
534
+
535
+ parseBlock() {
536
+ this.expect(T.LBRACE);
537
+ const stmts = [];
538
+ while (!this.check(T.RBRACE) && !this.check(T.EOF)) {
539
+ stmts.push(this.parseStmt());
540
+ this.semi();
541
+ }
542
+ this.expect(T.RBRACE);
543
+ return { kind: "Block", stmts };
544
+ }
545
+
546
+ parseStmt() {
547
+ const t = this.peek();
548
+ const stmt = this._parseStmt(t);
549
+ stmt._line = t.line;
550
+ return stmt;
551
+ }
552
+
553
+ _parseStmt(t) {
554
+ switch (t.type) {
555
+ case T.VAR:
556
+ return this.parseVarDecl();
557
+ case T.CONST:
558
+ return this.parseConstDecl();
559
+ case T.TYPE:
560
+ return this.parseTypeDecl();
561
+ case T.RETURN:
562
+ return this.parseReturn();
563
+ case T.DEFER:
564
+ return this.parseDefer();
565
+ case T.BREAK:
566
+ this.advance();
567
+ this.semi();
568
+ return { kind: "BranchStmt", keyword: "break" };
569
+ case T.CONTINUE:
570
+ this.advance();
571
+ this.semi();
572
+ return { kind: "BranchStmt", keyword: "continue" };
573
+ case T.FALLTHROUGH:
574
+ this.advance();
575
+ this.semi();
576
+ return { kind: "BranchStmt", keyword: "fallthrough" };
577
+ case T.IF:
578
+ return this.parseIf();
579
+ case T.FOR:
580
+ return this.parseFor();
581
+ case T.SWITCH:
582
+ return this.parseSwitch();
583
+ case T.LBRACE:
584
+ return this.parseBlock();
585
+ default:
586
+ return this.parseSimpleStmt();
587
+ }
588
+ }
589
+
590
+ parseDefer() {
591
+ this.expect(T.DEFER);
592
+ const call = this.parseExpr();
593
+ return { kind: "DeferStmt", call };
594
+ }
595
+
596
+ parseReturn() {
597
+ this.expect(T.RETURN);
598
+ if (this.check(T.SEMICOLON) || this.check(T.RBRACE)) {
599
+ return { kind: "ReturnStmt", values: [] };
600
+ }
601
+ const values = this.parseExprList();
602
+ return { kind: "ReturnStmt", values };
603
+ }
604
+
605
+ parseIf() {
606
+ this.expect(T.IF);
607
+ let init = null;
608
+ let cond;
609
+ // if init; cond { } — use noCompLit to avoid "{" being parsed as composite literal
610
+ const first = this.parseSimpleStmtRaw(true);
611
+ // Only treat as "init; cond" if semicolon is followed by a non-brace token
612
+ if (this.check(T.SEMICOLON) && this.peek2().type !== T.LBRACE) {
613
+ this.advance(); // consume ;
614
+ init = first;
615
+ cond = this.parseExpr(0, true);
616
+ } else {
617
+ this.match(T.SEMICOLON); // consume any auto-inserted ;
618
+ cond = this.stmtToExpr(first);
619
+ }
620
+ const body = this.parseBlock();
621
+ // Consume auto-inserted ; before else (Go allows "}\nelse")
622
+ this.match(T.SEMICOLON);
623
+ let elseBody = null;
624
+ if (this.match(T.ELSE)) {
625
+ elseBody = this.check(T.IF) ? this.parseIf() : this.parseBlock();
626
+ }
627
+ return { kind: "IfStmt", init, cond, body, elseBody };
628
+ }
629
+
630
+ parseFor() {
631
+ this.expect(T.FOR);
632
+
633
+ // for { } — infinite loop
634
+ if (this.check(T.LBRACE)) {
635
+ return {
636
+ kind: "ForStmt",
637
+ init: null,
638
+ cond: null,
639
+ post: null,
640
+ body: this.parseBlock(),
641
+ };
642
+ }
643
+
644
+ const first = this.parseSimpleStmtRaw(true);
645
+
646
+ if (this.check(T.LBRACE)) {
647
+ // for k, v := range expr { } — range stmt ends right before {
648
+ if (this.isRangeStmt(first)) {
649
+ return {
650
+ kind: "ForStmt",
651
+ init: first,
652
+ cond: null,
653
+ post: null,
654
+ body: this.parseBlock(),
655
+ };
656
+ }
657
+ // for cond { }
658
+ return {
659
+ kind: "ForStmt",
660
+ init: null,
661
+ cond: this.stmtToExpr(first),
662
+ post: null,
663
+ body: this.parseBlock(),
664
+ };
665
+ }
666
+
667
+ this.expect(T.SEMICOLON);
668
+
669
+ // for init; cond; post { }
670
+ let cond = null;
671
+ if (!this.check(T.SEMICOLON)) cond = this.parseExpr(0, true);
672
+ this.expect(T.SEMICOLON);
673
+ let post = null;
674
+ if (!this.check(T.LBRACE)) post = this.parseSimpleStmtRaw();
675
+ const body = this.parseBlock();
676
+ return { kind: "ForStmt", init: first, cond, post, body };
677
+ }
678
+
679
+ isRangeStmt(stmt) {
680
+ if (!stmt) return false;
681
+ if (stmt.kind !== "DefineStmt" && stmt.kind !== "AssignStmt") return false;
682
+ return stmt.rhs?.[0]?.kind === "RangeExpr";
683
+ }
684
+
685
+ parseSwitch() {
686
+ this.expect(T.SWITCH);
687
+ let init = null;
688
+ let tag = null;
689
+ if (!this.check(T.LBRACE)) {
690
+ const expr = this.parseExpr(0, true);
691
+ if (this.match(T.SEMICOLON)) {
692
+ init = this.exprToStmt(expr);
693
+ if (!this.check(T.LBRACE)) tag = this.parseExpr(0, true);
694
+ } else {
695
+ tag = expr;
696
+ }
697
+ }
698
+ this.expect(T.LBRACE);
699
+ const cases = [];
700
+ while (!this.check(T.RBRACE) && !this.check(T.EOF)) {
701
+ if (this.check(T.CASE)) {
702
+ this.advance();
703
+ const list = this.parseExprList();
704
+ this.expect(T.COLON);
705
+ const stmts = [];
706
+ while (
707
+ !this.check(T.CASE) &&
708
+ !this.check(T.DEFAULT) &&
709
+ !this.check(T.RBRACE)
710
+ ) {
711
+ stmts.push(this.parseStmt());
712
+ this.semi();
713
+ }
714
+ cases.push({ kind: "CaseClause", list, stmts });
715
+ } else if (this.check(T.DEFAULT)) {
716
+ this.advance();
717
+ this.expect(T.COLON);
718
+ const stmts = [];
719
+ while (
720
+ !this.check(T.CASE) &&
721
+ !this.check(T.DEFAULT) &&
722
+ !this.check(T.RBRACE)
723
+ ) {
724
+ stmts.push(this.parseStmt());
725
+ this.semi();
726
+ }
727
+ cases.push({ kind: "CaseClause", list: null, stmts });
728
+ } else {
729
+ this.err("Expected case or default");
730
+ }
731
+ }
732
+ this.expect(T.RBRACE);
733
+ return { kind: "SwitchStmt", init, tag, cases };
734
+ }
735
+
736
+ // Parses a "simple statement": assignment, define, inc/dec, or expression.
737
+ // Returns a statement node.
738
+ parseSimpleStmt() {
739
+ const s = this.parseSimpleStmtRaw();
740
+ return s;
741
+ }
742
+
743
+ parseSimpleStmtRaw(noCompLit = false) {
744
+ // Parse left-hand side (could be expr list for multi-assign)
745
+ const first = this.parseExpr(0, noCompLit);
746
+ const t = this.peek();
747
+
748
+ // a, b, c := ... or x, y = ...
749
+ if (t.type === T.COMMA) {
750
+ const lhs = [first];
751
+ while (this.match(T.COMMA)) lhs.push(this.parseExpr());
752
+ const op = this.advance(); // := or =
753
+ const rhs = this.parseExprList();
754
+ if (op.type === T.DEFINE) return { kind: "DefineStmt", lhs, rhs };
755
+ return { kind: "AssignStmt", lhs, op: "=", rhs };
756
+ }
757
+
758
+ // x := expr
759
+ if (t.type === T.DEFINE) {
760
+ this.advance();
761
+ const rhs = this.parseExprList();
762
+ return { kind: "DefineStmt", lhs: [first], rhs };
763
+ }
764
+
765
+ // x = expr or x += expr etc.
766
+ const assignOps = [
767
+ T.ASSIGN,
768
+ T.PLUS_ASSIGN,
769
+ T.MINUS_ASSIGN,
770
+ T.STAR_ASSIGN,
771
+ T.SLASH_ASSIGN,
772
+ T.PERCENT_ASSIGN,
773
+ ];
774
+ if (assignOps.includes(t.type)) {
775
+ const op = this.advance().value;
776
+ const rhs = this.parseExprList();
777
+ return { kind: "AssignStmt", lhs: [first], op, rhs };
778
+ }
779
+
780
+ // x++ x--
781
+ if (t.type === T.INC || t.type === T.DEC) {
782
+ const op = this.advance().value;
783
+ return { kind: "IncDecStmt", expr: first, op };
784
+ }
785
+
786
+ // for range detection: "for k, v := range expr"
787
+ // range keyword after :=
788
+ if (first.kind === "Ident" && first.name === "range") {
789
+ const expr = this.parseExpr();
790
+ return { kind: "RangeExpr", expr };
791
+ }
792
+
793
+ return { kind: "ExprStmt", expr: first };
794
+ }
795
+
796
+ // Converts an ExprStmt back to its expression
797
+ stmtToExpr(stmt) {
798
+ if (!stmt) return null;
799
+ if (stmt.kind === "ExprStmt") return stmt.expr;
800
+ return stmt;
801
+ }
802
+
803
+ exprToStmt(expr) {
804
+ return { kind: "ExprStmt", expr };
805
+ }
806
+
807
+ // ── Expressions ──────────────────────────────────────────────
808
+
809
+ parseExprList() {
810
+ const exprs = [this.parseExpr()];
811
+ while (this.match(T.COMMA)) exprs.push(this.parseExpr());
812
+ return exprs;
813
+ }
814
+
815
+ // noCompLit=true disables composite literal parsing in if/for/switch conditions
816
+ // to resolve the ambiguity between "if x {" and "if Type{ } {"
817
+ parseExpr(minPrec = 0, noCompLit = false) {
818
+ let left = this.parseUnary(noCompLit);
819
+ while (true) {
820
+ const prec = PREC[this.peek().type];
821
+ if (!prec || prec <= minPrec) break;
822
+ const op = this.advance().value;
823
+ const right = this.parseExpr(prec, noCompLit);
824
+ left = { kind: "BinaryExpr", left, op, right };
825
+ }
826
+ return left;
827
+ }
828
+
829
+ parseUnary(noCompLit = false) {
830
+ const t = this.peek();
831
+ if (
832
+ t.type === T.NOT ||
833
+ t.type === T.MINUS ||
834
+ t.type === T.PLUS ||
835
+ t.type === T.CARET ||
836
+ t.type === T.AMP ||
837
+ t.type === T.STAR
838
+ ) {
839
+ const op = this.advance().value;
840
+ return { kind: "UnaryExpr", op, operand: this.parseUnary(noCompLit) };
841
+ }
842
+ if (t.type === T.AWAIT) {
843
+ this.advance();
844
+ return { kind: "AwaitExpr", expr: this.parseUnary(noCompLit) };
845
+ }
846
+ return this.parsePostfix(noCompLit);
847
+ }
848
+
849
+ parsePostfix(noCompLit = false) {
850
+ let expr = this.parsePrimary();
851
+ while (true) {
852
+ const t = this.peek();
853
+ if (t.type === T.DOT) {
854
+ this.advance();
855
+ // type assertion: expr.(Type)
856
+ if (this.check(T.LPAREN)) {
857
+ this.advance();
858
+ const type = this.parseType();
859
+ this.expect(T.RPAREN);
860
+ expr = { kind: "TypeAssertExpr", expr, type };
861
+ } else {
862
+ const field = this.advance().value;
863
+ expr = { kind: "SelectorExpr", expr, field };
864
+ }
865
+ } else if (t.type === T.LBRACKET) {
866
+ this.advance();
867
+ const low = this.check(T.COLON) ? null : this.parseExpr();
868
+ if (this.match(T.COLON)) {
869
+ const high = this.check(T.RBRACKET) ? null : this.parseExpr();
870
+ this.expect(T.RBRACKET);
871
+ expr = { kind: "SliceExpr", expr, low, high };
872
+ } else {
873
+ this.expect(T.RBRACKET);
874
+ expr = { kind: "IndexExpr", expr, index: low };
875
+ }
876
+ } else if (t.type === T.LPAREN) {
877
+ expr = this.parseCall(expr);
878
+ } else if (
879
+ t.type === T.LBRACE &&
880
+ !noCompLit &&
881
+ this.isCompositeLitContext(expr)
882
+ ) {
883
+ expr = this.parseCompositeLit(expr);
884
+ } else {
885
+ break;
886
+ }
887
+ }
888
+ return expr;
889
+ }
890
+
891
+ // Composite literals are only allowed after a type name (not arbitrary exprs)
892
+ isCompositeLitContext(expr) {
893
+ return (
894
+ expr.kind === "Ident" ||
895
+ expr.kind === "SelectorExpr" ||
896
+ expr.kind === "TypeExpr"
897
+ );
898
+ }
899
+
900
+ parseCall(fn) {
901
+ this.expect(T.LPAREN);
902
+ const args = [];
903
+ if (!this.check(T.RPAREN)) {
904
+ do {
905
+ if (this.check(T.RPAREN)) break;
906
+ // map[K]V and []T as call args must be parsed as types, not expressions,
907
+ // when they are NOT followed by { (which would make them composite literals).
908
+ // Needed for make(map[K]V), make([]T, n), new(T), etc.
909
+ const t = this.peek();
910
+ const isSliceOrArrayType =
911
+ t.type === T.LBRACKET &&
912
+ (this.peek2()?.type === T.RBRACKET || // []T
913
+ this.peek2()?.type === T.INT); // [n]T
914
+ if (
915
+ (t.type === T.MAP || isSliceOrArrayType) &&
916
+ !this.looksLikeCompositeLitArg()
917
+ ) {
918
+ args.push({ kind: "TypeExpr", type: this.parseType() });
919
+ } else {
920
+ args.push(this.parseExpr());
921
+ }
922
+ this.match(T.ELLIPSIS); // variadic spread, ignore for now
923
+ } while (this.match(T.COMMA));
924
+ }
925
+ this.expect(T.RPAREN);
926
+ return { kind: "CallExpr", func: fn, args };
927
+ }
928
+
929
+ // Returns true if the current position looks like a composite literal argument,
930
+ // e.g. []int{1,2,3} or map[string]int{"a":1}. We scan ahead past the type
931
+ // tokens to check whether a { follows, without advancing this.pos permanently.
932
+ looksLikeCompositeLitArg() {
933
+ let i = this.pos;
934
+ const src = this.tokens;
935
+ // Skip over the type tokens: [, ], map, [, K, ], V, ident, etc.
936
+ // Simple approach: skip until we hit something that can't be part of a type
937
+ let depth = 0;
938
+ while (i < src.length) {
939
+ const tt = src[i].type;
940
+ if (tt === T.LBRACKET) {
941
+ depth++;
942
+ i++;
943
+ continue;
944
+ }
945
+ if (tt === T.RBRACKET) {
946
+ depth--;
947
+ i++;
948
+ continue;
949
+ }
950
+ if (depth > 0) {
951
+ i++;
952
+ continue;
953
+ }
954
+ if (
955
+ tt === T.MAP ||
956
+ tt === T.IDENT ||
957
+ tt === T.STAR ||
958
+ tt === T.INT ||
959
+ tt === T.FLOAT ||
960
+ tt === T.STRING
961
+ ) {
962
+ i++;
963
+ continue;
964
+ }
965
+ break;
966
+ }
967
+ // Skip whitespace tokens (shouldn't exist post-lex, but be safe)
968
+ while (i < src.length && src[i].type === T.SEMICOLON) i++;
969
+ return i < src.length && src[i].type === T.LBRACE;
970
+ }
971
+
972
+ parseCompositeLit(typeExpr) {
973
+ this.expect(T.LBRACE);
974
+ const elems = [];
975
+ while (!this.check(T.RBRACE) && !this.check(T.EOF)) {
976
+ // Implicit composite literal: {X: 1} inside []Point{{X:1}} or map[string]Point{"a": {X:1}}
977
+ if (this.check(T.LBRACE)) {
978
+ elems.push(this.parseCompositeLit(null)); // null typeExpr = inferred
979
+ } else {
980
+ const first = this.parseExpr();
981
+ if (this.match(T.COLON)) {
982
+ // Map key: value — value may also be an implicit composite lit
983
+ const val = this.check(T.LBRACE)
984
+ ? this.parseCompositeLit(null)
985
+ : this.parseExpr();
986
+ elems.push({ kind: "KeyValueExpr", key: first, value: val });
987
+ } else {
988
+ elems.push(first);
989
+ }
990
+ }
991
+ this.match(T.COMMA);
992
+ }
993
+ this.expect(T.RBRACE);
994
+ return { kind: "CompositeLit", typeExpr, elems };
995
+ }
996
+
997
+ parsePrimary() {
998
+ const line = this.peek().line;
999
+ const expr = this._parsePrimary();
1000
+ if (!expr._line) expr._line = line;
1001
+ return expr;
1002
+ }
1003
+
1004
+ _parsePrimary() {
1005
+ const t = this.peek();
1006
+
1007
+ // Literals
1008
+ if (t.type === T.INT) {
1009
+ this.advance();
1010
+ return { kind: "BasicLit", litKind: "INT", value: t.value };
1011
+ }
1012
+ if (t.type === T.FLOAT) {
1013
+ this.advance();
1014
+ return { kind: "BasicLit", litKind: "FLOAT", value: t.value };
1015
+ }
1016
+ if (t.type === T.STRING) {
1017
+ this.advance();
1018
+ return { kind: "BasicLit", litKind: "STRING", value: t.value };
1019
+ }
1020
+ if (t.type === T.TRUE) {
1021
+ this.advance();
1022
+ return { kind: "BasicLit", litKind: "BOOL", value: "true" };
1023
+ }
1024
+ if (t.type === T.FALSE) {
1025
+ this.advance();
1026
+ return { kind: "BasicLit", litKind: "BOOL", value: "false" };
1027
+ }
1028
+ if (t.type === T.NIL) {
1029
+ this.advance();
1030
+ return { kind: "BasicLit", litKind: "NIL", value: "null" };
1031
+ }
1032
+
1033
+ // Parenthesised expression
1034
+ if (t.type === T.LPAREN) {
1035
+ this.advance();
1036
+ const expr = this.parseExpr();
1037
+ this.expect(T.RPAREN);
1038
+ return expr;
1039
+ }
1040
+
1041
+ // Function literal (sync or async)
1042
+ if (t.type === T.FUNC || t.type === T.ASYNC) {
1043
+ const isAsync = t.type === T.ASYNC;
1044
+ this.advance();
1045
+ if (isAsync) this.expect(T.FUNC);
1046
+ const params = this.parseParamList();
1047
+ const returnType = this.parseReturnType();
1048
+ const body = this.parseBlock();
1049
+ return { kind: "FuncLit", params, returnType, body, async: isAsync };
1050
+ }
1051
+
1052
+ // range expression (only valid inside a for statement)
1053
+ if (t.type === T.RANGE) {
1054
+ this.advance();
1055
+ // Always use noCompLit here: "range slice {" — { is the for body, not a composite lit
1056
+ return { kind: "RangeExpr", expr: this.parseExpr(0, true) };
1057
+ }
1058
+
1059
+ // Builtin / identifier
1060
+ if (
1061
+ t.type === T.IDENT ||
1062
+ this.isTypeKeyword(t) ||
1063
+ this.isBuiltinKeyword(t)
1064
+ ) {
1065
+ this.advance();
1066
+ // Type conversion or composite lit: Type(expr) or Type{...}
1067
+ if (this.isTypeKeyword(t) || this.check(T.LPAREN)) {
1068
+ if (this.check(T.LPAREN) && this.isTypeKeyword(t)) {
1069
+ // type conversion: int(x), string(x), etc.
1070
+ this.advance();
1071
+ const expr = this.parseExpr();
1072
+ this.expect(T.RPAREN);
1073
+ return {
1074
+ kind: "TypeConversion",
1075
+ targetType: { kind: "TypeName", name: t.value },
1076
+ expr,
1077
+ };
1078
+ }
1079
+ }
1080
+ return { kind: "Ident", name: t.value };
1081
+ }
1082
+
1083
+ // Slice/array/map composite literals: []int{1,2}, map[string]int{"a":1}
1084
+ if (t.type === T.LBRACKET || t.type === T.MAP) {
1085
+ const typeExpr = this.parseType();
1086
+ return this.parseCompositeLit(typeExpr);
1087
+ }
1088
+
1089
+ this.err(`Unexpected token in expression: '${t.value}'`);
1090
+ }
1091
+
1092
+ isTypeKeyword(t) {
1093
+ return [
1094
+ "int",
1095
+ "float64",
1096
+ "string",
1097
+ "bool",
1098
+ "any",
1099
+ "byte",
1100
+ "rune",
1101
+ "error",
1102
+ ].includes(t.value);
1103
+ }
1104
+
1105
+ isBuiltinKeyword(t) {
1106
+ return [
1107
+ "new",
1108
+ "make",
1109
+ "len",
1110
+ "cap",
1111
+ "append",
1112
+ "delete",
1113
+ "copy",
1114
+ "print",
1115
+ "println",
1116
+ "panic",
1117
+ "error",
1118
+ ].includes(t.value);
1119
+ }
1120
+ }