Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions mathics/builtin/assignments/assignment.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ class Set(InfixOperator):
"setraw": "Cannot assign to raw object `1`.",
"shape": "Lists `1` and `2` are not the same shape.",
}

has_side_effects = True
summary_text = "assign a value"

def eval(self, lhs, rhs, evaluation):
Expand Down Expand Up @@ -258,7 +258,7 @@ class TagSet(Builtin):
"""

attributes = A_HOLD_ALL | A_PROTECTED | A_SEQUENCE_HOLD

has_side_effects = True
messages = {
"tagnfd": "Tag `1` not found or too deep for an assigned rule.",
}
Expand Down Expand Up @@ -353,6 +353,7 @@ class UpSet(InfixOperator):
"""

attributes = A_HOLD_FIRST | A_PROTECTED | A_SEQUENCE_HOLD
has_side_effects = True
grouping = "Right"

summary_text = (
Expand Down
5 changes: 4 additions & 1 deletion mathics/builtin/attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ class ClearAttributes(Builtin):
"""

attributes = A_HOLD_FIRST | A_PROTECTED
has_side_effects = True
summary_text = "clear the attributes of a symbol"

def eval(self, symbols, attributes, evaluation):
Expand Down Expand Up @@ -526,6 +527,7 @@ class Protect(Builtin):
"""

attributes = A_HOLD_ALL | A_PROTECTED
has_side_effects = True
summary_text = "protect a symbol against redefinitions"

def eval(self, symbols, evaluation):
Expand Down Expand Up @@ -696,7 +698,7 @@ class SetAttributes(Builtin):
"""

attributes = A_HOLD_FIRST | A_PROTECTED

has_side_effects = True
messages = {
"unknownattr": f"`1` should be one of {', '.join(attribute_string_to_number.keys())}"
}
Expand Down Expand Up @@ -752,6 +754,7 @@ class Unprotect(Builtin):
"""

attributes = A_HOLD_ALL | A_PROTECTED
has_side_effects = True
summary_text = "remove protection against redefinitions"

def eval(self, symbols, evaluation):
Expand Down
14 changes: 9 additions & 5 deletions mathics/builtin/procedural.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ class CompoundExpression(InfixOperator):
"""

attributes = A_HOLD_ALL | A_PROTECTED | A_READ_PROTECTED

has_side_effects = True
summary_text = "execute expressions in sequence"

def eval(self, expr, evaluation):
Expand Down Expand Up @@ -346,6 +346,7 @@ class For(Builtin):
"""

attributes = A_HOLD_REST | A_PROTECTED
has_side_effects = True
rules = {
"For[start_, test_, incr_]": "For[start, test, incr, Null]",
}
Expand Down Expand Up @@ -486,6 +487,7 @@ class Interrupt(Builtin):
| a
= $Aborted
"""
has_side_effects = True

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

has_side_effects = True
messages = {
"numnm": (
"Non-negative machine-sized number expected at " "position 1 in `1`."
Expand Down Expand Up @@ -563,7 +566,7 @@ class Return(Builtin):
rules = {
"Return[]": "Return[Null]",
}

has_side_effects = True
summary_text = "return from a function"

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

summary_text = "switch based on a value, with patterns allowed"
attributes = A_HOLD_REST | A_PROTECTED

has_side_effects = True
messages = {
"argct": (
"Switch called with `2` arguments. "
Expand Down Expand Up @@ -659,7 +662,7 @@ class Throw(Builtin):
# Set checking that the number of arguments required is one or two. WMA uses 1..3.
eval_error = Builtin.generic_argument_error
expected_args = (1, 2)

has_side_effects = True
messages = {
"nocatch": "Uncaught `1` returned to top level.",
}
Expand Down Expand Up @@ -714,6 +717,7 @@ class Which(SympyFunction):
"""

attributes = A_HOLD_ALL | A_PROTECTED
has_side_effects = True
summary_text = "test which of a sequence of conditions are true"

def eval(self, items, evaluation):
Expand Down Expand Up @@ -773,7 +777,7 @@ class While(Builtin):
# Set checking that the number of arguments required is one.
eval_error = Builtin.generic_argument_error
expected_args = (1, 2)

has_side_effects = True
rules = {
"While[test_]": "While[test, Null]",
}
Expand Down
12 changes: 7 additions & 5 deletions mathics/builtin/scoping.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ class Begin(Builtin):
## = Global`test`
"""

has_side_effects = True
rules = {
"Begin[context_String]": """
Unprotect[System`Private`$ContextStack];
Expand Down Expand Up @@ -128,7 +129,7 @@ class BeginPackage(Builtin):
## >> BeginPackage["test`"]
## = test`
"""

has_side_effects = True
messages = {"unimpl": "The second argument to BeginPackage is not yet implemented."}

rules = {
Expand Down Expand Up @@ -187,7 +188,7 @@ class Block(Builtin):
"""

attributes = A_HOLD_ALL | A_PROTECTED

has_side_effects = True
messages = {
"lvsym": (
"Local variable specification contains `1`, "
Expand Down Expand Up @@ -351,6 +352,7 @@ class End(Builtin):
</dl>
"""

has_side_effects = True
messages = {
"noctx": "No previous context defined.",
}
Expand Down Expand Up @@ -385,7 +387,7 @@ class EndPackage(Builtin):
After 'EndPackage', the values of '\$Context' and '\$ContextPath' at the \
time of the 'BeginPackage' call are restored, with the new package\'s context prepended to '\$ContextPath'.
"""

has_side_effects = True
messages = {
"noctx": "No previous context defined.",
}
Expand Down Expand Up @@ -450,7 +452,7 @@ class Module(Builtin):
"""

attributes = A_HOLD_ALL | A_PROTECTED

has_side_effects = True
messages = {
"lvsym": (
"Local variable specification contains `1`, "
Expand Down Expand Up @@ -710,7 +712,7 @@ class With(Builtin):
"""

attributes = A_HOLD_ALL | A_PROTECTED

has_side_effects = True
messages = {
"lvsym": (
"Local variable specification contains `1`, "
Expand Down
13 changes: 13 additions & 0 deletions mathics/core/builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,11 @@ def eval_with_options(x, evaluation: Evaluation, options: dict):
expected_args: Union[int, Tuple[int, int], range] = -1

formats: Dict[str, Any] = {}

# If the symbol include rules involving loops,
# setting values, or creating/changing contexts.
has_side_effects: bool = False

messages: Dict[str, Any] = {}
name: Optional[str] = None
options: Dict[str, Any] = {}
Expand Down Expand Up @@ -434,6 +439,13 @@ def contextify_form_name(f):
else:
definitions.builtin[name] = definition

# If the definition has side effects, store it in a
# dictionary.
if self.has_side_effects:
from mathics.core.definitions import SIDE_EFFECT_BUILTINS

SIDE_EFFECT_BUILTINS[name] = definition

makeboxes_def = definitions.builtin["System`MakeBoxes"]
for rule in box_rules:
makeboxes_def.add_rule(rule)
Expand Down Expand Up @@ -1024,6 +1036,7 @@ def get_name(cls, short=False) -> str:
class IterationFunction(Builtin, ABC):
attributes = A_HOLD_ALL | A_PROTECTED
allow_loopcontrol = False
has_side_effects = True
throw_iterb = True

def get_result(self, elements, is_uniform=False) -> Expression:
Expand Down
29 changes: 29 additions & 0 deletions mathics/core/convert/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from typing import Callable, Optional, Tuple

from mathics.core.definitions import SIDE_EFFECT_BUILTINS, Definition
from mathics.core.element import BaseElement
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression, from_python
from mathics.core.symbols import Symbol, SymbolFalse, SymbolTrue
Expand Down Expand Up @@ -44,6 +46,32 @@ def __init__(self, var):
self.var = var


def evaluate_without_side_effects(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this is the right place for this function.

expr: BaseElement, evaluation: Evaluation
) -> BaseElement:
"""
Evaluate an expression leaving unevaluated subexpressions
related with side-effects (assignments, loops).
"""
definitions = evaluation.definitions
# Temporarily remove the builtin definitions
# of symbols with side effects
for name, defin in SIDE_EFFECT_BUILTINS.items():
# Change the definition by a temporal definition setting
# just the name and the attributes.
definitions.builtin[name] = Definition(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe these definitions can be stored in some place, and then each time we compile something, bring them up, instead of constructing them each time.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two obvious places to start alternate representations or approximate computations of an expression (where a symbol is considered to be an expression) are either inside the compound Expression object or inside a definitions entry.

I suspect we'll need to make a pass over the definitions table at some point. As with so many other things, it is one of those dark areas where, I suspect, things are a bit unconventional from both the conventional compiler standpoint as well as how WMA thinks of scoping.

name, attributes=defin.attributes, builtin=defin.builtin
)
definitions.clear_cache(name)
try:
result = expr.evaluate(evaluation)
finally:
# Restore the definitions
for name, defin in SIDE_EFFECT_BUILTINS.items():
definitions.builtin[name] = defin
return result


def expression_to_callable(
expr: Expression,
args: Optional[list] = None,
Expand All @@ -56,6 +84,7 @@ def expression_to_callable(
args: a list of CompileArg elements
evaluation: an Evaluation object used if the llvm compilation fails
"""
expr = evaluate_without_side_effects(expr, evaluation)
try:
cfunc = _compile(expr, args) if (use_llvm and args is not None) else None
except CompileError:
Expand Down
6 changes: 6 additions & 0 deletions mathics/core/definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ def __repr__(self) -> str:
return repr_str


# Dictionary of builtin definitions involving side effects
# like setting values, changing contexts or running loops:

SIDE_EFFECT_BUILTINS: Dict[str, Definition] = {}


class Definitions:
"""The state of one instance of the Mathics3 interpreter is stored in this object.

Expand Down
3 changes: 2 additions & 1 deletion mathics/eval/drawing/plot_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import scipy
import sympy

from mathics.core.convert.function import evaluate_without_side_effects
from mathics.core.convert.sympy import SympyExpression
from mathics.core.symbols import strip_context
from mathics.core.util import print_expression_tree, print_sympy_tree
Expand Down Expand Up @@ -54,7 +55,7 @@ def plot_compile(evaluation, expr, names, debug=0):
# because some functions are not themselves sympy-enabled
# if they always get rewritten to one that is.
try:
new_expr = expr.evaluate(evaluation)
new_expr = eval_without_side_effects(expr, evaluation)
if new_expr:
expr = new_expr
except Exception:
Expand Down
Loading