From 20799cf5f03bc4815f82ac01fbe38a3ef650b7c1 Mon Sep 17 00:00:00 2001 From: rocky Date: Mon, 14 Nov 2022 22:18:35 -0500 Subject: [PATCH 1/2] Changes suggested in PR review Move core-like assignment interals out of builtins and into core. (We may want to split up core more in the future though) Better sort long list of assignment methods alphabetically Some small RstT tagging in a docstring Remove nonexistent word "evaluable" Add yet another type annotation to a signature Make test assert failure messages unique --- mathics/builtin/assignments/assignment.py | 77 ++- mathics/builtin/assignments/clear.py | 3 +- mathics/builtin/assignments/types.py | 3 +- mathics/builtin/assignments/upvalues.py | 2 +- mathics/builtin/atomic/symbols.py | 2 +- mathics/builtin/attributes.py | 2 +- mathics/builtin/scoping.py | 2 +- .../internals.py => core/assignment.py} | 522 ++++++++---------- mathics/core/list.py | 3 +- test/builtin/test_assignment.py | 8 +- 10 files changed, 323 insertions(+), 301 deletions(-) rename mathics/{builtin/assignments/internals.py => core/assignment.py} (91%) diff --git a/mathics/builtin/assignments/assignment.py b/mathics/builtin/assignments/assignment.py index 856942f18..2e5e6f0fd 100644 --- a/mathics/builtin/assignments/assignment.py +++ b/mathics/builtin/assignments/assignment.py @@ -4,8 +4,27 @@ """ -from mathics.builtin.assignments.internals import _SetOperator from mathics.builtin.base import BinaryOperator, Builtin +from mathics.core.assignment import ( + AssignmentException, + assign_store_rules_by_tag, + normalize_lhs, + process_assign_attributes, + process_assign_context, + process_assign_context_path, + process_assign_default, + process_assign_definition_values, + process_assign_format, + process_assign_list, + process_assign_makeboxes, + process_assign_messagename, + process_assign_n, + process_assign_numericq, + process_assign_options, + process_assign_random_state, + process_assign_part, +) + from mathics.core.attributes import ( A_HOLD_ALL, A_HOLD_FIRST, @@ -18,6 +37,62 @@ 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 `special_cases` dict. + """ + + # FIXME: + # Assigment is determined by the LHS. + # Is there a larger patterns or natural grouping that we are missing? + # For example it might be that + # there are some attributes or other properties of of the + # LHS of a builtin that we should be targetting. + # Below, we key on a string, but Symbol is more correct. + # + + 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_assign_part, + "System`SubValues": process_assign_definition_values, + "System`UpValues": process_assign_definition_values, + } + + def assign(self, lhs, rhs, evaluation, tags=None, upset=False): + lhs, lookup_name = normalize_lhs(lhs, evaluation) + try: + # Deal with direct assignation to properties of + # the definition object + func = self.special_cases.get(lookup_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 + + 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 05a72523a..e26477465 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 aee3c626f..217fab061 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 @@ -129,12 +132,12 @@ 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[uncondlhs, tst]) - with `uncondlhs` the result of strip all the conditions from lhs. - * if `uncondlhs` is not a `List` or a `Part` expression, evaluate the + ( 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 uncondlhs. + 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: @@ -265,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): @@ -376,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() @@ -407,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): @@ -532,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,...]]=....` """ @@ -695,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): @@ -811,58 +814,3 @@ def process_tags_and_upset_allow_custom(tags, upset, self, lhs, evaluation): raise AssignmentException(lhs, None) 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): - lhs, lookup_name = normalize_lhs(lhs, evaluation) - try: - # Deal with direct assignation to properties of - # the definition object - func = self.special_cases.get(lookup_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 diff --git a/mathics/core/list.py b/mathics/core/list.py index ddd36879f..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,7 @@ 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. diff --git a/test/builtin/test_assignment.py b/test/builtin/test_assignment.py index 3af7020c4..cb4e42d61 100644 --- a/test/builtin/test_assignment.py +++ b/test/builtin/test_assignment.py @@ -174,25 +174,25 @@ def test_setdelayed_oneidentity(): ( "F[x_]:=G[x]; H[F[y_]]:=Q[y]; ClearAll[F]; {H[G[5]],H[F[5]]}", "{Q[5], H[F[5]]}", - "The arguments on the LHS are evaluated before the assignment", + "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]]}", - "The arguments on the LHS are evaluated before the assignment", + "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", + "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", + "The arguments on the LHS are evaluated before the assignment in ^:= after G reset", ), (None, None, None), ( From ca35d60035ec0f820bc36dd621257e80b06e2cb0 Mon Sep 17 00:00:00 2001 From: rocky Date: Mon, 14 Nov 2022 22:43:10 -0500 Subject: [PATCH 2/2] Move ASSIGNMENT_FUNCTION_MAP into core --- mathics/builtin/assignments/assignment.py | 60 +++++------------------ mathics/core/assignment.py | 25 ++++++++++ 2 files changed, 37 insertions(+), 48 deletions(-) diff --git a/mathics/builtin/assignments/assignment.py b/mathics/builtin/assignments/assignment.py index 2e5e6f0fd..7f3008bf1 100644 --- a/mathics/builtin/assignments/assignment.py +++ b/mathics/builtin/assignments/assignment.py @@ -6,23 +6,10 @@ from mathics.builtin.base import BinaryOperator, Builtin from mathics.core.assignment import ( + ASSIGNMENT_FUNCTION_MAP, AssignmentException, assign_store_rules_by_tag, normalize_lhs, - process_assign_attributes, - process_assign_context, - process_assign_context_path, - process_assign_default, - process_assign_definition_values, - process_assign_format, - process_assign_list, - process_assign_makeboxes, - process_assign_messagename, - process_assign_n, - process_assign_numericq, - process_assign_options, - process_assign_random_state, - process_assign_part, ) from mathics.core.attributes import ( @@ -43,49 +30,26 @@ class _SetOperator: 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. + the ``ASSIGNMENT_FUNCTION_MAP`` dict. """ # FIXME: # Assigment is determined by the LHS. - # Is there a larger patterns or natural grouping that we are missing? - # For example it might be that - # there are some attributes or other properties of of the - # LHS of a builtin that we should be targetting. + # 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. - # - - 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_assign_part, - "System`SubValues": process_assign_definition_values, - "System`UpValues": process_assign_definition_values, - } def assign(self, lhs, rhs, evaluation, tags=None, upset=False): lhs, lookup_name = normalize_lhs(lhs, evaluation) try: - # Deal with direct assignation to properties of - # the definition object - func = self.special_cases.get(lookup_name, None) - if func: - return func(self, lhs, rhs, evaluation, tags, upset) + # 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: diff --git a/mathics/core/assignment.py b/mathics/core/assignment.py index 217fab061..90ca57cb9 100644 --- a/mathics/core/assignment.py +++ b/mathics/core/assignment.py @@ -814,3 +814,28 @@ def process_tags_and_upset_allow_custom(tags, upset, self, lhs, evaluation): raise AssignmentException(lhs, None) return tags, focus + + +# 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, +}