Skip to content

Commit 633e8c8

Browse files
committed
Sketch to showing how to drive Box from Form
A number of details of this are wrong - we probably want to hook into rules rather than write Python methods.
1 parent acf3c0d commit 633e8c8

2 files changed

Lines changed: 166 additions & 0 deletions

File tree

mathics/builtin/forms/base.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
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

69
form_symbol_to_class = {}
@@ -37,6 +40,57 @@ def __new__(cls, *args, **kwargs):
3740
form_symbol_to_class[Symbol(name)] = cls
3841
return instance
3942

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+
4094

4195
# FormBaseClass is a public Builtin class that
4296
# should not get added as a definition (and therefore not added to

test/formtest.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
"""
2+
Example to show how new Format-directed Boxing might work.
3+
4+
This is a minimal example so we can discuss feasiblity.
5+
"""
6+
from mathics.builtin.forms.base import box
7+
from mathics.core.atoms import IntegerM1, Integer1, Integer2, Rational
8+
from mathics.core.expression import Expression
9+
from mathics.core.parser import parse, MathicsSingleLineFeeder
10+
from mathics.core.symbols import SymbolDivide
11+
from mathics.core.systemsymbols import (
12+
SymbolFractionBox,
13+
SymbolMakeBoxes,
14+
SymbolPower,
15+
SymbolSqrt,
16+
SymbolStandardForm,
17+
)
18+
from mathics.session import MathicsSession
19+
20+
session = MathicsSession(character_encoding="ASCII")
21+
22+
23+
# Hacky pseudo boxing rules.
24+
# Currently in MakeBox rule rewriting occurs via MakeBox rewrite rules
25+
# which are attached to various Builtin classes.
26+
27+
# The rewrite is performed as a part of rewriting portion of Expression evaluation.
28+
# We probably want to segregate these rules from other kinds of rules.
29+
#
30+
# The below hacky code is to simulate the rule behavior for
31+
# just a few kinds of things so we don't have to hook into the
32+
# complex rewrite mechanism in use in general evaluation. The implementation we use
33+
# is just good enough for the new kinds of things we need.
34+
# It is not intended to be used in a final implementation.
35+
36+
37+
def fractionbox_fn(expr):
38+
# To be continued...
39+
return Expression(SymbolFractionBox, *expr.elements)
40+
41+
42+
def sqrtbox_fn(expr):
43+
return Expression(SymbolSqrt, expr.elements[0])
44+
45+
46+
def powerbox_fn(expr):
47+
new_expr = expr.elements[0]
48+
if new_expr.elements[-1] == IntegerM1:
49+
return Expression(SymbolDivide, Integer1, new_expr.elements[0])
50+
elif new_expr.elements[-1] in (Rational(1, 2), Integer1 * (Integer2**IntegerM1)):
51+
return Expression(SymbolSqrt, new_expr.elements[0])
52+
return new_expr
53+
54+
55+
boxform_rules = {
56+
# SymbolTimes: fractionbox_fn,
57+
SymbolPower: powerbox_fn,
58+
SymbolSqrt: sqrtbox_fn,
59+
}
60+
61+
62+
def apply_formatvalues_rules(expr, evaluation):
63+
"""
64+
Hacky replacement for rules() found in Expression rewrite_apply_eval().
65+
Note, we need to add builtin FormatValues() and the internals that go with that.
66+
"""
67+
if expr.elements[-1] not in (SymbolStandardForm,): # Or more generally $BoxForms
68+
# For other forms, there might be other transformations too, and this should
69+
# be discussed.
70+
# For simplicity, we will just handle a small number of $BoxForms rules
71+
return expr
72+
73+
# Remove the Form from expression "expr"
74+
unboxed_expr = expr.elements[0]
75+
76+
if unboxed_expr.head in boxform_rules:
77+
new_expr = boxform_rules[unboxed_expr.head](expr)
78+
return new_expr
79+
return unboxed_expr
80+
81+
82+
# Begin demo code.
83+
84+
for expr_str in (
85+
# FIXME;
86+
# "1 / x", # Show off "Division" boxing
87+
"a ^ b", # Show off "Power" boxing
88+
"Sqrt[a]", # "Square-root boxing"
89+
"a ^ (1/2)", # "Square-root boxing"
90+
):
91+
print("expression: ", expr_str)
92+
93+
# Parse, but don't evaluate expression.
94+
expr = parse(session.definitions, MathicsSingleLineFeeder(expr_str))
95+
print("Parsed expression: ", expr)
96+
97+
# Here is how Mathics currently evaluates MakeBoxes
98+
boxed_expr = Expression(SymbolMakeBoxes, expr, SymbolStandardForm)
99+
print("Mathics MakeBoxes: ", boxed_expr)
100+
101+
# Evaluate to get final printed/rendered form
102+
print("Eval'd Makeboxes: ", boxed_expr.evaluate(session.evaluation))
103+
104+
# Here is how Mathics might better box an expression.
105+
# First we apply MakeBox boxing transformation rules.
106+
# This handles expression rewriting.
107+
transformed_boxed_expr = apply_formatvalues_rules(boxed_expr, session.evaluation)
108+
109+
boxed_expr2 = box(transformed_boxed_expr, session.evaluation, SymbolStandardForm)
110+
print("New MakeBoxes: ", boxed_expr2)
111+
print("-" * 30)
112+
print("")

0 commit comments

Comments
 (0)