import { AstBuilder, Block, Expr, File, Param, Pat, Stmt, Ty } from "./ast.ts"; import { Tok } from "./tok.ts"; const t = new AstBuilder(); export class Parser { private i = 0; private eaten?: Tok; constructor( private toks: Tok[], ) {} parseFile(): File { const stmts: Stmt[] = []; while (!this.done) { stmts.push(this.parseItem()); } return t.file(stmts); } parseItem(): Stmt { const line = this.line(); if (this.test("fn")) { return this.parseFn(); } else if (this.test("let")) { return this.parseFn(); } else { this.expect("item"); this.step(); return t.stmt(line, "error", {}); } } parseBlock(): Block { const line = this.line(); this.step(); const stmts: Stmt[] = []; let expr: Expr | undefined = undefined; while (!this.done && !this.test("}")) { const line = this.line(); if (this.test("fn")) { stmts.push(this.parseFn()); } else if (this.test("let")) { stmts.push(this.parseLet()); this.expect(";"); } else { const lhs = this.parseExpr(); if (this.eat("=")) { const rhs = this.parseExpr(); this.expect(";"); stmts.push( t.stmt(line, "assign", { place: lhs, expr: rhs }), ); } else if (this.eat(";")) { stmts.push(t.stmt(line, "expr", { expr: lhs })); } else if (this.test("}")) { expr = lhs; break; } else { this.expect(";"); } } } this.expect("}"); return t.block(line, stmts, expr); } parseFn(): Stmt { const line = this.line(); this.step(); if (!this.expect("ident")) { return t.stmt(line, "error", {}); } const ident = this.eaten!.value!; const params: Param[] = []; if (!this.expect("(")) { return t.stmt(line, "error", {}); } if (!this.done && !this.test(")")) { params.push(this.parseParam()); while (this.eat(",")) { if (this.done || this.test(")")) { break; } params.push(this.parseParam()); } } if (!this.expect(")")) { return t.stmt(line, "error", {}); } let retTy: Ty | undefined = undefined; if (this.eat("->")) { retTy = this.parseTy(); } if (!this.test("{")) { this.expect("{"); return t.stmt(line, "error", {}); } const body = this.parseBlock(); return t.stmt(line, "fn", { ident, params, retTy, body }); } parseLet(): Stmt { const line = this.line(); this.step(); const param = this.parseParam(); if (!this.expect("=")) { return t.stmt(line, "error", {}); } const init = this.parseExpr(); return t.stmt(line, "let", { param, init }); } parseExpr(): Expr { return this.parsePostfix(); } parsePostfix(): Expr { let expr = this.parseOp(); while (true) { const line = this.line(); if (this.eat("(")) { const args: Expr[] = []; if (!this.done && !this.test(")")) { args.push(this.parseExpr()); while (this.eat(",")) { if (this.done || this.test(")")) { break; } args.push(this.parseExpr()); } } if (!this.expect(")")) { return t.expr(line, "error", {}); } expr = t.expr(line, "call", { expr, args }); } else { break; } } return expr; } parseOp(): Expr { const line = this.line(); if (this.eat("ident")) { const ident = this.eaten!.value!; return t.expr(line, "ident", { ident }); } else if (this.eat("int")) { const value = this.eaten!.value!; return t.expr(line, "int", { value }); } else if (this.eat("char")) { const value = this.eaten!.value!; return t.expr(line, "int", { value }); } else if (this.eat("str")) { const value = this.eaten!.value!; return t.expr(line, "int", { value }); } else { this.expect("expr"); this.step(); return t.expr(line, "error", {}); } } parseParam(): Param { const line = this.line(); const pat = this.parsePat(); let ty: Ty | undefined = undefined; if (this.eat(":")) { ty = this.parseTy(); } return t.param(line, pat, ty); } parsePat(): Pat { const line = this.line(); if (this.eat("ident")) { const ident = this.eaten!.value!; return t.pat(line, "ident", { ident }); } else { this.expect("pat"); this.step(); return t.pat(line, "error", {}); } } parseTy(): Ty { const line = this.line(); if (this.eat("ident")) { const ident = this.eaten!.value!; return t.ty(line, "ident", { ident }); } else { this.expect("ty"); this.step(); return t.ty(line, "error", {}); } } private expect(type: string): boolean { const line = this.line(); if (!this.eat(type)) { if (this.done) { this.error(line, `expected '${type}', got 'eof'`); } else { this.error(line, `expected '${type}', got '${this.tok.type}'`); } return false; } return true; } private eat(type: string): boolean { if (this.test(type)) { this.eaten = this.tok; this.step(); return true; } return false; } private test(type: string): boolean { return !this.done && this.tok.type === type; } private step() { this.i += 1; } private line(): number { return this.tok.line; } private get tok(): Tok { return this.toks[this.i]; } private get done(): boolean { return this.i >= this.toks.length; } private error(line: number, message: string) { console.error( `%cerror%c: ${message}\n %c--> line ${line}%c`, "font-weight: bold; color: red", "font-weight: bold; color: while", "color: cyan", "", ); } }