import * as AST from "./ast"; import Error, { isError } from "./error"; import Token, { TokenKind } from "./token"; export default class Parser { public tokens: Token[]; public position: number; constructor(tokens: Token[]) { this.tokens = tokens; this.position = 0; } public parse(): AST.Statement[] | Error { const tree: AST.Statement[] = []; while (!this.atEnd()) { const stmt = this.statement(); if (isError(stmt)) { return stmt; } else { tree.push(stmt); } } return tree; } private statement(): AST.Statement | Error { const token = this.currentToken(); switch (token.kind) { case TokenKind.SELECT: return this.select(); } return new Error(`Unexpected token ${token.kind}`, token.line); } private select(): AST.Statement | Error { this.eat(TokenKind.SELECT); const args = this.args(); if (isError(args)) { return args; } const from = this.match(TokenKind.FROM) ? this.from() : null; if (isError(from)) { return from; } const where = this.match(TokenKind.WHERE) ? this.where() : null; if (isError(where)) { return where; } return new AST.SelectStatement({ arguments: args, from, where, }); } private args(): AST.Secondary[] | Error { const args = []; while (true) { const arg = this.alias(); if (isError(arg)) { return arg; } args.push(arg); if (this.match(TokenKind.COMMA)) { this.eat(TokenKind.COMMA); } else { break; } } return args; } private from(): AST.Secondary | Error { this.eat(TokenKind.FROM); return this.alias(this.identifier); } private where(): AST.Expr | Error { this.eat(TokenKind.WHERE); return this.expr(); } private expr(): AST.Expr | Error { return this.or(); } private or(): AST.Expr | Error { let left = this.and(); if (isError(left)) { return left; } while (this.match(TokenKind.OR)) { this.eat(TokenKind.OR); const right = this.and(); if (isError(right)) { return right; } left = new AST.Binary({ left, right, type: AST.BinaryExpressionTypes.OR, }); } return left; } private and(): AST.Expr | Error { let left = this.equality(); if (isError(left)) { return left; } while (this.match(TokenKind.AND)) { this.eat(TokenKind.AND); const right = this.equality(); if (isError(right)) { return right; } left = new AST.Binary({ left, right, type: AST.BinaryExpressionTypes.AND, }); } return left; } private equality(): AST.Expr | Error { const left = this.addition(); if (isError(left)) { return left; } if (this.match(TokenKind.EQUALS)) { this.eat(TokenKind.EQUALS); const right = this.addition(); if (isError(right)) { return right; } return new AST.Binary({ left, right, type: AST.BinaryExpressionTypes.EQUALITY, }); } return left; } private addition(): AST.Expr | Error { const left = this.multiplication(); if (isError(left)) { return left; } if (this.match(TokenKind.PLUS)) { this.eat(TokenKind.PLUS); const right = this.multiplication(); if (isError(right)) { return right; } return new AST.Binary({ left, right, type: AST.BinaryExpressionTypes.ADDITION, }); } if (this.match(TokenKind.MINUS)) { this.eat(TokenKind.MINUS); const right = this.multiplication(); if (isError(right)) { return right; } return new AST.Binary({ left, right, type: AST.BinaryExpressionTypes.SUBTRACTION, }); } return left; } private multiplication(): AST.Expr | Error { const left = this.primary(); if (isError(left)) { return left; } if (this.match(TokenKind.STAR)) { this.eat(TokenKind.STAR); const right = this.primary(); if (isError(right)) { return right; } return new AST.Binary({ left, right, type: AST.BinaryExpressionTypes.MULTIPLICATION, }); } if (this.match(TokenKind.SLASH)) { this.eat(TokenKind.SLASH); const right = this.primary(); if (isError(right)) { return right; } return new AST.Binary({ left, right, type: AST.BinaryExpressionTypes.DIVISION, }); } return left; } private alias(fn: () => AST.Primary | Error = this.primary): AST.Secondary | Error { const primary = fn.bind(this)(); if (isError(primary)) { return primary; } if (this.match(TokenKind.AS)) { this.eat(TokenKind.AS); const name = this.identifier(); if (isError(name)) { return name; } return new AST.Alias(primary, name); } return primary; } private primary(): AST.Primary | Error { const token = this.currentToken(); switch (token.kind) { case TokenKind.NUMBER: return this.number(); case TokenKind.IDENTIFIER: return this.identifier(); } return new Error( `Unexpected token ${token.kind} ${token.value}`, token.line, ); } private identifier(): AST.Identifier | Error { const identifier = this.eat(TokenKind.IDENTIFIER); if (isError(identifier)) { return identifier; } return new AST.Identifier(identifier.value || ""); } private number(): AST.Number | Error { const n = this.eat(TokenKind.NUMBER); if (isError(n)) { return n; } return new AST.Number(parseFloat(n.value || "")); } private eat(kind: TokenKind) { const token = this.currentToken(); if (token.kind === kind) { this.advance(); return token; } const repr = `{ kind: ${token.kind}${token.value ? `, value: ${token.value} }` : ""}`; return new Error(`Unexpected token: ${repr}`, token.line); } private match(kind: TokenKind): boolean { return this.currentToken().kind === kind; } private currentToken(): Token { return this.tokens[this.position]; } private advance() { this.position += 1; } private atEnd(): boolean { return this.match(TokenKind.EOF); } }