UNPKG

valraiso-expression-parser

Version:

An expression evaluator written in typescript with the goal to support SQL like WHERE clauses.

194 lines (193 loc) 6.63 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Lexer = void 0; const token_1 = require("./token"); class Lexer { text; position = 0; readPosition = 0; currentChar = '\0'; constructor(text) { this.text = text; this.readChar(); } next() { let token; this.skipWhiteSpace(); switch (this.currentChar) { case '=': token = this.getToken(token_1.TokenType.Eq); break; case '<': if (this.peekChar() === '>') { this.readChar(); token = this.getToken(token_1.TokenType.Neq); } else if (this.peekChar() === '=') { this.readChar(); token = this.getToken(token_1.TokenType.Lte); } else { token = this.getToken(token_1.TokenType.Lt); } break; case '>': if (this.peekChar() === '=') { this.readChar(); token = this.getToken(token_1.TokenType.Gte); } else { token = this.getToken(token_1.TokenType.Gt); } break; case '"': case "'": { const unquotedValue = this.readString(this.currentChar); token = this.getToken(token_1.TokenType.String, unquotedValue); break; } case '+': token = this.getToken(token_1.TokenType.Plus); break; case '-': token = this.getToken(token_1.TokenType.Minus); break; case '*': token = this.getToken(token_1.TokenType.Mul); break; case '/': token = this.getToken(token_1.TokenType.Div); break; case '(': token = this.getToken(token_1.TokenType.Lparn); break; case ')': token = this.getToken(token_1.TokenType.Rparn); break; case ',': token = this.getToken(token_1.TokenType.Comma); break; case '\0': token = this.getToken(token_1.TokenType.EOF, '\0'); break; default: if (Lexer.isLetter(this.currentChar)) { const ident = this.readIdentifier(); const identType = Lexer.resolveIdentifier(ident); if (identType === token_1.TokenType.Identifier) { token = this.getToken(token_1.TokenType.Identifier, ident); } else { token = this.getToken(identType); } } else if (Lexer.isNumber(this.currentChar) || Lexer.isDot(this.currentChar)) { const num = this.readNumber(); token = this.getToken(token_1.TokenType.Numeric, num); } else { token = this.getToken(token_1.TokenType.Illegal, this.currentChar); } } this.readChar(); return token; } readChar() { if (this.readPosition >= this.text.length) { this.currentChar = '\0'; } else { this.currentChar = this.text[this.readPosition]; } this.position = this.readPosition; this.readPosition++; } peekChar() { if (this.readPosition >= this.text.length) { return '\0'; } return this.text[this.readPosition]; } skipWhiteSpace() { while (Lexer.isWhiteSpace(this.currentChar)) { this.readChar(); } } readString(quote) { const start = this.position + 1; do { this.readChar(); } while (this.currentChar !== quote && this.currentChar !== '\0'); return this.text.substring(start, this.position); } readIdentifier() { const start = this.position; while (Lexer.isLetter(this.peekChar())) { this.readChar(); } return this.text.substring(start, this.position + 1); } readNumber() { const start = this.position; let haveDecimal = Lexer.isDot(this.currentChar); while (Lexer.isNumber(this.peekChar()) || (Lexer.isDot(this.peekChar()) && !haveDecimal)) { if (Lexer.isDot(this.currentChar)) { haveDecimal = true; } this.readChar(); } return this.text.substring(start, this.position + 1); } getToken(type, literal) { const exactLiteral = literal ?? type; const adjustment = type === token_1.TokenType.String ? 0 : 1; const postion = this.position + adjustment - exactLiteral.length; return new token_1.Token(type, exactLiteral, postion); } static isWhiteSpace(charStr) { const charCode = charStr.charCodeAt(0); return (charCode === 0x09 || // '\t' charCode === 0x0a || // '\n' charCode === 0x0d || // '\r' charCode === 0x20 // ' ' ); } static isNumber(charStr) { const charCode = charStr.charCodeAt(0); return charCode >= 0x30 && charCode <= 0x39; // '0'-'9' } static isLetter(charStr) { const charCode = charStr.charCodeAt(0); return ((charCode >= 0x41 && charCode <= 0x5a) || // A-Z (charCode >= 0x61 && charCode <= 0x7a) || // a-z charCode === 0x5f // '_' ); } static isDot(charStr) { return charStr === '.'; } static keywords = { and: token_1.TokenType.And, or: token_1.TokenType.Or, not: token_1.TokenType.Not, true: token_1.TokenType.True, false: token_1.TokenType.False, is: token_1.TokenType.Is, between: token_1.TokenType.Between, null: token_1.TokenType.Null, case: token_1.TokenType.Case, when: token_1.TokenType.When, else: token_1.TokenType.Else, end: token_1.TokenType.End, then: token_1.TokenType.Then, in: token_1.TokenType.In, }; static resolveIdentifier(ident) { const tokenType = Lexer.keywords[ident.toLowerCase()]; if (tokenType) { return tokenType; } return token_1.TokenType.Identifier; } } exports.Lexer = Lexer;