import { Anno, BinaryType, EType, ETypeKind, Expr, ExprKind, Param, Stmt, StmtKind, } 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; private nextNodeId = 0; public constructor(private lexer: Lexer, private reporter: Reporter) { 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()) { this.reporter.reportError({ msg, pos, reporter: "Parser", }); printStackTrace(); } 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 etype(kind: ETypeKind, pos: Pos): EType { 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(); 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, ); } public 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; } public 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 }; } public 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; } public 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 }; } 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); } public 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); } public 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; } }