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.currentToken().kind === TokenKind.FROM ? this.from() : null; if (isError(from)) { return from; } return new AST.SelectStatement({ arguments: args, from, }); } private args(): AST.Primary[] | Error { const args = []; while (true) { const arg = this.primary(); if (isError(arg)) { return arg; } args.push(arg); if (this.currentToken().kind === TokenKind.COMMA) { this.eat(TokenKind.COMMA); } else { break; } } return args; } private from(): AST.Primary | Error { this.eat(TokenKind.FROM); return this.identifier(); } 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; } return new Error(`Unexpected token ${token}`, token.line); } private currentToken(): Token { return this.tokens[this.position]; } private advance() { this.position += 1; } private atEnd(): boolean { return this.currentToken().kind === TokenKind.EOF; } }