import { Anno, AssignType, AstCreator, BinaryType, EType, ETypeKind, Expr, ExprKind, GenericParam, Param, Stmt, StmtKind, UnaryType, } from "./ast.ts"; import { printStackTrace, Reporter } from "./info.ts"; import { Lexer } from "./lexer.ts"; import { Pos, Token } from "./token.ts"; type Res = { ok: true; value: T } | { ok: false }; 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(); 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(); } let anno: Anno | undefined; if (this.test("#")) { const result = this.parseAnno(); if (!result.ok) { return this.stmt({ type: "error" }, pos); } anno = result.value; } 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, anno, }, 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(): Res { const pos = this.pos(); this.step(); if (!this.test("[")) { this.report("expected '['"); return { ok: false }; } this.step(); if (!this.test("ident")) { this.report("expected identifier"); return { ok: false }; } const ident = this.current().identValue!; const values = this.parseAnnoArgs(); if (!this.test("]")) { this.report("expected ']'"); return { ok: false }; } this.step(); return { ok: true, value: { ident, pos, values } }; } private parseFnETypeParams(): GenericParam[] { return this.parseDelimitedList(this.parseETypeParam, ">", ","); } private veryTemporaryETypeParamIdCounter = 0; private parseETypeParam(): Res { const pos = this.pos(); if (this.test("ident")) { const ident = this.current().identValue!; this.step(); const id = this.veryTemporaryETypeParamIdCounter; this.veryTemporaryETypeParamIdCounter += 1; return { ok: true, value: { id, ident, pos } }; } this.report("expected generic parameter"); return { ok: false }; } private parseFnParams(): Param[] { return this.parseDelimitedList(this.parseParam, ")", ","); } private parseDelimitedList( parseElem: (this: Parser) => Res, endToken: string, delimiter: string, ): T[] { this.step(); if (this.test(endToken)) { this.step(); return []; } const elems: T[] = []; const elemRes = parseElem.call(this); if (!elemRes.ok) { return []; } elems.push(elemRes.value); while (this.test(delimiter)) { this.step(); if (this.test(endToken)) { break; } const elemRes = parseElem.call(this); if (!elemRes.ok) { return []; } elems.push(elemRes.value); } if (!this.test(endToken)) { this.report(`expected '${endToken}'`); return elems; } this.step(); return elems; } private parseParam(): Res { 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); } 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); } 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 { return { ok: true, value: this.parseExpr() }; } private parseETypeArg(): Res { 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.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 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()) { 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); } }