UNPKG

@firehammer/jexl

Version:

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

171 lines (163 loc) 6.72 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); /* * Jexl * Copyright 2020 Tom Shawver */ var handlers = require("./handlers"); /** * The Evaluator takes a Jexl expression tree as generated by the * {@link Parser} and calculates its value within a given context. The * collection of transforms, context, and a relative context to be used as the * root for relative identifiers, are all specific to an Evaluator instance. * When any of these things change, a new instance is required. However, a * single instance can be used to simultaneously evaluate many different * expressions, and does not have to be reinstantiated for each. * @param {{}} grammar A grammar object against which to evaluate the expression * tree * @param {{}} [context] A map of variable keys to their values. This will be * accessed to resolve the value of each non-relative identifier. Any * Promise values will be passed to the expression as their resolved * value. * @param {{}|Array<{}|Array>} [relativeContext] A map or array to be accessed * to resolve the value of a relative identifier. * @param {function} promise A constructor for the Promise class to be used; * probably either Promise or PromiseSync. */ var Evaluator = /*#__PURE__*/function () { function Evaluator(grammar, context, expression, relativeContext) { var promise = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : Promise; (0, _classCallCheck2.default)(this, Evaluator); this._grammar = grammar; this._context = context || {}; this._expression = expression; this._relContext = relativeContext || this._context; this.Promise = promise; } /** * Evaluates an expression tree within the configured context. * @param {{}} ast An expression tree object * @returns {Promise<*>} resolves with the resulting value of the expression. */ (0, _createClass2.default)(Evaluator, [{ key: "eval", value: function _eval(ast) { var _this = this; return this.Promise.resolve().then(function () { return handlers[ast.type].call(_this, ast); }); } /** * Simultaneously evaluates each expression within an array, and delivers the * response as an array with the resulting values at the same indexes as their * originating expressions. * @param {Array<string>} arr An array of expression strings to be evaluated * @returns {Promise<Array<{}>>} resolves with the result array */ }, { key: "evalArray", value: function evalArray(arr) { var _this2 = this; return this.Promise.all(arr.map(function (elem) { return _this2.eval(elem); })); } /** * Simultaneously evaluates each expression within a map, and delivers the * response as a map with the same keys, but with the evaluated result for each * as their value. * @param {{}} map A map of expression names to expression trees to be * evaluated * @returns {Promise<{}>} resolves with the result map. */ }, { key: "evalMap", value: function evalMap(map) { var _this3 = this; var keys = Object.keys(map); var result = {}; var asts = keys.map(function (key) { return _this3.eval(map[key]); }); return this.Promise.all(asts).then(function (vals) { vals.forEach(function (val, idx) { result[keys[idx]] = val; }); return result; }); } /** * Applies a filter expression with relative identifier elements to a subject. * The intent is for the subject to be an array of subjects that will be * individually used as the relative context against the provided expression * tree. Only the elements whose expressions result in a truthy value will be * included in the resulting array. * * If the subject is not an array of values, it will be converted to a single- * element array before running the filter. * @param {*} subject The value to be filtered usually an array. If this value is * not an array, it will be converted to an array with this value as the * only element. * @param {{}} expr The expression tree to run against each subject. If the * tree evaluates to a truthy result, then the value will be included in * the returned array otherwise, it will be eliminated. * @returns {Promise<Array>} resolves with an array of values that passed the * expression filter. * @private */ }, { key: "_filterRelative", value: function _filterRelative(subject, expr) { var _this4 = this; var promises = []; if (!Array.isArray(subject)) { subject = subject === undefined ? [] : [subject]; } subject.forEach(function (elem) { var evalInst = new Evaluator(_this4._grammar, _this4._context, _this4._expression, elem, _this4.Promise); promises.push(evalInst.eval(expr)); }); return this.Promise.all(promises).then(function (values) { var results = []; values.forEach(function (value, idx) { if (value) { results.push(subject[idx]); } }); return results; }); } /** * Applies a static filter expression to a subject value. If the filter * expression evaluates to boolean true, the subject is returned if false, * undefined. * * For any other resulting value of the expression, this function will attempt * to respond with the property at that name or index of the subject. * @param {*} subject The value to be filtered. Usually an Array (for which * the expression would generally resolve to a numeric index) or an * Object (for which the expression would generally resolve to a string * indicating a property name) * @param {{}} expr The expression tree to run against the subject * @returns {Promise<*>} resolves with the value of the drill-down. * @private */ }, { key: "_filterStatic", value: function _filterStatic(subject, expr) { return this.eval(expr).then(function (res) { if (subject === undefined || subject === null) { return undefined; } if (typeof res === "boolean") { return res ? subject : undefined; } return subject[res]; }); } }]); return Evaluator; }(); module.exports = Evaluator;