diff --git a/mathics/builtin/base.py b/mathics/builtin/base.py index b6faf3aac..2e1cbd942 100644 --- a/mathics/builtin/base.py +++ b/mathics/builtin/base.py @@ -818,7 +818,7 @@ def get_head_name(self): def get_lookup_name(self): return self.get_name() - def get_sort_key(self) -> tuple: + def get_sort_key(self, pattern_sort=False) -> tuple: return self.to_expression().get_sort_key() def get_string_value(self): @@ -826,7 +826,7 @@ def get_string_value(self): def sameQ(self, expr) -> bool: """Mathics SameQ""" - return expr.sameQ(self) + return self.to_expression().sameQ(expr) def do_format(self, evaluation, format): return self diff --git a/mathics/builtin/box/layout.py b/mathics/builtin/box/layout.py index f9e4a7401..d37d3e5b8 100644 --- a/mathics/builtin/box/layout.py +++ b/mathics/builtin/box/layout.py @@ -201,7 +201,10 @@ class RowBox(BoxExpression): summary_text = "horizontal arrange of boxes" def __repr__(self): - return "RowBox[List[" + self.items.__repr__() + "]]" + try: + return "RowBox[List[" + self.items.__repr__() + "]]" + except: + return "RowBox[List[{uninitialized}]]" def apply_list(self, boxes, evaluation): """RowBox[boxes_List]""" diff --git a/mathics/builtin/patterns.py b/mathics/builtin/patterns.py index 72fae8e61..f2b6c5ffd 100644 --- a/mathics/builtin/patterns.py +++ b/mathics/builtin/patterns.py @@ -1690,7 +1690,7 @@ def __init__(self, rulelist, evaluation): self._elements = None self._head = SymbolDispatch - def get_sort_key(self) -> tuple: + def get_sort_key(self, pattern_sort=False) -> tuple: return self.src.get_sort_key() def get_atom_name(self): diff --git a/mathics/core/assignment.py b/mathics/core/assignment.py index 90ca57cb9..44ed129bc 100644 --- a/mathics/core/assignment.py +++ b/mathics/core/assignment.py @@ -32,6 +32,12 @@ SymbolPart, SymbolPattern, SymbolRuleDelayed, + SymbolSet, + SymbolSetDelayed, + SymbolTagSet, + SymbolTagSetDelayed, + SymbolUpSet, + SymbolUpSetDelayed, ) @@ -40,6 +46,60 @@ from functools import reduce +# In Set* operators, the default behavior is that the +# elements of the LHS are evaluated before the assignment. +# So, if we define +# +# F[x_]:=G[x] +# +# and then +# +# M[F[x_]]:=x^2 +# +# The rule that is stored is +# +# M[G[x_]]->x^2 +# +# +# This behaviour does not aplies to a reduces subset of expressions, like +# in +# +# A={1,2,3} +# Part[A,1]:=s +# +# in a way that the result of the second line is to change a part of `A` +# A->{s, 2, 3} +# +# instead of trying to assign 1:=s +# +# Something similar happens with the Set* expressions. For example, +# the expected behavior of +# +# Set[F[x_],rhs_]:=Print["Do not set to F"] +# +# is not to forbid assignments to `G`, but to F: +# +# G[x_]:=x^4 +# still set the rule G[x_]->x^2 +# +# while +# +# F[x_]:=x^4 +# just will print the warning "Do not set to F". +# +# +NOT_EVALUATE_ELEMENTS_IN_ASSIGNMENTS = ( + SymbolSet, + SymbolSetDelayed, + SymbolUpSet, + SymbolUpSetDelayed, + SymbolTagSet, + SymbolTagSetDelayed, + SymbolList, + SymbolPart, +) + + class AssignmentException(Exception): def __init__(self, lhs, rhs) -> None: super().__init__(" %s cannot be assigned to %s" % (rhs, lhs)) @@ -57,14 +117,17 @@ def assign_store_rules_by_tag(self, lhs, rhs, evaluation, tags, upset=None): """ lhs, condition = unroll_conditions(lhs) lhs, rhs = unroll_patterns(lhs, rhs, evaluation) + defs = evaluation.definitions ignore_protection, tags = process_assign_other( self, lhs, rhs, evaluation, tags, upset ) + # In WMA, this does not happens. However, if we remove this, # some combinatorica tests fail. # Also, should not be at the begining? lhs, rhs = process_rhs_conditions(lhs, rhs, condition, evaluation) + count = 0 rule = Rule(lhs, rhs) position = "up" if upset else None @@ -145,7 +208,10 @@ def normalize_lhs(lhs, evaluation): 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): + if ( + isinstance(lhs, Expression) + and lhs.get_head() not in NOT_EVALUATE_ELEMENTS_IN_ASSIGNMENTS + ): lhs = lhs.evaluate_elements(evaluation) # If there was a conditional expression, rebuild it with the processed lhs if cond: @@ -649,6 +715,7 @@ def process_assign_other( tags, focus = process_tags_and_upset_allow_custom( tags, upset, self, lhs, evaluation ) + lhs_name = lhs.get_name() if lhs_name == "System`$RecursionLimit": process_assign_recursion_limit(lhs, rhs, evaluation) @@ -750,68 +817,110 @@ def process_rhs_conditions(lhs, rhs, condition, evaluation): return lhs, rhs +def find_tag(focus): + name = focus.get_lookup_name() + if name == "System`HoldPattern": + if len(focus.elements) == 1: + return find_tag(focus.elements[0]) + # If HoldPattern appears with more than one element, + # the message + # "HoldPattern::argx: HoldPattern called with 2 arguments; 1 argument is expected." + # must be shown. + raise AssignmentException(lhs, None) + if name == "System`Optional": + return None + if name == "System`Pattern" and len(focus.elements) == 2: + pat = focus.elements[1] + if pat.get_head_name() in ( + "System`Blank", + "System`BlankSequence", + "System`BlankNullSequence", + ): + elems = pat.elements + if len(elems) == 0: + return None + return find_tag(elems[0]) + else: + return find_tag(pat) + return name + + def process_tags_and_upset_dont_allow_custom(tags, upset, self, lhs, focus, evaluation): - focus = focus.evaluate_elements(evaluation) + + if ( + isinstance(focus, Expression) + and focus.head not in NOT_EVALUATE_ELEMENTS_IN_ASSIGNMENTS + ): + focus = focus.evaluate_elements(evaluation) + name = lhs.get_head_name() if tags is None and not upset: - name = focus.get_lookup_name() - if not name: + name = find_tag(focus) + if name == "": evaluation.message(self.get_name(), "setraw", focus) raise AssignmentException(lhs, None) - tags = [name] + tags = [] if name is None else [name] elif upset: - tags = [focus.get_lookup_name()] + name = find_tag(focus) + tags = [] if name is None else [name] else: - allowed_names = [focus.get_lookup_name()] + name = find_tag(focus) + allowed_names = [] if name is None else [name] for name in tags: if name not in allowed_names: evaluation.message(self.get_name(), "tagnfd", Symbol(name)) raise AssignmentException(lhs, None) + + if len(tags) == 0: + evaluation.message(self.get_name(), "nosym", focus) + raise AssignmentException(lhs, None) return tags def process_tags_and_upset_allow_custom(tags, upset, self, lhs, evaluation): name = lhs.get_head_name() focus = lhs - focus = focus.evaluate_elements(evaluation) + + if isinstance(focus, Expression) and focus.head not in ( + SymbolSet, + SymbolSetDelayed, + SymbolUpSet, + SymbolUpSetDelayed, + SymbolTagSet, + SymbolTagSetDelayed, + SymbolList, + SymbolPart, + ): + focus = focus.evaluate_elements(evaluation) + if tags is None and not upset: - name = focus.get_lookup_name() - if not name: + name = find_tag(focus) + if name == "": evaluation.message(self.get_name(), "setraw", focus) raise AssignmentException(lhs, None) - tags = [name] + tags = [] if name is None else [name] elif upset: tags = [] if isinstance(focus, Atom): evaluation.message(self.get_name(), "normal") raise AssignmentException(lhs, None) for element in focus.elements: - name = element.get_lookup_name() - tags.append(name) + name = find_tag(element) + if name != "" and name is not None: + tags.append(name) else: - allowed_names = [focus.get_lookup_name()] + allowed_names = [find_tag(focus)] for element in focus.get_elements(): - if not isinstance(element, Symbol) and element.get_head_name() in ( - "System`HoldPattern", - ): - element = element.elements[0] - if not isinstance(element, Symbol) and element.get_head_name() in ( - "System`Pattern", - ): - element = element.elements[1] - if not isinstance(element, Symbol) and element.get_head_name() in ( - "System`Blank", - "System`BlankSequence", - "System`BlankNullSequence", - ): - if len(element.elements) == 1: - element = element.elements[0] - - allowed_names.append(element.get_lookup_name()) + element_tag = find_tag(element) + if element_tag is not None and element_tag != "": + allowed_names.append(element_tag) for name in tags: if name not in allowed_names: evaluation.message(self.get_name(), "tagnfd", Symbol(name)) raise AssignmentException(lhs, None) + if len(tags) == 0: + evaluation.message(self.get_name(), "nosym", focus) + raise AssignmentException(lhs, None) return tags, focus diff --git a/mathics/core/element.py b/mathics/core/element.py index 26f525311..f061081b6 100644 --- a/mathics/core/element.py +++ b/mathics/core/element.py @@ -147,7 +147,7 @@ class KeyComparable: # FIXME: return type should be a specific kind of Tuple, not a list. # FIXME: Describe sensible, and easy to follow rules by which one # can create the kind of tuple for some new kind of element. - def get_sort_key(self) -> list: + def get_sort_key(self, pattern_sort=False) -> list: """ This returns a tuple in a way that it can be used to compare in expressions. diff --git a/mathics/core/evaluation.py b/mathics/core/evaluation.py index 3ac098309..212bb15b0 100644 --- a/mathics/core/evaluation.py +++ b/mathics/core/evaluation.py @@ -157,7 +157,7 @@ def __init__(self) -> None: self.is_print = False self.text = "" - def get_sort_key(self) -> Tuple[bool, bool, str]: + def get_sort_key(self, pattern_sort=False) -> Tuple[bool, bool, str]: return (self.is_message, self.is_print, self.text) diff --git a/mathics/core/pattern.py b/mathics/core/pattern.py index b0a398d58..6e05290ee 100644 --- a/mathics/core/pattern.py +++ b/mathics/core/pattern.py @@ -5,7 +5,7 @@ from typing import Optional from mathics.core.atoms import Integer -from mathics.core.element import BaseElement, ensure_context +from mathics.core.element import ensure_context, BaseElement, BoxElementMixin from mathics.core.evaluation import Evaluation from mathics.core.expression import Expression, SymbolDefault from mathics.core.symbols import Atom, Symbol, symbol_set @@ -549,6 +549,9 @@ def yield_next(next): yield_choice(vars) def __init__(self, expr): + if isinstance(expr, BoxElementMixin): + expr = expr.to_expression() + self.head = Pattern.create(expr.head) self.elements = [Pattern.create(element) for element in expr.elements] self.expr = expr diff --git a/mathics/core/rules.py b/mathics/core/rules.py index 717f9d64c..dac7426d0 100644 --- a/mathics/core/rules.py +++ b/mathics/core/rules.py @@ -107,7 +107,7 @@ def yield_match(vars, rest): def do_replace(self): raise NotImplementedError - def get_sort_key(self) -> tuple: + def get_sort_key(self, pattern_sort=False) -> tuple: # FIXME: check if this makes sense: return tuple((self.system, self.pattern.get_sort_key(True))) diff --git a/mathics/core/systemsymbols.py b/mathics/core/systemsymbols.py index a49c30a5d..dd77fac98 100644 --- a/mathics/core/systemsymbols.py +++ b/mathics/core/systemsymbols.py @@ -169,6 +169,7 @@ SymbolSeries = Symbol("System`Series") SymbolSeriesData = Symbol("System`SeriesData") SymbolSet = Symbol("System`Set") +SymbolSetDelayed = Symbol("System`SetDelayed") SymbolSign = Symbol("System`Sign") SymbolSimplify = Symbol("System`Simplify") SymbolSin = Symbol("System`Sin") @@ -186,6 +187,8 @@ SymbolSubsuperscriptBox = Symbol("System`SubsuperscriptBox") SymbolSuperscriptBox = Symbol("System`SuperscriptBox") SymbolTable = Symbol("System`Table") +SymbolTagSet = Symbol("System`TagSet") +SymbolTagSetDelayed = Symbol("System`TagSetDelayed") SymbolTeXForm = Symbol("System`TeXForm") SymbolThrow = Symbol("System`Throw") SymbolToString = Symbol("System`ToString") @@ -194,5 +197,7 @@ SymbolUndefined = Symbol("System`Undefined") SymbolUnequal = Symbol("System`Unequal") SymbolUnevaluated = Symbol("System`Unevaluated") +SymbolUpSet = Symbol("System`UpSet") +SymbolUpSetDelayed = Symbol("System`UpSetDelayed") SymbolUpValues = Symbol("System`UpValues") SymbolXor = Symbol("System`Xor")