UNPKG

expression-language

Version:

Javascript implementation of symfony/expression-language

211 lines (205 loc) 10.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _Lexer = require("./Lexer"); var _Parser = _interopRequireWildcard(require("./Parser")); var _Compiler = _interopRequireDefault(require("./Compiler")); var _ParsedExpression = _interopRequireDefault(require("./ParsedExpression")); var _ArrayAdapter = _interopRequireDefault(require("./Cache/ArrayAdapter")); var _LogicException = _interopRequireDefault(require("./LogicException")); var _ExpressionFunction = _interopRequireDefault(require("./ExpressionFunction")); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } 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); } class ExpressionLanguage { constructor(cache = null, providers = []) { /** * Compiles an expression source code. * * @param {Expression|string} expression The expression to compile * @param {Array} names An array of valid names * * @returns {string} The compiled javascript source code */ _defineProperty(this, "compile", (expression, names = []) => { return this.getCompiler().compile(this.parse(expression, names).getNodes()).getSource(); }); /** * Evaluate an expression * * @param {Expression|string} expression The expression to compile * @param {Object} values An array of values * * @returns {*} The result of the evaluation of the expression */ _defineProperty(this, "evaluate", (expression, values = {}) => { return this.parse(expression, Object.keys(values)).getNodes().evaluate(this.functions, values); }); /** * Parses an expression * * @param {Expression|string} expression The expression to parse * @param {Array} names An array of valid names * @param {int} flags * @returns {ParsedExpression} A ParsedExpression instance */ _defineProperty(this, "parse", (expression, names, flags = 0) => { if (expression instanceof _ParsedExpression.default) { return expression; } names.sort((a, b) => { let a_value = a, b_value = b; if (typeof a === "object") { a_value = Object.values(a)[0]; } if (typeof b === "object") { b_value = Object.values(b)[0]; } return a_value.localeCompare(b_value); }); let cacheKeyItems = []; for (let name of names) { let value = name; if (typeof name === "object") { let tmpName = Object.keys(name)[0], tmpValue = Object.values(name)[0]; value = tmpName + ":" + tmpValue; } cacheKeyItems.push(value); } let cacheItem = this.cache.getItem(this.fixedEncodeURIComponent(expression + "//" + cacheKeyItems.join("|"))), parsedExpression = cacheItem.get(); if (null === parsedExpression) { let nodes = this.getParser().parse(this.getLexer().tokenize(expression), names, flags); parsedExpression = new _ParsedExpression.default(expression, nodes); cacheItem.set(parsedExpression); this.cache.save(cacheItem); } return parsedExpression; }); _defineProperty(this, "lint", (expression, names = null, flags = 0) => { if (null === names) { console.log("Deprecated: passing \"null\" as the second argument of lint is deprecated, pass IGNORE_UNKNOWN_VARIABLES instead as the third argument"); flags |= _Parser.IGNORE_UNKNOWN_VARIABLES; names = []; } if (expression instanceof _ParsedExpression.default) { return; } // Ensure parser is initialized and pass names/flags to parser.lint this.getParser().lint(this.getLexer().tokenize(expression), names, flags); }); _defineProperty(this, "fixedEncodeURIComponent", str => { return encodeURIComponent(str).replace(/[!'()*]/g, function (c) { return '%' + c.charCodeAt(0).toString(16); }); }); /** * Registers a function * * @param {string} name The function name * @param {function} compiler A function able to compile the function * @param {function} evaluator A function able to evaluate the function * * @throws Error * * @see ExpressionFunction */ _defineProperty(this, "register", (name, compiler, evaluator) => { if (null !== this.parser) { throw new _LogicException.default("Registering functions after calling evaluate(), compile(), or parse() is not supported."); } this.functions[name] = { compiler: compiler, evaluator: evaluator }; }); _defineProperty(this, "addFunction", expressionFunction => { this.register(expressionFunction.getName(), expressionFunction.getCompiler(), expressionFunction.getEvaluator()); }); _defineProperty(this, "registerProvider", provider => { for (let fn of provider.getFunctions()) { this.addFunction(fn); } }); _defineProperty(this, "getLexer", () => { if (null === this.lexer) { this.lexer = { tokenize: _Lexer.tokenize }; } return this.lexer; }); _defineProperty(this, "getParser", () => { if (null === this.parser) { this.parser = new _Parser.default(this.functions); } return this.parser; }); _defineProperty(this, "getCompiler", () => { if (null === this.compiler) { this.compiler = new _Compiler.default(this.functions); } return this.compiler.reset(); }); this.functions = []; this.lexer = null; this.parser = null; this.compiler = null; this.cache = cache || new _ArrayAdapter.default(); this._registerBuiltinFunctions(); for (let provider of providers) { this.registerProvider(provider); } } _registerBuiltinFunctions() { const minFn = _ExpressionFunction.default.fromJavascript('Math.min', 'min'); const maxFn = _ExpressionFunction.default.fromJavascript('Math.max', 'max'); this.addFunction(minFn); this.addFunction(maxFn); // PHP-like constant(name): resolves a global/dotted path from globalThis (or window/global) this.addFunction(new _ExpressionFunction.default('constant', function compiler(constantName) { // Compile to an IIFE that resolves from global object, supporting dotted paths (e.g., "Math.PI") return `(function(__n){var __g=(typeof globalThis!=='undefined'?globalThis:(typeof window!=='undefined'?window:(typeof global!=='undefined'?global:{})));return __n.split('.')` + `.reduce(function(o,k){return o==null?undefined:o[k];}, __g)})(${constantName})`; }, function evaluator(values, constantName) { if (typeof constantName !== 'string' || !constantName) { return undefined; } const getGlobal = () => typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : {}; const resolvePath = (root, path) => { return path.split('.').reduce((o, k) => o == null ? undefined : o[k], root); }; // First try global resolution (supports dotted path like Math.PI) let resolved = resolvePath(getGlobal(), constantName); // As a convenience, also allow constants supplied in the evaluation values map by exact name if (resolved === undefined && values && Object.prototype.hasOwnProperty.call(values, constantName)) { resolved = values[constantName]; } return resolved; })); // PHP-like enum(FQN::CASE): resolves a namespaced path from global object this.addFunction(new _ExpressionFunction.default('enum', function compiler(enumName) { // normalize separators ('.', '\\', '::') into path segments without using regex return `(function(__n){var __g=(typeof globalThis!=='undefined'?globalThis:(typeof window!=='undefined'?window:(typeof global!=='undefined'?global:{})));` + `if(typeof __n!=='string'||!__n)return undefined;` + `var s=String(__n);var keys=[],buf='';` + `for(var i=0;i<s.length;i++){var c=s.charCodeAt(i);` + `if(c===46||c===92){if(buf){keys.push(buf);buf='';}continue;}` + `if(c===58){if(i+1<s.length&&s.charCodeAt(i+1)===58){if(buf){keys.push(buf);buf='';}i++;continue;}}` + `buf+=s[i];}` + `if(buf)keys.push(buf);` + `return keys.reduce(function(o,k){return o==null?undefined:o[k];}, __g)})(${enumName})`; }, function evaluator(values, enumName) { if (typeof enumName !== 'string' || !enumName) { return undefined; } const getGlobal = () => typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : {}; const normalize = s => { // Replace single backslashes and double-colon with dots return String(s).replace(/\\/g, '.').replace(/::/g, '.'); }; const resolvePath = (root, path) => path.split('.').reduce((o, k) => o == null ? undefined : o[k], root); const normalized = normalize(enumName); if (!normalized) return undefined; return resolvePath(getGlobal(), normalized); })); } } exports.default = ExpressionLanguage;