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,