Skip to content

Commit d451aab

Browse files
committed
metaprogramming to generate all no-method classes.
1 parent eb2db37 commit d451aab

File tree

4 files changed

+134
-57
lines changed

4 files changed

+134
-57
lines changed

mathics/builtin/atomic/symbols.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
)
5252

5353

54-
def _get_usage_string(symbol, evaluation, is_long_form: bool, htmlout=False):
54+
def _get_usage_string(symbol, evaluation, is_long_form: bool):
5555
"""
5656
Returns a python string with the documentation associated to a given symbol.
5757
"""
@@ -78,16 +78,13 @@ def _get_usage_string(symbol, evaluation, is_long_form: bool, htmlout=False):
7878
if bio is not None:
7979
if not is_long_form and hasattr(bio.builtin.__class__, "summary_text"):
8080
return bio.builtin.__class__.summary_text
81-
from mathics.doc.common_doc import XMLDoc
81+
from mathics.doc.common_doc import XMLDOC
8282

8383
docstr = bio.builtin.__class__.__doc__
8484
title = bio.builtin.__class__.__name__
8585
if docstr is None:
8686
return None
87-
if htmlout:
88-
usagetext = XMLDoc(docstr, title).html()
89-
else:
90-
usagetext = XMLDoc(docstr, title).text(0)
87+
usagetext = XMLDOC(docstr, title).text(0)
9188
usagetext = re.sub(r"\$([0-9a-zA-Z]*)\$", r"\1", usagetext)
9289
return usagetext
9390
return None

mathics/builtin/no_meaning.py

Lines changed: 44 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -6,46 +6,49 @@
66
You can use these operators as a way to build up your own notation within Mathics3.
77
"""
88

9-
from inspect import getmembers, isclass
10-
from sys import modules
11-
12-
from mathics.core.builtin import OPERATOR_DATA, NoMeaningInfixOperator
13-
14-
# Note: classes in this file must *only* be "no-meaning"
15-
# builtin operator classes.
16-
17-
18-
class Because(NoMeaningInfixOperator):
19-
r"""This text is replaced! But it needs to be here for documentation detection."""
20-
21-
22-
class Cap(NoMeaningInfixOperator):
23-
r"""This text is replaced! But it needs to be here for documentation detection."""
24-
25-
26-
class CenterDot(NoMeaningInfixOperator):
27-
r"""This text is replaced! But it needs to be here for documentation detection."""
28-
29-
30-
class Star(NoMeaningInfixOperator):
31-
r"""This text is replaced! But it needs to be here for documentation detection."""
9+
# WARNING: This module uses fancy MetaProgramming to create the Mathics3 Builtin classes for the
10+
# operators in that have no pre-defined meaning. This is tricky and probably fragile code.
11+
# We use the type() function to create the class, and setattr, to include this class inside
12+
# this module.
3213

14+
from sys import modules
3315

34-
# Generate Builtin No-meaning Builtin Infix operators, using
35-
# the Operator name and Operator Unicode found by reading
36-
# the operators JSON file.
37-
for name, operator_class in getmembers(modules[__name__]):
38-
if isclass(operator_class):
39-
operator_name = operator_class.__name__
40-
operator_string = OPERATOR_DATA["no-meaning-infix-operators"].get(operator_name)
41-
if operator_string is not None:
42-
operator_class.operator = operator_string
43-
operator_class.__doc__ = NoMeaningInfixOperator.__doc_pattern__.format(
44-
operator_name=operator_name, operator_string=operator_string
45-
)
46-
operator_class.summary_text = f"""{operator_name} infix operator "{operator_string}" (no pre-set meaning attached)"""
47-
operator_class.formats = {
48-
(("InputForm", "OutputForm", "StandardForm"), f"{operator_name}[args__]"): (
49-
('Infix[{args}, "%s"]' % operator_string)
50-
)
51-
}
16+
from mathics.core.builtin import (
17+
OPERATOR_DATA,
18+
NoMeaningInfixOperator,
19+
NoMeaningPostfixOperator,
20+
NoMeaningPrefixOperator,
21+
)
22+
23+
# Generate no-meaning Mathics3 Builtin class from the operator name,
24+
# affix, and Operator Unicode values found read from the JSON operators
25+
# file.
26+
for affix, format_fn, operator_base_class in (
27+
("infix", "Infix", NoMeaningInfixOperator),
28+
("postfix", "Postfix", NoMeaningPostfixOperator),
29+
("prefix", "Prefix", NoMeaningPrefixOperator),
30+
):
31+
for operator_name, operator_string in OPERATOR_DATA[
32+
f"no-meaning-{affix}-operators"
33+
].items():
34+
# Create the Mathics3 Builtin class...
35+
generated_operator_class = type(
36+
operator_name,
37+
(operator_base_class,),
38+
{
39+
"__doc__": operator_base_class.__doc_pattern__.format(
40+
operator_name=operator_name, operator_string=operator_string
41+
),
42+
"operator": operator_string,
43+
"summary_text": f"""{operator_name} {affix} operator "{operator_string}" (no pre-set meaning attached)""",
44+
"formats": {
45+
(
46+
("InputForm", "OutputForm", "StandardForm"),
47+
f"{operator_name}[args__]",
48+
): (('%s[{args}, "%s"]' % (format_fn, operator_string)))
49+
},
50+
},
51+
)
52+
53+
# Put the newly-created Builtin class inside this module.
54+
setattr(modules[__name__], operator_name, generated_operator_class)

mathics/core/builtin.py

Lines changed: 85 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1159,7 +1159,7 @@ def eval_multi(self, expr, first, sequ, evaluation):
11591159
return to_expression(name, to_expression(name, expr, *sequ), first)
11601160

11611161

1162-
class Operator(Builtin, ABC):
1162+
class Operator(Builtin):
11631163
"""
11641164
Base Class for operators: binary, unary, nullary, prefix postfix, ...
11651165
"""
@@ -1192,7 +1192,9 @@ def get_operator_display(self) -> Optional[str]:
11921192
return self.operator
11931193

11941194

1195-
class InfixOperator(Operator, ABC):
1195+
# Note: Metaprogramming in mathics.builtin.no_meaning fails if
1196+
# we inherit from ABC
1197+
class InfixOperator(Operator):
11961198
"""
11971199
Class for Mathics3 built-in Infix Operators. Infix operators are
11981200
represented with an operator in between each argument. A common
@@ -1254,14 +1256,14 @@ def __init__(self, *args, **kwargs):
12541256
self.rules = default_rules
12551257

12561258

1257-
class NoMeaningInfixOperator(InfixOperator, ABC):
1259+
class NoMeaningInfixOperator(InfixOperator):
12581260
"""
12591261
Operators that have no pre-defined meaning are derived from this class.
12601262
"""
12611263

12621264
# This will be used to create a docstring
12631265
__doc_pattern__ = r"""
1264-
{operator_name} <url>
1266+
<url>
12651267
:WML link:
12661268
https://reference.wolfram.com/language/ref/{operator_name}.html</url>
12671269
@@ -1277,6 +1279,13 @@ class NoMeaningInfixOperator(InfixOperator, ABC):
12771279
= a {operator_string} b
12781280
12791281
"""
1282+
__formats_pattern__ = r"""{lbrace}
1283+
(
1284+
("InputForm", "OutputForm", "StandardForm"),
1285+
f"{operator_name}[args__]",
1286+
): (('Infix[{lbrace}args{rbrace}, {operator_string}"]'))
1287+
{rbrace}"""
1288+
12801289
attributes = A_NO_ATTRIBUTES
12811290
default_formats = False # Don't use any default format rules. Instead, see below.
12821291

@@ -1296,8 +1305,10 @@ def get_functions(self, prefix="eval", is_pymodule=False) -> List[Callable]:
12961305
return functions
12971306

12981307

1299-
# Has to come before PostFixOperator and PrefixOperator
1300-
class UnaryOperator(Operator, ABC):
1308+
# Has to come before PostfixOperator and PrefixOperator
1309+
# Note: Metaprogramming in mathics.builtin.no_meaning fails if
1310+
# we inherit from ABC
1311+
class UnaryOperator(Operator):
13011312
"""
13021313
Class for Unary Operators, (e.g. Not, Factorial)
13031314
"""
@@ -1321,7 +1332,9 @@ def __init__(self, format_function, *args, **kwargs):
13211332
self.formats[op_pattern] = form
13221333

13231334

1324-
class PostfixOperator(UnaryOperator, ABC):
1335+
# Note: Metaprogramming in mathics.builtin.no_meaning fails if
1336+
# we inherit from ABC
1337+
class PostfixOperator(UnaryOperator):
13251338
"""
13261339
Class for Builtin Postfix Unary Operators, e.g. Factorial (!)
13271340
"""
@@ -1330,7 +1343,40 @@ def __init__(self, *args, **kwargs):
13301343
super().__init__("Postfix", *args, **kwargs)
13311344

13321345

1333-
class PrefixOperator(UnaryOperator, ABC):
1346+
# Has to be after PostfixOperator
1347+
class NoMeaningPostfixOperator(PostfixOperator):
1348+
"""
1349+
Postfix Operators that have no pre-defined meaning are derived from this class.
1350+
"""
1351+
1352+
# This will be used to create a docstring
1353+
__doc_pattern__ = r"""
1354+
<url>
1355+
:WML link:
1356+
https://reference.wolfram.com/language/ref/{operator_name}.html</url>
1357+
1358+
<dl>
1359+
<dt>'{operator_name}[$x$]'
1360+
<dd>displays $x$ {operator_string}
1361+
</dl>
1362+
1363+
>> {operator_name}[x]
1364+
= x {operator_string}
1365+
1366+
>> x \[{operator_name}]
1367+
= x {operator_string}
1368+
1369+
"""
1370+
attributes = A_NO_ATTRIBUTES
1371+
default_formats = False # Don't use any default format rules. Instead, see below.
1372+
1373+
operator = "This should be overwritten"
1374+
summary_text = "This should be overwritten"
1375+
1376+
1377+
# Note: Metaprogramming in mathics.builtin.no_meaning fails if
1378+
# we inherit from ABC
1379+
class PrefixOperator(UnaryOperator):
13341380
"""
13351381
Class for Builtin Prefix Unary Operators, e.g. Not ("¬")
13361382
"""
@@ -1339,6 +1385,37 @@ def __init__(self, *args, **kwargs):
13391385
super().__init__("Prefix", *args, **kwargs)
13401386

13411387

1388+
# Has to be after PrefixOperator
1389+
class NoMeaningPrefixOperator(PrefixOperator):
1390+
"""
1391+
Prefix Operators that have no pre-defined meaning are derived from this class.
1392+
"""
1393+
1394+
# This will be used to create a docstring
1395+
__doc_pattern__ = r"""
1396+
<url>
1397+
:WML link:
1398+
https://reference.wolfram.com/language/ref/{operator_name}.html</url>
1399+
1400+
<dl>
1401+
<dt>'{operator_name}[$x$]'
1402+
<dd>displays {operator_string} $x$
1403+
</dl>
1404+
1405+
>> {operator_name}[x]
1406+
= {operator_string}x
1407+
1408+
>> \[{operator_name}]x
1409+
= {operator_string}x
1410+
1411+
"""
1412+
attributes = A_NO_ATTRIBUTES
1413+
default_formats = False # Don't use any default format rules. Instead, see below.
1414+
1415+
operator = "This should be overwritten"
1416+
summary_text = "This should be overwritten"
1417+
1418+
13421419
class PatternObject(BuiltinElement, BasePattern):
13431420
needs_verbatim = True
13441421

test/builtin/test_datentime.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,13 +136,13 @@ def test_private_doctests_datetime(str_expr, msgs, str_expected, fail_msg):
136136
[
137137
##
138138
(
139-
"TimeConstrained[Integrate[Sin[x]^100,x],.1.1]",
139+
"TimeConstrained[Integrate[Sin[x]^100,x],1.1]",
140140
None,
141141
"$Aborted",
142142
"TimeConstrained with two arguments",
143143
),
144144
(
145-
"TimeConstrained[Integrate[Sin[x]^100,x],.1.1, Integrate[Cos[x],x]]",
145+
"TimeConstrained[Integrate[Sin[x]^100,x],1.1, Integrate[Cos[x],x]]",
146146
None,
147147
"Sin[x]",
148148
"TimeConstrained with three arguments",

0 commit comments

Comments
 (0)