From 35f6d6158bea0800c53f4be5dd6b7f536e2032ab Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 18 Dec 2022 23:04:47 -0500 Subject: [PATCH 01/12] WIP - separate PredefinedSymbol from Symbol .. PredefinedSymbol acts more like a numeric constant. --- mathics/builtin/makeboxes.py | 5 +++++ mathics/core/symbols.py | 10 +++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/mathics/builtin/makeboxes.py b/mathics/builtin/makeboxes.py index 8dbac5fb7..9a711d5d0 100644 --- a/mathics/builtin/makeboxes.py +++ b/mathics/builtin/makeboxes.py @@ -451,12 +451,17 @@ def eval_postprefix(self, p, expr, h, prec, f, evaluation): else: return MakeBoxes(expr, f).evaluate(evaluation) + # FIXME: prec is sometimes not an Integer for a ListExpession. + # And this is recent with respect to PredefinedSymbol revision. + # How does this get set and why? def eval_infix( self, expr, operator, prec: Integer, grouping, form: Symbol, evaluation ): """MakeBoxes[Infix[expr_, operator_, prec_:None, grouping_:None], form:StandardForm|TraditionalForm|OutputForm|InputForm]""" + assert isinstance(prec, Integer) + ## FIXME: this should go into a some formatter. def format_operator(operator) -> Union[String, BaseElement]: """ diff --git a/mathics/core/symbols.py b/mathics/core/symbols.py index db8b9f9a4..1f2e8c34e 100644 --- a/mathics/core/symbols.py +++ b/mathics/core/symbols.py @@ -329,19 +329,19 @@ def replace_slots(self, slots, evaluation) -> "Atom": class Symbol(Atom, NumericOperators, EvalMixin): - """A Symbol is a kind of Atom that acts as a symbolic variable. + """ + A Symbol is a kind of Atom that acts as a symbolic variable. All Symbols have a name that can be converted to string. - A Variable Symbol is a ``Symbol`` that is associated with a - ``Definition`` that has an ``OwnValue`` that determines its - evaluation value. + A Variable Symbol is a ``Symbol``` that is associated with a ``Definition`` + that determines its evaluation value. A Function Symbol, like a Variable Symbol, is a ``Symbol`` that is also associated with a ``Definition``. But it has a ``DownValue`` that is used in its evaluation. - We also have Symbols which in contrast to Variables Symbols have + We also have Symbols which, in contrast to Variables Symbols, have a constant value that cannot change. System`True and System`False are like this. From 4d49f6bb4830650c077d61bf04867e8ab4e0600b Mon Sep 17 00:00:00 2001 From: rocky Date: Mon, 19 Dec 2022 18:35:06 -0500 Subject: [PATCH 02/12] Clarify Symbol as per mmatera Update builtin/atomic/symbols.py according to current standards. --- mathics/core/symbols.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/mathics/core/symbols.py b/mathics/core/symbols.py index 1f2e8c34e..6e32d0dac 100644 --- a/mathics/core/symbols.py +++ b/mathics/core/symbols.py @@ -329,13 +329,17 @@ def replace_slots(self, slots, evaluation) -> "Atom": class Symbol(Atom, NumericOperators, EvalMixin): - """ - A Symbol is a kind of Atom that acts as a symbolic variable. + """A Symbol is a kind of Atom that acts as a symbolic variable. All Symbols have a name that can be converted to string. - A Variable Symbol is a ``Symbol``` that is associated with a ``Definition`` - that determines its evaluation value. + A Variable Symbol is a ``Symbol``` that is associated with a + ``Definition`` that has an ``OwnValue`` that determines its + evaluation value. + + A Function Symbol, like a Variable Symbol, is a ``Symbol`` that is + also associated with a ``Definition``. But it has a ``DownValue`` + that is used in its evaluation. A Function Symbol, like a Variable Symbol, is a ``Symbol`` that is also associated with a ``Definition``. But it has a ``DownValue`` From 21a81adfb111b23fb83104e9c251ab905ef05fbf Mon Sep 17 00:00:00 2001 From: mmatera Date: Mon, 26 Dec 2022 01:08:39 -0300 Subject: [PATCH 03/12] fix MakeBoxes[Infix[...]] --- mathics/builtin/layout.py | 1 + mathics/builtin/makeboxes.py | 44 +++++++++++++++++++++++++++++++----- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/mathics/builtin/layout.py b/mathics/builtin/layout.py index 26419763f..7ebc8d0d8 100644 --- a/mathics/builtin/layout.py +++ b/mathics/builtin/layout.py @@ -146,6 +146,7 @@ class Infix(Builtin): messages = { "normal": "Nonatomic expression expected at position `1`", + "intm": "Machine-sized integer expected at position 3 in `1`", } summary_text = "infix form" diff --git a/mathics/builtin/makeboxes.py b/mathics/builtin/makeboxes.py index 9a711d5d0..4dcf5007e 100644 --- a/mathics/builtin/makeboxes.py +++ b/mathics/builtin/makeboxes.py @@ -19,7 +19,14 @@ from mathics.core.list import ListExpression from mathics.core.number import dps from mathics.core.symbols import Atom, Symbol -from mathics.core.systemsymbols import SymbolInputForm, SymbolOutputForm, SymbolRowBox +from mathics.core.systemsymbols import ( + SymbolFullForm, + SymbolInfix, + SymbolInputForm, + SymbolNone, + SymbolOutputForm, + SymbolRowBox, +) from mathics.eval.makeboxes import _boxed_string, format_element @@ -455,12 +462,33 @@ def eval_postprefix(self, p, expr, h, prec, f, evaluation): # And this is recent with respect to PredefinedSymbol revision. # How does this get set and why? def eval_infix( - self, expr, operator, prec: Integer, grouping, form: Symbol, evaluation + self, expr, operator, precedence: Integer, grouping, form: Symbol, evaluation ): - """MakeBoxes[Infix[expr_, operator_, prec_:None, grouping_:None], + """MakeBoxes[Infix[expr_, operator_, precedence_:None, grouping_:None], form:StandardForm|TraditionalForm|OutputForm|InputForm]""" - assert isinstance(prec, Integer) + # In WMA, this is covered with two different rules: + # * ```MakeBoxes[Infix[expr_, operator_]``` + # * ```MakeBoxes[Infix[expr_, operator_, precedence_, grouping_:None]``` + # In the second form, precedence must be an Integer. Otherwise, a message is + # shown and fall back to the standard MakeBoxes form. + # Here we allow Precedence to be ```System`None```, to have just one rule. + + if precedence is SymbolNone: + precedence_value = None + elif isinstance(precedence, Integer): + precedence_value = precedence.value + else: + if grouping is not SymbolNone: + # Here I use a String as a head to avoid a circular evaluation. + # TODO: Use SymbolInfix when the MakeBoxes refactor be done. + expr = Expression(String("Infix"), expr, operator, precedence, grouping) + else: + expr = Expression(String("Infix"), expr, operator, precedence) + evaluation.message("Infix", "intm", expr) + return self.eval_general(expr, form, evaluation) + + grouping = grouping.get_name() ## FIXME: this should go into a some formatter. def format_operator(operator) -> Union[String, BaseElement]: @@ -501,7 +529,9 @@ def format_operator(operator) -> Union[String, BaseElement]: if len(elements) > 1: if operator.has_form("List", len(elements) - 1): operator = [format_operator(op) for op in operator.elements] - return make_boxes_infix(elements, operator, precedence, grouping, form) + return make_boxes_infix( + elements, operator, precedence_value, grouping, form + ) else: encoding_rule = evaluation.definitions.get_ownvalue( "$CharacterEncoding" @@ -523,7 +553,9 @@ def format_operator(operator) -> Union[String, BaseElement]: String(operator_to_unicode.get(op_str, op_str)) ) - return make_boxes_infix(elements, operator, precedence, grouping, form) + return make_boxes_infix( + elements, operator, precedence_value, grouping, form + ) elif len(elements) == 1: return MakeBoxes(elements[0], form) From e8ca60a6b16ffdcfd5993c21f09dba8dd703957d Mon Sep 17 00:00:00 2001 From: mmatera Date: Mon, 26 Dec 2022 13:14:44 -0300 Subject: [PATCH 04/12] MakeBoxes[Infix[...],form]-> Infix --- mathics/builtin/colors/color_operations.py | 2 +- mathics/builtin/layout.py | 189 ++++++++++++++++++++- mathics/builtin/makeboxes.py | 68 +------- mathics/eval/image.py | 8 +- mathics/eval/makeboxes.py | 70 ++++++-- 5 files changed, 246 insertions(+), 91 deletions(-) diff --git a/mathics/builtin/colors/color_operations.py b/mathics/builtin/colors/color_operations.py index 4de7f4cc6..f372a42b4 100644 --- a/mathics/builtin/colors/color_operations.py +++ b/mathics/builtin/colors/color_operations.py @@ -386,7 +386,7 @@ def eval(self, image, n, prop, evaluation, options): im = ( image.color_convert("RGB") .pil() - .convert("P", palette=PIL.Image.ADAPTIVE, colors=256) + .convert("P", palette=PILImage.ADAPTIVE, colors=256) ) pixels = numpy.array(list(im.getdata())) diff --git a/mathics/builtin/layout.py b/mathics/builtin/layout.py index 7ebc8d0d8..2f02c41cc 100644 --- a/mathics/builtin/layout.py +++ b/mathics/builtin/layout.py @@ -8,7 +8,7 @@ we can use ``Row`` """ - +from typing import Optional, Union from mathics.builtin.base import BinaryOperator, Builtin, Operator from mathics.builtin.box.layout import GridBox, RowBox, to_boxes @@ -18,10 +18,20 @@ from mathics.core.atoms import Real, String from mathics.core.expression import Evaluation, Expression from mathics.core.list import ListExpression -from mathics.core.symbols import Symbol -from mathics.core.systemsymbols import SymbolMakeBoxes -from mathics.eval.makeboxes import format_element - +from mathics.core.symbols import Atom, Symbol +from mathics.core.systemsymbols import ( + SymbolFullForm, + SymbolInfix, + SymbolInputForm, + SymbolLeft, + SymbolMakeBoxes, + SymbolNone, + SymbolOutputForm, + SymbolRight, +) +from mathics.eval.makeboxes import format_element, make_boxes_infix + +SymbolNonAssociative = Symbol("System`NonAssociative") SymbolSubscriptBox = Symbol("System`SubscriptBox") @@ -145,11 +155,178 @@ class Infix(Builtin): """ messages = { - "normal": "Nonatomic expression expected at position `1`", + "argb": "Infix called with `1` arguments; between 1 and 4 arguments are expected.", + "group": "Infix::group: Grouping specification `1` is not NonAssociative, None, Left, or Right.", "intm": "Machine-sized integer expected at position 3 in `1`", + "normal": "Nonatomic expression expected at position `1`", } summary_text = "infix form" + # the right rule should be + # mbexpression:MakeBoxes[Infix[___], form] + + def eval_infix_1(self, expr: Expression, form: Symbol, evaluation): + """MakeBoxes[Infix[expr_], + form:StandardForm|TraditionalForm|OutputForm|InputForm]""" + return self.do_eval_infix(expr, String("~"), form, evaluation) + + def eval_infix_2( + self, expr: Expression, operator: BaseElement, form: Symbol, evaluation + ): + """MakeBoxes[Infix[expr_, operator_], + form:StandardForm|TraditionalForm|OutputForm|InputForm]""" + return self.do_eval_infix(expr, operator, form, evaluation) + + def eval_infix_3( + self, + expr: Expression, + operator: BaseElement, + precedence: BaseElement, + form: Symbol, + evaluation, + ): + """MakeBoxes[Infix[expr_, operator_, precedence_], + form:StandardForm|TraditionalForm|OutputForm|InputForm]""" + + if not isinstance(precedence, Integer): + evaluation.message( + "Infix", + "intm", + Expression( + SymbolFullForm, Expression(SymbolInfix, expr, operator, precedence) + ), + ) + return + + return self.do_eval_infix(expr, operator, form, evaluation, precedence.value) + + def eval_infix_4( + self, + expr: Expression, + operator: BaseElement, + precedence: BaseElement, + grouping: BaseElement, + form: Symbol, + evaluation, + ): + """MakeBoxes[Infix[expr_, operator_, precedence_, grouping_], + form:StandardForm|TraditionalForm|OutputForm|InputForm]""" + if not isinstance(precedence, Integer): + fullform_expr = Expression( + SymbolFullForm, + Expression(SymbolInfix, expr, operator, precedence, grouping), + ) + evaluation.message( + "Infix", + "intm", + Expression( + SymbolFullForm, + Expression(SymbolInfix, expr, operator, precedence, grouping), + ), + ) + return fullform_expr + + if grouping is SymbolNone: + return self.do_eval_infix( + expr, operator, form, evaluation, precedence.value + ) + if grouping in (SymbolNonAssociative, SymbolLeft, SymbolRight): + return self.do_eval_infix( + expr, operator, form, evaluation, precedence.value, grouping.get_name() + ) + + evaluation.message("Infix", "argb", grouping) + return Expression( + SymbolFullForm, + Expression(SymbolInfix, expr, operator, precedence, grouping), + ) + + def eval_infix_default(self, parms, form: Symbol, evaluation): + """MakeBoxes[Infix[parms___], + form:StandardForm|TraditionalForm|OutputForm|InputForm]""" + evaluation.message("Infix", "argb", Integer(len(parms.get_sequence()))) + return None + + def do_eval_infix( + self, + expr: Expression, + operator: BaseElement, + form: Symbol, + evaluation, + precedence_value: Optional[int] = None, + grouping: Optional[str] = None, + ): + """Implements MakeBoxes[Infix[...]]""" + + ## FIXME: this should go into a some formatter. + def format_operator(operator) -> Union[String, BaseElement]: + """ + Format infix operator `operator`. To do this outside parameter form is used. + Sometimes no changes are made and operator is returned unchanged. + + This function probably should be rewritten be more scalable across other forms + and moved to a module that contiaing similar formatting routines. + """ + if not isinstance(operator, String): + return MakeBoxes(operator, form) + + op_str = operator.value + + # FIXME: performing a check using the operator symbol representation feels a bit + # fragile. The operator name seems more straightforward and more robust. + if form == SymbolInputForm and op_str in ["*", "^", " "]: + return operator + elif ( + form in (SymbolInputForm, SymbolOutputForm) + and not op_str.startswith(" ") + and not op_str.endswith(" ") + ): + # FIXME: Again, testing on specific forms is fragile and not scalable. + op = String(" " + op_str + " ") + return op + return operator + + if isinstance(expr, Atom): + evaluation.message("Infix", "normal", Integer1) + return None + + elements = expr.elements + if len(elements) > 1: + if operator.has_form("List", len(elements) - 1): + operator = [format_operator(op) for op in operator.elements] + return make_boxes_infix( + elements, operator, precedence_value, grouping, form + ) + else: + encoding_rule = evaluation.definitions.get_ownvalue( + "$CharacterEncoding" + ) + encoding = ( + "UTF8" if encoding_rule is None else encoding_rule.replace.value + ) + op_str = ( + operator.value + if isinstance(operator, String) + else operator.short_name + ) + if encoding == "ASCII": + operator = format_operator( + String(operator_to_ascii.get(op_str, op_str)) + ) + else: + operator = format_operator( + String(operator_to_unicode.get(op_str, op_str)) + ) + + return make_boxes_infix( + elements, operator, precedence_value, grouping, form + ) + + elif len(elements) == 1: + return MakeBoxes(elements[0], form) + else: + return MakeBoxes(expr, form) + class Left(Builtin): """ diff --git a/mathics/builtin/makeboxes.py b/mathics/builtin/makeboxes.py index 4dcf5007e..18471df78 100644 --- a/mathics/builtin/makeboxes.py +++ b/mathics/builtin/makeboxes.py @@ -11,23 +11,15 @@ from mathics.builtin.base import Builtin, Predefined from mathics.builtin.box.layout import RowBox, to_boxes -from mathics.core.atoms import Integer, Integer1, Real, String +from mathics.core.atoms import Integer, Real, String from mathics.core.attributes import A_HOLD_ALL_COMPLETE, A_READ_PROTECTED -from mathics.core.convert.op import operator_to_ascii, operator_to_unicode -from mathics.core.element import BaseElement, BoxElementMixin +from mathics.core.element import BoxElementMixin from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.number import dps -from mathics.core.symbols import Atom, Symbol -from mathics.core.systemsymbols import ( - SymbolFullForm, - SymbolInfix, - SymbolInputForm, - SymbolNone, - SymbolOutputForm, - SymbolRowBox, -) -from mathics.eval.makeboxes import _boxed_string, format_element +from mathics.core.symbols import Atom +from mathics.core.systemsymbols import SymbolRowBox +from mathics.eval.makeboxes import _boxed_string, format_element, parenthesize def int_to_s_exp(expr, n): @@ -42,56 +34,6 @@ def int_to_s_exp(expr, n): return s, exp, nonnegative -def parenthesize(precedence, element, element_boxes, when_equal): - from mathics.builtin import builtins_precedence - - while element.has_form("HoldForm", 1): - element = element.elements[0] - - if element.has_form(("Infix", "Prefix", "Postfix"), 3, None): - element_prec = element.elements[2].value - elif element.has_form("PrecedenceForm", 2): - element_prec = element.elements[1].value - # For negative values, ensure that the element_precedence is at least the precedence. (Fixes #332) - elif isinstance(element, (Integer, Real)) and element.value < 0: - element_prec = precedence - else: - element_prec = builtins_precedence.get(element.get_head_name()) - if precedence is not None and element_prec is not None: - if precedence > element_prec or (precedence == element_prec and when_equal): - return Expression( - SymbolRowBox, - ListExpression(String("("), element_boxes, String(")")), - ) - return element_boxes - - -# FIXME: op should be a string, so remove the Union. -def make_boxes_infix( - elements, op: Union[String, list], precedence: int, grouping, form: Symbol -): - result = [] - for index, element in enumerate(elements): - if index > 0: - if isinstance(op, list): - result.append(op[index - 1]) - else: - result.append(op) - parenthesized = False - if grouping == "System`NonAssociative": - parenthesized = True - elif grouping == "System`Left" and index > 0: - parenthesized = True - elif grouping == "System`Right" and index == 0: - parenthesized = True - - element_boxes = MakeBoxes(element, form) - element = parenthesize(precedence, element, element_boxes, parenthesized) - - result.append(element) - return Expression(SymbolRowBox, ListExpression(*result)) - - def real_to_s_exp(expr, n): if expr.is_zero: s = "0" diff --git a/mathics/eval/image.py b/mathics/eval/image.py index 3c3d48f3e..11039a59e 100644 --- a/mathics/eval/image.py +++ b/mathics/eval/image.py @@ -37,11 +37,11 @@ } # After Python 3.6 support is dropped, this can be simplified -# to for Pillow 9+ and use PIL.Image.Resampling only. -if hasattr(PIL.Image, "Resampling"): - pil_resize = PIL.Image.Resampling +# to for Pillow 9+ and use PILImage.Resampling only. +if hasattr(PILImage, "Resampling"): + pil_resize = PILImage.Resampling else: - pil_resize = PIL.Image + pil_resize = PILImage # See: # https://pillow.readthedocs.io/en/stable/handbook/concepts.html#filters diff --git a/mathics/eval/makeboxes.py b/mathics/eval/makeboxes.py index 700a6c9d4..dd50608b3 100644 --- a/mathics/eval/makeboxes.py +++ b/mathics/eval/makeboxes.py @@ -9,7 +9,7 @@ import typing from typing import Any, Dict, Type -from mathics.core.atoms import Complex, Integer, Rational, String, SymbolI +from mathics.core.atoms import Complex, Integer, Rational, Real, String, SymbolI from mathics.core.convert.expression import to_expression_with_specialization from mathics.core.definitions import OutputForms from mathics.core.element import BaseElement, BoxElementMixin, EvalMixin @@ -38,6 +38,7 @@ SymbolMinus, SymbolOutputForm, SymbolRational, + SymbolRowBox, SymbolStandardForm, ) @@ -53,30 +54,65 @@ def _boxed_string(string: str, **options): return StyleBox(String(string), **options) -def eval_fullform_makeboxes( - self, expr, evaluation: Evaluation, form=SymbolStandardForm -) -> Expression: - """ - This function takes the definitions provided by the evaluation - object, and produces a boxed form for expr. +def parenthesize(precedence, element, element_boxes, when_equal): + from mathics.builtin import builtins_precedence - Basically: MakeBoxes[expr // FullForm] - """ - # This is going to be reimplemented. - expr = Expression(SymbolFullForm, expr) - return Expression(SymbolMakeBoxes, expr, form).evaluate(evaluation) + while element.has_form("HoldForm", 1): + element = element.elements[0] + if element.has_form(("Infix", "Prefix", "Postfix"), 3, None): + element_prec = element.elements[2].value + elif element.has_form("PrecedenceForm", 2): + element_prec = element.elements[1].value + # For negative values, ensure that the element_precedence is at least the precedence. (Fixes #332) + elif isinstance(element, (Integer, Real)) and element.value < 0: + element_prec = precedence + else: + element_prec = builtins_precedence.get(element.get_head_name()) + if precedence is not None and element_prec is not None: + if precedence > element_prec or (precedence == element_prec and when_equal): + return Expression( + SymbolRowBox, + ListExpression(String("("), element_boxes, String(")")), + ) + return element_boxes + + +# FIXME: op should be a string, so remove the Union. +def make_boxes_infix( + elements, op: Union[String, list], precedence: int, grouping, form: Symbol +): + result = [] + for index, element in enumerate(elements): + if index > 0: + if isinstance(op, list): + result.append(op[index - 1]) + else: + result.append(op) + parenthesized = False + if grouping == "System`NonAssociative": + parenthesized = True + elif grouping == "System`Left" and index > 0: + parenthesized = True + elif grouping == "System`Right" and index == 0: + parenthesized = True + + element_boxes = Expression(SymbolMakeBoxes, element, form) + element = parenthesize(precedence, element, element_boxes, parenthesized) + + result.append(element) + return Expression(SymbolRowBox, ListExpression(*result)) -def eval_makeboxes( - self, expr, evaluation: Evaluation, form=SymbolStandardForm -) -> Expression: + +def eval_makeboxes(self, expr, evaluation, f=SymbolStandardForm) -> Expression: """ This function takes the definitions provided by the evaluation - object, and produces a boxed fullform for expr. + object, and produces a boxed form for expr. - Basically: MakeBoxes[expr // form] + Basically: MakeBoxes[expr // FullForm] """ # This is going to be reimplemented. + expr = Expression(SymbolFullForm, expr) return Expression(SymbolMakeBoxes, expr, form).evaluate(evaluation) From a417f0061246741ea979f860ef4e95308ac1f9d7 Mon Sep 17 00:00:00 2001 From: mmatera Date: Mon, 26 Dec 2022 18:08:37 -0300 Subject: [PATCH 05/12] alternative implementation --- mathics/builtin/layout.py | 93 ++++++++++++--------------------------- mathics/eval/makeboxes.py | 12 ++++- 2 files changed, 38 insertions(+), 67 deletions(-) diff --git a/mathics/builtin/layout.py b/mathics/builtin/layout.py index 2f02c41cc..575554623 100644 --- a/mathics/builtin/layout.py +++ b/mathics/builtin/layout.py @@ -29,7 +29,11 @@ SymbolOutputForm, SymbolRight, ) -from mathics.eval.makeboxes import format_element, make_boxes_infix +from mathics.eval.makeboxes import ( + eval_fullform_makeboxes, + format_element, + make_boxes_infix, +) SymbolNonAssociative = Symbol("System`NonAssociative") SymbolSubscriptBox = Symbol("System`SubscriptBox") @@ -164,68 +168,34 @@ class Infix(Builtin): # the right rule should be # mbexpression:MakeBoxes[Infix[___], form] - - def eval_infix_1(self, expr: Expression, form: Symbol, evaluation): - """MakeBoxes[Infix[expr_], - form:StandardForm|TraditionalForm|OutputForm|InputForm]""" - return self.do_eval_infix(expr, String("~"), form, evaluation) - - def eval_infix_2( - self, expr: Expression, operator: BaseElement, form: Symbol, evaluation - ): - """MakeBoxes[Infix[expr_, operator_], - form:StandardForm|TraditionalForm|OutputForm|InputForm]""" - return self.do_eval_infix(expr, operator, form, evaluation) - - def eval_infix_3( - self, - expr: Expression, - operator: BaseElement, - precedence: BaseElement, - form: Symbol, - evaluation, - ): - """MakeBoxes[Infix[expr_, operator_, precedence_], + def eval_all(self, expression, form, evaluation): + """MakeBoxes[Infix[___], form:StandardForm|TraditionalForm|OutputForm|InputForm]""" - + infix_expr = expression.elements[0] + elements = list(infix_expr.elements) + num_parms = len(elements) + + if num_parms == 0 or num_parms > 4: + evaluation.message("Infix", "argb", Integer(len(parms.get_sequence()))) + return eval_fullform_makeboxes(infix_expr, evaluation, form) + + if num_parms == 1: + expr = elements[0] + return self.do_eval_infix(expr, String("~"), form, evaluation) + if num_parms == 2: + expr, operator = elements + return self.do_eval_infix(expr, operator, form, evaluation) + + expr, operator, precedence = elements[:3] if not isinstance(precedence, Integer): evaluation.message( "Infix", "intm", - Expression( - SymbolFullForm, Expression(SymbolInfix, expr, operator, precedence) - ), + Expression(SymbolFullForm, infix_expr), ) - return - - return self.do_eval_infix(expr, operator, form, evaluation, precedence.value) - - def eval_infix_4( - self, - expr: Expression, - operator: BaseElement, - precedence: BaseElement, - grouping: BaseElement, - form: Symbol, - evaluation, - ): - """MakeBoxes[Infix[expr_, operator_, precedence_, grouping_], - form:StandardForm|TraditionalForm|OutputForm|InputForm]""" - if not isinstance(precedence, Integer): - fullform_expr = Expression( - SymbolFullForm, - Expression(SymbolInfix, expr, operator, precedence, grouping), - ) - evaluation.message( - "Infix", - "intm", - Expression( - SymbolFullForm, - Expression(SymbolInfix, expr, operator, precedence, grouping), - ), - ) - return fullform_expr + return eval_fullform_makeboxes(infix_expr, evaluation, form) + grouping = SymbolNone if num_parms < 4 else elements[3] if grouping is SymbolNone: return self.do_eval_infix( expr, operator, form, evaluation, precedence.value @@ -236,16 +206,7 @@ def eval_infix_4( ) evaluation.message("Infix", "argb", grouping) - return Expression( - SymbolFullForm, - Expression(SymbolInfix, expr, operator, precedence, grouping), - ) - - def eval_infix_default(self, parms, form: Symbol, evaluation): - """MakeBoxes[Infix[parms___], - form:StandardForm|TraditionalForm|OutputForm|InputForm]""" - evaluation.message("Infix", "argb", Integer(len(parms.get_sequence()))) - return None + return eval_fullform_makeboxes(infix_expr, evaluation, form) def do_eval_infix( self, diff --git a/mathics/eval/makeboxes.py b/mathics/eval/makeboxes.py index dd50608b3..d454c39d3 100644 --- a/mathics/eval/makeboxes.py +++ b/mathics/eval/makeboxes.py @@ -104,7 +104,17 @@ def make_boxes_infix( return Expression(SymbolRowBox, ListExpression(*result)) -def eval_makeboxes(self, expr, evaluation, f=SymbolStandardForm) -> Expression: +def eval_fullform_makeboxes(self, expr, evaluation, form=SymbolStandardForm): + """ + This function takes the definitions provided by the evaluation + object, and produces a boxed form for expr. + """ + # This is going to be reimplemented. + expr = Expression(SymbolFullForm, expr) + return Expression(SymbolMakeBoxes, expr, form).evaluate(evaluation) + + +def eval_makeboxes(self, expr, evaluation, form=SymbolStandardForm): """ This function takes the definitions provided by the evaluation object, and produces a boxed form for expr. From 889e9fccee133fa0bdf1c31b9f4d09764df1b9b7 Mon Sep 17 00:00:00 2001 From: mmatera Date: Mon, 26 Dec 2022 19:32:19 -0300 Subject: [PATCH 06/12] moving do_eval_infix to be a non-member function in the same module. --- mathics/builtin/layout.py | 162 ++++++++++++++++++-------------------- 1 file changed, 77 insertions(+), 85 deletions(-) diff --git a/mathics/builtin/layout.py b/mathics/builtin/layout.py index 575554623..fcff2c42e 100644 --- a/mathics/builtin/layout.py +++ b/mathics/builtin/layout.py @@ -39,6 +39,78 @@ SymbolSubscriptBox = Symbol("System`SubscriptBox") +def eval_makeboxes_infix( + expr: Expression, + operator: BaseElement, + form: Symbol, + evaluation, + precedence_value: Optional[int] = None, + grouping: Optional[str] = None, +): + """Implements MakeBoxes[Infix[...]]""" + + ## FIXME: this should go into a some formatter. + def format_operator(operator) -> Union[String, BaseElement]: + """ + Format infix operator `operator`. To do this outside parameter form is used. + Sometimes no changes are made and operator is returned unchanged. + + This function probably should be rewritten be more scalable across other forms + and moved to a module that contiaing similar formatting routines. + """ + if not isinstance(operator, String): + return MakeBoxes(operator, form) + + op_str = operator.value + + # FIXME: performing a check using the operator symbol representation feels a bit + # fragile. The operator name seems more straightforward and more robust. + if form == SymbolInputForm and op_str in ["*", "^", " "]: + return operator + elif ( + form in (SymbolInputForm, SymbolOutputForm) + and not op_str.startswith(" ") + and not op_str.endswith(" ") + ): + # FIXME: Again, testing on specific forms is fragile and not scalable. + op = String(" " + op_str + " ") + return op + return operator + + if isinstance(expr, Atom): + evaluation.message("Infix", "normal", Integer1) + return None + + elements = expr.elements + if len(elements) > 1: + if operator.has_form("List", len(elements) - 1): + operator = [format_operator(op) for op in operator.elements] + return make_boxes_infix( + elements, operator, precedence_value, grouping, form + ) + else: + encoding_rule = evaluation.definitions.get_ownvalue("$CharacterEncoding") + encoding = "UTF8" if encoding_rule is None else encoding_rule.replace.value + op_str = ( + operator.value if isinstance(operator, String) else operator.short_name + ) + if encoding == "ASCII": + operator = format_operator( + String(operator_to_ascii.get(op_str, op_str)) + ) + else: + operator = format_operator( + String(operator_to_unicode.get(op_str, op_str)) + ) + + return make_boxes_infix(elements, operator, precedence_value, grouping, form) + + elif len(elements) == 1: + return MakeBoxes(elements[0], form) + else: + return MakeBoxes(expr, form) + + class Center(Builtin): """ :WMA link:https://reference.wolfram.com/language/ref/Center.html @@ -168,7 +240,7 @@ class Infix(Builtin): # the right rule should be # mbexpression:MakeBoxes[Infix[___], form] - def eval_all(self, expression, form, evaluation): + def eval_makeboxes(self, expression, form, evaluation): """MakeBoxes[Infix[___], form:StandardForm|TraditionalForm|OutputForm|InputForm]""" infix_expr = expression.elements[0] @@ -181,10 +253,10 @@ def eval_all(self, expression, form, evaluation): if num_parms == 1: expr = elements[0] - return self.do_eval_infix(expr, String("~"), form, evaluation) + return eval_makeboxes_infix(expr, String("~"), form, evaluation) if num_parms == 2: expr, operator = elements - return self.do_eval_infix(expr, operator, form, evaluation) + return eval_makeboxes_infix(expr, operator, form, evaluation) expr, operator, precedence = elements[:3] if not isinstance(precedence, Integer): @@ -197,97 +269,17 @@ def eval_all(self, expression, form, evaluation): grouping = SymbolNone if num_parms < 4 else elements[3] if grouping is SymbolNone: - return self.do_eval_infix( + return eval_makeboxes_infix( expr, operator, form, evaluation, precedence.value ) if grouping in (SymbolNonAssociative, SymbolLeft, SymbolRight): - return self.do_eval_infix( + return eval_makeboxes_infix( expr, operator, form, evaluation, precedence.value, grouping.get_name() ) evaluation.message("Infix", "argb", grouping) return eval_fullform_makeboxes(infix_expr, evaluation, form) - def do_eval_infix( - self, - expr: Expression, - operator: BaseElement, - form: Symbol, - evaluation, - precedence_value: Optional[int] = None, - grouping: Optional[str] = None, - ): - """Implements MakeBoxes[Infix[...]]""" - - ## FIXME: this should go into a some formatter. - def format_operator(operator) -> Union[String, BaseElement]: - """ - Format infix operator `operator`. To do this outside parameter form is used. - Sometimes no changes are made and operator is returned unchanged. - - This function probably should be rewritten be more scalable across other forms - and moved to a module that contiaing similar formatting routines. - """ - if not isinstance(operator, String): - return MakeBoxes(operator, form) - - op_str = operator.value - - # FIXME: performing a check using the operator symbol representation feels a bit - # fragile. The operator name seems more straightforward and more robust. - if form == SymbolInputForm and op_str in ["*", "^", " "]: - return operator - elif ( - form in (SymbolInputForm, SymbolOutputForm) - and not op_str.startswith(" ") - and not op_str.endswith(" ") - ): - # FIXME: Again, testing on specific forms is fragile and not scalable. - op = String(" " + op_str + " ") - return op - return operator - - if isinstance(expr, Atom): - evaluation.message("Infix", "normal", Integer1) - return None - - elements = expr.elements - if len(elements) > 1: - if operator.has_form("List", len(elements) - 1): - operator = [format_operator(op) for op in operator.elements] - return make_boxes_infix( - elements, operator, precedence_value, grouping, form - ) - else: - encoding_rule = evaluation.definitions.get_ownvalue( - "$CharacterEncoding" - ) - encoding = ( - "UTF8" if encoding_rule is None else encoding_rule.replace.value - ) - op_str = ( - operator.value - if isinstance(operator, String) - else operator.short_name - ) - if encoding == "ASCII": - operator = format_operator( - String(operator_to_ascii.get(op_str, op_str)) - ) - else: - operator = format_operator( - String(operator_to_unicode.get(op_str, op_str)) - ) - - return make_boxes_infix( - elements, operator, precedence_value, grouping, form - ) - - elif len(elements) == 1: - return MakeBoxes(elements[0], form) - else: - return MakeBoxes(expr, form) - class Left(Builtin): """ From 94f2bb0626dab608e9e6cc75efa0195cb2858a27 Mon Sep 17 00:00:00 2001 From: mmatera Date: Mon, 26 Dec 2022 19:47:06 -0300 Subject: [PATCH 07/12] adding comment --- mathics/builtin/layout.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/mathics/builtin/layout.py b/mathics/builtin/layout.py index fcff2c42e..5ad1b2dce 100644 --- a/mathics/builtin/layout.py +++ b/mathics/builtin/layout.py @@ -47,7 +47,15 @@ def eval_makeboxes_infix( precedence_value: Optional[int] = None, grouping: Optional[str] = None, ): - """Implements MakeBoxes[Infix[...]]""" + """Implements MakeBoxes[Infix[...]].""" + + # Actually, this function implements one part of the evaluation: + # the one that should be done at the "format" level. In that point, + # the operators are processed to add spaces and use the right + # encoding. This should be done at the level of the formatter, not + # in the "MakeBoxes" step. + # Then, the true implementation is done + # in ```mathics.eval.makeboxes.make_boxes_infix```. ## FIXME: this should go into a some formatter. def format_operator(operator) -> Union[String, BaseElement]: From 11a7eec0713ef2e0fb638dcaa4bcfa07b125429e Mon Sep 17 00:00:00 2001 From: rocky Date: Mon, 26 Dec 2022 19:25:23 -0500 Subject: [PATCH 08/12] Move towards segregating box from eval * Some long lines split * some linting * correct a wrong symbol --- mathics/builtin/colors/color_operations.py | 2 +- mathics/builtin/layout.py | 145 ++++++++++++++------- mathics/eval/image.py | 8 +- mathics/eval/makeboxes.py | 74 ++++------- 4 files changed, 127 insertions(+), 102 deletions(-) diff --git a/mathics/builtin/colors/color_operations.py b/mathics/builtin/colors/color_operations.py index f372a42b4..4de7f4cc6 100644 --- a/mathics/builtin/colors/color_operations.py +++ b/mathics/builtin/colors/color_operations.py @@ -386,7 +386,7 @@ def eval(self, image, n, prop, evaluation, options): im = ( image.color_convert("RGB") .pil() - .convert("P", palette=PILImage.ADAPTIVE, colors=256) + .convert("P", palette=PIL.Image.ADAPTIVE, colors=256) ) pixels = numpy.array(list(im.getdata())) diff --git a/mathics/builtin/layout.py b/mathics/builtin/layout.py index 5ad1b2dce..a6588ef94 100644 --- a/mathics/builtin/layout.py +++ b/mathics/builtin/layout.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- """ -This module contains symbols used to define the high level layout for +This module contains symbols used to define the high level layout for \ expression formatting. -For instance, to represent a set of consecutive expressions in a row, -we can use ``Row`` +For instance, to represent a set of consecutive expressions in a row, \ +we can use ``Row``. """ from typing import Optional, Union @@ -15,49 +15,53 @@ from mathics.builtin.lists import list_boxes from mathics.builtin.makeboxes import MakeBoxes from mathics.builtin.options import options_to_rules -from mathics.core.atoms import Real, String +from mathics.core.atoms import Integer, Integer1, Real, String from mathics.core.expression import Evaluation, Expression +from mathics.core.convert.op import operator_to_ascii, operator_to_unicode +from mathics.core.element import BaseElement from mathics.core.list import ListExpression from mathics.core.symbols import Atom, Symbol from mathics.core.systemsymbols import ( SymbolFullForm, - SymbolInfix, SymbolInputForm, SymbolLeft, SymbolMakeBoxes, SymbolNone, SymbolOutputForm, SymbolRight, + SymbolRowBox, ) -from mathics.eval.makeboxes import ( - eval_fullform_makeboxes, - format_element, - make_boxes_infix, -) +from mathics.eval.makeboxes import eval_fullform_makeboxes, format_element, parenthesize SymbolNonAssociative = Symbol("System`NonAssociative") SymbolSubscriptBox = Symbol("System`SubscriptBox") -def eval_makeboxes_infix( +#################################################################### +# This section might get moved to mathics.box +# +# Some of the code below get replace Mathics code or may get put in +# collection of boxing modules, +# e.g. ``mathics.box.boxes_operators.box_infix()``. +# +#################################################################### + + +def box_infix( expr: Expression, operator: BaseElement, form: Symbol, - evaluation, - precedence_value: Optional[int] = None, + evaluation: Evaluation, + precedence_value: Optional[int] = 0, grouping: Optional[str] = None, -): - """Implements MakeBoxes[Infix[...]].""" - - # Actually, this function implements one part of the evaluation: - # the one that should be done at the "format" level. In that point, - # the operators are processed to add spaces and use the right - # encoding. This should be done at the level of the formatter, not - # in the "MakeBoxes" step. - # Then, the true implementation is done - # in ```mathics.eval.makeboxes.make_boxes_infix```. - - ## FIXME: this should go into a some formatter. +) -> Optional[Expression]: + """Implements MakeBoxes[Infix[...]]. + This function kicks off boxing for Infix operators. + + Operators are processed to add spaces and to use the right encoding. + """ + + # FIXME: this should go into a some formatter. def format_operator(operator) -> Union[String, BaseElement]: """ Format infix operator `operator`. To do this outside parameter form is used. @@ -93,7 +97,7 @@ def format_operator(operator) -> Union[String, BaseElement]: if len(elements) > 1: if operator.has_form("List", len(elements) - 1): operator = [format_operator(op) for op in operator.elements] - return make_boxes_infix( + return box_infix_elements( elements, operator, precedence_value, grouping, form ) else: @@ -111,7 +115,7 @@ def format_operator(operator) -> Union[String, BaseElement]: String(operator_to_unicode.get(op_str, op_str)) ) - return make_boxes_infix(elements, operator, precedence_value, grouping, form) + return box_infix_elements(elements, operator, precedence_value, grouping, form) elif len(elements) == 1: return MakeBoxes(elements[0], form) @@ -119,9 +123,42 @@ def format_operator(operator) -> Union[String, BaseElement]: return MakeBoxes(expr, form) +# FIXME: op should be a string, so remove the Union. +def box_infix_elements( + elements, op: Union[String, list], precedence: int, grouping, form: Symbol +) -> Expression: + result = [] + for index, element in enumerate(elements): + if index > 0: + if isinstance(op, list): + result.append(op[index - 1]) + else: + result.append(op) + parenthesized = False + if grouping == "System`NonAssociative": + parenthesized = True + elif grouping == "System`Left" and index > 0: + parenthesized = True + elif grouping == "System`Right" and index == 0: + parenthesized = True + + element_boxes = Expression(SymbolMakeBoxes, element, form) + element = parenthesize(precedence, element, element_boxes, parenthesized) + + result.append(element) + return Expression(SymbolRowBox, ListExpression(*result)) + + +#################################################################### +# End of section of code that might be in mathics.box. +#################################################################### + + class Center(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/Center.html + + :WMA link: + https://reference.wolfram.com/language/ref/Center.html
'Center' @@ -187,7 +224,7 @@ class Grid(Builtin): options = GridBox.options summary_text = " 2D layout containing arbitrary objects" - def eval_makeboxes(self, array, f, evaluation, options) -> Expression: + def eval_makeboxes(self, array, f, evaluation: Evaluation, options) -> Expression: """MakeBoxes[Grid[array_?MatrixQ, OptionsPattern[Grid]], f:StandardForm|TraditionalForm|OutputForm]""" return GridBox( @@ -248,7 +285,7 @@ class Infix(Builtin): # the right rule should be # mbexpression:MakeBoxes[Infix[___], form] - def eval_makeboxes(self, expression, form, evaluation): + def eval_makeboxes(self, expression, form, evaluation: Evaluation): """MakeBoxes[Infix[___], form:StandardForm|TraditionalForm|OutputForm|InputForm]""" infix_expr = expression.elements[0] @@ -256,15 +293,15 @@ def eval_makeboxes(self, expression, form, evaluation): num_parms = len(elements) if num_parms == 0 or num_parms > 4: - evaluation.message("Infix", "argb", Integer(len(parms.get_sequence()))) + evaluation.message("Infix", "argb", Integer(num_parms)) return eval_fullform_makeboxes(infix_expr, evaluation, form) if num_parms == 1: expr = elements[0] - return eval_makeboxes_infix(expr, String("~"), form, evaluation) + return box_infix(expr, String("~"), form, evaluation) if num_parms == 2: expr, operator = elements - return eval_makeboxes_infix(expr, operator, form, evaluation) + return box_infix(expr, operator, form, evaluation) expr, operator, precedence = elements[:3] if not isinstance(precedence, Integer): @@ -277,11 +314,9 @@ def eval_makeboxes(self, expression, form, evaluation): grouping = SymbolNone if num_parms < 4 else elements[3] if grouping is SymbolNone: - return eval_makeboxes_infix( - expr, operator, form, evaluation, precedence.value - ) + return box_infix(expr, operator, form, evaluation, precedence.value) if grouping in (SymbolNonAssociative, SymbolLeft, SymbolRight): - return eval_makeboxes_infix( + return box_infix( expr, operator, form, evaluation, precedence.value, grouping.get_name() ) @@ -295,7 +330,8 @@ class Left(Builtin):
'Left' -
is used with operator formatting constructs to specify a left-associative operator. +
is used with operator formatting constructs to specify a \ + left-associative operator.
""" @@ -310,7 +346,8 @@ class NonAssociative(Builtin):
'NonAssociative' -
is used with operator formatting constructs to specify a non-associative operator. +
is used with operator formatting constructs to specify a \ + non-associative operator.
""" @@ -427,7 +464,8 @@ class Right(Builtin):
'Right' -
is used with operator formatting constructs to specify a right-associative operator. +
is used with operator formatting constructs to specify a \ + right-associative operator.
""" @@ -446,21 +484,21 @@ class Row(Builtin): summary_text = "1D layouts containing arbitrary objects in a row" - def eval_makeboxes(self, items, sep, f, evaluation: Evaluation): + def eval_makeboxes(self, items, sep, form, evaluation: Evaluation): """MakeBoxes[Row[{items___}, sep_:""], - f:StandardForm|TraditionalForm|OutputForm]""" + form:StandardForm|TraditionalForm|OutputForm]""" items = items.get_sequence() if not isinstance(sep, String): - sep = MakeBoxes(sep, f) + sep = MakeBoxes(sep, form) if len(items) == 1: - return MakeBoxes(items[0], f) + return MakeBoxes(items[0], form) else: result = [] for index, item in enumerate(items): if index > 0 and not sep.sameQ(String("")): result.append(to_boxes(sep, evaluation)) - item = MakeBoxes(item, f).evaluate(evaluation) + item = MakeBoxes(item, form).evaluate(evaluation) item = to_boxes(item, evaluation) result.append(item) return RowBox(*result) @@ -473,22 +511,31 @@ class Style(Builtin):
'Style[$expr$, options]'
displays $expr$ formatted using the specified option settings. +
'Style[$expr$, "style"]'
uses the option settings for the specified style in the current notebook. +
'Style[$expr$, $color$]'
displays using the specified color. +
'Style[$expr$, $Bold$]'
displays with fonts made bold. +
'Style[$expr$, $Italic$]'
displays with fonts made italic. +
'Style[$expr$, $Underlined$]'
displays with fonts underlined. +
'Style[$expr$, $Larger$]
displays with fonts made larger. +
'Style[$expr$, $Smaller$]'
displays with fonts made smaller. +
'Style[$expr$, $n$]'
displays with font size n. +
'Style[$expr$, $Tiny$]'
'Style[$expr$, $Small$]', etc.
display with fonts that are tiny, small, etc. @@ -536,7 +583,9 @@ def eval_makeboxes(self, x, y, f, evaluation) -> Expression: class Subsuperscript(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/Subsuperscript.html + + :WMA link: + https://reference.wolfram.com/language/ref/Subsuperscript.html
'Subsuperscript[$a$, $b$, $c$]' @@ -558,7 +607,9 @@ class Subsuperscript(Builtin): class Superscript(Builtin): """ - :WMA link:https://reference.wolfram.com/language/ref/Superscript.html + + :WMA link: + https://reference.wolfram.com/language/ref/Superscript.html
'Superscript[$x$, $y$]' diff --git a/mathics/eval/image.py b/mathics/eval/image.py index 11039a59e..3c3d48f3e 100644 --- a/mathics/eval/image.py +++ b/mathics/eval/image.py @@ -37,11 +37,11 @@ } # After Python 3.6 support is dropped, this can be simplified -# to for Pillow 9+ and use PILImage.Resampling only. -if hasattr(PILImage, "Resampling"): - pil_resize = PILImage.Resampling +# to for Pillow 9+ and use PIL.Image.Resampling only. +if hasattr(PIL.Image, "Resampling"): + pil_resize = PIL.Image.Resampling else: - pil_resize = PILImage + pil_resize = PIL.Image # See: # https://pillow.readthedocs.io/en/stable/handbook/concepts.html#filters diff --git a/mathics/eval/makeboxes.py b/mathics/eval/makeboxes.py index d454c39d3..8e20f95f4 100644 --- a/mathics/eval/makeboxes.py +++ b/mathics/eval/makeboxes.py @@ -54,56 +54,6 @@ def _boxed_string(string: str, **options): return StyleBox(String(string), **options) -def parenthesize(precedence, element, element_boxes, when_equal): - from mathics.builtin import builtins_precedence - - while element.has_form("HoldForm", 1): - element = element.elements[0] - - if element.has_form(("Infix", "Prefix", "Postfix"), 3, None): - element_prec = element.elements[2].value - elif element.has_form("PrecedenceForm", 2): - element_prec = element.elements[1].value - # For negative values, ensure that the element_precedence is at least the precedence. (Fixes #332) - elif isinstance(element, (Integer, Real)) and element.value < 0: - element_prec = precedence - else: - element_prec = builtins_precedence.get(element.get_head_name()) - if precedence is not None and element_prec is not None: - if precedence > element_prec or (precedence == element_prec and when_equal): - return Expression( - SymbolRowBox, - ListExpression(String("("), element_boxes, String(")")), - ) - return element_boxes - - -# FIXME: op should be a string, so remove the Union. -def make_boxes_infix( - elements, op: Union[String, list], precedence: int, grouping, form: Symbol -): - result = [] - for index, element in enumerate(elements): - if index > 0: - if isinstance(op, list): - result.append(op[index - 1]) - else: - result.append(op) - parenthesized = False - if grouping == "System`NonAssociative": - parenthesized = True - elif grouping == "System`Left" and index > 0: - parenthesized = True - elif grouping == "System`Right" and index == 0: - parenthesized = True - - element_boxes = Expression(SymbolMakeBoxes, element, form) - element = parenthesize(precedence, element, element_boxes, parenthesized) - - result.append(element) - return Expression(SymbolRowBox, ListExpression(*result)) - - def eval_fullform_makeboxes(self, expr, evaluation, form=SymbolStandardForm): """ This function takes the definitions provided by the evaluation @@ -345,6 +295,30 @@ def do_format_expression( return expr +def parenthesize(precedence, element, element_boxes, when_equal): + from mathics.builtin import builtins_precedence + + while element.has_form("HoldForm", 1): + element = element.elements[0] + + if element.has_form(("Infix", "Prefix", "Postfix"), 3, None): + element_prec = element.elements[2].value + elif element.has_form("PrecedenceForm", 2): + element_prec = element.elements[1].value + # For negative values, ensure that the element_precedence is at least the precedence. (Fixes #332) + elif isinstance(element, (Integer, Real)) and element.value < 0: + element_prec = precedence + else: + element_prec = builtins_precedence.get(element.get_head_name()) + if precedence is not None and element_prec is not None: + if precedence > element_prec or (precedence == element_prec and when_equal): + return Expression( + SymbolRowBox, + ListExpression(String("("), element_boxes, String(")")), + ) + return element_boxes + + element_formatters[Rational] = do_format_rational element_formatters[Complex] = do_format_complex element_formatters[Expression] = do_format_expression From 0d4ce191ea82e3e26b89877d384707a20f237dd2 Mon Sep 17 00:00:00 2001 From: rocky Date: Mon, 26 Dec 2022 19:51:40 -0500 Subject: [PATCH 09/12] Adjust builtins_precedence and doc parenthesize We should be using Symbols insead of string where possible. builtins_precedence is really used in eval/makeboxes so it is defined there. It is just initialized in `mathics.buitins`. --- mathics/builtin/__init__.py | 6 +++--- mathics/eval/makeboxes.py | 14 +++++++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/mathics/builtin/__init__.py b/mathics/builtin/__init__.py index 0f88784ef..1d9704c46 100755 --- a/mathics/builtin/__init__.py +++ b/mathics/builtin/__init__.py @@ -36,6 +36,8 @@ mathics_to_python, ) from mathics.core.pattern import pattern_objects +from mathics.core.symbols import Symbol +from mathics.eval.makeboxes import builtins_precedence from mathics.settings import ENABLE_FILES_MODULE from mathics.version import __version__ # noqa used in loading to check consistency. @@ -60,7 +62,7 @@ def add_builtins(new_builtins): # print("XXX1", sympy_name) sympy_to_mathics[sympy_name] = builtin if isinstance(builtin, Operator): - builtins_precedence[name] = builtin.precedence + builtins_precedence[Symbol(name)] = builtin.precedence if isinstance(builtin, PatternObject): pattern_objects[name] = builtin.__class__ _builtins.update(dict(new_builtins)) @@ -237,8 +239,6 @@ def name_is_builtin_symbol(module, name: str) -> Optional[type]: mathics_to_sympy = {} # here we have: name -> sympy object sympy_to_mathics = {} -builtins_precedence = {} - new_builtins = _builtins_list # FIXME: some magic is going on here.. diff --git a/mathics/eval/makeboxes.py b/mathics/eval/makeboxes.py index 8e20f95f4..790d7da1e 100644 --- a/mathics/eval/makeboxes.py +++ b/mathics/eval/makeboxes.py @@ -295,9 +295,17 @@ def do_format_expression( return expr -def parenthesize(precedence, element, element_boxes, when_equal): - from mathics.builtin import builtins_precedence +def parenthesize( + precedence: int, element: Type[BaseElement], element_boxes, when_equal: bool +) -> Type[Expression]: + """ + "Determines if ``element_boxes`` needs to be surrounded with parenthesis. + This is done based on ``precedence`` and the computed preceence of + ``element``. The adjusted ListExpression is returned. + If when_equal is True, parentheses will be added if the two + precedence values are equal. + """ while element.has_form("HoldForm", 1): element = element.elements[0] @@ -309,7 +317,7 @@ def parenthesize(precedence, element, element_boxes, when_equal): elif isinstance(element, (Integer, Real)) and element.value < 0: element_prec = precedence else: - element_prec = builtins_precedence.get(element.get_head_name()) + element_prec = builtins_precedence.get(element.get_head()) if precedence is not None and element_prec is not None: if precedence > element_prec or (precedence == element_prec and when_equal): return Expression( From a43fd5ea33c6d2dea32f1759fcbdeb37c59fc00a Mon Sep 17 00:00:00 2001 From: rocky Date: Mon, 26 Dec 2022 20:51:22 -0500 Subject: [PATCH 10/12] PredefinedSymbol -> SymbolConstant... and try to clearify what is up here. --- mathics/builtin/makeboxes.py | 2 -- mathics/core/symbols.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/mathics/builtin/makeboxes.py b/mathics/builtin/makeboxes.py index 18471df78..faf50a523 100644 --- a/mathics/builtin/makeboxes.py +++ b/mathics/builtin/makeboxes.py @@ -5,8 +5,6 @@ Low level Format definitions """ -from typing import Union - import mpmath from mathics.builtin.base import Builtin, Predefined diff --git a/mathics/core/symbols.py b/mathics/core/symbols.py index 6e32d0dac..225601a1e 100644 --- a/mathics/core/symbols.py +++ b/mathics/core/symbols.py @@ -333,7 +333,7 @@ class Symbol(Atom, NumericOperators, EvalMixin): All Symbols have a name that can be converted to string. - A Variable Symbol is a ``Symbol``` that is associated with a + A Variable Symbol is a ``Symbol`` that is associated with a ``Definition`` that has an ``OwnValue`` that determines its evaluation value. From 45c12c48bd52b56432f6832b06604b7ea4bb84ed Mon Sep 17 00:00:00 2001 From: rocky Date: Mon, 26 Dec 2022 23:53:57 -0500 Subject: [PATCH 11/12] tweak docstring, more type annotations --- mathics/eval/makeboxes.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/mathics/eval/makeboxes.py b/mathics/eval/makeboxes.py index 790d7da1e..d248b48a6 100644 --- a/mathics/eval/makeboxes.py +++ b/mathics/eval/makeboxes.py @@ -54,25 +54,30 @@ def _boxed_string(string: str, **options): return StyleBox(String(string), **options) -def eval_fullform_makeboxes(self, expr, evaluation, form=SymbolStandardForm): +def eval_fullform_makeboxes( + self, expr, evaluation: Evaluation, form=SymbolStandardForm +) -> Expression: """ This function takes the definitions provided by the evaluation object, and produces a boxed form for expr. + + Basically: MakeBoxes[expr // FullForm] """ # This is going to be reimplemented. expr = Expression(SymbolFullForm, expr) return Expression(SymbolMakeBoxes, expr, form).evaluate(evaluation) -def eval_makeboxes(self, expr, evaluation, form=SymbolStandardForm): +def eval_makeboxes( + self, expr, evaluation: Evaluation, form=SymbolStandardForm +) -> Expression: """ This function takes the definitions provided by the evaluation - object, and produces a boxed form for expr. + object, and produces a boxed fullform for expr. - Basically: MakeBoxes[expr // FullForm] + Basically: MakeBoxes[expr // form] """ # This is going to be reimplemented. - expr = Expression(SymbolFullForm, expr) return Expression(SymbolMakeBoxes, expr, form).evaluate(evaluation) From c4c1e6dcfa0a9e95203c28c80972205f230983e7 Mon Sep 17 00:00:00 2001 From: rocky Date: Sat, 7 Jan 2023 09:03:32 -0500 Subject: [PATCH 12/12] Residual mege changes to get this working again --- mathics/builtin/layout.py | 2 +- mathics/builtin/makeboxes.py | 46 +++++++++++++++++++++++++++++++----- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/mathics/builtin/layout.py b/mathics/builtin/layout.py index a6588ef94..8812a3026 100644 --- a/mathics/builtin/layout.py +++ b/mathics/builtin/layout.py @@ -16,9 +16,9 @@ from mathics.builtin.makeboxes import MakeBoxes from mathics.builtin.options import options_to_rules from mathics.core.atoms import Integer, Integer1, Real, String -from mathics.core.expression import Evaluation, Expression from mathics.core.convert.op import operator_to_ascii, operator_to_unicode from mathics.core.element import BaseElement +from mathics.core.expression import Evaluation, Expression from mathics.core.list import ListExpression from mathics.core.symbols import Atom, Symbol from mathics.core.systemsymbols import ( diff --git a/mathics/builtin/makeboxes.py b/mathics/builtin/makeboxes.py index faf50a523..e58005889 100644 --- a/mathics/builtin/makeboxes.py +++ b/mathics/builtin/makeboxes.py @@ -5,18 +5,26 @@ Low level Format definitions """ +from typing import Union + import mpmath from mathics.builtin.base import Builtin, Predefined from mathics.builtin.box.layout import RowBox, to_boxes -from mathics.core.atoms import Integer, Real, String +from mathics.core.atoms import Integer, Integer1, Real, String from mathics.core.attributes import A_HOLD_ALL_COMPLETE, A_READ_PROTECTED -from mathics.core.element import BoxElementMixin +from mathics.core.convert.op import operator_to_ascii, operator_to_unicode +from mathics.core.element import BaseElement, BoxElementMixin from mathics.core.expression import Expression from mathics.core.list import ListExpression from mathics.core.number import dps -from mathics.core.symbols import Atom -from mathics.core.systemsymbols import SymbolRowBox +from mathics.core.symbols import Atom, Symbol +from mathics.core.systemsymbols import ( + SymbolInputForm, + SymbolNone, + SymbolOutputForm, + SymbolRowBox, +) from mathics.eval.makeboxes import _boxed_string, format_element, parenthesize @@ -32,6 +40,32 @@ def int_to_s_exp(expr, n): return s, exp, nonnegative +# FIXME: op should be a string, so remove the Union. +def make_boxes_infix( + elements, op: Union[String, list], precedence: int, grouping, form: Symbol +): + result = [] + for index, element in enumerate(elements): + if index > 0: + if isinstance(op, list): + result.append(op[index - 1]) + else: + result.append(op) + parenthesized = False + if grouping == "System`NonAssociative": + parenthesized = True + elif grouping == "System`Left" and index > 0: + parenthesized = True + elif grouping == "System`Right" and index == 0: + parenthesized = True + + element_boxes = MakeBoxes(element, form) + element = parenthesize(precedence, element, element_boxes, parenthesized) + + result.append(element) + return Expression(SymbolRowBox, ListExpression(*result)) + + def real_to_s_exp(expr, n): if expr.is_zero: s = "0" @@ -428,7 +462,7 @@ def eval_infix( evaluation.message("Infix", "intm", expr) return self.eval_general(expr, form, evaluation) - grouping = grouping.get_name() + grouping = grouping ## FIXME: this should go into a some formatter. def format_operator(operator) -> Union[String, BaseElement]: @@ -458,7 +492,7 @@ def format_operator(operator) -> Union[String, BaseElement]: return op return operator - precedence = prec.value if hasattr(prec, "value") else 0 + precedence = precedence.value if hasattr(precedence, "value") else 0 grouping = grouping.get_name() if isinstance(expr, Atom):