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
JavaScript
"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;