diff --git a/mathics/builtin/__init__.py b/mathics/builtin/__init__.py
index 0f88784ef..1d9704c46 100755
--- a/mathics/builtin/__init__.py
+++ b/mathics/builtin/__init__.py
@@ -36,6 +36,8 @@
mathics_to_python,
)
from mathics.core.pattern import pattern_objects
+from mathics.core.symbols import Symbol
+from mathics.eval.makeboxes import builtins_precedence
from mathics.settings import ENABLE_FILES_MODULE
from mathics.version import __version__ # noqa used in loading to check consistency.
@@ -60,7 +62,7 @@ def add_builtins(new_builtins):
# print("XXX1", sympy_name)
sympy_to_mathics[sympy_name] = builtin
if isinstance(builtin, Operator):
- builtins_precedence[name] = builtin.precedence
+ builtins_precedence[Symbol(name)] = builtin.precedence
if isinstance(builtin, PatternObject):
pattern_objects[name] = builtin.__class__
_builtins.update(dict(new_builtins))
@@ -237,8 +239,6 @@ def name_is_builtin_symbol(module, name: str) -> Optional[type]:
mathics_to_sympy = {} # here we have: name -> sympy object
sympy_to_mathics = {}
-builtins_precedence = {}
-
new_builtins = _builtins_list
# FIXME: some magic is going on here..
diff --git a/mathics/builtin/layout.py b/mathics/builtin/layout.py
index 26419763f..8812a3026 100644
--- a/mathics/builtin/layout.py
+++ b/mathics/builtin/layout.py
@@ -1,33 +1,164 @@
# -*- coding: utf-8 -*-
"""
-This module contains symbols used to define the high level layout for
+This module contains symbols used to define the high level layout for \
expression formatting.
-For instance, to represent a set of consecutive expressions in a row,
-we can use ``Row``
+For instance, to represent a set of consecutive expressions in a row, \
+we can use ``Row``.
"""
-
+from typing import Optional, Union
from mathics.builtin.base import BinaryOperator, Builtin, Operator
from mathics.builtin.box.layout import GridBox, RowBox, to_boxes
from mathics.builtin.lists import list_boxes
from mathics.builtin.makeboxes import MakeBoxes
from mathics.builtin.options import options_to_rules
-from mathics.core.atoms import Real, String
+from mathics.core.atoms import Integer, Integer1, Real, String
+from mathics.core.convert.op import operator_to_ascii, operator_to_unicode
+from mathics.core.element import BaseElement
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
-from mathics.eval.makeboxes import format_element
-
+from mathics.core.symbols import Atom, Symbol
+from mathics.core.systemsymbols import (
+ SymbolFullForm,
+ SymbolInputForm,
+ SymbolLeft,
+ SymbolMakeBoxes,
+ SymbolNone,
+ SymbolOutputForm,
+ SymbolRight,
+ SymbolRowBox,
+)
+from mathics.eval.makeboxes import eval_fullform_makeboxes, format_element, parenthesize
+
+SymbolNonAssociative = Symbol("System`NonAssociative")
SymbolSubscriptBox = Symbol("System`SubscriptBox")
+####################################################################
+# This section might get moved to mathics.box
+#
+# Some of the code below get replace Mathics code or may get put in
+# collection of boxing modules,
+# e.g. ``mathics.box.boxes_operators.box_infix()``.
+#
+####################################################################
+
+
+def box_infix(
+ expr: Expression,
+ operator: BaseElement,
+ form: Symbol,
+ evaluation: Evaluation,
+ precedence_value: Optional[int] = 0,
+ grouping: Optional[str] = None,
+) -> Optional[Expression]:
+ """Implements MakeBoxes[Infix[...]].
+ This function kicks off boxing for Infix operators.
+
+ Operators are processed to add spaces and to use the right encoding.
+ """
+
+ # FIXME: this should go into a some formatter.
+ def format_operator(operator) -> Union[String, BaseElement]:
+ """
+ Format infix operator `operator`. To do this outside parameter form is used.
+ Sometimes no changes are made and operator is returned unchanged.
+
+ This function probably should be rewritten be more scalable across other forms
+ and moved to a module that contiaing similar formatting routines.
+ """
+ if not isinstance(operator, String):
+ return MakeBoxes(operator, form)
+
+ op_str = operator.value
+
+ # FIXME: performing a check using the operator symbol representation feels a bit
+ # fragile. The operator name seems more straightforward and more robust.
+ if form == SymbolInputForm and op_str in ["*", "^", " "]:
+ return operator
+ elif (
+ form in (SymbolInputForm, SymbolOutputForm)
+ and not op_str.startswith(" ")
+ and not op_str.endswith(" ")
+ ):
+ # FIXME: Again, testing on specific forms is fragile and not scalable.
+ op = String(" " + op_str + " ")
+ return op
+ return operator
+
+ if isinstance(expr, Atom):
+ evaluation.message("Infix", "normal", Integer1)
+ return None
+
+ elements = expr.elements
+ if len(elements) > 1:
+ if operator.has_form("List", len(elements) - 1):
+ operator = [format_operator(op) for op in operator.elements]
+ return box_infix_elements(
+ elements, operator, precedence_value, grouping, form
+ )
+ else:
+ encoding_rule = evaluation.definitions.get_ownvalue("$CharacterEncoding")
+ encoding = "UTF8" if encoding_rule is None else encoding_rule.replace.value
+ op_str = (
+ operator.value if isinstance(operator, String) else operator.short_name
+ )
+ if encoding == "ASCII":
+ operator = format_operator(
+ String(operator_to_ascii.get(op_str, op_str))
+ )
+ else:
+ operator = format_operator(
+ String(operator_to_unicode.get(op_str, op_str))
+ )
+
+ return box_infix_elements(elements, operator, precedence_value, grouping, form)
+
+ elif len(elements) == 1:
+ return MakeBoxes(elements[0], form)
+ else:
+ return MakeBoxes(expr, form)
+
+
+# FIXME: op should be a string, so remove the Union.
+def box_infix_elements(
+ elements, op: Union[String, list], precedence: int, grouping, form: Symbol
+) -> Expression:
+ result = []
+ for index, element in enumerate(elements):
+ if index > 0:
+ if isinstance(op, list):
+ result.append(op[index - 1])
+ else:
+ result.append(op)
+ parenthesized = False
+ if grouping == "System`NonAssociative":
+ parenthesized = True
+ elif grouping == "System`Left" and index > 0:
+ parenthesized = True
+ elif grouping == "System`Right" and index == 0:
+ parenthesized = True
+
+ element_boxes = Expression(SymbolMakeBoxes, element, form)
+ element = parenthesize(precedence, element, element_boxes, parenthesized)
+
+ result.append(element)
+ return Expression(SymbolRowBox, ListExpression(*result))
+
+
+####################################################################
+# End of section of code that might be in mathics.box.
+####################################################################
+
+
class Center(Builtin):
"""
- :WMA link:https://reference.wolfram.com/language/ref/Center.html
+
+ :WMA link:
+ https://reference.wolfram.com/language/ref/Center.html
- 'Center'
@@ -93,7 +224,7 @@ class Grid(Builtin):
options = GridBox.options
summary_text = " 2D layout containing arbitrary objects"
- def eval_makeboxes(self, array, f, evaluation, options) -> Expression:
+ def eval_makeboxes(self, array, f, evaluation: Evaluation, options) -> Expression:
"""MakeBoxes[Grid[array_?MatrixQ, OptionsPattern[Grid]],
f:StandardForm|TraditionalForm|OutputForm]"""
return GridBox(
@@ -145,10 +276,53 @@ class Infix(Builtin):
"""
messages = {
+ "argb": "Infix called with `1` arguments; between 1 and 4 arguments are expected.",
+ "group": "Infix::group: Grouping specification `1` is not NonAssociative, None, Left, or Right.",
+ "intm": "Machine-sized integer expected at position 3 in `1`",
"normal": "Nonatomic expression expected at position `1`",
}
summary_text = "infix form"
+ # the right rule should be
+ # mbexpression:MakeBoxes[Infix[___], form]
+ def eval_makeboxes(self, expression, form, evaluation: Evaluation):
+ """MakeBoxes[Infix[___],
+ form:StandardForm|TraditionalForm|OutputForm|InputForm]"""
+ infix_expr = expression.elements[0]
+ elements = list(infix_expr.elements)
+ num_parms = len(elements)
+
+ if num_parms == 0 or num_parms > 4:
+ evaluation.message("Infix", "argb", Integer(num_parms))
+ return eval_fullform_makeboxes(infix_expr, evaluation, form)
+
+ if num_parms == 1:
+ expr = elements[0]
+ return box_infix(expr, String("~"), form, evaluation)
+ if num_parms == 2:
+ expr, operator = elements
+ return box_infix(expr, operator, form, evaluation)
+
+ expr, operator, precedence = elements[:3]
+ if not isinstance(precedence, Integer):
+ evaluation.message(
+ "Infix",
+ "intm",
+ Expression(SymbolFullForm, infix_expr),
+ )
+ return eval_fullform_makeboxes(infix_expr, evaluation, form)
+
+ grouping = SymbolNone if num_parms < 4 else elements[3]
+ if grouping is SymbolNone:
+ return box_infix(expr, operator, form, evaluation, precedence.value)
+ if grouping in (SymbolNonAssociative, SymbolLeft, SymbolRight):
+ return box_infix(
+ expr, operator, form, evaluation, precedence.value, grouping.get_name()
+ )
+
+ evaluation.message("Infix", "argb", grouping)
+ return eval_fullform_makeboxes(infix_expr, evaluation, form)
+
class Left(Builtin):
"""
@@ -156,7 +330,8 @@ class Left(Builtin):
- 'Left'
-
- is used with operator formatting constructs to specify a left-associative operator.
+
- is used with operator formatting constructs to specify a \
+ left-associative operator.
"""
@@ -171,7 +346,8 @@ class NonAssociative(Builtin):
- 'NonAssociative'
-
- is used with operator formatting constructs to specify a non-associative operator.
+
- is used with operator formatting constructs to specify a \
+ non-associative operator.
"""
@@ -288,7 +464,8 @@ class Right(Builtin):
- 'Right'
-
- is used with operator formatting constructs to specify a right-associative operator.
+
- is used with operator formatting constructs to specify a \
+ right-associative operator.
"""
@@ -307,21 +484,21 @@ class Row(Builtin):
summary_text = "1D layouts containing arbitrary objects in a row"
- def eval_makeboxes(self, items, sep, f, evaluation: Evaluation):
+ def eval_makeboxes(self, items, sep, form, evaluation: Evaluation):
"""MakeBoxes[Row[{items___}, sep_:""],
- f:StandardForm|TraditionalForm|OutputForm]"""
+ form:StandardForm|TraditionalForm|OutputForm]"""
items = items.get_sequence()
if not isinstance(sep, String):
- sep = MakeBoxes(sep, f)
+ sep = MakeBoxes(sep, form)
if len(items) == 1:
- return MakeBoxes(items[0], f)
+ return MakeBoxes(items[0], form)
else:
result = []
for index, item in enumerate(items):
if index > 0 and not sep.sameQ(String("")):
result.append(to_boxes(sep, evaluation))
- item = MakeBoxes(item, f).evaluate(evaluation)
+ item = MakeBoxes(item, form).evaluate(evaluation)
item = to_boxes(item, evaluation)
result.append(item)
return RowBox(*result)
@@ -334,22 +511,31 @@ class Style(Builtin):
- 'Style[$expr$, options]'
- displays $expr$ formatted using the specified option settings.
+
- 'Style[$expr$, "style"]'
- uses the option settings for the specified style in the current notebook.
+
- 'Style[$expr$, $color$]'
- displays using the specified color.
+
- 'Style[$expr$, $Bold$]'
- displays with fonts made bold.
+
- 'Style[$expr$, $Italic$]'
- displays with fonts made italic.
+
- 'Style[$expr$, $Underlined$]'
- displays with fonts underlined.
+
- 'Style[$expr$, $Larger$]
- displays with fonts made larger.
+
- 'Style[$expr$, $Smaller$]'
- displays with fonts made smaller.
+
- 'Style[$expr$, $n$]'
- displays with font size n.
+
- 'Style[$expr$, $Tiny$]'
- 'Style[$expr$, $Small$]', etc.
- display with fonts that are tiny, small, etc.
@@ -397,7 +583,9 @@ def eval_makeboxes(self, x, y, f, evaluation) -> Expression:
class Subsuperscript(Builtin):
"""
- :WMA link:https://reference.wolfram.com/language/ref/Subsuperscript.html
+
+ :WMA link:
+ https://reference.wolfram.com/language/ref/Subsuperscript.html
- 'Subsuperscript[$a$, $b$, $c$]'
@@ -419,7 +607,9 @@ class Subsuperscript(Builtin):
class Superscript(Builtin):
"""
- :WMA link:https://reference.wolfram.com/language/ref/Superscript.html
+
+ :WMA link:
+ https://reference.wolfram.com/language/ref/Superscript.html
- 'Superscript[$x$, $y$]'
diff --git a/mathics/builtin/makeboxes.py b/mathics/builtin/makeboxes.py
index 8dbac5fb7..e58005889 100644
--- a/mathics/builtin/makeboxes.py
+++ b/mathics/builtin/makeboxes.py
@@ -19,8 +19,13 @@
from mathics.core.list import ListExpression
from mathics.core.number import dps
from mathics.core.symbols import Atom, Symbol
-from mathics.core.systemsymbols import SymbolInputForm, SymbolOutputForm, SymbolRowBox
-from mathics.eval.makeboxes import _boxed_string, format_element
+from mathics.core.systemsymbols import (
+ SymbolInputForm,
+ SymbolNone,
+ SymbolOutputForm,
+ SymbolRowBox,
+)
+from mathics.eval.makeboxes import _boxed_string, format_element, parenthesize
def int_to_s_exp(expr, n):
@@ -35,30 +40,6 @@ def int_to_s_exp(expr, n):
return s, exp, nonnegative
-def parenthesize(precedence, element, element_boxes, when_equal):
- from mathics.builtin import builtins_precedence
-
- while element.has_form("HoldForm", 1):
- element = element.elements[0]
-
- if element.has_form(("Infix", "Prefix", "Postfix"), 3, None):
- element_prec = element.elements[2].value
- elif element.has_form("PrecedenceForm", 2):
- element_prec = element.elements[1].value
- # For negative values, ensure that the element_precedence is at least the precedence. (Fixes #332)
- elif isinstance(element, (Integer, Real)) and element.value < 0:
- element_prec = precedence
- else:
- element_prec = builtins_precedence.get(element.get_head_name())
- if precedence is not None and element_prec is not None:
- if precedence > element_prec or (precedence == element_prec and when_equal):
- return Expression(
- SymbolRowBox,
- ListExpression(String("("), element_boxes, String(")")),
- )
- return element_boxes
-
-
# FIXME: op should be a string, so remove the Union.
def make_boxes_infix(
elements, op: Union[String, list], precedence: int, grouping, form: Symbol
@@ -451,12 +432,38 @@ def eval_postprefix(self, p, expr, h, prec, f, evaluation):
else:
return MakeBoxes(expr, f).evaluate(evaluation)
+ # FIXME: prec is sometimes not an Integer for a ListExpession.
+ # And this is recent with respect to PredefinedSymbol revision.
+ # How does this get set and why?
def eval_infix(
- self, expr, operator, prec: Integer, grouping, form: Symbol, evaluation
+ self, expr, operator, precedence: Integer, grouping, form: Symbol, evaluation
):
- """MakeBoxes[Infix[expr_, operator_, prec_:None, grouping_:None],
+ """MakeBoxes[Infix[expr_, operator_, precedence_:None, grouping_:None],
form:StandardForm|TraditionalForm|OutputForm|InputForm]"""
+ # In WMA, this is covered with two different rules:
+ # * ```MakeBoxes[Infix[expr_, operator_]```
+ # * ```MakeBoxes[Infix[expr_, operator_, precedence_, grouping_:None]```
+ # In the second form, precedence must be an Integer. Otherwise, a message is
+ # shown and fall back to the standard MakeBoxes form.
+ # Here we allow Precedence to be ```System`None```, to have just one rule.
+
+ if precedence is SymbolNone:
+ precedence_value = None
+ elif isinstance(precedence, Integer):
+ precedence_value = precedence.value
+ else:
+ if grouping is not SymbolNone:
+ # Here I use a String as a head to avoid a circular evaluation.
+ # TODO: Use SymbolInfix when the MakeBoxes refactor be done.
+ expr = Expression(String("Infix"), expr, operator, precedence, grouping)
+ else:
+ expr = Expression(String("Infix"), expr, operator, precedence)
+ evaluation.message("Infix", "intm", expr)
+ return self.eval_general(expr, form, evaluation)
+
+ grouping = grouping
+
## FIXME: this should go into a some formatter.
def format_operator(operator) -> Union[String, BaseElement]:
"""
@@ -485,7 +492,7 @@ def format_operator(operator) -> Union[String, BaseElement]:
return op
return operator
- precedence = prec.value if hasattr(prec, "value") else 0
+ precedence = precedence.value if hasattr(precedence, "value") else 0
grouping = grouping.get_name()
if isinstance(expr, Atom):
@@ -496,7 +503,9 @@ def format_operator(operator) -> Union[String, BaseElement]:
if len(elements) > 1:
if operator.has_form("List", len(elements) - 1):
operator = [format_operator(op) for op in operator.elements]
- return make_boxes_infix(elements, operator, precedence, grouping, form)
+ return make_boxes_infix(
+ elements, operator, precedence_value, grouping, form
+ )
else:
encoding_rule = evaluation.definitions.get_ownvalue(
"$CharacterEncoding"
@@ -518,7 +527,9 @@ def format_operator(operator) -> Union[String, BaseElement]:
String(operator_to_unicode.get(op_str, op_str))
)
- return make_boxes_infix(elements, operator, precedence, grouping, form)
+ return make_boxes_infix(
+ elements, operator, precedence_value, grouping, form
+ )
elif len(elements) == 1:
return MakeBoxes(elements[0], form)
diff --git a/mathics/core/symbols.py b/mathics/core/symbols.py
index db8b9f9a4..225601a1e 100644
--- a/mathics/core/symbols.py
+++ b/mathics/core/symbols.py
@@ -341,7 +341,11 @@ class Symbol(Atom, NumericOperators, EvalMixin):
also associated with a ``Definition``. But it has a ``DownValue``
that is used in its evaluation.
- We also have Symbols which in contrast to Variables Symbols have
+ A Function Symbol, like a Variable Symbol, is a ``Symbol`` that is
+ also associated with a ``Definition``. But it has a ``DownValue``
+ that is used in its evaluation.
+
+ We also have Symbols which, in contrast to Variables Symbols, have
a constant value that cannot change. System`True and System`False
are like this.
diff --git a/mathics/eval/makeboxes.py b/mathics/eval/makeboxes.py
index 700a6c9d4..d248b48a6 100644
--- a/mathics/eval/makeboxes.py
+++ b/mathics/eval/makeboxes.py
@@ -9,7 +9,7 @@
import typing
from typing import Any, Dict, Type
-from mathics.core.atoms import Complex, Integer, Rational, String, SymbolI
+from mathics.core.atoms import Complex, Integer, Rational, Real, String, SymbolI
from mathics.core.convert.expression import to_expression_with_specialization
from mathics.core.definitions import OutputForms
from mathics.core.element import BaseElement, BoxElementMixin, EvalMixin
@@ -38,6 +38,7 @@
SymbolMinus,
SymbolOutputForm,
SymbolRational,
+ SymbolRowBox,
SymbolStandardForm,
)
@@ -299,6 +300,38 @@ def do_format_expression(
return expr
+def parenthesize(
+ precedence: int, element: Type[BaseElement], element_boxes, when_equal: bool
+) -> Type[Expression]:
+ """
+ "Determines if ``element_boxes`` needs to be surrounded with parenthesis.
+ This is done based on ``precedence`` and the computed preceence of
+ ``element``. The adjusted ListExpression is returned.
+
+ If when_equal is True, parentheses will be added if the two
+ precedence values are equal.
+ """
+ while element.has_form("HoldForm", 1):
+ element = element.elements[0]
+
+ if element.has_form(("Infix", "Prefix", "Postfix"), 3, None):
+ element_prec = element.elements[2].value
+ elif element.has_form("PrecedenceForm", 2):
+ element_prec = element.elements[1].value
+ # For negative values, ensure that the element_precedence is at least the precedence. (Fixes #332)
+ elif isinstance(element, (Integer, Real)) and element.value < 0:
+ element_prec = precedence
+ else:
+ element_prec = builtins_precedence.get(element.get_head())
+ if precedence is not None and element_prec is not None:
+ if precedence > element_prec or (precedence == element_prec and when_equal):
+ return Expression(
+ SymbolRowBox,
+ ListExpression(String("("), element_boxes, String(")")),
+ )
+ return element_boxes
+
+
element_formatters[Rational] = do_format_rational
element_formatters[Complex] = do_format_complex
element_formatters[Expression] = do_format_expression