Skip to content

Commit 6f5798b

Browse files
authored
More on Builtin.contribute and Builtin.get_functions. (#1298)
Some missing details that I discovered regarding these methods, and a pytest to test the right behaviour: * Fix the regular expression used in get_function to support multiline docstrings. * Fix how the full name of builtins are built in `contribute` when the class does not have a `context` attribute. * Add tests for Builtin.contribute More tests can be added later.
1 parent 1b27bc2 commit 6f5798b

File tree

2 files changed

+157
-7
lines changed

2 files changed

+157
-7
lines changed

mathics/core/builtin.py

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -239,12 +239,16 @@ def __init__(self, *args, **kwargs):
239239
def contribute(self, definitions: Definitions, is_pymodule=False):
240240
from mathics.core.parser import parse_builtin_rule
241241

242-
# Set the default context
243-
if not self.context:
244-
self.context = "Pymathics`" if is_pymodule else "System`"
245242
name = self.get_name()
246243
attributes = self.attributes
247244
options = {}
245+
# Set the default context
246+
if not self.context:
247+
self.context = "Pymathics`" if is_pymodule else "System`"
248+
# get_name takes the context from the class, not from the
249+
# instance, so even if we set the context here,
250+
# self.get_name() does not includes the context.
251+
name = self.context + name
248252

249253
# - 'Strict': warn and fail with unsupported options
250254
# - 'Warn': warn about unsupported options, but continue
@@ -468,7 +472,15 @@ def get_functions(self, prefix="eval", is_pymodule=False):
468472
if pattern is None: # Fixes PyPy bug
469473
continue
470474
else:
471-
m = re.match(r"[(]([\w,]+),[)]\:\s*(.*)", pattern)
475+
# TODO: consider to use a more sophisticated
476+
# regular expression, which handles breaklines
477+
# more properly, that supports format names
478+
# with contexts (context`name) and be less
479+
# fragile against leaving spaces between the
480+
# elements.
481+
m = re.match(
482+
r"[(]([\w,]+),[ ]*[)]\:\s*(.*)", pattern.replace("\n", " ")
483+
)
472484
if m is not None:
473485
attrs = m.group(1).split(",")
474486
pattern = m.group(2)
@@ -491,7 +503,7 @@ def get_functions(self, prefix="eval", is_pymodule=False):
491503
yield (pattern, function)
492504

493505
@staticmethod
494-
def get_option(options, name, evaluation, pop=False):
506+
def get_option(options, name, evaluation, pop=False) -> Optional[BaseElement]:
495507
return get_option(options, name, evaluation, pop)
496508

497509
def _get_unavailable_function(self) -> Optional[Callable]:
@@ -503,7 +515,12 @@ def _get_unavailable_function(self) -> Optional[Callable]:
503515
requires = getattr(self, "requires", [])
504516
return None if check_requires_list(requires) else UnavailableFunction(self)
505517

506-
def get_option_string(self, *params):
518+
def get_option_string(self, *params) -> Tuple[Optional[str], Optional[BaseElement]]:
519+
"""
520+
Return a tuple of a `str` representing the option name,
521+
and the proper Mathics value of the option.
522+
If the value does not have a name, the name is None.
523+
"""
507524
s = self.get_option(*params)
508525
if isinstance(s, String):
509526
return s.get_string_value(), s
@@ -789,7 +806,9 @@ def check_requires_list(requires: list) -> bool:
789806
return True
790807

791808

792-
def get_option(options: dict, name, evaluation, pop=False, evaluate=True):
809+
def get_option(
810+
options: dict, name, evaluation, pop=False, evaluate=True
811+
) -> Optional[BaseElement]:
793812
# we do not care whether an option X is given as System`X,
794813
# Global`X, or with any prefix from $ContextPath for that
795814
# matter. Also, the quoted string form "X" is ok. all these

test/core/test_builtin.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
"""
2+
Test how builtins are loaded
3+
"""
4+
5+
from mathics.builtin.makeboxes import MakeBoxes
6+
from mathics.core.builtin import Builtin
7+
from mathics.core.definitions import Definitions
8+
from mathics.core.element import BaseElement
9+
from mathics.core.evaluation import Evaluation
10+
from mathics.core.load_builtin import import_and_load_builtins
11+
from mathics.core.symbols import SymbolTrue
12+
13+
14+
class TrialBuiltin(Builtin):
15+
"""
16+
<dl>
17+
<dt>'TrialBuiltin'[$x$]
18+
<dd>nothing
19+
</dl>
20+
"""
21+
22+
options = {
23+
"FakeOption": "True",
24+
}
25+
messages = {
26+
"nomsg": "Test message `1`.",
27+
}
28+
29+
# A Downvalue
30+
def eval_downvalue(self, expr, evaluation):
31+
"""expr: TrialBuiltin[_:Symbol]"""
32+
return
33+
34+
# An Upvalue
35+
def eval_upvalue(self, expr, x, evaluation):
36+
"""expr: G[TrialBuiltin[x_:Symbol]]"""
37+
return
38+
39+
# A format rule for a custom format
40+
def format_parm1(self, expr, x, evaluation):
41+
"""(CustomForm,): expr: TrialBuiltin[x_:Symbol]"""
42+
return
43+
44+
# A MakeBoxes rule, using a name pattern,
45+
# with a line break.
46+
def format_parm2(self, expr, x, y, evaluation):
47+
"""(MakeBoxes, ):expr: TrialBuiltin[x_:Symbol,
48+
y_]
49+
"""
50+
return
51+
52+
# A format rule for OutputForm
53+
def format_parmb(self, expr, x, y, evaluation):
54+
"""(OutputForm,): expr: TrialBuiltin[x_:Symbol,
55+
y:P|Q]
56+
"""
57+
return
58+
59+
# A general format rule.
60+
def format_parm_general(self, expr, x, y, evaluation):
61+
"""expr: TrialBuiltin[x_:Symbol,
62+
y:P|Q]
63+
"""
64+
return
65+
66+
67+
# This happens before any call to import_and_load_builtins
68+
DEFINITIONS = Definitions()
69+
EVALUATION = Evaluation(DEFINITIONS)
70+
MakeBoxes(expression=False).contribute(DEFINITIONS)
71+
TrialBuiltin(expression=False).contribute(DEFINITIONS)
72+
73+
74+
def test_other_attributes_builtin():
75+
import_and_load_builtins()
76+
definitions = Definitions(add_builtin=True)
77+
definition = definitions.builtin["System`Plus"]
78+
79+
builtin = definition.builtin
80+
assert builtin.context == "System`"
81+
assert builtin.get_name() == "System`Plus"
82+
assert builtin.get_name(short=True) == "Plus"
83+
84+
85+
def test_builtin_get_functions():
86+
definitions = DEFINITIONS
87+
MakeBoxes.context = "System`"
88+
MakeBoxes(expression=False).contribute(definitions)
89+
builtin = definitions.builtin["System`TrialBuiltin"].builtin
90+
evalrules = list(builtin.get_functions("eval"))
91+
for r in evalrules:
92+
assert isinstance(r, tuple) and len(r) == 2
93+
assert isinstance(r[0], BaseElement)
94+
95+
evalrules = list(builtin.get_functions("format_"))
96+
for r in evalrules:
97+
assert isinstance(r, tuple) and len(r) == 2
98+
# For formatvalues, the pattern can be both a BaseElement
99+
# or a tuple of a string with a format name and a BaseElement.
100+
if isinstance(r[0], tuple):
101+
assert len(r[0]) == 2
102+
assert isinstance(r[0][0], list)
103+
assert isinstance(r[0][1], BaseElement)
104+
else:
105+
assert isinstance(r[0], BaseElement)
106+
107+
108+
def test_contribute_builtin():
109+
"""Test for Builtin.contribute."""
110+
111+
definitions = Definitions()
112+
evaluation = Evaluation(definitions)
113+
MakeBoxes(expression=False).contribute(definitions)
114+
115+
TrialBuiltin(expression=False).contribute(definitions)
116+
assert "System`TrialBuiltin" in definitions.builtin.keys()
117+
definition = definitions.get_definition("System`TrialBuiltin")
118+
# Check that the formats are loaded into the right places.
119+
assert "System`MakeBoxes" in definition.formatvalues
120+
assert "System`CustomForm" in definition.formatvalues
121+
assert "System`OutputForm" in definition.formatvalues
122+
# Test if the rules are loaded into the right place.
123+
assert definition.upvalues
124+
assert definition.downvalues
125+
assert definition.messages
126+
assert definition.options
127+
builtin = definition.builtin
128+
assert builtin.get_option_string(definition.options, "FakeOption", evaluation) == (
129+
"True",
130+
SymbolTrue,
131+
)

0 commit comments

Comments
 (0)