diff --git a/mathics/builtin/assignments/assignment.py b/mathics/builtin/assignments/assignment.py
index 856942f18..7f3008bf1 100644
--- a/mathics/builtin/assignments/assignment.py
+++ b/mathics/builtin/assignments/assignment.py
@@ -4,8 +4,14 @@
"""
-from mathics.builtin.assignments.internals import _SetOperator
from mathics.builtin.base import BinaryOperator, Builtin
+from mathics.core.assignment import (
+ ASSIGNMENT_FUNCTION_MAP,
+ AssignmentException,
+ assign_store_rules_by_tag,
+ normalize_lhs,
+)
+
from mathics.core.attributes import (
A_HOLD_ALL,
A_HOLD_FIRST,
@@ -18,6 +24,39 @@
from mathics.core.systemsymbols import SymbolFailed
+class _SetOperator:
+ """
+ This is the base class for assignment Builtin operators.
+
+ Special cases are determined by the head of the expression. Then
+ they are processed by specific routines, which are poke from
+ the ``ASSIGNMENT_FUNCTION_MAP`` dict.
+ """
+
+ # FIXME:
+ # Assigment is determined by the LHS.
+ # Are there a larger patterns or natural groupings that we are missing?
+ # For example, it might be that it
+ # we can key off of some attributes or other properties of the
+ # LHS of a builtin, instead of listing all of the builtins in that class
+ # (which may miss some).
+ # Below, we key on a string, but Symbol is more correct.
+
+ def assign(self, lhs, rhs, evaluation, tags=None, upset=False):
+ lhs, lookup_name = normalize_lhs(lhs, evaluation)
+ try:
+ # Using a builtin name, find which assignment procedure to perform,
+ # and then call that function.
+ assignment_func = ASSIGNMENT_FUNCTION_MAP.get(lookup_name, None)
+ if assignment_func:
+ return assignment_func(self, lhs, rhs, evaluation, tags, upset)
+
+ return assign_store_rules_by_tag(self, lhs, rhs, evaluation, tags, upset)
+ except AssignmentException:
+
+ return False
+
+
class Set(BinaryOperator, _SetOperator):
"""
diff --git a/mathics/builtin/assignments/clear.py b/mathics/builtin/assignments/clear.py
index e46dd3ab3..8dbda4ff5 100644
--- a/mathics/builtin/assignments/clear.py
+++ b/mathics/builtin/assignments/clear.py
@@ -38,10 +38,9 @@
SymbolUpValues,
)
+from mathics.core.assignment import is_protected
from mathics.core.atoms import String
-from mathics.builtin.assignments.internals import is_protected
-
class Clear(Builtin):
"""
diff --git a/mathics/builtin/assignments/types.py b/mathics/builtin/assignments/types.py
index 6a5ebb99a..3d340f989 100644
--- a/mathics/builtin/assignments/types.py
+++ b/mathics/builtin/assignments/types.py
@@ -8,8 +8,7 @@
from mathics.builtin.base import Builtin
-from mathics.builtin.assignments.internals import get_symbol_values
-
+from mathics.core.assignment import get_symbol_values
from mathics.core.attributes import A_HOLD_ALL, A_PROTECTED
diff --git a/mathics/builtin/assignments/upvalues.py b/mathics/builtin/assignments/upvalues.py
index 817a74272..7cecd29cc 100644
--- a/mathics/builtin/assignments/upvalues.py
+++ b/mathics/builtin/assignments/upvalues.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
-from mathics.builtin.assignments.internals import _SetOperator
+from mathics.builtin.assignments.assignment import _SetOperator
from mathics.builtin.base import BinaryOperator
from mathics.core.attributes import (
A_HOLD_ALL,
diff --git a/mathics/builtin/atomic/symbols.py b/mathics/builtin/atomic/symbols.py
index 753e4e649..fe02ed14e 100644
--- a/mathics/builtin/atomic/symbols.py
+++ b/mathics/builtin/atomic/symbols.py
@@ -7,7 +7,6 @@
import re
-from mathics.builtin.assignments.internals import get_symbol_values
from mathics.builtin.base import (
Builtin,
PrefixOperator,
@@ -16,6 +15,7 @@
from mathics.builtin.atomic.strings import to_regex
+from mathics.core.assignment import get_symbol_values
from mathics.core.atoms import String
from mathics.core.attributes import (
diff --git a/mathics/builtin/attributes.py b/mathics/builtin/attributes.py
index 5c161ecae..70a0a3777 100644
--- a/mathics/builtin/attributes.py
+++ b/mathics/builtin/attributes.py
@@ -15,7 +15,7 @@
from mathics.builtin.base import Predefined, Builtin
-from mathics.builtin.assignments.internals import get_symbol_list
+from mathics.core.assignment import get_symbol_list
from mathics.core.atoms import String
from mathics.core.attributes import (
attributes_bitset_to_list,
diff --git a/mathics/builtin/scoping.py b/mathics/builtin/scoping.py
index 3c76731f9..1d251bfd8 100644
--- a/mathics/builtin/scoping.py
+++ b/mathics/builtin/scoping.py
@@ -4,13 +4,13 @@
"""
-from mathics.builtin.assignments.internals import get_symbol_list
from mathics.core.attributes import (
A_HOLD_ALL,
A_PROTECTED,
attribute_string_to_number,
)
from mathics.builtin.base import Builtin, Predefined
+from mathics.core.assignment import get_symbol_list
from mathics.core.atoms import (
String,
Integer,
diff --git a/mathics/builtin/assignments/internals.py b/mathics/core/assignment.py
similarity index 91%
rename from mathics/builtin/assignments/internals.py
rename to mathics/core/assignment.py
index 64588d72e..90ca57cb9 100644
--- a/mathics/builtin/assignments/internals.py
+++ b/mathics/core/assignment.py
@@ -1,4 +1,7 @@
# -*- coding: utf-8 -*-
+"""
+Support for Set and SetDelayed, and other assignment-like builtins
+"""
from typing import Optional, Tuple
@@ -12,6 +15,7 @@
from mathics.core.symbols import (
Symbol,
SymbolFalse,
+ SymbolList,
SymbolMinPrecision,
SymbolMaxPrecision,
SymbolN,
@@ -25,6 +29,7 @@
SymbolHoldPattern,
SymbolMachinePrecision,
SymbolOptionValue,
+ SymbolPart,
SymbolPattern,
SymbolRuleDelayed,
)
@@ -122,6 +127,32 @@ def is_protected(tag, defin):
return A_PROTECTED & defin.get_attributes(tag)
+def normalize_lhs(lhs, evaluation):
+ """
+ Process the lhs in a way that
+ * if it is a conditional expression, reduce it to
+ a shallow conditional expression
+ ( Conditional[Conditional[...],tst] -> Conditional[stripped_lhs, tst])
+ with `stripped_lhs` the result of strip all the conditions from lhs.
+ * if ``stripped_lhs`` is not a ``List`` or a ``Part`` expression, evaluate the
+ elements.
+
+ returns a tuple with the normalized lhs, and the lookup_name of the head in stripped_lhs.
+ """
+ cond = None
+ if lhs.get_head() is SymbolCondition:
+ lhs, cond = unroll_conditions(lhs)
+
+ lookup_name = lhs.get_lookup_name()
+ # In WMA, before the assignment, the elements of the (stripped) LHS are evaluated.
+ if isinstance(lhs, Expression) and lhs.get_head() not in (SymbolList, SymbolPart):
+ lhs = lhs.evaluate_elements(evaluation)
+ # If there was a conditional expression, rebuild it with the processed lhs
+ if cond:
+ lhs = Expression(cond.get_head(), lhs, cond.elements[1])
+ return lhs, lookup_name
+
+
def repl_pattern_by_symbol(expr):
elements = expr.get_elements()
if len(elements) == 0:
@@ -237,72 +268,47 @@ def unroll_conditions(lhs) -> Tuple[BaseElement, Optional[Expression]]:
# maybe they should be member functions of _SetOperator.
-def process_assign_recursion_limit(lhs, rhs, evaluation):
- """
- Set ownvalue for the $RecursionLimit symbol.
- """
- rhs_int_value = rhs.get_int_value()
- # if (not rhs_int_value or rhs_int_value < 20) and not
- # rhs.get_name() == 'System`Infinity':
- if (
- not rhs_int_value or rhs_int_value < 20 or rhs_int_value > MAX_RECURSION_DEPTH
- ): # nopep8
-
- evaluation.message("$RecursionLimit", "limset", rhs)
- raise AssignmentException(lhs, None)
- try:
- set_python_recursion_limit(rhs_int_value)
- except OverflowError:
- # TODO: Message
- raise AssignmentException(lhs, None)
- return False
-
-
-def process_assign_iteration_limit(lhs, rhs, evaluation):
- """
- Set ownvalue for the $IterationLimit symbol.
- """
-
- rhs_int_value = rhs.get_int_value()
- if (
- not rhs_int_value or rhs_int_value < 20
- ) and not rhs.get_name() == "System`Infinity":
- evaluation.message("$IterationLimit", "limset", rhs)
- raise AssignmentException(lhs, None)
- return False
-
-
-def process_assign_module_number(lhs, rhs, evaluation):
+def process_assign_attributes(self, lhs, rhs, evaluation, tags, upset):
"""
- Set ownvalue for the $ModuleNumber symbol.
+ Process the case where lhs is of the form
+ `Attribute[symbol]`
"""
- rhs_int_value = rhs.get_int_value()
- if not rhs_int_value or rhs_int_value <= 0:
- evaluation.message("$ModuleNumber", "set", rhs)
- raise AssignmentException(lhs, None)
- return False
-
+ name = lhs.get_head_name()
+ if len(lhs.elements) != 1:
+ evaluation.message_args(name, len(lhs.elements), 1)
+ raise AssignmentException(lhs, rhs)
+ tag = lhs.elements[0].get_name()
+ if not tag:
+ evaluation.message(name, "sym", lhs.elements[0], 1)
+ raise AssignmentException(lhs, rhs)
+ if tags is not None and tags != [tag]:
+ evaluation.message(name, "tag", Symbol(name), Symbol(tag))
+ raise AssignmentException(lhs, rhs)
+ attributes_list = get_symbol_list(
+ rhs, lambda item: evaluation.message(name, "sym", item, 1)
+ )
+ if attributes_list is None:
+ raise AssignmentException(lhs, rhs)
+ if A_LOCKED & evaluation.definitions.get_attributes(tag):
+ evaluation.message(name, "locked", Symbol(tag))
+ raise AssignmentException(lhs, rhs)
-def process_assign_line_number_and_history_length(
- self, lhs, rhs, evaluation, tags, upset
-):
- """
- Set ownvalue for the $Line and $HistoryLength symbols.
- """
+ def reduce_attributes_from_list(x: int, y: str) -> int:
+ try:
+ return x | attribute_string_to_number[y]
+ except KeyError:
+ evaluation.message("SetAttributes", "unknowattr", y)
+ return x
- lhs_name = lhs.get_name()
- rhs_int_value = rhs.get_int_value()
- if rhs_int_value is None or rhs_int_value < 0:
- evaluation.message(lhs_name, "intnn", rhs)
- raise AssignmentException(lhs, None)
- return False
+ attributes = reduce(
+ reduce_attributes_from_list,
+ attributes_list,
+ 0,
+ )
+ evaluation.definitions.set_attributes(tag, attributes)
-def process_assign_random_state(self, lhs, rhs, evaluation, tags, upset):
- # TODO: allow setting of legal random states!
- # (but consider pickle's insecurity!)
- evaluation.message("$RandomState", "rndst", rhs)
- raise AssignmentException(lhs, None)
+ return True
def process_assign_context(self, lhs, rhs, evaluation, tags, upset):
@@ -348,6 +354,142 @@ def process_assign_context_path(self, lhs, rhs, evaluation, tags, upset):
raise AssignmentException(lhs, None)
+def process_assign_default(self, lhs, rhs, evaluation, tags, upset):
+ lhs, condition = unroll_conditions(lhs)
+ lhs, rhs = unroll_patterns(lhs, rhs, evaluation)
+ count = 0
+ defs = evaluation.definitions
+
+ if len(lhs.elements) not in (1, 2, 3):
+ evaluation.message_args(SymbolDefault, len(lhs.elements), 1, 2, 3)
+ raise AssignmentException(lhs, None)
+ focus = lhs.elements[0]
+ tags = process_tags_and_upset_dont_allow_custom(
+ tags, upset, self, lhs, focus, evaluation
+ )
+ lhs, rhs = process_rhs_conditions(lhs, rhs, condition, evaluation)
+ rule = Rule(lhs, rhs)
+ for tag in tags:
+ if rejected_because_protected(self, lhs, tag, evaluation):
+ continue
+ count += 1
+ defs.add_default(tag, rule)
+ return count > 0
+
+
+def process_assign_definition_values(self, lhs, rhs, evaluation, tags, upset):
+ name = lhs.get_head_name()
+ tag = find_tag_and_check(lhs, tags, evaluation)
+ rules = rhs.get_rules_list()
+ if rules is None:
+ evaluation.message(name, "vrule", lhs, rhs)
+ raise AssignmentException(lhs, None)
+ evaluation.definitions.set_values(tag, name, rules)
+ return True
+
+
+def process_assign_format(self, lhs, rhs, evaluation, tags, upset):
+ lhs, condition = unroll_conditions(lhs)
+ lhs, rhs = unroll_patterns(lhs, rhs, evaluation)
+ count = 0
+ defs = evaluation.definitions
+
+ if len(lhs.elements) not in (1, 2):
+ evaluation.message_args("Format", len(lhs.elements), 1, 2)
+ raise AssignmentException(lhs, None)
+ if len(lhs.elements) == 2:
+ form = lhs.elements[1]
+ form_name = form.get_name()
+ if not form_name:
+ evaluation.message("Format", "fttp", lhs.elements[1])
+ raise AssignmentException(lhs, None)
+ # If the form is not in defs.printforms / defs.outputforms
+ # add it.
+ for form_list in (defs.outputforms, defs.printforms):
+ if form not in form_list:
+ form_list.append(form)
+ else:
+ form_name = [
+ "System`StandardForm",
+ "System`TraditionalForm",
+ "System`OutputForm",
+ "System`TeXForm",
+ "System`MathMLForm",
+ ]
+ lhs = focus = lhs.elements[0]
+ tags = process_tags_and_upset_dont_allow_custom(
+ tags, upset, self, lhs, focus, evaluation
+ )
+ lhs, rhs = process_rhs_conditions(lhs, rhs, condition, evaluation)
+ rule = Rule(lhs, rhs)
+ for tag in tags:
+ if rejected_because_protected(self, lhs, tag, evaluation):
+ continue
+ count += 1
+ defs.add_format(tag, rule, form_name)
+ return count > 0
+
+
+def process_assign_iteration_limit(lhs, rhs, evaluation):
+ """
+ Set ownvalue for the $IterationLimit symbol.
+ """
+
+ rhs_int_value = rhs.get_int_value()
+ if (
+ not rhs_int_value or rhs_int_value < 20
+ ) and not rhs.get_name() == "System`Infinity":
+ evaluation.message("$IterationLimit", "limset", rhs)
+ raise AssignmentException(lhs, None)
+ return False
+
+
+def process_assign_line_number_and_history_length(
+ self, lhs, rhs, evaluation, tags, upset
+):
+ """
+ Set ownvalue for the $Line and $HistoryLength symbols.
+ """
+
+ lhs_name = lhs.get_name()
+ rhs_int_value = rhs.get_int_value()
+ if rhs_int_value is None or rhs_int_value < 0:
+ evaluation.message(lhs_name, "intnn", rhs)
+ raise AssignmentException(lhs, None)
+ return False
+
+
+def process_assign_list(self, lhs, rhs, evaluation, tags, upset):
+ if not (
+ rhs.get_head_name() == "System`List" and len(lhs.elements) == len(rhs.elements)
+ ): # nopep8
+ evaluation.message(self.get_name(), "shape", lhs, rhs)
+ return False
+ result = True
+ for left, right in zip(lhs.elements, rhs.elements):
+ if not self.assign(left, right, evaluation):
+ result = False
+ return result
+
+
+def process_assign_makeboxes(self, lhs, rhs, evaluation, tags, upset):
+ # FIXME: the below is a big hack.
+ # Currently MakeBoxes boxing is implemented as a bunch of rules.
+ # See mathics.builtin.base contribute().
+ # I think we want to change this so it works like normal SetDelayed
+ # That is:
+ # MakeBoxes[CubeRoot, StandardForm] := RadicalBox[3, StandardForm]
+ # rather than:
+ # MakeBoxes[CubeRoot, StandardForm] -> RadicalBox[3, StandardForm]
+
+ makeboxes_rule = Rule(lhs, rhs, system=False)
+ definitions = evaluation.definitions
+ definitions.add_rule("System`MakeBoxes", makeboxes_rule, "down")
+ # makeboxes_defs = evaluation.definitions.builtin["System`MakeBoxes"]
+ # makeboxes_defs.add_rule(makeboxes_rule)
+ return True
+
+
def process_assign_minprecision(self, lhs, rhs, evaluation, tags, upset):
lhs_name = lhs.get_name()
rhs_int_value = rhs.get_int_value()
@@ -379,15 +521,38 @@ def process_assign_maxprecision(self, lhs, rhs, evaluation, tags, upset):
raise AssignmentException(lhs, None)
-def process_assign_definition_values(self, lhs, rhs, evaluation, tags, upset):
- name = lhs.get_head_name()
- tag = find_tag_and_check(lhs, tags, evaluation)
- rules = rhs.get_rules_list()
- if rules is None:
- evaluation.message(name, "vrule", lhs, rhs)
+def process_assign_messagename(self, lhs, rhs, evaluation, tags, upset):
+ lhs, condition = unroll_conditions(lhs)
+ lhs, rhs = unroll_patterns(lhs, rhs, evaluation)
+ count = 0
+ defs = evaluation.definitions
+ if len(lhs.elements) != 2:
+ evaluation.message_args("MessageName", len(lhs.elements), 2)
raise AssignmentException(lhs, None)
- evaluation.definitions.set_values(tag, name, rules)
- return True
+ focus = lhs.elements[0]
+ tags = process_tags_and_upset_dont_allow_custom(
+ tags, upset, self, lhs, focus, evaluation
+ )
+ lhs, rhs = process_rhs_conditions(lhs, rhs, condition, evaluation)
+ rule = Rule(lhs, rhs)
+ for tag in tags:
+ # Messages can be assigned even if the symbol is protected...
+ # if rejected_because_protected(self, lhs, tag, evaluation):
+ # continue
+ count += 1
+ defs.add_message(tag, rule)
+ return count > 0
+
+
+def process_assign_module_number(lhs, rhs, evaluation):
+ """
+ Set ownvalue for the $ModuleNumber symbol.
+ """
+ rhs_int_value = rhs.get_int_value()
+ if not rhs_int_value or rhs_int_value <= 0:
+ evaluation.message("$ModuleNumber", "set", rhs)
+ raise AssignmentException(lhs, None)
+ return False
def process_assign_options(self, lhs, rhs, evaluation, tags, upset):
@@ -415,7 +580,7 @@ def process_assign_options(self, lhs, rhs, evaluation, tags, upset):
def process_assign_numericq(self, lhs, rhs, evaluation, tags, upset):
- lhs, condition = unroll_conditions(lhs)
+ # lhs, condition = unroll_conditions(lhs)
lhs, rhs = unroll_patterns(lhs, rhs, evaluation)
if rhs not in (SymbolTrue, SymbolFalse):
evaluation.message("NumericQ", "set", lhs, rhs)
@@ -504,146 +669,7 @@ def process_assign_other(
return True, tags
-def process_assign_attributes(self, lhs, rhs, evaluation, tags, upset):
- """
- Process the case where lhs is of the form
- `Attribute[symbol]`
- """
- name = lhs.get_head_name()
- if len(lhs.elements) != 1:
- evaluation.message_args(name, len(lhs.elements), 1)
- raise AssignmentException(lhs, rhs)
- tag = lhs.elements[0].get_name()
- if not tag:
- evaluation.message(name, "sym", lhs.elements[0], 1)
- raise AssignmentException(lhs, rhs)
- if tags is not None and tags != [tag]:
- evaluation.message(name, "tag", Symbol(name), Symbol(tag))
- raise AssignmentException(lhs, rhs)
- attributes_list = get_symbol_list(
- rhs, lambda item: evaluation.message(name, "sym", item, 1)
- )
- if attributes_list is None:
- raise AssignmentException(lhs, rhs)
- if A_LOCKED & evaluation.definitions.get_attributes(tag):
- evaluation.message(name, "locked", Symbol(tag))
- raise AssignmentException(lhs, rhs)
-
- def reduce_attributes_from_list(x: int, y: str) -> int:
- try:
- return x | attribute_string_to_number[y]
- except KeyError:
- evaluation.message("SetAttributes", "unknowattr", y)
- return x
-
- attributes = reduce(
- reduce_attributes_from_list,
- attributes_list,
- 0,
- )
-
- evaluation.definitions.set_attributes(tag, attributes)
-
- return True
-
-
-def process_assign_default(self, lhs, rhs, evaluation, tags, upset):
- lhs, condition = unroll_conditions(lhs)
- lhs, rhs = unroll_patterns(lhs, rhs, evaluation)
- count = 0
- defs = evaluation.definitions
-
- if len(lhs.elements) not in (1, 2, 3):
- evaluation.message_args(SymbolDefault, len(lhs.elements), 1, 2, 3)
- raise AssignmentException(lhs, None)
- focus = lhs.elements[0]
- tags = process_tags_and_upset_dont_allow_custom(
- tags, upset, self, lhs, focus, evaluation
- )
- lhs, rhs = process_rhs_conditions(lhs, rhs, condition, evaluation)
- rule = Rule(lhs, rhs)
- for tag in tags:
- if rejected_because_protected(self, lhs, tag, evaluation):
- continue
- count += 1
- defs.add_default(tag, rule)
- return count > 0
-
-
-def process_assign_format(self, lhs, rhs, evaluation, tags, upset):
- lhs, condition = unroll_conditions(lhs)
- lhs, rhs = unroll_patterns(lhs, rhs, evaluation)
- count = 0
- defs = evaluation.definitions
-
- if len(lhs.elements) not in (1, 2):
- evaluation.message_args("Format", len(lhs.elements), 1, 2)
- raise AssignmentException(lhs, None)
- if len(lhs.elements) == 2:
- form = lhs.elements[1]
- form_name = form.get_name()
- if not form_name:
- evaluation.message("Format", "fttp", lhs.elements[1])
- raise AssignmentException(lhs, None)
- # If the form is not in defs.printforms / defs.outputforms
- # add it.
- for form_list in (defs.outputforms, defs.printforms):
- if form not in form_list:
- form_list.append(form)
- else:
- form_name = [
- "System`StandardForm",
- "System`TraditionalForm",
- "System`OutputForm",
- "System`TeXForm",
- "System`MathMLForm",
- ]
- lhs = focus = lhs.elements[0]
- tags = process_tags_and_upset_dont_allow_custom(
- tags, upset, self, lhs, focus, evaluation
- )
- lhs, rhs = process_rhs_conditions(lhs, rhs, condition, evaluation)
- rule = Rule(lhs, rhs)
- for tag in tags:
- if rejected_because_protected(self, lhs, tag, evaluation):
- continue
- count += 1
- defs.add_format(tag, rule, form_name)
- return count > 0
-
-
-def process_assign_list(self, lhs, rhs, evaluation, tags, upset):
- if not (
- rhs.get_head_name() == "System`List" and len(lhs.elements) == len(rhs.elements)
- ): # nopep8
- evaluation.message(self.get_name(), "shape", lhs, rhs)
- return False
- result = True
- for left, right in zip(lhs.elements, rhs.elements):
- if not self.assign(left, right, evaluation):
- result = False
- return result
-
-
-def process_assign_makeboxes(self, lhs, rhs, evaluation, tags, upset):
- # FIXME: the below is a big hack.
- # Currently MakeBoxes boxing is implemented as a bunch of rules.
- # See mathics.builtin.base contribute().
- # I think we want to change this so it works like normal SetDelayed
- # That is:
- # MakeBoxes[CubeRoot, StandardForm] := RadicalBox[3, StandardForm]
- # rather than:
- # MakeBoxes[CubeRoot, StandardForm] -> RadicalBox[3, StandardForm]
-
- makeboxes_rule = Rule(lhs, rhs, system=False)
- definitions = evaluation.definitions
- definitions.add_rule("System`MakeBoxes", makeboxes_rule, "down")
- # makeboxes_defs = evaluation.definitions.builtin["System`MakeBoxes"]
- # makeboxes_defs.add_rule(makeboxes_rule)
- return True
-
-
-def process_assing_part(self, lhs, rhs, evaluation, tags, upset):
+def process_assign_part(self, lhs, rhs, evaluation, tags, upset):
"""
Special case `A[[i,j,...]]=....`
"""
@@ -667,27 +693,32 @@ def process_assing_part(self, lhs, rhs, evaluation, tags, upset):
return walk_parts([rule.replace], indices, evaluation, rhs)
-def process_assign_messagename(self, lhs, rhs, evaluation, tags, upset):
- lhs, condition = unroll_conditions(lhs)
- lhs, rhs = unroll_patterns(lhs, rhs, evaluation)
- count = 0
- defs = evaluation.definitions
- if len(lhs.elements) != 2:
- evaluation.message_args("MessageName", len(lhs.elements), 2)
+def process_assign_random_state(self, lhs, rhs, evaluation, tags, upset):
+ # TODO: allow setting of legal random states!
+ # (but consider pickle's insecurity!)
+ evaluation.message("$RandomState", "rndst", rhs)
+ raise AssignmentException(lhs, None)
+
+
+def process_assign_recursion_limit(lhs, rhs, evaluation):
+ """
+ Set ownvalue for the $RecursionLimit symbol.
+ """
+ rhs_int_value = rhs.get_int_value()
+ # if (not rhs_int_value or rhs_int_value < 20) and not
+ # rhs.get_name() == 'System`Infinity':
+ if (
+ not rhs_int_value or rhs_int_value < 20 or rhs_int_value > MAX_RECURSION_DEPTH
+ ): # nopep8
+
+ evaluation.message("$RecursionLimit", "limset", rhs)
raise AssignmentException(lhs, None)
- focus = lhs.elements[0]
- tags = process_tags_and_upset_dont_allow_custom(
- tags, upset, self, lhs, focus, evaluation
- )
- lhs, rhs = process_rhs_conditions(lhs, rhs, condition, evaluation)
- rule = Rule(lhs, rhs)
- for tag in tags:
- # Messages can be assigned even if the symbol is protected...
- # if rejected_because_protected(self, lhs, tag, evaluation):
- # continue
- count += 1
- defs.add_message(tag, rule)
- return count > 0
+ try:
+ set_python_recursion_limit(rhs_int_value)
+ except OverflowError:
+ # TODO: Message
+ raise AssignmentException(lhs, None)
+ return False
def process_rhs_conditions(lhs, rhs, condition, evaluation):
@@ -785,60 +816,26 @@ def process_tags_and_upset_allow_custom(tags, upset, self, lhs, evaluation):
return tags, focus
-class _SetOperator:
- """
- This is the base class for assignment Builtin operators.
-
- Special cases are determined by the head of the expression. Then
- they are processed by specific routines, which are poke from
- the `special_cases` dict.
- """
-
- # There are several idea about how to reimplement this. One possibility
- # would be to use a Symbol instead of a name as the key of this dictionary.
- #
- # Another possibility would be to move the specific function to be a
- # class method associated to the corresponding builtins. In any case,
- # the present implementation should be clear enough to understand the
- # logic.
- #
-
- special_cases = {
- "System`$Context": process_assign_context,
- "System`$ContextPath": process_assign_context_path,
- "System`$RandomState": process_assign_random_state,
- "System`Attributes": process_assign_attributes,
- "System`Default": process_assign_default,
- "System`DefaultValues": process_assign_definition_values,
- "System`DownValues": process_assign_definition_values,
- "System`Format": process_assign_format,
- "System`List": process_assign_list,
- "System`MakeBoxes": process_assign_makeboxes,
- "System`MessageName": process_assign_messagename,
- "System`Messages": process_assign_definition_values,
- "System`N": process_assign_n,
- "System`NValues": process_assign_definition_values,
- "System`NumericQ": process_assign_numericq,
- "System`Options": process_assign_options,
- "System`OwnValues": process_assign_definition_values,
- "System`Part": process_assing_part,
- "System`SubValues": process_assign_definition_values,
- "System`UpValues": process_assign_definition_values,
- }
-
- def assign(self, lhs, rhs, evaluation, tags=None, upset=False):
- if isinstance(lhs, Symbol):
- name = lhs.name
- else:
- name = lhs.get_head_name()
- # lhs._format_cache = None
- try:
- # Deal with direct assignation to properties of
- # the definition object
- func = self.special_cases.get(name, None)
- if func:
- return func(self, lhs, rhs, evaluation, tags, upset)
-
- return assign_store_rules_by_tag(self, lhs, rhs, evaluation, tags, upset)
- except AssignmentException:
- return False
+# Below is a mapping from a string Symbol name into an assignment function
+ASSIGNMENT_FUNCTION_MAP = {
+ "System`$Context": process_assign_context,
+ "System`$ContextPath": process_assign_context_path,
+ "System`$RandomState": process_assign_random_state,
+ "System`Attributes": process_assign_attributes,
+ "System`Default": process_assign_default,
+ "System`DefaultValues": process_assign_definition_values,
+ "System`DownValues": process_assign_definition_values,
+ "System`Format": process_assign_format,
+ "System`List": process_assign_list,
+ "System`MakeBoxes": process_assign_makeboxes,
+ "System`MessageName": process_assign_messagename,
+ "System`Messages": process_assign_definition_values,
+ "System`N": process_assign_n,
+ "System`NValues": process_assign_definition_values,
+ "System`NumericQ": process_assign_numericq,
+ "System`Options": process_assign_options,
+ "System`OwnValues": process_assign_definition_values,
+ "System`Part": process_assign_part,
+ "System`SubValues": process_assign_definition_values,
+ "System`UpValues": process_assign_definition_values,
+}
diff --git a/mathics/core/expression.py b/mathics/core/expression.py
index 5cb02211f..e03d20904 100644
--- a/mathics/core/expression.py
+++ b/mathics/core/expression.py
@@ -538,6 +538,10 @@ def evaluate(
return expr
def evaluate_elements(self, evaluation) -> "Expression":
+ """
+ return a new expression with the same head, and the
+ evaluable elements evaluated.
+ """
elements = [
element.evaluate(evaluation) if isinstance(element, EvalMixin) else element
for element in self._elements
diff --git a/mathics/core/list.py b/mathics/core/list.py
index bd5818681..19cb603e3 100644
--- a/mathics/core/list.py
+++ b/mathics/core/list.py
@@ -4,6 +4,7 @@
from typing import Optional, Tuple
from mathics.core.element import ElementsProperties
+from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
from mathics.core.symbols import EvalMixin, Symbol, SymbolList
@@ -85,7 +86,11 @@ def __str__(self) -> str:
return f""
# @timeit
- def evaluate_elements(self, evaluation):
+ def evaluate_elements(self, evaluation: Evaluation) -> Expression:
+ """
+ return a new expression with the same head, and the
+ evaluable elements evaluated.
+ """
elements_changed = False
# Make tuple self._elements mutable by turning it into a list.
elements = list(self._elements)
@@ -98,16 +103,17 @@ def evaluate_elements(self, evaluation):
elements_changed = True
elements[index] = new_value
- if elements_changed:
- # Save changed elements, making them immutable again.
- self._elements = tuple(elements)
+ if not elements_changed:
+ return self
- # TODO: we could have a specialized version of this
- # that keeps self.value up to date when that is
- # easy to do. That is left of some future time to
- # decide whether doing this this is warranted.
- self._build_elements_properties()
- self.value = None
+ new_list = ListExpression(*elements)
+ # TODO: we could have a specialized version of this
+ # that keeps self.value up to date when that is
+ # easy to do. That is left of some future time to
+ # decide whether doing this this is warranted.
+ new_list._build_elements_properties()
+ new_list.value = None
+ return new_list
@property
def is_literal(self) -> bool:
@@ -142,7 +148,7 @@ def rewrite_apply_eval_step(self, evaluation) -> Tuple[Expression, bool]:
self._build_elements_properties()
if not self.elements_properties.elements_fully_evaluated:
new = self.shallow_copy()
- new.evaluate_elements(evaluation)
+ new = new.evaluate_elements(evaluation)
return new, False
return self, False
diff --git a/mathics/core/systemsymbols.py b/mathics/core/systemsymbols.py
index 15088a264..a49c30a5d 100644
--- a/mathics/core/systemsymbols.py
+++ b/mathics/core/systemsymbols.py
@@ -140,6 +140,7 @@
SymbolOverflow = Symbol("System`Overflow")
SymbolOwnValues = Symbol("System`OwnValues")
SymbolPackages = Symbol("System`$Packages")
+SymbolPart = Symbol("System`Part")
SymbolPattern = Symbol("System`Pattern")
SymbolPatternTest = Symbol("System`PatternTest")
SymbolPower = Symbol("System`Power")
diff --git a/test/builtin/test_assignment.py b/test/builtin/test_assignment.py
index 5d6cc791c..cb4e42d61 100644
--- a/test/builtin/test_assignment.py
+++ b/test/builtin/test_assignment.py
@@ -158,6 +158,58 @@ def test_setdelayed_oneidentity():
"{a + b, Q[a, b], a + b}",
None,
),
+ (None, None, None),
+ (r"a=b; a=4; {a, b}", "{4, b}", None),
+ (None, None, None),
+ (r"a=b; b=4; {a,b}", "{4, 4}", None),
+ (None, None, None),
+ (r"a=b; b=4; Clear[a]; {a,b}", "{a, 4}", None),
+ (None, None, None),
+ ("a=b; b=4; Clear[b]; {a, b}", "{b, b}", None),
+ (None, None, None),
+ ("F[x_]:=x^2; G[x_]:=F[x]; ClearAll[F]; G[u]", "F[u]", None),
+ (None, None, None),
+ ("F[x_]:=G[x]; G[x_]:=x^2; ClearAll[G]; F[u]", "G[u]", None),
+ (None, None, None),
+ (
+ "F[x_]:=G[x]; H[F[y_]]:=Q[y]; ClearAll[F]; {H[G[5]],H[F[5]]}",
+ "{Q[5], H[F[5]]}",
+ "Arguments on the LHS are evaluated before the assignment in := after F reset",
+ ),
+ (None, None, None),
+ (
+ "F[x_]:=G[x]; H[F[y_]]^:=Q[y]; ClearAll[F]; {H[G[5]],H[F[5]]}",
+ "{Q[5], H[F[5]]}",
+ "Arguments on the LHS are evaluated before the assignment in ^:= after F reset",
+ ),
+ (None, None, None),
+ (
+ "F[x_]:=G[x]; H[F[y_]]:=Q[y]; ClearAll[G]; {H[G[5]],H[F[5]]}",
+ "{Q[5], Q[5]}",
+ "The arguments on the LHS are evaluated before the assignment in := after G reset",
+ ),
+ (None, None, None),
+ (
+ "F[x_]:=G[x]; H[F[y_]]^:=Q[y]; ClearAll[G]; {H[G[5]],H[F[5]]}",
+ "{H[G[5]], H[G[5]]}",
+ "The arguments on the LHS are evaluated before the assignment in ^:= after G reset",
+ ),
+ (None, None, None),
+ (
+ (
+ "A[x_]:=B[x];B[x_]:=F[x];F[x_]:=G[x];"
+ "H[A[y_]]:=Q[y]; ClearAll[F];"
+ "{H[A[5]],H[B[5]],H[F[5]],H[G[5]]}"
+ ),
+ "{H[F[5]], H[F[5]], H[F[5]], Q[5]}",
+ "The arguments on the LHS are completely evaluated before the assignment",
+ ),
+ (None, None, None),
+ (
+ "F[x_]:=G[x];N[F[x_]]:=x^2;ClearAll[F];{N[F[2]],N[G[2]]}",
+ "{F[2.], 4.}",
+ "Assign N rule",
+ ),
(
None,
None,