@digifi/jexl
Version:
Javascript Expression Language: Powerful context-based expression parser and evaluator
242 lines (183 loc) • 9.91 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
/*
* Jexl
* Copyright 2020 Tom Shawver
*/
var TokenType = require('../constants/TokenType');
/**
* Handles a subexpression that's used to define a transform argument's value.
* @param {{type: <string>}} ast The subexpression tree
*/
exports.argVal = function (ast) {
if (ast) {
this._cursor[this.nodeTransformer.transform('args')].push(ast);
}
};
/**
* Handles new array literals by adding them as a new node in the AST,
* initialized with an empty array.
*/
exports.arrayStart = function () {
var _this$_placeAtCursor;
this._placeAtCursor((_this$_placeAtCursor = {}, (0, _defineProperty2.default)(_this$_placeAtCursor, this.nodeTransformer.transform('type'), TokenType.ArrayLiteral), (0, _defineProperty2.default)(_this$_placeAtCursor, this.nodeTransformer.transform('value'), []), _this$_placeAtCursor));
};
/**
* Handles a subexpression representing an element of an array literal.
* @param {{type: <string>}} ast The subexpression tree
*/
exports.arrayVal = function (ast) {
if (ast) {
this._cursor[this.nodeTransformer.transform('value')].push(ast);
}
};
/**
* Handles tokens of type 'binaryOp', indicating an operation that has two
* inputs: a left side and a right side.
* @param {{type: <string>}} token A token object
*/
exports.binaryOp = function (token) {
var _node;
var precedence = this._grammar.elements[token.value].precedence || 0;
var parent = this._cursor._parent;
while (parent && parent[this.nodeTransformer.transform('operator')] && this._grammar.elements[parent[this.nodeTransformer.transform('operator')]].precedence >= precedence) {
this._cursor = parent;
parent = parent._parent;
}
var node = (_node = {}, (0, _defineProperty2.default)(_node, this.nodeTransformer.transform('type'), TokenType.BinaryExpression), (0, _defineProperty2.default)(_node, this.nodeTransformer.transform('operator'), token.value), (0, _defineProperty2.default)(_node, this.nodeTransformer.transform('left'), this._cursor), _node);
this._setParent(this._cursor, node);
this._cursor = parent;
this._placeAtCursor(node);
};
/**
* Handles successive nodes in an identifier chain. More specifically, it
* sets values that determine how the following identifier gets placed in the
* AST.
*/
exports.dot = function () {
this._nextIdentEncapsulate = this._cursor && this._cursor[this.nodeTransformer.transform('type')] !== TokenType.UnaryExpression && (this._cursor[this.nodeTransformer.transform('type')] !== TokenType.BinaryExpression || this._cursor[this.nodeTransformer.transform('type')] === TokenType.BinaryExpression && this._cursor[this.nodeTransformer.transform('right')]);
this._nextIdentRelative = !this._cursor || this._cursor && !this._nextIdentEncapsulate;
if (this._nextIdentRelative) {
this._relative = true;
}
};
/**
* Handles a subexpression used for filtering an array returned by an
* identifier chain.
* @param {{type: <string>}} ast The subexpression tree
*/
exports.filter = function (ast) {
var _this$_placeBeforeCur;
this._placeBeforeCursor((_this$_placeBeforeCur = {}, (0, _defineProperty2.default)(_this$_placeBeforeCur, this.nodeTransformer.transform('type'), TokenType.FilterExpression), (0, _defineProperty2.default)(_this$_placeBeforeCur, this.nodeTransformer.transform('expr'), ast), (0, _defineProperty2.default)(_this$_placeBeforeCur, this.nodeTransformer.transform('relative'), this._subParser.isRelative()), (0, _defineProperty2.default)(_this$_placeBeforeCur, this.nodeTransformer.transform('subject'), this._cursor), _this$_placeBeforeCur));
};
/**
* Handles identifier tokens when used to indicate the name of a function to
* be called.
* @param {{type: <string>}} token A token object
*/
exports.functionCall = function () {
var _this$_placeBeforeCur2;
if (!this._cursor[this.nodeTransformer.transform('value')]) {
throw new Error('Function call should have name.');
}
this._placeBeforeCursor((_this$_placeBeforeCur2 = {}, (0, _defineProperty2.default)(_this$_placeBeforeCur2, this.nodeTransformer.transform('type'), TokenType.FunctionCall), (0, _defineProperty2.default)(_this$_placeBeforeCur2, this.nodeTransformer.transform('name'), this._cursor[this.nodeTransformer.transform('value')]), (0, _defineProperty2.default)(_this$_placeBeforeCur2, this.nodeTransformer.transform('args'), []), (0, _defineProperty2.default)(_this$_placeBeforeCur2, "start", this._cursor.start), (0, _defineProperty2.default)(_this$_placeBeforeCur2, "end", this._cursor.end), _this$_placeBeforeCur2));
};
/**
* Handles identifier tokens by adding them as a new node in the AST.
* @param {{type: <string>}} token A token object
*/
exports.identifier = function (token) {
var _objectSpread2;
var node = _objectSpread(_objectSpread((_objectSpread2 = {}, (0, _defineProperty2.default)(_objectSpread2, this.nodeTransformer.transform('type'), TokenType.Identifier), (0, _defineProperty2.default)(_objectSpread2, this.nodeTransformer.transform('value'), token.value), _objectSpread2), this._minify ? {} : {
start: token.start
}), this._minify ? {} : {
end: token.end
});
if (this._nextIdentEncapsulate) {
node[this.nodeTransformer.transform('from')] = this._cursor;
this._placeBeforeCursor(node);
this._nextIdentEncapsulate = false;
} else {
if (this._nextIdentRelative) {
node[this.nodeTransformer.transform('relative')] = true;
this._nextIdentRelative = false;
}
this._placeAtCursor(node);
}
};
/**
* Handles literal values, such as strings, booleans, and numerics, by adding
* them as a new node in the AST.
* @param {{type: <string>}} token A token object
*/
exports.literal = function (token) {
var _this$_placeAtCursor2;
this._placeAtCursor((_this$_placeAtCursor2 = {}, (0, _defineProperty2.default)(_this$_placeAtCursor2, this.nodeTransformer.transform('type'), TokenType.Literal), (0, _defineProperty2.default)(_this$_placeAtCursor2, this.nodeTransformer.transform('value'), token.value), _this$_placeAtCursor2));
};
/**
* Queues a new object literal key to be written once a value is collected.
* @param {{type: <string>}} token A token object
*/
exports.objKey = function (token) {
this._curObjKey = token.value;
};
/**
* Handles new object literals by adding them as a new node in the AST,
* initialized with an empty object.
*/
exports.objStart = function () {
var _this$_placeAtCursor3;
this._placeAtCursor((_this$_placeAtCursor3 = {}, (0, _defineProperty2.default)(_this$_placeAtCursor3, this.nodeTransformer.transform('type'), TokenType.ObjectLiteral), (0, _defineProperty2.default)(_this$_placeAtCursor3, this.nodeTransformer.transform('value'), {}), _this$_placeAtCursor3));
};
/**
* Handles an object value by adding its AST to the queued key on the object
* literal node currently at the cursor.
* @param {{type: <string>}} ast The subexpression tree
*/
exports.objVal = function (ast) {
this._cursor[this.nodeTransformer.transform('value')][this._curObjKey] = ast;
};
/**
* Handles traditional subexpressions, delineated with the groupStart and
* groupEnd elements.
* @param {{type: <string>}} ast The subexpression tree
*/
exports.subExpression = function (ast) {
this._placeAtCursor(ast);
};
/**
* Handles a completed alternate subexpression of a ternary operator.
* @param {{type: <string>}} ast The subexpression tree
*/
exports.ternaryEnd = function (ast) {
this._cursor[this.nodeTransformer.transform('alternate')] = ast;
};
/**
* Handles a completed consequent subexpression of a ternary operator.
* @param {{type: <string>}} ast The subexpression tree
*/
exports.ternaryMid = function (ast) {
this._cursor[this.nodeTransformer.transform('consequent')] = ast;
};
/**
* Handles the start of a new ternary expression by encapsulating the entire
* AST in a ConditionalExpression node, and using the existing tree as the
* test element.
*/
exports.ternaryStart = function () {
var _this$_tree;
this._tree = (_this$_tree = {}, (0, _defineProperty2.default)(_this$_tree, this.nodeTransformer.transform('type'), TokenType.ConditionalExpression), (0, _defineProperty2.default)(_this$_tree, this.nodeTransformer.transform('test'), this._tree), _this$_tree);
this._cursor = this._tree;
};
/**
* Handles token of type 'unaryOp', indicating that the operation has only
* one input: a right side.
* @param {{type: <string>}} token A token object
*/
exports.unaryOp = function (token) {
var _this$_placeAtCursor4;
this._placeAtCursor((_this$_placeAtCursor4 = {}, (0, _defineProperty2.default)(_this$_placeAtCursor4, this.nodeTransformer.transform('type'), TokenType.UnaryExpression), (0, _defineProperty2.default)(_this$_placeAtCursor4, this.nodeTransformer.transform('operator'), token.value), _this$_placeAtCursor4));
};