From 80e69153f6f3c4bda249fab5f66b786e4bd25ef6 Mon Sep 17 00:00:00 2001
From: mmatera <matera@fisica.unlp.edu.ar>
Date: Fri, 28 Jul 2023 09:36:32 -0300
Subject: [PATCH 1/2] Times should have always at least 2 elements

Rework arithmetic format
---
 mathics/builtin/arithfns/basic.py            | 352 +++++++++++++------
 mathics/builtin/arithmetic.py                |   3 +-
 mathics/builtin/forms/output.py              |   4 +-
 mathics/builtin/layout.py                    |   2 +-
 mathics/builtin/numbers/calculus.py          |   4 +-
 mathics/builtin/quantum_mechanics/angular.py |   2 +-
 mathics/builtin/statistics/orderstats.py     |   2 +-
 mathics/core/convert/sympy.py                |   5 +-
 mathics/core/systemsymbols.py                |   1 +
 mathics/doc/documentation/1-Manual.mdoc      |  10 +-
 mathics/eval/makeboxes.py                    |  24 +-
 mathics/format/latex.py                      |  26 +-
 mathics/format/mathml.py                     |  25 +-
 mathics/format/text.py                       |   4 +-
 test/builtin/arithmetic/test_basic.py        |  12 +-
 test/format/test_format.py                   |  21 +-
 16 files changed, 357 insertions(+), 140 deletions(-)

diff --git a/mathics/builtin/arithfns/basic.py b/mathics/builtin/arithfns/basic.py
index 558369c9f..42e235148 100644
--- a/mathics/builtin/arithfns/basic.py
+++ b/mathics/builtin/arithfns/basic.py
@@ -6,7 +6,6 @@
 on a calculator.
 
 """
-
 from mathics.builtin.arithmetic import create_infix
 from mathics.builtin.base import (
     BinaryOperator,
@@ -52,14 +51,29 @@
 from mathics.core.systemsymbols import (
     SymbolBlank,
     SymbolComplexInfinity,
+    SymbolFractionBox,
+    SymbolI,
     SymbolIndeterminate,
     SymbolInfix,
+    SymbolInputForm,
     SymbolLeft,
+    SymbolMakeBoxes,
     SymbolMinus,
+    SymbolOutputForm,
     SymbolPattern,
+    SymbolPrecedenceForm,
+    SymbolRowBox,
     SymbolSequence,
+    SymbolStandardForm,
+    SymbolTraditionalForm,
 )
 from mathics.eval.arithmetic import eval_Plus, eval_Times
+from mathics.eval.makeboxes import (
+    builtins_precedence,
+    do_format_complex,
+    eval_makeboxes,
+    parenthesize,
+)
 from mathics.eval.nevaluator import eval_N
 from mathics.eval.numerify import numerify
 
@@ -178,7 +192,7 @@ class Divide(BinaryOperator):
     default_formats = False
 
     formats = {
-        (("InputForm", "OutputForm"), "Divide[x_, y_]"): (
+        ("InputForm", "Divide[x_, y_]"): (
             'Infix[{HoldForm[x], HoldForm[y]}, "/", 400, Left]'
         ),
     }
@@ -189,13 +203,48 @@ class Divide(BinaryOperator):
 
     rules = {
         "Divide[x_, y_]": "Times[x, Power[y, -1]]",
-        "MakeBoxes[Divide[x_, y_], f:StandardForm|TraditionalForm]": (
-            "FractionBox[MakeBoxes[x, f], MakeBoxes[y, f]]"
-        ),
+        #        "MakeBoxes[Divide[x_, y_], f:StandardForm|TraditionalForm]": (
+        #            "FractionBox[MakeBoxes[x, f], MakeBoxes[y, f]]"
+        #        ),
     }
-
     summary_text = "divide"
 
+    # The reason why we need these MakeBoxes methods instead of rules
+    # is that we have still several incompatibilities with WMA.
+    # In particular, OutputForm should produce a "2D" text representation,
+    # similar to the pretty print of sympy. So, for example,
+    # OutputForm[Divide[a+b,c+d]]  -> FractionBox[a+b, c+d]
+    # shoud produce something like
+    #
+    #     a + b
+    #    -------
+    #     c + d
+    # which does not require parenthesis in the numerator and the denominator.
+    #
+    # On the other hand, with our current implementation, FractionBox[a+b, c+d]
+    # produces
+    #  a + b / c + d
+    #
+    # For this reason, we need to add parenthesis to this expression.
+    # Then, when we translate it to some representation admitting the 2D representation
+    # like LaTeX or MathML, the parenthesis are removed.
+
+    def eval_makeboxes_input_output_form(self, x, y, form, evaluation):
+        """MakeBoxes[Divide[x_, y_], form:OutputForm]"""
+        # This adds the parenthesis when they are needed, for the case
+        # of 1D text output.
+        num = parenthesize(400, x, eval_makeboxes(x, evaluation, form), False)
+        den = parenthesize(400, y, eval_makeboxes(y, evaluation, form), True)
+        return Expression(SymbolFractionBox, num, den)
+
+    def eval_makeboxes_standardform(self, x, y, form, evaluation):
+        """MakeBoxes[Divide[x_, y_], form:(StandardForm|TraditionalForm)]"""
+        # This adds the parenthesis when they are needed, for the case
+        # of 1D text output.
+        num = parenthesize(400, x, eval_makeboxes(x, evaluation, form), False)
+        den = parenthesize(400, y, eval_makeboxes(y, evaluation, form), True)
+        return Expression(SymbolFractionBox, num, den)
+
 
 class Minus(PrefixOperator):
     """
@@ -223,16 +272,7 @@ class Minus(PrefixOperator):
     """
 
     attributes = A_LISTABLE | A_NUMERIC_FUNCTION | A_PROTECTED
-
-    formats = {
-        "Minus[x_]": 'Prefix[{HoldForm[x]}, "-", 480]',
-        # don't put e.g. -2/3 in parentheses
-        "Minus[expr_Divide]": 'Prefix[{HoldForm[expr]}, "-", 399]',
-        "Minus[Infix[expr_, op_, 400, grouping_]]": (
-            'Prefix[{Infix[expr, op, 400, grouping]}, "-", 399]'
-        ),
-    }
-
+    default_formats = False
     operator = "-"
     precedence = 480
 
@@ -246,6 +286,13 @@ def eval_int(self, x: Integer, evaluation):
         "Minus[x_Integer]"
         return Integer(-x.value)
 
+    def eval_makeboxes(self, x, form, evaluation):
+        """MakeBoxes[Minus[x_], form:(InputForm|OutputForm|StandardForm|TraditionalForm)]"""
+        # This adds the parenthesis when they are needed, for the case
+        # of 1D text output.
+        factor = parenthesize(400, x, eval_makeboxes(x, evaluation, form), False)
+        return Expression(SymbolRowBox, ListExpression(String(self.operator), factor))
+
 
 class Plus(BinaryOperator, SympyFunction):
     """
@@ -337,56 +384,78 @@ class Plus(BinaryOperator, SympyFunction):
     # Remember to up sympy doc link when this is corrected
     sympy_name = "Add"
 
-    def format_plus(self, items, evaluation):
-        "Plus[items__]"
-
-        def negate(item):  # -> Expression (see FIXME below)
-            if item.has_form("Times", 1, None):
-                if isinstance(item.elements[0], Number):
-                    neg = -item.elements[0]
-                    if neg.sameQ(Integer1):
-                        if len(item.elements) == 1:
-                            return neg
-                        else:
-                            return Expression(SymbolTimes, *item.elements[1:])
-                    else:
-                        return Expression(SymbolTimes, neg, *item.elements[1:])
-                else:
-                    return Expression(SymbolTimes, IntegerM1, *item.elements)
-            elif isinstance(item, Number):
-                return from_sympy(-item.to_sympy())
-            else:
-                return Expression(SymbolTimes, IntegerM1, item)
+    def eval_makeboxes_plus(self, items, form, evaluation):
+        "MakeBoxes[Plus[items__], form:(InputForm|OutputForm|StandardForm|TraditionalForm)]"
+        if form in (SymbolInputForm, SymbolOutputForm):
+            ops = [String(" + "), String(" - ")]
+        else:
+            ops = [String("+"), String("-")]
+        return self.do_eval_makeboxes_plus_ops(
+            items.get_sequence(), form, evaluation, ops
+        )
 
-        def is_negative(value) -> bool:
-            if isinstance(value, Complex):
-                real, imag = value.to_sympy().as_real_imag()
-                if real <= 0 and imag <= 0:
+    def do_eval_makeboxes_plus_ops(
+        self, terms, form, evaluation, ops=[String("+"), String("-")]
+    ):
+        term = terms[0]
+        formatted_term = eval_makeboxes(term, evaluation, form)
+        # formatted_term = parenthesize(310, term, formatted_term, True)
+        formatted_terms = [formatted_term]
+
+        def is_negative(t):
+            if isinstance(t, Complex):
+                re_symp, im_sympy = t.to_sympy().as_real_imag()
+                if re_sympy == 0:
+                    if im_sympy < 0:
+                        return True
+                if re_sympy < 0:
                     return True
-            elif isinstance(value, Number) and value.to_sympy() < 0:
-                return True
+            if isinstance(t, Number):
+                return t.value < 0
+            if t.has_form("Minus", 1):
+                return not is_negative(t.elements[0])
+            if t.has_form("Times", 2, None):
+                return is_negative(t.elements[0])
+            if t.has_form("HoldForm", 1):
+                return is_negative(t.elements[0])
+            if t.has_form("Divide", 2):
+                return is_negative(t.elements[0])
             return False
 
-        elements = items.get_sequence()
-        values = [to_expression(SymbolHoldForm, element) for element in elements[:1]]
-        ops = []
-        for element in elements[1:]:
-            if (
-                element.has_form("Times", 1, None) and is_negative(element.elements[0])
-            ) or is_negative(element):
-                element = negate(element)
-                op = "-"
-            else:
-                op = "+"
-            values.append(Expression(SymbolHoldForm, element))
-            ops.append(String(op))
-        return Expression(
-            SymbolInfix,
-            ListExpression(*values),
-            ListExpression(*ops),
-            Integer310,
-            SymbolLeft,
-        )
+        def negate(t):
+            if isinstance(t, Number):
+                return from_sympy(-t.to_sympy())
+            if t.has_form("Times", 2, None):
+                factors = t.elements
+                return Expression(SymbolTimes, negate(factors[0]), *factors[1:])
+            if t.has_form("Minus", 1):
+                return t.elements[0]
+            if t.has_form("HoldForm", 1):
+                return negate(t.elements[0])
+            if t.has_form("Divide", 2):
+                num, den = t.elements
+                return Expression(SymbolDivide, negate(num), den)
+            return Expression(Minus, t)
+
+        for term in terms[1:]:
+            minus = 0
+            while True:
+                if term.has_form("HoldForm", 1):
+                    term = term.elements[0]
+                    continue
+                if term.has_form("Minus", 1):
+                    term = term.elements[0]
+                    minus += 1
+                    continue
+                if is_negative(term):
+                    minus += 1
+                    term = negate(term)
+                break
+            formatted_terms.append(ops[minus % 2])
+            formatted_term = eval_makeboxes(term, evaluation, form)
+            formatted_term = parenthesize(310, term, formatted_term, True)
+            formatted_terms.append(formatted_term)
+        return Expression(SymbolRowBox, ListExpression(*formatted_terms))
 
     def eval(self, items, evaluation):
         "Plus[items___]"
@@ -771,8 +840,49 @@ class Times(BinaryOperator, SympyFunction):
 
     summary_text = "mutiply"
 
-    def format_times(self, items, evaluation, op="\u2062"):
-        "Times[items__]"
+    def do_eval_makeboxes_times(self, factors, form, evaluation, op=String("\u2062")):
+        op_str = String(op)
+        factor = factors[0]
+        formatted_factor = eval_makeboxes(factor, evaluation, form)
+        formatted_factor = parenthesize(400, factor, formatted_factor, False)
+        elements = [formatted_factor]
+        factors = factors[1:]
+
+        for factor in factors:
+            elements.append(op)
+            formatted_factor = eval_makeboxes(factor, evaluation, form)
+            formatted_factor = parenthesize(400, factor, formatted_factor, False)
+            elements.append(formatted_factor)
+
+        return Expression(SymbolRowBox, ListExpression(*elements))
+
+    def do_format_times(self, items, evaluation, form):
+        # format_times canonicalizes the form of a product in two ways:
+        # * removes (-1) from the first element of a product, replacing it
+        #   by "Minus[Times[rest]]"
+        # * collect all Rational, Divide and negative power factors,
+        # and reformat the expression as Divide[numerator, denominator]
+        # with numerator and denominator free of these class of factors.
+
+        factors = items.get_sequence()
+
+        if factors[0] is IntegerM1:
+            rest = factors[1:]
+            if len(rest) > 1:
+                result = Expression(
+                    SymbolHoldForm,
+                    Expression(SymbolMinus, Expression(SymbolTimes, *rest)),
+                )
+            else:
+                result = Expression(SymbolHoldForm, Expression(SymbolMinus, rest[0]))
+            return result
+        if factors[0].has_form("Minus", 1):
+            factors[0] = factors[0].elements[0]
+            result = Expression(
+                SymbolHoldForm,
+                Expression(SymbolMinus, Expression(SymbolTimes, *factors)),
+            )
+            return result
 
         def inverse(item):
             if item.has_form("Power", 2) and isinstance(  # noqa
@@ -786,16 +896,24 @@ def inverse(item):
             else:
                 return item
 
-        items = items.get_sequence()
+        # Segretate negative powers
         positive = []
         negative = []
-        for item in items:
+        changed = False
+        for item in factors:
             if (
                 item.has_form("Power", 2)
                 and isinstance(item.elements[1], (Integer, Rational, Real))
                 and item.elements[1].to_sympy() < 0
             ):  # nopep8
                 negative.append(inverse(item))
+            elif isinstance(item, Complex):
+                positive.append(do_format_complex(item, evaluation, SymbolOutputForm))
+            elif item.has_form("Divide", 2):
+                numerator, denominator = item.elements()
+                if numerator is not Integer1:
+                    positive.append(numerator)
+                negative.append(denominator)
             elif isinstance(item, Rational):
                 numerator = item.numerator()
                 if not numerator.sameQ(Integer1):
@@ -803,46 +921,80 @@ def inverse(item):
                 negative.append(item.denominator())
             else:
                 positive.append(item)
-        if positive and positive[0].get_int_value() == -1:
-            del positive[0]
-            minus = True
-        else:
-            minus = False
-        positive = [Expression(SymbolHoldForm, item) for item in positive]
-        negative = [Expression(SymbolHoldForm, item) for item in negative]
-        if positive:
-            positive = create_infix(positive, op, 400, "None")
-        else:
-            positive = Integer1
+
+        # positive = [Expression(SymbolHoldForm, item) for item in positive]
+        # negative = [Expression(SymbolHoldForm, item) for item in negative]
         if negative:
-            negative = create_infix(negative, op, 400, "None")
-            result = Expression(
-                SymbolDivide,
-                Expression(SymbolHoldForm, positive),
-                Expression(SymbolHoldForm, negative),
+            den = (
+                negative[0]
+                if len(negative) == 1
+                else Expression(SymbolTimes, *negative)
             )
+            change_sign = False
+            if positive:
+                if len(positive) == 1:
+                    num = positive[0]
+                else:
+                    first_factor = positive[0]
+                    change_sign = (
+                        isinstance(first_factor, (Integer, Real))
+                        and first_factor.value < 0
+                    )
+                    if change_sign:
+                        # negate first factor
+                        first_factor = -first_factor.to_sympy()
+                        if first_factor == 1:
+                            positive = positive[1:]
+                        else:
+                            positive[0] = from_sympy(first_factor)
+                    num = (
+                        Expression(SymbolTimes, *positive)
+                        if len(positive) > 1
+                        else positive[0]
+                    )
+            else:
+                num = Integer1
+            result = Expression(SymbolHoldForm, Expression(SymbolDivide, num, den))
+            if change_sign:
+                result = Expression(SymbolMinus, result)
+            return result
+        # This happends if there are pure imaginary factors
+        if changed or len(positive) != len(factors):
+            result = Expression(SymbolHoldForm, Expression(SymbolTimes, *positive))
+            return result
+
+        return None
+
+    def eval(self, items, evaluation):
+        "Times[items___]"
+        items = numerify(items, evaluation).get_sequence()
+        return eval_Times(*items)
+
+    def eval_makeboxes_times(self, items, form, evaluation):
+        "MakeBoxes[Times[items__], form:(InputForm|OutputForm|StandardForm|TraditionalForm)]"
+        factors = items.get_sequence()
+        if len(factors) < 2:
+            # Times[] and Times[...] are not evaluated here...
+            return None
+        if form is SymbolInputForm:
+            op = String("*")
         else:
-            result = positive
-        if minus:
-            result = Expression(
-                SymbolMinus, result
-            )  # Expression('PrecedenceForm', result, 481))
-        result = Expression(SymbolHoldForm, result)
+            op = String(" ")
+        result = self.do_eval_makeboxes_times(factors, form, evaluation, op)
         return result
 
-    def format_inputform(self, items, evaluation):
+    def format_times_input(self, items, evaluation):
         "InputForm: Times[items__]"
-        return self.format_times(items, evaluation, op="*")
-
-    def format_standardform(self, items, evaluation):
-        "StandardForm: Times[items__]"
-        return self.format_times(items, evaluation, op=" ")
+        return self.do_format_times(items, evaluation, SymbolInputForm)
 
-    def format_outputform(self, items, evaluation):
+    def format_times_output(self, items, evaluation):
         "OutputForm: Times[items__]"
-        return self.format_times(items, evaluation, op=" ")
+        return self.do_format_times(items, evaluation, SymbolOutputForm)
 
-    def eval(self, items, evaluation):
-        "Times[items___]"
-        items = numerify(items, evaluation).get_sequence()
-        return eval_Times(*items)
+    def format_times_standardform(self, items, evaluation):
+        "StandardForm: Times[items__]"
+        return self.do_format_times(items, evaluation, SymbolStandardForm)
+
+    def format_times_traditionalform(self, items, evaluation):
+        "TraditionalForm: Times[items__]"
+        return self.do_format_times(items, evaluation, SymbolStandardForm)
diff --git a/mathics/builtin/arithmetic.py b/mathics/builtin/arithmetic.py
index 19b2c3393..25b838907 100644
--- a/mathics/builtin/arithmetic.py
+++ b/mathics/builtin/arithmetic.py
@@ -524,7 +524,8 @@ class DirectedInfinity(SympyFunction):
 
     formats = {
         "DirectedInfinity[1]": "HoldForm[Infinity]",
-        "DirectedInfinity[-1]": "HoldForm[-Infinity]",
+        "DirectedInfinity[-1]": "HoldForm[Minus[Infinity]]",
+        "DirectedInfinity[-I]": "HoldForm[Minus[Infinity] I]",
         "DirectedInfinity[]": "HoldForm[ComplexInfinity]",
         "DirectedInfinity[DirectedInfinity[z_]]": "DirectedInfinity[z]",
         "DirectedInfinity[z_?NumericQ]": "HoldForm[z Infinity]",
diff --git a/mathics/builtin/forms/output.py b/mathics/builtin/forms/output.py
index 76f05d1e3..e9e28d93f 100644
--- a/mathics/builtin/forms/output.py
+++ b/mathics/builtin/forms/output.py
@@ -1060,9 +1060,9 @@ class MatrixForm(TableForm):
 
     ## Issue #182
     #> {{2*a, 0},{0,0}}//MatrixForm
-     = 2 ⁢ a   0
+     = 2 a   0
      .
-     . 0       0
+     . 0     0
     """
 
     in_outputforms = True
diff --git a/mathics/builtin/layout.py b/mathics/builtin/layout.py
index 0397b09b3..4fca006d1 100644
--- a/mathics/builtin/layout.py
+++ b/mathics/builtin/layout.py
@@ -500,7 +500,7 @@ class Superscript(Builtin):
 
     summary_text = "format an expression with a superscript"
     rules = {
-        "MakeBoxes[Superscript[x_, y_], f:StandardForm|TraditionalForm]": (
+        "MakeBoxes[Superscript[x_, y_], f:OutputForm|StandardForm|TraditionalForm]": (
             "SuperscriptBox[MakeBoxes[x, f], MakeBoxes[y, f]]"
         )
     }
diff --git a/mathics/builtin/numbers/calculus.py b/mathics/builtin/numbers/calculus.py
index 3287d35c7..cae92c715 100644
--- a/mathics/builtin/numbers/calculus.py
+++ b/mathics/builtin/numbers/calculus.py
@@ -1714,7 +1714,7 @@ class Series(Builtin):
     >> Clear[s];
     We can also expand over multiple variables
     >> Series[Exp[x-y], {x, 0, 2}, {y, 0, 2}]
-     = (1 - y + 1 / 2 y ^ 2 + O[y] ^ 3) + (1 - y + 1 / 2 y ^ 2 + O[y] ^ 3) x + (1 / 2 + (-1 / 2) y + 1 / 4 y ^ 2 + O[y] ^ 3) x ^ 2 + O[x] ^ 3
+     = 1 - y + 1 / 2 y ^ 2 + O[y] ^ 3 + (1 - y + 1 / 2 y ^ 2 + O[y] ^ 3) x + (1 / 2 - 1 / 2 y + 1 / 4 y ^ 2 + O[y] ^ 3) x ^ 2 + O[x] ^ 3
 
     """
 
@@ -2097,7 +2097,7 @@ def pre_makeboxes(self, x, x0, data, nmin, nmax, den, form, evaluation: Evaluati
             Expression(SymbolPlus, *expansion),
             Expression(SymbolPower, Expression(SymbolO, variable), powers[-1]),
         )
-        return Expression(SymbolInfix, expansion, String("+"), Integer(300), SymbolLeft)
+        return Expression(SymbolInfix, expansion, String("+"), Integer(299), SymbolLeft)
 
     def eval_makeboxes(
         self,
diff --git a/mathics/builtin/quantum_mechanics/angular.py b/mathics/builtin/quantum_mechanics/angular.py
index b063c7081..36505aa7d 100644
--- a/mathics/builtin/quantum_mechanics/angular.py
+++ b/mathics/builtin/quantum_mechanics/angular.py
@@ -107,7 +107,7 @@ class PauliMatrix(SympyFunction):
      = True
 
     >> MatrixExp[I \[Phi]/2 PauliMatrix[3]]
-     = {{E ^ (I / 2 ϕ), 0}, {0, E ^ ((-I / 2) ϕ)}}
+     = {{E ^ (I / 2 ϕ), 0}, {0, E ^ (-I / 2 ϕ)}}
 
     >> % /. \[Phi] -> 2 Pi
      = {{-1, 0}, {0, -1}}
diff --git a/mathics/builtin/statistics/orderstats.py b/mathics/builtin/statistics/orderstats.py
index 5a8f63f0c..dae8be89c 100644
--- a/mathics/builtin/statistics/orderstats.py
+++ b/mathics/builtin/statistics/orderstats.py
@@ -263,7 +263,7 @@ class Sort(Builtin):
     Sort uses 'OrderedQ' to determine ordering by default.
     You can sort patterns according to their precedence using 'PatternsOrderedQ':
     >> Sort[{items___, item_, OptionsPattern[], item_symbol, item_?test}, PatternsOrderedQ]
-     = {item_symbol, item_ ? test, item_, items___, OptionsPattern[]}
+     = {item_symbol, (item_) ? test, item_, items___, OptionsPattern[]}
 
     When sorting patterns, values of atoms do not matter:
     >> Sort[{a, b/;t}, PatternsOrderedQ]
diff --git a/mathics/core/convert/sympy.py b/mathics/core/convert/sympy.py
index 843d679f3..bb7bfe887 100644
--- a/mathics/core/convert/sympy.py
+++ b/mathics/core/convert/sympy.py
@@ -463,7 +463,10 @@ def old_from_sympy(expr) -> BaseElement:
                     else:
                         factors.append(Expression(SymbolPower, slot, from_sympy(exp)))
             if factors:
-                result.append(Expression(SymbolTimes, *factors))
+                if len(factors) == 1:
+                    result.append(factors[0])
+                else:
+                    result.append(Expression(SymbolTimes, *factors))
             else:
                 result.append(Integer1)
         return Expression(SymbolFunction, Expression(SymbolPlus, *result))
diff --git a/mathics/core/systemsymbols.py b/mathics/core/systemsymbols.py
index fa388a92d..c3f1f756e 100644
--- a/mathics/core/systemsymbols.py
+++ b/mathics/core/systemsymbols.py
@@ -190,6 +190,7 @@
 SymbolPolygon = Symbol("System`Polygon")
 SymbolPossibleZeroQ = Symbol("System`PossibleZeroQ")
 SymbolPrecision = Symbol("System`Precision")
+SymbolPrecedenceForm = Symbol("System`PrecedenceForm")
 SymbolQuantity = Symbol("System`Quantity")
 SymbolQuiet = Symbol("System`Quiet")
 SymbolQuotient = Symbol("System`Quotient")
diff --git a/mathics/doc/documentation/1-Manual.mdoc b/mathics/doc/documentation/1-Manual.mdoc
index 9b3477920..6703c13e6 100644
--- a/mathics/doc/documentation/1-Manual.mdoc
+++ b/mathics/doc/documentation/1-Manual.mdoc
@@ -1186,15 +1186,15 @@ A dice object shall be displayed as a rectangle with the given number of points
   #> Definition[Dice]
     = Attributes[Dice] = {Orderless}
    .
-   . Format[Dice[n_Integer ? (1 <= #1 <= 6&)], MathMLForm] = Block[{p = 0.2, r = 0.05}, Graphics[{EdgeForm[Black], White, Rectangle[], Black, EdgeForm[], If[OddQ[n], Disk[{0.5, 0.5}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{p, p}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{Plus[1, Times[-1, p]], Plus[1, Times[-1, p]]}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{p, Plus[1, Times[-1, p]]}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{Plus[1, Times[-1, p]], p}, r]], If[n === 6, {Disk[{p, 0.5}, r], Disk[{Plus[1, Times[-1, p]], 0.5}, r]}]}, ImageSize -> Tiny]]
+   . Format[Dice[(n_Integer) ? (1 <= #1 <= 6&)], MathMLForm] = Block[{p = 0.2, r = 0.05}, Graphics[{EdgeForm[Black], White, Rectangle[], Black, EdgeForm[], If[OddQ[n], Disk[{0.5, 0.5}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{p, p}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{1 - 1*p, 1 - 1*p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{p, 1 - 1*p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{1 - 1*p, p}, r]], If[n === 6, {Disk[{p, 0.5}, r], Disk[{1 - 1*p, 0.5}, r]}]}, ImageSize -> Tiny]]
    .
-   . Format[Dice[n_Integer ? (1 <= #1 <= 6&)], OutputForm] = Block[{p = 0.2, r = 0.05}, Graphics[{EdgeForm[Black], White, Rectangle[], Black, EdgeForm[], If[OddQ[n], Disk[{0.5, 0.5}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{p, p}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{Plus[1, Times[-1, p]], Plus[1, Times[-1, p]]}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{p, Plus[1, Times[-1, p]]}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{Plus[1, Times[-1, p]], p}, r]], If[n === 6, {Disk[{p, 0.5}, r], Disk[{Plus[1, Times[-1, p]], 0.5}, r]}]}, ImageSize -> Tiny]]
+   . Format[Dice[(n_Integer) ? (1 <= #1 <= 6&)], OutputForm] = Block[{p = 0.2, r = 0.05}, Graphics[{EdgeForm[Black], White, Rectangle[], Black, EdgeForm[], If[OddQ[n], Disk[{0.5, 0.5}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{p, p}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{1 - 1*p, 1 - 1*p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{p, 1 - 1*p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{1 - 1*p, p}, r]], If[n === 6, {Disk[{p, 0.5}, r], Disk[{1 - 1*p, 0.5}, r]}]}, ImageSize -> Tiny]]
    .
-   . Format[Dice[n_Integer ? (1 <= #1 <= 6&)], StandardForm] = Block[{p = 0.2, r = 0.05}, Graphics[{EdgeForm[Black], White, Rectangle[], Black, EdgeForm[], If[OddQ[n], Disk[{0.5, 0.5}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{p, p}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{Plus[1, Times[-1, p]], Plus[1, Times[-1, p]]}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{p, Plus[1, Times[-1, p]]}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{Plus[1, Times[-1, p]], p}, r]], If[n === 6, {Disk[{p, 0.5}, r], Disk[{Plus[1, Times[-1, p]], 0.5}, r]}]}, ImageSize -> Tiny]]
+   . Format[Dice[(n_Integer) ? (1 <= #1 <= 6&)], StandardForm] = Block[{p = 0.2, r = 0.05}, Graphics[{EdgeForm[Black], White, Rectangle[], Black, EdgeForm[], If[OddQ[n], Disk[{0.5, 0.5}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{p, p}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{1 - 1*p, 1 - 1*p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{p, 1 - 1*p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{1 - 1*p, p}, r]], If[n === 6, {Disk[{p, 0.5}, r], Disk[{1 - 1*p, 0.5}, r]}]}, ImageSize -> Tiny]]
    .
-   . Format[Dice[n_Integer ? (1 <= #1 <= 6&)], TeXForm] = Block[{p = 0.2, r = 0.05}, Graphics[{EdgeForm[Black], White, Rectangle[], Black, EdgeForm[], If[OddQ[n], Disk[{0.5, 0.5}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{p, p}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{Plus[1, Times[-1, p]], Plus[1, Times[-1, p]]}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{p, Plus[1, Times[-1, p]]}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{Plus[1, Times[-1, p]], p}, r]], If[n === 6, {Disk[{p, 0.5}, r], Disk[{Plus[1, Times[-1, p]], 0.5}, r]}]}, ImageSize -> Tiny]]
+   . Format[Dice[(n_Integer) ? (1 <= #1 <= 6&)], TeXForm] = Block[{p = 0.2, r = 0.05}, Graphics[{EdgeForm[Black], White, Rectangle[], Black, EdgeForm[], If[OddQ[n], Disk[{0.5, 0.5}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{p, p}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{1 - 1*p, 1 - 1*p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{p, 1 - 1*p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{1 - 1*p, p}, r]], If[n === 6, {Disk[{p, 0.5}, r], Disk[{1 - 1*p, 0.5}, r]}]}, ImageSize -> Tiny]]
    .
-   . Format[Dice[n_Integer ? (1 <= #1 <= 6&)], TraditionalForm] = Block[{p = 0.2, r = 0.05}, Graphics[{EdgeForm[Black], White, Rectangle[], Black, EdgeForm[], If[OddQ[n], Disk[{0.5, 0.5}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{p, p}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{Plus[1, Times[-1, p]], Plus[1, Times[-1, p]]}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{p, Plus[1, Times[-1, p]]}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{Plus[1, Times[-1, p]], p}, r]], If[n === 6, {Disk[{p, 0.5}, r], Disk[{Plus[1, Times[-1, p]], 0.5}, r]}]}, ImageSize -> Tiny]]
+   . Format[Dice[(n_Integer) ? (1 <= #1 <= 6&)], TraditionalForm] = Block[{p = 0.2, r = 0.05}, Graphics[{EdgeForm[Black], White, Rectangle[], Black, EdgeForm[], If[OddQ[n], Disk[{0.5, 0.5}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{p, p}, r]], If[MemberQ[{2, 3, 4, 5, 6}, n], Disk[{1 - 1*p, 1 - 1*p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{p, 1 - 1*p}, r]], If[MemberQ[{4, 5, 6}, n], Disk[{1 - 1*p, p}, r]], If[n === 6, {Disk[{p, 0.5}, r], Disk[{1 - 1*p, 0.5}, r]}]}, ImageSize -> Tiny]]
 
 The empty series of dice shall be displayed as an empty dice:
   >> Format[Dice[]] := Graphics[{EdgeForm[Black], White, Rectangle[]}, ImageSize -> Tiny]
diff --git a/mathics/eval/makeboxes.py b/mathics/eval/makeboxes.py
index 5ecd4e65e..df32eb71c 100644
--- a/mathics/eval/makeboxes.py
+++ b/mathics/eval/makeboxes.py
@@ -81,9 +81,7 @@ def eval_fullform_makeboxes(
     return Expression(SymbolMakeBoxes, expr, form).evaluate(evaluation)
 
 
-def eval_makeboxes(
-    self, expr, evaluation: Evaluation, form=SymbolStandardForm
-) -> Expression:
+def eval_makeboxes(expr, evaluation: Evaluation, form=SymbolStandardForm) -> Expression:
     """
     This function takes the definitions provided by the evaluation
     object, and produces a boxed fullform for expr.
@@ -334,13 +332,21 @@ def parenthesize(
     elif element.has_form("PrecedenceForm", 2):
         element_prec = element.elements[1].value
     # If "element" is a negative number, we need to parenthesize the number. (Fixes #332)
-    elif isinstance(element, (Integer, Real)) and element.value < 0:
-        # Force parenthesis by adjusting the surrounding context's precedence value,
-        # We can't change the precedence for the number since it, doesn't
-        # have a precedence value.
-        element_prec = precedence
+    elif isinstance(element, (Integer, Real)):
+        if element.value < 0:
+            # Force parenthesis by adjusting the surrounding context's precedence value,
+            # We can't change the precedence for the number since it, doesn't
+            # have a precedence value.
+            element_prec = 480
+        else:
+            element_prec = 999
+            when_equal = False
+    elif isinstance(element, Symbol):
+        precedence = precedence
+        element_prec = 999
+        when_equal = False
     else:
-        element_prec = builtins_precedence.get(element.get_head())
+        element_prec = builtins_precedence.get(element.get_head(), 670)
     if precedence is not None and element_prec is not None:
         if precedence > element_prec or (precedence == element_prec and when_equal):
             return Expression(
diff --git a/mathics/format/latex.py b/mathics/format/latex.py
index ac074bbbb..47a662d58 100644
--- a/mathics/format/latex.py
+++ b/mathics/format/latex.py
@@ -146,9 +146,31 @@ def fractionbox(self, **options) -> str:
     _options = self.box_options.copy()
     _options.update(options)
     options = _options
+
+    # This removes the auxiliar parentheses in
+    # numerator and denominator if they are not
+    # needed. See comment in `mathics.builtin.arithfns.basic.Divide`
+
+    def remove_trivial_parentheses(x):
+        if not isinstance(x, RowBox):
+            return x
+        elements = x.elements
+        if len(elements) != 3:
+            return x
+        left, center, right = elements
+        if not (isinstance(left, String) and isinstance(right, String)):
+            return x
+        open_p, close_p = elements[0].value, elements[-1].value
+        if open_p == "(" and close_p == ")":
+            return center
+        return x
+
+    num = remove_trivial_parentheses(self.num)
+    den = remove_trivial_parentheses(self.den)
+
     return "\\frac{%s}{%s}" % (
-        lookup_conversion_method(self.num, "latex")(self.num, **options),
-        lookup_conversion_method(self.den, "latex")(self.den, **options),
+        lookup_conversion_method(num, "latex")(num, **options),
+        lookup_conversion_method(den, "latex")(den, **options),
     )
 
 
diff --git a/mathics/format/mathml.py b/mathics/format/mathml.py
index c3ffbd010..e7478f45d 100644
--- a/mathics/format/mathml.py
+++ b/mathics/format/mathml.py
@@ -114,9 +114,30 @@ def fractionbox(self, **options) -> str:
     _options = self.box_options.copy()
     _options.update(options)
     options = _options
+
+    # This removes the auxiliar parentheses in
+    # numerator and denominator if they are not
+    # needed. See comment in `mathics.builtin.arithfns.basic.Divide`
+    def remove_trivial_parentheses(x):
+        if not isinstance(x, RowBox):
+            return x
+        elements = x.elements
+        if len(elements) != 3:
+            return x
+        left, center, right = elements
+        if not (isinstance(left, String) and isinstance(right, String)):
+            return x
+        open_p, close_p = elements[0].value, elements[-1].value
+        if open_p == "(" and close_p == ")":
+            return center
+        return x
+
+    num = remove_trivial_parentheses(self.num)
+    den = remove_trivial_parentheses(self.den)
+
     return "<mfrac>%s %s</mfrac>" % (
-        lookup_conversion_method(self.num, "mathml")(self.num, **options),
-        lookup_conversion_method(self.den, "mathml")(self.den, **options),
+        lookup_conversion_method(num, "mathml")(num, **options),
+        lookup_conversion_method(den, "mathml")(den, **options),
     )
 
 
diff --git a/mathics/format/text.py b/mathics/format/text.py
index 3d4be51e4..3ac897730 100644
--- a/mathics/format/text.py
+++ b/mathics/format/text.py
@@ -47,9 +47,9 @@ def fractionbox(self, **options) -> str:
     num_text = boxes_to_text(self.num, **options)
     den_text = boxes_to_text(self.den, **options)
     if isinstance(self.num, RowBox):
-        num_text = f"({num_text})"
+        num_text = f"{num_text}"
     if isinstance(self.den, RowBox):
-        den_text = f"({den_text})"
+        den_text = f"{den_text}"
 
     return " / ".join([num_text, den_text])
 
diff --git a/test/builtin/arithmetic/test_basic.py b/test/builtin/arithmetic/test_basic.py
index d99b0b9dc..6d8dc81fa 100644
--- a/test/builtin/arithmetic/test_basic.py
+++ b/test/builtin/arithmetic/test_basic.py
@@ -131,23 +131,25 @@ def test_multiply(str_expr, str_expected, msg):
     [
         (
             "a  b  DirectedInfinity[1. + 2. I]",
-            "a b ((0.447214 + 0.894427 I) Infinity)",
+            "a b (0.447214 + 0.894427 I) Infinity",
             "symbols times floating point complex directed infinity",
         ),
-        ("a  b  DirectedInfinity[I]", "a b (I Infinity)", ""),
+        ("a  b  DirectedInfinity[I]", "a b I Infinity", ""),
         (
             "a  b (-1 + 2 I) Infinity",
-            "a b ((-1 / 5 + 2 I / 5) Sqrt[5] Infinity)",
+            "a b (-1 / 5 + 2 I / 5) Sqrt[5] Infinity",
             "symbols times algebraic exact factor times infinity",
         ),
         (
             "a  b (-1 + 2 Pi I) Infinity",
-            "a b (Infinity (-1 + 2 I Pi) / Sqrt[1 + 4 Pi ^ 2])",
+            "a b (-0.157177 + 0.98757 I) Infinity",
+            # TODO: Improve handling irrational directions
+            # "a b Infinity (-1 + 2 I Pi) / Sqrt[1 + 4 Pi ^ 2]",
             "complex irrational exact",
         ),
         (
             "a  b  DirectedInfinity[(1 + 2 I)/ Sqrt[5]]",
-            "a b ((1 / 5 + 2 I / 5) Sqrt[5] Infinity)",
+            "a b (1 / 5 + 2 I / 5) Sqrt[5] Infinity",
             "symbols times algebraic complex directed infinity",
         ),
         ("a  b  DirectedInfinity[q]", "a b (q Infinity)", ""),
diff --git a/test/format/test_format.py b/test/format/test_format.py
index 161ebc5df..58574d224 100644
--- a/test/format/test_format.py
+++ b/test/format/test_format.py
@@ -411,13 +411,13 @@
             "System`StandardForm": "<msup><mi>a</mi> <mfrac><mi>b</mi> <mi>c</mi></mfrac></msup>",
             "System`TraditionalForm": "<msup><mi>a</mi> <mfrac><mi>b</mi> <mi>c</mi></mfrac></msup>",
             "System`InputForm": "<mrow><mi>a</mi> <mo>^</mo> <mrow><mo>(</mo> <mrow><mi>b</mi> <mtext>&nbsp;/&nbsp;</mtext> <mi>c</mi></mrow> <mo>)</mo></mrow></mrow>",
-            "System`OutputForm": "<mrow><mi>a</mi> <mtext>&nbsp;^&nbsp;</mtext> <mrow><mo>(</mo> <mrow><mi>b</mi> <mtext>&nbsp;/&nbsp;</mtext> <mi>c</mi></mrow> <mo>)</mo></mrow></mrow>",
+            "System`OutputForm": r"<mrow><mi>a</mi> <mtext>&nbsp;^&nbsp;</mtext> <mrow><mo>(</mo> <mfrac><mi>b</mi> <mi>c</mi></mfrac> <mo>)</mo></mrow></mrow>",
         },
         "latex": {
             "System`StandardForm": "a^{\\frac{b}{c}}",
             "System`TraditionalForm": "a^{\\frac{b}{c}}",
             "System`InputForm": "a{}^{\\wedge}\\left(b\\text{ / }c\\right)",
-            "System`OutputForm": "a\\text{ ${}^{\\wedge}$ }\\left(b\\text{ / }c\\right)",
+            "System`OutputForm": "a\\text{ ${}^{\\wedge}$ }\\left(\\frac{b}{c}\\right)",
         },
     },
     "1/(1+1/(1+1/a))": {
@@ -442,7 +442,12 @@
                 "Fragile!",
             ),
             "System`OutputForm": (
-                "<mrow><mn>1</mn> <mtext>&nbsp;/&nbsp;</mtext> <mrow><mo>(</mo> <mrow><mn>1</mn> <mtext>&nbsp;+&nbsp;</mtext> <mrow><mn>1</mn> <mtext>&nbsp;/&nbsp;</mtext> <mrow><mo>(</mo> <mrow><mn>1</mn> <mtext>&nbsp;+&nbsp;</mtext> <mrow><mn>1</mn> <mtext>&nbsp;/&nbsp;</mtext> <mi>a</mi></mrow></mrow> <mo>)</mo></mrow></mrow></mrow> <mo>)</mo></mrow></mrow>",
+                (
+                    r"<mfrac><mn>1</mn> <mrow><mn>1</mn> <mtext>&nbsp;+&nbsp;</mtext> "
+                    r"<mfrac><mn>1</mn> <mrow><mn>1</mn> <mtext>&nbsp;+&nbsp;</mtext> "
+                    r"<mfrac><mn>1</mn> <mi>a</mi></mfrac></mrow></mfrac></mrow>"
+                    r"</mfrac>"
+                ),
                 "Fragile!",
             ),
         },
@@ -450,7 +455,7 @@
             "System`StandardForm": "\\frac{1}{1+\\frac{1}{1+\\frac{1}{a}}}",
             "System`TraditionalForm": "\\frac{1}{1+\\frac{1}{1+\\frac{1}{a}}}",
             "System`InputForm": "1\\text{ / }\\left(1\\text{ + }1\\text{ / }\\left(1\\text{ + }1\\text{ / }a\\right)\\right)",
-            "System`OutputForm": "1\\text{ / }\\left(1\\text{ + }1\\text{ / }\\left(1\\text{ + }1\\text{ / }a\\right)\\right)",
+            "System`OutputForm": r"\frac{1}{1\text{ + }\frac{1}{1\text{ + }\frac{1}{a}}}",
         },
     },
     "Sqrt[1/(1+1/(1+1/a))]": {
@@ -475,7 +480,11 @@
                 "Fragile!",
             ),
             "System`OutputForm": (
-                "<mrow><mi>Sqrt</mi> <mo>[</mo> <mrow><mn>1</mn> <mtext>&nbsp;/&nbsp;</mtext> <mrow><mo>(</mo> <mrow><mn>1</mn> <mtext>&nbsp;+&nbsp;</mtext> <mrow><mn>1</mn> <mtext>&nbsp;/&nbsp;</mtext> <mrow><mo>(</mo> <mrow><mn>1</mn> <mtext>&nbsp;+&nbsp;</mtext> <mrow><mn>1</mn> <mtext>&nbsp;/&nbsp;</mtext> <mi>a</mi></mrow></mrow> <mo>)</mo></mrow></mrow></mrow> <mo>)</mo></mrow></mrow> <mo>]</mo></mrow>",
+                (
+                    r"<mrow><mi>Sqrt</mi> <mo>[</mo> <mfrac><mn>1</mn> <mrow><mn>1</mn> <mtext>&nbsp;+&nbsp;</mtext> "
+                    r"<mfrac><mn>1</mn> <mrow><mn>1</mn> <mtext>&nbsp;+&nbsp;</mtext> <mfrac><mn>1</mn> <mi>a</mi>"
+                    r"</mfrac></mrow></mfrac></mrow></mfrac> <mo>]</mo></mrow>"
+                ),
                 "Fragile!",
             ),
         },
@@ -483,7 +492,7 @@
             "System`StandardForm": "\\sqrt{\\frac{1}{1+\\frac{1}{1+\\frac{1}{a}}}}",
             "System`TraditionalForm": "\\sqrt{\\frac{1}{1+\\frac{1}{1+\\frac{1}{a}}}}",
             "System`InputForm": "\\text{Sqrt}\\left[1\\text{ / }\\left(1\\text{ + }1\\text{ / }\\left(1\\text{ + }1\\text{ / }a\\right)\\right)\\right]",
-            "System`OutputForm": "\\text{Sqrt}\\left[1\\text{ / }\\left(1\\text{ + }1\\text{ / }\\left(1\\text{ + }1\\text{ / }a\\right)\\right)\\right]",
+            "System`OutputForm": r"\text{Sqrt}\left[\frac{1}{1\text{ + }\frac{1}{1\text{ + }\frac{1}{a}}}\right]",
         },
     },
     # Grids, arrays and matrices

From 26f7f51f71b3c348c03439fe9fcb957c92233843 Mon Sep 17 00:00:00 2001
From: mmatera <matera@fisica.unlp.edu.ar>
Date: Fri, 28 Jul 2023 22:20:19 -0300
Subject: [PATCH 2/2] last comments

---
 mathics/builtin/numbers/calculus.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/mathics/builtin/numbers/calculus.py b/mathics/builtin/numbers/calculus.py
index cae92c715..fbb1c5e7e 100644
--- a/mathics/builtin/numbers/calculus.py
+++ b/mathics/builtin/numbers/calculus.py
@@ -1713,6 +1713,8 @@ class Series(Builtin):
      = 17
     >> Clear[s];
     We can also expand over multiple variables
+    ## TODO: In WMA, the first term is also sorounded by parenthesis. This is
+    ## to fix in another round, after complete the refactor of Infix.
     >> Series[Exp[x-y], {x, 0, 2}, {y, 0, 2}]
      = 1 - y + 1 / 2 y ^ 2 + O[y] ^ 3 + (1 - y + 1 / 2 y ^ 2 + O[y] ^ 3) x + (1 / 2 - 1 / 2 y + 1 / 4 y ^ 2 + O[y] ^ 3) x ^ 2 + O[x] ^ 3