mozjexl
Version:
Javascript Expression Language: Powerful context-based expression parser and evaluator
160 lines (150 loc) • 5.04 kB
JavaScript
/*
* Jexl
* Copyright (c) 2015 TechnologyAdvice
*/
/**
* 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.
* @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 self = this;
return Promise.all([
this.eval(ast.left),
this.eval(ast.right)
]).then(function(arr) {
return self._grammar[ast.operator].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 self = this;
return this.eval(ast.test).then(function(res) {
if (res) {
if (ast.consequent)
return self.eval(ast.consequent);
return res;
}
return self.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 self = this;
return this.eval(ast.subject).then(function(subject) {
if (ast.relative)
return self._filterRelative(subject, ast.expr);
return self._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 this.eval(ast.from).then(function(context) {
if (context === undefined)
return undefined;
if (Array.isArray(context))
context = context[0];
return context[ast.value];
});
}
else {
return ast.relative ? this._relContext[ast.value] :
this._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 Transform node by applying a function from the transforms map
* to the subject value.
* @param {{type: 'Transform', name: <string>, subject: {}}} ast An
* expression tree with a Transform as the top node
* @returns {Promise<*>|*} the value of the transformation, or a Promise that
* will resolve with the transformed value.
* @private
*/
exports.Transform = function(ast) {
var transform = this._transforms[ast.name];
if (!transform)
throw new Error("Transform '" + ast.name + "' is not defined.");
return Promise.all([
this.eval(ast.subject),
this.evalArray(ast.args || [])
]).then(function(arr) {
return transform.apply(null, [arr[0]].concat(arr[1]));
});
};
/**
* 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 self = this;
return this.eval(ast.right).then(function(right) {
return self._grammar[ast.operator].eval(right);
});
};