Skip to content

Commit cb1bcf9

Browse files
committed
WIP - skeletal Form refactor for discussion
Minimal code to show how we might refactor and organize Form boxing in a way that is simpler, more flexible, and closer to WMA.
1 parent 8f9ea38 commit cb1bcf9

10 files changed

Lines changed: 787 additions & 602 deletions

File tree

mathics/builtin/box/layout.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@
2121
from mathics.core.expression import Expression
2222
from mathics.core.list import ListExpression
2323
from mathics.core.symbols import Symbol, SymbolMakeBoxes
24-
from mathics.core.systemsymbols import SymbolRowBox, SymbolStandardForm
25-
26-
SymbolFractionBox = Symbol("System`FractionBox")
27-
SymbolSubscriptBox = Symbol("System`SubscriptBox")
28-
SymbolSubsuperscriptBox = Symbol("System`SubsuperscriptBox")
29-
SymbolSuperscriptBox = Symbol("System`SuperscriptBox")
30-
SymbolSqrtBox = Symbol("System`SqrtBox")
24+
from mathics.core.systemsymbols import (
25+
SymbolFractionBox,
26+
SymbolRowBox,
27+
SymbolSqrtBox,
28+
SymbolStandardForm,
29+
SymbolSubsuperscriptBox,
30+
SymbolSubscriptBox,
31+
SymbolSuperscriptBox,
32+
)
3133

3234

3335
# this temporarily replaces the _BoxedString class
@@ -165,8 +167,7 @@ class InterpretationBox(BoxExpression):
165167
"""
166168
<dl>
167169
<dt>'InterpretationBox[{...}, expr]'
168-
<dd> is a low-level box construct that displays as
169-
boxes but is interpreted on input as expr.
170+
<dd> is a low-level box construct that displays as boxes, but is interpreted on input as expr.
170171
</dl>
171172
172173
>> A = InterpretationBox["Pepe", 4]

mathics/builtin/forms/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
A <i>Form</i> format specifies the way Mathics Expression input is read or output written.
55
66
The variable <url>:$OutputForms':
7-
http://localhost:8000/doc/reference-of-built-in-symbols/forms-of-input-and-output/$outputforms/</url> has a list of Forms defined.
7+
http://localhost:8000/doc/reference-of-built-in-symbols/forms-of-input-and-output/form-variables/$outputforms/</url> has a list of Forms defined.
88
99
See also <url>:WMA link:
1010
https://reference.wolfram.com/language/tutorial/TextualInputAndOutput.html#12368</url>.

mathics/builtin/forms/base.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1+
from typing import Callable
2+
13
import mathics.core.definitions as definitions
24

35
from mathics.builtin.base import Builtin
6+
from mathics.core.element import BaseElement
47
from mathics.core.symbols import Symbol
58

9+
form_symbol_to_class = {}
10+
611

712
class FormBaseClass(Builtin):
813
"""
@@ -11,6 +16,9 @@ class FormBaseClass(Builtin):
1116
All Forms should subclass this.
1217
"""
1318

19+
# Mapping from Form and element type to a callable boxing method
20+
form_box_methods = {}
21+
1422
# Using "__new__" is not optimal for what we want.
1523
# We basically want to hook into class construction in order to
1624
# detect certain class attributes so we can add them to a list.
@@ -29,8 +37,60 @@ def __new__(cls, *args, **kwargs):
2937
if name in definitions.OutputForms:
3038
raise RuntimeError(f"{name} already added to $OutputsForms")
3139
definitions.OutputForms.add(Symbol(name))
40+
form_symbol_to_class[Symbol(name)] = cls
3241
return instance
3342

43+
@classmethod
44+
def box(cls, element: BaseElement, evaluation):
45+
"""
46+
This method is is called for each element that can be boxed.
47+
("box" here is a an active verb, not a noun).
48+
49+
This is a generic routine which calls the specific boxing routine
50+
that has been regstered in class variable ``form_box_methods`` previously.
51+
52+
If nothing has been registered, we just return ``element`` back unmodified
53+
as we do in evaluation.
54+
55+
Specific and custom method need to be implemented for each Form
56+
and element_type that perform some kind of boxing.
57+
"""
58+
method = cls.form_box_methods.get((cls, element.head), None)
59+
if method is None:
60+
# The default class should have been registered under FormBaseClass
61+
method = cls.form_box_methods.get((FormBaseClass, element.head), None)
62+
if method is None:
63+
# Just return the element unmodified.
64+
# Note: this is what we do in evaluation when we don't have a match
65+
return element
66+
67+
return method(element, evaluation)
68+
69+
@classmethod
70+
def register_box_method(cls, symbol: Symbol, method: Callable):
71+
"""
72+
Register ``method`` so method(element, ...) is called when
73+
``form.box(element, ...)`` is called.
74+
75+
"form" is something like ``StandardForm``, ``TraditionalForm``, etc.
76+
77+
To register the default boxing routine, register under the class
78+
``FormBaseClass``
79+
"""
80+
81+
cls.form_box_methods[cls, symbol] = method
82+
83+
84+
def box(element: BaseElement, evaluation, form: Symbol):
85+
"""
86+
Basically redirects the "box" call from a form symbol name to the "box" method off of
87+
the Form class named by the symbol.
88+
"""
89+
form_class = form_symbol_to_class.get(form, None)
90+
if form_class is None:
91+
return element
92+
return form_class.box(element, evaluation)
93+
3494

3595
# FormBaseClass is a public Builtin class that
3696
# should not get added as a definition (and therefore not added to
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"""
2+
Registers boxing routines for a number of the standard Builtin forms and Builtin Symbols
3+
"""
4+
5+
from mathics.builtin.box.layout import (
6+
SymbolFractionBox,
7+
SymbolSqrtBox,
8+
SymbolSuperscriptBox,
9+
)
10+
from mathics.builtin.forms.output import StandardForm, TraditionalForm
11+
from mathics.core.expression import Expression
12+
from mathics.core.symbols import SymbolDivide
13+
from mathics.core.systemsymbols import SymbolPower, SymbolSqrt
14+
15+
16+
def box_divide(expr: Expression, evaluation):
17+
assert (
18+
len(expr.elements) == 2
19+
), "Fraction Box element {elements} should have two items"
20+
return Expression(SymbolFractionBox, *expr.elements)
21+
22+
23+
def box_power(expr: Expression, evaluation):
24+
assert (
25+
len(expr.elements) == 2
26+
), "Fraction Box element {elements} should have two items"
27+
return Expression(SymbolSuperscriptBox, *expr.elements)
28+
29+
30+
def box_sqrt(expr: Expression, evaluation):
31+
assert len(expr.elements) == 1, "Sqrt Box element {elements} should have one item"
32+
return Expression(SymbolSqrtBox, *expr.elements)
33+
34+
35+
# More general, should we iterate over $BoxForms?
36+
for form in (StandardForm, TraditionalForm):
37+
form.register_box_method(SymbolDivide, box_divide)
38+
form.register_box_method(SymbolPower, box_power)
39+
form.register_box_method(SymbolSqrt, box_sqrt)

0 commit comments

Comments
 (0)