mirror of
				https://git.sfja.dk/Mikkel/slige.git
				synced 2025-11-04 07:38:19 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			800 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			800 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import {
 | 
						|
    Anno,
 | 
						|
    AssignType,
 | 
						|
    AstCreator,
 | 
						|
    BinaryType,
 | 
						|
    EType,
 | 
						|
    ETypeKind,
 | 
						|
    Expr,
 | 
						|
    ExprKind,
 | 
						|
    Param,
 | 
						|
    Stmt,
 | 
						|
    StmtKind,
 | 
						|
    UnaryType,
 | 
						|
} from "./ast.ts";
 | 
						|
import { printStackTrace, Reporter } from "./info.ts";
 | 
						|
import { Lexer } from "./lexer.ts";
 | 
						|
import { Pos, Token } from "./token.ts";
 | 
						|
 | 
						|
export class Parser {
 | 
						|
    private currentToken: Token | null;
 | 
						|
 | 
						|
    public constructor(
 | 
						|
        private lexer: Lexer,
 | 
						|
        private astCreator: AstCreator,
 | 
						|
        private reporter: Reporter,
 | 
						|
    ) {
 | 
						|
        this.currentToken = lexer.next();
 | 
						|
    }
 | 
						|
 | 
						|
    public parse(): Stmt[] {
 | 
						|
        return this.parseStmts();
 | 
						|
    }
 | 
						|
 | 
						|
    private parseStmts(): Stmt[] {
 | 
						|
        const stmts: Stmt[] = [];
 | 
						|
        while (!this.done()) {
 | 
						|
            if (this.test("fn")) {
 | 
						|
                stmts.push(this.parseFn());
 | 
						|
            } else if (
 | 
						|
                this.test("let") || this.test("return") || this.test("break")
 | 
						|
            ) {
 | 
						|
                stmts.push(this.parseSingleLineBlockStmt());
 | 
						|
                this.eatSemicolon();
 | 
						|
            } else if (
 | 
						|
                ["{", "if", "loop", "while", "for"].some((tt) => this.test(tt))
 | 
						|
            ) {
 | 
						|
                const expr = this.parseMultiLineBlockExpr();
 | 
						|
                stmts.push(this.stmt({ type: "expr", expr }, expr.pos));
 | 
						|
            } else {
 | 
						|
                stmts.push(this.parseAssign());
 | 
						|
                this.eatSemicolon();
 | 
						|
            }
 | 
						|
        }
 | 
						|
        return stmts;
 | 
						|
    }
 | 
						|
 | 
						|
    private parseMultiLineBlockExpr(): Expr {
 | 
						|
        const pos = this.pos();
 | 
						|
        if (this.test("{")) {
 | 
						|
            return this.parseBlock();
 | 
						|
        }
 | 
						|
        if (this.test("if")) {
 | 
						|
            return this.parseIf();
 | 
						|
        }
 | 
						|
        if (this.test("loop")) {
 | 
						|
            return this.parseLoop();
 | 
						|
        }
 | 
						|
        if (this.test("while")) {
 | 
						|
            return this.parseWhile();
 | 
						|
        }
 | 
						|
        if (this.test("for")) {
 | 
						|
            return this.parseFor();
 | 
						|
        }
 | 
						|
        this.report("expected expr");
 | 
						|
        return this.expr({ type: "error" }, pos);
 | 
						|
    }
 | 
						|
 | 
						|
    private parseSingleLineBlockStmt(): Stmt {
 | 
						|
        const pos = this.pos();
 | 
						|
        if (this.test("let")) {
 | 
						|
            return this.parseLet();
 | 
						|
        }
 | 
						|
        if (this.test("return")) {
 | 
						|
            return this.parseReturn();
 | 
						|
        }
 | 
						|
        if (this.test("break")) {
 | 
						|
            return this.parseBreak();
 | 
						|
        }
 | 
						|
        this.report("expected stmt");
 | 
						|
        return this.stmt({ type: "error" }, pos);
 | 
						|
    }
 | 
						|
 | 
						|
    private eatSemicolon() {
 | 
						|
        if (!this.test(";")) {
 | 
						|
            this.report(
 | 
						|
                `expected ';', got '${this.currentToken?.type ?? "eof"}'`,
 | 
						|
            );
 | 
						|
            return;
 | 
						|
        }
 | 
						|
        this.step();
 | 
						|
    }
 | 
						|
 | 
						|
    private parseExpr(): Expr {
 | 
						|
        return this.parseBinary();
 | 
						|
    }
 | 
						|
 | 
						|
    private parseBlock(): Expr {
 | 
						|
        const pos = this.pos();
 | 
						|
        this.step();
 | 
						|
        let stmts: Stmt[] = [];
 | 
						|
        while (!this.done()) {
 | 
						|
            if (this.test("}")) {
 | 
						|
                this.step();
 | 
						|
                return this.expr({ type: "block", stmts }, pos);
 | 
						|
            } else if (
 | 
						|
                this.test("return") || this.test("break") || this.test("let")
 | 
						|
            ) {
 | 
						|
                stmts.push(this.parseSingleLineBlockStmt());
 | 
						|
                this.eatSemicolon();
 | 
						|
            } else if (this.test("fn")) {
 | 
						|
                stmts.push(this.parseSingleLineBlockStmt());
 | 
						|
                stmts.push(this.parseFn());
 | 
						|
            } else if (
 | 
						|
                ["{", "if", "loop", "while", "for"].some((tt) => this.test(tt))
 | 
						|
            ) {
 | 
						|
                const expr = this.parseMultiLineBlockExpr();
 | 
						|
                if (this.test("}")) {
 | 
						|
                    this.step();
 | 
						|
                    return this.expr({ type: "block", stmts, expr }, expr.pos);
 | 
						|
                }
 | 
						|
                stmts.push(this.stmt({ type: "expr", expr }, expr.pos));
 | 
						|
            } else {
 | 
						|
                const expr = this.parseExpr();
 | 
						|
                if (this.test("=") || this.test("+=") || this.test("-=")) {
 | 
						|
                    const assignType = this.current().type as AssignType;
 | 
						|
                    this.step();
 | 
						|
                    const value = this.parseExpr();
 | 
						|
                    this.eatSemicolon();
 | 
						|
                    stmts.push(
 | 
						|
                        this.stmt(
 | 
						|
                            {
 | 
						|
                                type: "assign",
 | 
						|
                                assignType,
 | 
						|
                                subject: expr,
 | 
						|
                                value,
 | 
						|
                            },
 | 
						|
                            expr.pos,
 | 
						|
                        ),
 | 
						|
                    );
 | 
						|
                } else if (this.test(";")) {
 | 
						|
                    this.step();
 | 
						|
                    stmts.push(this.stmt({ type: "expr", expr }, expr.pos));
 | 
						|
                } else if (this.test("}")) {
 | 
						|
                    this.step();
 | 
						|
                    return this.expr({ type: "block", stmts, expr }, pos);
 | 
						|
                } else {
 | 
						|
                    this.report("expected ';' or '}'");
 | 
						|
                    return this.expr({ type: "error" }, this.pos());
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
        this.report("expected '}'");
 | 
						|
        return this.expr({ type: "error" }, pos);
 | 
						|
    }
 | 
						|
 | 
						|
    private parseFn(): Stmt {
 | 
						|
        const pos = this.pos();
 | 
						|
        this.step();
 | 
						|
        if (!this.test("ident")) {
 | 
						|
            this.report("expected ident");
 | 
						|
            return this.stmt({ type: "error" }, pos);
 | 
						|
        }
 | 
						|
        const ident = this.current().identValue!;
 | 
						|
        this.step();
 | 
						|
        if (!this.test("(")) {
 | 
						|
            this.report("expected '('");
 | 
						|
            return this.stmt({ type: "error" }, pos);
 | 
						|
        }
 | 
						|
        const params = this.parseFnParams();
 | 
						|
        let returnType: EType | null = null;
 | 
						|
        if (this.test("->")) {
 | 
						|
            this.step();
 | 
						|
            returnType = this.parseEType();
 | 
						|
        }
 | 
						|
 | 
						|
        let anno: Anno | null = null;
 | 
						|
        if (this.test("#")) {
 | 
						|
            anno = this.parseAnno();
 | 
						|
        }
 | 
						|
        if (!this.test("{")) {
 | 
						|
            this.report("expected block");
 | 
						|
            return this.stmt({ type: "error" }, pos);
 | 
						|
        }
 | 
						|
        const body = this.parseBlock();
 | 
						|
        return this.stmt(
 | 
						|
            {
 | 
						|
                type: "fn",
 | 
						|
                ident,
 | 
						|
                params,
 | 
						|
                returnType: returnType !== null ? returnType : undefined,
 | 
						|
                body,
 | 
						|
                anno: anno != null ? anno : undefined,
 | 
						|
            },
 | 
						|
            pos,
 | 
						|
        );
 | 
						|
    }
 | 
						|
 | 
						|
    private parseAnnoArgs(): Expr[] {
 | 
						|
        this.step();
 | 
						|
        if (!this.test("(")) {
 | 
						|
            this.report("expected '('");
 | 
						|
            return [];
 | 
						|
        }
 | 
						|
        this.step();
 | 
						|
        const annoArgs: Expr[] = [];
 | 
						|
        if (!this.test(")")) {
 | 
						|
            annoArgs.push(this.parseExpr());
 | 
						|
            while (this.test(",")) {
 | 
						|
                this.step();
 | 
						|
                if (this.test(")")) {
 | 
						|
                    break;
 | 
						|
                }
 | 
						|
                annoArgs.push(this.parseExpr());
 | 
						|
            }
 | 
						|
        }
 | 
						|
        if (!this.test(")")) {
 | 
						|
            this.report("expected ')'");
 | 
						|
            return [];
 | 
						|
        }
 | 
						|
        this.step();
 | 
						|
        return annoArgs;
 | 
						|
    }
 | 
						|
 | 
						|
    private parseAnno(): Anno | null {
 | 
						|
        const pos = this.pos();
 | 
						|
        this.step();
 | 
						|
        if (!this.test("[")) {
 | 
						|
            this.report("expected '['");
 | 
						|
            return null;
 | 
						|
        }
 | 
						|
        this.step();
 | 
						|
        if (!this.test("ident")) {
 | 
						|
            this.report("expected identifier");
 | 
						|
            return null;
 | 
						|
        }
 | 
						|
        const ident = this.current().identValue!;
 | 
						|
        const values = this.parseAnnoArgs();
 | 
						|
        if (!this.test("]")) {
 | 
						|
            this.report("expected ']'");
 | 
						|
            return null;
 | 
						|
        }
 | 
						|
        this.step();
 | 
						|
        return { ident, pos, values };
 | 
						|
    }
 | 
						|
 | 
						|
    private parseFnParams(): Param[] {
 | 
						|
        this.step();
 | 
						|
        if (this.test(")")) {
 | 
						|
            this.step();
 | 
						|
            return [];
 | 
						|
        }
 | 
						|
        const params: Param[] = [];
 | 
						|
        const paramResult = this.parseParam();
 | 
						|
        if (!paramResult.ok) {
 | 
						|
            return [];
 | 
						|
        }
 | 
						|
        params.push(paramResult.value);
 | 
						|
        while (this.test(",")) {
 | 
						|
            this.step();
 | 
						|
            if (this.test(")")) {
 | 
						|
                break;
 | 
						|
            }
 | 
						|
            const paramResult = this.parseParam();
 | 
						|
            if (!paramResult.ok) {
 | 
						|
                return [];
 | 
						|
            }
 | 
						|
            params.push(paramResult.value);
 | 
						|
        }
 | 
						|
        if (!this.test(")")) {
 | 
						|
            this.report("expected ')'");
 | 
						|
            return params;
 | 
						|
        }
 | 
						|
        this.step();
 | 
						|
        return params;
 | 
						|
    }
 | 
						|
 | 
						|
    private parseParam(): { ok: true; value: Param } | { ok: false } {
 | 
						|
        const pos = this.pos();
 | 
						|
        if (this.test("ident")) {
 | 
						|
            const ident = this.current().identValue!;
 | 
						|
            this.step();
 | 
						|
            if (this.test(":")) {
 | 
						|
                this.step();
 | 
						|
                const etype = this.parseEType();
 | 
						|
                return { ok: true, value: { ident, etype, pos } };
 | 
						|
            }
 | 
						|
            return { ok: true, value: { ident, pos } };
 | 
						|
        }
 | 
						|
        this.report("expected param");
 | 
						|
        return { ok: false };
 | 
						|
    }
 | 
						|
 | 
						|
    private parseLet(): Stmt {
 | 
						|
        const pos = this.pos();
 | 
						|
        this.step();
 | 
						|
        const paramResult = this.parseParam();
 | 
						|
        if (!paramResult.ok) {
 | 
						|
            return this.stmt({ type: "error" }, pos);
 | 
						|
        }
 | 
						|
        const param = paramResult.value;
 | 
						|
        if (!this.test("=")) {
 | 
						|
            this.report("expected '='");
 | 
						|
            return this.stmt({ type: "error" }, pos);
 | 
						|
        }
 | 
						|
        this.step();
 | 
						|
        const value = this.parseExpr();
 | 
						|
        return this.stmt({ type: "let", param, value }, pos);
 | 
						|
    }
 | 
						|
 | 
						|
    private parseAssign(): Stmt {
 | 
						|
        const pos = this.pos();
 | 
						|
        const subject = this.parseExpr();
 | 
						|
        if (this.test("=") || this.test("+=") || this.test("-=")) {
 | 
						|
            const assignType = this.current().type as AssignType;
 | 
						|
            this.step();
 | 
						|
            const value = this.parseExpr();
 | 
						|
            return this.stmt({
 | 
						|
                type: "assign",
 | 
						|
                assignType,
 | 
						|
                subject,
 | 
						|
                value,
 | 
						|
            }, pos);
 | 
						|
        }
 | 
						|
        return this.stmt({ type: "expr", expr: subject }, pos);
 | 
						|
    }
 | 
						|
 | 
						|
    private parseReturn(): Stmt {
 | 
						|
        const pos = this.pos();
 | 
						|
        this.step();
 | 
						|
        if (this.test(";")) {
 | 
						|
            return this.stmt({ type: "return" }, pos);
 | 
						|
        }
 | 
						|
        const expr = this.parseExpr();
 | 
						|
        return this.stmt({ type: "return", expr }, pos);
 | 
						|
    }
 | 
						|
 | 
						|
    private parseBreak(): Stmt {
 | 
						|
        const pos = this.pos();
 | 
						|
        this.step();
 | 
						|
        if (this.test(";")) {
 | 
						|
            return this.stmt({ type: "break" }, pos);
 | 
						|
        }
 | 
						|
        const expr = this.parseExpr();
 | 
						|
        return this.stmt({ type: "break", expr }, pos);
 | 
						|
    }
 | 
						|
 | 
						|
    private parseLoop(): Expr {
 | 
						|
        const pos = this.pos();
 | 
						|
        this.step();
 | 
						|
        if (!this.test("{")) {
 | 
						|
            this.report("expected '{'");
 | 
						|
            return this.expr({ type: "error" }, pos);
 | 
						|
        }
 | 
						|
        const body = this.parseExpr();
 | 
						|
        return this.expr({ type: "loop", body }, pos);
 | 
						|
    }
 | 
						|
 | 
						|
    private parseWhile(): Expr {
 | 
						|
        const pos = this.pos();
 | 
						|
        this.step();
 | 
						|
        const cond = this.parseExpr();
 | 
						|
        if (!this.test("{")) {
 | 
						|
            this.report("expected '{'");
 | 
						|
            return this.expr({ type: "error" }, pos);
 | 
						|
        }
 | 
						|
        const body = this.parseExpr();
 | 
						|
        return this.expr({ type: "while", cond, body }, pos);
 | 
						|
    }
 | 
						|
 | 
						|
    private parseFor(): Expr {
 | 
						|
        const pos = this.pos();
 | 
						|
        this.step();
 | 
						|
 | 
						|
        if (this.test("(")) {
 | 
						|
            return this.parseForClassicTail(pos);
 | 
						|
        }
 | 
						|
 | 
						|
        const paramRes = this.parseParam();
 | 
						|
        if (!paramRes.ok) {
 | 
						|
            return this.expr({ type: "error" }, pos);
 | 
						|
        }
 | 
						|
        const param = paramRes.value;
 | 
						|
 | 
						|
        if (!this.test("in")) {
 | 
						|
            this.report("expected 'in'");
 | 
						|
            return this.expr({ type: "error" }, pos);
 | 
						|
        }
 | 
						|
        this.step();
 | 
						|
        const value = this.parseExpr();
 | 
						|
 | 
						|
        if (!this.test("{")) {
 | 
						|
            this.report("expected '{'");
 | 
						|
            return this.expr({ type: "error" }, pos);
 | 
						|
        }
 | 
						|
        const body = this.parseExpr();
 | 
						|
        return this.expr({ type: "for_in", param, value, body }, pos);
 | 
						|
    }
 | 
						|
 | 
						|
    private parseForClassicTail(pos: Pos): Expr {
 | 
						|
        this.step();
 | 
						|
        let decl: Stmt | undefined;
 | 
						|
        if (!this.test(";")) {
 | 
						|
            decl = this.parseLet();
 | 
						|
        }
 | 
						|
        if (!this.test(";")) {
 | 
						|
            this.report("expected ';'");
 | 
						|
            return this.expr({ type: "error" }, pos);
 | 
						|
        }
 | 
						|
        this.step();
 | 
						|
        let cond: Expr | undefined;
 | 
						|
        if (!this.test(";")) {
 | 
						|
            cond = this.parseExpr();
 | 
						|
        }
 | 
						|
        if (!this.test(";")) {
 | 
						|
            this.report("expected ';'");
 | 
						|
            return this.expr({ type: "error" }, pos);
 | 
						|
        }
 | 
						|
        this.step();
 | 
						|
        let incr: Stmt | undefined;
 | 
						|
        if (!this.test(")")) {
 | 
						|
            incr = this.parseAssign();
 | 
						|
        }
 | 
						|
        if (!this.test(")")) {
 | 
						|
            this.report("expected '}'");
 | 
						|
            return this.expr({ type: "error" }, pos);
 | 
						|
        }
 | 
						|
        this.step();
 | 
						|
 | 
						|
        if (!this.test("{")) {
 | 
						|
            this.report("expected '{'");
 | 
						|
            return this.expr({ type: "error" }, pos);
 | 
						|
        }
 | 
						|
        const body = this.parseExpr();
 | 
						|
        return this.expr({ type: "for", decl, cond, incr, body }, pos);
 | 
						|
    }
 | 
						|
 | 
						|
    private parseIf(): Expr {
 | 
						|
        const pos = this.pos();
 | 
						|
        this.step();
 | 
						|
        const cond = this.parseExpr();
 | 
						|
        if (!this.test("{")) {
 | 
						|
            this.report("expected block");
 | 
						|
            return this.expr({ type: "error" }, pos);
 | 
						|
        }
 | 
						|
        const truthy = this.parseBlock();
 | 
						|
        if (!this.test("else")) {
 | 
						|
            return this.expr({ type: "if", cond, truthy }, pos);
 | 
						|
        }
 | 
						|
        this.step();
 | 
						|
        if (this.test("if")) {
 | 
						|
            const falsy = this.parseIf();
 | 
						|
            return this.expr({ type: "if", cond, truthy, falsy }, pos);
 | 
						|
        }
 | 
						|
        if (!this.test("{")) {
 | 
						|
            this.report("expected block");
 | 
						|
            return this.expr({ type: "error" }, pos);
 | 
						|
        }
 | 
						|
        const falsy = this.parseBlock();
 | 
						|
        return this.expr({ type: "if", cond, truthy, falsy }, pos);
 | 
						|
    }
 | 
						|
 | 
						|
    private parseBinary(): Expr {
 | 
						|
        return this.parseOr();
 | 
						|
    }
 | 
						|
 | 
						|
    private parseOr(): Expr {
 | 
						|
        const pos = this.pos();
 | 
						|
        let left = this.parseAnd();
 | 
						|
        while (true) {
 | 
						|
            if (this.test("or")) {
 | 
						|
                left = this.parBinTail(left, pos, this.parseAnd, "or");
 | 
						|
            } else {
 | 
						|
                break;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        return left;
 | 
						|
    }
 | 
						|
 | 
						|
    private parseAnd(): Expr {
 | 
						|
        const pos = this.pos();
 | 
						|
        let left = this.parseEquality();
 | 
						|
        while (true) {
 | 
						|
            if (this.test("and")) {
 | 
						|
                left = this.parBinTail(left, pos, this.parseEquality, "and");
 | 
						|
            } else {
 | 
						|
                break;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        return left;
 | 
						|
    }
 | 
						|
 | 
						|
    private parseEquality(): Expr {
 | 
						|
        const pos = this.pos();
 | 
						|
        const left = this.parseComparison();
 | 
						|
        if (this.test("==")) {
 | 
						|
            return this.parBinTail(left, pos, this.parseComparison, "==");
 | 
						|
        }
 | 
						|
        if (this.test("!=")) {
 | 
						|
            return this.parBinTail(left, pos, this.parseComparison, "!=");
 | 
						|
        }
 | 
						|
        return left;
 | 
						|
    }
 | 
						|
 | 
						|
    private parseComparison(): Expr {
 | 
						|
        const pos = this.pos();
 | 
						|
        const left = this.parseAddSub();
 | 
						|
        if (this.test("<")) {
 | 
						|
            return this.parBinTail(left, pos, this.parseAddSub, "<");
 | 
						|
        }
 | 
						|
        if (this.test(">")) {
 | 
						|
            return this.parBinTail(left, pos, this.parseAddSub, ">");
 | 
						|
        }
 | 
						|
        if (this.test("<=")) {
 | 
						|
            return this.parBinTail(left, pos, this.parseAddSub, "<=");
 | 
						|
        }
 | 
						|
        if (this.test(">=")) {
 | 
						|
            return this.parBinTail(left, pos, this.parseAddSub, ">=");
 | 
						|
        }
 | 
						|
        return left;
 | 
						|
    }
 | 
						|
 | 
						|
    private parseAddSub(): Expr {
 | 
						|
        const pos = this.pos();
 | 
						|
        let left = this.parseMulDiv();
 | 
						|
        while (true) {
 | 
						|
            if (this.test("+")) {
 | 
						|
                left = this.parBinTail(left, pos, this.parseMulDiv, "+");
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
            if (this.test("-")) {
 | 
						|
                left = this.parBinTail(left, pos, this.parseMulDiv, "-");
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        return left;
 | 
						|
    }
 | 
						|
 | 
						|
    private parseMulDiv(): Expr {
 | 
						|
        const pos = this.pos();
 | 
						|
        let left = this.parsePrefix();
 | 
						|
        while (true) {
 | 
						|
            if (this.test("*")) {
 | 
						|
                left = this.parBinTail(left, pos, this.parsePrefix, "*");
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
            if (this.test("/")) {
 | 
						|
                left = this.parBinTail(left, pos, this.parsePrefix, "/");
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        return left;
 | 
						|
    }
 | 
						|
 | 
						|
    private parBinTail(
 | 
						|
        left: Expr,
 | 
						|
        pos: Pos,
 | 
						|
        parseRight: (this: Parser) => Expr,
 | 
						|
        binaryType: BinaryType,
 | 
						|
    ): Expr {
 | 
						|
        this.step();
 | 
						|
        const right = parseRight.call(this);
 | 
						|
        return this.expr(
 | 
						|
            { type: "binary", binaryType, left, right },
 | 
						|
            pos,
 | 
						|
        );
 | 
						|
    }
 | 
						|
 | 
						|
    private parsePrefix(): Expr {
 | 
						|
        const pos = this.pos();
 | 
						|
        if (this.test("not") || this.test("-")) {
 | 
						|
            const unaryType = this.current().type as UnaryType;
 | 
						|
            this.step();
 | 
						|
            const subject = this.parsePrefix();
 | 
						|
            return this.expr({ type: "unary", unaryType, subject }, pos);
 | 
						|
        }
 | 
						|
        return this.parsePostfix();
 | 
						|
    }
 | 
						|
 | 
						|
    private parsePostfix(): Expr {
 | 
						|
        let subject = this.parseOperand();
 | 
						|
        while (true) {
 | 
						|
            const pos = this.pos();
 | 
						|
            if (this.test(".")) {
 | 
						|
                this.step();
 | 
						|
                if (!this.test("ident")) {
 | 
						|
                    this.report("expected ident");
 | 
						|
                    return this.expr({ type: "error" }, pos);
 | 
						|
                }
 | 
						|
                const value = this.current().identValue!;
 | 
						|
                this.step();
 | 
						|
                subject = this.expr({ type: "field", subject, value }, pos);
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
            if (this.test("[")) {
 | 
						|
                this.step();
 | 
						|
                const value = this.parseExpr();
 | 
						|
                if (!this.test("]")) {
 | 
						|
                    this.report("expected ']'");
 | 
						|
                    return this.expr({ type: "error" }, pos);
 | 
						|
                }
 | 
						|
                this.step();
 | 
						|
                subject = this.expr({ type: "index", subject, value }, pos);
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
            if (this.test("(")) {
 | 
						|
                this.step();
 | 
						|
                let args: Expr[] = [];
 | 
						|
                if (!this.test(")")) {
 | 
						|
                    args.push(this.parseExpr());
 | 
						|
                    while (this.test(",")) {
 | 
						|
                        this.step();
 | 
						|
                        if (this.test(")")) {
 | 
						|
                            break;
 | 
						|
                        }
 | 
						|
                        args.push(this.parseExpr());
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                if (!this.test(")")) {
 | 
						|
                    this.report("expected ')'");
 | 
						|
                    return this.expr({ type: "error" }, pos);
 | 
						|
                }
 | 
						|
                this.step();
 | 
						|
                subject = this.expr({ type: "call", subject, args }, pos);
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        return subject;
 | 
						|
    }
 | 
						|
 | 
						|
    private parseOperand(): Expr {
 | 
						|
        const pos = this.pos();
 | 
						|
        if (this.test("ident")) {
 | 
						|
            const value = this.current().identValue!;
 | 
						|
            this.step();
 | 
						|
            return this.expr({ type: "ident", value }, pos);
 | 
						|
        }
 | 
						|
        if (this.test("int")) {
 | 
						|
            const value = this.current().intValue!;
 | 
						|
            this.step();
 | 
						|
            return this.expr({ type: "int", value }, pos);
 | 
						|
        }
 | 
						|
        if (this.test("string")) {
 | 
						|
            const value = this.current().stringValue!;
 | 
						|
            this.step();
 | 
						|
            return this.expr({ type: "string", value }, pos);
 | 
						|
        }
 | 
						|
        if (this.test("false")) {
 | 
						|
            this.step();
 | 
						|
            return this.expr({ type: "bool", value: false }, pos);
 | 
						|
        }
 | 
						|
        if (this.test("true")) {
 | 
						|
            this.step();
 | 
						|
            return this.expr({ type: "bool", value: true }, pos);
 | 
						|
        }
 | 
						|
        if (this.test("null")) {
 | 
						|
            this.step();
 | 
						|
            return this.expr({ type: "null" }, pos);
 | 
						|
        }
 | 
						|
        if (this.test("(")) {
 | 
						|
            this.step();
 | 
						|
            const expr = this.parseExpr();
 | 
						|
            if (!this.test(")")) {
 | 
						|
                this.report("expected ')'");
 | 
						|
                return this.expr({ type: "error" }, pos);
 | 
						|
            }
 | 
						|
            this.step();
 | 
						|
            return this.expr({ type: "group", expr }, pos);
 | 
						|
        }
 | 
						|
        if (this.test("{")) {
 | 
						|
            return this.parseBlock();
 | 
						|
        }
 | 
						|
        if (this.test("if")) {
 | 
						|
            return this.parseIf();
 | 
						|
        }
 | 
						|
        if (this.test("loop")) {
 | 
						|
            return this.parseLoop();
 | 
						|
        }
 | 
						|
 | 
						|
        this.report("expected expr", pos);
 | 
						|
        this.step();
 | 
						|
        return this.expr({ type: "error" }, pos);
 | 
						|
    }
 | 
						|
 | 
						|
    private parseEType(): EType {
 | 
						|
        const pos = this.pos();
 | 
						|
        if (this.test("ident")) {
 | 
						|
            const ident = this.current().identValue!;
 | 
						|
            this.step();
 | 
						|
            return this.etype({ type: "ident", value: ident }, pos);
 | 
						|
        }
 | 
						|
        if (this.test("[")) {
 | 
						|
            this.step();
 | 
						|
            const inner = this.parseEType();
 | 
						|
            if (!this.test("]")) {
 | 
						|
                this.report("expected ']'", pos);
 | 
						|
                return this.etype({ type: "error" }, pos);
 | 
						|
            }
 | 
						|
            this.step();
 | 
						|
            return this.etype({ type: "array", inner }, pos);
 | 
						|
        }
 | 
						|
        if (this.test("struct")) {
 | 
						|
            this.step();
 | 
						|
            if (!this.test("{")) {
 | 
						|
                this.report("expected '{'");
 | 
						|
                return this.etype({ type: "error" }, pos);
 | 
						|
            }
 | 
						|
            const fields = this.parseETypeStructFields();
 | 
						|
            return this.etype({ type: "struct", fields }, pos);
 | 
						|
        }
 | 
						|
        this.report("expected type");
 | 
						|
        return this.etype({ type: "error" }, pos);
 | 
						|
    }
 | 
						|
 | 
						|
    private parseETypeStructFields(): Param[] {
 | 
						|
        this.step();
 | 
						|
        if (this.test("}")) {
 | 
						|
            this.step();
 | 
						|
            return [];
 | 
						|
        }
 | 
						|
        const params: Param[] = [];
 | 
						|
        const paramResult = this.parseParam();
 | 
						|
        if (!paramResult.ok) {
 | 
						|
            return [];
 | 
						|
        }
 | 
						|
        params.push(paramResult.value);
 | 
						|
        while (this.test(",")) {
 | 
						|
            this.step();
 | 
						|
            if (this.test("}")) {
 | 
						|
                break;
 | 
						|
            }
 | 
						|
            const paramResult = this.parseParam();
 | 
						|
            if (!paramResult.ok) {
 | 
						|
                return [];
 | 
						|
            }
 | 
						|
            params.push(paramResult.value);
 | 
						|
        }
 | 
						|
        if (!this.test("}")) {
 | 
						|
            this.report("expected '}'");
 | 
						|
            return params;
 | 
						|
        }
 | 
						|
        this.step();
 | 
						|
        return params;
 | 
						|
    }
 | 
						|
 | 
						|
    private step() {
 | 
						|
        this.currentToken = this.lexer.next();
 | 
						|
    }
 | 
						|
    private done(): boolean {
 | 
						|
        return this.currentToken == null;
 | 
						|
    }
 | 
						|
    private current(): Token {
 | 
						|
        return this.currentToken!;
 | 
						|
    }
 | 
						|
    private pos(): Pos {
 | 
						|
        if (this.done()) {
 | 
						|
            return this.lexer.currentPos();
 | 
						|
        }
 | 
						|
        return this.current().pos;
 | 
						|
    }
 | 
						|
 | 
						|
    private test(type: string): boolean {
 | 
						|
        return !this.done() && this.current().type === type;
 | 
						|
    }
 | 
						|
 | 
						|
    private report(msg: string, pos = this.pos()) {
 | 
						|
        console.log(`Parser: ${msg} at ${pos.line}:${pos.col}`);
 | 
						|
        this.reporter.reportError({
 | 
						|
            msg,
 | 
						|
            pos,
 | 
						|
            reporter: "Parser",
 | 
						|
        });
 | 
						|
        printStackTrace();
 | 
						|
    }
 | 
						|
 | 
						|
    private stmt(kind: StmtKind, pos: Pos): Stmt {
 | 
						|
        return this.astCreator.stmt(kind, pos);
 | 
						|
    }
 | 
						|
 | 
						|
    private expr(kind: ExprKind, pos: Pos): Expr {
 | 
						|
        return this.astCreator.expr(kind, pos);
 | 
						|
    }
 | 
						|
 | 
						|
    private etype(kind: ETypeKind, pos: Pos): EType {
 | 
						|
        return this.astCreator.etype(kind, pos);
 | 
						|
    }
 | 
						|
}
 |