UNPKG

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