@firehammer/jexl
Version:
Javascript Expression Language: Powerful context-based expression parser and evaluator
190 lines (179 loc) • 6.44 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
/*
* Jexl
* Copyright 2020 Tom Shawver
*/
var poolNames = {
functions: "Jexl Function",
transforms: "Transform"
};
/**
* 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.ArrayLiteral = function (ast) {
return this.evalArray(ast.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.BinaryExpression = function (ast) {
var _this = this;
var grammarOp = this._grammar.elements[ast.operator];
if (grammarOp.evalOnDemand) {
var wrap = function wrap(subAst) {
return {
eval: function _eval() {
return _this.eval(subAst);
}
};
};
return grammarOp.evalOnDemand(wrap(ast.left), wrap(ast.right));
}
return this.Promise.all([this.eval(ast.left), this.eval(ast.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.ConditionalExpression = function (ast) {
var _this2 = this;
return this.eval(ast.test).then(function (res) {
if (res) {
if (ast.consequent) {
return _this2.eval(ast.consequent);
}
return res;
}
return _this2.eval(ast.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.FilterExpression = function (ast) {
var _this3 = this;
return this.eval(ast.subject).then(function (subject) {
if (ast.relative) {
return _this3._filterRelative(subject, ast.expr);
}
return _this3._filterStatic(subject, ast.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.Identifier = function (ast) {
if (!ast.from) {
return ast.relative ? this._relContext[ast.value] : this._context[ast.value];
}
return this.eval(ast.from).then(function (context) {
if (context === undefined || context === null) {
return undefined;
}
if (Array.isArray(context)) {
context = context[0];
}
return context[ast.value];
});
};
/**
* 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.Literal = function (ast) {
return ast.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.ObjectLiteral = function (ast) {
return this.evalMap(ast.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.FunctionCall = function (ast) {
var poolName = poolNames[ast.pool];
if (!poolName) {
throw new Error("Corrupt AST: Pool '".concat(ast.pool, "' not found"));
}
var pool = this._grammar[ast.pool];
var func = pool[ast.name];
if (!func) {
throw new Error("".concat(poolName, " ").concat(ast.name, " is not defined."));
}
var context = {
astNode: ast,
context: this._context,
expression: this._expression
};
return this.evalArray(ast.args || []).then(function (args) {
return func.call.apply(func, [context].concat((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.UnaryExpression = function (ast) {
var _this4 = this;
return this.eval(ast.right).then(function (right) {
return _this4._grammar.elements[ast.operator].eval(right);
});
};