Skip to content

Commit 32dcf61

Browse files
authored
Merge pull request #1140 from mathics/makeboxesoverhault
Makeboxes overhaul
2 parents 516f3a3 + 054779a commit 32dcf61

18 files changed

Lines changed: 8877 additions & 7270 deletions

mathics/builtin/__init__.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
continue
4444
modules.append(module)
4545

46-
builtins = []
46+
_builtins = []
4747
builtins_by_module = {}
4848

4949

@@ -75,7 +75,7 @@ def is_builtin(var):
7575
# This set the default context for symbols in mathics.builtins
7676
if not type(instance).context:
7777
type(instance).context = "System`"
78-
builtins.append( (instance.get_name(), instance))
78+
_builtins.append( (instance.get_name(), instance))
7979
builtins_by_module[module.__name__].append(instance)
8080

8181

@@ -85,7 +85,6 @@ def is_builtin(var):
8585
mathics_to_python = {} # here we have: name -> string
8686
sympy_to_mathics = {}
8787

88-
box_constructs = {}
8988
pattern_objects = {}
9089
builtins_precedence = {}
9190

@@ -101,20 +100,24 @@ def add_builtins(new_builtins):
101100
mathics_to_sympy[name] = builtin
102101
for sympy_name in builtin.get_sympy_names():
103102
sympy_to_mathics[sympy_name] = builtin
104-
if isinstance(builtin, BoxConstruct):
105-
box_constructs[name] = builtin
106103
if isinstance(builtin, Operator):
107104
builtins_precedence[name] = builtin.precedence
108105
if isinstance(builtin, PatternObject):
109106
pattern_objects[name] = builtin.__class__
110-
builtins.update(dict(new_builtins))
107+
_builtins.update(dict(new_builtins))
111108

112109

113-
new_builtins = builtins
114-
builtins = {}
110+
new_builtins = _builtins
111+
_builtins = {}
115112
add_builtins(new_builtins)
116113

117114

115+
def builtins_dict():
116+
return { builtin.get_name() : builtin
117+
for modname, builtins in builtins_by_module.items()
118+
for builtin in builtins}
119+
120+
118121
def get_module_doc(module):
119122
doc = module.__doc__
120123
if doc is not None:
@@ -134,8 +137,8 @@ def get_module_doc(module):
134137

135138
def contribute(definitions):
136139
# let MakeBoxes contribute first
137-
builtins["System`MakeBoxes"].contribute(definitions)
138-
for name, item in builtins.items():
140+
_builtins["System`MakeBoxes"].contribute(definitions)
141+
for name, item in _builtins.items():
139142
if name != "System`MakeBoxes":
140143
item.contribute(definitions)
141144

mathics/builtin/assignment.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1766,9 +1766,6 @@ def apply(self, module, evaluation):
17661766
except ImportError as e:
17671767
evaluation.message(self.get_name(), 'notfound', module)
17681768
return SymbolFailed
1769-
except PyMathicsLoadException as e:
1770-
evaluation.message(self.get_name(), 'notmathicslib', module)
1771-
return SymbolFailed
17721769
else:
17731770
# Add Pymathics` to $ContextPath so that when user don't
17741771
# have to qualify Pymathics variables and functions,

mathics/builtin/base.py

Lines changed: 100 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,7 @@ def contribute(self, definitions, is_pymodule=False):
102102
# used, so it won't work.
103103
if option not in definitions.builtin:
104104
definitions.builtin[option] = Definition(
105-
name=name, attributes=set()
106-
)
105+
name=name, attributes=set())
107106

108107
# Check if the given options are actually supported by the Builtin.
109108
# If not, we might issue an optx error and abort. Using '$OptionSyntax'
@@ -247,8 +246,7 @@ def contextify_form_name(f):
247246
# used, so it won't work.
248247
if option not in definitions.builtin:
249248
definitions.builtin[option] = Definition(
250-
name=name, attributes=set()
251-
)
249+
name=name, attributes=set())
252250
defaults = []
253251
for spec, value in self.defaults.items():
254252
value = parse_builtin_rule(value)
@@ -364,7 +362,7 @@ def get_option_string(self, *params):
364362
return None, s
365363

366364

367-
class InstancableBuiltin(Builtin):
365+
class InstanceableBuiltin(Builtin):
368366
def __new__(cls, *args, **kwargs):
369367
new_kwargs = kwargs.copy()
370368
new_kwargs["expression"] = False
@@ -377,7 +375,7 @@ def __new__(cls, *args, **kwargs):
377375
try:
378376
instance.init(*args, **kwargs)
379377
except TypeError:
380-
# TypeError occurs when unpickling instance, e.g. PatterObject,
378+
# TypeError occurs when unpickling instance, e.g. PatternObject,
381379
# because parameter expr is not given. This should no be a
382380
# problem, as pickled objects need their init-method not
383381
# being called.
@@ -592,11 +590,101 @@ class BoxConstructError(Exception):
592590
pass
593591

594592

595-
class BoxConstruct(Builtin):
596-
def get_option_values(self, leaves, evaluation=None, **options):
597-
default = evaluation.definitions.get_options(self.get_name()).copy()
598-
options = Expression("List", *leaves).get_option_values(evaluation)
599-
default.update(options)
593+
class BoxConstruct(InstanceableBuiltin):
594+
def __new__(cls, *leaves, **kwargs):
595+
instance = super().__new__(cls, *leaves, **kwargs)
596+
instance._leaves = leaves
597+
return instance
598+
599+
def evaluate(self, evaluation):
600+
# THINK about: Should we evaluate the leaves here?
601+
return
602+
603+
def get_head_name(self):
604+
return self.get_name()
605+
606+
def get_lookup_name(self):
607+
return self.get_name()
608+
609+
def get_string_value(self):
610+
return "-@" + self.get_head_name() + "@-"
611+
612+
def same(self, expr):
613+
return expr.same(self)
614+
615+
def is_atom(self):
616+
return False
617+
618+
def do_format(self, evaluation, format):
619+
return self
620+
621+
def format(self, evaluation, fmt):
622+
return self
623+
624+
def get_head(self):
625+
return Symbol(self.get_name())
626+
627+
@property
628+
def head(self):
629+
return self.get_head()
630+
631+
@head.setter
632+
def head(self, value):
633+
raise ValueError('BoxConstruct.head is write protected.')
634+
635+
@property
636+
def leaves(self):
637+
return self._leaves
638+
639+
@leaves.setter
640+
def leaves(self, value):
641+
raise ValueError('BoxConstruct.leaves is write protected.')
642+
643+
# I need to repeat this, because this is not
644+
# an expression...
645+
def has_form(self, heads, *leaf_counts):
646+
"""
647+
leaf_counts:
648+
(,): no leaves allowed
649+
(None,): no constraint on number of leaves
650+
(n, None): leaf count >= n
651+
(n1, n2, ...): leaf count in {n1, n2, ...}
652+
"""
653+
654+
head_name = self.get_name()
655+
if isinstance(heads, (tuple, list, set)):
656+
if head_name not in [ensure_context(h) for h in heads]:
657+
return False
658+
else:
659+
if head_name != ensure_context(heads):
660+
return False
661+
if not leaf_counts:
662+
return False
663+
if leaf_counts and leaf_counts[0] is not None:
664+
count = len(self._leaves)
665+
if count not in leaf_counts:
666+
if (len(leaf_counts) == 2 and # noqa
667+
leaf_counts[1] is None and count >= leaf_counts[0]):
668+
return True
669+
else:
670+
return False
671+
return True
672+
673+
def flatten_pattern_sequence(self, evaluation) -> 'BoxConstruct':
674+
return self
675+
676+
def get_option_values(self, leaves, **options):
677+
evaluation = options.get("evaluation", None)
678+
if evaluation:
679+
default = evaluation.definitions.get_options(self.get_name()).copy()
680+
options = Expression("List", *leaves).get_option_values(evaluation)
681+
default.update(options)
682+
else:
683+
from mathics.core.parser import parse_builtin_rule
684+
default = {}
685+
for option, value in self.options.items():
686+
option = ensure_context(option)
687+
default[option] = parse_builtin_rule(value)
600688
return default
601689

602690
def boxes_to_text(self, leaves, **options) -> str:
@@ -619,7 +707,7 @@ def __init__(self, name, count, expected):
619707
super().__init__(None, None)
620708

621709

622-
class PatternObject(InstancableBuiltin, Pattern):
710+
class PatternObject(InstanceableBuiltin, Pattern):
623711
needs_verbatim = True
624712

625713
arg_counts: typing.List[int] = []

mathics/builtin/compilation.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,20 +149,26 @@ def __hash__(self):
149149
return hash(("CompiledCode", ctypes.addressof(self.cfunc))) # XXX hack
150150

151151
def atom_to_boxes(self, f, evaluation):
152-
return Expression('CompiledCodeBox')
152+
return CompiledCodeBox(String("Nocode"), evaluation=evaluation)
153153

154154

155155
class CompiledCodeBox(BoxConstruct):
156156
"""
157157
Used internally by <i>CompileCode[]</i>.
158158
"""
159-
def boxes_to_text(self, leaves, **options):
159+
def boxes_to_text(self, leaves=None, **options):
160+
if not leaves:
161+
leaves = self._leaves
160162
return '-CompiledCode-'
161163

162-
def boxes_to_xml(self, leaves, **options):
164+
def boxes_to_xml(self, leaves=None, **options):
165+
if not leaves:
166+
leaves = self._leaves
163167
return '-CompiledCode-'
164168

165-
def boxes_to_tex(self, leaves, **options):
169+
def boxes_to_tex(self, leaves=None, **options):
170+
if not leaves:
171+
leaves = self._leaves
166172
return '-CompiledCode-'
167173

168174

mathics/builtin/graphics.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from mathics.builtin.base import (
1616
Builtin,
17-
InstancableBuiltin,
17+
InstanceableBuiltin,
1818
BoxConstruct,
1919
BoxConstructError,
2020
)
@@ -508,11 +508,14 @@ def convert(content):
508508
for option in options:
509509
if option not in ("System`ImageSize",):
510510
options[option] = Expression("N", options[option]).evaluate(evaluation)
511-
box_name = "Graphics" + self.box_suffix
512-
return Expression(box_name, convert(content), *options_to_rules(options))
511+
from mathics.builtin.graphics3d import Graphics3DBox, Graphics3D
512+
if type(self) is Graphics:
513+
return GraphicsBox(convert(content), *options_to_rules(options))
514+
elif type(self) is Graphics3D:
515+
return Graphics3DBox(convert(content), *options_to_rules(options))
513516

514517

515-
class _GraphicsElement(InstancableBuiltin):
518+
class _GraphicsElement(InstanceableBuiltin):
516519
def init(self, graphics, item=None, style=None):
517520
if item is not None and not item.has_form(self.get_name(), None):
518521
raise BoxConstructError
@@ -2599,7 +2602,7 @@ def to_svg(self):
25992602
style = create_css(font_color=self.color)
26002603
svg = (
26012604
'<foreignObject x="%f" y="%f" ox="%f" oy="%f" style="%s">'
2602-
"<math>%s</math></foreignObject>"
2605+
'<math>%s</math></foreignObject>'
26032606
) % (x, y, self.opos[0], self.opos[1], style, content)
26042607
return svg
26052608

@@ -2936,7 +2939,10 @@ class GraphicsBox(BoxConstruct):
29362939

29372940
attributes = ("HoldAll", "ReadProtected")
29382941

2939-
def boxes_to_text(self, leaves, **options):
2942+
def boxes_to_text(self, leaves=None, **options):
2943+
if not leaves:
2944+
leaves = self._leaves
2945+
29402946
self._prepare_elements(leaves, options) # to test for Box errors
29412947
return "-Graphics-"
29422948

@@ -2992,9 +2998,7 @@ def _get_image_size(self, options, graphics_options, max_width):
29922998
def _prepare_elements(self, leaves, options, neg_y=False, max_width=None):
29932999
if not leaves:
29943000
raise BoxConstructError
2995-
29963001
graphics_options = self.get_option_values(leaves[1:], **options)
2997-
29983002
background = graphics_options["System`Background"]
29993003
if (
30003004
isinstance(background, Symbol)
@@ -3142,7 +3146,9 @@ def get_range(min, max):
31423146

31433147
return elements, calc_dimensions
31443148

3145-
def boxes_to_tex(self, leaves, **options):
3149+
def boxes_to_tex(self, leaves=None, **options):
3150+
if not leaves:
3151+
leaves = self._leaves
31463152
elements, calc_dimensions = self._prepare_elements(
31473153
leaves, options, max_width=450
31483154
)
@@ -3195,7 +3201,9 @@ def boxes_to_tex(self, leaves, **options):
31953201

31963202
return tex
31973203

3198-
def boxes_to_xml(self, leaves, **options):
3204+
def boxes_to_xml(self, leaves=None, **options):
3205+
if not leaves:
3206+
leaves = self._leaves
31993207
elements, calc_dimensions = self._prepare_elements(leaves, options, neg_y=True)
32003208

32013209
xmin, xmax, ymin, ymax, w, h, width, height = calc_dimensions()
@@ -3623,7 +3631,6 @@ class _ColorObject(Builtin):
36233631

36243632
def __init__(self, *args, **kwargs):
36253633
super(_ColorObject, self).__init__(*args, **kwargs)
3626-
36273634
if self.text_name is None:
36283635
text_name = strip_context(self.get_name()).lower()
36293636
else:
@@ -3660,6 +3667,7 @@ class Black(_ColorObject):
36603667
}
36613668

36623669

3670+
36633671
class White(_ColorObject):
36643672
"""
36653673
>> White
@@ -3737,6 +3745,7 @@ class Magenta(_ColorObject):
37373745
}
37383746

37393747

3748+
37403749
class Yellow(_ColorObject):
37413750
"""
37423751
>> Yellow
@@ -3860,6 +3869,7 @@ class Large(Builtin):
38603869
style_options = system_symbols_dict(
38613870
{
38623871
"FontColor": _style,
3872+
"ImageSizeMultipliers": (lambda *x: x[1])
38633873
}
38643874
)
38653875

0 commit comments

Comments
 (0)