UNPKG

@digifi/jexl

Version:

Javascript Expression Language: Powerful context-based expression parser and evaluator

203 lines (192 loc) 8.23 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); /* * Jexl * Copyright 2020 Tom Shawver */ var MissedVariableError = require('../errors/MissedVariableError'); var JexlError = require('../errors/JexlError'); var ReadFromEmptyObjectError = require('../errors/ReadFromEmptyObjectError'); var TokenType = require('../constants/TokenType'); /** * Evaluates an ArrayLiteral by returning its value, with each element * independently run through the evaluator. * @param {{type: 'ObjectLiteral', value: <{}>}} ast An expression tree with an * ObjectLiteral as the top node * @returns {Promise.<[]>} resolves to a map contained evaluated values. * @private */ exports[TokenType.ArrayLiteral] = function (ast) { return this.evalArray(ast[this.nodeTransformer.transform('value')]); }; /** * Evaluates a BinaryExpression node by running the Grammar's evaluator for * the given operator. Note that binary expressions support two types of * evaluators: `eval` is called with the left and right operands pre-evaluated. * `evalOnDemand`, if it exists, will be called with the left and right operands * each individually wrapped in an object with an "eval" function that returns * a promise with the resulting value. This allows the binary expression to * evaluate the operands conditionally. * @param {{type: 'BinaryExpression', operator: <string>, left: {}, * right: {}}} ast An expression tree with a BinaryExpression as the top * node * @returns {Promise<*>} resolves with the value of the BinaryExpression. * @private */ exports[TokenType.BinaryExpression] = function (ast) { var _this = this; var operator = ast[this.nodeTransformer.transform('operator')]; var grammarOp = this._grammar.elements[operator]; if (grammarOp.evalOnDemand) { var wrap = function wrap(subAst) { return { eval: function _eval() { return _this.eval(subAst); } }; }; return grammarOp.evalOnDemand(wrap(ast[this.nodeTransformer.transform('left')]), wrap(ast[this.nodeTransformer.transform('right')])); } return this.Promise.all([this.eval(ast[this.nodeTransformer.transform('left')]), this.eval(ast[this.nodeTransformer.transform('right')])]).then(function (arr) { return grammarOp.eval(arr[0], arr[1]); }); }; /** * Evaluates a ConditionalExpression node by first evaluating its test branch, * and resolving with the consequent branch if the test is truthy, or the * alternate branch if it is not. If there is no consequent branch, the test * result will be used instead. * @param {{type: 'ConditionalExpression', test: {}, consequent: {}, * alternate: {}}} ast An expression tree with a ConditionalExpression as * the top node * @private */ exports[TokenType.ConditionalExpression] = function (ast) { var _this2 = this; return this.eval(ast[this.nodeTransformer.transform('test')]).then(function (res) { if (res) { if (ast[_this2.nodeTransformer.transform('consequent')]) { return _this2.eval(ast[_this2.nodeTransformer.transform('consequent')]); } return res; } return _this2.eval(ast[_this2.nodeTransformer.transform('alternate')]); }); }; /** * Evaluates a FilterExpression by applying it to the subject value. * @param {{type: 'FilterExpression', relative: <boolean>, expr: {}, * subject: {}}} ast An expression tree with a FilterExpression as the top * node * @returns {Promise<*>} resolves with the value of the FilterExpression. * @private */ exports[TokenType.FilterExpression] = function (ast) { var _this3 = this; return this.eval(ast[this.nodeTransformer.transform('subject')]).then(function (subject) { if (ast[_this3.nodeTransformer.transform('relative')]) { return _this3._filterRelative(subject, ast[_this3.nodeTransformer.transform('expr')]); } return _this3._filterStatic(subject, ast[_this3.nodeTransformer.transform('expr')]); }); }; /** * Evaluates an Identifier by either stemming from the evaluated 'from' * expression tree or accessing the context provided when this Evaluator was * constructed. * @param {{type: 'Identifier', value: <string>, [from]: {}}} ast An expression * tree with an Identifier as the top node * @returns {Promise<*>|*} either the identifier's value, or a Promise that * will resolve with the identifier's value. * @private */ exports[TokenType.Identifier] = function (ast) { var _this4 = this; var valueNodeName = this.nodeTransformer.transform('value'); var relativeNodeName = this.nodeTransformer.transform('relative'); var fromNodeName = this.nodeTransformer.transform('from'); if (!ast[fromNodeName]) { if (ast[valueNodeName] === 'null' && !ast[relativeNodeName]) { return null; } if (ast[valueNodeName] === 'undefined' && !ast[relativeNodeName]) { return undefined; } if (!ast[relativeNodeName] && this.shouldThrowErrorIfVariableIsNotFound() && (!this._context.hasOwnProperty(ast[valueNodeName]) || this._context[ast[valueNodeName]] === undefined)) { throw new MissedVariableError(ast[valueNodeName]); } var value = ast[relativeNodeName] ? this._relContext[ast[valueNodeName]] : this._context[ast[valueNodeName]]; return value === undefined ? null : value; } return this.eval(ast[fromNodeName]).then(function (context) { if (Array.isArray(context)) { context = context[0]; } if (context === undefined || context === null) { if (_this4.shouldThrowErrorIfReadPropertyOfNullOrUndefined()) { throw new ReadFromEmptyObjectError(ast[valueNodeName], context); } return _this4.emptyContextValue; } if (!context.hasOwnProperty(ast[valueNodeName])) { return undefined; } return context[ast[valueNodeName]]; }); }; /** * Evaluates a Literal by returning its value property. * @param {{type: 'Literal', value: <string|number|boolean>}} ast An expression * tree with a Literal as its only node * @returns {string|number|boolean} The value of the Literal node * @private */ exports[TokenType.Literal] = function (ast) { return ast[this.nodeTransformer.transform('value')]; }; /** * Evaluates an ObjectLiteral by returning its value, with each key * independently run through the evaluator. * @param {{type: 'ObjectLiteral', value: <{}>}} ast An expression tree with an * ObjectLiteral as the top node * @returns {Promise<{}>} resolves to a map contained evaluated values. * @private */ exports[TokenType.ObjectLiteral] = function (ast) { return this.evalMap(ast[this.nodeTransformer.transform('value')]); }; /** * Evaluates a FunctionCall node by applying the supplied arguments to a * function defined in one of the grammar's function pools. * @param {{type: 'FunctionCall', name: <string>}} ast An * expression tree with a FunctionCall as the top node * @returns {Promise<*>|*} the value of the function call, or a Promise that * will resolve with the resulting value. * @private */ exports[TokenType.FunctionCall] = function (ast) { var functionName = ast[this.nodeTransformer.transform('name')]; var func = this._grammar.functions[functionName]; if (!func) { throw new JexlError("".concat(functionName, " is not defined.")); } return this.evalArray(ast[this.nodeTransformer.transform('args')] || []).then(function (args) { return func.apply(void 0, (0, _toConsumableArray2.default)(args)); }); }; /** * Evaluates a Unary expression by passing the right side through the * operator's eval function. * @param {{type: 'UnaryExpression', operator: <string>, right: {}}} ast An * expression tree with a UnaryExpression as the top node * @returns {Promise<*>} resolves with the value of the UnaryExpression. * @constructor */ exports[TokenType.UnaryExpression] = function (ast) { var _this5 = this; return this.eval(ast[this.nodeTransformer.transform('right')]).then(function (right) { var operator = ast[_this5.nodeTransformer.transform('operator')]; return _this5._grammar.elements[operator].eval(right); }); };