Skip to content

Commit 45c302b

Browse files
committed
adding evaluate_without_side_effects for helping compile expressions.
1 parent 1f5dbb2 commit 45c302b

File tree

8 files changed

+73
-14
lines changed

8 files changed

+73
-14
lines changed

mathics/builtin/assignments/assignment.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ class Set(InfixOperator):
134134
"setraw": "Cannot assign to raw object `1`.",
135135
"shape": "Lists `1` and `2` are not the same shape.",
136136
}
137-
137+
has_side_effects = True
138138
summary_text = "assign a value"
139139

140140
def eval(self, lhs, rhs, evaluation):
@@ -258,7 +258,7 @@ class TagSet(Builtin):
258258
"""
259259

260260
attributes = A_HOLD_ALL | A_PROTECTED | A_SEQUENCE_HOLD
261-
261+
has_side_effects = True
262262
messages = {
263263
"tagnfd": "Tag `1` not found or too deep for an assigned rule.",
264264
}
@@ -353,6 +353,7 @@ class UpSet(InfixOperator):
353353
"""
354354

355355
attributes = A_HOLD_FIRST | A_PROTECTED | A_SEQUENCE_HOLD
356+
has_side_effects = True
356357
grouping = "Right"
357358

358359
summary_text = (

mathics/builtin/attributes.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ class ClearAttributes(Builtin):
126126
"""
127127

128128
attributes = A_HOLD_FIRST | A_PROTECTED
129+
has_side_effects = True
129130
summary_text = "clear the attributes of a symbol"
130131

131132
def eval(self, symbols, attributes, evaluation):
@@ -526,6 +527,7 @@ class Protect(Builtin):
526527
"""
527528

528529
attributes = A_HOLD_ALL | A_PROTECTED
530+
has_side_effects = True
529531
summary_text = "protect a symbol against redefinitions"
530532

531533
def eval(self, symbols, evaluation):
@@ -696,7 +698,7 @@ class SetAttributes(Builtin):
696698
"""
697699

698700
attributes = A_HOLD_FIRST | A_PROTECTED
699-
701+
has_side_effects = True
700702
messages = {
701703
"unknownattr": f"`1` should be one of {', '.join(attribute_string_to_number.keys())}"
702704
}
@@ -752,6 +754,7 @@ class Unprotect(Builtin):
752754
"""
753755

754756
attributes = A_HOLD_ALL | A_PROTECTED
757+
has_side_effects = True
755758
summary_text = "remove protection against redefinitions"
756759

757760
def eval(self, symbols, evaluation):

mathics/builtin/procedural.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ class CompoundExpression(InfixOperator):
219219
"""
220220

221221
attributes = A_HOLD_ALL | A_PROTECTED | A_READ_PROTECTED
222-
222+
has_side_effects = True
223223
summary_text = "execute expressions in sequence"
224224

225225
def eval(self, expr, evaluation):
@@ -346,6 +346,7 @@ class For(Builtin):
346346
"""
347347

348348
attributes = A_HOLD_REST | A_PROTECTED
349+
has_side_effects = True
349350
rules = {
350351
"For[start_, test_, incr_]": "For[start, test, incr, Null]",
351352
}
@@ -486,6 +487,7 @@ class Interrupt(Builtin):
486487
| a
487488
= $Aborted
488489
"""
490+
has_side_effects = True
489491

490492
# Set checking that the no arguments are allowed.
491493
# eval_error = Builtin.generic_argument_error
@@ -511,6 +513,7 @@ class Pause(Builtin):
511513
>> Pause[0.5]
512514
"""
513515

516+
has_side_effects = True
514517
messages = {
515518
"numnm": (
516519
"Non-negative machine-sized number expected at " "position 1 in `1`."
@@ -563,7 +566,7 @@ class Return(Builtin):
563566
rules = {
564567
"Return[]": "Return[Null]",
565568
}
566-
569+
has_side_effects = True
567570
summary_text = "return from a function"
568571

569572
def eval(self, expr, evaluation: Evaluation): # pylint: disable=unused-argument
@@ -604,7 +607,7 @@ class Switch(Builtin):
604607

605608
summary_text = "switch based on a value, with patterns allowed"
606609
attributes = A_HOLD_REST | A_PROTECTED
607-
610+
has_side_effects = True
608611
messages = {
609612
"argct": (
610613
"Switch called with `2` arguments. "
@@ -659,7 +662,7 @@ class Throw(Builtin):
659662
# Set checking that the number of arguments required is one or two. WMA uses 1..3.
660663
eval_error = Builtin.generic_argument_error
661664
expected_args = (1, 2)
662-
665+
has_side_effects = True
663666
messages = {
664667
"nocatch": "Uncaught `1` returned to top level.",
665668
}
@@ -714,6 +717,7 @@ class Which(SympyFunction):
714717
"""
715718

716719
attributes = A_HOLD_ALL | A_PROTECTED
720+
has_side_effects = True
717721
summary_text = "test which of a sequence of conditions are true"
718722

719723
def eval(self, items, evaluation):
@@ -773,7 +777,7 @@ class While(Builtin):
773777
# Set checking that the number of arguments required is one.
774778
eval_error = Builtin.generic_argument_error
775779
expected_args = (1, 2)
776-
780+
has_side_effects = True
777781
rules = {
778782
"While[test_]": "While[test, Null]",
779783
}

mathics/builtin/scoping.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ class Begin(Builtin):
9999
## = Global`test`
100100
"""
101101

102+
has_side_effects = True
102103
rules = {
103104
"Begin[context_String]": """
104105
Unprotect[System`Private`$ContextStack];
@@ -128,7 +129,7 @@ class BeginPackage(Builtin):
128129
## >> BeginPackage["test`"]
129130
## = test`
130131
"""
131-
132+
has_side_effects = True
132133
messages = {"unimpl": "The second argument to BeginPackage is not yet implemented."}
133134

134135
rules = {
@@ -187,7 +188,7 @@ class Block(Builtin):
187188
"""
188189

189190
attributes = A_HOLD_ALL | A_PROTECTED
190-
191+
has_side_effects = True
191192
messages = {
192193
"lvsym": (
193194
"Local variable specification contains `1`, "
@@ -351,6 +352,7 @@ class End(Builtin):
351352
</dl>
352353
"""
353354

355+
has_side_effects = True
354356
messages = {
355357
"noctx": "No previous context defined.",
356358
}
@@ -385,7 +387,7 @@ class EndPackage(Builtin):
385387
After 'EndPackage', the values of '\$Context' and '\$ContextPath' at the \
386388
time of the 'BeginPackage' call are restored, with the new package\'s context prepended to '\$ContextPath'.
387389
"""
388-
390+
has_side_effects = True
389391
messages = {
390392
"noctx": "No previous context defined.",
391393
}
@@ -450,7 +452,7 @@ class Module(Builtin):
450452
"""
451453

452454
attributes = A_HOLD_ALL | A_PROTECTED
453-
455+
has_side_effects = True
454456
messages = {
455457
"lvsym": (
456458
"Local variable specification contains `1`, "
@@ -710,7 +712,7 @@ class With(Builtin):
710712
"""
711713

712714
attributes = A_HOLD_ALL | A_PROTECTED
713-
715+
has_side_effects = True
714716
messages = {
715717
"lvsym": (
716718
"Local variable specification contains `1`, "

mathics/core/builtin.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,11 @@ def eval_with_options(x, evaluation: Evaluation, options: dict):
207207
expected_args: Union[int, Tuple[int, int], range] = -1
208208

209209
formats: Dict[str, Any] = {}
210+
211+
# If the symbol include rules involving loops,
212+
# setting values, or creating/changing contexts.
213+
has_side_effects: bool = False
214+
210215
messages: Dict[str, Any] = {}
211216
name: Optional[str] = None
212217
options: Dict[str, Any] = {}
@@ -434,6 +439,13 @@ def contextify_form_name(f):
434439
else:
435440
definitions.builtin[name] = definition
436441

442+
# If the definition has side effects, store it in a
443+
# dictionary.
444+
if self.has_side_effects:
445+
from mathics.core.definitions import SIDE_EFFECT_BUILTINS
446+
447+
SIDE_EFFECT_BUILTINS[name] = definition
448+
437449
makeboxes_def = definitions.builtin["System`MakeBoxes"]
438450
for rule in box_rules:
439451
makeboxes_def.add_rule(rule)
@@ -1024,6 +1036,7 @@ def get_name(cls, short=False) -> str:
10241036
class IterationFunction(Builtin, ABC):
10251037
attributes = A_HOLD_ALL | A_PROTECTED
10261038
allow_loopcontrol = False
1039+
has_side_effects = True
10271040
throw_iterb = True
10281041

10291042
def get_result(self, elements, is_uniform=False) -> Expression:

mathics/core/convert/function.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from typing import Callable, Optional, Tuple
44

5+
from mathics.core.definitions import SIDE_EFFECT_BUILTINS, Definition
6+
from mathics.core.element import BaseElement
57
from mathics.core.evaluation import Evaluation
68
from mathics.core.expression import Expression, from_python
79
from mathics.core.symbols import Symbol, SymbolFalse, SymbolTrue
@@ -44,6 +46,32 @@ def __init__(self, var):
4446
self.var = var
4547

4648

49+
def evaluate_without_side_effects(
50+
expr: BaseElement, evaluation: Evaluation
51+
) -> BaseElement:
52+
"""
53+
Evaluate an expression leaving unevaluated subexpressions
54+
related with side-effects (assignments, loops).
55+
"""
56+
definitions = evaluation.definitions
57+
# Temporarily remove the builtin definitions
58+
# of symbols with side effects
59+
for name, defin in SIDE_EFFECT_BUILTINS.items():
60+
# Change the definition by a temporal definition setting
61+
# just the name and the attributes.
62+
definitions.builtin[name] = Definition(
63+
name, attributes=defin.attributes, builtin=defin.builtin
64+
)
65+
definitions.clear_cache(name)
66+
try:
67+
result = expr.evaluate(evaluation)
68+
finally:
69+
# Restore the definitions
70+
for name, defin in SIDE_EFFECT_BUILTINS.items():
71+
definitions.builtin[name] = defin
72+
return result
73+
74+
4775
def expression_to_callable(
4876
expr: Expression,
4977
args: Optional[list] = None,
@@ -56,6 +84,7 @@ def expression_to_callable(
5684
args: a list of CompileArg elements
5785
evaluation: an Evaluation object used if the llvm compilation fails
5886
"""
87+
expr = evaluate_without_side_effects(expr, evaluation)
5988
try:
6089
cfunc = _compile(expr, args) if (use_llvm and args is not None) else None
6190
except CompileError:

mathics/core/definitions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,12 @@ def __repr__(self) -> str:
121121
return repr_str
122122

123123

124+
# Dictionary of builtin definitions involving side effects
125+
# like setting values, changing contexts or running loops:
126+
127+
SIDE_EFFECT_BUILTINS: Dict[str, Definition] = {}
128+
129+
124130
class Definitions:
125131
"""The state of one instance of the Mathics3 interpreter is stored in this object.
126132

mathics/eval/drawing/plot_compile.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import scipy
1919
import sympy
2020

21+
from mathics.core.convert.function import evaluate_without_side_effects
2122
from mathics.core.convert.sympy import SympyExpression
2223
from mathics.core.symbols import strip_context
2324
from mathics.core.util import print_expression_tree, print_sympy_tree
@@ -54,7 +55,7 @@ def plot_compile(evaluation, expr, names, debug=0):
5455
# because some functions are not themselves sympy-enabled
5556
# if they always get rewritten to one that is.
5657
try:
57-
new_expr = expr.evaluate(evaluation)
58+
new_expr = eval_without_side_effects(expr, evaluation)
5859
if new_expr:
5960
expr = new_expr
6061
except Exception:

0 commit comments

Comments
 (0)