From 481a31827beefba54493a43b15d62af08c5f2311 Mon Sep 17 00:00:00 2001 From: mmatera Date: Wed, 28 Dec 2022 20:36:20 -0300 Subject: [PATCH] reimplementing eval_makeboxes. step 1 --- mathics/builtin/box/layout.py | 25 +--- mathics/builtin/drawing/image.py | 1 + mathics/builtin/forms/other.py | 3 +- mathics/builtin/forms/output.py | 4 +- mathics/builtin/layout.py | 4 +- mathics/builtin/makeboxes.py | 55 ++------- mathics/eval/image.py | 1 + mathics/eval/makeboxes.py | 189 +++++++++++++++++++++++++++++-- 8 files changed, 197 insertions(+), 85 deletions(-) diff --git a/mathics/builtin/box/layout.py b/mathics/builtin/box/layout.py index 74b83a2e6..d7400a32b 100644 --- a/mathics/builtin/box/layout.py +++ b/mathics/builtin/box/layout.py @@ -26,30 +26,7 @@ SymbolSubsuperscriptBox, SymbolSuperscriptBox, ) -from mathics.eval.makeboxes import eval_makeboxes - - -def to_boxes(x, evaluation: Evaluation, options={}) -> BoxElementMixin: - """ - This function takes the expression ``x`` - and tries to reduce it to a ``BoxElementMixin`` - expression unsing an evaluation object. - """ - if isinstance(x, BoxElementMixin): - return x - if isinstance(x, Atom): - x = x.atom_to_boxes(SymbolStandardForm, evaluation) - return to_boxes(x, evaluation, options) - if isinstance(x, Expression): - if x.has_form("MakeBoxes", None): - x_boxed = x.evaluate(evaluation) - else: - x_boxed = eval_makeboxes(x, evaluation) - if isinstance(x_boxed, BoxElementMixin): - return x_boxed - if isinstance(x_boxed, Atom): - return to_boxes(x_boxed, evaluation, options) - raise eval_makeboxes(Expression(SymbolFullForm, x), evaluation) +from mathics.eval.makeboxes import eval_makeboxes, to_boxes class BoxData(Builtin): diff --git a/mathics/builtin/drawing/image.py b/mathics/builtin/drawing/image.py index bfaa64348..32228ff3f 100644 --- a/mathics/builtin/drawing/image.py +++ b/mathics/builtin/drawing/image.py @@ -68,6 +68,7 @@ import numpy import PIL + import PIL.Image import PIL.ImageEnhance import PIL.ImageFilter import PIL.ImageOps diff --git a/mathics/builtin/forms/other.py b/mathics/builtin/forms/other.py index 21f5d02b0..6a5c9bc7d 100644 --- a/mathics/builtin/forms/other.py +++ b/mathics/builtin/forms/other.py @@ -4,11 +4,12 @@ import re -from mathics.builtin.box.layout import RowBox, to_boxes +from mathics.builtin.box.layout import RowBox from mathics.builtin.forms.base import FormBaseClass from mathics.builtin.makeboxes import MakeBoxes from mathics.core.atoms import String from mathics.core.element import EvalMixin +from mathics.eval.makeboxes import to_boxes class StringForm(FormBaseClass): diff --git a/mathics/builtin/forms/output.py b/mathics/builtin/forms/output.py index 06061e9a4..277cd48b6 100644 --- a/mathics/builtin/forms/output.py +++ b/mathics/builtin/forms/output.py @@ -14,7 +14,7 @@ from typing import Optional from mathics.builtin.base import Builtin -from mathics.builtin.box.layout import GridBox, RowBox, to_boxes +from mathics.builtin.box.layout import GridBox, RowBox from mathics.builtin.comparison import expr_min from mathics.builtin.forms.base import FormBaseClass from mathics.builtin.makeboxes import MakeBoxes, number_form @@ -49,7 +49,7 @@ SymbolSubscriptBox, SymbolSuperscriptBox, ) -from mathics.eval.makeboxes import format_element +from mathics.eval.makeboxes import format_element, to_boxes MULTI_NEWLINE_RE = re.compile(r"\n{2,}") diff --git a/mathics/builtin/layout.py b/mathics/builtin/layout.py index 9b68b9ce9..969402e2a 100644 --- a/mathics/builtin/layout.py +++ b/mathics/builtin/layout.py @@ -11,7 +11,7 @@ from mathics.builtin.base import BinaryOperator, Builtin, Operator -from mathics.builtin.box.layout import GridBox, RowBox, to_boxes +from mathics.builtin.box.layout import GridBox, RowBox from mathics.builtin.lists import list_boxes from mathics.builtin.makeboxes import MakeBoxes from mathics.builtin.options import options_to_rules @@ -20,7 +20,7 @@ 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.eval.makeboxes import format_element, to_boxes SymbolSubscriptBox = Symbol("System`SubscriptBox") diff --git a/mathics/builtin/makeboxes.py b/mathics/builtin/makeboxes.py index ed906e3ef..96c7f49f0 100644 --- a/mathics/builtin/makeboxes.py +++ b/mathics/builtin/makeboxes.py @@ -10,7 +10,7 @@ import mpmath from mathics.builtin.base import Builtin, Predefined -from mathics.builtin.box.layout import RowBox, to_boxes +from mathics.builtin.box.layout import RowBox from mathics.core.atoms import Integer, Integer1, 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 @@ -20,7 +20,12 @@ from mathics.core.number import dps from mathics.core.symbols import Atom, Symbol from mathics.core.systemsymbols import SymbolInputForm, SymbolOutputForm, SymbolRowBox -from mathics.eval.makeboxes import _boxed_string, format_element +from mathics.eval.makeboxes import ( + _boxed_string, + eval_makeboxes, + format_element, + to_boxes, +) def int_to_s_exp(expr, n): @@ -374,51 +379,7 @@ class MakeBoxes(Builtin): def eval_general(self, expr, f, evaluation): """MakeBoxes[expr_, f:TraditionalForm|StandardForm|OutputForm|InputForm|FullForm]""" - if isinstance(expr, BoxElementMixin): - expr = expr.to_expression() - if isinstance(expr, Atom): - return expr.atom_to_boxes(f, evaluation) - else: - head = expr.head - elements = expr.elements - - f_name = f.get_name() - if f_name == "System`TraditionalForm": - left, right = "(", ")" - else: - left, right = "[", "]" - - # Parenthesize infix operators at the head of expressions, - # like (a + b)[x], but not f[a] in f[a][b]. - # - head_boxes = parenthesize(670, head, MakeBoxes(head, f), False) - head_boxes = head_boxes.evaluate(evaluation) - head_boxes = to_boxes(head_boxes, evaluation) - result = [head_boxes, to_boxes(String(left), evaluation)] - - if len(elements) > 1: - row = [] - if f_name in ( - "System`InputForm", - "System`OutputForm", - "System`FullForm", - ): - sep = ", " - else: - sep = "," - for index, element in enumerate(elements): - if index > 0: - row.append(to_boxes(String(sep), evaluation)) - row.append( - to_boxes(MakeBoxes(element, f).evaluate(evaluation), evaluation) - ) - result.append(RowBox(*row)) - elif len(elements) == 1: - result.append( - to_boxes(MakeBoxes(elements[0], f).evaluate(evaluation), evaluation) - ) - result.append(to_boxes(String(right), evaluation)) - return RowBox(*result) + return eval_makeboxes(expr, evaluation, f) def eval_outerprecedenceform(self, expr, prec, evaluation): """MakeBoxes[PrecedenceForm[expr_, prec_], diff --git a/mathics/eval/image.py b/mathics/eval/image.py index 3f78371bd..cbc84f4fd 100644 --- a/mathics/eval/image.py +++ b/mathics/eval/image.py @@ -9,6 +9,7 @@ import numpy import PIL +import PIL.Image from mathics.builtin.base import String from mathics.core.atoms import Rational diff --git a/mathics/eval/makeboxes.py b/mathics/eval/makeboxes.py index 7c51be55f..10856f31d 100644 --- a/mathics/eval/makeboxes.py +++ b/mathics/eval/makeboxes.py @@ -7,9 +7,9 @@ import typing -from typing import Any +from typing import Any, 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, ) @@ -51,13 +52,130 @@ def _boxed_string(string: str, **options): return StyleBox(String(string), **options) -def eval_makeboxes(self, expr, evaluation, f=SymbolStandardForm): +def eval_fullform_makeboxes( + self, expr, evaluation: Evaluation, form=SymbolStandardForm +) -> Expression: """ - This function takes the definitions prodived by the evaluation + This function takes the definitions provided by the evaluation object, and produces a boxed form for expr. + + Basically: MakeBoxes[expr // FullForm] + """ + return eval_makeboxes(Expression(SymbolFullForm, expr), evaluation, form) + + +def eval_makeboxes( + expr: BaseElement, evaluation: Evaluation, form: Symbol = SymbolStandardForm +): + """ + This function takes the definitions provided by the evaluation + object, and produces a boxed fullform for expr. + + Basically: MakeBoxes[expr, form] """ - # This is going to be reimplemented. - return Expression(SymbolMakeBoxes, expr, f).evaluate(evaluation) + + def build_rowbox(*items): + """ + This function builds ```RowBox[{items}]```. + """ + # One way to avoid the local import would be to + # replace the body by + # return Expression(SymbolRowBox, *items).evaluate(evaluation) + from mathics.builtin.box.layout import RowBox + + return RowBox(*items) + + if isinstance(expr, BoxElementMixin): + expr = expr.to_expression() + + mb_expr = Expression(SymbolMakeBoxes, expr, form) + # TODO: This private function should be reformulated + # when MakeBoxes rules be stored in the proper place, + # supporting upvalues too. + + def mb_rules(): + # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + # This is used because we need to skip eval_general rule. + # Hopefully, this is not going to be necesary anymore when + # makeboxes rules be stored as formatvalues. + from mathics.core.rules import BuiltinRule + + mbdef = evaluation.definitions.builtin.get("System`MakeBoxes") + eval_general_method = mbdef.builtin.eval_general if mbdef is not None else None + # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + rules = evaluation.definitions.get_downvalues("System`MakeBoxes") + for rule in rules: + # MakeBoxes.eval_general should be skipped, and applied here + # as an special case. Once the other MakeBoxes rules + # be stored as format rules, this check is not going to be need + # anymore. + if isinstance(rule, BuiltinRule): + if rule.function == eval_general_method: + continue + yield rule + + for rule in mb_rules(): + result = rule.apply(mb_expr, evaluation, fully=False) + if result is None or result is mb_expr: + continue + + if isinstance(result, BoxElementMixin): + return result + if isinstance(result, EvalMixin): + result = result.evaluate(evaluation) + if isinstance(result, BoxElementMixin): + return result + + # Default case + if isinstance(expr, Atom): + try: + return expr.atom_to_boxes(form, evaluation) + except NotImplementedError: + print("not implemented!") + return String(str(expr)) + else: + head = expr.head + elements = expr.elements + + f_name = form.get_name() + if f_name == "System`TraditionalForm": + left, right = "(", ")" + else: + left, right = "[", "]" + + # Parenthesize infix operators at the head of expressions, + # like (a + b)[x], but not f[a] in f[a][b]. + # + head_boxes = parenthesize( + 670, head, Expression(SymbolMakeBoxes, head, form), False + ) + head_boxes = head_boxes.evaluate(evaluation) + head_boxes = to_boxes(head_boxes, evaluation) + result = [head_boxes, to_boxes(String(left), evaluation)] + + if len(elements) > 1: + row = [] + if f_name in ( + "System`InputForm", + "System`OutputForm", + "System`FullForm", + ): + sep = ", " + else: + sep = "," + for index, element in enumerate(elements): + if index > 0: + row.append(to_boxes(String(sep), evaluation)) + row.append( + to_boxes(eval_makeboxes(element, evaluation, form), evaluation) + ) + result.append(build_rowbox(*row)) + elif len(elements) == 1: + result.append( + to_boxes(eval_makeboxes(elements[0], evaluation, form), evaluation) + ) + result.append(to_boxes(String(right), evaluation)) + return build_rowbox(*result) def format_element( @@ -67,8 +185,7 @@ def format_element( Applies formats associated to the expression, and then calls Makeboxes """ expr = do_format(element, evaluation, form) - result = Expression(SymbolMakeBoxes, expr, form) - result_box = result.evaluate(evaluation) + result_box = eval_makeboxes(expr, evaluation, form) if isinstance(result_box, String): return result_box if isinstance(result_box, BoxElementMixin): @@ -94,7 +211,6 @@ def do_format_element( Applies formats associated to the expression and removes superfluous enclosing formats. """ - evaluation.inc_recursion_depth() try: expr = element @@ -279,6 +395,61 @@ def do_format_expression( return expr +def parenthesize( + precedence: int, + element: Type[BaseElement], + element_boxes: Expression, + when_equal: bool, +) -> Type[Expression]: + 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 + + +def to_boxes(x: BaseElement, evaluation: Evaluation, options={}) -> BoxElementMixin: + """ + This function takes the expression ``x`` + and tries to reduce it to a ``BoxElementMixin`` + expression unsing an evaluation object. + """ + if isinstance(x, BoxElementMixin): + return x + if isinstance(x, Atom): + try: + x = x.atom_to_boxes(SymbolStandardForm, evaluation) + return to_boxes(x, evaluation, options) + except NotImplementedError: + pass + + if x.has_form("MakeBoxes", None): + x_boxed = x.evaluate(evaluation) + else: + x_boxed = eval_makeboxes(x, evaluation) + if isinstance(x_boxed, BoxElementMixin): + return x_boxed + if isinstance(x_boxed, Atom): + return to_boxes(x_boxed, evaluation, options) + return eval_makeboxes(Expression(SymbolFullForm, x), evaluation) + + element_formatters[Rational] = do_format_rational element_formatters[Complex] = do_format_complex element_formatters[Expression] = do_format_expression