import { BinaryType, ParsedExpr, ParsedParameter, ParsedType, } from "./parsed.ts"; import { CompileError, Position, Token, TokenIter, TokenType, } from "./token.ts"; export class Parser { private current: Token; public errors: CompileError[] = []; public constructor(private lexer: TokenIter) { this.current = lexer.next(); } public parse(): ParsedExpr[] { while (this.currentIs("semicolon")) this.step(); const statements: ParsedExpr[] = []; while (!this.done()) { while (this.currentIs("semicolon")) this.step(); } return statements; } private checkAndSkipStatementLinebreak() { if (!this.currentIs("semicolon")) { this.errors.push({ pos: this.position(), message: `expected ';' after statement, got '${this.current.tokenType}'`, }); } else { this.step(); } } public parseStatement(): ParsedExpr { if (this.currentIs("fn")) { return this.parseFn(); } else if (this.currentIs("if")) { return this.parseIf(); } else if (this.currentIs("loop")) { return this.parseLoop(); } else if (this.currentIs("while")) { return this.parseWhile(); } else if (this.currentIs("return")) { const statement = this.parseReturn(); this.checkAndSkipStatementLinebreak(); return statement; } else if (this.currentIs("break")) { const statement = this.parseBreak(); this.checkAndSkipStatementLinebreak(); return statement; } else if (this.currentIs("continue")) { const statement = this.parseContinue(); this.checkAndSkipStatementLinebreak(); return statement; } else if (this.currentIs("let")) { const statement = this.parseLet(); this.checkAndSkipStatementLinebreak(); return statement; } else { const statement = this.parseAssign(); this.checkAndSkipStatementLinebreak(); return statement; } } private parseFn(): ParsedExpr { const pos = this.position(); this.step(); if (this.done() || this.current.tokenType != "id") { return this.errorExpr( `expected 'Id' after 'fn' as function name, got '${this.current.tokenType}'`, ); } const subject = this.current.value; this.step(); if (!this.currentIs("lparen")) { return this.errorExpr( `expected '(' before function-parameters, got '${this.current.tokenType}'`, ); } this.step(); const params: ParsedParameter[] = []; params.push(this.parseParam()); while (this.currentIs("comma")) { this.step(); if (this.done() || this.currentIs("rparen")) break; params.push(this.parseParam()); } if (!this.currentIs("rparen")) { return this.errorExpr( `expected ')' after function-parameters, got '${this.current.tokenType}'`, ); } this.step(); let returnType: ParsedType | null = null; if (this.currentIs("minusgt")) { this.step(); returnType = this.parseType(); } if (!this.currentIs("lbrace")) { return this.errorExpr( `expected '{' after 'loop', got '${this.current.tokenType}'`, ); } const body = this.parseBlock(); return { pos, exprType: "fn", subject, params, returnType, body, }; } private parseLoop(): ParsedExpr { const pos = this.position(); this.step(); if (!this.currentIs("lbrace")) { return this.errorExpr( `expected '{' after 'loop', got '${this.current.tokenType}'`, ); } const body = this.parseBlock(); return { pos, exprType: "loop", body, }; } private parseWhile(): ParsedExpr { const pos = this.position(); this.step(); const condition = this.parseExpr(); if (!this.currentIs("lbrace")) { return this.errorExpr( `expected '{' after while-condition, got '${this.current.tokenType}'`, ); } const body = this.parseBlock(); return { pos, exprType: "while", condition, body, }; } private parseReturn(): ParsedExpr { const pos = this.position(); this.step(); let value: ParsedExpr | null = null; if (!this.done() && this.current.tokenType != "semicolon") { value = this.parseExpr(); } return { pos, exprType: "return", value }; } private parseBreak(): ParsedExpr { const pos = this.position(); this.step(); return { pos, exprType: "break" }; } private parseContinue(): ParsedExpr { const pos = this.position(); this.step(); return { pos, exprType: "continue" }; } private parseLet(): ParsedExpr { const pos = this.position(); this.step(); const param = this.parseParam(); if (!this.currentIs("equal")) { return this.errorExpr( `expected '=' after let-parameter, got '${this.current.tokenType}'`, ); } this.step(); const value = this.parseExpr(); return { pos, exprType: "let", param, value, }; } private parseParam(): ParsedParameter { const pos = this.position(); const mutable = this.currentIs("mut"); if (mutable) this.step(); if (this.done() || this.current.tokenType != "id") { return this.errorParam( `expected 'id' in parameter, got '${this.current.tokenType}'`, ); } const subject = this.current.value; this.step(); let valueType: ParsedType | null = null; if (this.currentIs("colon")) { this.step(); valueType = this.parseType(); } return { pos, paramType: "value", subject, valueType, mutable, }; } private parseAssign(): ParsedExpr { const pos = this.position(); const subject = this.parseExpr(); if (this.currentIs("equal")) { this.step(); const value = this.parseExpr(); return { pos, exprType: "assign", assignType: "equal", subject, value, }; } else if (this.currentIs("plusequal")) { this.step(); const value = this.parseExpr(); return { pos, exprType: "assign", assignType: "add", subject, value, }; } else if (this.currentIs("minusequal")) { this.step(); const value = this.parseExpr(); return { pos, exprType: "assign", assignType: "subtract", subject, value, }; } else if (this.currentIs("ampersandequal")) { this.step(); const value = this.parseExpr(); return { pos, exprType: "assign", assignType: "and", subject, value, }; } else if (this.currentIs("pipeequal")) { this.step(); const value = this.parseExpr(); return { pos, exprType: "assign", assignType: "or", subject, value, }; } else if (this.currentIs("hatequal")) { this.step(); const value = this.parseExpr(); return { pos, exprType: "assign", assignType: "xor", subject, value, }; } else { return subject; } } public parseExpr(): ParsedExpr { return this.parseLogOr(); } private binaryExpr( binaryType: BinaryType, left: ParsedExpr, right: ParsedExpr, pos: Position, ): ParsedExpr { return { pos, exprType: "binary", binaryType, left, right, }; } private parseLogOr(): ParsedExpr { const pos = this.position(); let left = this.parseLogAnd(); while (!this.done()) { if (this.currentIs("or")) { this.step(); const right = this.parseLogAnd(); left = this.binaryExpr("log_or", left, right, pos); } else { break; } } return left; } private parseLogAnd(): ParsedExpr { const pos = this.position(); let left = this.parseBitOr(); while (!this.done()) { if (this.currentIs("and")) { this.step(); const right = this.parseBitOr(); left = this.binaryExpr("log_and", left, right, pos); } else { break; } } return left; } private parseBitOr(): ParsedExpr { const pos = this.position(); let left = this.parseBitXor(); while (!this.done()) { if (this.currentIs("pipe")) { this.step(); const right = this.parseBitXor(); left = this.binaryExpr("bit_or", left, right, pos); } else { break; } } return left; } private parseBitXor(): ParsedExpr { const pos = this.position(); let left = this.parseBitAnd(); while (!this.done()) { if (this.currentIs("hat")) { this.step(); const right = this.parseBitAnd(); left = this.binaryExpr("bit_xor", left, right, pos); } else { break; } } return left; } private parseBitAnd(): ParsedExpr { const pos = this.position(); let left = this.parseEquality(); while (!this.done()) { if (this.currentIs("ampersand")) { this.step(); const right = this.parseEquality(); left = this.binaryExpr("bit_and", left, right, pos); } else { break; } } return left; } private parseEquality(): ParsedExpr { const pos = this.position(); let left = this.parseComparison(); while (!this.done()) { if (this.currentIs("equalequal")) { this.step(); const right = this.parseComparison(); left = this.binaryExpr("equal", left, right, pos); } else if (this.currentIs("exclamationequal")) { this.step(); const right = this.parseComparison(); left = this.binaryExpr("inequal", left, right, pos); } else { break; } } return left; } private parseComparison(): ParsedExpr { const pos = this.position(); const left = this.parseAddSubtract(); if (this.currentIs("lt")) { this.step(); const right = this.parseAddSubtract(); return this.binaryExpr("lt", left, right, pos); } else if (this.currentIs("gt")) { this.step(); const right = this.parseAddSubtract(); return this.binaryExpr("gt", left, right, pos); } else if (this.currentIs("ltequal")) { this.step(); const right = this.parseAddSubtract(); return this.binaryExpr("ltequal", left, right, pos); } else if (this.currentIs("gtequal")) { this.step(); const right = this.parseAddSubtract(); return this.binaryExpr("gtequal", left, right, pos); } else { return left; } } private parseAddSubtract(): ParsedExpr { const pos = this.position(); let left = this.parseUnary(); while (!this.done()) { if (this.currentIs("plus")) { this.step(); const right = this.parseUnary(); left = this.binaryExpr("add", left, right, pos); } else if (this.currentIs("minus")) { this.step(); const right = this.parseUnary(); left = this.binaryExpr("subtract", left, right, pos); } else { break; } } return left; } private parseUnary(): ParsedExpr { if (this.currentIs("minus")) { const pos = this.position(); this.step(); return { pos, exprType: "unary", unaryType: "negate", subject: this.parseUnary(), }; } else if (this.currentIs("not")) { const pos = this.position(); this.step(); return { pos, exprType: "unary", unaryType: "log_not", subject: this.parseUnary(), }; } else if (this.currentIs("tilde")) { const pos = this.position(); this.step(); return { pos, exprType: "unary", unaryType: "bit_not", subject: this.parseUnary(), }; } else if (this.currentIs("ampersand")) { const pos = this.position(); this.step(); return { pos, exprType: "unary", unaryType: "addressof", subject: this.parseUnary(), }; } else if (this.currentIs("asterisk")) { const pos = this.position(); this.step(); return { pos, exprType: "unary", unaryType: "dereference", subject: this.parseUnary(), }; } else { return this.parseCallIndex(); } } private parseCallIndex(): ParsedExpr { const pos = this.position(); let subject = this.parseOperand(); while (!this.done()) { if (this.currentIs("lparen")) { this.step(); const args: ParsedExpr[] = []; if (!this.done() && this.current.tokenType != "rparen") { args.push(this.parseExpr()); while (this.currentIs("comma")) { this.step(); if (this.done() || this.currentIs("rparen")) { break; } args.push(this.parseExpr()); } } if (!this.currentIs("rparen")) { return this.errorExpr( `expected ')', got '${this.current.tokenType}'`, ); } this.step(); subject = { pos, exprType: "call", subject, args, }; } else if (this.currentIs("lbracket")) { this.step(); const value = this.parseExpr(); if (!this.currentIs("rbracket")) { return this.errorExpr( `expected ']', got '${this.current.tokenType}'`, ); } this.step(); subject = { pos, exprType: "index", subject, value, }; } else { break; } } return subject; } private parseOperand(): ParsedExpr { if (this.current.tokenType == "id") { const pos = this.position(); const value = this.current.value; this.step(); return { pos, exprType: "id", value, }; } else if (this.current.tokenType == "int") { const pos = this.position(); const value = this.current.value; this.step(); return { pos, exprType: "int", value, }; } else if (this.currentIs("lparen")) { const pos = this.position(); this.step(); if (this.currentIs("rparen")) { this.step(); return { pos, exprType: "unit" }; } const subject = this.parseExpr(); if (!this.currentIs("rparen")) { return this.errorExpr( `expected ')', got '${this.current.tokenType}'`, ); } this.step(); subject.pos = pos; return subject; } else if (this.currentIs("if")) { return this.parseIf(); } else if (this.currentIs("lbrace")) { return this.parseBlock(); } else { return this.errorExpr( `expected operand, got '${this.current.tokenType}'`, ); } } private parseIf(): ParsedExpr { const pos = this.position(); this.step(); const condition = this.parseExpr(); if (!this.currentIs("lbrace")) { return this.errorExpr( `expected '{' after if-condition, got '${this.current.tokenType}'`, ); } const truthy = this.parseBlock(); let falsy: ParsedExpr | null = null; if (this.currentIs("else")) { this.step(); if (!this.currentIs("lbrace")) { return this.errorExpr( `expected '{' after 'else', got '${this.current.tokenType}'`, ); } falsy = this.parseBlock(); } return { pos, exprType: "if", condition, truthy, falsy, }; } private parseBlock(): ParsedExpr { const pos = this.position(); this.step(); while (this.currentIs("semicolon")) this.step(); const body: ParsedExpr[] = []; while (!this.done() && this.current.tokenType != "rbrace") { body.push(this.parseStatement()); while (this.currentIs("semicolon")) this.step(); } if (!this.currentIs("rbrace")) { return this.errorExpr( `expected '}' after block, got '${this.current.tokenType}'`, ); } this.step(); return { pos, exprType: "block", body, }; } private parseType(): ParsedType { return this.parsePointerType(); } private parsePointerType(): ParsedType { if (this.currentIs("asterisk")) { const pos = this.position(); this.step(); let mutable = false; if (this.currentIs("mut")) { mutable = true; this.step(); } const subject = this.parseTypeOperand(); return { pos, typeType: "pointer", subject, mutable, }; } else { return this.parseTypeOperand(); } } private parseTypeOperand(): ParsedType { if (this.current.tokenType == "id") { const pos = this.position(); const value = this.current.value; this.step(); return { pos, typeType: "id", value, }; } else if (this.currentIs("lparen")) { const pos = this.position(); this.step(); if (this.currentIs("rparen")) { this.step(); return { pos, typeType: "unit" }; } const subject = this.parseType(); if (!this.currentIs("rparen")) { return this.errorType( `expected ')', got '${this.current.tokenType}'`, ); } this.step(); subject.pos = pos; return subject; } else { return this.errorType( `expected type, got '${this.current.tokenType}'`, ); } } private errorExpr( message: string, pos: Position = this.position(), ): ParsedExpr { this.errors.push({ pos, message }); return { pos, exprType: "error", message, }; } private errorParam( message: string, pos: Position = this.position(), ): ParsedParameter { this.errors.push({ pos, message }); return { pos, paramType: "error", message, }; } private errorType( message: string, pos: Position = this.position(), ): ParsedType { this.errors.push({ pos, message }); return { pos, typeType: "error", message, }; } private position() { return this.current.pos; } private step() { this.current = this.lexer.next(); } private currentIs(tokenType: TokenType): boolean { return this.current.tokenType == tokenType; } private done(): boolean { return this.current.tokenType == "eof"; } }