gofront 0.0.1 → 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.
- package/README.md +357 -33
- package/package.json +7 -3
- package/src/codegen/expressions.js +804 -0
- package/src/codegen/source-map.js +45 -0
- package/src/codegen/statements.js +406 -0
- package/src/codegen.js +50 -851
- package/src/compiler.js +44 -23
- package/src/dts-parser.js +0 -4
- package/src/index.js +11 -20
- package/src/lexer.js +170 -12
- package/src/parser/expressions.js +389 -0
- package/src/parser/statements.js +355 -0
- package/src/parser/types.js +140 -0
- package/src/parser.js +57 -745
- package/src/resolver.js +1 -1
- package/src/typechecker/expressions.js +359 -0
- package/src/typechecker/statements.js +250 -0
- package/src/typechecker/types.js +226 -0
- package/src/typechecker.js +390 -647
package/src/parser.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
// Recursive-descent parser. Produces an AST from a GoFront token stream.
|
|
2
2
|
//
|
|
3
|
+
// Split into sub-modules under parser/:
|
|
4
|
+
// types.js — type expression parsing (slice, map, struct, interface, etc.)
|
|
5
|
+
// statements.js — block, control flow, simple statements
|
|
6
|
+
// expressions.js — operator precedence, unary, postfix, primary, literals
|
|
7
|
+
//
|
|
3
8
|
// Node kinds used throughout the compiler:
|
|
4
9
|
// Program, PackageDecl, ImportDecl
|
|
5
10
|
// FuncDecl, MethodDecl, VarDecl, ConstDecl, TypeDecl
|
|
@@ -12,6 +17,9 @@
|
|
|
12
17
|
// CompositeLit, KeyValueExpr, FuncLit, AwaitExpr
|
|
13
18
|
|
|
14
19
|
import { T } from "./lexer.js";
|
|
20
|
+
import { expressionParserMethods } from "./parser/expressions.js";
|
|
21
|
+
import { statementParserMethods } from "./parser/statements.js";
|
|
22
|
+
import { typeParserMethods } from "./parser/types.js";
|
|
15
23
|
|
|
16
24
|
export class ParseError extends Error {
|
|
17
25
|
constructor(msg, token, filename, sourceCode) {
|
|
@@ -33,28 +41,6 @@ export class ParseError extends Error {
|
|
|
33
41
|
}
|
|
34
42
|
}
|
|
35
43
|
|
|
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
44
|
export class Parser {
|
|
59
45
|
constructor(tokens, filename = null, source = null) {
|
|
60
46
|
this.tokens = tokens;
|
|
@@ -116,7 +102,11 @@ export class Parser {
|
|
|
116
102
|
const imports = [];
|
|
117
103
|
while (this.check(T.IMPORT)) imports.push(this.parseImport());
|
|
118
104
|
const decls = [];
|
|
119
|
-
while (!this.check(T.EOF))
|
|
105
|
+
while (!this.check(T.EOF)) {
|
|
106
|
+
const result = this.parseTopDecl();
|
|
107
|
+
if (Array.isArray(result)) decls.push(...result);
|
|
108
|
+
else decls.push(result);
|
|
109
|
+
}
|
|
120
110
|
return { kind: "Program", pkg, imports, decls, _filename: this.filename };
|
|
121
111
|
}
|
|
122
112
|
|
|
@@ -129,18 +119,30 @@ export class Parser {
|
|
|
129
119
|
|
|
130
120
|
parseImport() {
|
|
131
121
|
this.expect(T.IMPORT);
|
|
132
|
-
const
|
|
122
|
+
const imports = [];
|
|
123
|
+
const parseOne = () => {
|
|
124
|
+
const _line = this.peek().line;
|
|
125
|
+
let alias = null;
|
|
126
|
+
if (this.check(T.IDENT)) {
|
|
127
|
+
alias = this.advance().value;
|
|
128
|
+
} else if (this.check(T.DOT)) {
|
|
129
|
+
alias = ".";
|
|
130
|
+
this.advance();
|
|
131
|
+
}
|
|
132
|
+
const path = this.expect(T.STRING).value;
|
|
133
|
+
return { path, alias, _line };
|
|
134
|
+
};
|
|
133
135
|
if (this.match(T.LPAREN)) {
|
|
134
136
|
while (!this.check(T.RPAREN) && !this.check(T.EOF)) {
|
|
135
|
-
|
|
137
|
+
imports.push(parseOne());
|
|
136
138
|
this.semi();
|
|
137
139
|
}
|
|
138
140
|
this.expect(T.RPAREN);
|
|
139
141
|
} else {
|
|
140
|
-
|
|
142
|
+
imports.push(parseOne());
|
|
141
143
|
}
|
|
142
144
|
this.semi();
|
|
143
|
-
return { kind: "ImportDecl",
|
|
145
|
+
return { kind: "ImportDecl", imports };
|
|
144
146
|
}
|
|
145
147
|
|
|
146
148
|
// ── Top-level declarations ───────────────────────────────────
|
|
@@ -155,7 +157,7 @@ export class Parser {
|
|
|
155
157
|
case T.TYPE:
|
|
156
158
|
return this.parseTypeDecl();
|
|
157
159
|
case T.VAR:
|
|
158
|
-
return this.parseVarDecl(
|
|
160
|
+
return this.parseVarDecl();
|
|
159
161
|
case T.CONST:
|
|
160
162
|
return this.parseConstDecl();
|
|
161
163
|
default:
|
|
@@ -276,23 +278,18 @@ export class Parser {
|
|
|
276
278
|
tok.type === T.FUNC ||
|
|
277
279
|
tok.type === T.INTERFACE ||
|
|
278
280
|
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)
|
|
281
|
+
(tok.type === T.IDENT && tok.value !== "_")
|
|
290
282
|
);
|
|
291
283
|
}
|
|
292
284
|
|
|
293
285
|
parseReturnType(inTypeExpr = false) {
|
|
294
286
|
// No return type
|
|
295
|
-
if (
|
|
287
|
+
if (
|
|
288
|
+
this.check(T.LBRACE) ||
|
|
289
|
+
this.check(T.RBRACE) ||
|
|
290
|
+
this.check(T.SEMICOLON) ||
|
|
291
|
+
this.check(T.EOF)
|
|
292
|
+
)
|
|
296
293
|
return null;
|
|
297
294
|
// Inside a type expression (e.g. func type in a tuple), `,` and `)` also end the type
|
|
298
295
|
if (inTypeExpr && (this.check(T.COMMA) || this.check(T.RPAREN)))
|
|
@@ -329,13 +326,27 @@ export class Parser {
|
|
|
329
326
|
|
|
330
327
|
parseTypeDecl() {
|
|
331
328
|
this.expect(T.TYPE);
|
|
329
|
+
if (this.match(T.LPAREN)) {
|
|
330
|
+
const decls = [];
|
|
331
|
+
while (!this.check(T.RPAREN) && !this.check(T.EOF)) {
|
|
332
|
+
const name = this.expect(T.IDENT).value;
|
|
333
|
+
const isAlias = this.match(T.ASSIGN);
|
|
334
|
+
const type = this.parseType();
|
|
335
|
+
decls.push({ kind: "TypeDecl", name, type, isAlias });
|
|
336
|
+
this.semi();
|
|
337
|
+
}
|
|
338
|
+
this.expect(T.RPAREN);
|
|
339
|
+
this.semi();
|
|
340
|
+
return decls;
|
|
341
|
+
}
|
|
332
342
|
const name = this.expect(T.IDENT).value;
|
|
343
|
+
const isAlias = this.match(T.ASSIGN);
|
|
333
344
|
const type = this.parseType();
|
|
334
345
|
this.semi();
|
|
335
|
-
return { kind: "TypeDecl", name, type };
|
|
346
|
+
return { kind: "TypeDecl", name, type, isAlias };
|
|
336
347
|
}
|
|
337
348
|
|
|
338
|
-
parseVarDecl(
|
|
349
|
+
parseVarDecl() {
|
|
339
350
|
this.expect(T.VAR);
|
|
340
351
|
const decls = [];
|
|
341
352
|
if (this.match(T.LPAREN)) {
|
|
@@ -414,707 +425,8 @@ export class Parser {
|
|
|
414
425
|
this.semi();
|
|
415
426
|
return { kind: "ConstDecl", decls };
|
|
416
427
|
}
|
|
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
428
|
}
|
|
429
|
+
|
|
430
|
+
Object.assign(Parser.prototype, typeParserMethods);
|
|
431
|
+
Object.assign(Parser.prototype, statementParserMethods);
|
|
432
|
+
Object.assign(Parser.prototype, expressionParserMethods);
|