Skip to content
Closed
4 changes: 2 additions & 2 deletions mathics/builtin/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -818,15 +818,15 @@ 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):
return "-@" + self.get_head_name() + "@-"

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
Expand Down
5 changes: 4 additions & 1 deletion mathics/builtin/box/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]"""
Expand Down
2 changes: 1 addition & 1 deletion mathics/builtin/patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
171 changes: 140 additions & 31 deletions mathics/core/assignment.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@
SymbolPart,
SymbolPattern,
SymbolRuleDelayed,
SymbolSet,
SymbolSetDelayed,
SymbolTagSet,
SymbolTagSetDelayed,
SymbolUpSet,
SymbolUpSetDelayed,
)


Expand All @@ -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))
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion mathics/core/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion mathics/core/evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)


Expand Down
5 changes: 4 additions & 1 deletion mathics/core/pattern.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion mathics/core/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)))

Expand Down
5 changes: 5 additions & 0 deletions mathics/core/systemsymbols.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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")
Expand All @@ -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")