diff --git a/web_pivot_computed_measure/static/src/helpers/utils.esm.js b/web_pivot_computed_measure/static/src/helpers/utils.esm.js index c50480220..df95c84bc 100644 --- a/web_pivot_computed_measure/static/src/helpers/utils.esm.js +++ b/web_pivot_computed_measure/static/src/helpers/utils.esm.js @@ -1,27 +1,124 @@ /** @odoo-module **/ -/* Copyright 2022 Tecnativa - Carlos Roca +/* Copyright 2023 Tecnativa - Carlos Roca * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) */ /** - * Helper function to eval text for a given object + * Function that traverse the text given character by character * * @param {String} text - * @param {Object} vals - * @returns {any} + * @returns {Array} */ -export const evalOperation = (text, vals) => { - for (const variable in vals) { - if (vals.hasOwnProperty(variable)) { - const regex = new RegExp(variable, "g"); - text = text.replace(regex, vals[variable]); +function getTokensFromText(text) { + const symbols = ["+", "-", "*", "/", "(", ")"]; + const tokens = []; + let token = ""; + for (let i = 0; i < text.length; i++) { + const c = text[i]; + if (c === " ") continue; + if (symbols.includes(c)) { + if (token !== "") { + tokens.push(token); + token = ""; + } + tokens.push(c); + } else { + token += c; } } - try { - // eslint-disable-next-line no-eval - const res = eval(text); - return res; - } catch (error) { - console.error("Error trying to eval operation:", error); - return; + if (token !== "") { + tokens.push(token); } -}; + return tokens; +} + +/** + * Function that executes an operation between the last two operands in the operands stack + * and the last operator in the operators stack, and saves the result in the operands stack. + * + * @param {Array} operands + * @param {Array} operators + */ +function executeOperation(operands, operators) { + const b = operands.pop(); + const a = operands.pop(); + const op = operators.pop(); + switch (op) { + case "+": + operands.push(a + b); + break; + case "-": + operands.push(a - b); + break; + case "*": + operands.push(a * b); + break; + case "/": + operands.push(a / b); + break; + } +} + +/** + * Function that returns the precedence of an operator + * + * @param {String} op + * @returns {Number} + */ +function precedence(op) { + if (op === "+" || op === "-") { + return 1; + } + if (op === "*" || op === "/") { + return 2; + } + if (op === "(" || op === ")") { + return 0; + } +} + +/** + * Helper function that takes a mathematical expression in text form and an object + * of variable values, evaluates the expression, and returns the result. + * + * @param {String} text + * @param {Object} values + * @returns {any} + */ +export function evalOperation(text, values) { + const tokens = getTokensFromText(text); + const operands = []; + const operators = []; + for (const token of tokens) { + if (!isNaN(token)) { + // If the token is a number, convert it to a number and add it to the operands stack + operands.push(Number(token)); + } else if (token in values) { + // If the token is a variable, get its value from the object and add it to the operands stack + operands.push(values[token]); + } else if (token === "(") { + // If the token is an open parenthesis, add it to the operators stack + operators.push(token); + } else if (token === ")") { + // If the token is a closing parenthesis, pop and execute operators from the stack until an open parenthesis is found + while (operators.length > 0 && operators[operators.length - 1] !== "(") { + executeOperation(operands, operators); + } + // Pop the open parenthesis from the operators stack + operators.pop(); + } else { + // If the token is an operator, pop and execute operators from the stack while they have equal or higher precedence than the token + while ( + operators.length > 0 && + precedence(operators[operators.length - 1]) >= precedence(token) + ) { + executeOperation(operands, operators); + } + // Add the token to the operators stack + operators.push(token); + } + } + while (operators.length > 0) { + executeOperation(operands, operators); + } + return operands.pop(); +}