@hemia/workflow-core
Version:
Core del sistema de orquestación de workflows de Hemia. Permite definir, ejecutar y probar flujos personalizados.
1,624 lines (1,406 loc) • 81.4 kB
JavaScript
function getVar(key, context, fallback) {
return context.getVariable(key) ?? fallback;
}
function setVar(key, value, context) {
context.setVariable(key, value);
}
function logInfo(context, message, data) {
context.log(`[INFO] ${message}`, data);
}
function logWarn(context, message, data) {
context.log(`[WARN] ${message}`, data);
}
function logError(context, message, data) {
context.log(`[ERROR] ${message}`, data);
}
function getOutput(stepId, context) {
return context.getVariable(`output_${stepId}`);
}
function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
var Jexl$1 = {exports: {}};
var interopRequireDefault = {exports: {}};
(function (module) {
function _interopRequireDefault(e) {
return e && e.__esModule ? e : {
"default": e
};
}
module.exports = _interopRequireDefault, module.exports.__esModule = true, module.exports["default"] = module.exports;
} (interopRequireDefault));
var interopRequireDefaultExports = interopRequireDefault.exports;
var defineProperty = {exports: {}};
var toPropertyKey = {exports: {}};
var _typeof = {exports: {}};
var hasRequired_typeof;
function require_typeof () {
if (hasRequired_typeof) return _typeof.exports;
hasRequired_typeof = 1;
(function (module) {
function _typeof(o) {
"@babel/helpers - typeof";
return module.exports = _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, module.exports.__esModule = true, module.exports["default"] = module.exports, _typeof(o);
}
module.exports = _typeof, module.exports.__esModule = true, module.exports["default"] = module.exports;
} (_typeof));
return _typeof.exports;
}
var toPrimitive = {exports: {}};
var hasRequiredToPrimitive;
function requireToPrimitive () {
if (hasRequiredToPrimitive) return toPrimitive.exports;
hasRequiredToPrimitive = 1;
(function (module) {
var _typeof = require_typeof()["default"];
function toPrimitive(t, r) {
if ("object" != _typeof(t) || !t) return t;
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r || "default");
if ("object" != _typeof(i)) return i;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return ("string" === r ? String : Number)(t);
}
module.exports = toPrimitive, module.exports.__esModule = true, module.exports["default"] = module.exports;
} (toPrimitive));
return toPrimitive.exports;
}
var hasRequiredToPropertyKey;
function requireToPropertyKey () {
if (hasRequiredToPropertyKey) return toPropertyKey.exports;
hasRequiredToPropertyKey = 1;
(function (module) {
var _typeof = require_typeof()["default"];
var toPrimitive = requireToPrimitive();
function toPropertyKey(t) {
var i = toPrimitive(t, "string");
return "symbol" == _typeof(i) ? i : i + "";
}
module.exports = toPropertyKey, module.exports.__esModule = true, module.exports["default"] = module.exports;
} (toPropertyKey));
return toPropertyKey.exports;
}
var hasRequiredDefineProperty;
function requireDefineProperty () {
if (hasRequiredDefineProperty) return defineProperty.exports;
hasRequiredDefineProperty = 1;
(function (module) {
var toPropertyKey = requireToPropertyKey();
function _defineProperty(e, r, t) {
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
value: t,
enumerable: true,
configurable: true,
writable: true
}) : e[r] = t, e;
}
module.exports = _defineProperty, module.exports.__esModule = true, module.exports["default"] = module.exports;
} (defineProperty));
return defineProperty.exports;
}
var classCallCheck = {exports: {}};
var hasRequiredClassCallCheck;
function requireClassCallCheck () {
if (hasRequiredClassCallCheck) return classCallCheck.exports;
hasRequiredClassCallCheck = 1;
(function (module) {
function _classCallCheck(a, n) {
if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function");
}
module.exports = _classCallCheck, module.exports.__esModule = true, module.exports["default"] = module.exports;
} (classCallCheck));
return classCallCheck.exports;
}
var createClass = {exports: {}};
var hasRequiredCreateClass;
function requireCreateClass () {
if (hasRequiredCreateClass) return createClass.exports;
hasRequiredCreateClass = 1;
(function (module) {
var toPropertyKey = requireToPropertyKey();
function _defineProperties(e, r) {
for (var t = 0; t < r.length; t++) {
var o = r[t];
o.enumerable = o.enumerable || false, o.configurable = true, "value" in o && (o.writable = true), Object.defineProperty(e, toPropertyKey(o.key), o);
}
}
function _createClass(e, r, t) {
return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", {
writable: false
}), e;
}
module.exports = _createClass, module.exports.__esModule = true, module.exports["default"] = module.exports;
} (createClass));
return createClass.exports;
}
var handlers$1 = {};
var toConsumableArray = {exports: {}};
var arrayWithoutHoles = {exports: {}};
var arrayLikeToArray = {exports: {}};
var hasRequiredArrayLikeToArray;
function requireArrayLikeToArray () {
if (hasRequiredArrayLikeToArray) return arrayLikeToArray.exports;
hasRequiredArrayLikeToArray = 1;
(function (module) {
function _arrayLikeToArray(r, a) {
(null == a || a > r.length) && (a = r.length);
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
return n;
}
module.exports = _arrayLikeToArray, module.exports.__esModule = true, module.exports["default"] = module.exports;
} (arrayLikeToArray));
return arrayLikeToArray.exports;
}
var hasRequiredArrayWithoutHoles;
function requireArrayWithoutHoles () {
if (hasRequiredArrayWithoutHoles) return arrayWithoutHoles.exports;
hasRequiredArrayWithoutHoles = 1;
(function (module) {
var arrayLikeToArray = requireArrayLikeToArray();
function _arrayWithoutHoles(r) {
if (Array.isArray(r)) return arrayLikeToArray(r);
}
module.exports = _arrayWithoutHoles, module.exports.__esModule = true, module.exports["default"] = module.exports;
} (arrayWithoutHoles));
return arrayWithoutHoles.exports;
}
var iterableToArray = {exports: {}};
var hasRequiredIterableToArray;
function requireIterableToArray () {
if (hasRequiredIterableToArray) return iterableToArray.exports;
hasRequiredIterableToArray = 1;
(function (module) {
function _iterableToArray(r) {
if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r);
}
module.exports = _iterableToArray, module.exports.__esModule = true, module.exports["default"] = module.exports;
} (iterableToArray));
return iterableToArray.exports;
}
var unsupportedIterableToArray = {exports: {}};
var hasRequiredUnsupportedIterableToArray;
function requireUnsupportedIterableToArray () {
if (hasRequiredUnsupportedIterableToArray) return unsupportedIterableToArray.exports;
hasRequiredUnsupportedIterableToArray = 1;
(function (module) {
var arrayLikeToArray = requireArrayLikeToArray();
function _unsupportedIterableToArray(r, a) {
if (r) {
if ("string" == typeof r) return arrayLikeToArray(r, a);
var t = {}.toString.call(r).slice(8, -1);
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? arrayLikeToArray(r, a) : void 0;
}
}
module.exports = _unsupportedIterableToArray, module.exports.__esModule = true, module.exports["default"] = module.exports;
} (unsupportedIterableToArray));
return unsupportedIterableToArray.exports;
}
var nonIterableSpread = {exports: {}};
var hasRequiredNonIterableSpread;
function requireNonIterableSpread () {
if (hasRequiredNonIterableSpread) return nonIterableSpread.exports;
hasRequiredNonIterableSpread = 1;
(function (module) {
function _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
module.exports = _nonIterableSpread, module.exports.__esModule = true, module.exports["default"] = module.exports;
} (nonIterableSpread));
return nonIterableSpread.exports;
}
var hasRequiredToConsumableArray;
function requireToConsumableArray () {
if (hasRequiredToConsumableArray) return toConsumableArray.exports;
hasRequiredToConsumableArray = 1;
(function (module) {
var arrayWithoutHoles = requireArrayWithoutHoles();
var iterableToArray = requireIterableToArray();
var unsupportedIterableToArray = requireUnsupportedIterableToArray();
var nonIterableSpread = requireNonIterableSpread();
function _toConsumableArray(r) {
return arrayWithoutHoles(r) || iterableToArray(r) || unsupportedIterableToArray(r) || nonIterableSpread();
}
module.exports = _toConsumableArray, module.exports.__esModule = true, module.exports["default"] = module.exports;
} (toConsumableArray));
return toConsumableArray.exports;
}
var hasRequiredHandlers$1;
function requireHandlers$1 () {
if (hasRequiredHandlers$1) return handlers$1;
hasRequiredHandlers$1 = 1;
var _interopRequireDefault = interopRequireDefaultExports;
var _toConsumableArray2 = _interopRequireDefault(requireToConsumableArray());
/*
* 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
*/
handlers$1.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
*/
handlers$1.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
*/
handlers$1.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
*/
handlers$1.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
*/
handlers$1.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
*/
handlers$1.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
*/
handlers$1.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
*/
handlers$1.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."));
}
return this.evalArray(ast.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
*/
handlers$1.UnaryExpression = function (ast) {
var _this4 = this;
return this.eval(ast.right).then(function (right) {
return _this4._grammar.elements[ast.operator].eval(right);
});
};
return handlers$1;
}
var Evaluator_1;
var hasRequiredEvaluator;
function requireEvaluator () {
if (hasRequiredEvaluator) return Evaluator_1;
hasRequiredEvaluator = 1;
var _interopRequireDefault = interopRequireDefaultExports;
var _classCallCheck2 = _interopRequireDefault(requireClassCallCheck());
var _createClass2 = _interopRequireDefault(requireCreateClass());
/*
* Jexl
* Copyright 2020 Tom Shawver
*/
var handlers = requireHandlers$1();
/**
* 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, relativeContext) {
var promise = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : Promise;
(0, _classCallCheck2.default)(this, Evaluator);
this._grammar = grammar;
this._context = context || {};
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, 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 (typeof res === 'boolean') {
return res ? subject : undefined;
}
return subject[res];
});
}
}]);
return Evaluator;
}();
Evaluator_1 = Evaluator;
return Evaluator_1;
}
var Lexer_1;
var hasRequiredLexer;
function requireLexer () {
if (hasRequiredLexer) return Lexer_1;
hasRequiredLexer = 1;
var _interopRequireDefault = interopRequireDefaultExports;
var _classCallCheck2 = _interopRequireDefault(requireClassCallCheck());
var _createClass2 = _interopRequireDefault(requireCreateClass());
/*
* Jexl
* Copyright 2020 Tom Shawver
*/
var numericRegex = /^-?(?:(?:[0-9]*\.[0-9]+)|[0-9]+)$/;
var identRegex = /^[a-zA-Zа-яА-Я_\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF$][a-zA-Zа-яА-Я0-9_\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF$]*$/;
var escEscRegex = /\\\\/;
var whitespaceRegex = /^\s*$/;
var preOpRegexElems = [// Strings
"'(?:(?:\\\\')|[^'])*'", '"(?:(?:\\\\")|[^"])*"', // Whitespace
'\\s+', // Booleans
'\\btrue\\b', '\\bfalse\\b'];
var postOpRegexElems = [// Identifiers
"[a-zA-Z\u0430-\u044F\u0410-\u042F_\xC0-\xD6\xD8-\xF6\xF8-\xFF\\$][a-zA-Z0-9\u0430-\u044F\u0410-\u042F_\xC0-\xD6\xD8-\xF6\xF8-\xFF\\$]*", // Numerics (without negative symbol)
'(?:(?:[0-9]*\\.[0-9]+)|[0-9]+)'];
var minusNegatesAfter = ['binaryOp', 'unaryOp', 'openParen', 'openBracket', 'question', 'colon'];
/**
* Lexer is a collection of stateless, statically-accessed functions for the
* lexical parsing of a Jexl string. Its responsibility is to identify the
* "parts of speech" of a Jexl expression, and tokenize and label each, but
* to do only the most minimal syntax checking; the only errors the Lexer
* should be concerned with are if it's unable to identify the utility of
* any of its tokens. Errors stemming from these tokens not being in a
* sensible configuration should be left for the Parser to handle.
* @type {{}}
*/
var Lexer = /*#__PURE__*/function () {
function Lexer(grammar) {
(0, _classCallCheck2.default)(this, Lexer);
this._grammar = grammar;
}
/**
* Splits a Jexl expression string into an array of expression elements.
* @param {string} str A Jexl expression string
* @returns {Array<string>} An array of substrings defining the functional
* elements of the expression.
*/
(0, _createClass2.default)(Lexer, [{
key: "getElements",
value: function getElements(str) {
var regex = this._getSplitRegex();
return str.split(regex).filter(function (elem) {
// Remove empty strings
return elem;
});
}
/**
* Converts an array of expression elements into an array of tokens. Note that
* the resulting array may not equal the element array in length, as any
* elements that consist only of whitespace get appended to the previous
* token's "raw" property. For the structure of a token object, please see
* {@link Lexer#tokenize}.
* @param {Array<string>} elements An array of Jexl expression elements to be
* converted to tokens
* @returns {Array<{type, value, raw}>} an array of token objects.
*/
}, {
key: "getTokens",
value: function getTokens(elements) {
var tokens = [];
var negate = false;
for (var i = 0; i < elements.length; i++) {
if (this._isWhitespace(elements[i])) {
if (tokens.length) {
tokens[tokens.length - 1].raw += elements[i];
}
} else if (elements[i] === '-' && this._isNegative(tokens)) {
negate = true;
} else {
if (negate) {
elements[i] = '-' + elements[i];
negate = false;
}
tokens.push(this._createToken(elements[i]));
}
} // Catch a - at the end of the string. Let the parser handle that issue.
if (negate) {
tokens.push(this._createToken('-'));
}
return tokens;
}
/**
* Converts a Jexl string into an array of tokens. Each token is an object
* in the following format:
*
* {
* type: <string>,
* [name]: <string>,
* value: <boolean|number|string>,
* raw: <string>
* }
*
* Type is one of the following:
*
* literal, identifier, binaryOp, unaryOp
*
* OR, if the token is a control character its type is the name of the element
* defined in the Grammar.
*
* Name appears only if the token is a control string found in
* {@link grammar#elements}, and is set to the name of the element.
*
* Value is the value of the token in the correct type (boolean or numeric as
* appropriate). Raw is the string representation of this value taken directly
* from the expression string, including any trailing spaces.
* @param {string} str The Jexl string to be tokenized
* @returns {Array<{type, value, raw}>} an array of token objects.
* @throws {Error} if the provided string contains an invalid token.
*/
}, {
key: "tokenize",
value: function tokenize(str) {
var elements = this.getElements(str);
return this.getTokens(elements);
}
/**
* Creates a new token object from an element of a Jexl string. See
* {@link Lexer#tokenize} for a description of the token object.
* @param {string} element The element from which a token should be made
* @returns {{value: number|boolean|string, [name]: string, type: string,
* raw: string}} a token object describing the provided element.
* @throws {Error} if the provided string is not a valid expression element.
* @private
*/
}, {
key: "_createToken",
value: function _createToken(element) {
var token = {
type: 'literal',
value: element,
raw: element
};
if (element[0] === '"' || element[0] === "'") {
token.value = this._unquote(element);
} else if (element.match(numericRegex)) {
token.value = parseFloat(element);
} else if (element === 'true' || element === 'false') {
token.value = element === 'true';
} else if (this._grammar.elements[element]) {
token.type = this._grammar.elements[element].type;
} else if (element.match(identRegex)) {
token.type = 'identifier';
} else {
throw new Error("Invalid expression token: ".concat(element));
}
return token;
}
/**
* Escapes a string so that it can be treated as a string literal within a
* regular expression.
* @param {string} str The string to be escaped
* @returns {string} the RegExp-escaped string.
* @see https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions
* @private
*/
}, {
key: "_escapeRegExp",
value: function _escapeRegExp(str) {
str = str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
if (str.match(identRegex)) {
str = '\\b' + str + '\\b';
}
return str;
}
/**
* Gets a RegEx object appropriate for splitting a Jexl string into its core
* elements.
* @returns {RegExp} An element-splitting RegExp object
* @private
*/
}, {
key: "_getSplitRegex",
value: function _getSplitRegex() {
var _this = this;
if (!this._splitRegex) {
// Sort by most characters to least, then regex escape each
var elemArray = Object.keys(this._grammar.elements).sort(function (a, b) {
return b.length - a.length;
}).map(function (elem) {
return _this._escapeRegExp(elem);
}, this);
this._splitRegex = new RegExp('(' + [preOpRegexElems.join('|'), elemArray.join('|'), postOpRegexElems.join('|')].join('|') + ')');
}
return this._splitRegex;
}
/**
* Determines whether the addition of a '-' token should be interpreted as a
* negative symbol for an upcoming number, given an array of tokens already
* processed.
* @param {Array<Object>} tokens An array of tokens already processed
* @returns {boolean} true if adding a '-' should be considered a negative
* symbol; false otherwise
* @private
*/
}, {
key: "_isNegative",
value: function _isNegative(tokens) {
if (!tokens.length) return true;
return minusNegatesAfter.some(function (type) {
return type === tokens[tokens.length - 1].type;
});
}
/**
* A utility function to determine if a string consists of only space
* characters.
* @param {string} str A string to be tested
* @returns {boolean} true if the string is empty or consists of only spaces;
* false otherwise.
* @private
*/
}, {
key: "_isWhitespace",
value: function _isWhitespace(str) {
return !!str.match(whitespaceRegex);
}
/**
* Removes the beginning and trailing quotes from a string, unescapes any
* escaped quotes on its interior, and unescapes any escaped escape
* characters. Note that this function is not defensive; it assumes that the
* provided string is not empty, and that its first and last characters are
* actually quotes.
* @param {string} str A string whose first and last characters are quotes
* @returns {string} a string with the surrounding quotes stripped and escapes
* properly processed.
* @private
*/
}, {
key: "_unquote",
value: function _unquote(str) {
var quote = str[0];
var escQuoteRegex = new RegExp('\\\\' + quote, 'g');
return str.substr(1, str.length - 2).replace(escQuoteRegex, quote).replace(escEscRegex, '\\');
}
}]);
return Lexer;
}();
Lexer_1 = Lexer;
return Lexer_1;
}
var handlers = {};
var hasRequiredHandlers;
function requireHandlers () {
if (hasRequiredHandlers) return handlers;
hasRequiredHandlers = 1;
/*
* Jexl
* Copyright 2020 Tom Shawver
*/
/**
* Handles a subexpression that's used to define a transform argument's value.
* @param {{type: <string>}} ast The subexpression tree
*/
handlers.argVal = function (ast) {
if (ast) this._cursor.args.push(ast);
};
/**
* Handles new array literals by adding them as a new node in the AST,
* initialized with an empty array.
*/
handlers.arrayStart = function () {
this._placeAtCursor({
type: 'ArrayLiteral',
value: []
});
};
/**
* Handles a subexpression representing an element of an array literal.
* @param {{type: <string>}} ast The subexpression tree
*/
handlers.arrayVal = function (ast) {
if (ast) {
this._cursor.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
*/
handlers.binaryOp = function (token) {
var precedence = this._grammar.elements[token.value].precedence || 0;
var parent = this._cursor._parent;
while (parent && parent.operator && this._grammar.elements[parent.operator].precedence >= precedence) {
this._cursor = parent;
parent = parent._parent;
}
var node = {
type: 'BinaryExpression',
operator: token.value,
left: this._cursor
};
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.
*/
handlers.dot = function () {
this._nextIdentEncapsulate = this._cursor && this._cursor.type !== 'UnaryExpression' && (this._cursor.type !== 'BinaryExpression' || this._cursor.type === 'BinaryExpression' && this._cursor.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
*/
handlers.filter = function (ast) {
this._placeBeforeCursor({
type: 'FilterExpression',
expr: ast,
relative: this._subParser.isRelative(),
subject: this._cursor
});
};
/**
* Handles identifier tokens when used to indicate the name of a function to
* be called.
* @param {{type: <string>}} token A token object
*/
handlers.functionCall = function () {
this._placeBeforeCursor({
type: 'FunctionCall',
name: this._cursor.value,
args: [],
pool: 'functions'
});
};
/**
* Handles identifier tokens by adding them as a new node in the AST.
* @param {{type: <string>}} token A token object
*/
handlers.identifier = function (token) {
var node = {
type: 'Identifier',
value: token.value
};
if (this._nextIdentEncapsulate) {
node.from = this._cursor;
this._placeBeforeCursor(node);
this._nextIdentEncapsulate = false;
} else {
if (this._nextIdentRelative) {
node.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
*/
handlers.literal = function (token) {
this._placeAtCursor({
type: 'Literal',
value: token.value
});
};
/**
* Queues a new object literal key to be written once a value is collected.
* @param {{type: <string>}} token A token object
*/
handlers.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.
*/
handlers.objStart = function () {
this._placeAtCursor({
type: 'ObjectLiteral',
value: {}
});
};
/**
* 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
*/
handlers.objVal = function (ast) {
this._cursor.value[this._curObjKey] = ast;
};
/**
* Handles traditional subexpressions, delineated with the groupStart and
* groupEnd elements.
* @param {{type: <string>}} ast The subexpression tree
*/
handlers.subExpression = function (ast) {
this._placeAtCursor(ast);
};
/**
* Handles a completed alternate subexpression of a ternary operator.
* @param {{type: <string>}} ast The subexpression tree
*/
handlers.ternaryEnd = function (ast) {
this._cursor.alternate = ast;
};
/**
* Handles a completed consequent subexpression of a ternary operator.
* @param {{type: <string>}} ast The subexpression tree
*/
handlers.ternaryMid = function (ast) {
this._cursor.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.
*/
handlers.ternaryStart = function () {
this._tree = {
type: 'ConditionalExpression',
test: this._tree
};
this._cursor = this._tree;
};
/**
* Handles identifier tokens when used to indicate the name of a transform to
* be applied.
* @param {{type: <string>}} token A token object
*/
handlers.transform = function (token) {
this._placeBeforeCursor({
type: 'FunctionCall',
name: token.value,
args: [this._cursor],
pool: 'transforms'
});
};
/**
* Handles token of type 'unaryOp', indicating that the operation has only
* one input: a right side.
* @param {{type: <string>}} token A token object
*/
handlers.unaryOp = function (token) {
this._placeAtCursor({
type: 'UnaryExpression',
operator: token.value
});
};
return handlers;
}
var states = {};
var hasRequiredStates;
function requireStates () {
if (hasRequiredStates) return states;
hasRequiredStates = 1;
/*
* Jexl
* Copyright 2020 Tom Shawver
*/
var h = requireHandlers();
/**
* A mapping of all states in the finite state machine to a set of instructions
* for handling or transitioning into other states. Each state can be handled
* in one of two schemes: a tokenType map, or a subHandler.
*
* Standard expression elements are handled through the tokenType object. This
* is an object map of all legal token types to encounter in this state (and
* any unexpected token types will generate a thrown error) to an options
* object that defines how they're handled. The available options are:
*
* {string} toState: The name of the state to which to transition
* immediately after handling this token
* {string} handler: The handler function to call when this token type is
* encountered in this state. If omitted, the default handler
* matching the token's "type" property will be called. If the handler
* function does not exist, no call will be made and no error will be
* generated. This is useful for tokens whose sole purpose is to
* transition to other states.
*
* States that consume a subexpression should define a subHandler, the
* function to be called with an expression tree argument when the
* subexpression is complete. Completeness is determined through the
* endStates object, which maps tokens on which an expression should end to the
* state to which to transition once the subHandler function has been called.
*
* Additionally, any state in which it is legal to mark the AST as completed
* should have a 'completable' property set to boolean true. Attempting to
* call {@link Parser#complete} in any state without this property will result
* in a thrown Error.
*
* @type {{}}
*/
states.states = {
expectOperand: {
tokenTypes: {
literal: {
toState: 'expectBinOp'
},
identifier: {
toState: 'identifier'
},
unaryOp: {},
openParen: {
toState: 'subExpression'
},
openCurl: {
toState: 'expectObjKey',
handler: h.objStart
},
dot: {
toState: 'traverse'
},
openBracket: {
toState: 'arrayVal',
handler: h.arrayStart
}
}
},
expectBinOp: {
tokenTypes: {
binaryOp: {
toState: 'expectOperand'
},
pipe: {
toState: 'expectTransform'
},
dot: {
toState: 'traverse'
},
question: {
toState: 'ternaryMid',
handler: h.ternaryStart
}
},
completable: true
},
expectTransform: {
tokenTypes: {
identifier: {
toState: 'postTransform',
handler: h.transform
}
}
},
expectObjKey: {
tokenTypes: {
identifier: {
toState: 'expectKeyValSep',
handler: h.objKey
},
closeCurl: {
toState: 'expectBinOp'
}
}
},
expectKeyValSep: {
tokenTypes: {
colon: {
toState: 'objVal'
}
}
},
postTransform: {
tokenTypes: {
openParen: {
toState: 'argVal'
},
binaryOp: {
toState: 'expectOperand'
},
dot: {
toState: 'traverse'
},
openBracket: {
toState: 'filter'
},
pipe: {
toState: 'expectTransform'
}
},
completable: true
},
postArgs: {
tokenTypes: {
binaryOp: {
toState: 'expectOperand'
},
dot: {
toState: 'traverse'
},
openBracket: {
toState: 'filter'
},
pipe: {
toState: 'expectTransform'
}
},
completable: true
},
identifier: {
tokenTypes: {
binaryOp: {
toState: 'expectOperand'
},
dot: {
toState: 'traverse'
},
openBracket: {
toState: 'filter'
},
openParen: {
toState: 'argVal',
handler: h.functionCall
},
pipe: {
toState: 'expectTransform'
},
question: {
toState: 'ternaryMid',
handler: h.ternaryStart
}
},
completable: true
},
traverse: {
tokenTypes: {
identifier: {
toState: 'identifier'
}
}
},
filter: {
subHandler: h.filter,
endStates: {
closeBracket: 'identifier'
}
},
subExpression: {
subHandler: h.subExpression,
endStates: {
closeParen: 'expectBinOp'
}
},
argVal: {
subHandler: h.argVal,
endStates: {
comma: 'argVal',
closeParen: 'postArgs'
}
},
objVal: {
subHandler: h.objVal,
endStates: {
comma: 'expectObjKey',
closeCurl: 'expectBinOp'
}
},
arrayVal: {
subHandler: h.arrayVal,
endStates: {
comma: 'arrayVal',
closeBracket: 'expectBinOp'
}
},
ternaryMid: {
subHandler: h.ternaryMid,
endStates: {
colon: 'ternaryEnd'
}
},
ternaryEnd: {
subHandler: h.ternaryEnd,
completable: true
}
};
return states;
}
var Parser_1;
var hasRequiredParser;
function requireParser () {
if (hasRequiredParser) return Parser_1;
hasRequiredParser = 1;
var _interopRequireDefault = interopRequireDefaultExports;
var _classCallCheck2 = _interopRequireDefault(requireClassCallCheck());
var _createClass2 = _interopRequireDefault(requireCreateClass());
/*
* Jexl
* Copyright 2020 Tom Shawver
*/
var handlers = requireHandlers();
var states = requireStates().states;
/**
* The Parser is a state machine that converts tokens from the {@link Lexer}
* into an Abstract Syntax Tree (AST), capable of being evaluated in any
* context by the {@link Evaluator}. The Parser expects that all tokens
* provided to it are legal and typed properly according to the grammar, but
* accepts that the tokens may still be in an invalid order or in some other
* unparsable configuration that requires it to throw an Error.
* @param {{}} grammar The grammar object to use to parse Jexl strings
* @param {string} [prefix] A string prefix to prepend to the expression string
* for error messaging purposes. This is useful for when a new Parser is
* instantiated to parse an subexpression, as the parent Parser's
* expression string thus far can be passed for a more user-friendly
* error message.
* @param {{}} [stopMap] A mapping of token types to any truthy value. When the
* token type is encountered, the parser will return the mapped value
* instead of boolean false.
*/
var Parser = /*#__PURE__*/function () {
function Parser(grammar, prefix, stopMap) {
(0, _classCallCheck2.default)(this, Parser);
this._grammar = grammar;
this._state = 'expectOperand';
this._tree = null;
this._exprStr = prefix || '';
this._relative = false;
this._stopMap = stopMap || {};
}
/**
* Processes a new token into the AST and manages the transitions of the state
* machine.
* @param {{type: <string>}} token A token object, as provided by the
* {@link Lexer#tokenize} function.
* @throws {Error} if a token is added when the Parser has been marked as
* complete by {@link #complete}, or if an unexpected token type is added.
* @returns {boolean|*} the stopState value if this parser encountered a token
* in the stopState mapb false if tokens can continue.
*/
(0, _createClass2.default)(Parser, [{
key: "addToken",
value: function addToken(token) {
if (this._state === 'complete') {
throw new Error('Cannot add a new token to a completed Parser');
}
var state = states[this._state];
var startExpr = this._exprStr;
this._exprStr += token.raw;
if (state.subHandler) {
if (!this._subParser) {
this._startSubExpression(startExpr);
}
var stopState = this._subParser.addToken(token);
if (stopState) {
this._endSubExpression();
if (this._parentStop) return stopState;
this._state = stopState;
}
} else if (state.tokenTypes[token.type]) {
var typeOpts = state.tokenTypes[token.type];
var handleFunc = handlers[token.type];
if (typeOpts.handler) {
handleFunc = typeOpts.handler;
}
if (handleFunc) {
handleFunc.call(this, token);
}
if (typeOpts.toState) {
this._state = typeOpts.toState;
}
} else if (this._stopMap[token.type]) {
return this._stopMap[token.type];
} else {
throw new Error("Token ".concat(token.raw, " (").concat(token.type, ") unexpected in expression: ").concat(this._exprStr));
}
return false;
}
/**
* Processes an array of tokens iteratively through the {@link #addToken}
* function.
* @param {Array<{type: <string>}>} tokens An array of tokens, as provided by
* the {@link Lexer#tokenize} function.
*/
}, {
key: "addTokens",
value: function addTokens(tokens) {
tokens.forEach(this.addToken, this);
}
/**
* Marks this Parser instance as completed and retrieves the full AST.
* @returns {{}|null} a full expression tree, ready for evaluation by the
* {@link Evaluator#eval} function, or null if no tokens were passed to
* the parser before complete was called
* @throws {Error} if the parser is not in a state where it's legal to end
* the expression, indicating that the expression is incomplete
*/
}, {
key: "complete",
value: function complete() {
if (this._cursor && !states[this._state].completable) {
throw new Error("Unexpected end of expression: ".concat(this._exprStr));
}
if (this._subParser) {
this._endSubExpression();
}
this._state = 'complete';
return this._cursor ? this._tree : null;
}
/**
* Indicates whether the expression tree contains a relative path identifier.
* @returns {boolean} true if a relative identifier exists false otherwise.
*/
}, {
key: "isRelative",
value: function isRelative() {
return this._relative;
}
/**
* Ends a subexpression by completing the subParser and passing its result
* to the subHandler configured in the current state.
* @private
*/
}, {
key: "_endSubExpression",
value: function _endSubExpression() {
states[this._state].subHandler.call(this, this._subParser.complete());
this._subParser = null;
}
/**
* Places a new tree node at the current position of the cursor (to the 'right'
* property) and then advances the cursor to the new node. This function also
* handles setting the parent of the new node.
* @param {{type: <string>}} node A node to be added to the AST