diff --git a/mathics/builtin/arithfns/basic.py b/mathics/builtin/arithfns/basic.py index ca56158fb..91edaaa3e 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 @@ -151,7 +165,7 @@ class Divide(BinaryOperator): default_formats = False formats = { - (("InputForm", "OutputForm"), "Divide[x_, y_]"): ( + ("InputForm", "Divide[x_, y_]"): ( 'Infix[{HoldForm[x], HoldForm[y]}, "/", 400, Left]' ), } @@ -162,13 +176,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): """ @@ -196,16 +245,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 @@ -219,6 +259,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): """ @@ -290,56 +337,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___]" @@ -630,8 +699,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 @@ -645,16 +755,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): @@ -662,46 +780,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 832cab44c..60817cb8a 100644 --- a/mathics/builtin/arithmetic.py +++ b/mathics/builtin/arithmetic.py @@ -476,7 +476,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 dffebb42c..9e41ab1f5 100644 --- a/mathics/builtin/numbers/calculus.py +++ b/mathics/builtin/numbers/calculus.py @@ -1712,8 +1712,10 @@ 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 + = 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 """ @@ -2096,7 +2098,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 9a27b4290..be2efdd3a 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 "%s %s" % ( - 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 1bec99de7..0d3c479be 100644 --- a/test/builtin/arithmetic/test_basic.py +++ b/test/builtin/arithmetic/test_basic.py @@ -137,23 +137,25 @@ def test_multiply(str_expr, str_expected, msg): ("DirectedInfinity[Sqrt[3]]", "Infinity", None), ( "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 cc01d3439..a3792a921 100644 --- a/test/format/test_format.py +++ b/test/format/test_format.py @@ -528,13 +528,13 @@ "System`StandardForm": "a b c", "System`TraditionalForm": "a b c", "System`InputForm": "a ^ ( b  /  c )", - "System`OutputForm": "a  ^  ( b  /  c )", + "System`OutputForm": r"a  ^  ( b c )", }, "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))": { @@ -559,7 +559,12 @@ "Fragile!", ), "System`OutputForm": ( - "1  /  ( 1  +  1  /  ( 1  +  1  /  a ) )", + ( + r"1 1  +  " + r"1 1  +  " + r"1 a" + r"" + ), "Fragile!", ), }, @@ -567,7 +572,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))]": { @@ -592,7 +597,11 @@ "Fragile!", ), "System`OutputForm": ( - "Sqrt [ 1  /  ( 1  +  1  /  ( 1  +  1  /  a ) ) ]", + ( + r"Sqrt [ 1 1  +  " + r"1 1  +  1 a" + r" ]" + ), "Fragile!", ), }, @@ -600,7 +609,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