import { Expr, ExprKind, Param, Stmt, StmtKind, BinaryType} from "./ast.ts"; import { Lexer } from "./Lexer.ts"; import { Pos, Token } from "./Token.ts"; export class Parser { private currentToken: Token | null; private nextNodeId = 0; public constructor(private lexer: Lexer) { this.currentToken = lexer.next(); } private step() { this.currentToken = this.lexer.next() } public 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}`); class ReportNotAnError extends Error { constructor() { super("ReportNotAnError"); } } try { throw new ReportNotAnError(); } catch (error) { if (!(error instanceof ReportNotAnError)) throw error; console.log(error) } } private stmt(kind: StmtKind, pos: Pos): Stmt { const id = this.nextNodeId; this.nextNodeId += 1; return { kind, pos, id }; } private expr(kind: ExprKind, pos: Pos): Expr { const id = this.nextNodeId; this.nextNodeId += 1; return { kind, pos, id }; } 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(); 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(); } public parseExpr(): Expr { return this.parsePrefix(); } public 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 (this.test("{") || this.test("if") || this.test("loop")) { let expr = this.parseMultiLineBlockExpr(); if (this.test("}")) { this.step(); return this.expr({ type: "block", stmts, expr }, pos); } stmts.push(this.stmt({ type: "expr", expr }, expr.pos)); } else { const expr = this.parseExpr(); if (this.test("=")) { this.step(); const value = this.parseExpr(); this.eatSemicolon(); stmts.push(this.stmt({ type: "assign", subject: expr, value }, 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" }, pos); } } } this.report("expected '}'"); return this.expr({ type: "error" }, pos); } public parseStmts(): Stmt[] { let 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 (this.test("{") || this.test("if") || this.test("loop")) { const expr = this.parseMultiLineBlockExpr(); stmts.push(this.stmt({ type: "expr", expr }, expr.pos)); } else { stmts.push(this.parseAssign()); this.eatSemicolon(); } } return stmts; } public 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(); if (!this.test("{")) { this.report("expected block"); return this.stmt({ type: "error" }, pos); } const body = this.parseBlock(); return this.stmt({ type: "fn", ident, params, body }, pos); } public parseFnParams(): Param[] { this.step(); if (this.test(")")) { this.step(); return []; } let 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; } public parseParam(): { ok: true, value: Param } | { ok: false } { const pos = this.pos(); if (this.test("ident")) { const ident = this.current().identValue!; this.step(); return { ok: true, value: { ident, pos } }; } this.report("expected param"); return { ok: false }; } public 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); } public parseAssign(): Stmt { const pos = this.pos(); const subject = this.parseExpr(); if (!this.test("=")) { return this.stmt({ type: "expr", expr: subject }, pos); } this.step(); const value = this.parseExpr(); return this.stmt({ type: "assign", subject, value }, pos); } public 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); } public 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); } public 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); } public 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); } public parsePrefix(): Expr { const pos = this.pos(); if (this.test("not")) { this.step(); const subject = this.parsePrefix(); return this.expr({ type: "unary", unaryType: "not", subject }, pos); } for (const binaryType of ["+", "*", "==", "-", "/", "!=", "<", ">", "<=", ">=", "or", "and"]) { const subject = this.parseBinary(binaryType as BinaryType, pos) if (subject !== null) { return subject } } return this.parsePostfix(); } public parseBinary(binaryType: BinaryType, pos: Pos): Expr | null { if (this.test(binaryType)) { this.step(); const left = this.parsePrefix(); const right = this.parsePrefix(); return this.expr({ type: "binary", binaryType, left, right }, pos); } return null } public 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; } public 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); } }