valraiso-expression-parser
Version:
An expression evaluator written in typescript with the goal to support SQL like WHERE clauses.
219 lines (218 loc) • 8.83 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parse = exports.Parser = void 0;
const ast_1 = require("./ast");
const lexer_1 = require("./lexer");
const token_1 = require("./token");
const precedences = {
[token_1.TokenType.And]: 1,
[token_1.TokenType.Or]: 2,
[token_1.TokenType.Not]: 3,
[token_1.TokenType.Eq]: 4,
[token_1.TokenType.Neq]: 4,
[token_1.TokenType.Lt]: 5,
[token_1.TokenType.Lte]: 5,
[token_1.TokenType.Gt]: 5,
[token_1.TokenType.Gte]: 5,
[token_1.TokenType.Between]: 6,
[token_1.TokenType.In]: 7,
[token_1.TokenType.Is]: 7,
[token_1.TokenType.Plus]: 8,
[token_1.TokenType.Minus]: 8,
[token_1.TokenType.Mul]: 9,
[token_1.TokenType.Div]: 9,
[token_1.TokenType.Comma]: 9,
[token_1.TokenType.Lparn]: 10,
};
class Parser {
lexer;
precedenceMap;
currentToken;
peekToken;
get currentPrecedence() {
return this.precedenceMap[this.currentToken.type] ?? 0;
}
get peekPrecedence() {
return this.precedenceMap[this.peekToken.type] ?? 0;
}
prefixParsers;
infixParsers;
constructor(lexer, precedenceMap = precedences) {
this.lexer = lexer;
this.precedenceMap = precedenceMap;
this.currentToken = this.lexer.next();
this.peekToken = this.lexer.next();
this.prefixParsers = {
[token_1.TokenType.Identifier]: this.parseIdentifier.bind(this),
[token_1.TokenType.String]: this.parseString.bind(this),
[token_1.TokenType.Numeric]: this.parseNumber.bind(this),
[token_1.TokenType.Lparn]: this.parseGroupedExpression.bind(this),
[token_1.TokenType.True]: this.parseBoolean.bind(this),
[token_1.TokenType.False]: this.parseBoolean.bind(this),
[token_1.TokenType.Case]: this.parseCaseExpression.bind(this),
};
this.infixParsers = {
[token_1.TokenType.Plus]: this.parseInfixExpression.bind(this),
[token_1.TokenType.Minus]: this.parseInfixExpression.bind(this),
[token_1.TokenType.Mul]: this.parseInfixExpression.bind(this),
[token_1.TokenType.Div]: this.parseInfixExpression.bind(this),
[token_1.TokenType.Eq]: this.parseInfixExpression.bind(this),
[token_1.TokenType.Neq]: this.parseInfixExpression.bind(this),
[token_1.TokenType.Gt]: this.parseInfixExpression.bind(this),
[token_1.TokenType.Lt]: this.parseInfixExpression.bind(this),
[token_1.TokenType.Gte]: this.parseInfixExpression.bind(this),
[token_1.TokenType.Lte]: this.parseInfixExpression.bind(this),
[token_1.TokenType.And]: this.parseInfixExpression.bind(this),
[token_1.TokenType.Or]: this.parseInfixExpression.bind(this),
[token_1.TokenType.Is]: this.parseIsExpression.bind(this),
[token_1.TokenType.In]: this.parseInExpression.bind(this),
[token_1.TokenType.Not]: this.parseNotExpression.bind(this),
[token_1.TokenType.Between]: this.parseBetweenExpression.bind(this),
[token_1.TokenType.Lparn]: this.parseCallExpression.bind(this),
[token_1.TokenType.Comma]: this.parseCommaExpression.bind(this),
};
}
parse() {
return this.parseExpression();
}
parseExpression(precedence = 0) {
const prefixParser = this.prefixParsers[this.currentToken.type];
if (!prefixParser) {
throw new Error(`Unexpected start of expression: ${this.currentToken}`);
}
let leftExpression = prefixParser();
while (precedence < this.peekPrecedence && !this.currentTokenIs(token_1.TokenType.EOF)) {
const infixParser = this.infixParsers[this.peekToken.type];
if (!infixParser) {
return leftExpression;
}
this.nextToken();
leftExpression = infixParser(leftExpression);
}
return leftExpression;
}
nextToken() {
this.currentToken = this.peekToken;
this.peekToken = this.lexer.next();
if (this.currentTokenIs(token_1.TokenType.Illegal)) {
throw new Error(`Invalid input: ${this.currentToken}`);
}
return this.currentToken;
}
currentTokenIs(type) {
return this.currentToken.type === type;
}
peekTokenIs(type) {
return this.peekToken.type === type;
}
expectPeekToken(type) {
if (this.peekTokenIs(type)) {
this.nextToken();
return this.currentToken;
}
throw new Error(`Expected ${type} but got ${this.peekToken}`);
}
parseIdentifier() {
return new ast_1.IdentifierExpression(this.currentToken.literal);
}
parseNumber() {
return new ast_1.ValueExpression(Number(this.currentToken.literal));
}
parseString() {
return new ast_1.ValueExpression(this.currentToken.literal);
}
parseBoolean() {
return new ast_1.ValueExpression(this.currentToken.type === token_1.TokenType.True);
}
parseGroupedExpression() {
this.nextToken();
const expression = this.parseExpression();
if (!this.expectPeekToken(token_1.TokenType.Rparn)) {
throw new Error(`Expected ) but got ${this.peekToken}`);
}
return expression;
}
parseInfixExpression(left) {
const { currentPrecedence } = this;
const op = this.currentToken.type;
this.nextToken();
return new ast_1.BinaryExpression(op, left, this.parseExpression(currentPrecedence));
}
parseNotExpression(left) {
if (!this.peekTokenIs(token_1.TokenType.In)) {
throw new Error(`Expected in keyword but got ${this.peekToken}`);
}
this.nextToken();
return new ast_1.NotExpression(this.parseInExpression(left));
}
parseInExpression(left) {
this.nextToken();
const expression = this.parseExpression(this.currentPrecedence);
return new ast_1.InExpression(left, expression);
}
parseIsExpression(left) {
let op = token_1.TokenType.Eq;
if (this.peekTokenIs(token_1.TokenType.Not)) {
op = token_1.TokenType.Neq;
this.nextToken();
}
this.expectPeekToken(token_1.TokenType.Null);
return new ast_1.BinaryExpression(op, left, new ast_1.ValueExpression(null));
}
parseBetweenExpression(left) {
this.expectPeekToken(token_1.TokenType.Numeric);
const min = this.parseNumber();
this.expectPeekToken(token_1.TokenType.And);
this.expectPeekToken(token_1.TokenType.Numeric);
const max = this.parseNumber();
return new ast_1.BinaryExpression(token_1.TokenType.And, new ast_1.BinaryExpression(token_1.TokenType.Gte, left, min), new ast_1.BinaryExpression(token_1.TokenType.Lte, left, max));
}
parseCallExpression(fn) {
const args = [];
this.nextToken();
while (!this.currentTokenIs(token_1.TokenType.Rparn)) {
args.push(this.parseExpression());
this.nextToken();
if (!this.currentTokenIs(token_1.TokenType.Comma) && !this.currentTokenIs(token_1.TokenType.Rparn)) {
throw new Error(`Expected , or ) got ${this.currentToken}`);
}
}
return new ast_1.FunctionCallExpression(fn.name, args);
}
parseCommaExpression(left) {
this.nextToken();
const right = this.parseExpression();
const values = right.type === 'GroupExpression' ? right.values : [right];
return new ast_1.GroupExpression([left, ...values]);
}
parseCaseExpression() {
const conditions = [];
let last;
if (!this.peekTokenIs(token_1.TokenType.When)) {
throw new Error(`Expected when got ${this.currentToken}`);
}
while (!this.peekTokenIs(token_1.TokenType.End) && !this.peekTokenIs(token_1.TokenType.Else)) {
this.expectPeekToken(token_1.TokenType.When);
this.nextToken();
const when = this.parseExpression();
this.expectPeekToken(token_1.TokenType.Then);
this.nextToken();
const then = this.parseExpression();
conditions.push({ when, then });
}
if (this.peekTokenIs(token_1.TokenType.Else)) {
this.nextToken();
this.nextToken();
last = this.parseExpression();
}
this.expectPeekToken(token_1.TokenType.End);
return new ast_1.CaseExpression(conditions, last);
}
}
exports.Parser = Parser;
function parse(expression) {
const lexer = new lexer_1.Lexer(expression);
const parser = new Parser(lexer);
return parser.parse();
}
exports.parse = parse;