diff --git a/mathics/builtin/assignments/clear.py b/mathics/builtin/assignments/clear.py index b31bbae06..0777b699a 100644 --- a/mathics/builtin/assignments/clear.py +++ b/mathics/builtin/assignments/clear.py @@ -17,18 +17,13 @@ ) from mathics.core.builtin import Builtin, PostfixOperator from mathics.core.expression import Expression -from mathics.core.symbols import Atom, Symbol, SymbolNull, symbol_set +from mathics.core.symbols import Atom, Symbol, SymbolNull from mathics.core.systemsymbols import ( + SYSTEM_SYMBOL_VALUES, SymbolContext, SymbolContextPath, - SymbolDownValues, SymbolFailed, - SymbolMessages, - SymbolNValues, SymbolOptions, - SymbolOwnValues, - SymbolSubValues, - SymbolUpValues, ) @@ -258,7 +253,6 @@ class Unset(PostfixOperator): def eval(self, expr, evaluation): "Unset[expr_]" - head = expr.get_head() if head in SYSTEM_SYMBOL_VALUES: if len(expr.elements) != 1: @@ -284,13 +278,11 @@ def eval(self, expr, evaluation): return SymbolFailed return SymbolNull - -SYSTEM_SYMBOL_VALUES = symbol_set( - SymbolDownValues, - SymbolMessages, - SymbolNValues, - SymbolOptions, - SymbolOwnValues, - SymbolSubValues, - SymbolUpValues, -) + def eval_unset_makeboxes(self, expr, evaluation): + "Unset[expr:MakeBoxes[_, _]]" + if not evaluation.definitions.unset_format( + "System`MakeBoxes", "_MakeBoxes", expr + ): + evaluation.message("Unset", "norep", expr, Symbol("System`MakeBoxes")) + return SymbolFailed + return SymbolNull diff --git a/mathics/builtin/forms/print.py b/mathics/builtin/forms/print.py index 685a16d54..ce5a1d406 100644 --- a/mathics/builtin/forms/print.py +++ b/mathics/builtin/forms/print.py @@ -19,6 +19,7 @@ from mathics.core.expression import Expression from mathics.core.symbols import SymbolFalse, SymbolTrue from mathics.core.systemsymbols import SymbolInputForm, SymbolOutputForm +from mathics.format.box.formatvalues import do_format_element from mathics.format.box.makeboxes import is_print_form_callback from mathics.format.form import render_input_form, render_output_form @@ -256,8 +257,8 @@ def eval_makeboxes_outputform(expr: BaseElement, evaluation: Evaluation, **kwarg """ Build a 2D representation of the expression using only keyboard characters. """ - - text_outputform = str(render_output_form(expr, evaluation, **kwargs)) + f_expr = do_format_element(expr, evaluation, SymbolOutputForm) + text_outputform = str(render_output_form(f_expr, evaluation, **kwargs)) pane = PaneBox(String('"' + text_outputform + '"')) return InterpretationBox( pane, Expression(SymbolOutputForm, expr), **{"System`Editable": SymbolFalse} diff --git a/mathics/core/assignment.py b/mathics/core/assignment.py index f25dec5eb..d273518c7 100644 --- a/mathics/core/assignment.py +++ b/mathics/core/assignment.py @@ -115,13 +115,22 @@ def get_symbol_values( except KeyError: return ListExpression() - elements = [] + elements: list[BaseElement] = [] if position == "formatvalues": format_rules = definition.formatvalues for key, rules in format_rules.items(): - if key == "System`MakeBoxes": - elements.extend(rules) + if key == "_MakeBoxes": + elements.extend( + ( + Expression( + SymbolRuleDelayed, + Expression(SymbolHoldPattern, rule.pattern.expr), + rule.replace, + ) + for rule in rules + ) + ) continue if key: elements.extend( diff --git a/mathics/core/definitions.py b/mathics/core/definitions.py index 46624b2e6..3059d6081 100644 --- a/mathics/core/definitions.py +++ b/mathics/core/definitions.py @@ -107,6 +107,15 @@ def add_rule(self, rule: BaseRule) -> bool: return self.add_rule_at(rule, pos) return False + def remove_format_rule(self, lhs: BaseElement, form: str) -> bool: + """Remove a rule""" + format_rule_list = self.formatvalues.get(form, []) + for index, existing in enumerate(format_rule_list): + if existing.pattern.expr.sameQ(lhs): + del format_rule_list[index] + return True + return False + def remove_rule(self, lhs: BaseElement) -> bool: """Remove a rule""" position = get_tag_position(lhs, self.name) @@ -810,6 +819,15 @@ def unset(self, name: str, expr: BaseElement) -> bool: self.clear_definitions_cache(name) return result + def unset_format(self, name: str, form: str, expr: BaseElement) -> bool: + """Remove the rule corresponding to the expression `expr` in + the format `form` of the Definition `name`""" + definition = self.get_user_definition(self.lookup_name(name)) + result = definition.remove_format_rule(expr, form) + self.mark_changed(definition) + self.clear_definitions_cache(name) + return result + def get_config_value( self, name: str, default: Optional[int] = None ) -> Optional[int]: diff --git a/mathics/core/systemsymbols.py b/mathics/core/systemsymbols.py index 234bf694d..cfbd92b87 100644 --- a/mathics/core/systemsymbols.py +++ b/mathics/core/systemsymbols.py @@ -447,3 +447,13 @@ SymbolRepeated, SymbolRepeatedNull, ) + +SYSTEM_SYMBOL_VALUES = symbol_set( + SymbolDownValues, + SymbolMessages, + SymbolNValues, + SymbolOptions, + SymbolOwnValues, + SymbolSubValues, + SymbolUpValues, +) diff --git a/mathics/format/box/formatvalues.py b/mathics/format/box/formatvalues.py index 90210bcfd..37ea3c2d1 100644 --- a/mathics/format/box/formatvalues.py +++ b/mathics/format/box/formatvalues.py @@ -30,7 +30,7 @@ SymbolRepeatedNull, SymbolTimes, ) -from mathics.core.systemsymbols import SymbolMinus +from mathics.core.systemsymbols import SymbolInputForm, SymbolMinus, SymbolOutputForm # These Strings are used in Boxing output StringElipsis = String("...") @@ -74,12 +74,18 @@ def do_format_element( head = element.get_head() # use element.head elements = element.get_elements() include_form = False - # If the expression is enclosed by a Format - # takes the form from the expression and - # removes the format from the expression. - if head in OUTPUT_FORMS and len(elements) == 1 and isinstance(head, Symbol): - expr = elements[0] - if not form.sameQ(head): + + if elements is not None and len(elements) == 1: + # Special cases: SymbolInputForm and SymbolOutputForm + # are only processed from MakeBoxes, using the explicit + # form parameter. + if head in (SymbolInputForm, SymbolOutputForm): + return expr + # If the expression is enclosed by other Format + # takes the form from the expression and + # removes the format from the expression. + if head in OUTPUT_FORMS and isinstance(head, Symbol): + expr = elements[0] form = head include_form = True diff --git a/mathics/format/box/makeboxes.py b/mathics/format/box/makeboxes.py index 38c7a01c0..5d0f1ad81 100644 --- a/mathics/format/box/makeboxes.py +++ b/mathics/format/box/makeboxes.py @@ -26,6 +26,8 @@ from mathics.core.systemsymbols import ( # SymbolRule, SymbolRuleDelayed, SymbolAborted, SymbolComplex, + SymbolInputForm, + SymbolOutputForm, SymbolParentForm, SymbolRational, SymbolStandardForm, @@ -332,7 +334,12 @@ def format_element( """ was_boxing = evaluation.is_boxing evaluation.is_boxing = True - formatted_expr = do_format(element, evaluation, form) + # InputForm and OutputForm do format in the MakeBoxes implementation. + if form in (SymbolInputForm, SymbolOutputForm): + formatted_expr = element + else: + formatted_expr = do_format(element, evaluation, form) + if form not in evaluation.definitions.boxforms: formatted_expr = Expression(form, formatted_expr) form = SymbolStandardForm diff --git a/mathics/format/form/inputform.py b/mathics/format/form/inputform.py index 80c8f5e6d..b25dfc8ad 100644 --- a/mathics/format/form/inputform.py +++ b/mathics/format/form/inputform.py @@ -197,6 +197,15 @@ def _infix_expression_to_inputform_text( return result +@register_inputform("System`OutputForm") +def outputform(expr: Expression, evaluation: Evaluation, **kwargs): + from .outputform import render_output_form + + if len(expr.elements) != 1: + raise _WrongFormattedExpression + return render_output_form(expr.elements[0], evaluation, **kwargs) + + @register_inputform("System`Prefix") def _prefix_expression_to_inputform_text( expr: Expression, evaluation: Evaluation, **kwargs diff --git a/mathics/format/form/outputform.py b/mathics/format/form/outputform.py index 3e6d3774e..5a94d37fe 100644 --- a/mathics/format/form/outputform.py +++ b/mathics/format/form/outputform.py @@ -442,7 +442,9 @@ def _infix_outputform_text(expr: Expression, evaluation: Evaluation, **kwargs) - @register_outputform("System`InputForm") def inputform(expr: Expression, evaluation: Evaluation, **kwargs): - return render_input_form(expr, evaluation, **kwargs) + if len(expr.elements) != 1: + raise _WrongFormattedExpression + return render_input_form(expr.elements[0], evaluation, **kwargs) @register_outputform("System`List") diff --git a/test/format/test_makeboxes.py b/test/format/test_makeboxes.py index 6969d3854..1626df519 100644 --- a/test/format/test_makeboxes.py +++ b/test/format/test_makeboxes.py @@ -172,6 +172,7 @@ def test_makeboxes_others_fail(str_expr, str_expected, msg): @pytest.mark.parametrize( ("str_expr", "str_expected", "msg"), [ + (None, None, None), ( r"MakeBoxes[G[F[2.]], StandardForm]", r'RowBox[{"G","[",RowBox[{"F","[","2.`","]"}],"]"}]', @@ -199,7 +200,7 @@ def test_makeboxes_others_fail(str_expr, str_expected, msg): ), ( r"MakeBoxes[OutputForm[G[F[3.002]]], StandardForm]", - r'InterpretationBox[PaneBox["\"G[{Formatted f, {3.002}, Standard}]\""], G[{Formatted f, {3.002}, Standard}], Editable -> False]', + r'InterpretationBox[PaneBox["\"G[{Formatted f, {3.002}, Standard}]\""], OutputForm[G[F[3.002]]], Editable -> False]', "with the defined OutputForm", ), ( @@ -215,7 +216,7 @@ def test_makeboxes_others_fail(str_expr, str_expected, msg): # InterpretationBox is now used here... = InterpretationBox[PaneBox["\"G[{Formatted f, {3.002}, Output}]\""], OutputForm[G[F[3.002`]]], Rule[Editable, False]] ( r"MakeBoxes[OutputForm[G[F[3.002]]], StandardForm]", - r'RowBox[{"G","[",RowBox[{"{",RowBox[{"\"Formatted f\"",", ",RowBox[{"{","3.002","}"}],", ","\"Standard\""}],"}"}],"]"}]', + r'InterpretationBox[PaneBox["\"G[{Formatted f, {3.002}, Output}]\""], OutputForm[G[F[3.002]]], Rule[Editable, False]]', "Test Custom OutputForm", ), ( @@ -225,8 +226,8 @@ def test_makeboxes_others_fail(str_expr, str_expected, msg): ), ( r"MakeBoxes[F[x_], fmt_]=.; MakeBoxes[G[F[2.]], StandardForm]", - r'RowBox[{"G","[",RowBox[{"F","[","2.","]"}],"]"}]', - "Clear MakeBoxes rule", + r'RowBox[{"G", "[", RowBox[{"F", "[", "2.`", "]"}], "]"}]', + "Clear MakeBoxes rule - Fails because <> does not clear the rule.", ), ], ) @@ -260,6 +261,7 @@ def test_makeboxes_custom(str_expr, str_expected, msg): @pytest.mark.parametrize( ("str_expr", "str_expected", "msg"), [ + (None, None, None), ( r'MakeBoxes[F[x__], fmt_] := RowBox[{"F", "<~", RowBox[MakeBoxes[#1, fmt] & /@ List[x]], "~>"}]', "Null",