diff --git a/mathics/builtin/forms/print.py b/mathics/builtin/forms/print.py index 59efe7c56..01ca58e8f 100644 --- a/mathics/builtin/forms/print.py +++ b/mathics/builtin/forms/print.py @@ -12,19 +12,16 @@ below are the functions that appear in '$PrintForms' at startup. """ -from mathics.builtin.box.layout import InterpretationBox, StyleBox, TagBox +from mathics.builtin.box.layout import InterpretationBox, PaneBox, StyleBox from mathics.builtin.forms.base import FormBaseClass from mathics.core.atoms import String +from mathics.core.element import BaseElement +from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression -from mathics.core.symbols import SymbolFalse, SymbolFullForm, SymbolTrue +from mathics.core.symbols import SymbolFalse, SymbolTrue from mathics.core.systemsymbols import SymbolInputForm, SymbolOutputForm -from mathics.format.box import ( - eval_makeboxes_fullform, - eval_makeboxes_outputform, - eval_mathmlform, - eval_texform, -) -from mathics.format.form import render_input_form +from mathics.format.box.makeboxes import is_print_form_callback +from mathics.format.form import render_input_form, render_output_form sort_order = "mathics.builtin.forms.general-purpose-forms" @@ -52,19 +49,6 @@ class FullForm(FormBaseClass): in_printforms = False summary_text = "format expression in underlying M-Expression representation" - def eval_makeboxes(self, expr, fmt, evaluation): - """MakeBoxes[FullForm[expr_], fmt_]""" - fullform_box = eval_makeboxes_fullform(expr, evaluation) - style_box = StyleBox( - fullform_box, - **{ - "System`ShowSpecialCharacters": SymbolFalse, - "System`ShowStringCharacters": SymbolTrue, - "System`NumberMarks": SymbolTrue, - }, - ) - return TagBox(style_box, SymbolFullForm) - class InputForm(FormBaseClass): r""" @@ -116,26 +100,6 @@ class InputForm(FormBaseClass): in_printforms = True summary_text = "format expression suitable for Mathics3 input" - # TODO: eventually, remove OutputForm in the second argument. - def eval_makeboxes(self, expr, evaluation): - """MakeBoxes[InputForm[expr_], StandardForm|TraditionalForm]""" - - inputform = String(render_input_form(expr, evaluation)) - inputform = StyleBox( - inputform, - **{ - "System`ShowSpecialCharacters": SymbolFalse, - "System`ShowStringCharacters": SymbolTrue, - "System`NumberMarks": SymbolTrue, - }, - ) - expr = Expression(SymbolInputForm, expr) - return InterpretationBox( - inputform, - expr, - **{"System`Editable": SymbolTrue, "System`AutoDelete": SymbolTrue}, - ) - class MathMLForm(FormBaseClass): """ @@ -169,10 +133,6 @@ class MathMLForm(FormBaseClass): summary_text = "format expression as MathML commands" - def eval_mathml(self, expr, evaluation) -> Expression: - "MakeBoxes[MathMLForm[expr_], StandardForm|TraditionalForm]" - return eval_mathmlform(expr, evaluation) - class OutputForm(FormBaseClass): """ @@ -202,13 +162,6 @@ class OutputForm(FormBaseClass): formats = {"OutputForm[s_String]": "s"} summary_text = "format expression in plain text" - def eval_makeboxes(self, expr, form, evaluation): - """MakeBoxes[OutputForm[expr_], form_]""" - pane = eval_makeboxes_outputform(expr, evaluation, form) - return InterpretationBox( - pane, Expression(SymbolOutputForm, expr), **{"System`Editable": SymbolFalse} - ) - class StandardForm(FormBaseClass): """ @@ -276,8 +229,34 @@ class TeXForm(FormBaseClass): in_printforms = True summary_text = "format expression as LaTeX commands" - def eval_tex(self, expr, evaluation) -> Expression: - "MakeBoxes[TeXForm[expr_], StandardForm|TraditionalForm]" - # TeXForm by default uses `TraditionalForm` - return eval_texform(expr, evaluation) +@is_print_form_callback("System`InputForm") +def eval_makeboxes_inputform(expr: BaseElement, evaluation: Evaluation): + """MakeBoxes[InputForm[expr_], StandardForm|TraditionalForm]""" + inputform = String(render_input_form(expr, evaluation)) + inputform = StyleBox( + inputform, + **{ + "System`ShowStringCharacters": SymbolTrue, + "System`NumberMarks": SymbolTrue, + }, + ) + expr = Expression(SymbolInputForm, expr) + return InterpretationBox( + inputform, + expr, + **{"System`Editable": SymbolTrue, "System`AutoDelete": SymbolTrue}, + ) + + +@is_print_form_callback("System`OutputForm") +def eval_makeboxes_outputform(expr: BaseElement, evaluation: Evaluation, **kwargs): + """ + Build a 2D representation of the expression using only keyboard characters. + """ + + text_outputform = str(render_output_form(expr, evaluation, **kwargs)) + pane = PaneBox(String('"' + text_outputform + '"')) + return InterpretationBox( + pane, Expression(SymbolOutputForm, expr), **{"System`Editable": SymbolFalse} + ) diff --git a/mathics/builtin/list/constructing.py b/mathics/builtin/list/constructing.py index 2e084b542..9bcd1b64f 100644 --- a/mathics/builtin/list/constructing.py +++ b/mathics/builtin/list/constructing.py @@ -12,7 +12,6 @@ from itertools import permutations from typing import Optional, Tuple -from mathics.builtin.box.layout import RowBox from mathics.core.atoms import ByteArray, Integer, Integer1, is_integer_rational_or_real from mathics.core.attributes import A_HOLD_FIRST, A_LISTABLE, A_LOCKED, A_PROTECTED from mathics.core.builtin import BasePattern, Builtin, IterationFunction @@ -24,7 +23,7 @@ from mathics.core.list import ListExpression from mathics.core.symbols import Atom, Symbol from mathics.core.systemsymbols import SymbolNormal, SymbolTuples -from mathics.eval.lists import get_tuples, list_boxes +from mathics.eval.lists import get_tuples class Array(Builtin): @@ -165,13 +164,6 @@ def eval(self, elements, evaluation: Evaluation): elements_part_of_elements__ = elements.get_sequence() return ListExpression(*elements_part_of_elements__) - def eval_makeboxes(self, items, f, evaluation): - """MakeBoxes[{items___}, - (f:StandardForm|TraditionalForm)]""" - - items = items.get_sequence() - return RowBox(*list_boxes(items, f, evaluation, "{", "}")) - class Normal(Builtin): """ diff --git a/mathics/builtin/makeboxes.py b/mathics/builtin/makeboxes.py index 5c8023776..2dea6be10 100644 --- a/mathics/builtin/makeboxes.py +++ b/mathics/builtin/makeboxes.py @@ -3,10 +3,9 @@ Low-level Format definitions """ - from mathics.core.attributes import A_HOLD_ALL_COMPLETE, A_READ_PROTECTED from mathics.core.builtin import Builtin, Predefined -from mathics.format.box import eval_generic_makeboxes, format_element +from mathics.format.box import format_element # TODO: Differently from the current implementation, MakeBoxes should only # accept as its format field the symbols in `$BoxForms`. This is something to @@ -85,16 +84,12 @@ class MakeBoxes(Builtin): "MakeBoxes[expr_]": "MakeBoxes[expr, StandardForm]", # The following rule is temporal. "MakeBoxes[expr_, form:(TeXForm|MathMLForm)]": "MakeBoxes[form[expr], StandardForm]", - ( - "MakeBoxes[(form:StandardForm|TraditionalForm)" - "[expr_], StandardForm|TraditionalForm]" - ): ("MakeBoxes[expr, form]"), } summary_text = "settable low-level translator from expression to display boxes" def eval_general(self, expr, f, evaluation): """MakeBoxes[expr_, f:TraditionalForm|StandardForm]""" - return eval_generic_makeboxes(expr, f, evaluation) + return format_element(expr, evaluation, f) class ToBoxes(Builtin): @@ -126,5 +121,6 @@ def eval(self, expr, form, evaluation): form_name = form.get_name() if form_name is None: evaluation.message("ToBoxes", "boxfmt", form) + boxes = format_element(expr, evaluation, form) return boxes diff --git a/mathics/core/builtin.py b/mathics/core/builtin.py index 09d001408..dc8a9ac0e 100644 --- a/mathics/core/builtin.py +++ b/mathics/core/builtin.py @@ -347,7 +347,7 @@ def contextify_form_name(f): """Handle adding 'System`' to a form name, unless it's "" (meaning the rule applies to all forms). """ - return "" if f == "" else ensure_context(f) + return f if f in ("", "_MakeBoxes") else ensure_context(f) if isinstance(pattern, tuple): forms, pattern = pattern @@ -383,6 +383,9 @@ def contextify_form_name(f): formatvalues[form].append( Rule(pattern, parse_builtin_rule(replace), system=True) ) + + formatvalues.setdefault("_MakeBoxes", []).extend(box_rules) + for form, formatrules in formatvalues.items(): formatrules.sort(key=lambda x: x.pattern_precedence) @@ -434,10 +437,6 @@ def contextify_form_name(f): else: definitions.builtin[name] = definition - makeboxes_def = definitions.builtin["System`MakeBoxes"] - for rule in box_rules: - makeboxes_def.add_rule(rule) - # This method is used to produce generic argument mismatch errors # (tags: "argx", "argr", "argrx", "argt", or "argtu") for builtin # functions that define this as an eval method. e.g. For example diff --git a/mathics/core/load_builtin.py b/mathics/core/load_builtin.py index dfff80c10..418a7527d 100644 --- a/mathics/core/load_builtin.py +++ b/mathics/core/load_builtin.py @@ -133,11 +133,8 @@ def definition_contribute(definitions): Load the Definition objects associated to all the builtins on `Definitions` """ - # let MakeBoxes contribute first - _builtins["System`MakeBoxes"].contribute(definitions) for name, item in _builtins.items(): - if name != "System`MakeBoxes": - item.contribute(definitions) + item.contribute(definitions) from mathics.core.definitions import Definition from mathics.core.expression import ensure_context diff --git a/mathics/doc/documentation/1-Manual.mdoc b/mathics/doc/documentation/1-Manual.mdoc index f3f6ac18e..433a65c38 100644 --- a/mathics/doc/documentation/1-Manual.mdoc +++ b/mathics/doc/documentation/1-Manual.mdoc @@ -912,14 +912,13 @@ In a similar way, in the CLI, we can ask for TraditionalForm explicitly = c 'MakeBoxes' for another form: - >> MakeBoxes[TeXForm[b], form_] = "d"; >> b // TeXForm = ... You can cause a much bigger mess by overriding 'MakeBoxes' than by sticking to 'Format', e.g. generate invalid XML: - >> MakeBoxes[MathMLForm[c], form_] = "> MakeBoxes[MathMLForm[c], form_] := "> c // MathMLForm //StandardForm = RadicalBox[3, StandardForm] + if not lhs.has_form("MakeBoxes", 2): + evaluation.message("MakeBoxes", "argrx", Integer(len(lhs.elements))) + raise AssignmentException(lhs, None) + target, form = lhs.elements + # Check second argument + makeboxes_rule = Rule(lhs, rhs, system=False) + tags = [] if tags is None else tags + if upset: + tags = tags + [target.get_lookup_name()] + else: + if not tags: + tags = ["System`MakeBoxes"] + definitions = evaluation.definitions - definitions.add_rule("System`MakeBoxes", makeboxes_rule, "downvalues") - # makeboxes_defs = evaluation.definitions.builtin["System`MakeBoxes"] - # makeboxes_defs.add_rule(makeboxes_rule) + for tag in tags: + if is_protected(tag, definitions): + evaluation.message(self.get_name(), "wrsym", Symbol(tag)) + return False + definitions.add_format(tag, makeboxes_rule, "_MakeBoxes") return True diff --git a/mathics/eval/lists.py b/mathics/eval/lists.py index 130d9ffb1..f20173ed6 100644 --- a/mathics/eval/lists.py +++ b/mathics/eval/lists.py @@ -1,4 +1,3 @@ -from mathics.builtin.box.layout import RowBox from mathics.core.atoms import String from mathics.core.convert.expression import to_expression from mathics.core.exceptions import PartDepthError, PartRangeError @@ -61,6 +60,8 @@ def get_tuples(items): def list_boxes(items, f, evaluation, open=None, close=None): + from mathics.builtin.box.layout import RowBox + result = [ Expression(SymbolMakeBoxes, item, f).evaluate(evaluation) for item in items ] diff --git a/mathics/format/box/__init__.py b/mathics/format/box/__init__.py index 42d3f798c..48688dd37 100644 --- a/mathics/format/box/__init__.py +++ b/mathics/format/box/__init__.py @@ -6,9 +6,7 @@ from mathics.format.box.makeboxes import ( _boxed_string, eval_generic_makeboxes, - eval_makeboxes, eval_makeboxes_fullform, - eval_makeboxes_outputform, format_element, to_boxes, ) @@ -36,9 +34,7 @@ "eval_baseform", "eval_generic_makeboxes", "eval_infix", - "eval_makeboxes", "eval_makeboxes_fullform", - "eval_makeboxes_outputform", "eval_mathmlform", "eval_postprefix", "eval_tableform", diff --git a/mathics/format/box/makeboxes.py b/mathics/format/box/makeboxes.py index 2090080c4..e365570a7 100644 --- a/mathics/format/box/makeboxes.py +++ b/mathics/format/box/makeboxes.py @@ -9,49 +9,41 @@ from typing import List from mathics.core.atoms import Complex, Rational, String -from mathics.core.element import BaseElement, BoxElementMixin +from mathics.core.element import BaseElement, BoxElementMixin, EvalMixin from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression from mathics.core.symbols import ( Atom, Symbol, + SymbolFalse, SymbolFullForm, SymbolList, SymbolMakeBoxes, + SymbolTrue, ) from mathics.core.systemsymbols import ( # SymbolRule, SymbolRuleDelayed, + SymbolAborted, SymbolComplex, SymbolRational, SymbolStandardForm, SymbolTraditionalForm, ) +from mathics.eval.lists import list_boxes from mathics.format.box.formatvalues import do_format from mathics.format.box.precedence import parenthesize BOX_FORMS = {SymbolStandardForm, SymbolTraditionalForm} +PRINT_FORMS_CALLBACK = {} -def to_boxes(x, evaluation: Evaluation, options={}) -> BoxElementMixin: - """ - This function takes the expression ``x`` - and tries to reduce it to a ``BoxElementMixin`` - expression using 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) - return eval_makeboxes_fullform(x, evaluation) +def is_print_form_callback(head_name: str): + """Decorator for register print form callbacks""" + + def _register(func): + PRINT_FORMS_CALLBACK[head_name] = func + return func + + return _register # this temporarily replaces the _BoxedString class @@ -61,10 +53,86 @@ def _boxed_string(string: str, **options): return StyleBox(String(string), **options) +@is_print_form_callback("System`StandardForm") +def eval_makeboxes_standard_form(expr, evaluation): + from mathics.builtin.box.layout import FormBox, TagBox + + boxed = apply_makeboxes_rules(expr, evaluation, SymbolStandardForm) + boxed = FormBox(boxed, SymbolStandardForm) + boxed = TagBox(boxed, SymbolStandardForm, **{"System`Editable": SymbolTrue}) + return boxed + + +@is_print_form_callback("System`TraditionalForm") +def eval_makeboxes_traditional_form(expr, evaluation): + from mathics.builtin.box.layout import FormBox, TagBox + + boxed = apply_makeboxes_rules(expr, evaluation, SymbolTraditionalForm) + boxed = FormBox(boxed, SymbolTraditionalForm) + boxed = TagBox(boxed, SymbolTraditionalForm, **{"System`Editable": SymbolTrue}) + return boxed + + +def apply_makeboxes_rules( + expr: BaseElement, evaluation: Evaluation, form: Symbol = SymbolStandardForm +) -> BoxElementMixin: + """ + This function takes the definitions provided by the evaluation + object, and produces a boxed fullform for expr. + + Basically: MakeBoxes[expr, form] + """ + assert form in BOX_FORMS, f"{form} not in BOX_FORMS" + + def yield_rules(): + # Look + for lookup in (expr.get_lookup_name(), "System`MakeBoxes"): + definition = evaluation.definitions.get_definition(lookup) + for rule in definition.formatvalues.get("_MakeBoxes", []): + yield rule + + mb_expr = Expression(SymbolMakeBoxes, expr, form) + boxed = mb_expr + for rule in yield_rules(): + try: + boxed = rule.apply(mb_expr, evaluation, fully=False) + except OverflowError: + evaluation.message("General", "ovfl") + boxed = mb_expr + continue + if boxed is mb_expr or boxed is None or boxed.sameQ(mb_expr): + continue + if boxed is SymbolAborted: + return String("Aborted") + if isinstance(boxed, EvalMixin): + return boxed.evaluate(evaluation) + if isinstance(boxed, BoxElementMixin): + return boxed + return eval_generic_makeboxes(expr, form, evaluation) + + # TODO: evaluation is needed because `atom_to_boxes` uses it. Can we remove this # argument? +@is_print_form_callback("System`FullForm") def eval_makeboxes_fullform( - element: BaseElement, evaluation: Evaluation + element: BaseElement, evaluation: Evaluation, **kwargs +) -> BoxElementMixin: + from mathics.builtin.box.layout import StyleBox, TagBox + + result = eval_makeboxes_fullform_recursive(element, evaluation, **kwargs) + style_box = StyleBox( + result, + **{ + "System`ShowSpecialCharacters": SymbolFalse, + "System`ShowStringCharacters": SymbolTrue, + "System`NumberMarks": SymbolTrue, + }, + ) + return TagBox(style_box, SymbolFullForm) + + +def eval_makeboxes_fullform_recursive( + element: BaseElement, evaluation: Evaluation, **kwargs ) -> BoxElementMixin: """Same as MakeBoxes[FullForm[expr_], f_]""" from mathics.builtin.box.expression import BoxExpression @@ -90,7 +158,7 @@ def eval_makeboxes_fullform( head, elements = expr.head, expr.elements boxed_elements = tuple( - (eval_makeboxes_fullform(element, evaluation) for element in elements) + (eval_makeboxes_fullform_recursive(element, evaluation) for element in elements) ) # In some places it would be less verbose to use special outputs for # `List`, `Rule` and `RuleDelayed`. WMA does not that, but we do it for @@ -106,7 +174,7 @@ def eval_makeboxes_fullform( result_elements = [left] else: left, right, sep = (String(ch) for ch in ("[", "]", ",")) - result_elements = [eval_makeboxes_fullform(head, evaluation), left] + result_elements = [eval_makeboxes_fullform_recursive(head, evaluation), left] if len(boxed_elements) > 1: arguments: List[BoxElementMixin] = [] @@ -121,32 +189,24 @@ def eval_makeboxes_fullform( return RowBox(*result_elements) -def eval_makeboxes_outputform( - expr: BaseElement, evaluation: Evaluation, form: Symbol, **kwargs -): - """ - Build a 2D representation of the expression using only keyboard characters. - """ - from mathics.builtin.box.layout import PaneBox - from mathics.format.form.outputform import render_output_form - - text_outputform = str(render_output_form(expr, evaluation, **kwargs)) - elem1 = PaneBox(String('"' + text_outputform + '"')) - return elem1 - - def eval_generic_makeboxes(expr, f, evaluation): """MakeBoxes[expr_, f:TraditionalForm|StandardForm]""" from mathics.builtin.box.layout import RowBox + assert f in BOX_FORMS, f"{f} not in BOX_FORMS" if isinstance(expr, BoxElementMixin): expr = expr.to_expression() if isinstance(expr, Atom): return expr.atom_to_boxes(f, evaluation) + if expr.has_form("List", None): + return RowBox(*list_boxes(expr.elements, f, evaluation, "{", "}")) else: head = expr.head elements = expr.elements + printform_callback = PRINT_FORMS_CALLBACK.get(head.get_name(), None) + if printform_callback is not None: + return printform_callback(elements[0], evaluation) f_name = f.get_name() if f_name == "System`TraditionalForm": @@ -170,6 +230,7 @@ def eval_generic_makeboxes(expr, f, evaluation): "System`InputForm", "System`OutputForm", ): + raise ValueError sep = ", " else: sep = "," @@ -194,41 +255,41 @@ def eval_generic_makeboxes(expr, f, evaluation): return RowBox(*result) -def eval_makeboxes( - expr, evaluation: Evaluation, form=SymbolStandardForm -) -> BoxElementMixin: - """ - 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. By now, much of the formatting - # relies in rules of the form `MakeBoxes[expr, OutputForm]` - # which is wrong. - if form is SymbolFullForm: - return eval_makeboxes_fullform(expr, evaluation) - if form not in BOX_FORMS: - # print(form, "not in", BOX_FORMS) - expr = Expression(form, expr) - form = SymbolStandardForm - mb_expr = Expression(SymbolMakeBoxes, expr, form) - # print(" evaluate", mb_expr) - return mb_expr.evaluate(evaluation) - - def format_element( element: BaseElement, evaluation: Evaluation, form: Symbol, **kwargs ) -> BoxElementMixin: """ Applies formats associated to the expression, and then calls Makeboxes """ - if form is SymbolFullForm: - return eval_makeboxes_fullform(element, evaluation) - evaluation.is_boxing = True formatted_expr = do_format(element, evaluation, form) - result_box = eval_makeboxes(formatted_expr, evaluation, form) + if form not in BOX_FORMS: + formatted_expr = Expression(form, formatted_expr) + form = SymbolStandardForm + result_box = apply_makeboxes_rules(formatted_expr, evaluation, form) if isinstance(result_box, BoxElementMixin): return result_box - return eval_makeboxes_fullform(element, evaluation) + return eval_makeboxes_fullform_recursive(element, evaluation) + + +def to_boxes(x, evaluation: Evaluation, options={}) -> BoxElementMixin: + """ + This function takes the expression ``x`` + and tries to reduce it to a ``BoxElementMixin`` + expression using 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", 1, 2): + x_boxed = x.evaluate(evaluation) + if isinstance(x_boxed, BoxElementMixin): + return x_boxed + if isinstance(x_boxed, Atom): + return to_boxes(x_boxed, evaluation, options) + else: + return apply_makeboxes_rules(x, evaluation) + return eval_makeboxes_fullform_recursive(x, evaluation) diff --git a/mathics/format/box/outputforms.py b/mathics/format/box/outputforms.py index a653ee5f8..60d187f4d 100644 --- a/mathics/format/box/outputforms.py +++ b/mathics/format/box/outputforms.py @@ -1,18 +1,33 @@ import re from mathics.core.atoms import Integer, String +from mathics.core.element import BaseElement, BoxElementMixin +from mathics.core.evaluation import Evaluation from mathics.core.expression import BoxError, Expression from mathics.core.list import ListExpression -from mathics.core.symbols import SymbolFalse, SymbolFullForm, SymbolList -from mathics.core.systemsymbols import SymbolRowBox, SymbolTraditionalForm +from mathics.core.symbols import ( + Symbol, + SymbolFalse, + SymbolFullForm, + SymbolList, + SymbolTrue, +) +from mathics.core.systemsymbols import ( + SymbolMathMLForm, + SymbolTeXForm, + SymbolTraditionalForm, +) from mathics.eval.testing_expressions import expr_min -from mathics.format.box.makeboxes import format_element +from mathics.format.box.makeboxes import format_element, is_print_form_callback MULTI_NEWLINE_RE = re.compile(r"\n{2,}") -def eval_mathmlform(expr, evaluation) -> Expression: +@is_print_form_callback("System`MathMLForm") +def eval_mathmlform(expr: BaseElement, evaluation: Evaluation) -> BoxElementMixin: "MakeBoxes[MathMLForm[expr_], form_]" + from mathics.builtin.box.layout import InterpretationBox + boxes = format_element(expr, evaluation, SymbolTraditionalForm) try: mathml = boxes.boxes_to_mathml(evaluation=evaluation) @@ -34,14 +49,23 @@ def eval_mathmlform(expr, evaluation) -> Expression: mathml = '%s' % mathml mathml = '%s' % mathml # convert_box(boxes) - return Expression(SymbolRowBox, ListExpression(String(mathml))) + return InterpretationBox( + String(f'"{mathml}"'), + Expression(SymbolMathMLForm, expr), + **{"System`AutoDelete": SymbolTrue, "System`Editable": SymbolTrue}, + ) -def eval_tableform(self, table, f, evaluation, options): +def eval_tableform( + self, table: BaseElement, f: Symbol, evaluation: Evaluation, options +): """MakeBoxes[TableForm[table_], f_]""" from mathics.builtin.box.layout import GridBox from mathics.builtin.tensors import get_dimensions + if not isinstance(table, Expression): + return format_element(table, evaluation, f) + dims = len(get_dimensions(table, head=SymbolList)) depth = self.get_option(options, "TableDepth", evaluation, pop=True) options["System`TableDepth"] = depth @@ -93,7 +117,10 @@ def transform_item(item): return result -def eval_texform(expr, evaluation) -> Expression: +@is_print_form_callback("System`TeXForm") +def eval_texform(expr: BaseElement, evaluation: Evaluation) -> BoxElementMixin: + from mathics.builtin.box.layout import InterpretationBox + boxes = format_element(expr, evaluation, SymbolTraditionalForm) try: # Here we set ``show_string_characters`` to False, to reproduce @@ -114,4 +141,8 @@ def eval_texform(expr, evaluation) -> Expression: Expression(SymbolFullForm, expr).evaluate(evaluation), ) tex = "" - return Expression(SymbolRowBox, ListExpression(String(tex))) + return InterpretationBox( + String(f'"{tex}"'), + Expression(SymbolTeXForm, expr), + **{"System`AutoDelete": SymbolTrue, "System`Editable": SymbolTrue}, + ) diff --git a/test/builtin/box/test_custom_boxexpression.py b/test/builtin/box/test_custom_boxexpression.py index d3b36fdcb..aaac0e8d2 100644 --- a/test/builtin/box/test_custom_boxexpression.py +++ b/test/builtin/box/test_custom_boxexpression.py @@ -6,6 +6,7 @@ from mathics.core.builtin import Predefined from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression +from mathics.core.rules import BaseRule, FunctionApplyRule, Rule from mathics.core.symbols import Symbol SymbolCustomGraphicsBox = Symbol("CustomGraphicsBox") @@ -42,12 +43,28 @@ class CustomAtom(Predefined): "N[System`CustomAtom]": "37", } - def eval_to_boxes(self, evaluation): - "System`MakeBoxes[System`CustomAtom, StandardForm|TraditionalForm|OutputForm]" + # Since this is a Mathics3 Module which is loaded after + # the core symbols are loaded, it is safe to assume that `MakeBoxes` + # definition was already loaded. We can add then rules to it. + # This modified `contribute` method do that, adding specific + # makeboxes rules for this kind of atoms. + def contribute(self, definitions, is_pymodule=True): + super().contribute(definitions, is_pymodule) + # Add specific MakeBoxes rules + name = self.get_name() + + for pattern, function in self.get_functions("makeboxes_"): + mb_rule = FunctionApplyRule( + name, pattern, function, None, attributes=None, system=True + ) + definitions.add_format("System`MakeBoxes", mb_rule, "_MakeBoxes") + + def makeboxes_general(self, evaluation): + "System`MakeBoxes[System`CustomAtom, StandardForm|TraditionalForm]" return CustomBoxExpression(evaluation=evaluation) - def eval_to_boxes_inputform(self, evaluation): - "System`MakeBoxes[InputForm[System`CustomAtom], StandardForm|TraditionalForm|OutputForm]" + def makeboxes_inputform(self, evaluation): + "System`MakeBoxes[InputForm[System`CustomAtom], StandardForm|TraditionalForm]" return CustomBoxExpression(evaluation=evaluation) @@ -57,6 +74,22 @@ class CustomGraphicsBox(BoxExpression): options = GRAPHICS_OPTIONS attributes = A_HOLD_ALL | A_PROTECTED | A_READ_PROTECTED + # Since this is a Mathics3 Module which is loaded after + # the core symbols are loaded, it is safe to assume that `MakeBoxes` + # definition was already loaded. We can add then rules to it. + # This modified `contribute` method do that, adding specific + # makeboxes rules for this kind of BoxExpression. + def contribute(self, definitions, is_pymodule=True): + super().contribute(definitions, is_pymodule) + # Add specific MakeBoxes rules + name = self.get_name() + + for pattern, function in self.get_functions("makeboxes_"): + mb_rule = FunctionApplyRule( + name, pattern, function, None, attributes=None, system=True + ) + definitions.add_format("System`MakeBoxes", mb_rule, "_MakeBoxes") + def init(self, *elems, **options): self._elements = elems self.evaluation = options.pop("evaluation", None) @@ -65,16 +98,15 @@ def init(self, *elems, **options): def to_expression(self): return Expression(SymbolCustomGraphicsBox, *self.elements) - def eval_box(self, expr, evaluation: Evaluation, options: dict): + def makeboxes_graphics(self, expr, evaluation: Evaluation, options: dict): """System`MakeBoxes[System`Graphics[System`expr_, System`OptionsPattern[System`Graphics]], - System`StandardForm|System`TraditionalForm|System`OutputForm]""" + System`StandardForm|System`TraditionalForm]""" instance = CustomGraphicsBox(*(expr.elements), evaluation=evaluation) return instance - def eval_box_outputForm(self, expr, evaluation: Evaluation, options: dict): + def makeboxes_outputForm(self, expr, evaluation: Evaluation, options: dict): """System`MakeBoxes[System`OutputForm[System`Graphics[System`expr_, System`OptionsPattern[System`Graphics]]], System`StandardForm|System`TraditionalForm]""" - print("MakeBoxes OutputForm") instance = CustomGraphicsBox(*(expr.elements), evaluation=evaluation) return instance diff --git a/test/format/makeboxes_tests.yaml b/test/format/makeboxes_tests.yaml index 23d2e5990..4363450bb 100644 --- a/test/format/makeboxes_tests.yaml +++ b/test/format/makeboxes_tests.yaml @@ -65,10 +65,10 @@ Basic Forms: Arithmetic: FullForm: - expect: TagBox[StyleBox[RowBox[{"Plus", "[", RowBox[{"a", ",", RowBox[{"Times", "[", RowBox[{RowBox[{"-", "1"}], ",", "b"}], "]"}]}], "]"}], ShowSpecialCharacters-> False, ShowStringCharacters -> True, NumberMarks -> True], FullForm] + expect: 'TagBox[StyleBox[RowBox[{"Plus", "[", RowBox[{"a", ",", RowBox[{"Times", "[", RowBox[{RowBox[{"-", "1"}], ",", "b"}], "]"}]}], "]"}], System`ShowSpecialCharacters-> False, System`ShowStringCharacters -> True, System`NumberMarks -> True], FullForm]' expr: MakeBoxes[a-b//FullForm] InputForm: - expect: InterpretationBox[StyleBox["a - b", ShowStringCharacters -> True, NumberMarks-> True], InputForm[a - b], Editable -> True, AutoDelete -> True] + expect: InterpretationBox[StyleBox["a - b", System`ShowStringCharacters -> True, NumberMarks-> True], InputForm[a - b], Editable -> True, AutoDelete -> True] expr: MakeBoxes[a-b//InputForm] OutputForm: expect: InterpretationBox[PaneBox["\"a - b\""], OutputForm[a - b], Editable-> False] @@ -87,10 +87,10 @@ Basic Forms: expect: TagBox[FormBox[RowBox[List["F", "(", "x", ")"]], TraditionalForm], TraditionalForm, Editable-> True] expr: MakeBoxes[F[x]//TraditionalForm] FullForm: - expect: TagBox[StyleBox[RowBox[{"F", "[", "x", "]"}], ShowSpecialCharacters-> False, ShowStringCharacters -> True, NumberMarks -> True], FullForm] + expect: TagBox[StyleBox[RowBox[{"F", "[", "x", "]"}], ShowSpecialCharacters-> False, System`ShowStringCharacters -> True, System`NumberMarks -> True], FullForm] expr: MakeBoxes[F[x]//FullForm] InputForm: - expect: InterpretationBox[StyleBox["F[x]", ShowStringCharacters -> True, NumberMarks-> True], InputForm[F[x]], Editable -> True, AutoDelete -> True] + expect: InterpretationBox[StyleBox["F[x]", System`ShowStringCharacters -> True, NumberMarks-> True], InputForm[F[x]], Editable -> True, AutoDelete -> True] expr: MakeBoxes[F[x]//InputForm] OutputForm: expect: InterpretationBox[PaneBox["\"F[x]\""], OutputForm[F[x]], Editable ->False] @@ -103,10 +103,10 @@ Basic Forms: expr: MakeBoxes[F[x]//TeXForm] Integer_negative: FullForm: - expect: TagBox[StyleBox[RowBox[{"-", "14"}], ShowSpecialCharacters-> False, ShowStringCharacters -> True, NumberMarks -> True], FullForm] + expect: TagBox[StyleBox[RowBox[{"-", "14"}], ShowSpecialCharacters-> False, System`ShowStringCharacters -> True, System`NumberMarks -> True], FullForm] expr: MakeBoxes[-14//FullForm] InputForm: - expect: InterpretationBox[StyleBox["-14", ShowStringCharacters -> True, NumberMarks -> True], InputForm[-14], Editable -> True, AutoDelete -> True] + expect: InterpretationBox[StyleBox["-14", System`ShowStringCharacters -> True, System`NumberMarks -> True], InputForm[-14], Editable -> True, AutoDelete -> True] expr: MakeBoxes[-14//InputForm] OutputForm: expect: InterpretationBox[PaneBox["\"-14\""], OutputForm[-14], Editable -> False] @@ -119,10 +119,10 @@ Basic Forms: expr: MakeBoxes[-14//TeXForm] Integer_positive: FullForm: - expect: TagBox[StyleBox["14", ShowSpecialCharacters -> False, ShowStringCharacters-> True, NumberMarks -> True], FullForm] + expect: TagBox[StyleBox["14", System`ShowSpecialCharacters -> False, System`ShowStringCharacters-> True, System`NumberMarks -> True], FullForm] expr: MakeBoxes[14//FullForm] InputForm: - expect: InterpretationBox[StyleBox["14", ShowStringCharacters -> True, NumberMarks-> True], InputForm[14], Editable -> True, AutoDelete -> True] + expect: InterpretationBox[StyleBox["14", System`ShowStringCharacters -> True, NumberMarks-> True], InputForm[14], Editable -> True, AutoDelete -> True] expr: MakeBoxes[14//InputForm] OutputForm: expect: InterpretationBox[PaneBox["\"14\""], OutputForm[14], Editable -> False] @@ -135,12 +135,12 @@ Basic Forms: expr: MakeBoxes[14//TeXForm] PrecisionReal: FullForm: - expect: TagBox[StyleBox[RowBox[{"-", "14.`3."}], ShowSpecialCharacters -> False, ShowStringCharacters-> True, NumberMarks -> True], FullForm] + expect: TagBox[StyleBox[RowBox[{"-", "14.`3."}], System`ShowSpecialCharacters -> False, System`ShowStringCharacters-> True, System`NumberMarks -> True], FullForm] expr: MakeBoxes[-14.`3//FullForm] msg: "In Mathics3, precision is always an integer number." InputForm: expr: MakeBoxes[-14.`3//InputForm] - expect: InterpretationBox[StyleBox["-14.`3.", ShowStringCharacters -> True, NumberMarks-> True], InputForm[-14.`3], Editable -> True, AutoDelete -> True] + expect: InterpretationBox[StyleBox["-14.`3.", System`ShowStringCharacters -> True, NumberMarks-> True], InputForm[-14.`3], Editable -> True, AutoDelete -> True] OutputForm: expect: InterpretationBox[PaneBox["\"-14.\""], OutputForm[-14.0], Editable-> False] expr: MakeBoxes[-14.0//OutputForm] @@ -153,10 +153,10 @@ Basic Forms: -> True] Symbol: FullForm: - expect: TagBox[StyleBox["x", ShowSpecialCharacters -> False, ShowStringCharacters-> True, NumberMarks -> True], FullForm] + expect: TagBox[StyleBox["x", System`ShowSpecialCharacters -> False, System`ShowStringCharacters-> True, System`NumberMarks -> True], FullForm] expr: MakeBoxes[x//FullForm] InputForm: - expect: InterpretationBox[StyleBox["x", ShowStringCharacters -> True, NumberMarks-> True], InputForm[x], Editable -> True, AutoDelete -> True] + expect: InterpretationBox[StyleBox["x", System`ShowStringCharacters -> True, NumberMarks-> True], InputForm[x], Editable -> True, AutoDelete -> True] expr: MakeBoxes[x//InputForm] OutputForm: expect: InterpretationBox[PaneBox["\"x\""], OutputForm[x], Editable -> False] diff --git a/test/format/test_makeboxes.py b/test/format/test_makeboxes.py index e4a1386b1..31b39a6bf 100644 --- a/test/format/test_makeboxes.py +++ b/test/format/test_makeboxes.py @@ -28,8 +28,8 @@ def makeboxes_basic_forms_iterator(block): for key, tests in MAKEBOXES_TESTS[block].items(): for form, entry in tests.items(): msg = f"{key}, {form}" - expr = entry["expr"] - expect = entry["expect"] + expr = entry["expr"] + "//InputForm" + expect = entry["expect"] + "//InputForm" yield expr, expect, msg @@ -44,7 +44,7 @@ def test_makeboxes_basic_forms(str_expr, str_expected, fail_msg): str_expected, to_string_expr=True, to_string_expected=True, - hold_expected=True, + hold_expected=False, failure_message=fail_msg, ) @@ -62,7 +62,7 @@ def test_makeboxes_real(str_expr, str_expected, msg): str_expected, to_string_expr=True, to_string_expected=True, - hold_expected=True, + hold_expected=False, failure_message=msg, )