[IMP] web_pivot_computed_measure: Function that eval operations

This commit is contained in:
Carlos Roca
2023-11-27 11:09:47 +01:00
parent 148495e6dc
commit e97a883a6f

View File

@@ -1,27 +1,124 @@
/** @odoo-module **/ /** @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) */ * 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 {String} text
* @param {Object} vals * @returns {Array}
*/
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;
}
}
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} * @returns {any}
*/ */
export const evalOperation = (text, vals) => { export function evalOperation(text, values) {
for (const variable in vals) { const tokens = getTokensFromText(text);
if (vals.hasOwnProperty(variable)) { const operands = [];
const regex = new RegExp(variable, "g"); const operators = [];
text = text.replace(regex, vals[variable]); 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);
} }
} }
try { while (operators.length > 0) {
// eslint-disable-next-line no-eval executeOperation(operands, operators);
const res = eval(text);
return res;
} catch (error) {
console.error("Error trying to eval operation:", error);
return;
} }
}; return operands.pop();
}