import {
    AssignType,
    AstCreator,
    BinaryType,
    EType,
    ETypeKind,
    Expr,
    ExprKind,
    Field,
    GenericParam,
    Param,
    Stmt,
    StmtDetails,
    StmtKind,
    UnaryType,
} from "./ast.ts";
import { printStackTrace, Reporter } from "./info.ts";
import { Lexer } from "./lexer.ts";
import { Pos, Token } from "./token.ts";

type Res<T> = { ok: true; value: T } | { ok: false; pos?: Pos };

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()) {
            stmts.push(this.parseStmt());
        }
        return stmts;
    }

    private parseStmt(): Stmt {
        if (
            ["#", "pub", "mod", "fn"].some((tt) => this.test(tt))
        ) {
            return this.parseItemStmt();
        } else if (
            ["let", "type_alias", "return", "break"].some((tt) => this.test(tt))
        ) {
            const expr = this.parseSingleLineBlockStmt();
            this.eatSemicolon();
            return expr;
        } else if (
            ["{", "if", "loop", "while", "for"].some((tt) => this.test(tt))
        ) {
            const expr = this.parseMultiLineBlockExpr();
            return (this.stmt({ type: "expr", expr }, expr.pos));
        } else {
            const expr = this.parseAssign();
            this.eatSemicolon();
            return expr;
        }
    }

    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("type_alias")) {
            return this.parseTypeAlias();
        }
        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();
        const stmts: Stmt[] = [];
        while (!this.done()) {
            if (this.test("}")) {
                this.step();
                return this.expr({ type: "block", stmts }, pos);
            } else if (
                ["#", "pub", "mod", "fn"].some((tt) => this.test(tt))
            ) {
                stmts.push(this.parseItemStmt());
            } else if (
                ["let", "type_alias", "return", "break"]
                    .some((tt) => this.test(tt))
            ) {
                stmts.push(this.parseSingleLineBlockStmt());
                this.eatSemicolon();
            } 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 parseItemStmt(
        pos = this.pos(),
        details: StmtDetails = {
            pub: false,
            annos: [],
        },
    ): Stmt {
        const spos = this.pos();
        if (this.test("#") && !details.pub) {
            this.step();
            if (!this.test("[")) {
                this.report("expected '['");
                return this.stmt({ type: "error" }, spos);
            }
            this.step();
            if (!this.test("ident")) {
                this.report("expected 'ident'");
                return this.stmt({ type: "error" }, spos);
            }
            const ident = this.current().identValue!;
            this.step();
            const args: Expr[] = [];
            if (this.test("(")) {
                this.step();
                if (!this.done() && !this.test(")")) {
                    args.push(this.parseExpr());
                    while (this.test(",")) {
                        this.step();
                        if (this.done() || this.test(")")) {
                            break;
                        }
                        args.push(this.parseExpr());
                    }
                }
                if (!this.test(")")) {
                    this.report("expected ')'");
                    return this.stmt({ type: "error" }, spos);
                }
                this.step();
            }
            if (!this.test("]")) {
                this.report("expected ']'");
                return this.stmt({ type: "error" }, spos);
            }
            this.step();
            const anno = { ident, args, pos: spos };
            return this.parseItemStmt(pos, {
                ...details,
                annos: [...details.annos, anno],
            });
        } else if (this.test("pub") && !details.pub) {
            this.step();
            return this.parseItemStmt(pos, { ...details, pub: true });
        } else if (this.test("mod")) {
            return this.parseMod(details);
        } else if (this.test("fn")) {
            return this.parseFn(details);
        } else {
            this.report("expected item statement");
            return this.stmt({ type: "error" }, pos);
        }
    }

    private parseMod(details: StmtDetails): 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.eatSemicolon();
            return this.stmt({ type: "mod_file", ident, filePath: ident }, pos);
        }
        if (this.test("string")) {
            const filePath = this.current().stringValue!;
            this.step();
            this.eatSemicolon();
            return this.stmt({ type: "mod_file", ident, filePath }, pos);
        }

        if (!this.test("{")) {
            this.report("expected '{' or 'string'");
            return this.stmt({ type: "error" }, pos);
        }
        this.step();

        const stmts: Stmt[] = [];
        while (!this.done() && !this.test("}")) {
            stmts.push(this.parseStmt());
        }

        if (!this.test("}")) {
            this.report("expected '}'");
            return this.stmt({ type: "error" }, pos);
        }
        this.step();

        return this.stmt({ type: "mod_block", ident, stmts }, pos, details);
    }

    private parseFn(details: StmtDetails): 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();
        let genericParams: GenericParam[] | undefined;
        if (this.test("<")) {
            genericParams = this.parseFnETypeParams();
        }
        if (!this.test("(")) {
            this.report("expected '('");
            return this.stmt({ type: "error" }, pos);
        }
        const params = this.parseFnParams();
        let returnType: EType | undefined;
        if (this.test("->")) {
            this.step();
            returnType = this.parseEType();
        }

        if (!this.test("{")) {
            this.report("expected block");
            return this.stmt({ type: "error" }, pos);
        }
        const body = this.parseBlock();
        return this.stmt(
            {
                type: "fn",
                ident,
                genericParams,
                params,
                returnType,
                body,
            },
            pos,
            details,
        );
    }

    private parseFnETypeParams(): GenericParam[] {
        return this.parseDelimitedList(this.parseETypeParam, ">", ",");
    }

    private parseETypeParam(index: number): Res<GenericParam> {
        const pos = this.pos();
        if (this.test("ident")) {
            const ident = this.current().identValue!;
            this.step();
            return {
                ok: true,
                value: this.astCreator.genericParam({ index, ident, pos }),
            };
        }
        this.report("expected generic parameter");
        return { ok: false };
    }

    private parseFnParams(): Param[] {
        return this.parseDelimitedList(this.parseParam, ")", ",");
    }

    private parseDelimitedList<T>(
        parseElem: (this: Parser, index: number) => Res<T>,
        endToken: string,
        delimiter: string,
    ): T[] {
        this.step();
        if (this.test(endToken)) {
            this.step();
            return [];
        }
        let i = 0;
        const elems: T[] = [];
        const elemRes = parseElem.call(this, i);
        if (!elemRes.ok) {
            return [];
        }
        elems.push(elemRes.value);
        i += 1;
        while (this.test(delimiter)) {
            this.step();
            if (this.test(endToken)) {
                break;
            }
            const elemRes = parseElem.call(this, i);
            if (!elemRes.ok) {
                return [];
            }
            elems.push(elemRes.value);
            i += 1;
        }
        if (!this.test(endToken)) {
            this.report(`expected '${endToken}'`);
            return elems;
        }
        this.step();
        return elems;
    }

    private parseParam(index?: number): Res<Param> {
        const pos = this.pos();
        if (this.test("ident") || this.test("mut")) {
            let mut = false;
            if (this.test("mut")) {
                mut = true;
                this.step();
            }
            const ident = this.current().identValue!;
            this.step();
            if (this.test(":")) {
                this.step();
                const etype = this.parseEType();
                return {
                    ok: true,
                    value: this.astCreator.param({
                        index,
                        ident,
                        mut,
                        etype,
                        pos,
                    }),
                };
            }
            return {
                ok: true,
                value: this.astCreator.param({ index, ident, mut, 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 parseTypeAlias(): Stmt {
        const pos = this.pos();
        this.step();
        const paramResult = this.parseParam();
        if (!paramResult.ok) {
            return this.stmt({ type: "error" }, pos);
        }
        const param = paramResult.value;
        return this.stmt({ type: "type_alias", param }, 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 parseArray(): Expr {
        const pos = this.pos();
        this.step();
        const exprs: Expr[] = [];
        if (!this.test("]")) {
            exprs.push(this.parseExpr());
            while (this.test(",")) {
                this.step();
                if (this.done() || this.test("]")) {
                    break;
                }
                exprs.push(this.parseExpr());
            }
        }
        if (!this.test("]")) {
            this.report("expected ']'");
            return this.expr({ type: "error" }, pos);
        }
        this.step();
        return this.expr({ type: "array", exprs }, pos);
    }

    private parseStruct(): Expr {
        const pos = this.pos();
        this.step();
        if (!this.test("{")) {
            this.report("expected '{'");
            return this.expr({ type: "error" }, pos);
        }
        this.step();
        const fields: Field[] = [];
        if (!this.test("}")) {
            const res = this.parseStructField();
            if (!res.ok) {
                return this.expr({ type: "error" }, res.pos!);
            }
            fields.push(res.value);
            while (this.test(",")) {
                this.step();
                if (this.done() || this.test("}")) {
                    break;
                }
                const res = this.parseStructField();
                if (!res.ok) {
                    return this.expr({ type: "error" }, res.pos!);
                }
                fields.push(res.value);
            }
        }
        if (!this.test("}")) {
            this.report("expected '}'");
            return this.expr({ type: "error" }, pos);
        }
        this.step();
        return this.expr({ type: "struct", fields }, pos);
    }

    private parseStructField(): Res<Field> {
        const pos = this.pos();
        if (!this.test("ident")) {
            this.report("expected 'ident'");
            return { ok: false, pos };
        }
        const ident = this.current().identValue!;
        this.step();
        if (!this.test(":")) {
            this.report("expected ':'");
            return { ok: false, pos };
        }
        this.step();
        const expr = this.parseExpr();
        return { ok: true, value: { ident, expr, 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);
        }
        const elsePos = this.pos();
        this.step();
        if (this.test("if")) {
            const falsy = this.parseIf();
            return this.expr({ type: "if", cond, truthy, falsy, elsePos }, 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, elsePos }, 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);
        }
        if (this.test("&")) {
            this.step();
            let type: "ref" | "ref_mut" = "ref";
            if (this.test("mut")) {
                this.step();
                type = "ref_mut";
            }
            const subject = this.parsePrefix();
            return this.expr({ type, subject }, pos);
        }
        if (this.test("*")) {
            this.step();
            const subject = this.parsePrefix();
            return this.expr({ type: "deref", subject }, pos);
        }
        return this.parsePostfix();
    }

    private parsePostfix(): Expr {
        let subject = this.parseOperand();
        while (true) {
            if (this.test(".")) {
                subject = this.parseFieldTail(subject);
                continue;
            }
            if (this.test("[")) {
                subject = this.parseIndexTail(subject);
                continue;
            }
            if (this.test("(")) {
                subject = this.parseCallTail(subject);
                continue;
            }
            if (this.test("::")) {
                subject = this.parsePathTail(subject);
                continue;
            }
            if (this.test("::<")) {
                subject = this.parseETypeArgsTail(subject);
                continue;
            }
            break;
        }
        return subject;
    }

    private parseETypeArgsTail(subject: Expr): Expr {
        const pos = this.pos();
        const etypeArgs = this.parseDelimitedList(
            this.parseETypeArg,
            ">",
            ",",
        );
        return this.expr(
            { type: "etype_args", subject, etypeArgs },
            pos,
        );
    }

    private parseFieldTail(subject: Expr): Expr {
        const pos = this.pos();
        this.step();
        if (!this.test("ident")) {
            this.report("expected ident");
            return this.expr({ type: "error" }, pos);
        }
        const ident = this.current().identValue!;
        this.step();
        return this.expr({ type: "field", subject, ident }, pos);
    }

    private parseIndexTail(subject: Expr): Expr {
        const pos = this.pos();
        this.step();
        const value = this.parseExpr();
        if (!this.test("]")) {
            this.report("expected ']'");
            return this.expr({ type: "error" }, pos);
        }
        this.step();
        return this.expr({ type: "index", subject, value }, pos);
    }

    private parseCallTail(subject: Expr): Expr {
        const pos = this.pos();
        const args = this.parseDelimitedList(
            this.parseExprArg,
            ")",
            ",",
        );
        return this.expr({ type: "call", subject, args }, pos);
    }

    private parsePathTail(subject: Expr): Expr {
        const pos = this.pos();
        this.step();
        if (!this.test("ident")) {
            this.report("expected ident");
            return this.expr({ type: "error" }, pos);
        }
        const ident = this.current().identValue!;
        this.step();
        return this.expr({ type: "path", subject, ident }, pos);
    }

    private parseExprArg(): Res<Expr> {
        return { ok: true, value: this.parseExpr() };
    }

    private parseETypeArg(): Res<EType> {
        return { ok: true, value: this.parseEType() };
    }

    private parseOperand(): Expr {
        const pos = this.pos();
        if (this.test("ident")) {
            const ident = this.current().identValue!;
            this.step();
            return this.expr({ type: "ident", ident }, 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.parseArray();
        }
        if (this.test("struct")) {
            return this.parseStruct();
        }
        if (this.test("{")) {
            return this.parseBlock();
        }
        if (this.test("if")) {
            return this.parseIf();
        }
        if (this.test("loop")) {
            return this.parseLoop();
        }

        this.report(`expected expr, got '${this.current().type}'`, pos);
        this.step();
        return this.expr({ type: "error" }, pos);
    }

    private parseEType(): EType {
        const pos = this.pos();
        if (["null", "int", "bool", "string"].includes(this.current().type)) {
            const type = this.current().type as
                | "null"
                | "int"
                | "bool"
                | "string";
            this.step();
            return this.etype({ type }, pos);
        }
        if (this.test("ident")) {
            const ident = this.current().identValue!;
            this.step();
            return this.etype({ type: "ident", ident: ident }, pos);
        }
        if (this.test("[")) {
            this.step();
            const subject = this.parseEType();
            if (!this.test("]")) {
                this.report("expected ']'", pos);
                return this.etype({ type: "error" }, pos);
            }
            this.step();
            return this.etype({ type: "array", subject }, 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);
        }
        if (this.test("&")) {
            this.step();
            let type: "ref" | "ref_mut" = "ref";
            if (this.test("mut")) {
                this.step();
                type = "ref_mut";
            }
            const subject = this.parseEType();
            return this.etype({ type, subject }, pos);
        }
        if (this.test("*")) {
            this.step();
            let type: "ptr" | "ptr_mut" = "ptr";
            if (this.test("mut")) {
                this.step();
                type = "ptr_mut";
            }
            const subject = this.parseEType();
            return this.etype({ type, subject }, 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()) {
        this.reporter.reportError({
            msg,
            pos,
            reporter: "Parser",
        });
        printStackTrace();
    }

    private stmt(kind: StmtKind, pos: Pos, details?: StmtDetails): Stmt {
        return this.astCreator.stmt(kind, pos, details);
    }

    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);
    }
}
