import Error, { isError } from "./error"; import Token, { TokenKind } from "./token"; export default class Lexer { public source: string; public position: number; public line: number; constructor(source: string) { this.source = source; this.position = 0; this.line = 1; } public scan(): Token[] | Error { const tokens = []; while (!this.atEnd()) { const result = this.getToken(); if (isError(result)) { return result; } else if (result) { tokens.push(result); } } tokens.push(new Token(TokenKind.EOF, null, this.line)); return tokens; } private getToken(): Token | Error | null { const source = this.source.slice(this.position); if (source.match(/^select/i)) { this.advance(6); return new Token(TokenKind.SELECT, null, this.line); } else if (source.match(/^where/i)) { this.advance(5); return new Token(TokenKind.WHERE, null, this.line); } else if (source.match(/^from/i)) { this.advance(4); return new Token(TokenKind.FROM, null, this.line); } else if (source.match(/^as/i)) { this.advance(2); return new Token(TokenKind.AS, null, this.line); } else if (source.match(/^and/i)) { this.advance(3); return new Token(TokenKind.AND, null, this.line); } else if (source.match(/^or/i)) { this.advance(2); return new Token(TokenKind.OR, null, this.line); } else if (source.match(/^\+/)) { this.advance(); return new Token(TokenKind.PLUS, null, this.line); } else if (source.match(/^-/)) { this.advance(); return new Token(TokenKind.MINUS, null, this.line); } else if (source.match(/^\*/)) { this.advance(); return new Token(TokenKind.STAR, null, this.line); } else if (source.match(/^\//)) { this.advance(); return new Token(TokenKind.SLASH, null, this.line); } else if (source.match(/^=/)) { this.advance(); return new Token(TokenKind.EQUALS, null, this.line); } else if (source.match(/^,/)) { this.advance(); return new Token(TokenKind.COMMA, null, this.line); } else if (source.match(/^`/)) { this.advance(); return new Token(TokenKind.BACKTICK, null, this.line); } else if (source.match(/^\.([^0-9]|$)/)) { this.advance(); return new Token(TokenKind.DOT, null, this.line); } else if (source.match(/^;/)) { this.advance(); return new Token(TokenKind.SEMICOLON, null, this.line); } else if (source.match(/^\(/)) { this.advance(); return new Token(TokenKind.LPAREN, null, this.line); } else if (source.match(/^\)/)) { this.advance(); return new Token(TokenKind.RPAREN, null, this.line); } else if (source.match(/^[0-9]+(\.[0-9]+)?/)) { const match = source.match(/^[0-9]+(\.[0-9]+)?/); if (match) { const numeric = match[0]; this.advance(numeric.length); return new Token(TokenKind.NUMBER, numeric, this.line); } } else if (source.match(/^\.[0-9]+/)) { const match = source.match(/^\.[0-9]+/); if (match) { const numeric = match[0]; this.advance(numeric.length); return new Token(TokenKind.NUMBER, numeric, this.line); } } else if (source.match(/^[a-zA-Z_][a-zA-Z0-9_]*/)) { const match = source.match(/^[a-zA-Z_][a-zA-Z0-9_]*/); if (match) { const identifier = match[0]; this.advance(identifier.length); return new Token(TokenKind.IDENTIFIER, identifier, this.line); } } else if (source.match(/^\n/)) { this.advance(); this.nextLine(); return null; } else if (source.match(/^\s/)) { this.advance(); return null; } return new Error(`Unrecognized character ${source[0]}`, this.line); } private advance(step: number = 1) { this.position += step; } private nextLine() { this.line += 1; } private atEnd(): boolean { return this.position === this.source.length; } }