UNPKG

@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
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