From 26a52b3cbbd21bee79fe24599762c702f865a723 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Mon, 19 Jan 2026 14:59:17 -0300 Subject: [PATCH 01/31] more tweaks in InputForm --- mathics/builtin/assignments/types.py | 2 +- mathics/builtin/atomic/symbols.py | 20 +++++++++------- mathics/format/form/inputform.py | 35 +++++++++++++++++++++------- test/builtin/atomic/test_symbols.py | 2 +- 4 files changed, 41 insertions(+), 18 deletions(-) diff --git a/mathics/builtin/assignments/types.py b/mathics/builtin/assignments/types.py index 1f2b7f105..f40ddb99d 100644 --- a/mathics/builtin/assignments/types.py +++ b/mathics/builtin/assignments/types.py @@ -155,7 +155,7 @@ class SubValues(Builtin): >> SubValues[f] = {HoldPattern[f[2][x_]] :> x ^ 2, HoldPattern[f[1][x_]] :> x} >> Definition[f] - = f[2][x_] = x ^ 2 + = f[2][x_] = x^2 . . f[1][x_] = x """ diff --git a/mathics/builtin/atomic/symbols.py b/mathics/builtin/atomic/symbols.py index eb0acafff..dce1f5531 100644 --- a/mathics/builtin/atomic/symbols.py +++ b/mathics/builtin/atomic/symbols.py @@ -78,6 +78,7 @@ def format_rule( {"System`Definition": Expression(SymbolHoldForm, SymbolDefinition)} ) ) + r = Expression(SymbolInputForm, r) lines.append( Expression( SymbolHoldForm, @@ -107,7 +108,9 @@ def gather_rules(definition: Definition): for rule in rules: def lhs(expr): - return Expression(SymbolFormat, expr, Symbol(format)) + return Expression( + SymbolInputForm, Expression(SymbolFormat, expr, Symbol(format)) + ) def rhs(expr): if expr.has_form("Infix", None): @@ -235,7 +238,7 @@ class Definition(Builtin): >> f[x_] := x ^ 2 >> g[f] ^:= 2 >> Definition[f] - = f[x_] = x ^ 2 + = f[x_] = x^2 . . g[f] ^= 2 @@ -266,19 +269,20 @@ class Definition(Builtin): . . N[r, MachinePrecision] = 3.5 . - . Format[args___, MathMLForm] = Infix[{args}, "~"] + . Format[r[args___], MathMLForm] = Infix[{args}, "~"] . - . Format[args___, OutputForm] = Infix[{args}, "~"] + . Format[r[args___], OutputForm] = Infix[{args}, "~"] . - . Format[args___, StandardForm] = Infix[{args}, "~"] + . Format[r[args___], StandardForm] = Infix[{args}, "~"] . - . Format[args___, TeXForm] = Infix[{args}, "~"] + . Format[r[args___], TeXForm] = Infix[{args}, "~"] . - . Format[args___, TraditionalForm] = Infix[{args}, "~"] + . Format[r[args___], TraditionalForm] = Infix[{args}, "~"] . . Default[r, 1] = 2 . - . Options[r] = {Opt -> 3} + .Options[r] = {Opt -> 3} + . For 'ReadProtected' symbols, 'Definition' just prints attributes, default values and options: >> SetAttributes[r, ReadProtected] diff --git a/mathics/format/form/inputform.py b/mathics/format/form/inputform.py index ea9969441..b5eff3e66 100644 --- a/mathics/format/form/inputform.py +++ b/mathics/format/form/inputform.py @@ -91,7 +91,7 @@ def render_input_form(expr: BaseElement, evaluation: Evaluation, **kwargs) -> st @register_inputform("System`Association") def _association_expression_to_inputform_text( expr: Expression, evaluation: Evaluation, **kwargs -): +) -> str: elements = expr.elements result = ", ".join( [render_input_form(elem, evaluation, **kwargs) for elem in elements] @@ -210,7 +210,8 @@ def _prefix_expression_to_inputform_text( operand = operands[0] kwargs["encoding"] = kwargs.get("encoding", SYSTEM_CHARACTER_ENCODING) target_txt = render_input_form(operand, evaluation, **kwargs) - target_txt = parenthesize(precedence, operand, target_txt, True) + parenthesized = group in (None, SymbolRight, SymbolNonAssociative) + target_txt = parenthesize(precedence, operand, target_txt, parenthesized) return str(op_head) + target_txt @@ -229,15 +230,16 @@ def _postfix_expression_to_inputform_text( if len(operands) != 1 or not isinstance(op_head, str): raise _WrongFormattedExpression operand = operands[0] + parenthesized = group in (None, SymbolRight, SymbolNonAssociative) inputform_txt = render_input_form(operand, evaluation, **kwargs) - target_txt = parenthesize(precedence, operand, inputform_txt, True) + target_txt = parenthesize(precedence, operand, inputform_txt, parenthesized) return target_txt + op_head @register_inputform("System`Blank") @register_inputform("System`BlankSequence") @register_inputform("System`BlankNullSequence") -def _blanks(expr: Expression, evaluation: Evaluation, **kwargs): +def _blanks(expr: Expression, evaluation: Evaluation, **kwargs) -> str: elements = expr.elements if len(elements) > 1: return _generic_to_inputform_text(expr, evaluation, **kwargs) @@ -252,8 +254,25 @@ def _blanks(expr: Expression, evaluation: Evaluation, **kwargs): return _generic_to_inputform_text(expr, evaluation, **kwargs) +@register_inputform("System`Optional") +def _optional(expr: Expression, evaluation: Evaluation, **kwargs) -> str: + name: str = "" + elements = expr.elements + if len(elements) != 1: + raise _WrongFormattedExpression + operand = elements[0] + if operand.has_form("Pattern", 2): + name = render_input_form(operand.elements[0], evaluation, **kwargs) + operand = operand.elements[1] + + if not operand.has_form(("Blank", "BlankNullSequence", "BlankSequence"), 0): + raise _WrongFormattedExpression + + return name + BLANKS_TO_STRINGS[operand.head] + "." + + @register_inputform("System`Pattern") -def _pattern(expr: Expression, evaluation: Evaluation, **kwargs): +def _pattern(expr: Expression, evaluation: Evaluation, **kwargs) -> str: elements = expr.elements if len(elements) != 2: return _generic_to_inputform_text(expr, evaluation, **kwargs) @@ -263,7 +282,7 @@ def _pattern(expr: Expression, evaluation: Evaluation, **kwargs): @register_inputform("System`Rule") @register_inputform("System`RuleDelayed") -def _rule_to_inputform_text(expr, evaluation: Evaluation, **kwargs): +def _rule_to_inputform_text(expr, evaluation: Evaluation, **kwargs) -> str: """Rule|RuleDelayed[{...}]""" head = expr.head elements = expr.elements @@ -280,7 +299,7 @@ def _rule_to_inputform_text(expr, evaluation: Evaluation, **kwargs): @register_inputform("System`Slot") def _slot_expression_to_inputform_text( expr: Expression, evaluation: Evaluation, **kwargs -): +) -> str: elements = expr.elements if len(elements) != 1: raise _WrongFormattedExpression @@ -298,7 +317,7 @@ def _slot_expression_to_inputform_text( @register_inputform("System`SlotSequence") def _slotsequence_expression_to_inputform_text( expr: Expression, evaluation: Evaluation, **kwargs -): +) -> str: elements = expr.elements if len(elements) != 1: raise _WrongFormattedExpression diff --git a/test/builtin/atomic/test_symbols.py b/test/builtin/atomic/test_symbols.py index ec44b7974..2a86f4f7b 100644 --- a/test/builtin/atomic/test_symbols.py +++ b/test/builtin/atomic/test_symbols.py @@ -47,7 +47,7 @@ def test_downvalues(): ( "Information[f]", tuple(), - "f[x] returns the square of x\n\nf[x_] = x ^ 2\n\ng[f] ^= 2\n", + "f[x] returns the square of x\n\nf[x_] = x^2\n\ng[f] ^= 2\n", None, ), ('Length[Names["System`*"]] > 350', None, "True", None), From e67e06f8c9407daa06b7a1a6540effdb5425c6c2 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Mon, 19 Jan 2026 16:46:52 -0300 Subject: [PATCH 02/31] improve OptionValues InputForm. Adding documentation --- mathics/builtin/arithfns/basic.py | 136 ++------------------------ mathics/builtin/patterns/defaults.py | 7 ++ mathics/format/form/inputform.py | 23 ++++- mathics/format/form/util.py | 4 +- mathics/format/form_rule/arithfns.py | 139 +++++++++++++++++++++++++++ 5 files changed, 176 insertions(+), 133 deletions(-) create mode 100644 mathics/format/form_rule/arithfns.py diff --git a/mathics/builtin/arithfns/basic.py b/mathics/builtin/arithfns/basic.py index 0e3820823..27f300298 100644 --- a/mathics/builtin/arithfns/basic.py +++ b/mathics/builtin/arithfns/basic.py @@ -7,19 +7,14 @@ """ -from mathics.builtin.arithmetic import create_infix from mathics.core.atoms import ( Complex, Integer, Integer1, Integer3, - Integer310, IntegerM1, Number, - Rational, RationalOneHalf, - Real, - String, ) from mathics.core.attributes import ( A_FLAT, @@ -37,32 +32,20 @@ PrefixOperator, SympyFunction, ) -from mathics.core.convert.expression import to_expression -from mathics.core.convert.sympy import from_sympy from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression -from mathics.core.list import ListExpression -from mathics.core.symbols import ( - Symbol, - SymbolDivide, - SymbolHoldForm, - SymbolNull, - SymbolPower, - SymbolTimes, -) +from mathics.core.symbols import Symbol, SymbolNull, SymbolPower, SymbolTimes from mathics.core.systemsymbols import ( SymbolBlank, SymbolComplexInfinity, SymbolIndeterminate, - SymbolInfix, - SymbolLeft, - SymbolMinus, SymbolPattern, SymbolSequence, ) from mathics.eval.arithfns.basic import eval_Plus, eval_Times from mathics.eval.nevaluator import eval_N from mathics.eval.numerify import numerify +from mathics.format.form_rule.arithfns import format_plus, format_times class CubeRoot(Builtin): @@ -303,54 +286,7 @@ def eval(self, elements, evaluation: Evaluation): def format_plus(self, items, evaluation: Evaluation): "Plus[items__]" - - def negate(item): # -> Expression (see FIXME below) - if item.has_form("Times", 2, None): - if isinstance(item.elements[0], Number): - first, *rest = item.elements - first = -first - if first.sameQ(Integer1): - if len(rest) == 1: - return rest[0] - return Expression(SymbolTimes, *rest) - - return Expression(SymbolTimes, first, *rest) - else: - return Expression(SymbolTimes, IntegerM1, *item.elements) - elif isinstance(item, Number): - return from_sympy(-item.to_sympy()) - else: - return Expression(SymbolTimes, IntegerM1, item) - - def is_negative(value) -> bool: - if isinstance(value, Complex): - real, imag = value.to_sympy().as_real_imag() - if real <= 0 and imag <= 0: - return True - elif isinstance(value, Number) and value.to_sympy() < 0: - return True - return False - - elements = items.get_sequence() - values = [to_expression(SymbolHoldForm, element) for element in elements[:1]] - ops = [] - for element in elements[1:]: - if ( - element.has_form("Times", 1, None) and is_negative(element.elements[0]) - ) or is_negative(element): - element = negate(element) - op = "-" - else: - op = "+" - values.append(Expression(SymbolHoldForm, element)) - ops.append(String(op)) - return Expression( - SymbolInfix, - ListExpression(*values), - ListExpression(*ops), - Integer310, - SymbolLeft, - ) + return format_plus(items, evaluation) class Power(InfixOperator, MPMathFunction): @@ -645,74 +581,16 @@ def eval(self, elements, evaluation: Evaluation): def format_times(self, items, evaluation: Evaluation, op="\u2062"): "Times[items__]" - - def inverse(item): - if item.has_form("Power", 2) and isinstance( # noqa - item.elements[1], (Integer, Rational, Real) - ): - neg = -item.elements[1] - if neg.sameQ(Integer1): - return item.elements[0] - else: - return Expression(SymbolPower, item.elements[0], neg) - else: - return item - - items = items.get_sequence() - if len(items) < 2: - return - positive = [] - negative = [] - for item in items: - if ( - item.has_form("Power", 2) - and isinstance(item.elements[1], (Integer, Rational, Real)) - and item.elements[1].to_sympy() < 0 - ): # nopep8 - negative.append(inverse(item)) - elif isinstance(item, Rational): - numerator = item.numerator() - if not numerator.sameQ(Integer1): - positive.append(numerator) - negative.append(item.denominator()) - else: - positive.append(item) - - if positive and hasattr(positive[0], "value") and positive[0].value == -1: - del positive[0] - minus = True - else: - minus = False - positive = [Expression(SymbolHoldForm, item) for item in positive] - negative = [Expression(SymbolHoldForm, item) for item in negative] - if positive: - positive = create_infix(positive, op, 400, "Left") - else: - positive = Integer1 - if negative: - negative = create_infix(negative, op, 400, "Left") - result = Expression( - SymbolDivide, - Expression(SymbolHoldForm, positive), - Expression(SymbolHoldForm, negative), - ) - else: - result = positive - if minus: - result = Expression( - SymbolMinus, result - ) # Expression('PrecedenceForm', result, 481)) - result = Expression(SymbolHoldForm, result) - return result + return format_times(items, evaluation, op) def format_inputform(self, items, evaluation): "(InputForm,): Times[items__]" - return self.format_times(items, evaluation, op="*") + return format_times(items, evaluation, op="*") def format_standardform(self, items, evaluation): "(StandardForm,): Times[items__]" - return self.format_times(items, evaluation, op=" ") + return format_times(items, evaluation, op=" ") def format_outputform(self, items, evaluation): "(OutputForm,): Times[items__]" - return self.format_times(items, evaluation, op=" ") + return format_times(items, evaluation, op=" ") diff --git a/mathics/builtin/patterns/defaults.py b/mathics/builtin/patterns/defaults.py index ceef0f6c0..8183fc424 100644 --- a/mathics/builtin/patterns/defaults.py +++ b/mathics/builtin/patterns/defaults.py @@ -51,6 +51,13 @@ class Optional(InfixOperator, PatternObject): >> FullForm[s_.] = Optional[Pattern[s, Blank[]]] + 'InputForm' shows it in its 'Infix' or 'Postfix' form depending on the \ + number of parameters: + >> InputForm[s_:a+b^2] + = s_ : a + b^2 + Following WMA conventions, + >> InputForm[Optional[s__]] + = (s__.) >> Default[h, k_] := k >> h[a] /. h[x_, y_.] -> {x, y} = {a, 2} diff --git a/mathics/format/form/inputform.py b/mathics/format/form/inputform.py index b5eff3e66..635d92795 100644 --- a/mathics/format/form/inputform.py +++ b/mathics/format/form/inputform.py @@ -46,6 +46,7 @@ from .util import ( ARITHMETIC_OPERATOR_STRINGS, BLANKS_TO_STRINGS, + PRECEDENCE_BOX_GROUP, _WrongFormattedExpression, collect_in_pre_post_arguments, get_operator_str, @@ -160,6 +161,14 @@ def _infix_expression_to_inputform_text( ) # Infix needs at least two operands: if len(operands) < 2: + if ( + ops_lst[0] == "~" + and group is SymbolNone + and precedence == PRECEDENCE_BOX_GROUP + ): + expr = Expression(expr.get_head(), expr.elements[0]) + return _generic_to_inputform_text(expr, evaluation, **kwargs) + raise _WrongFormattedExpression # Process the first operand: @@ -257,9 +266,15 @@ def _blanks(expr: Expression, evaluation: Evaluation, **kwargs) -> str: @register_inputform("System`Optional") def _optional(expr: Expression, evaluation: Evaluation, **kwargs) -> str: name: str = "" + post: str = "" elements = expr.elements - if len(elements) != 1: + if not expr.has_form("Optional", 1, 2): raise _WrongFormattedExpression + if len(elements) == 2: + post = ":" + render_input_form(elements[1], evaluation, **kwargs) + else: + post = "." + operand = elements[0] if operand.has_form("Pattern", 2): name = render_input_form(operand.elements[0], evaluation, **kwargs) @@ -268,7 +283,11 @@ def _optional(expr: Expression, evaluation: Evaluation, **kwargs) -> str: if not operand.has_form(("Blank", "BlankNullSequence", "BlankSequence"), 0): raise _WrongFormattedExpression - return name + BLANKS_TO_STRINGS[operand.head] + "." + result = name + BLANKS_TO_STRINGS[operand.head] + post + # `name_.` cannot be reentered if it is not wrapped in parenthesis: + if post == ".": + result = f"({result})" + return result @register_inputform("System`Pattern") diff --git a/mathics/format/form/util.py b/mathics/format/form/util.py index 0bca29f07..65054b916 100644 --- a/mathics/format/form/util.py +++ b/mathics/format/form/util.py @@ -82,7 +82,7 @@ def collect_in_pre_post_arguments( raise _WrongFormattedExpression head = expr.head - group = None + group = SymbolNone precedence = PRECEDENCE_BOX_GROUP operands = list(target.elements) @@ -122,7 +122,7 @@ def collect_in_pre_post_arguments( if group not in (SymbolNone, SymbolLeft, SymbolRight, SymbolNonAssociative): raise _WrongFormattedExpression if group is SymbolNone: - group = None + group = SymbolNone return operands, operator_spec, precedence, group diff --git a/mathics/format/form_rule/arithfns.py b/mathics/format/form_rule/arithfns.py new file mode 100644 index 000000000..deae84c6b --- /dev/null +++ b/mathics/format/form_rule/arithfns.py @@ -0,0 +1,139 @@ +""" +Format functions for arithmetic expressions. + +""" + +from mathics.builtin.arithmetic import create_infix +from mathics.core.atoms import ( + Complex, + Integer, + Integer1, + IntegerM1, + Number, + Rational, + Real, + String, +) +from mathics.core.convert.expression import to_expression +from mathics.core.convert.sympy import from_sympy +from mathics.core.evaluation import Evaluation +from mathics.core.expression import Expression +from mathics.core.list import ListExpression +from mathics.core.symbols import SymbolDivide, SymbolHoldForm, SymbolPower, SymbolTimes +from mathics.core.systemsymbols import SymbolInfix, SymbolLeft, SymbolMinus +from mathics.format.form.util import PRECEDENCE_PLUS, PRECEDENCE_TIMES + + +def format_plus(items, evaluation: Evaluation): + """format Times[___] using `op` as operator""" + + def negate(item): # -> Expression (see FIXME below) + if item.has_form("Times", 2, None): + if isinstance(item.elements[0], Number): + first, *rest = item.elements + first = -first + if first.sameQ(Integer1): + if len(rest) == 1: + return rest[0] + return Expression(SymbolTimes, *rest) + + return Expression(SymbolTimes, first, *rest) + else: + return Expression(SymbolTimes, IntegerM1, *item.elements) + elif isinstance(item, Number): + return from_sympy(-item.to_sympy()) + else: + return Expression(SymbolTimes, IntegerM1, item) + + def is_negative(value) -> bool: + if isinstance(value, Complex): + real, imag = value.to_sympy().as_real_imag() + if real <= 0 and imag <= 0: + return True + elif isinstance(value, Number) and value.to_sympy() < 0: + return True + return False + + elements = items.get_sequence() + values = [to_expression(SymbolHoldForm, element) for element in elements[:1]] + ops = [] + for element in elements[1:]: + if ( + element.has_form("Times", 1, None) and is_negative(element.elements[0]) + ) or is_negative(element): + element = negate(element) + op = "-" + else: + op = "+" + values.append(Expression(SymbolHoldForm, element)) + ops.append(String(op)) + return Expression( + SymbolInfix, + ListExpression(*values), + ListExpression(*ops), + Integer(PRECEDENCE_PLUS), + SymbolLeft, + ) + + +def format_times(items, evaluation, op="\u2062"): + """format Times[___] using `op` as operator""" + + def inverse(item): + if item.has_form("Power", 2) and isinstance( # noqa + item.elements[1], (Integer, Rational, Real) + ): + neg = -item.elements[1] + if neg.sameQ(Integer1): + return item.elements[0] + else: + return Expression(SymbolPower, item.elements[0], neg) + else: + return item + + items = items.get_sequence() + if len(items) < 2: + return + positive = [] + negative = [] + for item in items: + if ( + item.has_form("Power", 2) + and isinstance(item.elements[1], (Integer, Rational, Real)) + and item.elements[1].to_sympy() < 0 + ): # nopep8 + negative.append(inverse(item)) + elif isinstance(item, Rational): + numerator = item.numerator() + if not numerator.sameQ(Integer1): + positive.append(numerator) + negative.append(item.denominator()) + else: + positive.append(item) + + if positive and hasattr(positive[0], "value") and positive[0].value == -1: + del positive[0] + minus = True + else: + minus = False + positive = [Expression(SymbolHoldForm, item) for item in positive] + negative = [Expression(SymbolHoldForm, item) for item in negative] + if positive: + positive = create_infix(positive, op, PRECEDENCE_TIMES, "Left") + else: + positive = Integer1 + if negative: + negative = create_infix(negative, op, PRECEDENCE_TIMES, "Left") + result = Expression( + SymbolDivide, + Expression(SymbolHoldForm, positive), + Expression(SymbolHoldForm, negative), + ) + else: + result = positive + if minus: + result = Expression( + SymbolMinus, result + ) # Expression('PrecedenceForm', result, 481)) + result = Expression(SymbolHoldForm, result) + return result From f8437561e94a21f672c46889f185842ec3c7fe58 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Mon, 19 Jan 2026 17:24:55 -0300 Subject: [PATCH 03/31] improve Definition --- mathics/builtin/atomic/symbols.py | 47 ++++++++++++++----------------- mathics/format/form/inputform.py | 9 ------ test/builtin/drawing/fonts.py | 6 ++-- 3 files changed, 24 insertions(+), 38 deletions(-) diff --git a/mathics/builtin/atomic/symbols.py b/mathics/builtin/atomic/symbols.py index dce1f5531..a9253138b 100644 --- a/mathics/builtin/atomic/symbols.py +++ b/mathics/builtin/atomic/symbols.py @@ -62,6 +62,11 @@ def gather_and_format_definition_rules( """Return a list of lines describing the definition of `symbol`""" lines = [] + def rhs_format(expr): + if expr.has_form("Infix", None): + expr = Expression(Expression(SymbolHoldForm, expr.head), *expr.elements) + return expr + def format_rule( rule: Rule, up: bool = False, @@ -73,18 +78,17 @@ def format_rule( """ evaluation.check_stopped() if isinstance(rule, Rule): - r = rhs( + lhs_pat = Expression(SymbolInputForm, lhs(rule.pattern.expr)) + repl_expr = rhs( rule.replace.replace_vars( {"System`Definition": Expression(SymbolHoldForm, SymbolDefinition)} ) ) - r = Expression(SymbolInputForm, r) + repl_expr = Expression(SymbolInputForm, repl_expr) lines.append( Expression( SymbolHoldForm, - Expression( - up and SymbolUpSet or SymbolSet, lhs(rule.pattern.expr), r - ), + Expression(up and SymbolUpSet or SymbolSet, lhs_pat, repl_expr), ) ) @@ -104,22 +108,13 @@ def gather_rules(definition: Definition): for rule in definition.nvalues: format_rule(rule) formats = sorted(definition.formatvalues.items()) - for format, rules in formats: + for form_name, rules in formats: for rule in rules: - def lhs(expr): - return Expression( - SymbolInputForm, Expression(SymbolFormat, expr, Symbol(format)) - ) - - def rhs(expr): - if expr.has_form("Infix", None): - expr = Expression( - Expression(SymbolHoldForm, expr.head), *expr.elements - ) - return Expression(SymbolInputForm, expr) + def lhs_format(expr): + return Expression(SymbolFormat, expr, Symbol(form_name)) - format_rule(rule, lhs=lhs, rhs=rhs) + format_rule(rule, lhs=lhs_format, rhs=rhs_format) name = symbol.get_name() if not name: @@ -244,7 +239,7 @@ class Definition(Builtin): Definition of a rather evolved (though meaningless) symbol: >> Attributes[r] := {Orderless} - >> Format[r[args___]] := Infix[{args}, "~"] + >> Format[r[args___]] := Infix[{args}, "#"] >> N[r] := 3.5 >> Default[r, 1] := 2 >> r::msg := "My message" @@ -253,7 +248,7 @@ class Definition(Builtin): Some usage: >> r[z, x, y] - = x ~ y ~ z + = x # y # z >> N[r] = 3.5 >> r[] @@ -265,19 +260,19 @@ class Definition(Builtin): >> Definition[r] = Attributes[r] = {Orderless} . - . arg_. ~ OptionsPattern[r] = {arg, OptionValue[Opt]} + . r[(arg_.), OptionsPattern[r]] = {arg, OptionValue[Opt]} . . N[r, MachinePrecision] = 3.5 . - . Format[r[args___], MathMLForm] = Infix[{args}, "~"] + . Format[r[args___], MathMLForm] = Infix[{args}, "#"] . - . Format[r[args___], OutputForm] = Infix[{args}, "~"] + . Format[r[args___], OutputForm] = Infix[{args}, "#"] . - . Format[r[args___], StandardForm] = Infix[{args}, "~"] + . Format[r[args___], StandardForm] = Infix[{args}, "#"] . - . Format[r[args___], TeXForm] = Infix[{args}, "~"] + . Format[r[args___], TeXForm] = Infix[{args}, "#"] . - . Format[r[args___], TraditionalForm] = Infix[{args}, "~"] + . Format[r[args___], TraditionalForm] = Infix[{args}, "#"] . . Default[r, 1] = 2 . diff --git a/mathics/format/form/inputform.py b/mathics/format/form/inputform.py index 635d92795..bb16e4e04 100644 --- a/mathics/format/form/inputform.py +++ b/mathics/format/form/inputform.py @@ -46,7 +46,6 @@ from .util import ( ARITHMETIC_OPERATOR_STRINGS, BLANKS_TO_STRINGS, - PRECEDENCE_BOX_GROUP, _WrongFormattedExpression, collect_in_pre_post_arguments, get_operator_str, @@ -161,14 +160,6 @@ def _infix_expression_to_inputform_text( ) # Infix needs at least two operands: if len(operands) < 2: - if ( - ops_lst[0] == "~" - and group is SymbolNone - and precedence == PRECEDENCE_BOX_GROUP - ): - expr = Expression(expr.get_head(), expr.elements[0]) - return _generic_to_inputform_text(expr, evaluation, **kwargs) - raise _WrongFormattedExpression # Process the first operand: diff --git a/test/builtin/drawing/fonts.py b/test/builtin/drawing/fonts.py index 078db2ec5..da2d5a9c7 100644 --- a/test/builtin/drawing/fonts.py +++ b/test/builtin/drawing/fonts.py @@ -8,13 +8,13 @@ SVG_NS = "http://www.w3.org/2000/svg" ET.register_namespace("", SVG_NS) -css = f""" -text, tspan, * {{ +css = """ +text, tspan, * { font-family: "Noto Sans" !important; font-size: 10px !important; font-style: normal !important; font-weight: regular !important; -}} +} """.strip() From 14ddd3036ab5455999ceb2878e3a522fa4d6b881 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Mon, 19 Jan 2026 17:43:05 -0300 Subject: [PATCH 04/31] Fix parenthesized condition in inputform.py --- mathics/format/form/inputform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mathics/format/form/inputform.py b/mathics/format/form/inputform.py index bb16e4e04..177609de3 100644 --- a/mathics/format/form/inputform.py +++ b/mathics/format/form/inputform.py @@ -163,7 +163,7 @@ def _infix_expression_to_inputform_text( raise _WrongFormattedExpression # Process the first operand: - parenthesized = group in (SymbolNone, SymbolRight, SymbolNonAssociative) + parenthesized = group in (SymbolRight, SymbolNonAssociative) operand = operands[0] result = str(render_input_form(operand, evaluation, **kwargs)) result = parenthesize(precedence, operand, result, parenthesized) From d7cdc45abad6e2f60c7d7b3dc4c7a61a5867b3c9 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Mon, 19 Jan 2026 17:53:44 -0300 Subject: [PATCH 05/31] Use new implementation of OutputForm --- mathics/builtin/datentime.py | 4 +- mathics/builtin/drawing/graphics3d.py | 5 - mathics/builtin/forms/data.py | 34 +-- mathics/builtin/forms/print.py | 27 ++- mathics/builtin/functional/application.py | 6 +- mathics/builtin/graphics.py | 2 +- mathics/builtin/kernel_sessions.py | 4 +- mathics/builtin/layout.py | 6 +- mathics/builtin/list/associations.py | 2 +- mathics/builtin/list/constructing.py | 2 +- mathics/builtin/list/eol.py | 7 +- mathics/builtin/mainloop.py | 1 + mathics/builtin/makeboxes.py | 13 +- mathics/builtin/messages.py | 5 +- mathics/builtin/numbers/calculus.py | 1 - mathics/builtin/patterns/basic.py | 16 +- mathics/builtin/patterns/composite.py | 2 +- mathics/builtin/patterns/defaults.py | 4 +- mathics/core/builtin.py | 1 - mathics/core/parser/__init__.py | 3 +- mathics/doc/documentation/1-Manual.mdoc | 26 +- mathics/eval/strings.py | 12 +- mathics/format/box/__init__.py | 2 + mathics/format/box/makeboxes.py | 32 ++- mathics/format/form/outputform.py | 3 +- mathics/session.py | 2 +- test/format/format_tests.yaml | 280 ++++++++++------------ 27 files changed, 241 insertions(+), 261 deletions(-) diff --git a/mathics/builtin/datentime.py b/mathics/builtin/datentime.py index c89be0132..659cc8ad0 100644 --- a/mathics/builtin/datentime.py +++ b/mathics/builtin/datentime.py @@ -593,7 +593,7 @@ class DateObject(_DateFormat, ImmutableValueMixin): >> DateObject[{2020, 4, 15}] - = [...] + = ... """ fmt_keywords = { @@ -697,7 +697,7 @@ def eval_makeboxes( fmt: BaseElement, evaluation: Evaluation, ) -> Optional[Expression]: - "MakeBoxes[DateObject[datetime_List, gran_, cal_, tz_, fmt_], StandardForm|TraditionalForm|OutputForm]" + "MakeBoxes[DateObject[datetime_List, gran_, cal_, tz_, fmt_], StandardForm|TraditionalForm]" # TODO: if fmt.sameQ(SymbolAutomatic): fmt = ListExpression(String("DateTimeShort")) diff --git a/mathics/builtin/drawing/graphics3d.py b/mathics/builtin/drawing/graphics3d.py index 5caa0b1c3..7e133e1c8 100644 --- a/mathics/builtin/drawing/graphics3d.py +++ b/mathics/builtin/drawing/graphics3d.py @@ -140,11 +140,6 @@ class Graphics3D(Graphics): messages = {"invlight": "`1` is not a valid list of light sources."} - rules = { - "MakeBoxes[Graphics3D[content_, OptionsPattern[Graphics3D]], " - " OutputForm]": '"-Graphics3D-"' - } - def total_extent_3d(extents): xmin = xmax = ymin = ymax = zmin = zmax = None diff --git a/mathics/builtin/forms/data.py b/mathics/builtin/forms/data.py index 049116338..6ce2f7d3f 100644 --- a/mathics/builtin/forms/data.py +++ b/mathics/builtin/forms/data.py @@ -11,23 +11,16 @@ """ from typing import Any, Callable, Dict, List, Optional -from mathics.builtin.box.layout import RowBox, StyleBox +from mathics.builtin.box.layout import RowBox, StyleBox, SuperscriptBox from mathics.builtin.forms.base import FormBaseClass from mathics.core.atoms import Integer, Real, String from mathics.core.builtin import Builtin from mathics.core.element import BaseElement from mathics.core.evaluation import Evaluation 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, SymbolFalse, SymbolNull, SymbolTrue -from mathics.core.systemsymbols import ( - SymbolAutomatic, - SymbolInfinity, - SymbolMakeBoxes, - SymbolRowBox, - SymbolSuperscriptBox, -) +from mathics.core.systemsymbols import SymbolAutomatic, SymbolInfinity, SymbolMakeBoxes from mathics.eval.strings import eval_StringForm_MakeBoxes, eval_ToString from mathics.format.box import ( StringLParen, @@ -95,7 +88,7 @@ class BaseForm(FormBaseClass): def eval_makeboxes(self, expr, n, f, evaluation: Evaluation): """MakeBoxes[BaseForm[expr_, n_], - f:StandardForm|TraditionalForm|OutputForm]""" + (f:StandardForm|TraditionalForm)]""" try: return eval_baseform(expr, n, f, evaluation) except ValueError: @@ -564,16 +557,13 @@ def default_NumberFormat( py_exp = exp.get_string_value() if py_exp: mul = String(options["NumberMultiplier"]) - return Expression( - SymbolRowBox, - ListExpression(man, mul, Expression(SymbolSuperscriptBox, base, exp)), - ) + return RowBox(man, mul, SuperscriptBox(base, exp)) return man def eval_makeboxes(self, fexpr, form, evaluation): """MakeBoxes[fexpr:NumberForm[_?AtomQ, ___], - form:StandardForm|TraditionalForm|OutputForm]""" + form:StandardForm|TraditionalForm]""" try: target, prec_parms, py_options = get_numberform_parameters( fexpr, evaluation @@ -603,7 +593,6 @@ def eval_makeboxes(self, fexpr, form, evaluation): if py_n is not None: py_options["_Form"] = form.get_name() - return numberform_to_boxes(target, py_n, py_f, evaluation, py_options) return Expression(SymbolMakeBoxes, target, form) @@ -637,7 +626,7 @@ class SequenceForm(FormBaseClass): def eval_makeboxes(self, args, form, evaluation, options: dict): """MakeBoxes[SequenceForm[args___, OptionsPattern[SequenceForm]], - form:StandardForm|TraditionalForm|OutputForm]""" + form:StandardForm|TraditionalForm]""" encoding = options["System`CharacterEncoding"] return RowBox( *[ @@ -712,7 +701,7 @@ class StringForm(FormBaseClass): def eval_makeboxes(self, s, args, form, evaluation): """MakeBoxes[StringForm[s_String, args___], - form:StandardForm|TraditionalForm|OutputForm]""" + form:StandardForm|TraditionalForm]""" try: result = eval_StringForm_MakeBoxes(s, args.get_sequence(), form, evaluation) except ValueError: @@ -771,8 +760,8 @@ class TableForm(FormBaseClass): summary_text = "format as a table" def eval_makeboxes(self, table, f, evaluation, options): - """MakeBoxes[%(name)s[table_, OptionsPattern[%(name)s]], - f:StandardForm|TraditionalForm|OutputForm]""" + """MakeBoxes[%(name)s[table_, OptionsPattern[]], + f:StandardForm|TraditionalForm]""" return eval_tableform(self, table, f, evaluation, options) @@ -803,9 +792,8 @@ class MatrixForm(TableForm): summary_text = "format as a matrix" def eval_makeboxes_matrix(self, table, form, evaluation, options): - """MakeBoxes[%(name)s[table_, OptionsPattern[%(name)s]], - form:StandardForm|TraditionalForm]""" - + """MakeBoxes[MatrixForm[table_, OptionsPattern[]], + (form:StandardForm|TraditionalForm)]""" result = super().eval_makeboxes(table, form, evaluation, options) if result.get_head_name() == "System`GridBox": return RowBox(StringLParen, result, StringRParen) diff --git a/mathics/builtin/forms/print.py b/mathics/builtin/forms/print.py index 7e5e34503..d7466cc74 100644 --- a/mathics/builtin/forms/print.py +++ b/mathics/builtin/forms/print.py @@ -17,8 +17,13 @@ from mathics.core.atoms import String from mathics.core.expression import Expression from mathics.core.symbols import SymbolFalse, SymbolFullForm, SymbolTrue -from mathics.core.systemsymbols import SymbolInputForm -from mathics.format.box import eval_makeboxes_fullform, eval_mathmlform, eval_texform +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 sort_order = "mathics.builtin.forms.general-purpose-forms" @@ -113,7 +118,7 @@ class InputForm(FormBaseClass): # TODO: eventually, remove OutputForm in the second argument. def eval_makeboxes(self, expr, evaluation): - """MakeBoxes[InputForm[expr_], Alternatives[StandardForm,TraditionalForm,OutputForm]]""" + """MakeBoxes[InputForm[expr_], Alternatives[StandardForm,TraditionalForm]]""" inputform = String(render_input_form(expr, evaluation)) inputform = StyleBox( @@ -165,7 +170,7 @@ class MathMLForm(FormBaseClass): summary_text = "format expression as MathML commands" def eval_mathml(self, expr, evaluation) -> Expression: - "MakeBoxes[MathMLForm[expr_], (OutputForm|StandardForm|TraditionalForm)]" + "MakeBoxes[MathMLForm[expr_], Alternatives[StandardForm,TraditionalForm]]" return eval_mathmlform(expr, evaluation) @@ -194,9 +199,15 @@ class OutputForm(FormBaseClass): = -Graphics- """ + formats = {"OutputForm[s_String]": "s"} summary_text = "format expression in plain text" - # Remove me at the end of the refactor - rules = {"MakeBoxes[OutputForm[expr_], form_]": "MakeBoxes[expr, OutputForm]"} + + 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): @@ -266,5 +277,7 @@ class TeXForm(FormBaseClass): summary_text = "format expression as LaTeX commands" def eval_tex(self, expr, evaluation) -> Expression: - "MakeBoxes[TeXForm[expr_], (OutputForm|StandardForm|TraditionalForm)]" + "MakeBoxes[TeXForm[expr_], Alternatives[StandardForm,TraditionalForm]]" + # TeXForm by default uses `TraditionalForm` + return eval_texform(expr, evaluation) diff --git a/mathics/builtin/functional/application.py b/mathics/builtin/functional/application.py index 3c5bebbcd..414ec25b1 100644 --- a/mathics/builtin/functional/application.py +++ b/mathics/builtin/functional/application.py @@ -198,9 +198,7 @@ class Slot(SympyFunction, PrefixOperator): rules = { "Slot[]": "Slot[1]", "MakeBoxes[Slot[n_Integer?NonNegative]," - " f:StandardForm|TraditionalForm|InputForm|OutputForm]": ( - '"#" <> ToString[n]' - ), + " (f:StandardForm|TraditionalForm)]": ('"#" <> ToString[n]'), } summary_text = "one argument of a pure function" @@ -237,6 +235,6 @@ class SlotSequence(PrefixOperator, Builtin): rules = { "SlotSequence[]": "SlotSequence[1]", "MakeBoxes[SlotSequence[n_Integer?Positive]," - "f:StandardForm|TraditionalForm|InputForm|OutputForm]": ('"##" <> ToString[n]'), + "(f:StandardForm|TraditionalForm)]": ('"##" <> ToString[n]'), } summary_text = "the full sequence of arguments of a pure function" diff --git a/mathics/builtin/graphics.py b/mathics/builtin/graphics.py index b4f53603a..6ec9ed50f 100644 --- a/mathics/builtin/graphics.py +++ b/mathics/builtin/graphics.py @@ -316,7 +316,7 @@ class Graphics(Builtin): def eval_makeboxes(self, content, evaluation, options): """MakeBoxes[%(name)s[content_, OptionsPattern[%(name)s]], - StandardForm|TraditionalForm|OutputForm]""" + Alternatives[StandardForm,TraditionalForm]]""" def convert(content): head = content.get_head() diff --git a/mathics/builtin/kernel_sessions.py b/mathics/builtin/kernel_sessions.py index 64046bb56..8a2250242 100644 --- a/mathics/builtin/kernel_sessions.py +++ b/mathics/builtin/kernel_sessions.py @@ -55,9 +55,9 @@ class Out(Builtin): "Out[k_Integer?Negative]": "Out[$Line + k]", "Out[]": "Out[$Line - 1]", "MakeBoxes[Out[k_Integer?((-10 <= # < 0)&)]," - " f:StandardForm|TraditionalForm|InputForm|OutputForm]": r'StringJoin[ConstantArray["%%", -k]]', + " f:StandardForm|TraditionalForm]": r'StringJoin[ConstantArray["%%", -k]]', "MakeBoxes[Out[k_Integer?Positive]," - " f:StandardForm|TraditionalForm|InputForm|OutputForm]": r'"%%" <> ToString[k]', + " f:StandardForm|TraditionalForm]": r'"%%" <> ToString[k]', } summary_text = "result of the Kth input line" diff --git a/mathics/builtin/layout.py b/mathics/builtin/layout.py index 4a5f34c70..a3785cce3 100644 --- a/mathics/builtin/layout.py +++ b/mathics/builtin/layout.py @@ -121,7 +121,7 @@ class Grid(Builtin): def eval_makeboxes(self, array, f, evaluation: Evaluation, options) -> Expression: """MakeBoxes[Grid[array_List, OptionsPattern[Grid]], - f:StandardForm|TraditionalForm|OutputForm]""" + f:StandardForm|TraditionalForm]""" elements = array.elements @@ -221,7 +221,7 @@ class Pane(Builtin): A Pane is treated as an unbroken rectangular region for purposes of line breaking. >> Pane[37!] - = 13763753091226345046315979581580902400000000 + = Pane[13763753091226345046315979581580902400000000] In TeXForm, $Pane$ produce minipage environments: >> {{Pane[a,3], Pane[expt, 3]}}//TableForm//TeXForm @@ -398,7 +398,7 @@ class Row(Builtin): def eval_makeboxes(self, items, sep, form, evaluation: Evaluation): """MakeBoxes[Row[{items___}, sep_:""], - form:StandardForm|TraditionalForm|OutputForm]""" + form:StandardForm|TraditionalForm]""" items = items.get_sequence() if not isinstance(sep, String): diff --git a/mathics/builtin/list/associations.py b/mathics/builtin/list/associations.py index ebe90b410..2bd46eea3 100644 --- a/mathics/builtin/list/associations.py +++ b/mathics/builtin/list/associations.py @@ -56,7 +56,7 @@ class Association(Builtin): def eval_makeboxes(self, rules, f, evaluation: Evaluation): """MakeBoxes[<|rules___|>, - f:StandardForm|TraditionalForm|OutputForm|InputForm]""" + (f:StandardForm|TraditionalForm)]""" def validate(exprs): for expr in exprs: diff --git a/mathics/builtin/list/constructing.py b/mathics/builtin/list/constructing.py index 59be9e1ba..2e084b542 100644 --- a/mathics/builtin/list/constructing.py +++ b/mathics/builtin/list/constructing.py @@ -167,7 +167,7 @@ def eval(self, elements, evaluation: Evaluation): def eval_makeboxes(self, items, f, evaluation): """MakeBoxes[{items___}, - f:StandardForm|TraditionalForm|OutputForm|InputForm|FullForm]""" + (f:StandardForm|TraditionalForm)]""" items = items.get_sequence() return RowBox(*list_boxes(items, f, evaluation, "{", "}")) diff --git a/mathics/builtin/list/eol.py b/mathics/builtin/list/eol.py index 89c061144..8eb365ade 100644 --- a/mathics/builtin/list/eol.py +++ b/mathics/builtin/list/eol.py @@ -1168,14 +1168,11 @@ class Part(Builtin): def eval_makeboxes(self, list, i, f, evaluation): """MakeBoxes[Part[list_, i___], - f:StandardForm|TraditionalForm|OutputForm|InputForm]""" + (f:StandardForm|TraditionalForm)]""" i = i.get_sequence() list = Expression(SymbolMakeBoxes, list, f).evaluate(evaluation) - if f.get_name() in ("System`OutputForm", "System`InputForm"): - open, close = "[[", "]]" - else: - open, close = "\u301a", "\u301b" + open, close = "\u301a", "\u301b" indices = list_boxes(i, f, evaluation, open, close) result = RowBox(list, *indices) return result diff --git a/mathics/builtin/mainloop.py b/mathics/builtin/mainloop.py index 8bce8b01a..2c69b6dd2 100644 --- a/mathics/builtin/mainloop.py +++ b/mathics/builtin/mainloop.py @@ -93,6 +93,7 @@ class In(Builtin): . In[2] = x = x + 1 . . In[1] = x = 1 + . """ attributes = A_LISTABLE | A_PROTECTED diff --git a/mathics/builtin/makeboxes.py b/mathics/builtin/makeboxes.py index 3ac80f9ab..8fea8b539 100644 --- a/mathics/builtin/makeboxes.py +++ b/mathics/builtin/makeboxes.py @@ -92,7 +92,7 @@ class MakeBoxes(Builtin): rules = { "MakeBoxes[Infix[head_[elements___]], " - " f:StandardForm|TraditionalForm|OutputForm]": ( + " f:StandardForm|TraditionalForm]": ( 'MakeBoxes[Infix[head[elements], StringForm["~`1`~", head]], f]' ), "MakeBoxes[expr_]": "MakeBoxes[expr, StandardForm]", @@ -100,10 +100,9 @@ class MakeBoxes(Builtin): "MakeBoxes[expr_, form:(TeXForm|MathMLForm)]": "MakeBoxes[form[expr], StandardForm]", ( "MakeBoxes[(form:StandardForm|TraditionalForm)" - "[expr_], StandardForm|TraditionalForm|OutputForm]" + "[expr_], StandardForm|TraditionalForm]" ): ("MakeBoxes[expr, form]"), # BoxForms goes as second argument - "MakeBoxes[(form:StandardForm|TraditionalForm|OutputForm)[expr_], OutputForm]": "MakeBoxes[expr, form]", "MakeBoxes[PrecedenceForm[expr_, prec_], f_]": "MakeBoxes[expr, f]", "MakeBoxes[Style[expr_, OptionsPattern[Style]], f_]": ( "StyleBox[MakeBoxes[expr, f], " @@ -118,12 +117,12 @@ def eval_fullform(self, expr, evaluation): def eval_general(self, expr, f, evaluation): """MakeBoxes[expr_, - f:TraditionalForm|StandardForm|OutputForm]""" + f:TraditionalForm|StandardForm]""" return eval_generic_makeboxes(expr, f, evaluation) def eval_outerprecedenceform(self, expr, precedence, form, evaluation): """MakeBoxes[PrecedenceForm[expr_, precedence_], - form:StandardForm|TraditionalForm|OutputForm]""" + form:StandardForm|TraditionalForm]""" py_precedence = precedence.get_int_value() boxes = MakeBoxes(expr, form) @@ -131,13 +130,13 @@ def eval_outerprecedenceform(self, expr, precedence, form, evaluation): def eval_postprefix(self, p, expr, h, precedence, form, evaluation): """MakeBoxes[(p:Prefix|Postfix)[expr_, h_, precedence_:None], - form:StandardForm|TraditionalForm|OutputForm]""" + form:StandardForm|TraditionalForm]""" return eval_postprefix(self, p, expr, h, precedence, form, evaluation) def eval_infix( self, expr, operator, precedence: Integer, grouping, form: Symbol, evaluation ): - """MakeBoxes[Infix[expr_, operator_, precedence_:None, grouping_:None], form:StandardForm|TraditionalForm|OutputForm]""" + """MakeBoxes[Infix[expr_, operator_, precedence_:None, grouping_:None], form:StandardForm|TraditionalForm]""" return eval_infix(self, expr, operator, precedence, grouping, form, evaluation) diff --git a/mathics/builtin/messages.py b/mathics/builtin/messages.py index b91f6e52f..b0d0b9732 100644 --- a/mathics/builtin/messages.py +++ b/mathics/builtin/messages.py @@ -335,12 +335,9 @@ class MessageName(InfixOperator): messages = {"messg": "Message cannot be set to `1`. It must be set to a string."} rules = { "MakeBoxes[MessageName[symbol_Symbol, tag_String], " - "f:StandardForm|TraditionalForm|OutputForm]": ( + "f:StandardForm|TraditionalForm]": ( 'RowBox[{MakeBoxes[symbol, f], "::", MakeBoxes[tag, f]}]' ), - "MakeBoxes[MessageName[symbol_Symbol, tag_String], InputForm]": ( - 'RowBox[{MakeBoxes[symbol, InputForm], "::", tag}]' - ), } summary_text = "associate a message name with a tag" diff --git a/mathics/builtin/numbers/calculus.py b/mathics/builtin/numbers/calculus.py index 501258da7..210d6f601 100644 --- a/mathics/builtin/numbers/calculus.py +++ b/mathics/builtin/numbers/calculus.py @@ -417,7 +417,6 @@ class Derivative(PostfixOperator, SympyFunction): r' "\[Prime]\[Prime]", If[{n} === {1}, "\[Prime]", ' r' RowBox[{"(", Sequence @@ Riffle[{n}, ","], ")"}]]]]' ), - "MakeBoxes[Derivative[n:1|2][f_], form:OutputForm]": """RowBox[{MakeBoxes[f, form], If[n==1, "'", "''"]}]""", # The following rules should be applied in the eval method, instead of relying on the pattern matching # mechanism. "Derivative[0...][f_]": "f", diff --git a/mathics/builtin/patterns/basic.py b/mathics/builtin/patterns/basic.py index 6aeb59175..3e6f44716 100644 --- a/mathics/builtin/patterns/basic.py +++ b/mathics/builtin/patterns/basic.py @@ -90,13 +90,9 @@ class Blank(_Blank): """ rules = { + ("MakeBoxes[Verbatim[Blank][], " "f:StandardForm|TraditionalForm]"): '"_"', ( - "MakeBoxes[Verbatim[Blank][], " - "f:StandardForm|TraditionalForm|OutputForm|InputForm]" - ): '"_"', - ( - "MakeBoxes[Verbatim[Blank][head_Symbol], " - "f:StandardForm|TraditionalForm|OutputForm|InputForm]" + "MakeBoxes[Verbatim[Blank][head_Symbol], " "f:StandardForm|TraditionalForm]" ): ('"_" <> MakeBoxes[head, f]'), } summary_text = "match to any single expression" @@ -154,8 +150,8 @@ class BlankNullSequence(_Blank): """ rules = { - "MakeBoxes[Verbatim[BlankNullSequence][], f:StandardForm|TraditionalForm|OutputForm|InputForm]": '"___"', - "MakeBoxes[Verbatim[BlankNullSequence][head_Symbol], f:StandardForm|TraditionalForm|OutputForm|InputForm]": '"___" <> MakeBoxes[head, f]', + "MakeBoxes[Verbatim[BlankNullSequence][], f:StandardForm|TraditionalForm]": '"___"', + "MakeBoxes[Verbatim[BlankNullSequence][head_Symbol], f:StandardForm|TraditionalForm]": '"___" <> MakeBoxes[head, f]', } summary_text = "match to a sequence of zero or more elements" @@ -245,8 +241,8 @@ class BlankSequence(_Blank): """ rules = { - "MakeBoxes[Verbatim[BlankSequence][], f:StandardForm|TraditionalForm|OutputForm|InputForm]": '"__"', - "MakeBoxes[Verbatim[BlankSequence][head_Symbol], f:StandardForm|TraditionalForm|OutputForm|InputForm]": '"__" <> MakeBoxes[head, f]', + "MakeBoxes[Verbatim[BlankSequence][], f:StandardForm|TraditionalForm]": '"__"', + "MakeBoxes[Verbatim[BlankSequence][head_Symbol], f:StandardForm|TraditionalForm]": '"__" <> MakeBoxes[head, f]', } summary_text = "match to a non-empty sequence of elements" diff --git a/mathics/builtin/patterns/composite.py b/mathics/builtin/patterns/composite.py index 9b6a73a00..d869efc48 100644 --- a/mathics/builtin/patterns/composite.py +++ b/mathics/builtin/patterns/composite.py @@ -441,7 +441,7 @@ class Pattern(PatternObject): ( "MakeBoxes[Verbatim[Pattern][symbol_Symbol, blank_Blank|" "blank_BlankSequence|blank_BlankNullSequence], " - "f:StandardForm|TraditionalForm|InputForm|OutputForm]" + "(f:StandardForm|TraditionalForm)]" ): "MakeBoxes[symbol, f] <> MakeBoxes[blank, f]", # 'StringForm["`1``2`", HoldForm[symbol], blank]', } diff --git a/mathics/builtin/patterns/defaults.py b/mathics/builtin/patterns/defaults.py index 8183fc424..b9396e92b 100644 --- a/mathics/builtin/patterns/defaults.py +++ b/mathics/builtin/patterns/defaults.py @@ -72,8 +72,8 @@ class Optional(InfixOperator, PatternObject): } grouping = "Right" rules = { - "MakeBoxes[Verbatim[Optional][Verbatim[Pattern][symbol_Symbol, Verbatim[_]]], f:StandardForm|TraditionalForm|InputForm|OutputForm]": 'MakeBoxes[symbol, f] <> "_."', - "MakeBoxes[Verbatim[Optional][Verbatim[_]], f:StandardForm|TraditionalForm|InputForm|OutputForm]": '"_."', + "MakeBoxes[Verbatim[Optional][Verbatim[Pattern][symbol_Symbol, Verbatim[_]]], (f:StandardForm|TraditionalForm)]": 'MakeBoxes[symbol, f] <> "_."', + "MakeBoxes[Verbatim[Optional][Verbatim[_]], (f:StandardForm|TraditionalForm)]": '"_."', } summary_text = "an optional argument with a default value" diff --git a/mathics/core/builtin.py b/mathics/core/builtin.py index dc037ec50..09d001408 100644 --- a/mathics/core/builtin.py +++ b/mathics/core/builtin.py @@ -1353,7 +1353,6 @@ def __init__(self, *args, **kwargs): "MakeBoxes[{0}, form:StandardForm|TraditionalForm]".format( op_pattern ): formatted, - f"MakeBoxes[{op_pattern}, form:InputForm|OutputForm]": formatted, } default_rules.update(self.rules) self.rules = default_rules diff --git a/mathics/core/parser/__init__.py b/mathics/core/parser/__init__.py index cdc9ffc6f..cb8178d4e 100644 --- a/mathics/core/parser/__init__.py +++ b/mathics/core/parser/__init__.py @@ -18,7 +18,7 @@ MathicsMultiLineFeeder, MathicsSingleLineFeeder, ) -from mathics.core.parser.operators import all_operator_names +from mathics.core.parser.operators import all_operator_names, operator_precedences from mathics.core.parser.util import parse, parse_builtin_rule __all__ = [ @@ -29,6 +29,7 @@ "MathicsSingleLineFeeder", "all_operator_names", "is_symbol_name", + "operator_precedences", "parse", "parse_builtin_rule", ] diff --git a/mathics/doc/documentation/1-Manual.mdoc b/mathics/doc/documentation/1-Manual.mdoc index 3e545a2e6..72d38bfde 100644 --- a/mathics/doc/documentation/1-Manual.mdoc +++ b/mathics/doc/documentation/1-Manual.mdoc @@ -890,29 +890,37 @@ There are several methods to display expressions in 2-D: >> Subscript[a, 1, 2] // TeXForm = a_{1, 2} -If you want even more low-level control over expression display, override 'MakeBoxes': >> MakeBoxes[b, TraditionalForm] = "c"; >> b = b ## This will be displayed as c in the browser and LaTeX documentation. -This will even apply to 'TeXForm', because 'TeXForm' implies 'TraditionalForm': +In the browser, this will even apply to 'TeXForm', because 'TeXForm' implies 'TraditionalForm': >> b // TeXForm - = c + = ... + +Notice however that in the CLI, default output formatted in 'OutputForm', +which do not take into account 'MakeBoxes' rules. The same happens if we +ask explicitly for this form: -Except some other form is applied first: >> b // OutputForm // TeXForm - = b + = \text{b} + +In a similar way, in the CLI, we can ask for TraditionalForm explicitly + >> b // TraditionalForm // TeXForm + = c + 'MakeBoxes' for another form: >> MakeBoxes[TeXForm[b], form_] = "d"; >> b // TeXForm - = d + = ... + You can cause a much bigger mess by overriding 'MakeBoxes' than by sticking to 'Format', e.g. generate invalid XML: >> MakeBoxes[MathMLForm[c], form_] = "> c // MathMLForm + >> c // MathMLForm //StandardForm = > {1, 2, 3} = {1, 2, 3} - #> {1, 2, 3} // TeXForm + >> {1, 2, 3} // TeXForm = \left[1 2 3\right] However, this will not be accepted as input to \Mathics anymore: >> [1 2 3] - : Expression cannot begin with "[1 2 3]" (line 1 of ""). + : Expression cannot begin with "[1 2 3]" (line 1 of ""). >> Clear[MakeBoxes] diff --git a/mathics/eval/strings.py b/mathics/eval/strings.py index 05f986637..55d4567b3 100644 --- a/mathics/eval/strings.py +++ b/mathics/eval/strings.py @@ -182,7 +182,8 @@ def eval_StringForm_MakeBoxes(strform, items, form, evaluation): # character: if not remaining: evaluation.message("StringForm", "sfq", strform) - raise ValueError + return strform.value + # part must be an index or an empty string. # If is an empty string, pick the next element: if part == "": @@ -194,7 +195,8 @@ def eval_StringForm_MakeBoxes(strform, items, form, evaluation): Integer(num_items), strform, ) - return ValueError + return strform.value + result.append(items[curr_indx]) curr_indx += 1 quote_open = False @@ -206,14 +208,16 @@ def eval_StringForm_MakeBoxes(strform, items, form, evaluation): evaluation.message( "StringForm", "sfr", Integer0, Integer(num_items), strform ) - raise + return strform.value + # indx must be greater than 0, and not greater than # the number of items if indx <= 0 or indx > len(items): evaluation.message( "StringForm", "sfr", Integer(indx), Integer(len(items)), strform ) - raise ValueError + return strform.value + result.append(items[indx - 1]) curr_indx = indx quote_open = False diff --git a/mathics/format/box/__init__.py b/mathics/format/box/__init__.py index ceaf952cf..42d3f798c 100644 --- a/mathics/format/box/__init__.py +++ b/mathics/format/box/__init__.py @@ -8,6 +8,7 @@ eval_generic_makeboxes, eval_makeboxes, eval_makeboxes_fullform, + eval_makeboxes_outputform, format_element, to_boxes, ) @@ -37,6 +38,7 @@ "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 3b75fa918..2090080c4 100644 --- a/mathics/format/box/makeboxes.py +++ b/mathics/format/box/makeboxes.py @@ -21,13 +21,15 @@ ) from mathics.core.systemsymbols import ( # SymbolRule, SymbolRuleDelayed, SymbolComplex, - SymbolInputForm, SymbolRational, SymbolStandardForm, + SymbolTraditionalForm, ) from mathics.format.box.formatvalues import do_format from mathics.format.box.precedence import parenthesize +BOX_FORMS = {SymbolStandardForm, SymbolTraditionalForm} + def to_boxes(x, evaluation: Evaluation, options={}) -> BoxElementMixin: """ @@ -119,9 +121,23 @@ 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|OutputForm|InputForm]""" + f:TraditionalForm|StandardForm]""" from mathics.builtin.box.layout import RowBox if isinstance(expr, BoxElementMixin): @@ -192,10 +208,13 @@ def eval_makeboxes( # which is wrong. if form is SymbolFullForm: return eval_makeboxes_fullform(expr, evaluation) - if form is SymbolInputForm: + if form not in BOX_FORMS: + # print(form, "not in", BOX_FORMS) expr = Expression(form, expr) form = SymbolStandardForm - return Expression(SymbolMakeBoxes, expr, form).evaluate(evaluation) + mb_expr = Expression(SymbolMakeBoxes, expr, form) + # print(" evaluate", mb_expr) + return mb_expr.evaluate(evaluation) def format_element( @@ -204,11 +223,12 @@ def format_element( """ 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) - # print(" FormatValues->", formatted_expr) result_box = eval_makeboxes(formatted_expr, evaluation, form) - # print(" box rules->", result_box) if isinstance(result_box, BoxElementMixin): return result_box return eval_makeboxes_fullform(element, evaluation) diff --git a/mathics/format/form/outputform.py b/mathics/format/form/outputform.py index e54ec33f5..578629b57 100644 --- a/mathics/format/form/outputform.py +++ b/mathics/format/form/outputform.py @@ -244,6 +244,7 @@ def render_output_form(expr: BaseElement, evaluation: Evaluation, **kwargs): if format_expr is None: return "" + head = format_expr.get_head() lookup_name: str = head.get_name() or head.get_lookup_name() callback = EXPR_TO_OUTPUTFORM_TEXT_MAP.get(lookup_name, None) @@ -373,7 +374,7 @@ def _infix_outputform_text(expr: Expression, evaluation: Evaluation, **kwargs) - # raise _WrongFormattedExpression # Process the first operand: - parenthesized = group in (SymbolNone, SymbolRight, SymbolNonAssociative) + parenthesized = group in (SymbolRight, SymbolNonAssociative) operand = operands[0] result = str(render_output_form(operand, evaluation, **kwargs)) result = parenthesize(precedence, operand, result, parenthesized) diff --git a/mathics/session.py b/mathics/session.py index af4ef54ee..607319e6a 100644 --- a/mathics/session.py +++ b/mathics/session.py @@ -115,7 +115,7 @@ def __init__( # the formats must be already loaded. # The need of importing this module here seems # to be related to an issue in the modularity design. - import mathics.format + import mathics.format.render if character_encoding is not None: mathics.settings.SYSTEM_CHARACTER_ENCODING = character_encoding diff --git a/test/format/format_tests.yaml b/test/format/format_tests.yaml index 0664d3586..5fba246d5 100644 --- a/test/format/format_tests.yaml +++ b/test/format/format_tests.yaml @@ -78,7 +78,7 @@ msg: small negative real number (>10^-5) latex: System`InputForm: '-0.00001' - System`OutputForm: '-0.00001' + System`OutputForm: '\text{-0.00001}' mathml: {} text: System`InputForm: '-0.00001' @@ -87,25 +87,25 @@ msg: very small negative real number (<10^-6) latex: System`InputForm: -1.*{}^{\wedge}-6 - System`OutputForm: -1.\times 10^{-6} + System`OutputForm: '\text{-1.×10${}^{\wedge}$-6}' mathml: {} text: System`InputForm: -1.*^-6 System`OutputForm: "-1.\xD710^-6" -1. 10^5: + msg: large negative real number (<10^6) latex: System`InputForm: '-100000.' - System`OutputForm: '-100000.' + System`OutputForm: '\text{-100000.}' mathml: {} - msg: large negative real number (>10^6) text: System`InputForm: '-100000.' System`OutputForm: '-100000.' -1. 10^6: - msg: large negative real number (>10^6) + msg: very large negative real number (>10^6) latex: System`InputForm: -1.*{}^{\wedge}6 - System`OutputForm: -1.\times 10^6 + System`OutputForm: '\text{-1.×10${}^{\wedge}$6}' System`StandardForm: -1.\text{*${}^{\wedge}$}6 System`TraditionalForm: -1.\times 10^6 mathml: {} @@ -118,12 +118,12 @@ msg: An Integer latex: System`InputForm: '-4' - System`OutputForm: '-4' + System`OutputForm: '\text{-4}' System`StandardForm: '-4' System`TraditionalForm: '-4' mathml: System`InputForm: -4 - System`OutputForm: -4 + System`OutputForm: -4 System`StandardForm: - 4 System`TraditionalForm: - 4 text: @@ -135,12 +135,12 @@ msg: A MachineReal number latex: System`InputForm: '-4.32' - System`OutputForm: '-4.32' + System`OutputForm: '\text{-4.32}' System`StandardForm: '-4.32' System`TraditionalForm: '-4.32' mathml: System`InputForm: -4.32 - System`OutputForm: -4.32 + System`OutputForm: -4.32 System`StandardForm: - 4.32 System`TraditionalForm: - 4.32 text: @@ -149,75 +149,75 @@ System`StandardForm: -4.32` System`TraditionalForm: -4.32` -4.326563712`2: + msg: A negative Real number latex: System`InputForm: '-4.33' - System`OutputForm: '-4.3' + System`OutputForm: '\text{-4.3}' System`StandardForm: '-4.33' System`TraditionalForm: '-4.33' mathml: System`InputForm: -4.33 - System`OutputForm: -4.3 + System`OutputForm: -4.3 System`StandardForm: - 4.33 System`TraditionalForm: - 4.33 - msg: A Real number text: System`InputForm: -4.33`2. System`OutputForm: '-4.3' System`StandardForm: -4.33`2. System`TraditionalForm: -4.33`2. -4.32`4: + msg: A negative PrecisionReal number latex: System`InputForm: '-4.32' - System`OutputForm: '-4.320' + System`OutputForm: '\text{-4.320}' System`StandardForm: '-4.32' System`TraditionalForm: '-4.32' mathml: System`InputForm: -4.32 - System`OutputForm: -4.320 + System`OutputForm: -4.320 System`StandardForm: - 4.32 System`TraditionalForm: - 4.32 - msg: A PrecisionReal number text: System`InputForm: -4.32`4. System`OutputForm: '-4.320' System`StandardForm: -4.32`4. System`TraditionalForm: -4.32`4. 1. 10^-5: - msg: small real number (<10^-5) + msg: small positive real number (>10^-6) latex: System`InputForm: '0.00001' - System`OutputForm: '0.00001' + System`OutputForm: '\text{0.00001}' mathml: {} text: System`InputForm: '0.00001' System`OutputForm: '0.00001' 1. 10^-6: - msg: very small real number (<10^-6) + msg: very small positive real number (<10^-6) latex: System`InputForm: 1.*{}^{\wedge}-6 - System`OutputForm: 1.\times 10^{-6} + System`OutputForm: '\text{1.×10${}^{\wedge}$-6}' mathml: {} text: System`InputForm: 1.*^-6 System`OutputForm: "1.\xD710^-6" 1. 10^5: + msg: large positive real number (<10^6) latex: System`InputForm: '100000.' - System`OutputForm: '100000.' + System`OutputForm: '\text{100000.}' System`StandardForm: '100000.' System`TraditionalForm: '100000.' mathml: {} - msg: large real number (>10^6) text: System`InputForm: '100000.' System`OutputForm: '100000.' System`StandardForm: 100000.` System`TraditionalForm: 100000.` 1. 10^6: - msg: very large real number (>10^6) + msg: very large positive real number (>10^6) latex: System`InputForm: 1.*{}^{\wedge}6 - System`OutputForm: 1.\times 10^6 + System`OutputForm: '\text{1.×10${}^{\wedge}$6}' System`StandardForm: 1.\text{*${}^{\wedge}$}6 System`TraditionalForm: 1.\times 10^6 mathml: {} @@ -227,10 +227,10 @@ System`StandardForm: 1.`*^6 System`TraditionalForm: "1.`\xD710^6" 1/(1+1/(1+1/a)): + msg: FractionBox latex: System`InputForm: 1/(1 + 1/(1 + 1/a)) - System`OutputForm: 1\text{ / }\left(1\text{ + }1\text{ / }\left(1\text{ + }1\text{ - / }a\right)\right) + System`OutputForm: '\text{1 / (1 + 1 / (1 + 1 / a))}' System`StandardForm: \frac{1}{1+\frac{1}{1+\frac{1}{a}}} System`TraditionalForm: \frac{1}{1+\frac{1}{1+\frac{1}{a}}} mathml: @@ -238,17 +238,14 @@ - 1/(1 + 1/(1 + 1/a)) - Fragile! System`OutputForm: - - 1  /  ( 1 -  +  1  /  ( - 1  +  1  /  - a ) ) + - '1 / (1 + 1 / (1 + 1 / a))' - Fragile! System`StandardForm: &id001 - 1 1 + 1 1 + 1 a - Fragile! System`TraditionalForm: *id001 - msg: FractionBox + text: System`InputForm: 1/(1 + 1/(1 + 1/a)) System`OutputForm: 1 / (1 + 1 / (1 + 1 / a)) @@ -258,17 +255,12 @@ msg: Association latex: System`InputForm: \text{<$\vert$a -> x, b -> y, c -> <$\vert$d -> t$\vert$>$\vert$>} - System`OutputForm: \text{<$\vert$}a\text{ -> }x, b\text{ -> }y, c\text{ -> }\text{<$\vert$}d\text{ - -> }t\text{$\vert$>}\text{$\vert$>} + System`OutputForm: '\text{<$\vert$a -> x, b -> y, c -> <$\vert$d -> t$\vert$>$\vert$>}' System`StandardForm: \text{<$\vert$}a->x, b->y, c->\text{<$\vert$}d->t\text{$\vert$>}\text{$\vert$>} System`TraditionalForm: \text{<$\vert$}a->x, b->y, c->\text{<$\vert$}d->t\text{$\vert$>}\text{$\vert$>} mathml: System`InputForm: <|a -> x, b -> y, c -> <|d -> t|>|> - System`OutputForm: <| a  ->  - x b  ->  - y c  ->  - <| d  ->  - t |> |> + System`OutputForm: '<|a -> x, b -> y, c -> <|d -> t|>|>' System`StandardForm: <| a -> x , b -> y , c -> <| d @@ -286,18 +278,12 @@ Association[a -> x, b -> y, c -> Association[d -> t, Association[e -> u]]]: msg: Nested Association latex: System`InputForm: \text{<$\vert$a -> x, b -> y, c -> <$\vert$d -> t, e -> u$\vert$>$\vert$>} - System`OutputForm: \text{<$\vert$}a\text{ -> }x, b\text{ -> }y, c\text{ -> }\text{<$\vert$}d\text{ - -> }t, e\text{ -> }u\text{$\vert$>}\text{$\vert$>} + System`OutputForm: '\text{<$\vert$a -> x, b -> y, c -> <$\vert$d -> t, e -> u$\vert$>$\vert$>}' System`StandardForm: \text{<$\vert$}a->x, b->y, c->\text{<$\vert$}d->t, e->u\text{$\vert$>}\text{$\vert$>} System`TraditionalForm: \text{<$\vert$}a->x, b->y, c->\text{<$\vert$}d->t, e->u\text{$\vert$>}\text{$\vert$>} mathml: System`InputForm: <|a -> x, b -> y, c -> <|d -> t, e -> u|>|> - System`OutputForm: <| a  ->  - x b  ->  - y c  ->  - <| d  ->  - t e  ->  - u |> |> + System`OutputForm: '<|a -> x, b -> y, c -> <|d -> t, e -> u|>|>' System`StandardForm: <| a -> x , b -> y , c -> <| d @@ -314,22 +300,20 @@ Association[a -> x, b -> y, c -> Association[d -> t, Association[e -> u]]]: System`StandardForm: <|a->x, b->y, c-><|d->t, e->u|>|> System`TraditionalForm: <|a->x, b->y, c-><|d->t, e->u|>|> Complex[1.09*^12, 3.]: + msg: Complex number latex: System`InputForm: 1.09*{}^{\wedge}12 + 3.*I - System`OutputForm: 1.09\times 10^{12}\text{ + }3. I + System`OutputForm: '\text{1.09×10${}^{\wedge}$12 + 3. I}' System`StandardForm: 1.09\text{*${}^{\wedge}$}12+3. I System`TraditionalForm: 1.09\times 10^{12}+3. I mathml: System`InputForm: 1.09*^12 + 3.*I - System`OutputForm: "1.09 \xD7 10\ - \ 12  +  3.  \ - \ I" + System`OutputForm: '1.09×10^12 + 3. I' System`StandardForm: 1.09 *^ 12 + 3.   I System`TraditionalForm: "1.09 \xD7 10\ \ 12 + 3. \u2062 I" - msg: Complex number text: System`InputForm: 1.09*^12 + 3.*I System`OutputForm: "1.09\xD710^12 + 3. I" @@ -339,26 +323,7 @@ Graphics[{Text[a^b,{0,0}]}]: msg: Nontrivial Graphics - Fragile! latex: System`InputForm: \text{Graphics[\{Text[a${}^{\wedge}$b, \{0, 0\}]\}]} - System`OutputForm: ' - - \begin{asy} - - usepackage("amsmath"); - - size(4.9cm, 5.8333cm); - - - // InsetBox - - label("$a^b$", (147.0,175.0), align=SW, rgb(0, 0, 0)+fontsize(3)); - - - clip(box((136.5,162.5), (157.5,187.5))); - - - \end{asy} - - ' + System`OutputForm: '\text{-Graphics-}' System`StandardForm: ' \begin{asy} @@ -407,22 +372,7 @@ Graphics[{Text[a^b,{0,0}]}]: Graphics[{}]: latex: System`InputForm: \text{Graphics[\{\}]} - System`OutputForm: ' - - \begin{asy} - - usepackage("amsmath"); - - size(5.8333cm, 5.8333cm); - - - - clip(box((-1,-1), (1,1))); - - - \end{asy} - - ' + System`OutputForm: '\text{-Graphics-}' System`StandardForm: ' \begin{asy} @@ -462,22 +412,28 @@ Graphics[{}]: System`StandardForm: -Graphics- System`TraditionalForm: -Graphics- "Grid[{{\"Spanish\", \"Hola!\"},{\"Portuguese\", \"Ol\xE0!\"},{\"English\", \"Hi!\"}}]": + msg: Strings in a GridBox latex: System`InputForm: \text{Grid[\{\{"Spanish", "Hola!"\}, \{"Portuguese", "Olà!"\}, \{"English", "Hi!"\}\}]} - System`OutputForm: "\\begin{array}{cc} \\text{Spanish} & \\text{Hola!}\\\\ \\\ - text{Portuguese} & \\text{Ol\xE0!}\\\\ \\text{English} & \\text{Hi!}\\end{array}" + System`OutputForm: '\text{Spanish Hola!\newline + + \newline + + Portuguese Olà!\newline + + \newline + + English Hi!\newline + + } +' System`StandardForm: "\\begin{array}{cc} \\text{Spanish} & \\text{Hola!}\\\\ \\\ text{Portuguese} & \\text{Ol\xE0!}\\\\ \\text{English} & \\text{Hi!}\\end{array}" System`TraditionalForm: "\\begin{array}{cc} \\text{Spanish} & \\text{Hola!}\\\\\ \ \\text{Portuguese} & \\text{Ol\xE0!}\\\\ \\text{English} & \\text{Hi!}\\end{array}" mathml: System`InputForm: "Grid[{{"Spanish", "Hola!"}, {"Portuguese", "Olà!"}, {"English", "Hi!"}}]" - System`OutputForm: "\nSpanishHola!\n\ - PortugueseOl\xE0!\nEnglishHi!\n\ - " + System`OutputForm: 'Spanish      Hola!Portuguese   Olà!English      Hi!' System`StandardForm: "\nSpanishHola!\n\ PortugueseOl\xE0!\nEnglishHi!\n\ " - msg: Strings in a GridBox text: System`InputForm: "Grid[{{\"Spanish\", \"Hola!\"}, {\"Portuguese\", \"Ol\xE0!\"\ }, {\"English\", \"Hi!\"}}]" @@ -501,20 +456,21 @@ Graphics[{}]: System`TraditionalForm: "Spanish Hola!\n\nPortuguese Ol\xE0!\n\nEnglish\ \ Hi!\n" Grid[{{a,b},{c,d}}]: + msg: GridBox latex: System`InputForm: \text{Grid[\{\{a, b\}, \{c, d\}\}]} - System`OutputForm: \begin{array}{cc} a & b\\ c & d\end{array} - System`StandardForm: \begin{array}{cc} a & b\\ c & d\end{array} - System`TraditionalForm: \begin{array}{cc} a & b\\ c & d\end{array} + System`OutputForm: ' + \text{a b\newline + + \newline + + c d\newline + + } + ' mathml: System`InputForm: Grid[{{a, b}, {c, d}}] - System`OutputForm: ' - - ab - - cd - - ' + System`OutputForm: 'a   bc   d' System`StandardForm: ' ab @@ -529,7 +485,6 @@ Grid[{{a,b},{c,d}}]: cd ' - msg: GridBox text: System`InputForm: Grid[{{a, b}, {c, d}}] System`OutputForm: 'a b @@ -551,35 +506,34 @@ Grid[{{a,b},{c,d}}]: ' Integrate[F[x], {x, a, g[b]}]: + msg: Nontrivial SubsuperscriptBox latex: System`InputForm: \text{Integrate[F[x], \{x, a, g[b]\}]} - System`OutputForm: \text{Integrate}\left[F\left[x\right], \left\{x, a, g\left[b\right]\right\}\right] + System`OutputForm: '\text{Integrate[F[x], \{x, a, g[b]\}]}' System`StandardForm: \int_a^{g\left[b\right]} F\left[x\right] \, dx System`TraditionalForm: \int_a^{g\left(b\right)} F\left(x\right) \, dx mathml: System`InputForm: Integrate[F[x], {x, a, g[b]}] - System`OutputForm: Integrate [ F - [ x ] { - x a g - [ b ] } ] - msg: Nontrivial SubsuperscriptBox + System`OutputForm: 'Integrate[F[x], {x, a, g[b]}]' text: System`OutputForm: Integrate[F[x], {x, a, g[b]}] MatrixForm[{{a,b},{c,d}}]: + msg: GridBox in a matrix latex: System`InputForm: \text{MatrixForm[\{\{a, b\}, \{c, d\}\}]} - System`OutputForm: \begin{array}{cc} a & b\\ c & d\end{array} + System`OutputForm: '\text{a b\newline + + \newline + + c d\newline + + } +' System`StandardForm: \left(\begin{array}{cc} a & b\\ c & d\end{array}\right) System`TraditionalForm: \left(\begin{array}{cc} a & b\\ c & d\end{array}\right) mathml: System`InputForm: MatrixForm[{{a, b}, {c, d}}] - System`OutputForm: ' - - ab - - cd - - ' + System`OutputForm: 'a   bc   d' System`StandardForm: '( ab @@ -594,7 +548,6 @@ MatrixForm[{{a,b},{c,d}}]: cd )' - msg: GridBox in a matrix text: System`InputForm: MatrixForm[{{a, b}, {c, d}}] System`OutputForm: 'a b @@ -619,8 +572,7 @@ Sqrt[1/(1+1/(1+1/a))]: msg: SqrtBox latex: System`InputForm: \text{Sqrt[1/(1 + 1/(1 + 1/a))]} - System`OutputForm: \text{Sqrt}\left[1\text{ / }\left(1\text{ + }1\text{ / }\left(1\text{ - + }1\text{ / }a\right)\right)\right] + System`OutputForm: '\text{Sqrt[1 / (1 + 1 / (1 + 1 / a))]}' System`StandardForm: \sqrt{\frac{1}{1+\frac{1}{1+\frac{1}{a}}}} System`TraditionalForm: \sqrt{\frac{1}{1+\frac{1}{1+\frac{1}{a}}}} mathml: @@ -628,11 +580,7 @@ Sqrt[1/(1+1/(1+1/a))]: - Sqrt[1/(1 + 1/(1 + 1/a))] - Fragile! System`OutputForm: - - Sqrt [ 1  /  - ( 1  +  1 -  /  ( 1  +  - 1  /  a ) - ) ] + - 'Sqrt[1 / (1 + 1 / (1 + 1 / a))]' - Fragile! System`StandardForm: &id002 - 1 1 + 1 1 @@ -646,9 +594,10 @@ Sqrt[1/(1+1/(1+1/a))]: System`StandardForm: Sqrt[1 / (1+1 / (1+1 / a))] System`TraditionalForm: Sqrt[1 / (1+1 / (1+1 / a))] Subscript[a, 4]: + msg: SubscriptBox latex: System`InputForm: \text{Subscript[a, 4]} - System`OutputForm: \text{Subscript}\left[a, 4\right] + System`OutputForm: '\text{Subscript[a, 4]}' System`StandardForm: a_4 System`TraditionalForm: a_4 mathml: @@ -656,12 +605,10 @@ Subscript[a, 4]: - Subscript[a, 4] - Fragile! System`OutputForm: - - Subscript [ a - 4 ] + - 'Subscript[a, 4]' - Fragile! System`StandardForm: a 4 System`TraditionalForm: a 4 - msg: SubscriptBox text: System`InputForm: Subscript[a, 4] System`OutputForm: Subscript[a, 4] @@ -670,18 +617,17 @@ Subscript[a, 4]: - BoxError System`TraditionalForm: *id003 Subsuperscript[a, p, q]: + msg: SubsuperscriptBox latex: System`InputForm: \text{Subsuperscript[a, p, q]} - System`OutputForm: \text{Subsuperscript}\left[a, p, q\right] + System`OutputForm: '\text{Subsuperscript[a, p, q]}' System`StandardForm: a_p^q System`TraditionalForm: a_p^q mathml: System`InputForm: Subsuperscript[a, p, q] - System`OutputForm: Subsuperscript [ a - p q ] + System`OutputForm: 'Subsuperscript[a, p, q]' System`StandardForm: a p q System`TraditionalForm: a p q - msg: SubsuperscriptBox text: System`InputForm: Subsuperscript[a, p, q] System`OutputForm: Subsuperscript[a, p, q] @@ -723,20 +669,21 @@ TableForm[{Graphics[{Text[a^b,{0,0}]}], Graphics[{Text[a^b,{0,0}]}]}]: ' TableForm[{{a,b},{c,d}}]: + msg: GridBox in a table latex: System`InputForm: \text{TableForm[\{\{a, b\}, \{c, d\}\}]} - System`OutputForm: \begin{array}{cc} a & b\\ c & d\end{array} + System`OutputForm: '\text{a b\newline + + \newline + + c d\newline + + }' System`StandardForm: \begin{array}{cc} a & b\\ c & d\end{array} System`TraditionalForm: \begin{array}{cc} a & b\\ c & d\end{array} mathml: System`InputForm: TableForm[{{a, b}, {c, d}}] - System`OutputForm: ' - - ab - - cd - - ' + System`OutputForm: 'a   bc   d' System`StandardForm: ' ab @@ -751,7 +698,6 @@ TableForm[{{a,b},{c,d}}]: cd ' - msg: GridBox in a table text: System`InputForm: TableForm[{{a, b}, {c, d}}] System`OutputForm: 'a b @@ -773,6 +719,7 @@ TableForm[{{a,b},{c,d}}]: ' \[Pi]: + msg: Pi latex: System`InputForm: \text{Pi} System`OutputForm: \text{Pi} @@ -780,61 +727,76 @@ TableForm[{{a,b},{c,d}}]: System`TraditionalForm: \pi mathml: System`InputForm: Pi - System`OutputForm: Pi + System`OutputForm: 'Pi' System`StandardForm: "\u03C0" System`TraditionalForm: "\u03C0" - msg: A greek letter Symbol text: System`InputForm: Pi System`OutputForm: Pi System`StandardForm: "\u03C0" System`TraditionalForm: "\u03C0" +\[Alpha]: + msg: A greek letter symbol + latex: + System`InputForm: \alpha + System`OutputForm: \text{α} + System`StandardForm: \alpha + System`TraditionalForm: \alpha + mathml: + System`InputForm: 'α' + System`OutputForm: 'α' + System`StandardForm: 'α' + System`TraditionalForm: 'α' + text: + System`InputForm: 'α' + System`OutputForm: 'α' + System`StandardForm: "α" + System`TraditionalForm: "α" a: + msg: A Symbol latex: System`InputForm: a - System`OutputForm: a + System`OutputForm: '\text{a}' System`StandardForm: a System`TraditionalForm: a mathml: System`InputForm: a - System`OutputForm: a + System`OutputForm: 'a' System`StandardForm: a System`TraditionalForm: a - msg: A Symbol text: System`InputForm: a System`OutputForm: a System`StandardForm: a System`TraditionalForm: a a^(g[b]/c): + msg: SuperscriptBox with a nested expression. latex: System`InputForm: \text{a${}^{\wedge}$(g[b]/c)} - System`OutputForm: a\text{ ${}^{\wedge}$ }\left(g\left[b\right]\text{ / }c\right) + System`OutputForm: '\text{a ${}^{\wedge}$ (g[b] / c)}' System`StandardForm: a^{\frac{g\left[b\right]}{c}} System`TraditionalForm: a^{\frac{g\left(b\right)}{c}} mathml: System`InputForm: a^(g[b]/c) - System`OutputForm: a  ^  ( g [ b ]  /  c ) - System`StandardForm: a g [ b ] c + System`OutputForm: 'a ^ (g[b] / c)' System`TraditionalForm: a g ( b ) c - msg: SuperscriptBox with a nested expression. text: System`InputForm: a^(g[b]/c) System`OutputForm: a ^ (g[b] / c) System`StandardForm: a^((g[b]) / c) System`TraditionalForm: a^((g(b)) / c) a^4: + msg: SuperscriptBox latex: System`InputForm: \text{a${}^{\wedge}$4} - System`OutputForm: a\text{ ${}^{\wedge}$ }4 + System`OutputForm: '\text{a ${}^{\wedge}$ 4}' System`StandardForm: a^4 System`TraditionalForm: a^4 mathml: - System`InputForm: a^4 - System`OutputForm: a  ^  4 + System`InputForm: 'a^4' + System`OutputForm: 'a ^ 4' System`StandardForm: a 4 System`TraditionalForm: a 4 - msg: SuperscriptBox text: System`InputForm: a^4 System`OutputForm: a ^ 4 From a5a2f0949bcfe339f33d809dca50f80f0c6ac258 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Mon, 19 Jan 2026 18:09:51 -0300 Subject: [PATCH 06/31] more explicit logic for parenthesize --- mathics/format/form/inputform.py | 4 +--- mathics/format/form/outputform.py | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/mathics/format/form/inputform.py b/mathics/format/form/inputform.py index 177609de3..e131b332e 100644 --- a/mathics/format/form/inputform.py +++ b/mathics/format/form/inputform.py @@ -167,9 +167,7 @@ def _infix_expression_to_inputform_text( operand = operands[0] result = str(render_input_form(operand, evaluation, **kwargs)) result = parenthesize(precedence, operand, result, parenthesized) - - if group in (SymbolLeft, SymbolRight): - parenthesized = not parenthesized + parenthesized = group in (SymbolLeft, SymbolNonAssociative) # Process the rest of operands num_ops = len(ops_lst) diff --git a/mathics/format/form/outputform.py b/mathics/format/form/outputform.py index 578629b57..cdeeb38eb 100644 --- a/mathics/format/form/outputform.py +++ b/mathics/format/form/outputform.py @@ -378,9 +378,7 @@ def _infix_outputform_text(expr: Expression, evaluation: Evaluation, **kwargs) - operand = operands[0] result = str(render_output_form(operand, evaluation, **kwargs)) result = parenthesize(precedence, operand, result, parenthesized) - - if group in (SymbolLeft, SymbolRight): - parenthesized = not parenthesized + parenthesized = group in (SymbolLeft, SymbolNonAssociative) # Process the rest of operands num_ops = len(ops_lst) From 13c71b9c97df95341a8b8f9d56019030003dcc97 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Mon, 19 Jan 2026 18:11:27 -0300 Subject: [PATCH 07/31] more explicit parenthesize --- mathics/format/form/inputform.py | 4 +--- mathics/format/form/outputform.py | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/mathics/format/form/inputform.py b/mathics/format/form/inputform.py index 177609de3..67e9040e7 100644 --- a/mathics/format/form/inputform.py +++ b/mathics/format/form/inputform.py @@ -168,10 +168,8 @@ def _infix_expression_to_inputform_text( result = str(render_input_form(operand, evaluation, **kwargs)) result = parenthesize(precedence, operand, result, parenthesized) - if group in (SymbolLeft, SymbolRight): - parenthesized = not parenthesized - # Process the rest of operands + parenthesized = (SymbolLeft, SymbolNonAssociative) num_ops = len(ops_lst) for index, operand in enumerate(operands[1:]): curr_op = ops_lst[index % num_ops] diff --git a/mathics/format/form/outputform.py b/mathics/format/form/outputform.py index e54ec33f5..3c3789750 100644 --- a/mathics/format/form/outputform.py +++ b/mathics/format/form/outputform.py @@ -378,10 +378,8 @@ def _infix_outputform_text(expr: Expression, evaluation: Evaluation, **kwargs) - result = str(render_output_form(operand, evaluation, **kwargs)) result = parenthesize(precedence, operand, result, parenthesized) - if group in (SymbolLeft, SymbolRight): - parenthesized = not parenthesized - # Process the rest of operands + parenthesized = (SymbolLeft, SymbolNonAssociative) num_ops = len(ops_lst) for index, operand in enumerate(operands[1:]): curr_op = ops_lst[index % num_ops] From dff8acdd9c6c00d40aef905a198d9cf9a7dfd2c9 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Mon, 19 Jan 2026 18:15:01 -0300 Subject: [PATCH 08/31] group in --- mathics/format/form/inputform.py | 2 +- mathics/format/form/outputform.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mathics/format/form/inputform.py b/mathics/format/form/inputform.py index 67e9040e7..e262e167a 100644 --- a/mathics/format/form/inputform.py +++ b/mathics/format/form/inputform.py @@ -169,7 +169,7 @@ def _infix_expression_to_inputform_text( result = parenthesize(precedence, operand, result, parenthesized) # Process the rest of operands - parenthesized = (SymbolLeft, SymbolNonAssociative) + parenthesized = group in (SymbolLeft, SymbolNonAssociative) num_ops = len(ops_lst) for index, operand in enumerate(operands[1:]): curr_op = ops_lst[index % num_ops] diff --git a/mathics/format/form/outputform.py b/mathics/format/form/outputform.py index 391920a26..7c94252aa 100644 --- a/mathics/format/form/outputform.py +++ b/mathics/format/form/outputform.py @@ -380,7 +380,7 @@ def _infix_outputform_text(expr: Expression, evaluation: Evaluation, **kwargs) - result = parenthesize(precedence, operand, result, parenthesized) # Process the rest of operands - parenthesized = (SymbolLeft, SymbolNonAssociative) + parenthesized = group in (SymbolLeft, SymbolNonAssociative) num_ops = len(ops_lst) for index, operand in enumerate(operands[1:]): curr_op = ops_lst[index % num_ops] From 461f5523a86f0c590e6aa1aa7f1779b03047c146 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Mon, 19 Jan 2026 18:15:47 -0300 Subject: [PATCH 09/31] group in --- mathics/format/form/inputform.py | 2 +- mathics/format/form/outputform.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mathics/format/form/inputform.py b/mathics/format/form/inputform.py index 67e9040e7..e262e167a 100644 --- a/mathics/format/form/inputform.py +++ b/mathics/format/form/inputform.py @@ -169,7 +169,7 @@ def _infix_expression_to_inputform_text( result = parenthesize(precedence, operand, result, parenthesized) # Process the rest of operands - parenthesized = (SymbolLeft, SymbolNonAssociative) + parenthesized = group in (SymbolLeft, SymbolNonAssociative) num_ops = len(ops_lst) for index, operand in enumerate(operands[1:]): curr_op = ops_lst[index % num_ops] diff --git a/mathics/format/form/outputform.py b/mathics/format/form/outputform.py index 3c3789750..9a8c4d084 100644 --- a/mathics/format/form/outputform.py +++ b/mathics/format/form/outputform.py @@ -379,7 +379,7 @@ def _infix_outputform_text(expr: Expression, evaluation: Evaluation, **kwargs) - result = parenthesize(precedence, operand, result, parenthesized) # Process the rest of operands - parenthesized = (SymbolLeft, SymbolNonAssociative) + parenthesized = group in (SymbolLeft, SymbolNonAssociative) num_ops = len(ops_lst) for index, operand in enumerate(operands[1:]): curr_op = ops_lst[index % num_ops] From 54be6192335c8802cfa776c8c6c70a7d44d9d8f7 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Mon, 19 Jan 2026 19:08:46 -0300 Subject: [PATCH 10/31] improve associativity --- mathics/format/form/inputform.py | 6 ++++-- mathics/format/form/outputform.py | 6 ++++-- mathics/format/form/util.py | 25 +++++++++++++++++++------ 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/mathics/format/form/inputform.py b/mathics/format/form/inputform.py index e262e167a..a062c55d1 100644 --- a/mathics/format/form/inputform.py +++ b/mathics/format/form/inputform.py @@ -46,6 +46,8 @@ from .util import ( ARITHMETIC_OPERATOR_STRINGS, BLANKS_TO_STRINGS, + PARENTHESIZED_FIRST, + PARENTHESIZED_REST, _WrongFormattedExpression, collect_in_pre_post_arguments, get_operator_str, @@ -163,13 +165,13 @@ def _infix_expression_to_inputform_text( raise _WrongFormattedExpression # Process the first operand: - parenthesized = group in (SymbolRight, SymbolNonAssociative) + parenthesized = group in PARENTHESIZED_FIRST operand = operands[0] result = str(render_input_form(operand, evaluation, **kwargs)) result = parenthesize(precedence, operand, result, parenthesized) # Process the rest of operands - parenthesized = group in (SymbolLeft, SymbolNonAssociative) + parenthesized = group in PARENTHESIZED_REST num_ops = len(ops_lst) for index, operand in enumerate(operands[1:]): curr_op = ops_lst[index % num_ops] diff --git a/mathics/format/form/outputform.py b/mathics/format/form/outputform.py index 9a8c4d084..749170823 100644 --- a/mathics/format/form/outputform.py +++ b/mathics/format/form/outputform.py @@ -51,6 +51,8 @@ from .inputform import render_input_form from .util import ( BLANKS_TO_STRINGS, + PARENTHESIZED_FIRST, + PARENTHESIZED_REST, PRECEDENCE_FUNCTION_APPLY, PRECEDENCE_PLUS, PRECEDENCE_POWER, @@ -373,13 +375,13 @@ def _infix_outputform_text(expr: Expression, evaluation: Evaluation, **kwargs) - # raise _WrongFormattedExpression # Process the first operand: - parenthesized = group in (SymbolNone, SymbolRight, SymbolNonAssociative) + parenthesized = group in PARENTHESIZED_FIRST operand = operands[0] result = str(render_output_form(operand, evaluation, **kwargs)) result = parenthesize(precedence, operand, result, parenthesized) # Process the rest of operands - parenthesized = group in (SymbolLeft, SymbolNonAssociative) + parenthesized = group in PARENTHESIZED_REST num_ops = len(ops_lst) for index, operand in enumerate(operands[1:]): curr_op = ops_lst[index % num_ops] diff --git a/mathics/format/form/util.py b/mathics/format/form/util.py index 65054b916..17661af9c 100644 --- a/mathics/format/form/util.py +++ b/mathics/format/form/util.py @@ -50,6 +50,20 @@ class _WrongFormattedExpression(Exception): PRECEDENCE_POWER: Final[int] = PRECEDENCES.get("Power", 590) +# These constants are used to decide if two operands with the +# same precedence than the operation which are part of must be +# parenthesized or not. +# For example, `Sequence[a,b,c]` === a;;b;;c has "None" associativity, +# so `Sequence[-1;;-1;;-1]` is formatted as ``-1;;-1;;-1`` +# On the other hand, `Divide` is left associative, so +# `Divide[-1,-1]` is formatted as `-1 / (-1)`, while +# `Power`, which is right associative, format `Power[-1,-1]` +# as `(-1)^-1`. + +PARENTHESIZED_FIRST = {SymbolRight.name, SymbolNonAssociative.name} +PARENTHESIZED_REST = {SymbolLeft.name, SymbolNonAssociative.name} + + BLANKS_TO_STRINGS = { SymbolBlank: "_", SymbolBlankSequence: "__", @@ -64,7 +78,7 @@ def square_bracket(expr_str: str) -> str: def collect_in_pre_post_arguments( expr: Expression, evaluation: Evaluation, **kwargs -) -> Tuple[list, str | List[str], int, Optional[Symbol]]: +) -> Tuple[list, str | List[str], int, str]: """ Determine operands, operator(s), precedence, and grouping """ @@ -82,7 +96,7 @@ def collect_in_pre_post_arguments( raise _WrongFormattedExpression head = expr.head - group = SymbolNone + group_name = "None" precedence = PRECEDENCE_BOX_GROUP operands = list(target.elements) @@ -98,7 +112,7 @@ def collect_in_pre_post_arguments( operator_spec = f"{operator_spec}{operator_to_string['Prefix']}" elif head is SymbolPostfix: operator_spec = f"{operator_to_string['Postfix']}{operator_spec}" - return operands, operator_spec, precedence, group + return operands, operator_spec, precedence, group_name # At least two parameters: get the operator spec. ops = elements[1] @@ -121,10 +135,9 @@ def collect_in_pre_post_arguments( group = elements[3] if group not in (SymbolNone, SymbolLeft, SymbolRight, SymbolNonAssociative): raise _WrongFormattedExpression - if group is SymbolNone: - group = SymbolNone + group_name = group.get_name() - return operands, operator_spec, precedence, group + return operands, operator_spec, precedence, group_name def get_operator_str(head, evaluation, **kwargs) -> str: From 616c143e0aa102fe6d78c3c15d36620930aea4a5 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Mon, 19 Jan 2026 19:27:50 -0300 Subject: [PATCH 11/31] Update mathics/format/form/util.py Co-authored-by: R. Bernstein --- mathics/format/form/util.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mathics/format/form/util.py b/mathics/format/form/util.py index 17661af9c..d8a049dcc 100644 --- a/mathics/format/form/util.py +++ b/mathics/format/form/util.py @@ -50,6 +50,8 @@ class _WrongFormattedExpression(Exception): PRECEDENCE_POWER: Final[int] = PRECEDENCES.get("Power", 590) +# TODO: (rocky) See if we can accomplish parenthesization using precedence and association values as is common for this kind of thing. +# # These constants are used to decide if two operands with the # same precedence than the operation which are part of must be # parenthesized or not. From fd095d9c9e6e3cb4d128f1d59ebe6b315b81e5ae Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Mon, 19 Jan 2026 19:29:03 -0300 Subject: [PATCH 12/31] black --- mathics/format/form/util.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/mathics/format/form/util.py b/mathics/format/form/util.py index d8a049dcc..82679f6f0 100644 --- a/mathics/format/form/util.py +++ b/mathics/format/form/util.py @@ -3,6 +3,7 @@ Common routines and objects used in rendering PrintForms. """ + from typing import Final, FrozenSet, List, Optional, Tuple from mathics.core.atoms import Integer, String @@ -51,7 +52,7 @@ class _WrongFormattedExpression(Exception): # TODO: (rocky) See if we can accomplish parenthesization using precedence and association values as is common for this kind of thing. -# +# # These constants are used to decide if two operands with the # same precedence than the operation which are part of must be # parenthesized or not. @@ -233,12 +234,14 @@ def normalize_cols(rows, full_rows): for line in cell: col_widths[col] = max(col_widths[col], len(line)) rows = [ - row - if is_full_row - else [ - [line.ljust(col_widths[col]) for line in cell] - for col, cell in enumerate(row) - ] + ( + row + if is_full_row + else [ + [line.ljust(col_widths[col]) for line in cell] + for col, cell in enumerate(row) + ] + ) for row in rows ] return rows, col_widths From f6395c88efb1342f6896827add713836557a4f4c Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Mon, 19 Jan 2026 19:54:46 -0300 Subject: [PATCH 13/31] Apply suggestions from code review Co-authored-by: R. Bernstein --- mathics/builtin/patterns/defaults.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mathics/builtin/patterns/defaults.py b/mathics/builtin/patterns/defaults.py index 8183fc424..f908473e4 100644 --- a/mathics/builtin/patterns/defaults.py +++ b/mathics/builtin/patterns/defaults.py @@ -36,7 +36,7 @@ class Optional(InfixOperator, PatternObject): >> f[a] = {a, 1} - Note that '$symb$ : $pattern$' represents a 'Pattern' object. However, there is no + Note that '$symb$ : $pattern$' represents a 'Pattern' object. However, there is no \ disambiguity, since $symb$ has to be a symbol in this case. >> x:_ // FullForm @@ -46,7 +46,7 @@ class Optional(InfixOperator, PatternObject): >> x:_+y_:d // FullForm = Pattern[x, Plus[Blank[], Optional[Pattern[y, Blank[]], d]]] - 's_.' is equivalent to 'Optional[s_]' and represents an optional parameter which, if omitted, + 's_.' is equivalent to 'Optional[s_]' and represents an optional parameter which, if omitted, \ gets its value from 'Default'. >> FullForm[s_.] = Optional[Pattern[s, Blank[]]] From 3a74c9e4d3621cbca8dc155ac35ee15566453ebc Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Mon, 19 Jan 2026 20:04:30 -0300 Subject: [PATCH 14/31] fix alternatives --- mathics/builtin/forms/print.py | 6 +++--- mathics/builtin/graphics.py | 2 +- mathics/builtin/numbers/calculus.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mathics/builtin/forms/print.py b/mathics/builtin/forms/print.py index d7466cc74..59efe7c56 100644 --- a/mathics/builtin/forms/print.py +++ b/mathics/builtin/forms/print.py @@ -118,7 +118,7 @@ class InputForm(FormBaseClass): # TODO: eventually, remove OutputForm in the second argument. def eval_makeboxes(self, expr, evaluation): - """MakeBoxes[InputForm[expr_], Alternatives[StandardForm,TraditionalForm]]""" + """MakeBoxes[InputForm[expr_], StandardForm|TraditionalForm]""" inputform = String(render_input_form(expr, evaluation)) inputform = StyleBox( @@ -170,7 +170,7 @@ class MathMLForm(FormBaseClass): summary_text = "format expression as MathML commands" def eval_mathml(self, expr, evaluation) -> Expression: - "MakeBoxes[MathMLForm[expr_], Alternatives[StandardForm,TraditionalForm]]" + "MakeBoxes[MathMLForm[expr_], StandardForm|TraditionalForm]" return eval_mathmlform(expr, evaluation) @@ -277,7 +277,7 @@ class TeXForm(FormBaseClass): summary_text = "format expression as LaTeX commands" def eval_tex(self, expr, evaluation) -> Expression: - "MakeBoxes[TeXForm[expr_], Alternatives[StandardForm,TraditionalForm]]" + "MakeBoxes[TeXForm[expr_], StandardForm|TraditionalForm]" # TeXForm by default uses `TraditionalForm` return eval_texform(expr, evaluation) diff --git a/mathics/builtin/graphics.py b/mathics/builtin/graphics.py index 6ec9ed50f..f554ba986 100644 --- a/mathics/builtin/graphics.py +++ b/mathics/builtin/graphics.py @@ -316,7 +316,7 @@ class Graphics(Builtin): def eval_makeboxes(self, content, evaluation, options): """MakeBoxes[%(name)s[content_, OptionsPattern[%(name)s]], - Alternatives[StandardForm,TraditionalForm]]""" + StandardForm|TraditionalForm]""" def convert(content): head = content.get_head() diff --git a/mathics/builtin/numbers/calculus.py b/mathics/builtin/numbers/calculus.py index 210d6f601..b9290e6a4 100644 --- a/mathics/builtin/numbers/calculus.py +++ b/mathics/builtin/numbers/calculus.py @@ -422,7 +422,7 @@ class Derivative(PostfixOperator, SympyFunction): "Derivative[0...][f_]": "f", "Derivative[n__Integer][Derivative[m__Integer][f_]] /; Length[{m}] " "== Length[{n}]": "Derivative[Sequence @@ ({n} + {m})][f]", - "Derivative[n__Integer][Alternatives[_Integer|_Rational|_Real|_Complex]]": "0 &", + "Derivative[n__Integer][_Integer|_Rational|_Real|_Complex]": "0 &", # The following rule tries to evaluate a derivative of a pure function by applying it to a list # of symbolic elements and use the rules in `D`. # The rule just applies if f is not a locked symbol, and it does not have a previous definition @@ -491,7 +491,7 @@ def __init__(self, *args, **kwargs): super(Derivative, self).__init__(*args, **kwargs) def eval_locked_symbols(self, n, **kwargs): - """Derivative[n__Integer][Alternatives[True|False|Symbol|TooBig|$Aborted|Removed|Locked|$PrintLiteral|$Off]]/; True""" + """Derivative[n__Integer][True|False|Symbol|TooBig|$Aborted|Removed|Locked|$PrintLiteral|$Off]/; True""" # Conditionals always come first... # Prevents the evaluation for True, False, and other Locked symbols # as function names. This produces a recursion error in the evaluation rule for Derivative. From bc8826b9b385062bc3ab6cb3b5a73750618f6e01 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Tue, 20 Jan 2026 11:49:44 -0300 Subject: [PATCH 15/31] move MakeBoxes rules to their right place --- mathics/builtin/layout.py | 50 +++++++++++++++++++++++++++++-- mathics/builtin/makeboxes.py | 38 +---------------------- mathics/format/form/outputform.py | 7 +++-- 3 files changed, 53 insertions(+), 42 deletions(-) diff --git a/mathics/builtin/layout.py b/mathics/builtin/layout.py index a3785cce3..ded78b2f6 100644 --- a/mathics/builtin/layout.py +++ b/mathics/builtin/layout.py @@ -11,13 +11,14 @@ from mathics.builtin.box.layout import GridBox, PaneBox, RowBox, to_boxes from mathics.builtin.makeboxes import MakeBoxes -from mathics.core.atoms import Real, String +from mathics.core.atoms import Integer, Real, String from mathics.core.builtin import Builtin, Operator, PostfixOperator, PrefixOperator 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, SymbolSubscriptBox from mathics.eval.lists import list_boxes -from mathics.format.box import format_element +from mathics.format.box import eval_infix, eval_postprefix, format_element, parenthesize class Center(Builtin): @@ -172,8 +173,20 @@ class Infix(Builtin): = a + b - c """ + rules = { + ( + "MakeBoxes[Infix[head_[elements___]], " + " f:StandardForm|TraditionalForm]" + ): ('MakeBoxes[Infix[head[elements], StringForm["~`1`~", head]], f]'), + } summary_text = "infix form" + def eval_makeboxes_infix( + self, expr, operator, precedence: Integer, grouping, form: Symbol, evaluation + ): + """MakeBoxes[Infix[expr_, operator_, precedence_:None, grouping_:None], form:StandardForm|TraditionalForm]""" + return eval_infix(self, expr, operator, precedence, grouping, form, evaluation) + class Left(Builtin): """ @@ -278,6 +291,13 @@ class Postfix(PostfixOperator): operator_display = None summary_text = "postfix form" + def eval_makeboxes_postfix(self, expr, h, precedence, form, evaluation): + """MakeBoxes[Postfix[expr_, h_, precedence_:None], + form:StandardForm|TraditionalForm]""" + return eval_postprefix( + self, SymbolPostfix, expr, h, precedence, form, evaluation + ) + class Precedence(Builtin): """ @@ -332,8 +352,20 @@ class PrecedenceForm(Builtin):
'PrecedenceForm'[$expr$, $prec$]
format $expr$ parenthesized as it would be if it contained an operator of precedence $prec$. + + >> PrecedenceForm[x/y, 12] - z + = -z + (x / y) + """ + def eval_outerprecedenceform(self, expr, precedence, form, evaluation): + """MakeBoxes[PrecedenceForm[expr_, precedence_], + form:StandardForm|TraditionalForm]""" + + py_precedence = precedence.get_int_value() + boxes = format_element(expr, evaluation, form) + return parenthesize(py_precedence, expr, boxes, True) + summary_text = "parenthesize with a precedence" @@ -370,6 +402,13 @@ class Prefix(PrefixOperator): operator_display = None summary_text = "prefix form" + def eval_makeboxes_prefix(self, expr, h, precedence, form, evaluation): + """MakeBoxes[Prefix[expr_, h_, precedence_:None], + form:StandardForm|TraditionalForm]""" + return eval_postprefix( + self, SymbolPrefix, expr, h, precedence, form, evaluation + ) + class Right(Builtin): """ @@ -456,7 +495,12 @@ class Style(Builtin): summary_text = "wrapper for styles and style options to apply" options = {"ImageSizeMultipliers": "Automatic"} - + rules = { + "MakeBoxes[Style[expr_, OptionsPattern[Style]], f_]": ( + "StyleBox[MakeBoxes[expr, f], " + "ImageSizeMultipliers -> OptionValue[ImageSizeMultipliers]]" + ), + } rules = { "MakeBoxes[Style[expr_, OptionsPattern[Style]], f_]": ( "StyleBox[MakeBoxes[expr, f], " diff --git a/mathics/builtin/makeboxes.py b/mathics/builtin/makeboxes.py index 8fea8b539..795ee7fdc 100644 --- a/mathics/builtin/makeboxes.py +++ b/mathics/builtin/makeboxes.py @@ -10,9 +10,7 @@ from mathics.core.symbols import Symbol from mathics.format.box import ( eval_generic_makeboxes, - eval_infix, eval_makeboxes_fullform, - eval_postprefix, format_element, parenthesize, ) @@ -91,10 +89,6 @@ class MakeBoxes(Builtin): attributes = A_HOLD_ALL_COMPLETE rules = { - "MakeBoxes[Infix[head_[elements___]], " - " f:StandardForm|TraditionalForm]": ( - 'MakeBoxes[Infix[head[elements], StringForm["~`1`~", head]], f]' - ), "MakeBoxes[expr_]": "MakeBoxes[expr, StandardForm]", # The following rule is temporal. "MakeBoxes[expr_, form:(TeXForm|MathMLForm)]": "MakeBoxes[form[expr], StandardForm]", @@ -102,43 +96,13 @@ class MakeBoxes(Builtin): "MakeBoxes[(form:StandardForm|TraditionalForm)" "[expr_], StandardForm|TraditionalForm]" ): ("MakeBoxes[expr, form]"), - # BoxForms goes as second argument - "MakeBoxes[PrecedenceForm[expr_, prec_], f_]": "MakeBoxes[expr, f]", - "MakeBoxes[Style[expr_, OptionsPattern[Style]], f_]": ( - "StyleBox[MakeBoxes[expr, f], " - "ImageSizeMultipliers -> OptionValue[ImageSizeMultipliers]]" - ), } summary_text = "settable low-level translator from expression to display boxes" - def eval_fullform(self, expr, evaluation): - """MakeBoxes[expr_, FullForm]""" - return eval_makeboxes_fullform(expr, evaluation) - def eval_general(self, expr, f, evaluation): - """MakeBoxes[expr_, - f:TraditionalForm|StandardForm]""" + """MakeBoxes[expr_, f:TraditionalForm|StandardForm]""" return eval_generic_makeboxes(expr, f, evaluation) - def eval_outerprecedenceform(self, expr, precedence, form, evaluation): - """MakeBoxes[PrecedenceForm[expr_, precedence_], - form:StandardForm|TraditionalForm]""" - - py_precedence = precedence.get_int_value() - boxes = MakeBoxes(expr, form) - return parenthesize(py_precedence, expr, boxes, True) - - def eval_postprefix(self, p, expr, h, precedence, form, evaluation): - """MakeBoxes[(p:Prefix|Postfix)[expr_, h_, precedence_:None], - form:StandardForm|TraditionalForm]""" - return eval_postprefix(self, p, expr, h, precedence, form, evaluation) - - def eval_infix( - self, expr, operator, precedence: Integer, grouping, form: Symbol, evaluation - ): - """MakeBoxes[Infix[expr_, operator_, precedence_:None, grouping_:None], form:StandardForm|TraditionalForm]""" - return eval_infix(self, expr, operator, precedence, grouping, form, evaluation) - class ToBoxes(Builtin): """ diff --git a/mathics/format/form/outputform.py b/mathics/format/form/outputform.py index d10a1bc18..e0a02df1f 100644 --- a/mathics/format/form/outputform.py +++ b/mathics/format/form/outputform.py @@ -619,13 +619,16 @@ def power_render_output_form( @register_outputform("System`PrecedenceForm") def precedenceform_render_output_form( - expr: Expression, evaluation: Evaluation, form: Symbol, **kwargs + expr: Expression, evaluation: Evaluation, **kwargs ) -> str: if not isinstance(expr.head, Symbol): raise _WrongFormattedExpression if len(expr.elements) == 2: - return render_output_form(expr.elements[0], evaluation, **kwargs) + arg_1, arg_2 = expr.elements + if not isinstance(arg_2, (Integer, Real)): + raise _WrongFormattedExpression + return render_output_form(arg_1, evaluation, **kwargs) raise _WrongFormattedExpression From b591419f382abcc91214f5aefc63a2d8368b39a4 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Tue, 20 Jan 2026 12:07:02 -0300 Subject: [PATCH 16/31] fix Optional --- mathics/builtin/layout.py | 7 ++++++- mathics/builtin/makeboxes.py | 9 +-------- mathics/builtin/patterns/defaults.py | 25 +++++++++++++++++++++++-- mathics/format/form/inputform.py | 7 +++---- mathics/format/form/outputform.py | 28 ++++++++++++++++++++++++++-- test/format/format_tests.yaml | 24 ++++++++++++++++++++++++ 6 files changed, 83 insertions(+), 17 deletions(-) diff --git a/mathics/builtin/layout.py b/mathics/builtin/layout.py index ded78b2f6..eb9884e32 100644 --- a/mathics/builtin/layout.py +++ b/mathics/builtin/layout.py @@ -16,7 +16,12 @@ 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, SymbolSubscriptBox +from mathics.core.systemsymbols import ( + SymbolMakeBoxes, + SymbolPostfix, + SymbolPrefix, + SymbolSubscriptBox, +) from mathics.eval.lists import list_boxes from mathics.format.box import eval_infix, eval_postprefix, format_element, parenthesize diff --git a/mathics/builtin/makeboxes.py b/mathics/builtin/makeboxes.py index 795ee7fdc..5c8023776 100644 --- a/mathics/builtin/makeboxes.py +++ b/mathics/builtin/makeboxes.py @@ -4,16 +4,9 @@ """ -from mathics.core.atoms import Integer from mathics.core.attributes import A_HOLD_ALL_COMPLETE, A_READ_PROTECTED from mathics.core.builtin import Builtin, Predefined -from mathics.core.symbols import Symbol -from mathics.format.box import ( - eval_generic_makeboxes, - eval_makeboxes_fullform, - format_element, - parenthesize, -) +from mathics.format.box import eval_generic_makeboxes, format_element # TODO: Differently from the current implementation, MakeBoxes should only # accept as its format field the symbols in `$BoxForms`. This is something to diff --git a/mathics/builtin/patterns/defaults.py b/mathics/builtin/patterns/defaults.py index ca38a8ea8..828771360 100644 --- a/mathics/builtin/patterns/defaults.py +++ b/mathics/builtin/patterns/defaults.py @@ -72,8 +72,29 @@ class Optional(InfixOperator, PatternObject): } grouping = "Right" rules = { - "MakeBoxes[Verbatim[Optional][Verbatim[Pattern][symbol_Symbol, Verbatim[_]]], (f:StandardForm|TraditionalForm)]": 'MakeBoxes[symbol, f] <> "_."', - "MakeBoxes[Verbatim[Optional][Verbatim[_]], (f:StandardForm|TraditionalForm)]": '"_."', + ( + "MakeBoxes[Verbatim[Optional][" + "Verbatim[Pattern][symbol_Symbol," + "(kind:(Verbatim[Blank]|Verbatim[BlankSequence]|Verbatim[BlankNullSequence])[])]], " + "(f:StandardForm|TraditionalForm)]" + ): 'MakeBoxes[symbol, f] <> ToString[kind, f] <>"."', + ( + "MakeBoxes[Verbatim[Optional][" + "(kind:(Verbatim[Blank]|Verbatim[BlankSequence]|Verbatim[BlankNullSequence])[])], " + "(f:StandardForm|TraditionalForm)]" + ): 'ToString[kind, f]<>"."', + # Two arguments + ( + "MakeBoxes[Verbatim[Optional][" + "Verbatim[Pattern][symbol_Symbol," + "(kind:(Verbatim[Blank]|Verbatim[BlankSequence]|Verbatim[BlankNullSequence])[]), value_]], " + "(f:StandardForm|TraditionalForm)]" + ): 'RowBox[{MakeBoxes[symbol, f], ToString[kind, f], ":",MakeBoxes[value, f]}]', + ( + "MakeBoxes[Verbatim[Optional][" + "(kind:(Verbatim[Blank]|Verbatim[BlankSequence]|Verbatim[BlankNullSequence])[]), value_], " + "(f:StandardForm|TraditionalForm)]" + ): 'RowBox[{ToString[kind, f], ":", MakeBoxes[value, f]}]', } summary_text = "an optional argument with a default value" diff --git a/mathics/format/form/inputform.py b/mathics/format/form/inputform.py index a062c55d1..f20f02d63 100644 --- a/mathics/format/form/inputform.py +++ b/mathics/format/form/inputform.py @@ -35,9 +35,7 @@ from mathics.core.symbols import Atom from mathics.core.systemsymbols import ( SymbolInputForm, - SymbolLeft, SymbolNonAssociative, - SymbolNone, SymbolRight, ) from mathics.format.box.formatvalues import do_format # , format_element @@ -274,8 +272,9 @@ def _optional(expr: Expression, evaluation: Evaluation, **kwargs) -> str: if not operand.has_form(("Blank", "BlankNullSequence", "BlankSequence"), 0): raise _WrongFormattedExpression - result = name + BLANKS_TO_STRINGS[operand.head] + post - # `name_.` cannot be reentered if it is not wrapped in parenthesis: + blank_kind = operand.head + result = name + BLANKS_TO_STRINGS[blank_kind] + post + # `name__.` cannot be reentered if it is not wrapped in parenthesis: if post == ".": result = f"({result})" return result diff --git a/mathics/format/form/outputform.py b/mathics/format/form/outputform.py index e0a02df1f..4d4afce4f 100644 --- a/mathics/format/form/outputform.py +++ b/mathics/format/form/outputform.py @@ -30,7 +30,6 @@ SymbolInfix, SymbolLeft, SymbolNonAssociative, - SymbolNone, SymbolOutputForm, SymbolPower, SymbolRight, @@ -321,7 +320,6 @@ def other_forms(expr, evaluation, **kwargs): if not isinstance(expr.head, Symbol): raise _WrongFormattedExpression - print("format", expr) result = format_element(expr, evaluation, SymbolStandardForm, **kwargs) return result.boxes_to_text() @@ -490,6 +488,32 @@ def _numberform_outputform(expr, evaluation, **kwargs): return render_output_form(target, evaluation, **kwargs) +# TODO: DRY ME with input form +@register_outputform("System`Optional") +def _optional(expr: Expression, evaluation: Evaluation, **kwargs) -> str: + name: str = "" + post: str = "" + elements = expr.elements + if not expr.has_form("Optional", 1, 2): + raise _WrongFormattedExpression + if len(elements) == 2: + post = ":" + render_output_form(elements[1], evaluation, **kwargs) + else: + post = "." + + operand = elements[0] + if operand.has_form("Pattern", 2): + name = render_output_form(operand.elements[0], evaluation, **kwargs) + operand = operand.elements[1] + + if not operand.has_form(("Blank", "BlankNullSequence", "BlankSequence"), 0): + raise _WrongFormattedExpression + + blank_kind = operand.head + result = name + BLANKS_TO_STRINGS[blank_kind] + post + return result + + @register_outputform("System`Out") def out_outputform(expr: Expression, evaluation: Evaluation, **kwargs): if not isinstance(expr.head, Symbol): diff --git a/test/format/format_tests.yaml b/test/format/format_tests.yaml index 5fba246d5..dcd43dde8 100644 --- a/test/format/format_tests.yaml +++ b/test/format/format_tests.yaml @@ -802,3 +802,27 @@ a^4: System`OutputForm: a ^ 4 System`StandardForm: a^4 System`TraditionalForm: a^4 +Optional[x__]: + msg: Optional with one argument + latex: + System`OutputForm: '\text{x$\_\_$.}' + mathml: + System`OutputForm: 'x__.' + System`StandardForm: 'x__.' + text: + System`InputForm: '(x__.)' + System`OutputForm: 'x__.' + System`StandardForm: 'x__.' + System`TraditionalForm: 'x__.' +Optional[x__, a+b]: + msg: Optional with two arguments + latex: + System`OutputForm: ' \text{x$\_\_$ : a + b}' + System`StandardForm: '\text{x$\_\_$}:a+b' + mathml: + System`OutputForm: 'x__ : a + b' + text: + System`InputForm: 'x__ : a + b' + System`OutputForm: 'x__ : a + b' + System`StandardForm: 'x__:a+b' + System`TraditionalForm: 'x__:a+b' From e597a041de35b254fcc93a0e81b70e5531fb65c4 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Tue, 20 Jan 2026 12:24:59 -0300 Subject: [PATCH 17/31] handle `Optional` in OutputForm. Fix PrecedenceForm. --- mathics/format/form/inputform.py | 7 +++---- mathics/format/form/outputform.py | 35 +++++++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/mathics/format/form/inputform.py b/mathics/format/form/inputform.py index a062c55d1..f20f02d63 100644 --- a/mathics/format/form/inputform.py +++ b/mathics/format/form/inputform.py @@ -35,9 +35,7 @@ from mathics.core.symbols import Atom from mathics.core.systemsymbols import ( SymbolInputForm, - SymbolLeft, SymbolNonAssociative, - SymbolNone, SymbolRight, ) from mathics.format.box.formatvalues import do_format # , format_element @@ -274,8 +272,9 @@ def _optional(expr: Expression, evaluation: Evaluation, **kwargs) -> str: if not operand.has_form(("Blank", "BlankNullSequence", "BlankSequence"), 0): raise _WrongFormattedExpression - result = name + BLANKS_TO_STRINGS[operand.head] + post - # `name_.` cannot be reentered if it is not wrapped in parenthesis: + blank_kind = operand.head + result = name + BLANKS_TO_STRINGS[blank_kind] + post + # `name__.` cannot be reentered if it is not wrapped in parenthesis: if post == ".": result = f"({result})" return result diff --git a/mathics/format/form/outputform.py b/mathics/format/form/outputform.py index d10a1bc18..4d4afce4f 100644 --- a/mathics/format/form/outputform.py +++ b/mathics/format/form/outputform.py @@ -30,7 +30,6 @@ SymbolInfix, SymbolLeft, SymbolNonAssociative, - SymbolNone, SymbolOutputForm, SymbolPower, SymbolRight, @@ -321,7 +320,6 @@ def other_forms(expr, evaluation, **kwargs): if not isinstance(expr.head, Symbol): raise _WrongFormattedExpression - print("format", expr) result = format_element(expr, evaluation, SymbolStandardForm, **kwargs) return result.boxes_to_text() @@ -490,6 +488,32 @@ def _numberform_outputform(expr, evaluation, **kwargs): return render_output_form(target, evaluation, **kwargs) +# TODO: DRY ME with input form +@register_outputform("System`Optional") +def _optional(expr: Expression, evaluation: Evaluation, **kwargs) -> str: + name: str = "" + post: str = "" + elements = expr.elements + if not expr.has_form("Optional", 1, 2): + raise _WrongFormattedExpression + if len(elements) == 2: + post = ":" + render_output_form(elements[1], evaluation, **kwargs) + else: + post = "." + + operand = elements[0] + if operand.has_form("Pattern", 2): + name = render_output_form(operand.elements[0], evaluation, **kwargs) + operand = operand.elements[1] + + if not operand.has_form(("Blank", "BlankNullSequence", "BlankSequence"), 0): + raise _WrongFormattedExpression + + blank_kind = operand.head + result = name + BLANKS_TO_STRINGS[blank_kind] + post + return result + + @register_outputform("System`Out") def out_outputform(expr: Expression, evaluation: Evaluation, **kwargs): if not isinstance(expr.head, Symbol): @@ -619,13 +643,16 @@ def power_render_output_form( @register_outputform("System`PrecedenceForm") def precedenceform_render_output_form( - expr: Expression, evaluation: Evaluation, form: Symbol, **kwargs + expr: Expression, evaluation: Evaluation, **kwargs ) -> str: if not isinstance(expr.head, Symbol): raise _WrongFormattedExpression if len(expr.elements) == 2: - return render_output_form(expr.elements[0], evaluation, **kwargs) + arg_1, arg_2 = expr.elements + if not isinstance(arg_2, (Integer, Real)): + raise _WrongFormattedExpression + return render_output_form(arg_1, evaluation, **kwargs) raise _WrongFormattedExpression From de96e3894fdebff1efa023e80eac3a1f90040749 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Tue, 20 Jan 2026 12:40:48 -0300 Subject: [PATCH 18/31] add more tests for format_test.yaml --- test/format/format_tests.yaml | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/test/format/format_tests.yaml b/test/format/format_tests.yaml index dcd43dde8..d9d92c555 100644 --- a/test/format/format_tests.yaml +++ b/test/format/format_tests.yaml @@ -805,7 +805,8 @@ a^4: Optional[x__]: msg: Optional with one argument latex: - System`OutputForm: '\text{x$\_\_$.}' + System`OutputForm: '\text{x\_\_.}' + System`StandardForm: '\text{x\_\_.}' mathml: System`OutputForm: 'x__.' System`StandardForm: 'x__.' @@ -817,8 +818,8 @@ Optional[x__]: Optional[x__, a+b]: msg: Optional with two arguments latex: - System`OutputForm: ' \text{x$\_\_$ : a + b}' - System`StandardForm: '\text{x$\_\_$}:a+b' + System`OutputForm: ' \text{x\_\_ : a + b}' + System`StandardForm: '\text{x\_\_}:a+b' mathml: System`OutputForm: 'x__ : a + b' text: @@ -826,3 +827,16 @@ Optional[x__, a+b]: System`OutputForm: 'x__ : a + b' System`StandardForm: 'x__:a+b' System`TraditionalForm: 'x__:a+b' +a+PrecedenceForm[b+c,10]: + msg: "PrecedenceForm" + latex: + System`OutputForm: '\text{a + (b + c)}' + System`StandardForm: 'a+\left(b+c\right)' + mathml: + System`OutputForm: 'a + (b + c)' + System`StandardForm: 'a + ( b + c )' + text: + System`InputForm: 'a + (PrecedenceForm[b + c, 10])' + System`OutputForm: 'a + (b + c)' + System`StandardForm: 'a+(b+c)' + System`TraditionalForm: 'a+(b+c)' From a718404c611e6dd98cee39fbdf508212e09a0378 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Wed, 21 Jan 2026 16:05:21 -0300 Subject: [PATCH 19/31] makeboxes_overhault --- mathics/builtin/box/layout.py | 44 ++++ mathics/builtin/forms/data.py | 2 +- mathics/builtin/forms/print.py | 94 ++++----- mathics/builtin/layout.py | 57 ++++- mathics/builtin/list/constructing.py | 15 +- mathics/builtin/makeboxes.py | 76 +++---- mathics/builtin/patterns/defaults.py | 25 ++- mathics/core/builtin.py | 9 +- mathics/core/load_builtin.py | 5 +- mathics/doc/documentation/1-Manual.mdoc | 3 +- mathics/eval/assignments/assignment.py | 29 ++- mathics/eval/lists.py | 3 +- mathics/format/box/__init__.py | 4 - mathics/format/box/makeboxes.py | 195 ++++++++++++------ mathics/format/box/outputforms.py | 39 +++- mathics/format/form/outputform.py | 1 - test/builtin/box/test_custom_boxexpression.py | 48 ++++- test/format/format_tests.yaml | 47 +++++ 18 files changed, 462 insertions(+), 234 deletions(-) diff --git a/mathics/builtin/box/layout.py b/mathics/builtin/box/layout.py index 4c1c33c55..e532a5999 100644 --- a/mathics/builtin/box/layout.py +++ b/mathics/builtin/box/layout.py @@ -85,6 +85,50 @@ def is_constant_list(list): return True +class FormBox(BoxExpression): + """ + + :WMA link: + https://reference.wolfram.com/language/ref/FormBox.html + +
+
'FormBox[boxes, form]' +
is a low-level box construct that displays as \ + boxes and keep information about the form used to generate \ + the box representation. +
+ """ + + attributes = A_PROTECTED | A_READ_PROTECTED + summary_text = "box with an associated form" + + def init(self, *elems, **kwargs): + self.box_options = kwargs + self.form = elems[1] + self.boxed = elems[0] + assert isinstance(self.boxed, BoxElementMixin), f"{type(self.boxes)}" + + @property + def elements(self): + if self._elements is None: + self._elements = elements_to_expressions( + self, + ( + self.boxed, + self.form, + ), + self.box_options, + ) + return self._elements + + def eval_tagbox(self, expr, form: Symbol, evaluation: Evaluation): + """FormBox[expr_, form_Symbol]""" + options = {} + expr = to_boxes(expr, evaluation, options) + assert isinstance(expr, BoxElementMixin), f"{expr}" + return FormBox(expr, form, **options) + + class FractionBox(BoxExpression): """ diff --git a/mathics/builtin/forms/data.py b/mathics/builtin/forms/data.py index 6ce2f7d3f..6d042f27d 100644 --- a/mathics/builtin/forms/data.py +++ b/mathics/builtin/forms/data.py @@ -791,7 +791,7 @@ class MatrixForm(TableForm): in_printforms = False summary_text = "format as a matrix" - def eval_makeboxes_matrix(self, table, form, evaluation, options): + def eval_makeboxes(self, table, form, evaluation, options): """MakeBoxes[MatrixForm[table_, OptionsPattern[]], (form:StandardForm|TraditionalForm)]""" result = super().eval_makeboxes(table, form, evaluation, options) diff --git a/mathics/builtin/forms/print.py b/mathics/builtin/forms/print.py index 59efe7c56..d58db57f9 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,35 @@ 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`ShowSpecialCharacters": SymbolFalse, + "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/layout.py b/mathics/builtin/layout.py index a3785cce3..eb9884e32 100644 --- a/mathics/builtin/layout.py +++ b/mathics/builtin/layout.py @@ -11,13 +11,19 @@ from mathics.builtin.box.layout import GridBox, PaneBox, RowBox, to_boxes from mathics.builtin.makeboxes import MakeBoxes -from mathics.core.atoms import Real, String +from mathics.core.atoms import Integer, Real, String from mathics.core.builtin import Builtin, Operator, PostfixOperator, PrefixOperator from mathics.core.expression import Evaluation, Expression from mathics.core.list import ListExpression -from mathics.core.systemsymbols import SymbolMakeBoxes, SymbolSubscriptBox +from mathics.core.symbols import Symbol +from mathics.core.systemsymbols import ( + SymbolMakeBoxes, + SymbolPostfix, + SymbolPrefix, + SymbolSubscriptBox, +) from mathics.eval.lists import list_boxes -from mathics.format.box import format_element +from mathics.format.box import eval_infix, eval_postprefix, format_element, parenthesize class Center(Builtin): @@ -172,8 +178,20 @@ class Infix(Builtin): = a + b - c """ + rules = { + ( + "MakeBoxes[Infix[head_[elements___]], " + " f:StandardForm|TraditionalForm]" + ): ('MakeBoxes[Infix[head[elements], StringForm["~`1`~", head]], f]'), + } summary_text = "infix form" + def eval_makeboxes_infix( + self, expr, operator, precedence: Integer, grouping, form: Symbol, evaluation + ): + """MakeBoxes[Infix[expr_, operator_, precedence_:None, grouping_:None], form:StandardForm|TraditionalForm]""" + return eval_infix(self, expr, operator, precedence, grouping, form, evaluation) + class Left(Builtin): """ @@ -278,6 +296,13 @@ class Postfix(PostfixOperator): operator_display = None summary_text = "postfix form" + def eval_makeboxes_postfix(self, expr, h, precedence, form, evaluation): + """MakeBoxes[Postfix[expr_, h_, precedence_:None], + form:StandardForm|TraditionalForm]""" + return eval_postprefix( + self, SymbolPostfix, expr, h, precedence, form, evaluation + ) + class Precedence(Builtin): """ @@ -332,8 +357,20 @@ class PrecedenceForm(Builtin):
'PrecedenceForm'[$expr$, $prec$]
format $expr$ parenthesized as it would be if it contained an operator of precedence $prec$. + + >> PrecedenceForm[x/y, 12] - z + = -z + (x / y) + """ + def eval_outerprecedenceform(self, expr, precedence, form, evaluation): + """MakeBoxes[PrecedenceForm[expr_, precedence_], + form:StandardForm|TraditionalForm]""" + + py_precedence = precedence.get_int_value() + boxes = format_element(expr, evaluation, form) + return parenthesize(py_precedence, expr, boxes, True) + summary_text = "parenthesize with a precedence" @@ -370,6 +407,13 @@ class Prefix(PrefixOperator): operator_display = None summary_text = "prefix form" + def eval_makeboxes_prefix(self, expr, h, precedence, form, evaluation): + """MakeBoxes[Prefix[expr_, h_, precedence_:None], + form:StandardForm|TraditionalForm]""" + return eval_postprefix( + self, SymbolPrefix, expr, h, precedence, form, evaluation + ) + class Right(Builtin): """ @@ -456,7 +500,12 @@ class Style(Builtin): summary_text = "wrapper for styles and style options to apply" options = {"ImageSizeMultipliers": "Automatic"} - + rules = { + "MakeBoxes[Style[expr_, OptionsPattern[Style]], f_]": ( + "StyleBox[MakeBoxes[expr, f], " + "ImageSizeMultipliers -> OptionValue[ImageSizeMultipliers]]" + ), + } rules = { "MakeBoxes[Style[expr_, OptionsPattern[Style]], f_]": ( "StyleBox[MakeBoxes[expr, f], " diff --git a/mathics/builtin/list/constructing.py b/mathics/builtin/list/constructing.py index 2e084b542..74f89ffbf 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,12 +164,12 @@ 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, "{", "}")) + # 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 8fea8b539..56a67f143 100644 --- a/mathics/builtin/makeboxes.py +++ b/mathics/builtin/makeboxes.py @@ -3,19 +3,9 @@ Low-level Format definitions """ - -from mathics.core.atoms import Integer from mathics.core.attributes import A_HOLD_ALL_COMPLETE, A_READ_PROTECTED from mathics.core.builtin import Builtin, Predefined -from mathics.core.symbols import Symbol -from mathics.format.box import ( - eval_generic_makeboxes, - eval_infix, - eval_makeboxes_fullform, - eval_postprefix, - format_element, - parenthesize, -) +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 @@ -91,53 +81,36 @@ class MakeBoxes(Builtin): attributes = A_HOLD_ALL_COMPLETE rules = { - "MakeBoxes[Infix[head_[elements___]], " - " f:StandardForm|TraditionalForm]": ( - 'MakeBoxes[Infix[head[elements], StringForm["~`1`~", head]], f]' - ), "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]"), - # BoxForms goes as second argument - "MakeBoxes[PrecedenceForm[expr_, prec_], f_]": "MakeBoxes[expr, f]", - "MakeBoxes[Style[expr_, OptionsPattern[Style]], f_]": ( - "StyleBox[MakeBoxes[expr, f], " - "ImageSizeMultipliers -> OptionValue[ImageSizeMultipliers]]" - ), } summary_text = "settable low-level translator from expression to display boxes" - def eval_fullform(self, expr, evaluation): - """MakeBoxes[expr_, FullForm]""" - return eval_makeboxes_fullform(expr, evaluation) - def eval_general(self, expr, f, evaluation): - """MakeBoxes[expr_, - f:TraditionalForm|StandardForm]""" - return eval_generic_makeboxes(expr, f, evaluation) - - def eval_outerprecedenceform(self, expr, precedence, form, evaluation): - """MakeBoxes[PrecedenceForm[expr_, precedence_], - form:StandardForm|TraditionalForm]""" - - py_precedence = precedence.get_int_value() - boxes = MakeBoxes(expr, form) - return parenthesize(py_precedence, expr, boxes, True) - - def eval_postprefix(self, p, expr, h, precedence, form, evaluation): - """MakeBoxes[(p:Prefix|Postfix)[expr_, h_, precedence_:None], - form:StandardForm|TraditionalForm]""" - return eval_postprefix(self, p, expr, h, precedence, form, evaluation) - - def eval_infix( - self, expr, operator, precedence: Integer, grouping, form: Symbol, evaluation - ): - """MakeBoxes[Infix[expr_, operator_, precedence_:None, grouping_:None], form:StandardForm|TraditionalForm]""" - return eval_infix(self, expr, operator, precedence, grouping, form, evaluation) + """MakeBoxes[expr_, f:TraditionalForm|StandardForm]""" + return format_element(expr, evaluation, f) + + # TODO: Consider to attach this rule as FormatValue of MakeBoxes. + # + # In WMA, "upvalue" rules are considered before downvalues. + # Consider this assignments: + # + # MakeBoxes[F[x_],_]:="1" + # MakeBoxes[F[x_],_]^:="2" + # MakeBoxes[F[3],_]:="3" + # + # If we evaluate StandardForm[F[3]] is evaluated, we get "2". + # Now, if we set + # + # MakeBoxes[TeXForm[c],_]:="x" + # in WMA, StandardForm[TeXForm[c]] result in "x", but + # here, the default rule (which is an upvalue) is applied giving + # "c". + # Same apply to MathMLForm and other forms. + + def format_inputform(self, expr, evaluation): + """(_MakeBoxes,):MakeBoxes[InputForm[expr_], StandardForm|TraditionalForm]""" class ToBoxes(Builtin): @@ -169,5 +142,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/builtin/patterns/defaults.py b/mathics/builtin/patterns/defaults.py index ca38a8ea8..828771360 100644 --- a/mathics/builtin/patterns/defaults.py +++ b/mathics/builtin/patterns/defaults.py @@ -72,8 +72,29 @@ class Optional(InfixOperator, PatternObject): } grouping = "Right" rules = { - "MakeBoxes[Verbatim[Optional][Verbatim[Pattern][symbol_Symbol, Verbatim[_]]], (f:StandardForm|TraditionalForm)]": 'MakeBoxes[symbol, f] <> "_."', - "MakeBoxes[Verbatim[Optional][Verbatim[_]], (f:StandardForm|TraditionalForm)]": '"_."', + ( + "MakeBoxes[Verbatim[Optional][" + "Verbatim[Pattern][symbol_Symbol," + "(kind:(Verbatim[Blank]|Verbatim[BlankSequence]|Verbatim[BlankNullSequence])[])]], " + "(f:StandardForm|TraditionalForm)]" + ): 'MakeBoxes[symbol, f] <> ToString[kind, f] <>"."', + ( + "MakeBoxes[Verbatim[Optional][" + "(kind:(Verbatim[Blank]|Verbatim[BlankSequence]|Verbatim[BlankNullSequence])[])], " + "(f:StandardForm|TraditionalForm)]" + ): 'ToString[kind, f]<>"."', + # Two arguments + ( + "MakeBoxes[Verbatim[Optional][" + "Verbatim[Pattern][symbol_Symbol," + "(kind:(Verbatim[Blank]|Verbatim[BlankSequence]|Verbatim[BlankNullSequence])[]), value_]], " + "(f:StandardForm|TraditionalForm)]" + ): 'RowBox[{MakeBoxes[symbol, f], ToString[kind, f], ":",MakeBoxes[value, f]}]', + ( + "MakeBoxes[Verbatim[Optional][" + "(kind:(Verbatim[Blank]|Verbatim[BlankSequence]|Verbatim[BlankNullSequence])[]), value_], " + "(f:StandardForm|TraditionalForm)]" + ): 'RowBox[{ToString[kind, f], ":", MakeBoxes[value, f]}]', } summary_text = "an optional argument with a default value" 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..6617d86ab 100644 --- a/mathics/format/box/outputforms.py +++ b/mathics/format/box/outputforms.py @@ -1,18 +1,29 @@ 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 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 RowBox + boxes = format_element(expr, evaluation, SymbolTraditionalForm) try: mathml = boxes.boxes_to_mathml(evaluation=evaluation) @@ -34,14 +45,19 @@ def eval_mathmlform(expr, evaluation) -> Expression: mathml = '%s' % mathml mathml = '%s' % mathml # convert_box(boxes) - return Expression(SymbolRowBox, ListExpression(String(mathml))) + return RowBox(String(mathml)) -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 +109,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 +133,8 @@ def eval_texform(expr, evaluation) -> Expression: Expression(SymbolFullForm, expr).evaluate(evaluation), ) tex = "" - return Expression(SymbolRowBox, ListExpression(String(tex))) + return InterpretationBox( + String(tex), + Expression(SymbolTeXForm, expr), + **{"System`AutoDelete": SymbolTrue, "System`Editable": SymbolTrue}, + ) diff --git a/mathics/format/form/outputform.py b/mathics/format/form/outputform.py index 4d4afce4f..585062b08 100644 --- a/mathics/format/form/outputform.py +++ b/mathics/format/form/outputform.py @@ -319,7 +319,6 @@ def other_forms(expr, evaluation, **kwargs): if not isinstance(expr.head, Symbol): raise _WrongFormattedExpression - result = format_element(expr, evaluation, SymbolStandardForm, **kwargs) return result.boxes_to_text() 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/format_tests.yaml b/test/format/format_tests.yaml index b8eb032e6..a9cfed0ad 100644 --- a/test/format/format_tests.yaml +++ b/test/format/format_tests.yaml @@ -23,6 +23,7 @@ # because we use both in documentation and in the web interface. # + '"-7.32"': msg: A String with a number latex: @@ -813,6 +814,8 @@ TableForm[{{a,b},{c,d}}]: System`OutputForm: 'α' System`StandardForm: "α" System`TraditionalForm: "α" + + a: msg: A Symbol latex: @@ -867,3 +870,47 @@ a^4: System`OutputForm: a ^ 4 System`StandardForm: a^4 System`TraditionalForm: a^4 + + +Optional[x__]: + msg: Optional with one argument + latex: + System`OutputForm: '\text{x\_\_.}' + System`StandardForm: '\text{x\_\_.}' + mathml: + System`OutputForm: 'x__.' + System`StandardForm: 'x__.' + text: + System`InputForm: '(x__.)' + System`OutputForm: 'x__.' + System`StandardForm: 'x__.' + System`TraditionalForm: 'x__.' + + +Optional[x__, a+b]: + msg: Optional with two arguments + latex: + System`OutputForm: ' \text{x\_\_ : a + b}' + System`StandardForm: '\text{x\_\_}:a+b' + mathml: + System`OutputForm: 'x__ : a + b' + text: + System`InputForm: 'x__ : a + b' + System`OutputForm: 'x__ : a + b' + System`StandardForm: 'x__:a+b' + System`TraditionalForm: 'x__:a+b' + + +a+PrecedenceForm[b+c,10]: + msg: "PrecedenceForm" + latex: + System`OutputForm: '\text{a + (b + c)}' + System`StandardForm: 'a+\left(b+c\right)' + mathml: + System`OutputForm: 'a + (b + c)' + System`StandardForm: 'a + ( b + c )' + text: + System`InputForm: 'a + (PrecedenceForm[b + c, 10])' + System`OutputForm: 'a + (b + c)' + System`StandardForm: 'a+(b+c)' + System`TraditionalForm: 'a+(b+c)' From a7333491c87f196c63fbc57cf24daac74f23ae62 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Wed, 21 Jan 2026 16:08:00 -0300 Subject: [PATCH 20/31] add FormBox --- mathics/builtin/box/layout.py | 44 +++++++++++++++++++++++++++++++++ mathics/format/render/latex.py | 6 +++-- mathics/format/render/mathml.py | 6 +++-- mathics/format/render/text.py | 6 +++-- 4 files changed, 56 insertions(+), 6 deletions(-) diff --git a/mathics/builtin/box/layout.py b/mathics/builtin/box/layout.py index 4c1c33c55..e532a5999 100644 --- a/mathics/builtin/box/layout.py +++ b/mathics/builtin/box/layout.py @@ -85,6 +85,50 @@ def is_constant_list(list): return True +class FormBox(BoxExpression): + """ + + :WMA link: + https://reference.wolfram.com/language/ref/FormBox.html + +
+
'FormBox[boxes, form]' +
is a low-level box construct that displays as \ + boxes and keep information about the form used to generate \ + the box representation. +
+ """ + + attributes = A_PROTECTED | A_READ_PROTECTED + summary_text = "box with an associated form" + + def init(self, *elems, **kwargs): + self.box_options = kwargs + self.form = elems[1] + self.boxed = elems[0] + assert isinstance(self.boxed, BoxElementMixin), f"{type(self.boxes)}" + + @property + def elements(self): + if self._elements is None: + self._elements = elements_to_expressions( + self, + ( + self.boxed, + self.form, + ), + self.box_options, + ) + return self._elements + + def eval_tagbox(self, expr, form: Symbol, evaluation: Evaluation): + """FormBox[expr_, form_Symbol]""" + options = {} + expr = to_boxes(expr, evaluation, options) + assert isinstance(expr, BoxElementMixin), f"{expr}" + return FormBox(expr, form, **options) + + class FractionBox(BoxExpression): """ diff --git a/mathics/format/render/latex.py b/mathics/format/render/latex.py index fe9adc75c..fc1d54163 100644 --- a/mathics/format/render/latex.py +++ b/mathics/format/render/latex.py @@ -17,6 +17,7 @@ from mathics.builtin.box.graphics import GraphicsBox from mathics.builtin.box.graphics3d import Graphics3DBox from mathics.builtin.box.layout import ( + FormBox, FractionBox, GridBox, InterpretationBox, @@ -646,8 +647,9 @@ def graphics3dbox(self, elements=None, **options) -> str: add_conversion_fn(Graphics3DBox, graphics3dbox) -def tag_box(self, **options): +def tag_and_form_box(self, **options): return lookup_conversion_method(self.boxed, "latex")(self.boxed, **options) -add_conversion_fn(TagBox, tag_box) +add_conversion_fn(FormBox, tag_and_form_box) +add_conversion_fn(TagBox, tag_and_form_box) diff --git a/mathics/format/render/mathml.py b/mathics/format/render/mathml.py index d48a2eb6f..e229e0c18 100644 --- a/mathics/format/render/mathml.py +++ b/mathics/format/render/mathml.py @@ -13,6 +13,7 @@ from mathics.builtin.box.graphics import GraphicsBox from mathics.builtin.box.graphics3d import Graphics3DBox from mathics.builtin.box.layout import ( + FormBox, FractionBox, GridBox, InterpretationBox, @@ -371,8 +372,9 @@ def graphics3dbox(self, elements=None, **options) -> str: add_conversion_fn(Graphics3DBox, graphics3dbox) -def tag_box(self, **options): +def tag_and_form_box(self, **options): return lookup_conversion_method(self.boxed, "mathml")(self.boxed, **options) -add_conversion_fn(TagBox, tag_box) +add_conversion_fn(FormBox, tag_and_form_box) +add_conversion_fn(TagBox, tag_and_form_box) diff --git a/mathics/format/render/text.py b/mathics/format/render/text.py index 59e9236c2..49a71a510 100644 --- a/mathics/format/render/text.py +++ b/mathics/format/render/text.py @@ -7,6 +7,7 @@ from mathics.builtin.box.graphics import GraphicsBox from mathics.builtin.box.graphics3d import Graphics3DBox from mathics.builtin.box.layout import ( + FormBox, FractionBox, GridBox, InterpretationBox, @@ -235,8 +236,9 @@ def graphics3dbox(self, elements=None, **options) -> str: add_conversion_fn(Graphics3DBox, graphics3dbox) -def tag_box(self, **options): +def tag_and_form_box(self, **options): return boxes_to_text(self.boxed, **options) -add_conversion_fn(TagBox, tag_box) +add_conversion_fn(FormBox, tag_and_form_box) +add_conversion_fn(TagBox, tag_and_form_box) From a97fa9b4220857ee0e7807dd5aaaf153cbe80eae Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Wed, 21 Jan 2026 16:47:27 -0300 Subject: [PATCH 21/31] removing trailing code --- mathics/builtin/list/constructing.py | 7 ------- mathics/builtin/makeboxes.py | 21 --------------------- mathics/format/form/outputform.py | 1 + 3 files changed, 1 insertion(+), 28 deletions(-) diff --git a/mathics/builtin/list/constructing.py b/mathics/builtin/list/constructing.py index 74f89ffbf..9bcd1b64f 100644 --- a/mathics/builtin/list/constructing.py +++ b/mathics/builtin/list/constructing.py @@ -164,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 56a67f143..2dea6be10 100644 --- a/mathics/builtin/makeboxes.py +++ b/mathics/builtin/makeboxes.py @@ -91,27 +91,6 @@ def eval_general(self, expr, f, evaluation): """MakeBoxes[expr_, f:TraditionalForm|StandardForm]""" return format_element(expr, evaluation, f) - # TODO: Consider to attach this rule as FormatValue of MakeBoxes. - # - # In WMA, "upvalue" rules are considered before downvalues. - # Consider this assignments: - # - # MakeBoxes[F[x_],_]:="1" - # MakeBoxes[F[x_],_]^:="2" - # MakeBoxes[F[3],_]:="3" - # - # If we evaluate StandardForm[F[3]] is evaluated, we get "2". - # Now, if we set - # - # MakeBoxes[TeXForm[c],_]:="x" - # in WMA, StandardForm[TeXForm[c]] result in "x", but - # here, the default rule (which is an upvalue) is applied giving - # "c". - # Same apply to MathMLForm and other forms. - - def format_inputform(self, expr, evaluation): - """(_MakeBoxes,):MakeBoxes[InputForm[expr_], StandardForm|TraditionalForm]""" - class ToBoxes(Builtin): """ diff --git a/mathics/format/form/outputform.py b/mathics/format/form/outputform.py index 585062b08..4d4afce4f 100644 --- a/mathics/format/form/outputform.py +++ b/mathics/format/form/outputform.py @@ -319,6 +319,7 @@ def other_forms(expr, evaluation, **kwargs): if not isinstance(expr.head, Symbol): raise _WrongFormattedExpression + result = format_element(expr, evaluation, SymbolStandardForm, **kwargs) return result.boxes_to_text() From 76fac714553baf154ca3b4dc7bfb924e1ce5ac4f Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Wed, 21 Jan 2026 19:49:39 -0300 Subject: [PATCH 22/31] interpretationbox tweaks --- mathics/builtin/box/layout.py | 2 -- mathics/builtin/forms/print.py | 1 - mathics/core/atoms/strings.py | 11 +++++++++-- mathics/format/box/outputforms.py | 14 +++++++++++--- test/format/makeboxes_tests.yaml | 24 ++++++++++++------------ test/format/test_makeboxes.py | 8 ++++---- test/helper.py | 4 ++-- 7 files changed, 38 insertions(+), 26 deletions(-) diff --git a/mathics/builtin/box/layout.py b/mathics/builtin/box/layout.py index e532a5999..e0267aa08 100644 --- a/mathics/builtin/box/layout.py +++ b/mathics/builtin/box/layout.py @@ -514,8 +514,6 @@ class StyleBox(BoxExpression): """ options = { - "ShowStringCharacters": "False", - "ShowSpecialCharacters": "False", "$OptionSyntax": "Ignore", } attributes = A_PROTECTED | A_READ_PROTECTED diff --git a/mathics/builtin/forms/print.py b/mathics/builtin/forms/print.py index d58db57f9..01ca58e8f 100644 --- a/mathics/builtin/forms/print.py +++ b/mathics/builtin/forms/print.py @@ -237,7 +237,6 @@ def eval_makeboxes_inputform(expr: BaseElement, evaluation: Evaluation): inputform = StyleBox( inputform, **{ - "System`ShowSpecialCharacters": SymbolFalse, "System`ShowStringCharacters": SymbolTrue, "System`NumberMarks": SymbolTrue, }, diff --git a/mathics/core/atoms/strings.py b/mathics/core/atoms/strings.py index a58a0a0fe..29659ca8a 100644 --- a/mathics/core/atoms/strings.py +++ b/mathics/core/atoms/strings.py @@ -9,7 +9,7 @@ from mathics.core.element import BoxElementMixin from mathics.core.keycomparable import BASIC_ATOM_STRING_ELT_ORDER -from mathics.core.symbols import Atom, Symbol, SymbolTrue, symbol_set +from mathics.core.symbols import Atom, Symbol, SymbolFalse, SymbolTrue, symbol_set from mathics.core.systemsymbols import SymbolFullForm, SymbolInputForm SymbolString = Symbol("String") @@ -42,7 +42,14 @@ def atom_to_boxes(self, f, evaluation): inner = str(self.value) if f in SYSTEM_SYMBOLS_INPUT_OR_FULL_FORM: inner = '"' + inner.replace("\\", "\\\\") + '"' - return _boxed_string(inner, **{"System`ShowStringCharacters": SymbolTrue}) + return _boxed_string( + inner, + **{ + "System`NumberMarks": SymbolTrue, + "System`ShowSpecialCharacters": SymbolFalse, + "System`ShowStringCharacters": SymbolTrue, + }, + ) return String('"' + inner + '"') def do_copy(self) -> "String": diff --git a/mathics/format/box/outputforms.py b/mathics/format/box/outputforms.py index 6617d86ab..c8e94fda1 100644 --- a/mathics/format/box/outputforms.py +++ b/mathics/format/box/outputforms.py @@ -12,7 +12,11 @@ SymbolList, SymbolTrue, ) -from mathics.core.systemsymbols import SymbolTeXForm, SymbolTraditionalForm +from mathics.core.systemsymbols import ( + SymbolMathMLForm, + SymbolTeXForm, + SymbolTraditionalForm, +) from mathics.eval.testing_expressions import expr_min from mathics.format.box.makeboxes import format_element, is_print_form_callback @@ -45,7 +49,11 @@ def eval_mathmlform(expr: BaseElement, evaluation: Evaluation) -> BoxElementMixi mathml = '%s' % mathml mathml = '%s' % mathml # convert_box(boxes) - return RowBox(String(mathml)) + return InterpretationBox( + String(f'"{mathml}"'), + Expression(SymbolMathMLForm, expr), + **{"System`AutoDelete": SymbolTrue, "System`Editable": SymbolTrue}, + ) def eval_tableform( @@ -134,7 +142,7 @@ def eval_texform(expr: BaseElement, evaluation: Evaluation) -> BoxElementMixin: ) tex = "" return InterpretationBox( - String(tex), + String(f'"{tex}"'), Expression(SymbolTeXForm, expr), **{"System`AutoDelete": SymbolTrue, "System`Editable": SymbolTrue}, ) 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, ) diff --git a/test/helper.py b/test/helper.py index f9df14b31..0baa2205c 100644 --- a/test/helper.py +++ b/test/helper.py @@ -126,10 +126,10 @@ def check_evaluation( print(time.asctime()) if failure_message: - print(f"got: {result}, expect: {expected} -- {failure_message}") + print(f"got: \n{result}\nexpect:\n{expected}\n -- {failure_message}") assert result == expected, failure_message else: - print(f"got: {result}, expect: {expected}") + print(f"got: \n{result}\nexpect:\n{expected}\n --") if isinstance(expected, re.Pattern): assert expected.match(result) else: From 51bb3c9a9cfd5b601622d48f01e438e625b7bd97 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Wed, 21 Jan 2026 20:29:48 -0300 Subject: [PATCH 23/31] missing import. Input and FullForm escape quotes in strings --- mathics/core/atoms/strings.py | 4 +++- mathics/format/box/outputforms.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/mathics/core/atoms/strings.py b/mathics/core/atoms/strings.py index 29659ca8a..3243bf475 100644 --- a/mathics/core/atoms/strings.py +++ b/mathics/core/atoms/strings.py @@ -41,7 +41,9 @@ def atom_to_boxes(self, f, evaluation): inner = str(self.value) if f in SYSTEM_SYMBOLS_INPUT_OR_FULL_FORM: - inner = '"' + inner.replace("\\", "\\\\") + '"' + inner = inner.replace("\\", "\\\\") + inner = inner.replace('"', '\\"') + inner = f'"{inner}"' return _boxed_string( inner, **{ diff --git a/mathics/format/box/outputforms.py b/mathics/format/box/outputforms.py index c8e94fda1..60d187f4d 100644 --- a/mathics/format/box/outputforms.py +++ b/mathics/format/box/outputforms.py @@ -26,7 +26,7 @@ @is_print_form_callback("System`MathMLForm") def eval_mathmlform(expr: BaseElement, evaluation: Evaluation) -> BoxElementMixin: "MakeBoxes[MathMLForm[expr_], form_]" - from mathics.builtin.box.layout import RowBox + from mathics.builtin.box.layout import InterpretationBox boxes = format_element(expr, evaluation, SymbolTraditionalForm) try: From 02e7c12d595c843a4f6d5c1e48fb53645e0980db Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Fri, 23 Jan 2026 07:25:28 -0300 Subject: [PATCH 24/31] Update layout.py --- mathics/builtin/box/layout.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mathics/builtin/box/layout.py b/mathics/builtin/box/layout.py index e0267aa08..b38ab63ab 100644 --- a/mathics/builtin/box/layout.py +++ b/mathics/builtin/box/layout.py @@ -209,7 +209,7 @@ def elements(self): return self._elements def init(self, *elems, **kwargs): - self.options = kwargs + self.box_options = kwargs self.items = elems self._elements = elems @@ -217,7 +217,7 @@ def get_array(self, elements, evaluation): if not elements: raise BoxConstructError - options = self.options + options = self.box_options expr = elements[0] if not expr.has_form("List", None): From eac3119649c4f5b0efa59a17677796f64e676d58 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Sat, 24 Jan 2026 15:17:43 -0300 Subject: [PATCH 25/31] Implement Format MakeBoxes and Format//OutputFOrm --- SYMBOLS_MANIFEST.txt | 2 ++ mathics/builtin/atomic/symbols.py | 7 ++++- mathics/builtin/layout.py | 44 +++++++++++++++++++++++++++++++ mathics/builtin/options.py | 14 ++++++++++ mathics/format/form/outputform.py | 15 +++++++++++ 5 files changed, 81 insertions(+), 1 deletion(-) diff --git a/SYMBOLS_MANIFEST.txt b/SYMBOLS_MANIFEST.txt index 35e37ac65..f4e4c96dc 100644 --- a/SYMBOLS_MANIFEST.txt +++ b/SYMBOLS_MANIFEST.txt @@ -12,6 +12,7 @@ ImportExport`RegisterExport ImportExport`RegisterImport Internal`RealValuedNumberQ Internal`RealValuedNumericQ +JSON`Import`JSONImport System`$Aborted System`$Assumptions System`$BaseDirectory @@ -486,6 +487,7 @@ System`FoldList System`FontColor System`For System`Format +System`FormatType System`FormatValues System`FractionBox System`FractionalPart diff --git a/mathics/builtin/atomic/symbols.py b/mathics/builtin/atomic/symbols.py index a9253138b..5f1770f34 100644 --- a/mathics/builtin/atomic/symbols.py +++ b/mathics/builtin/atomic/symbols.py @@ -412,7 +412,12 @@ class FormatValues(Builtin): >> Format[F[x_], OutputForm]:= Subscript[x, F] >> FormatValues[F] - = {HoldPattern[Format[Subscript[x_, F], OutputForm]] :> Subscript[x, F]} + = {HoldPattern[Subscript[x_, F]] :> Subscript[x, F]} + + Notice that the pattern was formatted using the rule. To reveal \ + the rules, use 'InputForm': + >> FormatValues[F] //InputForm + = {HoldPattern[Format[F[x_], OutputForm]] :> Subscript[x, F]} """ summary_text = ( diff --git a/mathics/builtin/layout.py b/mathics/builtin/layout.py index a3785cce3..403db689c 100644 --- a/mathics/builtin/layout.py +++ b/mathics/builtin/layout.py @@ -62,12 +62,56 @@ class Format(Builtin): Formats must be attached to the head of an expression: >> f /: Format[g[f]] = "my f"; : Tag f not found or too deep for an assigned rule. + + Format can be used to specify the request format: + >> Format[Integrate[F[x], x], TeXForm] + = \\int F\\left(x\\right) \\, dx + + Format evaluates its first element before applying the format: + >> Format[Integrate[Cos[x], x], TeXForm] + = ... + but the result keeps the structure: + >> % //FullForm + = Format[Sin[x], TeXForm] + + If the second parameter is ommited, 'Format' is ignored: + >> Format[F[x]] + = F[x] + + If the second argument is not one of '$PrintForms', a message \ + is shown, and the argument is discarded: + >> Format[F[x], NoFormat] + : Value of option FormatType -> NoFormat is not valid. + = F[x] + + Notice that differently from WMA, 'Format' expressions are not \ + formatted in 'InputForm': + >> Format[{a->Integrate[F[x], x]}, StandardForm] + = ... + >> Format[{a->Integrate[F[x], x]}, StandardForm] //InputForm + = Format[{a -> Integrate[F[x], x]}, StandardForm] + + This choice is more consistent with the meaning of 'InputForm' \ + in the sense it gives the text required to reproduce the expression. + Also, it allows to get a more clear expression that what would be \ + get using 'FullForm': + >> Format[{a->Integrate[F[x], x]}, StandardForm] //FullForm + = Format[{Rule[a, Integrate[F[x], x]]}, StandardForm] + """ messages = {"fttp": "Format type `1` is not a symbol."} summary_text = ( "settable low-level translator from various forms to evaluatable expressions" ) + rules = {"MakeBoxes[Format[expr_], fmt_]": "MakeBoxes[expr, fmt]"} + + def eval_Makeboxes(self, expr, form, evaluation): + """MakeBoxes[Format[expr_, form_], _]""" + if form not in evaluation.definitions.printforms: + evaluation.message("FormatType", "ftype", form) + return format_element(expr, evaluation) + return format_element(expr, evaluation, form) class Grid(Builtin): diff --git a/mathics/builtin/options.py b/mathics/builtin/options.py index 4e650bfbd..34fc0eeb8 100644 --- a/mathics/builtin/options.py +++ b/mathics/builtin/options.py @@ -171,6 +171,20 @@ def matched(): return ListExpression(*list(matched())) +class FormatType(Predefined): + """ + :WMA link:https://reference.wolfram.com/language/ref/FormatType.html +
+
'FormatType' +
is an option for output streams, graphics and functions like 'Text' \ + that specifies the default format. +
+ """ + + messages = {"ftype": "Value of option FormatType -> `` is not valid."} + summary_text = "specify the request format" + + class None_(Predefined): """ :WMA link:https://reference.wolfram.com/language/ref/None.html diff --git a/mathics/format/form/outputform.py b/mathics/format/form/outputform.py index 4d4afce4f..002b04fbf 100644 --- a/mathics/format/form/outputform.py +++ b/mathics/format/form/outputform.py @@ -264,6 +264,21 @@ def render_output_form(expr: BaseElement, evaluation: Evaluation, **kwargs): return _default_render_output_form(format_expr, evaluation, **kwargs) +@register_outputform("System`Format") +def format_format(expr, evaluation, **kwargs): + """Format[expr_, form___]""" + elements = expr.elements + if len(elements) == 1: + return render_output_form(elements[0], evaluation, **kwargs) + if len(elements) == 2: + expr, form = elements + if form not in evaluation.definitions.printforms: + evaluation.message("FormatType", "ftype", form) + return render_output_form(expr, evaluation, **kwargs) + return other_forms(Expression(form, expr), evaluation, **kwargs) + raise _WrongFormattedExpression + + @register_outputform("System`Graphics") def graphics(expr: Expression, evaluation: Evaluation, **kwargs) -> str: if not isinstance(expr.head, Symbol): From ba922a1a789952662fbebc6e5e0a0034f94be623 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Sat, 24 Jan 2026 16:07:29 -0300 Subject: [PATCH 26/31] Makeboxes overhault (#1642) This PR does the largest step so far in making the formatting process in Mathics compatible with the one in WMA. The main change is in the sequence of formatting. Now, `MakeBoxes` rules are not `Downvalues`, of the `MakeBoxes` symbol, but are stored as `FormatValues` of the corresponding symbols. Rules in `MakeBoxes` are now restricted to call the `format_element` function, and return a Box expression that represents its input. Hence, in loading definitions, `MakeBoxes` is not a special symbol anymore. Also, the default implementation for formatting basic elements like symbols, expressions, and lists does not pass through the evaluation process until explicit rules are set by the user. --- mathics/builtin/box/layout.py | 6 +- mathics/builtin/forms/data.py | 2 +- mathics/builtin/forms/print.py | 93 ++++----- mathics/builtin/layout.py | 57 ++++- mathics/builtin/list/constructing.py | 10 +- mathics/builtin/makeboxes.py | 55 +---- mathics/builtin/patterns/defaults.py | 25 ++- mathics/core/atoms/strings.py | 15 +- mathics/core/builtin.py | 9 +- mathics/core/load_builtin.py | 5 +- mathics/doc/documentation/1-Manual.mdoc | 3 +- mathics/eval/assignments/assignment.py | 29 ++- mathics/eval/lists.py | 3 +- mathics/format/box/__init__.py | 4 - mathics/format/box/makeboxes.py | 195 ++++++++++++------ mathics/format/box/outputforms.py | 47 ++++- test/builtin/box/test_custom_boxexpression.py | 48 ++++- test/format/format_tests.yaml | 47 +++++ test/format/makeboxes_tests.yaml | 24 +-- test/format/test_makeboxes.py | 8 +- test/helper.py | 4 +- 21 files changed, 430 insertions(+), 259 deletions(-) diff --git a/mathics/builtin/box/layout.py b/mathics/builtin/box/layout.py index e532a5999..b38ab63ab 100644 --- a/mathics/builtin/box/layout.py +++ b/mathics/builtin/box/layout.py @@ -209,7 +209,7 @@ def elements(self): return self._elements def init(self, *elems, **kwargs): - self.options = kwargs + self.box_options = kwargs self.items = elems self._elements = elems @@ -217,7 +217,7 @@ def get_array(self, elements, evaluation): if not elements: raise BoxConstructError - options = self.options + options = self.box_options expr = elements[0] if not expr.has_form("List", None): @@ -514,8 +514,6 @@ class StyleBox(BoxExpression): """ options = { - "ShowStringCharacters": "False", - "ShowSpecialCharacters": "False", "$OptionSyntax": "Ignore", } attributes = A_PROTECTED | A_READ_PROTECTED diff --git a/mathics/builtin/forms/data.py b/mathics/builtin/forms/data.py index 6ce2f7d3f..6d042f27d 100644 --- a/mathics/builtin/forms/data.py +++ b/mathics/builtin/forms/data.py @@ -791,7 +791,7 @@ class MatrixForm(TableForm): in_printforms = False summary_text = "format as a matrix" - def eval_makeboxes_matrix(self, table, form, evaluation, options): + def eval_makeboxes(self, table, form, evaluation, options): """MakeBoxes[MatrixForm[table_, OptionsPattern[]], (form:StandardForm|TraditionalForm)]""" result = super().eval_makeboxes(table, form, evaluation, options) 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/layout.py b/mathics/builtin/layout.py index a3785cce3..eb9884e32 100644 --- a/mathics/builtin/layout.py +++ b/mathics/builtin/layout.py @@ -11,13 +11,19 @@ from mathics.builtin.box.layout import GridBox, PaneBox, RowBox, to_boxes from mathics.builtin.makeboxes import MakeBoxes -from mathics.core.atoms import Real, String +from mathics.core.atoms import Integer, Real, String from mathics.core.builtin import Builtin, Operator, PostfixOperator, PrefixOperator from mathics.core.expression import Evaluation, Expression from mathics.core.list import ListExpression -from mathics.core.systemsymbols import SymbolMakeBoxes, SymbolSubscriptBox +from mathics.core.symbols import Symbol +from mathics.core.systemsymbols import ( + SymbolMakeBoxes, + SymbolPostfix, + SymbolPrefix, + SymbolSubscriptBox, +) from mathics.eval.lists import list_boxes -from mathics.format.box import format_element +from mathics.format.box import eval_infix, eval_postprefix, format_element, parenthesize class Center(Builtin): @@ -172,8 +178,20 @@ class Infix(Builtin): = a + b - c """ + rules = { + ( + "MakeBoxes[Infix[head_[elements___]], " + " f:StandardForm|TraditionalForm]" + ): ('MakeBoxes[Infix[head[elements], StringForm["~`1`~", head]], f]'), + } summary_text = "infix form" + def eval_makeboxes_infix( + self, expr, operator, precedence: Integer, grouping, form: Symbol, evaluation + ): + """MakeBoxes[Infix[expr_, operator_, precedence_:None, grouping_:None], form:StandardForm|TraditionalForm]""" + return eval_infix(self, expr, operator, precedence, grouping, form, evaluation) + class Left(Builtin): """ @@ -278,6 +296,13 @@ class Postfix(PostfixOperator): operator_display = None summary_text = "postfix form" + def eval_makeboxes_postfix(self, expr, h, precedence, form, evaluation): + """MakeBoxes[Postfix[expr_, h_, precedence_:None], + form:StandardForm|TraditionalForm]""" + return eval_postprefix( + self, SymbolPostfix, expr, h, precedence, form, evaluation + ) + class Precedence(Builtin): """ @@ -332,8 +357,20 @@ class PrecedenceForm(Builtin):
'PrecedenceForm'[$expr$, $prec$]
format $expr$ parenthesized as it would be if it contained an operator of precedence $prec$. + + >> PrecedenceForm[x/y, 12] - z + = -z + (x / y) + """ + def eval_outerprecedenceform(self, expr, precedence, form, evaluation): + """MakeBoxes[PrecedenceForm[expr_, precedence_], + form:StandardForm|TraditionalForm]""" + + py_precedence = precedence.get_int_value() + boxes = format_element(expr, evaluation, form) + return parenthesize(py_precedence, expr, boxes, True) + summary_text = "parenthesize with a precedence" @@ -370,6 +407,13 @@ class Prefix(PrefixOperator): operator_display = None summary_text = "prefix form" + def eval_makeboxes_prefix(self, expr, h, precedence, form, evaluation): + """MakeBoxes[Prefix[expr_, h_, precedence_:None], + form:StandardForm|TraditionalForm]""" + return eval_postprefix( + self, SymbolPrefix, expr, h, precedence, form, evaluation + ) + class Right(Builtin): """ @@ -456,7 +500,12 @@ class Style(Builtin): summary_text = "wrapper for styles and style options to apply" options = {"ImageSizeMultipliers": "Automatic"} - + rules = { + "MakeBoxes[Style[expr_, OptionsPattern[Style]], f_]": ( + "StyleBox[MakeBoxes[expr, f], " + "ImageSizeMultipliers -> OptionValue[ImageSizeMultipliers]]" + ), + } rules = { "MakeBoxes[Style[expr_, OptionsPattern[Style]], f_]": ( "StyleBox[MakeBoxes[expr, f], " 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 8fea8b539..2dea6be10 100644 --- a/mathics/builtin/makeboxes.py +++ b/mathics/builtin/makeboxes.py @@ -3,19 +3,9 @@ Low-level Format definitions """ - -from mathics.core.atoms import Integer from mathics.core.attributes import A_HOLD_ALL_COMPLETE, A_READ_PROTECTED from mathics.core.builtin import Builtin, Predefined -from mathics.core.symbols import Symbol -from mathics.format.box import ( - eval_generic_makeboxes, - eval_infix, - eval_makeboxes_fullform, - eval_postprefix, - format_element, - parenthesize, -) +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 @@ -91,53 +81,15 @@ class MakeBoxes(Builtin): attributes = A_HOLD_ALL_COMPLETE rules = { - "MakeBoxes[Infix[head_[elements___]], " - " f:StandardForm|TraditionalForm]": ( - 'MakeBoxes[Infix[head[elements], StringForm["~`1`~", head]], f]' - ), "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]"), - # BoxForms goes as second argument - "MakeBoxes[PrecedenceForm[expr_, prec_], f_]": "MakeBoxes[expr, f]", - "MakeBoxes[Style[expr_, OptionsPattern[Style]], f_]": ( - "StyleBox[MakeBoxes[expr, f], " - "ImageSizeMultipliers -> OptionValue[ImageSizeMultipliers]]" - ), } summary_text = "settable low-level translator from expression to display boxes" - def eval_fullform(self, expr, evaluation): - """MakeBoxes[expr_, FullForm]""" - return eval_makeboxes_fullform(expr, evaluation) - def eval_general(self, expr, f, evaluation): - """MakeBoxes[expr_, - f:TraditionalForm|StandardForm]""" - return eval_generic_makeboxes(expr, f, evaluation) - - def eval_outerprecedenceform(self, expr, precedence, form, evaluation): - """MakeBoxes[PrecedenceForm[expr_, precedence_], - form:StandardForm|TraditionalForm]""" - - py_precedence = precedence.get_int_value() - boxes = MakeBoxes(expr, form) - return parenthesize(py_precedence, expr, boxes, True) - - def eval_postprefix(self, p, expr, h, precedence, form, evaluation): - """MakeBoxes[(p:Prefix|Postfix)[expr_, h_, precedence_:None], - form:StandardForm|TraditionalForm]""" - return eval_postprefix(self, p, expr, h, precedence, form, evaluation) - - def eval_infix( - self, expr, operator, precedence: Integer, grouping, form: Symbol, evaluation - ): - """MakeBoxes[Infix[expr_, operator_, precedence_:None, grouping_:None], form:StandardForm|TraditionalForm]""" - return eval_infix(self, expr, operator, precedence, grouping, form, evaluation) + """MakeBoxes[expr_, f:TraditionalForm|StandardForm]""" + return format_element(expr, evaluation, f) class ToBoxes(Builtin): @@ -169,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/builtin/patterns/defaults.py b/mathics/builtin/patterns/defaults.py index ca38a8ea8..828771360 100644 --- a/mathics/builtin/patterns/defaults.py +++ b/mathics/builtin/patterns/defaults.py @@ -72,8 +72,29 @@ class Optional(InfixOperator, PatternObject): } grouping = "Right" rules = { - "MakeBoxes[Verbatim[Optional][Verbatim[Pattern][symbol_Symbol, Verbatim[_]]], (f:StandardForm|TraditionalForm)]": 'MakeBoxes[symbol, f] <> "_."', - "MakeBoxes[Verbatim[Optional][Verbatim[_]], (f:StandardForm|TraditionalForm)]": '"_."', + ( + "MakeBoxes[Verbatim[Optional][" + "Verbatim[Pattern][symbol_Symbol," + "(kind:(Verbatim[Blank]|Verbatim[BlankSequence]|Verbatim[BlankNullSequence])[])]], " + "(f:StandardForm|TraditionalForm)]" + ): 'MakeBoxes[symbol, f] <> ToString[kind, f] <>"."', + ( + "MakeBoxes[Verbatim[Optional][" + "(kind:(Verbatim[Blank]|Verbatim[BlankSequence]|Verbatim[BlankNullSequence])[])], " + "(f:StandardForm|TraditionalForm)]" + ): 'ToString[kind, f]<>"."', + # Two arguments + ( + "MakeBoxes[Verbatim[Optional][" + "Verbatim[Pattern][symbol_Symbol," + "(kind:(Verbatim[Blank]|Verbatim[BlankSequence]|Verbatim[BlankNullSequence])[]), value_]], " + "(f:StandardForm|TraditionalForm)]" + ): 'RowBox[{MakeBoxes[symbol, f], ToString[kind, f], ":",MakeBoxes[value, f]}]', + ( + "MakeBoxes[Verbatim[Optional][" + "(kind:(Verbatim[Blank]|Verbatim[BlankSequence]|Verbatim[BlankNullSequence])[]), value_], " + "(f:StandardForm|TraditionalForm)]" + ): 'RowBox[{ToString[kind, f], ":", MakeBoxes[value, f]}]', } summary_text = "an optional argument with a default value" diff --git a/mathics/core/atoms/strings.py b/mathics/core/atoms/strings.py index a58a0a0fe..3243bf475 100644 --- a/mathics/core/atoms/strings.py +++ b/mathics/core/atoms/strings.py @@ -9,7 +9,7 @@ from mathics.core.element import BoxElementMixin from mathics.core.keycomparable import BASIC_ATOM_STRING_ELT_ORDER -from mathics.core.symbols import Atom, Symbol, SymbolTrue, symbol_set +from mathics.core.symbols import Atom, Symbol, SymbolFalse, SymbolTrue, symbol_set from mathics.core.systemsymbols import SymbolFullForm, SymbolInputForm SymbolString = Symbol("String") @@ -41,8 +41,17 @@ def atom_to_boxes(self, f, evaluation): inner = str(self.value) if f in SYSTEM_SYMBOLS_INPUT_OR_FULL_FORM: - inner = '"' + inner.replace("\\", "\\\\") + '"' - return _boxed_string(inner, **{"System`ShowStringCharacters": SymbolTrue}) + inner = inner.replace("\\", "\\\\") + inner = inner.replace('"', '\\"') + inner = f'"{inner}"' + return _boxed_string( + inner, + **{ + "System`NumberMarks": SymbolTrue, + "System`ShowSpecialCharacters": SymbolFalse, + "System`ShowStringCharacters": SymbolTrue, + }, + ) return String('"' + inner + '"') def do_copy(self) -> "String": 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/format_tests.yaml b/test/format/format_tests.yaml index b8eb032e6..a9cfed0ad 100644 --- a/test/format/format_tests.yaml +++ b/test/format/format_tests.yaml @@ -23,6 +23,7 @@ # because we use both in documentation and in the web interface. # + '"-7.32"': msg: A String with a number latex: @@ -813,6 +814,8 @@ TableForm[{{a,b},{c,d}}]: System`OutputForm: 'α' System`StandardForm: "α" System`TraditionalForm: "α" + + a: msg: A Symbol latex: @@ -867,3 +870,47 @@ a^4: System`OutputForm: a ^ 4 System`StandardForm: a^4 System`TraditionalForm: a^4 + + +Optional[x__]: + msg: Optional with one argument + latex: + System`OutputForm: '\text{x\_\_.}' + System`StandardForm: '\text{x\_\_.}' + mathml: + System`OutputForm: 'x__.' + System`StandardForm: 'x__.' + text: + System`InputForm: '(x__.)' + System`OutputForm: 'x__.' + System`StandardForm: 'x__.' + System`TraditionalForm: 'x__.' + + +Optional[x__, a+b]: + msg: Optional with two arguments + latex: + System`OutputForm: ' \text{x\_\_ : a + b}' + System`StandardForm: '\text{x\_\_}:a+b' + mathml: + System`OutputForm: 'x__ : a + b' + text: + System`InputForm: 'x__ : a + b' + System`OutputForm: 'x__ : a + b' + System`StandardForm: 'x__:a+b' + System`TraditionalForm: 'x__:a+b' + + +a+PrecedenceForm[b+c,10]: + msg: "PrecedenceForm" + latex: + System`OutputForm: '\text{a + (b + c)}' + System`StandardForm: 'a+\left(b+c\right)' + mathml: + System`OutputForm: 'a + (b + c)' + System`StandardForm: 'a + ( b + c )' + text: + System`InputForm: 'a + (PrecedenceForm[b + c, 10])' + System`OutputForm: 'a + (b + c)' + System`StandardForm: 'a+(b+c)' + System`TraditionalForm: 'a+(b+c)' 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, ) diff --git a/test/helper.py b/test/helper.py index f9df14b31..0baa2205c 100644 --- a/test/helper.py +++ b/test/helper.py @@ -126,10 +126,10 @@ def check_evaluation( print(time.asctime()) if failure_message: - print(f"got: {result}, expect: {expected} -- {failure_message}") + print(f"got: \n{result}\nexpect:\n{expected}\n -- {failure_message}") assert result == expected, failure_message else: - print(f"got: {result}, expect: {expected}") + print(f"got: \n{result}\nexpect:\n{expected}\n --") if isinstance(expected, re.Pattern): assert expected.match(result) else: From 5e9213f8dd85533a0fda60b0c31ed54421bf5d75 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Sat, 24 Jan 2026 22:37:01 -0300 Subject: [PATCH 27/31] format optional --- mathics/builtin/patterns/defaults.py | 25 +++++++++++++-- test/format/format_tests.yaml | 47 ++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/mathics/builtin/patterns/defaults.py b/mathics/builtin/patterns/defaults.py index ca38a8ea8..828771360 100644 --- a/mathics/builtin/patterns/defaults.py +++ b/mathics/builtin/patterns/defaults.py @@ -72,8 +72,29 @@ class Optional(InfixOperator, PatternObject): } grouping = "Right" rules = { - "MakeBoxes[Verbatim[Optional][Verbatim[Pattern][symbol_Symbol, Verbatim[_]]], (f:StandardForm|TraditionalForm)]": 'MakeBoxes[symbol, f] <> "_."', - "MakeBoxes[Verbatim[Optional][Verbatim[_]], (f:StandardForm|TraditionalForm)]": '"_."', + ( + "MakeBoxes[Verbatim[Optional][" + "Verbatim[Pattern][symbol_Symbol," + "(kind:(Verbatim[Blank]|Verbatim[BlankSequence]|Verbatim[BlankNullSequence])[])]], " + "(f:StandardForm|TraditionalForm)]" + ): 'MakeBoxes[symbol, f] <> ToString[kind, f] <>"."', + ( + "MakeBoxes[Verbatim[Optional][" + "(kind:(Verbatim[Blank]|Verbatim[BlankSequence]|Verbatim[BlankNullSequence])[])], " + "(f:StandardForm|TraditionalForm)]" + ): 'ToString[kind, f]<>"."', + # Two arguments + ( + "MakeBoxes[Verbatim[Optional][" + "Verbatim[Pattern][symbol_Symbol," + "(kind:(Verbatim[Blank]|Verbatim[BlankSequence]|Verbatim[BlankNullSequence])[]), value_]], " + "(f:StandardForm|TraditionalForm)]" + ): 'RowBox[{MakeBoxes[symbol, f], ToString[kind, f], ":",MakeBoxes[value, f]}]', + ( + "MakeBoxes[Verbatim[Optional][" + "(kind:(Verbatim[Blank]|Verbatim[BlankSequence]|Verbatim[BlankNullSequence])[]), value_], " + "(f:StandardForm|TraditionalForm)]" + ): 'RowBox[{ToString[kind, f], ":", MakeBoxes[value, f]}]', } summary_text = "an optional argument with a default value" diff --git a/test/format/format_tests.yaml b/test/format/format_tests.yaml index a94ae3af1..b25b9c1f3 100644 --- a/test/format/format_tests.yaml +++ b/test/format/format_tests.yaml @@ -23,6 +23,7 @@ # because we use both in documentation and in the web interface. # + '"-7.32"': msg: A String with a number latex: @@ -813,6 +814,8 @@ TableForm[{{a,b},{c,d}}]: System`OutputForm: 'α' System`StandardForm: "α" System`TraditionalForm: "α" + + a: msg: A Symbol latex: @@ -867,3 +870,47 @@ a^4: System`OutputForm: a ^ 4 System`StandardForm: a^4 System`TraditionalForm: a^4 + + +Optional[x__]: + msg: Optional with one argument + latex: + System`OutputForm: '\text{x\_\_.}' + System`StandardForm: '\text{x\_\_.}' + mathml: + System`OutputForm: 'x__.' + System`StandardForm: 'x__.' + text: + System`InputForm: '(x__.)' + System`OutputForm: 'x__.' + System`StandardForm: 'x__.' + System`TraditionalForm: 'x__.' + + +Optional[x__, a+b]: + msg: Optional with two arguments + latex: + System`OutputForm: ' \text{x\_\_ : a + b}' + System`StandardForm: '\text{x\_\_}:a+b' + mathml: + System`OutputForm: 'x__ : a + b' + text: + System`InputForm: 'x__ : a + b' + System`OutputForm: 'x__ : a + b' + System`StandardForm: 'x__:a+b' + System`TraditionalForm: 'x__:a+b' + + +a+PrecedenceForm[b+c,10]: + msg: "PrecedenceForm" + latex: + System`OutputForm: '\text{a + (b + c)}' + System`StandardForm: 'a+\left(b+c\right)' + mathml: + System`OutputForm: 'a + (b + c)' + System`StandardForm: 'a + ( b + c )' + text: + System`InputForm: 'a + (PrecedenceForm[b + c, 10])' + System`OutputForm: 'a + (b + c)' + System`StandardForm: 'a+(b+c)' + System`TraditionalForm: 'a+(b+c)' From d85b179498793d60ad793be37a6de034de0e4203 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Sat, 24 Jan 2026 22:59:19 -0300 Subject: [PATCH 28/31] new_formbox --- mathics/builtin/box/layout.py | 50 ++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/mathics/builtin/box/layout.py b/mathics/builtin/box/layout.py index 4c1c33c55..b38ab63ab 100644 --- a/mathics/builtin/box/layout.py +++ b/mathics/builtin/box/layout.py @@ -85,6 +85,50 @@ def is_constant_list(list): return True +class FormBox(BoxExpression): + """ + + :WMA link: + https://reference.wolfram.com/language/ref/FormBox.html + +
+
'FormBox[boxes, form]' +
is a low-level box construct that displays as \ + boxes and keep information about the form used to generate \ + the box representation. +
+ """ + + attributes = A_PROTECTED | A_READ_PROTECTED + summary_text = "box with an associated form" + + def init(self, *elems, **kwargs): + self.box_options = kwargs + self.form = elems[1] + self.boxed = elems[0] + assert isinstance(self.boxed, BoxElementMixin), f"{type(self.boxes)}" + + @property + def elements(self): + if self._elements is None: + self._elements = elements_to_expressions( + self, + ( + self.boxed, + self.form, + ), + self.box_options, + ) + return self._elements + + def eval_tagbox(self, expr, form: Symbol, evaluation: Evaluation): + """FormBox[expr_, form_Symbol]""" + options = {} + expr = to_boxes(expr, evaluation, options) + assert isinstance(expr, BoxElementMixin), f"{expr}" + return FormBox(expr, form, **options) + + class FractionBox(BoxExpression): """ @@ -165,7 +209,7 @@ def elements(self): return self._elements def init(self, *elems, **kwargs): - self.options = kwargs + self.box_options = kwargs self.items = elems self._elements = elems @@ -173,7 +217,7 @@ def get_array(self, elements, evaluation): if not elements: raise BoxConstructError - options = self.options + options = self.box_options expr = elements[0] if not expr.has_form("List", None): @@ -470,8 +514,6 @@ class StyleBox(BoxExpression): """ options = { - "ShowStringCharacters": "False", - "ShowSpecialCharacters": "False", "$OptionSyntax": "Ignore", } attributes = A_PROTECTED | A_READ_PROTECTED From 1004bc95abc71ec0ef5980a3bb4c0e67d500861e Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Sat, 24 Jan 2026 23:08:32 -0300 Subject: [PATCH 29/31] add FormBox --- mathics/format/render/latex.py | 6 ++++-- mathics/format/render/mathml.py | 6 ++++-- mathics/format/render/text.py | 6 ++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/mathics/format/render/latex.py b/mathics/format/render/latex.py index e432992ca..a24ee0447 100644 --- a/mathics/format/render/latex.py +++ b/mathics/format/render/latex.py @@ -17,6 +17,7 @@ from mathics.builtin.box.graphics import GraphicsBox from mathics.builtin.box.graphics3d import Graphics3DBox from mathics.builtin.box.layout import ( + FormBox, FractionBox, GridBox, InterpretationBox, @@ -663,8 +664,9 @@ def graphics3dbox(self, elements=None, **options) -> str: add_conversion_fn(Graphics3DBox, graphics3dbox) -def tag_box(self, **options): +def tag_and_form_box(self, **options): return lookup_conversion_method(self.boxed, "latex")(self.boxed, **options) -add_conversion_fn(TagBox, tag_box) +add_conversion_fn(FormBox, tag_and_form_box) +add_conversion_fn(TagBox, tag_and_form_box) diff --git a/mathics/format/render/mathml.py b/mathics/format/render/mathml.py index d48a2eb6f..e229e0c18 100644 --- a/mathics/format/render/mathml.py +++ b/mathics/format/render/mathml.py @@ -13,6 +13,7 @@ from mathics.builtin.box.graphics import GraphicsBox from mathics.builtin.box.graphics3d import Graphics3DBox from mathics.builtin.box.layout import ( + FormBox, FractionBox, GridBox, InterpretationBox, @@ -371,8 +372,9 @@ def graphics3dbox(self, elements=None, **options) -> str: add_conversion_fn(Graphics3DBox, graphics3dbox) -def tag_box(self, **options): +def tag_and_form_box(self, **options): return lookup_conversion_method(self.boxed, "mathml")(self.boxed, **options) -add_conversion_fn(TagBox, tag_box) +add_conversion_fn(FormBox, tag_and_form_box) +add_conversion_fn(TagBox, tag_and_form_box) diff --git a/mathics/format/render/text.py b/mathics/format/render/text.py index 59e9236c2..49a71a510 100644 --- a/mathics/format/render/text.py +++ b/mathics/format/render/text.py @@ -7,6 +7,7 @@ from mathics.builtin.box.graphics import GraphicsBox from mathics.builtin.box.graphics3d import Graphics3DBox from mathics.builtin.box.layout import ( + FormBox, FractionBox, GridBox, InterpretationBox, @@ -235,8 +236,9 @@ def graphics3dbox(self, elements=None, **options) -> str: add_conversion_fn(Graphics3DBox, graphics3dbox) -def tag_box(self, **options): +def tag_and_form_box(self, **options): return boxes_to_text(self.boxed, **options) -add_conversion_fn(TagBox, tag_box) +add_conversion_fn(FormBox, tag_and_form_box) +add_conversion_fn(TagBox, tag_and_form_box) From e011bda7b2a7ce8851b187a84625ada9c67c41f0 Mon Sep 17 00:00:00 2001 From: Juan Mauricio Matera Date: Sun, 25 Jan 2026 14:01:34 -0300 Subject: [PATCH 30/31] boxed-> boxes --- mathics/builtin/box/layout.py | 24 ++++++++++++------------ mathics/format/render/latex.py | 6 +++--- mathics/format/render/mathml.py | 6 +++--- mathics/format/render/text.py | 6 +++--- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/mathics/builtin/box/layout.py b/mathics/builtin/box/layout.py index 5f1898777..77bf1d88d 100644 --- a/mathics/builtin/box/layout.py +++ b/mathics/builtin/box/layout.py @@ -124,8 +124,8 @@ class FormBox(BoxExpression): def init(self, *elems, **kwargs): self.box_options = kwargs self.form = elems[1] - self.boxed = elems[0] - assert isinstance(self.boxed, BoxElementMixin), f"{type(self.boxes)}" + self.boxes = elems[0] + assert isinstance(self.boxes, BoxElementMixin), f"{type(self.boxes)}" @property def elements(self): @@ -133,7 +133,7 @@ def elements(self): self._elements = elements_to_expressions( self, ( - self.boxed, + self.boxes, self.form, ), self.box_options, @@ -288,12 +288,12 @@ class InterpretationBox(BoxExpression): summary_text = "box associated to an input expression" def __repr__(self): - result = "InterpretationBox\n " + repr(self.boxed) + result = "InterpretationBox\n " + repr(self.boxes) result += f"\n {self.box_options}" return result def init(self, *expr, **options): - self.boxed = expr[0] + self.boxes = expr[0] self.expr = expr[1] self.box_options = options @@ -303,7 +303,7 @@ def elements(self): self._elements = elements_to_expressions( self, ( - self.boxed, + self.boxes, self.expr, ), self.box_options, @@ -333,7 +333,7 @@ def eval_to_expression2(self, boxexpr, form, evaluation): def eval_display(self, boxexpr, evaluation): """DisplayForm[boxexpr_InterpretationBox]""" - return boxexpr.boxed + return boxexpr.boxes class PaneBox(BoxExpression): @@ -357,12 +357,12 @@ class PaneBox(BoxExpression): def elements(self): if self._elements is None: self._elements = elements_to_expressions( - self, (self.boxed,), self.box_options + self, (self.boxes,), self.box_options ) return self._elements def init(self, expr, **options): - self.boxed = expr + self.boxes = expr self.box_options = options def eval_panebox1(self, expr, evaluation, options): @@ -745,8 +745,8 @@ class TagBox(BoxExpression): def init(self, *elems, **kwargs): self.box_options = kwargs self.form = elems[1] - self.boxed = elems[0] - assert isinstance(self.boxed, BoxElementMixin), f"{type(self.boxes)}" + self.boxes = elems[0] + assert isinstance(self.boxes, BoxElementMixin), f"{type(self.boxes)}" @property def elements(self): @@ -754,7 +754,7 @@ def elements(self): self._elements = elements_to_expressions( self, ( - self.boxed, + self.boxes, self.form, ), self.box_options, diff --git a/mathics/format/render/latex.py b/mathics/format/render/latex.py index a24ee0447..93deb289e 100644 --- a/mathics/format/render/latex.py +++ b/mathics/format/render/latex.py @@ -159,14 +159,14 @@ def render(format, string, in_text=False): def interpretation_box(self, **options): - return lookup_conversion_method(self.boxed, "latex")(self.boxed, **options) + return lookup_conversion_method(self.boxes, "latex")(self.boxes, **options) add_conversion_fn(InterpretationBox, interpretation_box) def pane_box(self, **options): - content = lookup_conversion_method(self.boxed, "latex")(self.boxed, **options) + content = lookup_conversion_method(self.boxes, "latex")(self.boxes, **options) options = self.box_options size = options.get("System`ImageSize", SymbolAutomatic).to_python() @@ -665,7 +665,7 @@ def graphics3dbox(self, elements=None, **options) -> str: def tag_and_form_box(self, **options): - return lookup_conversion_method(self.boxed, "latex")(self.boxed, **options) + return lookup_conversion_method(self.boxes, "latex")(self.boxes, **options) add_conversion_fn(FormBox, tag_and_form_box) diff --git a/mathics/format/render/mathml.py b/mathics/format/render/mathml.py index e229e0c18..5e99980f9 100644 --- a/mathics/format/render/mathml.py +++ b/mathics/format/render/mathml.py @@ -124,14 +124,14 @@ def render(format, string): def interpretation_box(self, **options): - return lookup_conversion_method(self.boxed, "mathml")(self.boxed, **options) + return lookup_conversion_method(self.boxes, "mathml")(self.boxes, **options) add_conversion_fn(InterpretationBox, interpretation_box) def pane_box(self, **options): - content = lookup_conversion_method(self.boxed, "mathml")(self.boxed, **options) + content = lookup_conversion_method(self.boxes, "mathml")(self.boxes, **options) options = self.box_options size = options.get("System`ImageSize", SymbolAutomatic).to_python() if size is SymbolAutomatic: @@ -373,7 +373,7 @@ def graphics3dbox(self, elements=None, **options) -> str: def tag_and_form_box(self, **options): - return lookup_conversion_method(self.boxed, "mathml")(self.boxed, **options) + return lookup_conversion_method(self.boxes, "mathml")(self.boxes, **options) add_conversion_fn(FormBox, tag_and_form_box) diff --git a/mathics/format/render/text.py b/mathics/format/render/text.py index 49a71a510..fb46ee70a 100644 --- a/mathics/format/render/text.py +++ b/mathics/format/render/text.py @@ -46,14 +46,14 @@ def string(self, **options) -> str: def interpretation_box(self, **options): - return boxes_to_text(self.boxed, **options) + return boxes_to_text(self.boxes, **options) add_conversion_fn(InterpretationBox, interpretation_box) def pane_box(self, **options): - result = boxes_to_text(self.boxed, **options) + result = boxes_to_text(self.boxes, **options) return result @@ -237,7 +237,7 @@ def graphics3dbox(self, elements=None, **options) -> str: def tag_and_form_box(self, **options): - return boxes_to_text(self.boxed, **options) + return boxes_to_text(self.boxes, **options) add_conversion_fn(FormBox, tag_and_form_box) From f3140de1624c101e2abe4feb13b14f8a5156786d Mon Sep 17 00:00:00 2001 From: "R. Bernstein" Date: Sun, 25 Jan 2026 15:05:12 -0500 Subject: [PATCH 31/31] Format format update docs (#1657) Revise doc descriptions for `FormatValues` and `Format`. --- mathics/builtin/atomic/symbols.py | 10 +++++++--- mathics/builtin/layout.py | 30 +++++++++++++++++------------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/mathics/builtin/atomic/symbols.py b/mathics/builtin/atomic/symbols.py index 5f1770f34..5af79bb99 100644 --- a/mathics/builtin/atomic/symbols.py +++ b/mathics/builtin/atomic/symbols.py @@ -407,15 +407,19 @@ class FormatValues(Builtin): :WMA link:https://reference.wolfram.com/language/tutorial/PatternsAndTransformationRules.html#6025
'FormatValues'[$symbol$] -
gives the list of formatvalues associated with $symbol$. +
gives the list of format rules associated with $symbol$.
+ First, use 'Format' to set a formatting rule for a form: + >> Format[F[x_], OutputForm]:= Subscript[x, F] + + Now, to see the rules, we can use 'FormatValues': + >> FormatValues[F] = {HoldPattern[Subscript[x_, F]] :> Subscript[x, F]} - Notice that the pattern was formatted using the rule. To reveal \ - the rules, use 'InputForm': + The replacment pattern on the right in the delayed rule is formatted according to the top-level form. To see the rule input, we can use 'InputForm': >> FormatValues[F] //InputForm = {HoldPattern[Format[F[x_], OutputForm]] :> Subscript[x, F]} """ diff --git a/mathics/builtin/layout.py b/mathics/builtin/layout.py index c9dfb40a9..8ce757748 100644 --- a/mathics/builtin/layout.py +++ b/mathics/builtin/layout.py @@ -46,12 +46,15 @@ class Format(Builtin):
'Format'[$expr$] -
holds values specifying how $expr$ should be printed. +
used on the left-hand side of an assignment to specify how $expr$ should be printed.
- Assign values to 'Format' to control how particular expressions - should be formatted when printed to the user. + First, we set up a 'Format' definition for 'f' to display its arguments as if it were equivalent to an infix operator "~": + >> Format[f[x___]] := Infix[{x}, "~"] + + Now, to see this format in use: + >> f[1, 2, 3] = 1 ~ 2 ~ 3 >> f[1] @@ -80,7 +83,7 @@ class Format(Builtin): >> % //FullForm = Format[Sin[x], TeXForm] - If the second parameter is ommited, 'Format' is ignored: + If the second parameter is omitted, 'Format' is ignored: >> Format[F[x]] = F[x] @@ -90,20 +93,21 @@ class Format(Builtin): : Value of option FormatType -> NoFormat is not valid. = F[x] - Notice that differently from WMA, 'Format' expressions are not \ - formatted in 'InputForm': - >> Format[{a->Integrate[F[x], x]}, StandardForm] - = ... + Mathics3 'Format' output can differ slightly from WMA in what we hope \ + is a more useful way. + + Use 'InputForm' if you want to get a 'Format' definition that can be used as \ + Mathics3 input: + >> Format[{a->Integrate[F[x], x]}, StandardForm] //InputForm = Format[{a -> Integrate[F[x], x]}, StandardForm] - This choice is more consistent with the meaning of 'InputForm' \ - in the sense it gives the text required to reproduce the expression. - Also, it allows to get a more clear expression that what would be \ - get using 'FullForm': + In WMA, you might not get something that can be used as input. + + Similarly, use 'Fullform' to get a valid FullForm equivalent expression: + >> Format[{a->Integrate[F[x], x]}, StandardForm] //FullForm = Format[{Rule[a, Integrate[F[x], x]]}, StandardForm] - """ messages = {"fttp": "Format type `1` is not a symbol."}