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.repr()}`, token.line); } private select(): AST.Statement | Error { this.eat(TokenKind.SELECT); const args = this.selectArguments(); 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 selectArguments(): AST.SelectArgument[] | Error { const args = []; while (true) { const arg = this.expr(); if (isError(arg)) { return arg; } args.push(arg); if (this.match(TokenKind.COMMA)) { this.eat(TokenKind.COMMA); } else { break; } } return args; } private from(): AST.FromTarget | Error { this.eat(TokenKind.FROM); return this.fromTarget(); } private fromTarget(): AST.FromTarget | Error { if (this.match(TokenKind.LPAREN)) { this.eat(TokenKind.LPAREN); const stmt = this.select(); if (isError(stmt)) { return stmt; } const rparen = this.eat(TokenKind.RPAREN); if (isError(rparen)) { return rparen; } if (this.match(TokenKind.AS)) { return this.alias(stmt); } return stmt; } const primary = this.backtickOrIdentifier(); if (isError(primary)) { return primary; } return this.alias(primary); } private alias(obj: T): AST.Alias | T | Error { if (!this.match(TokenKind.AS)) { return obj; } this.eat(TokenKind.AS); const token = this.currentToken(); const alias = this.match(TokenKind.BACKTICK) ? this.backtick() : this.match(TokenKind.IDENTIFIER) ? this.identifier() : new Error(`Unexpected token: ${token.repr()}`, token.line); if (isError(alias)) { return alias; } return new AST.Alias(obj, alias); } 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 primary(): AST.Expr | Error { const token = this.currentToken(); const primary = (() => { switch (token.kind) { case TokenKind.NUMBER: return this.number(); case TokenKind.IDENTIFIER: return this.identifier(); case TokenKind.BACKTICK: return this.backtick(); case TokenKind.LPAREN: return this.group(); default: return new Error(`Unexpected token: ${token.repr()}`, token.line); } })(); if (isError(primary)) { return primary; } if (this.match(TokenKind.AS)) { this.eat(TokenKind.AS); const name = this.match(TokenKind.BACKTICK) ? this.backtick() : this.identifier(); if (isError(name)) { return name; } return new AST.Alias(primary, name); } else if (this.match(TokenKind.DOT)) { this.eat(TokenKind.DOT); const right = this.match(TokenKind.BACKTICK) ? this.backtick() : this.identifier(); if (isError(right)) { return right; } return new AST.Binary({ left: primary, right, type: AST.BinaryExpressionTypes.DOT, }); } return primary; } private group(): AST.Expr | Error { this.eat(TokenKind.LPAREN); const expr = this.expr(); const rparen = this.eat(TokenKind.RPAREN); if (isError(rparen)) { return rparen; } return expr; } private backtickOrIdentifier(): AST.Backtick | AST.Identifier | Error { const token = this.currentToken(); switch (token.kind) { case TokenKind.BACKTICK: return this.backtick(); case TokenKind.IDENTIFIER: return this.identifier(); default: return new Error(`Unexpected token: ${token.repr()}`, token.line); } } private backtick(): AST.Backtick | Error { this.eat(TokenKind.BACKTICK); const identifier = this.identifier(); if (isError(identifier)) { return identifier; } const closeTick = this.eat(TokenKind.BACKTICK); if (isError(closeTick)) { return closeTick; } return new AST.Backtick(identifier); } 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; } return new Error(`Unexpected token: ${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); } }