From f45795b24c81d606f4a1363d42d6067872aaf842 Mon Sep 17 00:00:00 2001
From: Carlos Roca <carlos.roca@tecnativa.com>
Date: Mon, 27 Nov 2023 11:09:47 +0100
Subject: [PATCH] [IMP] web_pivot_computed_measure: Function that eval
 operations

---
 web_pivot_computed_measure/README.rst         |   2 +-
 web_pivot_computed_measure/__manifest__.py    |   2 +-
 .../static/description/index.html             |   2 +-
 .../static/src/helpers/utils.esm.js           | 131 +++++++++++++++---
 4 files changed, 117 insertions(+), 20 deletions(-)

diff --git a/web_pivot_computed_measure/README.rst b/web_pivot_computed_measure/README.rst
index 7b53c329a..c5dc6f1ac 100644
--- a/web_pivot_computed_measure/README.rst
+++ b/web_pivot_computed_measure/README.rst
@@ -7,7 +7,7 @@ Web Pivot Computed Measure
    !! This file is generated by oca-gen-addon-readme !!
    !! changes will be overwritten.                   !!
    !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-   !! source digest: sha256:ece4336d91177883eff784573de23cd6a67b1044e080eb758f7de5e6374d57f0
+   !! source digest: sha256:34691d378ce32fade67311831059070fab3df66f5cb2ec7fa88287018b85de7b
    !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 
 .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
diff --git a/web_pivot_computed_measure/__manifest__.py b/web_pivot_computed_measure/__manifest__.py
index 7f172c2ed..e77ae591a 100644
--- a/web_pivot_computed_measure/__manifest__.py
+++ b/web_pivot_computed_measure/__manifest__.py
@@ -3,7 +3,7 @@
 {
     "name": "Web Pivot Computed Measure",
     "category": "web",
-    "version": "15.0.1.0.2",
+    "version": "15.0.1.0.3",
     "author": "Tecnativa, Odoo Community Association (OCA)",
     "license": "AGPL-3",
     "website": "https://github.com/OCA/web",
diff --git a/web_pivot_computed_measure/static/description/index.html b/web_pivot_computed_measure/static/description/index.html
index b124f1866..f3e6801f0 100644
--- a/web_pivot_computed_measure/static/description/index.html
+++ b/web_pivot_computed_measure/static/description/index.html
@@ -367,7 +367,7 @@ ul.auto-toc {
 !! This file is generated by oca-gen-addon-readme !!
 !! changes will be overwritten.                   !!
 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-!! source digest: sha256:ece4336d91177883eff784573de23cd6a67b1044e080eb758f7de5e6374d57f0
+!! source digest: sha256:34691d378ce32fade67311831059070fab3df66f5cb2ec7fa88287018b85de7b
 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
 <p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/web/tree/15.0/web_pivot_computed_measure"><img alt="OCA/web" src="https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/web-15-0/web-15-0-web_pivot_computed_measure"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/web&amp;target_branch=15.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
 <p>Adds support for computed measures on the pivot view.</p>
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();
+}