diff --git a/mathics/builtin/box/layout.py b/mathics/builtin/box/layout.py index 94fb41c85..e26d45447 100644 --- a/mathics/builtin/box/layout.py +++ b/mathics/builtin/box/layout.py @@ -188,6 +188,33 @@ def eval_display(boxexpr, evaluation): return boxexpr.elements[0] +class PaneBox(BoxExpression): + """ + + :WMA link: + https://reference.wolfram.com/language/ref/InterpretationBox.html + +
+
'PaneBox[expr]' +
is a low-level box construct, used in OutputForm. +
+ + """ + + attributes = A_HOLD_ALL_COMPLETE | A_PROTECTED | A_READ_PROTECTED + summary_text = "box associated to panel" + + def apply_display_form(boxexpr, form, evaluation, expression): + """ToExpression[boxexpr_PaneBox, form_]""" + return Expression(expression.head, boxexpr.elements[0], form).evaluate( + evaluation + ) + + def apply_display(boxexpr, evaluation): + """DisplayForm[boxexpr_PaneBox]""" + return boxexpr.elements[0] + + class RowBox(BoxExpression): """ diff --git a/mathics/builtin/forms/output.py b/mathics/builtin/forms/output.py index 7ec5d1d48..a6ea6ea37 100644 --- a/mathics/builtin/forms/output.py +++ b/mathics/builtin/forms/output.py @@ -37,6 +37,7 @@ StringLParen, StringRParen, eval_baseform, + eval_makeboxes_outputform, eval_mathmlform, eval_tableform, eval_texform, @@ -490,8 +491,13 @@ class OutputForm(FormBaseClass): = -Graphics- """ + formats = {"OutputForm[s_String]": "s"} summary_text = "plain-text output format" + def eval_makeboxes(self, expr, form, evaluation): + """MakeBoxes[OutputForm[expr_], form_]""" + return eval_makeboxes_outputform(expr, evaluation, form) + class PythonForm(FormBaseClass): """ diff --git a/mathics/core/atoms.py b/mathics/core/atoms.py index d6e542a60..e2b37e4c9 100644 --- a/mathics/core/atoms.py +++ b/mathics/core/atoms.py @@ -1016,10 +1016,13 @@ def __str__(self) -> str: return '"%s"' % self.value def atom_to_boxes(self, f, evaluation): + return self.make_boxes(f.get_name()) + + def make_boxes(self, f): from mathics.eval.makeboxes import _boxed_string inner = str(self.value) - if f in SYSTEM_SYMBOLS_INPUT_OR_FULL_FORM: + if f in ("System`InputForm", "System`FullForm"): inner = '"' + inner.replace("\\", "\\\\") + '"' return _boxed_string(inner, **{"System`ShowStringCharacters": SymbolTrue}) return String('"' + inner + '"') diff --git a/mathics/eval/makeboxes/__init__.py b/mathics/eval/makeboxes/__init__.py index a6cb2a6e7..ec20abf22 100644 --- a/mathics/eval/makeboxes/__init__.py +++ b/mathics/eval/makeboxes/__init__.py @@ -7,6 +7,7 @@ _boxed_string, eval_generic_makeboxes, eval_makeboxes, + eval_makeboxes_outputform, format_element, int_to_string_shorter_repr, to_boxes, @@ -18,7 +19,11 @@ eval_tableform, eval_texform, ) -from mathics.eval.makeboxes.precedence import builtins_precedence, parenthesize +from mathics.eval.makeboxes.precedence import ( + builtins_precedence, + compare_precedence, + parenthesize, +) __all__ = [ "NumberForm_to_String", @@ -26,11 +31,13 @@ "StringRParen", "_boxed_string", "builtins_precedence", + "compare_precedence", "do_format", "eval_baseform", "eval_generic_makeboxes", "eval_infix", "eval_makeboxes", + "eval_makeboxes_outputform", "eval_mathmlform", "eval_postprefix", "eval_tableform", diff --git a/mathics/eval/makeboxes/makeboxes.py b/mathics/eval/makeboxes/makeboxes.py index 2d7275a2e..66b8c6b4d 100644 --- a/mathics/eval/makeboxes/makeboxes.py +++ b/mathics/eval/makeboxes/makeboxes.py @@ -13,7 +13,7 @@ from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression from mathics.core.symbols import Atom, Symbol, SymbolFullForm, SymbolMakeBoxes -from mathics.core.systemsymbols import SymbolStandardForm +from mathics.core.systemsymbols import SymbolOutputForm, SymbolStandardForm from mathics.eval.makeboxes.formatvalues import do_format from mathics.eval.makeboxes.precedence import parenthesize @@ -126,8 +126,21 @@ def int_to_string_shorter_repr(value: int, form: Symbol, max_digits=640): return String(value_str) +def eval_makeboxes_outputform(expr, evaluation, form): + """ + Build a 2D text representation of the expression. + """ + from mathics.builtin.box.layout import InterpretationBox, PaneBox + from mathics.format.outputform import expression_to_outputform_text + + text_outputform = str(expression_to_outputform_text(expr, evaluation, form)) + elem1 = PaneBox(String(text_outputform)) + elem2 = Expression(SymbolOutputForm, expr) + return InterpretationBox(elem1, elem2) + + def eval_fullform_makeboxes( - self, expr, evaluation: Evaluation, form=SymbolStandardForm + expr, evaluation: Evaluation, form=SymbolStandardForm ) -> Optional[BaseElement]: """ This function takes the definitions provided by the evaluation @@ -220,9 +233,27 @@ def format_element( Applies formats associated to the expression, and then calls Makeboxes """ evaluation.is_boxing = True + while element.get_head() is form: + element = element.elements[0] + + if element.has_form("FullForm", 1): + return eval_fullform_makeboxes(element.elements[0], evaluation) + + # In order to work like in WMA, `format_element` + # should evaluate `MakeBoxes[element//form, StandardForm]` + # Then, MakeBoxes[expr_, StandardForm], for any expr, + # should apply Format[...] rules, and then + # MakeBoxes[...] rules. These rules should be stored + # as FormatValues[...] + # As a first step in that direction, let's mimic this behaviour + # just for the case of OutputForm: + if element.has_form("OutputForm", 1): + return eval_makeboxes_outputform(element.elements[0], evaluation, form) + expr = do_format(element, evaluation, form) if expr is None: return None + result = Expression(SymbolMakeBoxes, expr, form) result_box = result.evaluate(evaluation) if isinstance(result_box, String): diff --git a/mathics/format/latex.py b/mathics/format/latex.py index 707e6ec41..eda0ca037 100644 --- a/mathics/format/latex.py +++ b/mathics/format/latex.py @@ -19,6 +19,8 @@ from mathics.builtin.box.layout import ( FractionBox, GridBox, + InterpretationBox, + PaneBox, RowBox, SqrtBox, StyleBox, @@ -133,6 +135,16 @@ def render(format, string, in_text=False): add_conversion_fn(String, string) +def interpretation_panebox(self, **options): + return lookup_conversion_method(self.elements[0], "latex")( + self.elements[0], **options + ) + + +add_conversion_fn(InterpretationBox, interpretation_panebox) +add_conversion_fn(PaneBox, interpretation_panebox) + + def fractionbox(self, **options) -> str: _options = self.box_options.copy() _options.update(options) diff --git a/mathics/format/mathml.py b/mathics/format/mathml.py index 7bbdd65da..d8ac94922 100644 --- a/mathics/format/mathml.py +++ b/mathics/format/mathml.py @@ -15,6 +15,8 @@ from mathics.builtin.box.layout import ( FractionBox, GridBox, + InterpretationBox, + PaneBox, RowBox, SqrtBox, StyleBox, @@ -110,6 +112,16 @@ def render(format, string): add_conversion_fn(String, string) +def interpretation_panebox(self, **options): + return lookup_conversion_method(self.elements[0], "latex")( + self.elements[0], **options + ) + + +add_conversion_fn(InterpretationBox, interpretation_panebox) +add_conversion_fn(PaneBox, interpretation_panebox) + + def fractionbox(self, **options) -> str: _options = self.box_options.copy() _options.update(options) diff --git a/mathics/format/outputform.py b/mathics/format/outputform.py new file mode 100644 index 000000000..0fc8309ec --- /dev/null +++ b/mathics/format/outputform.py @@ -0,0 +1,779 @@ +""" +This module builts the 2D string associated to the OutputForm +""" + +from typing import Callable, Dict, List, Union + +from mathics.core.atoms import ( + Integer, + Integer1, + Integer2, + IntegerM1, + Rational, + Real, + String, +) +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.symbols import Atom, Symbol, SymbolTimes +from mathics.core.systemsymbols import ( + SymbolDerivative, + SymbolInfix, + SymbolNone, + SymbolOutputForm, + SymbolPower, + SymbolStandardForm, + SymbolTraditionalForm, +) +from mathics.eval.makeboxes import compare_precedence, do_format # , format_element + +SymbolNonAssociative = Symbol("System`NonAssociative") +SymbolPostfix = Symbol("System`Postfix") +SymbolPrefix = Symbol("System`Prefix") +SymbolRight = Symbol("System`Right") +SymbolLeft = Symbol("System`Left") + + +expr_to_outputform_text_map: Dict[str, Callable] = {} + + +# This Exception if the expression should +# be processed by the default routine +class _WrongFormattedExpression(Exception): + pass + + +class IsNotGrid(Exception): + pass + + +class IsNot2DArray(Exception): + pass + + +def parenthesize(expr_str: str) -> str: + """wrap with parenthesis""" + return f"({expr_str})" + + +def bracket(expr_str: str) -> str: + """wrap with square brackets""" + return f"[{expr_str}]" + + +def grid(expr): + raise NotImplementedError + + +def expression_to_outputform_text( + expr: BaseElement, evaluation: Evaluation, form=SymbolStandardForm, **kwargs +): + """ + Build a 2d text from an `Expression` + """ + ## TODO: format the expression + format_expr: Expression = do_format(expr, evaluation, SymbolOutputForm) # type: ignore + + # Strip HoldForm + while format_expr.has_form("HoldForm", 1): # type: ignore + format_expr = format_expr.elements[0] + + lookup_name = format_expr.get_head().get_lookup_name() + try: + result = expr_to_outputform_text_map[lookup_name]( + format_expr, evaluation, form, **kwargs + ) + return result + except _WrongFormattedExpression: + # If the key is not present, or the execution fails for any reason, use + # the default + pass + except KeyError: + pass + return _default_expression_to_outputform_text( + format_expr, evaluation, form, **kwargs + ) + + +def _default_expression_to_outputform_text( + expr: Expression, evaluation: Evaluation, form: Symbol, **kwargs +) -> str: + """ + Default representation of a function + """ + expr_head = expr.head + head = expression_to_outputform_text(expr_head, evaluation, form, **kwargs) + comma = ", " + elements = [ + expression_to_outputform_text(elem, evaluation) for elem in expr.elements + ] + result = elements.pop(0) if elements else " " + while elements: + result = result + comma + elements.pop(0) + + if form is SymbolTraditionalForm: + return head + parenthesize(result) + return head + bracket(result) + + +def _divide(num, den, evaluation, form, **kwargs): + infix_form = Expression( + SymbolInfix, ListExpression(num, den), String("/"), Integer(400), SymbolLeft + ) + return expression_to_outputform_text(infix_form, evaluation, form, **kwargs) + + +def _strip_1_parm_expression_to_outputform_text( + expr: Expression, evaluation: Evaluation, form: Symbol, **kwargs +) -> str: + if len(expr.elements) != 1: + raise _WrongFormattedExpression + return expression_to_outputform_text(expr.elements[0], evaluation, form, **kwargs) + + +expr_to_outputform_text_map[ + "System`HoldForm" +] = _strip_1_parm_expression_to_outputform_text +expr_to_outputform_text_map[ + "System`InputForm" +] = _strip_1_parm_expression_to_outputform_text + + +def derivative_expression_to_outputform_text( + expr: Expression, evaluation: Evaluation, form: Symbol, **kwargs +) -> str: + """Derivative operator""" + head = expr.get_head() + if head is SymbolDerivative: + return _default_expression_to_outputform_text(expr, evaluation, form, **kwargs) + super_head = head.get_head() + if super_head is SymbolDerivative: + expr_elements = expr.elements + if len(expr_elements) != 1: + return _default_expression_to_outputform_text( + expr, evaluation, form, **kwargs + ) + function_head = expression_to_outputform_text( + expr_elements[0], evaluation, form, **kwargs + ) + derivatives = head.elements + if len(derivatives) == 1: + order_iv = derivatives[0] + if order_iv == Integer1: + return function_head + "'" + elif order_iv == Integer2: + return function_head + "''" + + return _default_expression_to_outputform_text(expr, evaluation, form, **kwargs) + + # Full Function with arguments: delegate to the default conversion. + # It will call us again with the head + return _default_expression_to_outputform_text(expr, evaluation, form, **kwargs) + + +expr_to_outputform_text_map[ + "System`Derivative" +] = derivative_expression_to_outputform_text + + +def divide_expression_to_outputform_text( + expr: Expression, evaluation: Evaluation, form: Symbol, **kwargs +) -> str: + if len(expr.elements) != 2: + raise _WrongFormattedExpression + num, den = expr.elements + return _divide(num, den, evaluation, form, **kwargs) + + +expr_to_outputform_text_map["System`Divide"] = divide_expression_to_outputform_text + + +def graphics(expr: Expression, evaluation: Evaluation, form: Symbol, **kwargs) -> str: + return "-Graphics-" + + +expr_to_outputform_text_map["System`Graphics"] = graphics + + +def graphics3d(expr: Expression, evaluation: Evaluation, form: Symbol, **kwargs) -> str: + return "-Graphics3D-" + + +expr_to_outputform_text_map["System`Graphics3D"] = graphics3d + + +def grid_expression_to_outputform_text( + expr: Expression, evaluation: Evaluation, form: Symbol, **kwargs +) -> str: + if len(expr.elements) == 0: + raise IsNotGrid + if len(expr.elements) > 1 and not expr.elements[1].has_form( + ["Rule", "RuleDelayed"], 2 + ): + raise IsNotGrid + if not expr.elements[0].has_form("List", None): + raise IsNotGrid + + elements = expr.elements[0].elements + rows = [] + for idx, item in enumerate(elements): + if item.has_form("List", None): + rows.append( + [ + expression_to_outputform_text(item_elem, evaluation, form, **kwargs) + for item_elem in item.elements + ] + ) + else: + rows.append(expression_to_outputform_text(item, evaluation, form, **kwargs)) + + return grid(rows) + + +expr_to_outputform_text_map["System`Grid"] = grid_expression_to_outputform_text + + +def integer_expression_to_outputform_text( + n: Integer, evaluation: Evaluation, form: Symbol, **kwargs +): + return str(n.value) + + +expr_to_outputform_text_map["System`Integer"] = integer_expression_to_outputform_text + + +def list_expression_to_outputform_text( + expr: Expression, evaluation: Evaluation, form: Symbol, **kwargs +) -> str: + result, *rest_elems = ( + expression_to_outputform_text(elem, evaluation, form, **kwargs) + for elem in expr.elements + ) + comma_tb = ", " + for next_elem in rest_elems: + result = result + comma_tb + next_elem + return "{" + result + "}" + + +expr_to_outputform_text_map["System`List"] = list_expression_to_outputform_text + + +def mathmlform_expression_to_outputform_text( + expr: Expression, evaluation: Evaluation, form: Symbol, **kwargs +) -> str: + # boxes = format_element(expr.elements[0], evaluation, form) + boxes = Expression( + Symbol("System`MakeBoxes"), expr.elements[0], SymbolStandardForm + ).evaluate(evaluation) + return boxes.boxes_to_mathml() # type: ignore[union-attr] + + +expr_to_outputform_text_map[ + "System`MathMLForm" +] = mathmlform_expression_to_outputform_text + + +def matrixform_expression_to_outputform_text( + expr: Expression, evaluation: Evaluation, form: Symbol, **kwargs +) -> str: + # return parenthesize(tableform_expression_to_outputform_text(expr, evaluation, form, **kwargs)) + return tableform_expression_to_outputform_text(expr, evaluation, form, **kwargs) + + +expr_to_outputform_text_map[ + "System`MatrixForm" +] = matrixform_expression_to_outputform_text + + +def plus_expression_to_outputform_text( + expr: Expression, evaluation: Evaluation, form: Symbol, **kwargs +) -> str: + elements = expr.elements + result = "" + for i, elem in enumerate(elements): + if elem.has_form("Times", None): + # If the first element is -1, remove it and use + # a minus sign. Otherwise, if negative, do not add a sign. + first = elem.elements[0] + if isinstance(first, Integer): + if first.value == -1: + result = ( + result + + " - " + + expression_to_outputform_text( + Expression(SymbolTimes, *elem.elements[1:]), + evaluation, + form, + **kwargs, + ) + ) + continue + elif first.value < 0: + result = ( + result + + " " + + expression_to_outputform_text( + elem, evaluation, form, **kwargs + ) + ) + continue + elif isinstance(first, Real): + if first.value < 0: + result = ( + result + + " " + + expression_to_outputform_text( + elem, evaluation, form, **kwargs + ) + ) + continue + result = ( + result + + " + " + + expression_to_outputform_text(elem, evaluation, form, **kwargs) + ) + ## TODO: handle complex numbers? + else: + elem_txt = expression_to_outputform_text(elem, evaluation, form, **kwargs) + if (compare_precedence(elem, 310) or -1) < 0: + elem_txt = parenthesize(elem_txt) + result = result + " + " + elem_txt + elif i == 0 or ( + (isinstance(elem, Integer) and elem.value < 0) + or (isinstance(elem, Real) and elem.value < 0) + ): + result = result + elem_txt + else: + result = ( + result + + " + " + + expression_to_outputform_text(elem, evaluation, form, **kwargs) + ) + return result + + +expr_to_outputform_text_map["System`Plus"] = plus_expression_to_outputform_text + + +def power_expression_to_outputform_text( + expr: Expression, evaluation: Evaluation, form: Symbol, **kwargs +): + if len(expr.elements) != 2: + raise _WrongFormattedExpression + + infix_form = Expression( + SymbolInfix, + ListExpression(*(expr.elements)), + String("^"), + Integer(590), + SymbolRight, + ) + return expression_to_outputform_text(infix_form, evaluation, form, **kwargs) + + +expr_to_outputform_text_map["System`Power"] = power_expression_to_outputform_text + + +def pre_pos_fix_expression_to_outputform_text( + expr: Expression, evaluation: Evaluation, form: Symbol, **kwargs +) -> str: + elements = expr.elements + if not (0 <= len(elements) <= 4): + raise _WrongFormattedExpression + + group = None + precedence = 670 + # Processing the first argument: + head = expr.get_head() + target = expr.elements[0] + if isinstance(target, Atom): + raise _WrongFormattedExpression + + operands = list(target.elements) + if len(operands) != 1: + raise _WrongFormattedExpression + + # Processing the second argument, if it is there: + if len(elements) > 1: + ops = elements[1] + ops_txt = [expression_to_outputform_text(ops, evaluation, form, **kwargs)] + else: + if head is SymbolPrefix: + default_symb = " @ " + ops_txt = ( + expression_to_outputform_text(head, evaluation, form, **kwargs) + + default_symb + ) + elif head is SymbolPostfix: + default_symb = " // " + ops_txt = default_symb + expression_to_outputform_text( + head, evaluation, form, **kwargs + ) + + # Processing the third argument, if it is there: + if len(elements) > 2: + if isinstance(elements[2], Integer): + precedence = elements[2].value + else: + raise _WrongFormattedExpression + + # Processing the forth argument, if it is there: + if len(elements) > 3: + group = elements[3] + if group not in (SymbolNone, SymbolLeft, SymbolRight, SymbolNonAssociative): + raise _WrongFormattedExpression + if group is SymbolNone: + group = None + + operand = operands[0] + cmp_precedence = compare_precedence(operand, precedence) + target_txt = expression_to_outputform_text(operand, evaluation, form, **kwargs) + if cmp_precedence is not None and cmp_precedence != -1: + target_txt = parenthesize(target_txt) + + return ops_txt[0] + target_txt if head is SymbolPrefix else target_txt + ops_txt[0] + + +expr_to_outputform_text_map["System`Prefix"] = pre_pos_fix_expression_to_outputform_text +expr_to_outputform_text_map[ + "System`Postfix" +] = pre_pos_fix_expression_to_outputform_text + + +def infix_expression_to_outputform_text( + expr: Expression, evaluation: Evaluation, form: Symbol, **kwargs +) -> str: + elements = expr.elements + if not (0 <= len(elements) <= 4): + raise _WrongFormattedExpression + + group = None + precedence = 670 + # Processing the first argument: + head = expr.get_head() + target = expr.elements[0] + if isinstance(target, Atom): + raise _WrongFormattedExpression + + operands = list(target.elements) + + if len(operands) < 2: + raise _WrongFormattedExpression + + # Processing the second argument, if it is there: + if len(elements) > 1: + ops = elements[1] + if head is SymbolInfix: + # This is not the WMA behaviour, but the Mathics current implementation requires it: + num_ops = 1 + if ops.has_form("List", None): + num_ops = len(ops.elements) + ops_lst = [ + expression_to_outputform_text(op, evaluation, form, **kwargs) + for op in ops.elements + ] + else: + ops_lst = [ + expression_to_outputform_text(ops, evaluation, form, **kwargs) + ] + elif head in (SymbolPrefix, SymbolPostfix): + ops_txt = [expression_to_outputform_text(ops, evaluation, form, **kwargs)] + else: + if head is SymbolInfix: + num_ops = 1 + default_symb = " ~ " + ops_lst = [ + default_symb + + expression_to_outputform_text(head, evaluation, form, **kwargs) + + default_symb + ] + elif head is SymbolPrefix: + default_symb = " @ " + ops_txt = ( + expression_to_outputform_text(head, evaluation, form, **kwargs) + + default_symb + ) + elif head is SymbolPostfix: + default_symb = " // " + ops_txt = default_symb + expression_to_outputform_text( + head, evaluation, form, **kwargs + ) + + # Processing the third argument, if it is there: + if len(elements) > 2: + if isinstance(elements[2], Integer): + precedence = elements[2].value + else: + raise _WrongFormattedExpression + + # Processing the forth argument, if it is there: + if len(elements) > 3: + group = elements[3] + if group not in (SymbolNone, SymbolLeft, SymbolRight, SymbolNonAssociative): + raise _WrongFormattedExpression + if group is SymbolNone: + group = None + + if head is SymbolPrefix: + operand = operands[0] + cmp_precedence = compare_precedence(operand, precedence) + target_txt = expression_to_outputform_text(operand, evaluation, form, **kwargs) + if cmp_precedence is not None and cmp_precedence != -1: + target_txt = parenthesize(target_txt) + return ops_txt[0] + target_txt + if head is SymbolPostfix: + operand = operands[0] + cmp_precedence = compare_precedence(operand, precedence) + target_txt = expression_to_outputform_text(operand, evaluation, form, **kwargs) + if cmp_precedence is not None and cmp_precedence != -1: + target_txt = parenthesize(target_txt) + return target_txt + ops_txt[0] + else: # Infix + parenthesized = group in (None, SymbolRight, SymbolNonAssociative) + for index, operand in enumerate(operands): + operand_txt = str( + expression_to_outputform_text(operand, evaluation, form, **kwargs) + ) + cmp_precedence = compare_precedence(operand, precedence) + if cmp_precedence is not None and ( + cmp_precedence == -1 or (cmp_precedence == 0 and parenthesized) + ): + operand_txt = parenthesize(operand_txt) + + if index == 0: + result = operand_txt + # After the first element, for lateral + # associativity, parenthesized is flipped: + if group in (SymbolLeft, SymbolRight): + parenthesized = not parenthesized + else: + space = " " + result_lst: List[str] + if str(ops_lst[index % num_ops]) != " ": + result_lst = [ + result, + space, + str(ops_lst[index % num_ops]), + space, + operand_txt, + ] + else: + result_lst = [result, space, operand_txt] + + return "".join(result_lst) + + +expr_to_outputform_text_map["System`Infix"] = infix_expression_to_outputform_text + + +def precedenceform_expression_to_outputform_text( + expr: Expression, evaluation: Evaluation, form: Symbol, **kwargs +) -> str: + if len(expr.elements) == 2: + return expression_to_outputform_text( + expr.elements[0], evaluation, form, **kwargs + ) + raise _WrongFormattedExpression + + +expr_to_outputform_text_map[ + "System`PrecedenceForm" +] = precedenceform_expression_to_outputform_text + + +def rational_expression_to_outputform_text( + n: Union[Rational, Expression], evaluation: Evaluation, form: Symbol, **kwargs +): + if n.has_form("Rational", 2): + num, den = n.elements # type: ignore[union-attr] + else: + num, den = n.numerator(), n.denominator() # type: ignore[union-attr] + return _divide(num, den, evaluation, form, **kwargs) + + +expr_to_outputform_text_map["System`Rational"] = rational_expression_to_outputform_text + + +def real_expression_to_outputform_text( + n: Real, evaluation: Evaluation, form: Symbol, **kwargs +): + str_n = n.make_boxes("System`OutputForm").boxes_to_text() # type: ignore[attr-defined] + return str(str_n) + + +expr_to_outputform_text_map["System`Real"] = real_expression_to_outputform_text + + +def string_expression_to_outputform_text( + expr: String, evaluation: Evaluation, form: Symbol, **kwargs +) -> str: + lines = expr.value.split("\n") + max_len = max([len(line) for line in lines]) + lines = [line + (max_len - len(line)) * " " for line in lines] + return "\n".join(lines) + + +expr_to_outputform_text_map["System`String"] = string_expression_to_outputform_text + + +def stringform_expression_to_outputform_text( + expr: Expression, evaluation: Evaluation, form: Symbol, **kwargs +) -> str: + strform = expr.elements[0] + if not isinstance(strform, String): + raise _WrongFormattedExpression + + items = list( + expression_to_outputform_text(item, evaluation, form, **kwargs) + for item in expr.elements[1:] + ) + + curr_indx = 0 + parts = strform.value.split("`") + result = str(parts[0]) + if len(parts) == 1: + return result + + quote_open = True + remaining = len(parts) - 1 + + for part in parts[1:]: + remaining -= 1 + if quote_open: + if remaining == 0: + result = result + "`" + part + quote_open = False + continue + if len(part) == 0: + result = result + items[curr_indx] + continue + try: + idx = int(part) + except ValueError: + idx = None + if idx is not None and str(idx) == part: + curr_indx = idx - 1 + result = result + items[curr_indx] + quote_open = False + continue + else: + result = result + "`" + part + "`" + quote_open = False + continue + else: + result = result + part + quote_open = True + + return result + + +expr_to_outputform_text_map[ + "System`StringForm" +] = stringform_expression_to_outputform_text + + +def symbol_expression_to_outputform_text( + symb: Symbol, evaluation: Evaluation, form: Symbol, **kwargs +): + return evaluation.definitions.shorten_name(symb.name) + + +expr_to_outputform_text_map["System`Symbol"] = symbol_expression_to_outputform_text + + +def tableform_expression_to_outputform_text( + expr: Expression, evaluation: Evaluation, form: Symbol, **kwargs +) -> str: + return grid_expression_to_outputform_text(expr, evaluation, form) + + +expr_to_outputform_text_map[ + "System`TableForm" +] = tableform_expression_to_outputform_text + + +def texform_expression_to_outputform_text( + expr: Expression, evaluation: Evaluation, form: Symbol, **kwargs +) -> str: + # boxes = format_element(expr.elements[0], evaluation, form) + boxes = Expression( + Symbol("System`MakeBoxes"), expr.elements[0], SymbolStandardForm + ).evaluate(evaluation) + return boxes.boxes_to_tex() # type: ignore + + +expr_to_outputform_text_map["System`TeXForm"] = texform_expression_to_outputform_text + + +def times_expression_to_outputform_text( + expr: Expression, evaluation: Evaluation, form: Symbol, **kwargs +) -> str: + elements = expr.elements + num: List[BaseElement] = [] + den: List[BaseElement] = [] + # First, split factors with integer, negative powers: + for elem in elements: + if elem.has_form("Power", 2): + base, exponent = elem.elements + if isinstance(exponent, Integer): + if exponent.value == -1: + den.append(base) + continue + elif exponent.value < 0: + den.append(Expression(SymbolPower, base, Integer(-exponent.value))) + continue + elif isinstance(elem, Rational): + num.append(elem.numerator()) + den.append(elem.denominator()) + continue + elif elem.has_form("Rational", 2): + elem_elements = elem.elements + num.append(elem_elements[0]) + den.append(elem_elements[1]) + continue + + num.append(elem) + + # If there are integer, negative powers, process as a fraction: + if den: + den_expr = den[0] if len(den) == 1 else Expression(SymbolTimes, *den) + num_expr = ( + Expression(SymbolTimes, *num) + if len(num) > 1 + else num[0] + if len(num) == 1 + else Integer1 + ) + return _divide(num_expr, den_expr, evaluation, form, **kwargs) + + # there are no integer negative powers: + if len(num) == 1: + return expression_to_outputform_text(num[0], evaluation, form, **kwargs) + + prefactor = 1 + result: str = "" + for i, elem in enumerate(num): + if elem is IntegerM1: + prefactor *= -1 + continue + if isinstance(elem, Integer): + prefactor *= -1 + elem = Integer(-elem.value) + + elem_txt = expression_to_outputform_text(elem, evaluation, form, **kwargs) + if compare_precedence(elem, 400): + elem_txt = parenthesize(elem_txt) + if i == 0: + result = elem_txt + else: + result = result + " " + elem_txt + if result == "": + result = "1" + if prefactor == -1: + result = "-" + result + return result + + +expr_to_outputform_text_map["System`Times"] = times_expression_to_outputform_text diff --git a/mathics/format/text.py b/mathics/format/text.py index 422ce940a..681017848 100644 --- a/mathics/format/text.py +++ b/mathics/format/text.py @@ -9,6 +9,8 @@ from mathics.builtin.box.layout import ( FractionBox, GridBox, + InterpretationBox, + PaneBox, RowBox, SqrtBox, StyleBox, @@ -40,6 +42,14 @@ def string(self, **options) -> str: add_conversion_fn(String, string) +def interpretation_panebox(self, **options): + return boxes_to_text(self.elements[0], **options) + + +add_conversion_fn(InterpretationBox, interpretation_panebox) +add_conversion_fn(PaneBox, interpretation_panebox) + + def fractionbox(self, **options) -> str: _options = self.box_options.copy() _options.update(options) @@ -72,13 +82,15 @@ def gridbox(self, elements=None, **box_options) -> str: widths = [0] cells = [ - [ - # TODO: check if this evaluation is necessary. - boxes_to_text(item, **box_options).splitlines() - for item in row - ] - if isinstance(row, tuple) - else [boxes_to_text(row, **box_options).splitlines()] + ( + [ + # TODO: check if this evaluation is necessary. + boxes_to_text(item, **box_options).splitlines() + for item in row + ] + if isinstance(row, tuple) + else [boxes_to_text(row, **box_options).splitlines()] + ) for row in items ]