UNPKG

superfly-timeline

Version:

Resolver for defining objects with temporal boolean logic relationships on a timeline

183 lines 7.87 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ExpressionHandler = exports.REGEXP_OPERATORS = exports.OPERATORS = void 0; const lib_1 = require("./lib/lib"); const cache_1 = require("./lib/cache"); const expression_1 = require("./lib/expression"); exports.OPERATORS = ['&', '|', '+', '-', '*', '/', '%', '!']; exports.REGEXP_OPERATORS = new RegExp('([' + exports.OPERATORS.map((o) => '\\' + o).join('') + '\\(\\)])', 'g'); class ExpressionHandler { constructor(autoClearCache, skipValidation) { this.skipValidation = skipValidation; this.cache = new cache_1.Cache(autoClearCache); } interpretExpression(expression) { if ((0, expression_1.isNumericExpr)(expression)) { return parseFloat(expression); } else if (typeof expression === 'string') { const expressionString = expression; return this.cache.cacheResult(expressionString, () => { const expr = expressionString.replace(exports.REGEXP_OPERATORS, ' $1 '); // Make sure there's a space between every operator & operand const words = (0, lib_1.compact)(expr.split(' ')); if (words.length === 0) return null; // empty expression // Fix special case: a + - b for (let i = words.length - 2; i >= 1; i--) { if ((words[i] === '-' || words[i] === '+') && wordIsOperator(exports.OPERATORS, words[i - 1])) { words[i] = words[i] + words[i + 1]; words.splice(i + 1, 1); } } const innerExpression = this.wrapInnerExpressions(words); if (innerExpression.rest.length) throw new Error(`interpretExpression: syntax error: parentheses don't add up in "${expr}".`); if (innerExpression.inner.length % 2 !== 1) { throw new Error(`interpretExpression: operands & operators don't add up: "${innerExpression.inner.join(' ')}".`); } const returnExpression = this.words2Expression(exports.OPERATORS, innerExpression.inner); if (!this.skipValidation) this.validateExpression(exports.OPERATORS, returnExpression); return returnExpression; }, 60 * 60 * 1000 // 1 hour ); } else { return expression; } } /** Try to simplify an expression, this includes: * * Combine constant operands, using arithmetic operators * ...more to come? */ simplifyExpression(expr0) { const expr = typeof expr0 === 'string' ? this.interpretExpression(expr0) : expr0; if (!expr) return expr; if (isExpressionObject(expr)) { const l = this.simplifyExpression(expr.l); const o = expr.o; const r = this.simplifyExpression(expr.r); if (typeof l === 'number' && typeof r === 'number') { // The operands can be combined: switch (o) { case '+': return l + r; case '-': return l - r; case '*': return l * r; case '/': return l / r; case '%': return l % r; default: return { l, o, r }; } } return { l, o, r }; } return expr; } // Turns ['a', '(', 'b', 'c', ')'] into ['a', ['b', 'c']] // or ['a', '&', '!', 'b'] into ['a', '&', ['', '!', 'b']] wrapInnerExpressions(words) { for (let i = 0; i < words.length; i++) { switch (words[i]) { case '(': { const tmp = this.wrapInnerExpressions(words.slice(i + 1)); // insert inner expression and remove tha words[i] = tmp.inner; words.splice(i + 1, 99999, ...tmp.rest); break; } case ')': return { inner: words.slice(0, i), rest: words.slice(i + 1), }; case '!': { const tmp = this.wrapInnerExpressions(words.slice(i + 1)); // insert inner expression after the '!' words[i] = ['', '!'].concat(tmp.inner); words.splice(i + 1, 99999, ...tmp.rest); break; } } } return { inner: words, rest: [], }; } /** Validates an expression. Returns true on success, throws error if not */ validateExpression(operatorList, expr0, breadcrumbs) { if (!breadcrumbs) breadcrumbs = 'ROOT'; if ((0, lib_1.isObject)(expr0) && !(0, lib_1.isArray)(expr0)) { const expr = expr0; if (expr.l === undefined) throw new Error(`validateExpression: ${breadcrumbs}.l missing in ${JSON.stringify(expr)}`); if (expr.o === undefined) throw new Error(`validateExpression: ${breadcrumbs}.o missing in ${JSON.stringify(expr)}`); if (expr.r === undefined) throw new Error(`validateExpression: ${breadcrumbs}.r missing in ${JSON.stringify(expr)}`); if (typeof expr.o !== 'string') throw new Error(`validateExpression: ${breadcrumbs}.o not a string`); if (!wordIsOperator(operatorList, expr.o)) throw new Error(breadcrumbs + '.o not valid: "' + expr.o + '"'); return (this.validateExpression(operatorList, expr.l, breadcrumbs + '.l') && this.validateExpression(operatorList, expr.r, breadcrumbs + '.r')); } else if (expr0 !== null && typeof expr0 !== 'string' && typeof expr0 !== 'number') { throw new Error(`validateExpression: ${breadcrumbs} is of invalid type`); } return true; } clearCache() { this.cache.clear(); } words2Expression(operatorList, words) { /* istanbul ignore if */ if (!words?.length) throw new Error('words2Expression: syntax error: unbalanced expression'); while (words.length === 1 && words[0] !== null && (0, lib_1.isArray)(words[0])) words = words[0]; if (words.length === 1) return words[0]; // Find the operator with the highest priority: let operatorI = -1; for (let i = 0; i < operatorList.length; i++) { const operator = operatorList[i]; if (operatorI === -1) { operatorI = words.lastIndexOf(operator); } } if (operatorI !== -1) { const l = words.slice(0, operatorI); const r = words.slice(operatorI + 1); const expr = { l: this.words2Expression(operatorList, l), o: words[operatorI], r: this.words2Expression(operatorList, r), }; return expr; } else throw new Error('words2Expression: syntax error: operator not found: "' + words.join(' ') + '"'); } } exports.ExpressionHandler = ExpressionHandler; function isExpressionObject(expr) { return (typeof expr === 'object' && expr !== null && expr.l !== undefined && expr.o !== undefined && expr.r !== undefined); } function wordIsOperator(operatorList, word) { if (operatorList.indexOf(word) !== -1) return true; return false; } //# sourceMappingURL=ExpressionHandler.js.map