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