diff --git a/CHANGES.rst b/CHANGES.rst
index 836d64e9d..d763134ae 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,4 +1,4 @@
-.. contents::
+ .. contents::
CHANGES
=======
@@ -41,6 +41,8 @@ Internals
#. Operator name to unicode or ASCII comes from Mathics scanner character tables.
#. ``eval*`` methods in `Builtin` classes are considerer as synonyms of ``apply*`` methods.
#. Modularize and improve the way in which `Builtin` classes are selected to have an associated `Definition`.
+#. `_SetOperator.assign_elementary` was renamed as `_SetOperator.assign`. All the special cases are not handled by the `_SetOperator.special_cases` dict.
+
Bugs
diff --git a/Makefile b/Makefile
index 9698c1b60..bef14777a 100644
--- a/Makefile
+++ b/Makefile
@@ -112,7 +112,7 @@ clean: clean-cython clean-cache
#: Run py.test tests. Use environment variable "o" for pytest options
pytest:
- py.test $(PYTEST_WORKERS) test $o
+ $(PYTHON) -m pytest $(PYTEST_WORKERS) test $o
#: Run a more extensive pattern-matching test
diff --git a/mathics/builtin/__init__.py b/mathics/builtin/__init__.py
index f7e9b0a61..79e6e1897 100755
--- a/mathics/builtin/__init__.py
+++ b/mathics/builtin/__init__.py
@@ -20,6 +20,8 @@
PatternObject,
)
+from mathics.core.pattern import pattern_objects
+
from mathics.settings import ENABLE_FILES_MODULE
from mathics.version import __version__ # noqa used in loading to check consistency.
@@ -267,7 +269,6 @@ def sanity_check(cls, module):
mathics_to_python = {} # here we have: name -> string
sympy_to_mathics = {}
-pattern_objects = {}
builtins_precedence = {}
new_builtins = _builtins
diff --git a/mathics/builtin/assignments/assignment.py b/mathics/builtin/assignments/assignment.py
index f1637c927..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):
"""
@@ -144,8 +183,30 @@ class SetDelayed(Set):
= 2 / 3
>> F[-3, 2]
= -2 / 3
+ We can use conditional delayed assignments to define \
+ symbols with values conditioned to the context. For example,
+ >> ClearAll[a,b]; a/; b>0:= 3
+ Set $a$ to have a value of $3$ if certain variable $b$ is positive.\
+ So, if this variable is not set, $a$ stays unevaluated:
+ >> a
+ = a
+ If now we assign a positive value to $b$, then $a$ is evaluated:
+ >> b=2; a
+ = 3
"""
+ # I WMA, if we assign a value without a condition on the LHS,
+ # conditional values are never reached. So,
+ #
+ # Notice however that if we assign an unconditional value to $a$, \
+ # this overrides the condition:
+ # >> a:=0; a/; b>1:= 3
+ # >> a
+ # = 0
+ #
+ # In Mathics, this last line would return 3
+ # """
+
operator = ":="
attributes = A_HOLD_ALL | A_PROTECTED | A_SEQUENCE_HOLD
@@ -203,7 +264,7 @@ def apply(self, f, lhs, rhs, evaluation):
return
rhs = rhs.evaluate(evaluation)
- self.assign_elementary(lhs, rhs, evaluation, tags=[name])
+ self.assign(lhs, rhs, evaluation, tags=[name])
return rhs
@@ -228,7 +289,7 @@ def apply(self, f, lhs, rhs, evaluation):
evaluation.message(self.get_name(), "sym", f, 1)
return
- if self.assign_elementary(lhs, rhs, evaluation, tags=[name]):
+ if self.assign(lhs, rhs, evaluation, tags=[name]):
return SymbolNull
else:
return SymbolFailed
diff --git a/mathics/builtin/assignments/clear.py b/mathics/builtin/assignments/clear.py
index 471eb5692..8dbda4ff5 100644
--- a/mathics/builtin/assignments/clear.py
+++ b/mathics/builtin/assignments/clear.py
@@ -22,20 +22,25 @@
Atom,
Symbol,
SymbolNull,
- system_symbols,
+ symbol_set,
)
from mathics.core.systemsymbols import (
SymbolContext,
SymbolContextPath,
+ SymbolDownValues,
SymbolFailed,
+ SymbolMessages,
+ SymbolNValues,
SymbolOptions,
+ SymbolOwnValues,
+ SymbolSubValues,
+ 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):
"""
@@ -320,12 +325,12 @@ def apply(self, expr, evaluation):
return SymbolNull
-SYSTEM_SYMBOL_VALUES = system_symbols(
- "OwnValues",
- "DownValues",
- "SubValues",
- "UpValues",
- "NValues",
- "Options",
- "Messages",
+SYSTEM_SYMBOL_VALUES = symbol_set(
+ SymbolDownValues,
+ SymbolMessages,
+ SymbolNValues,
+ SymbolOptions,
+ SymbolOwnValues,
+ SymbolSubValues,
+ SymbolUpValues,
)
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 ac794fa48..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,
@@ -58,7 +58,7 @@ class UpSet(BinaryOperator, _SetOperator):
def apply(self, lhs, rhs, evaluation):
"lhs_ ^= rhs_"
- self.assign_elementary(lhs, rhs, evaluation, upset=True)
+ self.assign(lhs, rhs, evaluation, upset=True)
return rhs
@@ -92,7 +92,7 @@ class UpSetDelayed(UpSet):
def apply(self, lhs, rhs, evaluation):
"lhs_ ^:= rhs_"
- if self.assign_elementary(lhs, rhs, evaluation, upset=True):
+ if self.assign(lhs, rhs, evaluation, upset=True):
return SymbolNull
else:
return SymbolFailed
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 e0cf105c6..e26477465 100644
--- a/mathics/builtin/attributes.py
+++ b/mathics/builtin/attributes.py
@@ -1,17 +1,21 @@
# -*- coding: utf-8 -*-
-r"""
+"""
Attributes of Definitions
-While a definition like 'cube[$x_$] = $x$^3' gives a way to specify values of a function, attributes allow a way to specify general properties of functions and symbols. This is independent of the parameters they take and the values they produce.
+While a definition like 'cube[$x_$] = $x$^3' gives a way to specify \
+values of a function, attributes allow a way to \
+specify general properties of functions and symbols. This is \
+independent of the parameters they take and the values they produce.
-The builtin-attributes having a predefined meaning in \Mathics which are described below.
+The builtin-attributes having a predefined meaning in \Mathics which \
+are described below.
However in contrast to \Mathematica, you can set any symbol as an attribute.
"""
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,
@@ -33,6 +37,10 @@
class Attributes(Builtin):
"""
+
+ :WMA link:
+ https://reference.wolfram.com/language/ref/Attributes.html
+
- 'Attributes'[$symbol$]
- returns the attributes of $symbol$.
@@ -72,9 +80,12 @@ class Attributes(Builtin):
"""
attributes = A_HOLD_ALL | A_LISTABLE | A_PROTECTED
+ messages = {
+ "attnf": "`1` is not a known attribute.",
+ }
summary_text = "find the attributes of a symbol"
- def apply(self, expr, evaluation):
+ def eval(self, expr, evaluation):
"Attributes[expr_]"
if isinstance(expr, String):
@@ -88,59 +99,12 @@ def apply(self, expr, evaluation):
return ListExpression(*attributes_symbols)
-class SetAttributes(Builtin):
- """
-
- - 'SetAttributes'[$symbol$, $attrib$]
-
- adds $attrib$ to the list of $symbol$'s attributes.
-
-
- >> SetAttributes[f, Flat]
- >> Attributes[f]
- = {Flat}
-
- Multiple attributes can be set at the same time using lists:
- >> SetAttributes[{f, g}, {Flat, Orderless}]
- >> Attributes[g]
- = {Flat, Orderless}
- """
-
- attributes = A_HOLD_FIRST | A_PROTECTED
-
- messages = {
- "unknownattr": f"`1` should be one of {', '.join(attribute_string_to_number.keys())}"
- }
- summary_text = "set attributes for a symbol"
-
- def apply(self, symbols, attributes, evaluation):
- "SetAttributes[symbols_, attributes_]"
-
- symbols = get_symbol_list(
- symbols, lambda item: evaluation.message("SetAttributes", "sym", item, 1)
- )
- if symbols is None:
- return
- values = get_symbol_list(
- attributes, lambda item: evaluation.message("SetAttributes", "sym", item, 2)
- )
- if values is None:
- return
- for symbol in symbols:
- if A_LOCKED & evaluation.definitions.get_attributes(symbol):
- evaluation.message("SetAttributes", "locked", Symbol(symbol))
- else:
- for value in values:
- try:
- evaluation.definitions.set_attribute(
- symbol, attribute_string_to_number[value]
- )
- except KeyError:
- evaluation.message("SetAttributes", "unknowattr", value)
- return SymbolNull
-
-
class ClearAttributes(Builtin):
"""
+
+ :WMA link:
+ https://reference.wolfram.com/language/ref/ClearAttributes.html
+
- 'ClearAttributes'[$symbol$, $attrib$]
- removes $attrib$ from $symbol$'s attributes.
@@ -161,7 +125,7 @@ class ClearAttributes(Builtin):
attributes = A_HOLD_FIRST | A_PROTECTED
summary_text = "clear the attributes of a symbol"
- def apply(self, symbols, attributes, evaluation):
+ def eval(self, symbols, attributes, evaluation):
"ClearAttributes[symbols_, attributes_]"
symbols = get_symbol_list(
@@ -180,194 +144,209 @@ def apply(self, symbols, attributes, evaluation):
evaluation.message("ClearAttributes", "locked", Symbol(symbol))
else:
for value in values:
- evaluation.definitions.clear_attribute(
- symbol, attribute_string_to_number[value]
- )
+ try:
+ evaluation.definitions.clear_attribute(
+ symbol, attribute_string_to_number[value]
+ )
+ except KeyError:
+ evaluation.message("Attributes", "attnf", Symbol(value))
return SymbolNull
-class Protect(Builtin):
+class Constant(Predefined):
"""
-
- - 'Protect'[$s1$, $s2$, ...]
-
- sets the attribute 'Protected' for the symbols $si$.
+
+ :WMA link:
+ https://reference.wolfram.com/language/ref/Constant.html
-
- 'Protect'[$str1$, $str2$, ...]
-
- protects all symbols whose names textually match $stri$.
+
+ - 'Constant'
+
- is an attribute that indicates that a symbol is a constant.
- >> A = {1, 2, 3};
- >> Protect[A]
- >> A[[2]] = 4;
- : Symbol A is Protected.
- >> A
- = {1, 2, 3}
+ Mathematical constants like 'E' have attribute 'Constant':
+ >> Attributes[E]
+ = {Constant, Protected, ReadProtected}
+
+ Constant symbols cannot be used as variables in 'Solve' and
+ related functions:
+ >> Solve[x + E == 0, E]
+ : E is not a valid variable.
+ = Solve[x + E == 0, E]
"""
- attributes = A_HOLD_ALL | A_PROTECTED
- summary_text = "protect a symbol against redefinitions"
+ summary_text = "treat as a constant in differentiation, etc"
- def apply(self, symbols, evaluation):
- "Protect[symbols___]"
- protected = SymbolProtected
- items = []
- if isinstance(symbols, Symbol):
- symbols = [symbols]
- elif isinstance(symbols, String):
- symbols = [symbols]
- elif isinstance(symbols, Expression):
- if symbols.get_head_name() in ("System`Sequence", "System`List"):
- symbols = symbols.elements
- else:
- evaluation.message("Protect", "ssym", symbols)
- return SymbolNull
+class Flat(Predefined):
+ """
+
+ :WMA link:
+ https://reference.wolfram.com/language/ref/Flat.html
- for symbol in symbols:
- if isinstance(symbol, Symbol):
- items.append(symbol)
- else:
- pattern = symbol.get_string_value()
- if not pattern or pattern == "":
- evaluation.message("Protect", "ssym", symbol)
- continue
+
+ - 'Flat'
+
- is an attribute that specifies that nested occurrences of \
+ a function should be automatically flattened.
+
- if pattern[0] == "`":
- pattern = evaluation.definitions.get_current_context() + pattern[1:]
- names = evaluation.definitions.get_matching_names(pattern)
- for defn in names:
- symbol = Symbol(defn)
- if not A_LOCKED & evaluation.definitions.get_attributes(defn):
- items.append(symbol)
+ A symbol with the 'Flat' attribute represents an associative \
+ mathematical operation:
- Expression(SymbolSetAttributes, ListExpression(*items), protected).evaluate(
- evaluation
- )
- return SymbolNull
+ >> SetAttributes[f, Flat]
+ >> f[a, f[b, c]]
+ = f[a, b, c]
+ 'Flat' is taken into account in pattern matching:
+ >> f[a, b, c] /. f[a, b] -> d
+ = f[d, c]
-class Unprotect(Builtin):
+ #> SetAttributes[{u, v}, Flat]
+ #> u[x_] := {x}
+ #> u[]
+ = u[]
+ #> u[a]
+ = {a}
+ #> u[a, b]
+ : Iteration limit of 1000 exceeded.
+ = $Aborted
+ #> u[a, b, c]
+ : Iteration limit of 1000 exceeded.
+ = $Aborted
+ #> v[x_] := x
+ #> v[]
+ = v[]
+ #> v[a]
+ = a
+ #> v[a, b] (* in Mathematica: Iteration limit of 4096 exceeded. *)
+ = v[a, b]
+ #> v[a, b, c] (* in Mathematica: Iteration limit of 4096 exceeded. *)
+ : Iteration limit of 1000 exceeded.
+ = $Aborted
"""
-
- - 'Unprotect'[$s1$, $s2$, ...]
-
- removes the attribute 'Protected' for the symbols $si$.
-
- 'Unprotect'[$str$]
-
- unprotects symbols whose names textually match $str$.
-
- """
+ summary_text = "attribute for associative symbols"
- attributes = A_HOLD_ALL | A_PROTECTED
- summary_text = "remove protection against redefinitions"
- def apply(self, symbols, evaluation):
- "Unprotect[symbols___]"
- protected = SymbolProtected
- items = []
- if isinstance(symbols, Symbol):
- symbols = [symbols]
- elif isinstance(symbols, Expression):
- symbols = symbols.elements
- elif isinstance(symbols, String):
- symbols = [symbols]
- else:
- symbols = symbols.get_sequence()
+class HoldAll(Predefined):
+ """
+
+ :WMA link:
+ https://reference.wolfram.com/language/ref/HoldAll.html
- for symbol in symbols:
- if isinstance(symbol, Symbol):
- items.append(symbol)
- else:
- pattern = symbol.get_string_value()
- if not pattern or pattern == "":
- evaluation.message("Unprotect", "ssym", symbol)
- continue
+
+ - 'HoldAll'
+
- is an attribute specifying that all arguments of a \
+ function should be left unevaluated.
+
- if pattern[0] == "`":
- pattern = evaluation.definitions.get_current_context() + pattern[1:]
- names = evaluation.definitions.get_matching_names(pattern)
- for defn in names:
- symbol = Symbol(defn)
- if not A_LOCKED & evaluation.definitions.get_attributes(defn):
- items.append(symbol)
+ >> Attributes[Function]
+ = {HoldAll, Protected}
+ """
- Expression(
- SymbolClearAttributes,
- ListExpression(*items),
- protected,
- ).evaluate(evaluation)
- return SymbolNull
+ summary_text = "attribute for symbols that keep unevaluated all their elements"
-class Protected(Predefined):
+class HoldAllComplete(Predefined):
"""
+
+ :WMA link:
+ https://reference.wolfram.com/language/ref/HoldAllComplete.html
+
- - 'Protected'
-
- is an attribute that prevents values on a symbol from
- being modified.
+
- 'HoldAllComplete'
+
- is an attribute that includes the effects of 'HoldAll' and \
+ 'SequenceHold', and also protects the function from being \
+ affected by the upvalues of any arguments.
- Values of 'Protected' symbols cannot be modified:
- >> Attributes[p] = {Protected};
- >> p = 2;
- : Symbol p is Protected.
- >> f[p] ^= 3;
- : Tag p in f[p] is Protected.
- >> Format[p] = "text";
- : Symbol p is Protected.
-
- However, attributes might still be set:
- >> SetAttributes[p, Flat]
- >> Attributes[p]
- = {Flat, Protected}
- Thus, you can easily remove the attribute 'Protected':
- >> Attributes[p] = {};
- >> p = 2
- = 2
- You can also use 'Protect' or 'Unprotect', resp.
- >> Protect[p]
- >> Attributes[p]
- = {Protected}
- >> Unprotect[p]
+ 'HoldAllComplete' even prevents upvalues from being used, and \
+ includes 'SequenceHold'.
- If a symbol is 'Protected' and 'Locked', it can never be changed again:
- >> SetAttributes[p, {Protected, Locked}]
- >> p = 2
- : Symbol p is Protected.
- = 2
- >> Unprotect[p]
- : Symbol p is locked.
+ >> SetAttributes[f, HoldAllComplete]
+ >> f[a] ^= 3;
+ >> f[a]
+ = f[a]
+ >> f[Sequence[a, b]]
+ = f[Sequence[a, b]]
"""
- summary_text = "attribute of protected symbols"
+ summary_text = "attribute for symbols that keep unevaluated all their elements, and discards upvalues"
-class ReadProtected(Predefined):
+class HoldFirst(Predefined):
"""
+
+ :WMA link:
+ https://reference.wolfram.com/language/ref/HoldFirst.html
+
- - 'ReadProtected'
-
- is an attribute that prevents values on a symbol from
- being read.
+
- 'HoldFirst'
+
- is an attribute specifying that the first argument of a \
+ function should be left unevaluated.
- Values associated with 'ReadProtected' symbols cannot be seen in
- 'Definition':
- >> ClearAll[p]
- >> p = 3;
- >> Definition[p]
- = p = 3
- >> SetAttributes[p, ReadProtected]
- >> Definition[p]
- = Attributes[p] = {ReadProtected}
+ >> Attributes[Set]
+ = {HoldFirst, Protected, SequenceHold}
"""
- summary_text = "attribute of symbols with hidden definitions"
+ summary_text = "attribute for symbols that keep unevaluated their first element"
+
+
+class HoldRest(Predefined):
+ """
+
+ :WMA link:
+ https://reference.wolfram.com/language/ref/HoldRest.html
+
+
+ - 'HoldRest'
+
- is an attribute specifying that all but the first argument \
+ of a function should be left unevaluated.
+
+
+ >> Attributes[If]
+ = {HoldRest, Protected}
+ """
+
+ summary_text = (
+ "attribute for symbols that keep unevaluated all but their first element"
+ )
+
+
+class Listable(Predefined):
+ """
+
+ :WMA link:
+ https://reference.wolfram.com/language/ref/Listable.html
+
+
+ - 'Listable'
+
- is an attribute specifying that a function should be \
+ automatically applied to each element of a list.
+
+
+ >> SetAttributes[f, Listable]
+ >> f[{1, 2, 3}, {4, 5, 6}]
+ = {f[1, 4], f[2, 5], f[3, 6]}
+ >> f[{1, 2, 3}, 4]
+ = {f[1, 4], f[2, 4], f[3, 4]}
+ >> {{1, 2}, {3, 4}} + {5, 6}
+ = {{6, 7}, {9, 10}}
+ """
+
+ summary_text = "automatically thread over lists appearing in arguments"
class Locked(Predefined):
"""
+
+ :WMA link:
+ https://reference.wolfram.com/language/ref/Locked.html
+
- 'Locked'
-
- is an attribute that prevents attributes on a symbol from
+
- is an attribute that prevents attributes on a symbol from \
being modified.
@@ -391,94 +370,114 @@ class Locked(Predefined):
summary_text = "keep all attributes locked (settable but not clearable)"
-class Flat(Predefined):
+class NHoldAll(Predefined):
"""
+
+ :WMA link:
+ https://reference.wolfram.com/language/ref/NHoldAll.html
+
- - 'Flat'
-
- is an attribute that specifies that nested occurrences of
- a function should be automatically flattened.
+
- 'NHoldAll'
+
- is an attribute that protects all arguments of a \
+ function from numeric evaluation.
- A symbol with the 'Flat' attribute represents an associative
- mathematical operation:
- >> SetAttributes[f, Flat]
- >> f[a, f[b, c]]
- = f[a, b, c]
+ >> N[f[2, 3]]
+ = f[2., 3.]
+ >> SetAttributes[f, NHoldAll]
+ >> N[f[2, 3]]
+ = f[2, 3]
+ """
- 'Flat' is taken into account in pattern matching:
- >> f[a, b, c] /. f[a, b] -> d
- = f[d, c]
+ summary_text = "prevent numerical evaluation of elements"
- #> SetAttributes[{u, v}, Flat]
- #> u[x_] := {x}
- #> u[]
- = u[]
- #> u[a]
- = {a}
- #> u[a, b]
- : Iteration limit of 1000 exceeded.
- = $Aborted
- #> u[a, b, c]
- : Iteration limit of 1000 exceeded.
- = $Aborted
- #> v[x_] := x
- #> v[]
- = v[]
- #> v[a]
- = a
- #> v[a, b] (* in Mathematica: Iteration limit of 4096 exceeded. *)
- = v[a, b]
- #> v[a, b, c] (* in Mathematica: Iteration limit of 4096 exceeded. *)
- : Iteration limit of 1000 exceeded.
- = $Aborted
+
+class NHoldFirst(Predefined):
"""
+
+ :WMA link:
+ https://reference.wolfram.com/language/ref/NHoldFirst.html
- summary_text = "attribute for associative symbols"
+
+ - 'NHoldFirst'
+
- is an attribute that protects the first argument of a \
+ function from numeric evaluation.
+
+ """
+ summary_text = "prevent numerical evaluation of the first element"
-class Orderless(Predefined):
- """
- - 'Orderless'
-
- is an attribute that can be assigned to a symbol $f$ to
- indicate that the elements $ei$ in expressions of the form
- $f$[$e1$, $e2$, ...] should automatically be sorted into
- canonical order. This property is accounted for in pattern
- matching.
+
+class NHoldRest(Predefined):
+ """
+
+ :WMA link:
+ https://reference.wolfram.com/language/ref/NHoldRest.html
+
+
+ - 'NHoldRest'
+
- is an attribute that protects all but the first argument
+ of a function from numeric evaluation.
+ """
- The leaves of an 'Orderless' function are automatically sorted:
- >> SetAttributes[f, Orderless]
- >> f[c, a, b, a + b, 3, 1.0]
- = f[1., 3, a, b, c, a + b]
+ summary_text = "prevent numerical evaluation of all but the first element"
- A symbol with the 'Orderless' attribute represents a commutative
- mathematical operation.
- >> f[a, b] == f[b, a]
- = True
- 'Orderless' affects pattern matching:
- >> SetAttributes[f, Flat]
- >> f[a, b, c] /. f[a, c] -> d
- = f[b, d]
+class NumericFunction(Predefined):
+ """
+
+ :WMA link:
+ https://reference.wolfram.com/language/ref/NumericFunction.html
+
+
+ - 'NumericFunction'
+
- is an attribute that indicates that a symbol is the head of a numeric function.
+
+
+ Mathematical functions like 'Sqrt' have attribute 'NumericFunction':
+ >> Attributes[Sqrt]
+ = {Listable, NumericFunction, Protected}
+ Expressions with a head having this attribute, and with all the elements \
+ being numeric expressions, are considered numeric expressions:
+ >> NumericQ[Sqrt[1]]
+ = True
+ >> NumericQ[a]=True; NumericQ[Sqrt[a]]
+ = True
+ >> NumericQ[a]=False; NumericQ[Sqrt[a]]
+ = False
"""
- summary_text = "attribute for commutative symbols"
+ summary_text = "treat as a numeric function"
class OneIdentity(Predefined):
"""
+
+ :WMA link:
+ https://reference.wolfram.com/language/ref/OneIdentity.html
+
- 'OneIdentity'
-
- is an attribute specifying that '$f$[$x$]' should be treated
- as equivalent to $x$ in pattern matching.
+
- is an attribute assigned to a symbol, say $f$, indicating that '$f$[$x$]', $f$[$f$[$x$]], etc. are all \
+ equivalent to $x$ in pattern matching.
- 'OneIdentity' affects pattern matching:
+ >> a /. f[x_:0, u_] -> {u}
+ = a
+
+ Here is how 'OneIdentity' changes the pattern matched above :
+
>> SetAttributes[f, OneIdentity]
- >> a /. f[args___] -> {args}
+ >> a /. f[x_:0, u_] -> {u}
= {a}
- It does not affect evaluation:
+
+ However, without a default argument, the pattern does not match:
+ >> a /. f[u_] -> {u}
+ = a
+
+ 'OneIdentity' does not change evaluation:
>> f[a]
= f[a]
"""
@@ -486,204 +485,318 @@ class OneIdentity(Predefined):
summary_text = "attribute for idempotent symbols"
-class SequenceHold(Predefined):
+class Orderless(Predefined):
"""
+
+ :WMA link:
+ https://reference.wolfram.com/language/ref/Orderless.html
+
- - 'SequenceHold'
-
- is an attribute that prevents 'Sequence' objects from being
- spliced into a function's arguments.
+
- 'Orderless'
+
- is an attribute that can be assigned to a symbol $f$ to \
+ indicate that the elements $ei$ in expressions of the form \
+ $f$[$e1$, $e2$, ...] should automatically be sorted into \
+ canonical order. This property is accounted for in pattern \
+ matching.
- Normally, 'Sequence' will be spliced into a function:
- >> f[Sequence[a, b]]
- = f[a, b]
- It does not for 'SequenceHold' functions:
- >> SetAttributes[f, SequenceHold]
- >> f[Sequence[a, b]]
- = f[Sequence[a, b]]
+ The elements of an 'Orderless' function are automatically sorted:
+ >> SetAttributes[f, Orderless]
+ >> f[c, a, b, a + b, 3, 1.0]
+ = f[1., 3, a, b, c, a + b]
+
+ A symbol with the 'Orderless' attribute represents a commutative \
+ mathematical operation.
+ >> f[a, b] == f[b, a]
+ = True
+
+ 'Orderless' affects pattern matching:
+ >> SetAttributes[f, Flat]
+ >> f[a, b, c] /. f[a, c] -> d
+ = f[b, d]
- E.g., 'Set' has attribute 'SequenceHold' to allow assignment of sequences to variables:
- >> s = Sequence[a, b];
- >> s
- = Sequence[a, b]
- >> Plus[s]
- = a + b
"""
- summary_text = "attribute for symbols that do not expand sequences"
+ summary_text = "attribute for commutative symbols"
-class HoldFirst(Predefined):
+class Protect(Builtin):
"""
+
+ :WMA link:
+ https://reference.wolfram.com/language/ref/Protect.html
+
- - 'HoldFirst'
-
- is an attribute specifying that the first argument of a
- function should be left unevaluated.
+
- 'Protect'[$s1$, $s2$, ...]
+
- sets the attribute 'Protected' for the symbols $si$.
+
+
- 'Protect'[$str1$, $str2$, ...]
+
- protects all symbols whose names textually match $stri$.
- >> Attributes[Set]
- = {HoldFirst, Protected, SequenceHold}
+ >> A = {1, 2, 3};
+ >> Protect[A]
+ >> A[[2]] = 4;
+ : Symbol A is Protected.
+ >> A
+ = {1, 2, 3}
"""
- summary_text = "attribute for symbols that keep unevaluated their first element"
+ attributes = A_HOLD_ALL | A_PROTECTED
+ summary_text = "protect a symbol against redefinitions"
+ def eval(self, symbols, evaluation):
+ "Protect[symbols___]"
+ protected = SymbolProtected
+ items = []
-class HoldRest(Predefined):
- """
-
- - 'HoldRest'
-
- is an attribute specifying that all but the first argument
- of a function should be left unevaluated.
-
+ if isinstance(symbols, Symbol):
+ symbols = [symbols]
+ elif isinstance(symbols, String):
+ symbols = [symbols]
+ elif isinstance(symbols, Expression):
+ if symbols.get_head_name() in ("System`Sequence", "System`List"):
+ symbols = symbols.elements
+ else:
+ evaluation.message("Protect", "ssym", symbols)
+ return SymbolNull
- >> Attributes[If]
- = {HoldRest, Protected}
- """
+ for symbol in symbols:
+ if isinstance(symbol, Symbol):
+ items.append(symbol)
+ else:
+ pattern = symbol.get_string_value()
+ if not pattern or pattern == "":
+ evaluation.message("Protect", "ssym", symbol)
+ continue
- summary_text = (
- "attribute for symbols that keep unevaluated all but their first element"
- )
+ if pattern[0] == "`":
+ pattern = evaluation.definitions.get_current_context() + pattern[1:]
+ names = evaluation.definitions.get_matching_names(pattern)
+ for defn in names:
+ symbol = Symbol(defn)
+ if not A_LOCKED & evaluation.definitions.get_attributes(defn):
+ items.append(symbol)
+ Expression(SymbolSetAttributes, ListExpression(*items), protected).evaluate(
+ evaluation
+ )
+ return SymbolNull
-class HoldAll(Predefined):
+
+class Protected(Predefined):
"""
+
+ :WMA link:
+ https://reference.wolfram.com/language/ref/Protected.html
+
- - 'HoldAll'
-
- is an attribute specifying that all arguments of a
- function should be left unevaluated.
+
- 'Protected'
+
- is an attribute that prevents values on a symbol from
+ being modified.
- >> Attributes[Function]
- = {HoldAll, Protected}
+ Values of 'Protected' symbols cannot be modified:
+ >> Attributes[p] = {Protected};
+ >> p = 2;
+ : Symbol p is Protected.
+ >> f[p] ^= 3;
+ : Tag p in f[p] is Protected.
+ >> Format[p] = "text";
+ : Symbol p is Protected.
+
+ However, attributes might still be set:
+ >> SetAttributes[p, Flat]
+ >> Attributes[p]
+ = {Flat, Protected}
+ Thus, you can easily remove the attribute 'Protected':
+ >> Attributes[p] = {};
+ >> p = 2
+ = 2
+ You can also use 'Protect' or 'Unprotect', resp.
+ >> Protect[p]
+ >> Attributes[p]
+ = {Protected}
+ >> Unprotect[p]
+
+ If a symbol is 'Protected' and 'Locked', it can never be changed again:
+ >> SetAttributes[p, {Protected, Locked}]
+ >> p = 2
+ : Symbol p is Protected.
+ = 2
+ >> Unprotect[p]
+ : Symbol p is locked.
"""
- summary_text = "attribute for symbols that keep unevaluated all their elements"
+ summary_text = "attribute of protected symbols"
-class HoldAllComplete(Predefined):
+class ReadProtected(Predefined):
"""
+
+ :WMA link:
+ https://reference.wolfram.com/language/ref/ReadProtected.html
+
- - 'HoldAllComplete'
-
- is an attribute that includes the effects of 'HoldAll' and
- 'SequenceHold', and also protects the function from being
- affected by the upvalues of any arguments.
+
- 'ReadProtected'
+
- is an attribute that prevents values on a symbol from \
+ being read.
- 'HoldAllComplete' even prevents upvalues from being used, and
- includes 'SequenceHold'.
- >> SetAttributes[f, HoldAllComplete]
- >> f[a] ^= 3;
- >> f[a]
- = f[a]
- >> f[Sequence[a, b]]
- = f[Sequence[a, b]]
+ Values associated with 'ReadProtected' symbols cannot be seen in \
+ 'Definition':
+
+ >> ClearAll[p]
+ >> p = 3;
+ >> Definition[p]
+ = p = 3
+ >> SetAttributes[p, ReadProtected]
+ >> Definition[p]
+ = Attributes[p] = {ReadProtected}
"""
- summary_text = "attribute for symbols that keep unevaluated all their elements, and discards upvalues"
+ summary_text = "attribute of symbols with hidden definitions"
-class NHoldAll(Predefined):
+class SequenceHold(Predefined):
"""
+
+ :WMA link:
+ https://reference.wolfram.com/language/ref/SequenceHold.html
+
- - 'NHoldAll'
-
- is an attribute that protects all arguments of a
- function from numeric evaluation.
+
- 'SequenceHold'
+
- is an attribute that prevents 'Sequence' objects from being \
+ spliced into a function's arguments.
- >> N[f[2, 3]]
- = f[2., 3.]
- >> SetAttributes[f, NHoldAll]
- >> N[f[2, 3]]
- = f[2, 3]
- """
+ Normally, 'Sequence' will be spliced into a function:
- summary_text = "prevent numerical evaluation of elements"
+ >> f[Sequence[a, b]]
+ = f[a, b]
+ It does not for 'SequenceHold' functions:
+ >> SetAttributes[f, SequenceHold]
+ >> f[Sequence[a, b]]
+ = f[Sequence[a, b]]
+ E.g., 'Set' has attribute 'SequenceHold' to allow assignment of sequences to variables:
-class NHoldFirst(Predefined):
- """
-
- - 'NHoldFirst'
-
- is an attribute that protects the first argument of a
- function from numeric evaluation.
-
+ >> s = Sequence[a, b];
+ >> s
+ = Sequence[a, b]
+ >> Plus[s]
+ = a + b
"""
- summary_text = "prevent numerical evaluation of the first element"
+ summary_text = "attribute for symbols that do not expand sequences"
-class NHoldRest(Predefined):
+class SetAttributes(Builtin):
"""
+
+ :WMA link:
+ https://reference.wolfram.com/language/ref/SetAttributes.html
+
- - 'NHoldRest'
-
- is an attribute that protects all but the first argument
- of a function from numeric evaluation.
+
- 'SetAttributes'[$symbol$, $attrib$]
+
- adds $attrib$ to the list of $symbol$'s attributes.
- """
-
- summary_text = "prevent numerical evaluation of all but the first element"
+ >> SetAttributes[f, Flat]
+ >> Attributes[f]
+ = {Flat}
-class Listable(Predefined):
+ Multiple attributes can be set at the same time using lists:
+ >> SetAttributes[{f, g}, {Flat, Orderless}]
+ >> Attributes[g]
+ = {Flat, Orderless}
"""
-
- - 'Listable'
-
- is an attribute specifying that a function should be
- automatically applied to each element of a list.
-
- >> SetAttributes[f, Listable]
- >> f[{1, 2, 3}, {4, 5, 6}]
- = {f[1, 4], f[2, 5], f[3, 6]}
- >> f[{1, 2, 3}, 4]
- = {f[1, 4], f[2, 4], f[3, 4]}
- >> {{1, 2}, {3, 4}} + {5, 6}
- = {{6, 7}, {9, 10}}
- """
+ attributes = A_HOLD_FIRST | A_PROTECTED
- summary_text = "automatically thread over lists appearing in arguments"
+ messages = {
+ "unknownattr": f"`1` should be one of {', '.join(attribute_string_to_number.keys())}"
+ }
+ summary_text = "set attributes for a symbol"
+ def eval(self, symbols, attributes, evaluation):
+ "SetAttributes[symbols_, attributes_]"
-class Constant(Predefined):
- """
-
- - 'Constant'
-
- is an attribute that indicates that a symbol is a constant.
-
+ symbols = get_symbol_list(
+ symbols, lambda item: evaluation.message("SetAttributes", "sym", item, 1)
+ )
+ if symbols is None:
+ return
+ values = get_symbol_list(
+ attributes, lambda item: evaluation.message("SetAttributes", "sym", item, 2)
+ )
+ if values is None:
+ return
+ for symbol in symbols:
+ if A_LOCKED & evaluation.definitions.get_attributes(symbol):
+ evaluation.message("SetAttributes", "locked", Symbol(symbol))
+ else:
+ for value in values:
+ try:
+ evaluation.definitions.set_attribute(
+ symbol, attribute_string_to_number[value]
+ )
+ except KeyError:
+ evaluation.message("Attributes", "attnf", Symbol(value))
+ return SymbolNull
- Mathematical constants like 'E' have attribute 'Constant':
- >> Attributes[E]
- = {Constant, Protected, ReadProtected}
- Constant symbols cannot be used as variables in 'Solve' and
- related functions:
- >> Solve[x + E == 0, E]
- : E is not a valid variable.
- = Solve[x + E == 0, E]
+class Unprotect(Builtin):
"""
+
+ :WMA link:
+ https://reference.wolfram.com/language/ref/Unprotect.html
- summary_text = "treat as a constant in differentiation, etc"
-
-
-class NumericFunction(Predefined):
- """
- - 'NumericFunction'
-
- is an attribute that indicates that a symbol is the head of a numeric function.
+
- 'Unprotect'[$s1$, $s2$, ...]
+
- removes the attribute 'Protected' for the symbols $si$.
+
+
- 'Unprotect'[$str$]
+
- unprotects symbols whose names textually match $str$.
+ """
- Mathematical functions like 'Sqrt' have attribute 'NumericFunction':
- >> Attributes[Sqrt]
- = {Listable, NumericFunction, Protected}
+ attributes = A_HOLD_ALL | A_PROTECTED
+ summary_text = "remove protection against redefinitions"
- Expressions with a head having this attribute, and with all the leaves
- being numeric expressions, are considered numeric expressions:
- >> NumericQ[Sqrt[1]]
- = True
- >> NumericQ[a]=True; NumericQ[Sqrt[a]]
- = True
- >> NumericQ[a]=False; NumericQ[Sqrt[a]]
- = False
- """
+ def eval(self, symbols, evaluation):
+ "Unprotect[symbols___]"
+ protected = SymbolProtected
+ items = []
+ if isinstance(symbols, Symbol):
+ symbols = [symbols]
+ elif isinstance(symbols, Expression):
+ symbols = symbols.elements
+ elif isinstance(symbols, String):
+ symbols = [symbols]
+ else:
+ symbols = symbols.get_sequence()
- summary_text = "treat as a numeric function"
+ for symbol in symbols:
+ if isinstance(symbol, Symbol):
+ items.append(symbol)
+ else:
+ pattern = symbol.get_string_value()
+ if not pattern or pattern == "":
+ evaluation.message("Unprotect", "ssym", symbol)
+ continue
+
+ if pattern[0] == "`":
+ pattern = evaluation.definitions.get_current_context() + pattern[1:]
+ names = evaluation.definitions.get_matching_names(pattern)
+ for defn in names:
+ symbol = Symbol(defn)
+ if not A_LOCKED & evaluation.definitions.get_attributes(defn):
+ items.append(symbol)
+
+ Expression(
+ SymbolClearAttributes,
+ ListExpression(*items),
+ protected,
+ ).evaluate(evaluation)
+ return SymbolNull
diff --git a/mathics/builtin/base.py b/mathics/builtin/base.py
index 57a8473eb..b6faf3aac 100644
--- a/mathics/builtin/base.py
+++ b/mathics/builtin/base.py
@@ -283,15 +283,16 @@ def check_options(options_to_check, evaluation):
rules.append(
BuiltinRule(name, pattern, function, check_options, system=True)
)
- for pattern, replace in self.rules.items():
- if not isinstance(pattern, BaseElement):
- pattern = pattern % {"name": name}
- pattern = parse_builtin_rule(pattern, definition_class)
- replace = replace % {"name": name}
- # FIXME: Should system=True be system=not is_pymodule ?
- rules.append(Rule(pattern, parse_builtin_rule(replace), system=True))
+ for pattern_str, replace_str in self.rules.items():
+ pattern_str = pattern_str % {"name": name}
+ pattern = parse_builtin_rule(pattern_str, definition_class)
+ replace_str = replace_str % {"name": name}
+ rules.append(
+ Rule(pattern, parse_builtin_rule(replace_str), system=not is_pymodule)
+ )
box_rules = []
+ # FIXME: Why a special case for System`MakeBoxes? Remove this
if name != "System`MakeBoxes":
new_rules = []
for rule in rules:
diff --git a/mathics/builtin/files_io/files.py b/mathics/builtin/files_io/files.py
index e989eba9f..e801649bb 100644
--- a/mathics/builtin/files_io/files.py
+++ b/mathics/builtin/files_io/files.py
@@ -296,7 +296,7 @@ class FilePrint(Builtin):
}
def apply(self, path, evaluation, options):
- "FilePrint[path_ OptionsPattern[FilePrint]]"
+ "FilePrint[path_, OptionsPattern[FilePrint]]"
pypath = path.to_python()
if not (
isinstance(pypath, str)
@@ -1696,13 +1696,15 @@ class Write(Builtin):
def apply(self, channel, expr, evaluation):
"Write[channel_, expr___]"
- strm = channel_to_stream(channel)
+ stream = None
+ if isinstance(channel, String):
+ stream = {"stdout": 1, "stderr": 2}.get(channel.value, None)
- if strm is None:
- return
-
- n = strm.elements[1].get_int_value()
- stream = stream_manager.lookup_stream(n)
+ if stream is None:
+ strm = channel_to_stream(channel, "w")
+ if strm is None:
+ return
+ stream = stream_manager.lookup_stream(strm.elements[1].get_int_value())
if stream is None or stream.io is None or stream.io.closed:
evaluation.message("General", "openx", channel)
@@ -1763,6 +1765,9 @@ class WriteString(Builtin):
#> DeleteFile[pathname];
#> Clear[pathname];
+
+ If stream is the string "stdout" or "stderr", writes to the system standard output/ standard error channel:
+ >> WriteString["stdout", "Hola"]
"""
summary_text = "write a sequence of strings to a stream, with no extra newlines"
@@ -1773,12 +1778,18 @@ class WriteString(Builtin):
def apply(self, channel, expr, evaluation):
"WriteString[channel_, expr___]"
- strm = channel_to_stream(channel, "w")
-
- if strm is None:
- return
-
- stream = stream_manager.lookup_stream(strm.elements[1].get_int_value())
+ stream = None
+ if isinstance(channel, String):
+ if channel.value == "stdout":
+ stream = stream_manager.lookup_stream(1)
+ elif channel.value == "stderr":
+ stream = stream_manager.lookup_stream(2)
+
+ if stream is None:
+ strm = channel_to_stream(channel, "w")
+ if strm is None:
+ return
+ stream = stream_manager.lookup_stream(strm.elements[1].get_int_value())
if stream is None or stream.io is None or stream.io.closed:
return None
diff --git a/mathics/builtin/files_io/filesystem.py b/mathics/builtin/files_io/filesystem.py
index e7ea7ec83..a11299cb5 100644
--- a/mathics/builtin/files_io/filesystem.py
+++ b/mathics/builtin/files_io/filesystem.py
@@ -2274,7 +2274,7 @@ def apply_2(self, url, filename, evaluation, **options):
url = url.value
if filename is None:
result = urlsave_tmp(url, None, **options)
- elif filename.get_head_name() == "String":
+ elif isinstance(filename, String):
filename = filename.value
result = urlsave_tmp(url, filename, **options)
else:
diff --git a/mathics/builtin/graphics.py b/mathics/builtin/graphics.py
index 9651cefb7..de1e3f3a2 100644
--- a/mathics/builtin/graphics.py
+++ b/mathics/builtin/graphics.py
@@ -47,13 +47,16 @@
from mathics.core.list import ListExpression
from mathics.core.symbols import (
Symbol,
- system_symbols,
+ symbol_set,
system_symbols_dict,
SymbolList,
SymbolNull,
)
from mathics.core.systemsymbols import (
+ SymbolEdgeForm,
+ SymbolFaceForm,
SymbolMakeBoxes,
+ SymbolRule,
)
from mathics.core.formatter import lookup_method
@@ -61,9 +64,6 @@
from mathics.core.attributes import A_PROTECTED, A_READ_PROTECTED
-SymbolEdgeForm = Symbol("System`EdgeForm")
-SymbolFaceForm = Symbol("System`FaceForm")
-
GRAPHICS_OPTIONS = {
"AspectRatio": "Automatic",
"Axes": "False",
@@ -1427,26 +1427,26 @@ class Tiny(Builtin):
element_heads = frozenset(
- system_symbols(
- "Arrow",
- "BezierCurve",
- "Circle",
- "Cone",
- "Cuboid",
- "Cylinder",
- "Disk",
- "FilledCurve",
- "Inset",
- "Line",
- "Point",
- "Polygon",
- "Rectangle",
- "RegularPolygon",
- "Sphere",
- "Style",
- "Text",
- "Tube",
- "UniformPolyhedron",
+ symbol_set(
+ Symbol("System`Arrow"),
+ Symbol("System`BezierCurve"),
+ Symbol("System`Circle"),
+ Symbol("System`Cone"),
+ Symbol("System`Cuboid"),
+ Symbol("System`Cylinder"),
+ Symbol("System`Disk"),
+ Symbol("System`FilledCurve"),
+ Symbol("System`Inset"),
+ Symbol("System`Line"),
+ Symbol("System`Point"),
+ Symbol("System`Polygon"),
+ Symbol("System`Rectangle"),
+ Symbol("System`RegularPolygon"),
+ Symbol("System`Sphere"),
+ Symbol("System`Style"),
+ Symbol("System`Text"),
+ Symbol("System`Tube"),
+ Symbol("System`UniformPolyhedron"),
)
)
@@ -1477,7 +1477,7 @@ class Tiny(Builtin):
style_heads = frozenset(styles.keys())
style_and_form_heads = frozenset(
- style_heads.union(system_symbols("System`EdgeForm", "System`FaceForm"))
+ style_heads.union(symbol_set(SymbolEdgeForm, SymbolFaceForm))
)
GLOBALS.update(
@@ -1497,8 +1497,8 @@ class Tiny(Builtin):
GLOBALS.update(styles)
GRAPHICS_SYMBOLS = {
- Symbol("System`List"),
- Symbol("System`Rule"),
+ SymbolList,
+ SymbolRule,
Symbol("System`VertexColors"),
*element_heads,
*[Symbol(element.name + "Box") for element in element_heads],
diff --git a/mathics/builtin/options.py b/mathics/builtin/options.py
index e9d0371e5..9df8bee11 100644
--- a/mathics/builtin/options.py
+++ b/mathics/builtin/options.py
@@ -79,11 +79,11 @@ def eval(self, f, i, evaluation):
i = [index.get_int_value() for index in i]
for index in i:
if index is None or index < 1:
- evaluation.message(SymbolDefault, "intp")
+ evaluation.message(SymbolDefault.name, "intp")
return
name = f.get_name()
if not name:
- evaluation.message(SymbolDefault, "sym", f, 1)
+ evaluation.message(SymbolDefault.name, "sym", f, 1)
return
result = get_default_value(name, evaluation, *i)
return result
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 77%
rename from mathics/builtin/assignments/internals.py
rename to mathics/core/assignment.py
index 132d9e390..90ca57cb9 100644
--- a/mathics/builtin/assignments/internals.py
+++ b/mathics/core/assignment.py
@@ -1,8 +1,13 @@
# -*- coding: utf-8 -*-
+"""
+Support for Set and SetDelayed, and other assignment-like builtins
+"""
+from typing import Optional, Tuple
from mathics.algorithm.parts import walk_parts
from mathics.core.atoms import Atom, Integer
+from mathics.core.element import BaseElement
from mathics.core.evaluation import MAX_RECURSION_DEPTH, set_python_recursion_limit
from mathics.core.expression import Expression, SymbolDefault
from mathics.core.list import ListExpression
@@ -10,6 +15,7 @@
from mathics.core.symbols import (
Symbol,
SymbolFalse,
+ SymbolList,
SymbolMinPrecision,
SymbolMaxPrecision,
SymbolN,
@@ -23,6 +29,7 @@
SymbolHoldPattern,
SymbolMachinePrecision,
SymbolOptionValue,
+ SymbolPart,
SymbolPattern,
SymbolRuleDelayed,
)
@@ -41,12 +48,22 @@ def __init__(self, lhs, rhs) -> None:
def assign_store_rules_by_tag(self, lhs, rhs, evaluation, tags, upset=None):
+ """
+ This is the default assignment. Stores a rule of the form lhs->rhs
+ as a value associated to each symbol listed in tags.
+ For special cases, such like conditions or patterns in the lhs,
+ lhs and rhs are rewritten in a normal form, where
+ conditions are associated to the lhs.
+ """
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)
@@ -110,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:
@@ -132,7 +175,7 @@ def repl_pattern_by_symbol(expr):
return expr
-# Here are the functions related to assign_elementary
+# Here are the functions related to assign
# Auxiliary routines
@@ -166,7 +209,11 @@ def find_tag_and_check(lhs, tags, evaluation):
return tag
-def unroll_patterns(lhs, rhs, evaluation):
+def unroll_patterns(lhs, rhs, evaluation) -> Tuple[BaseElement, BaseElement]:
+ """
+ Pattern[symb, pat]=rhs -> pat = (rhs/.(symb->pat))
+ HoldPattern[lhs] = rhs -> lhs = rhs
+ """
if isinstance(lhs, Atom):
return lhs, rhs
name = lhs.get_head_name()
@@ -174,15 +221,24 @@ def unroll_patterns(lhs, rhs, evaluation):
if name == "System`Pattern":
lhs = lhs_elements[1]
rulerepl = (lhs_elements[0], repl_pattern_by_symbol(lhs))
+ # Maybe this replamement should be delayed instead,
+ # like
+ # rhs = Expression(Symbol("System`Replace"), Rule(*rulerepl))
+ # TODO: check if this is the correct behavior.
rhs, status = rhs.do_apply_rules([Rule(*rulerepl)], evaluation)
name = lhs.get_head_name()
-
- if name == "System`HoldPattern":
+ elif name == "System`HoldPattern":
lhs = lhs_elements[0]
return lhs, rhs
-def unroll_conditions(lhs):
+def unroll_conditions(lhs) -> Tuple[BaseElement, Optional[Expression]]:
+ """
+ If lhs is a nested `Condition` expression,
+ gather all the conditions in a single one, and returns a tuple
+ with the lhs stripped from the conditions and the shallow condition.
+ If there is not any condition, returns the lhs and None
+ """
if isinstance(lhs, Symbol):
return lhs, None
else:
@@ -207,63 +263,52 @@ def unroll_conditions(lhs):
return lhs, condition
-# Here starts the functions that implement `assign_elementary` for different
+# Here starts the functions that implement `assign` for different
# kind of expressions. Maybe they should be put in a separated module, or
# maybe they should be member functions of _SetOperator.
-def process_assign_recursion_limit(lhs, rhs, evaluation):
- 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):
- 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):
- 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_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
-def process_assign_line_number_and_history_length(
- self, lhs, rhs, evaluation, tags, upset
-):
- 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):
@@ -309,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()
@@ -340,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):
@@ -376,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)
@@ -430,7 +634,18 @@ def process_assign_n(self, lhs, rhs, evaluation, tags, upset):
return count > 0
-def process_assign_other(self, lhs, rhs, evaluation, tags=None, upset=False):
+def process_assign_other(
+ self, lhs, rhs, evaluation, tags=None, upset=False
+) -> Tuple[bool, list]:
+ """
+ Process special cases, performing certain side effects, like modifying
+ the value of internal variables that are not stored as rules.
+
+ The function returns a tuple of a bool value and a list of tags.
+ If lhs is one of the special cases, then the bool variable is
+ True, meaning that the `Protected` attribute should not be taken into accout.
+ Otherwise, the value is False.
+ """
tags, focus = process_tags_and_upset_allow_custom(
tags, upset, self, lhs, evaluation
)
@@ -454,134 +669,62 @@ def process_assign_other(self, lhs, rhs, evaluation, tags=None, upset=False):
return True, tags
-def process_assign_attributes(self, lhs, rhs, evaluation, tags, upset):
- 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
+def process_assign_part(self, lhs, rhs, evaluation, tags, upset):
+ """
+ Special case `A[[i,j,...]]=....`
+ """
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
+ if len(lhs.elements) < 1:
+ evaluation.message(self.get_name(), "setp", lhs)
+ return False
+ symbol = lhs.elements[0]
+ name = symbol.get_name()
+ if not name:
+ evaluation.message(self.get_name(), "setps", symbol)
+ return False
+ if is_protected(name, defs):
+ evaluation.message(self.get_name(), "wrsym", symbol)
+ return False
+ rule = defs.get_ownvalue(name)
+ if rule is None:
+ evaluation.message(self.get_name(), "noval", symbol)
+ return False
+ indices = lhs.elements[1:]
+ return walk_parts([rule.replace], indices, evaluation, rhs)
-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
+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)
- 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_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
-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)
+ 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):
+ """
+ lhs = Condition[rhs, test] -> Condition[lhs, test] = rhs
+ """
# To Handle `OptionValue` in `Condition`
rulopc = build_rulopc(lhs.get_head())
rhs_name = rhs.get_head_name()
@@ -608,12 +751,7 @@ def process_rhs_conditions(lhs, rhs, condition, evaluation):
def process_tags_and_upset_dont_allow_custom(tags, upset, self, lhs, focus, evaluation):
- # TODO: the following provides a hacky fix for 1259. I know @rocky loves
- # this kind of things, but otherwise we need to work on rebuild the pattern
- # matching mechanism...
- flag_ioi, evaluation.ignore_oneidentity = evaluation.ignore_oneidentity, True
focus = focus.evaluate_elements(evaluation)
- evaluation.ignore_oneidentity = flag_ioi
name = lhs.get_head_name()
if tags is None and not upset:
name = focus.get_lookup_name()
@@ -633,14 +771,9 @@ def process_tags_and_upset_dont_allow_custom(tags, upset, self, lhs, focus, eval
def process_tags_and_upset_allow_custom(tags, upset, self, lhs, evaluation):
- # TODO: the following provides a hacky fix for 1259. I know @rocky loves
- # this kind of things, but otherwise we need to work on rebuild the pattern
- # matching mechanism...
name = lhs.get_head_name()
focus = lhs
- flag_ioi, evaluation.ignore_oneidentity = evaluation.ignore_oneidentity, True
focus = focus.evaluate_elements(evaluation)
- evaluation.ignore_oneidentity = flag_ioi
if tags is None and not upset:
name = focus.get_lookup_name()
if not name:
@@ -683,77 +816,26 @@ def process_tags_and_upset_allow_custom(tags, upset, self, lhs, evaluation):
return tags, focus
-class _SetOperator:
- special_cases = {
- "System`OwnValues": process_assign_definition_values,
- "System`DownValues": process_assign_definition_values,
- "System`SubValues": process_assign_definition_values,
- "System`UpValues": process_assign_definition_values,
- "System`NValues": process_assign_definition_values,
- "System`DefaultValues": process_assign_definition_values,
- "System`Messages": process_assign_definition_values,
- "System`Attributes": process_assign_attributes,
- "System`Options": process_assign_options,
- "System`$RandomState": process_assign_random_state,
- "System`$Context": process_assign_context,
- "System`$ContextPath": process_assign_context_path,
- "System`N": process_assign_n,
- "System`NumericQ": process_assign_numericq,
- "System`MessageName": process_assign_messagename,
- "System`Default": process_assign_default,
- "System`Format": process_assign_format,
- }
-
- def assign_elementary(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
-
- def assign(self, lhs, rhs, evaluation):
- # lhs._format_cache = None
- defs = evaluation.definitions
- if lhs.get_head_name() == "System`List":
- if not (rhs.get_head_name() == "System`List") or len(lhs.elements) != len(
- rhs.elements
- ): # nopep8
-
- evaluation.message(self.get_name(), "shape", lhs, rhs)
- return False
- else:
- result = True
- for left, right in zip(lhs.elements, rhs.elements):
- if not self.assign(left, right, evaluation):
- result = False
- return result
- elif lhs.get_head_name() == "System`Part":
- if len(lhs.elements) < 1:
- evaluation.message(self.get_name(), "setp", lhs)
- return False
- symbol = lhs.elements[0]
- name = symbol.get_name()
- if not name:
- evaluation.message(self.get_name(), "setps", symbol)
- return False
- if is_protected(name, defs):
- evaluation.message(self.get_name(), "wrsym", symbol)
- return False
- rule = defs.get_ownvalue(name)
- if rule is None:
- evaluation.message(self.get_name(), "noval", symbol)
- return False
- indices = lhs.elements[1:]
- return walk_parts([rule.replace], indices, evaluation, rhs)
- else:
- return self.assign_elementary(lhs, rhs, evaluation)
+# 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/atoms.py b/mathics/core/atoms.py
index 9134b16d9..57e8661c5 100644
--- a/mathics/core/atoms.py
+++ b/mathics/core/atoms.py
@@ -24,9 +24,9 @@
Symbol,
SymbolNull,
SymbolTrue,
- system_symbols,
+ symbol_set,
)
-from mathics.core.systemsymbols import SymbolInfinity
+from mathics.core.systemsymbols import SymbolInfinity, SymbolInputForm, SymbolFullForm
# Imperical number that seems to work.
# We have to be able to match mpmath values with sympy values
@@ -35,7 +35,7 @@
SymbolI = Symbol("I")
SymbolString = Symbol("String")
-SYSTEM_SYMBOLS_INPUT_OR_FULL_FORM = system_symbols("InputForm", "FullForm")
+SYSTEM_SYMBOLS_INPUT_OR_FULL_FORM = symbol_set(SymbolInputForm, SymbolFullForm)
class Number(Atom, ImmutableValueMixin, NumericOperators):
diff --git a/mathics/core/attributes.py b/mathics/core/attributes.py
index e07cc3fa1..cceec0fda 100644
--- a/mathics/core/attributes.py
+++ b/mathics/core/attributes.py
@@ -9,37 +9,73 @@
# (the most of the cases).
# To check if a builtin has an attribute, you do:
-# ATTRIBUTE_NAME & attributes
+# ATTRIBUTE_NAME & attributes
+#
# To set all the attributes of a builtin you do:
-# attributes = ATTRIBUTE1 | ATTRIBUTE2 | ATTRIBUTE3 | ...
+# attributes = ATTRIBUTE1 | ATTRIBUTE2 | ATTRIBUTE3 | ...
+#
# To add an attribute to a builtin you do:
-# attributes = ATTRIBUTE_NAME | attributes
+# attributes = ATTRIBUTE_NAME | attributes
+#
# To remove an attribute you do:
-# attributes = ~ATTRIBUTE_NAME & attributes
+# attributes = ~ATTRIBUTE_NAME & attributes
from typing import Dict, Generator
# fmt: off
A_NO_ATTRIBUTES = 0b0000000000000000
-# FIXME: remove lowercase after thes are no longer imported
-# alphabetical order
+# Symbol is a constant.
A_CONSTANT = 0b00000000000000001
+
+# Nested occurrences of a function should be automatically flattened.
A_FLAT = 0b00000000000000010
+
+# All arguments of a function should be left unevaluated.
A_HOLD_ALL = 0b00000000000000100
+
+# Includes the effects of 'HoldAll' and 'SequenceHold', and also
+# protects the function from being affected by the upvalues of any
+# arguments.
A_HOLD_ALL_COMPLETE = 0b00000000000001000
+
+# First argument of a function should be left unevaluated.
A_HOLD_FIRST = 0b00000000000010000
+
+# All but the first argument of a function should be left unevaluated.
A_HOLD_REST = 0b00000000000100000
+
+# Function should be automatically applied to each element of a list.
A_LISTABLE = 0b00000000001000000
+
+# prevents attributes on a symbol from being modified.
A_LOCKED = 0b00000000010000000
+
+# Protects all arguments of a function from numeric evaluation
A_N_HOLD_ALL = 0b00000000100000000
+
+# Protects the first argument of a function from numeric evaluation.
A_N_HOLD_FIRST = 0b00000001000000000
+
+# Protects all but the first argument of a function from numeric evaluation.
A_N_HOLD_REST = 0b00000010000000000
+
+# Symbol is the head of a numeric function.
A_NUMERIC_FUNCTION = 0b00000100000000000
+
+# '$f$[$x$]' should be treated as equivalent to $x$ in pattern matching.
A_ONE_IDENTITY = 0b00001000000000000
+
+# elements should automatically be sorted into canonical order
A_ORDERLESS = 0b00010000000000000
+
+# Prevents values on a symbol from being modified.
A_PROTECTED = 0b00100000000000000
+
+# Prevents values on a symbol from being read.
A_READ_PROTECTED = 0b01000000000000000
+
+# prevents 'Sequence' objects from being spliced into a function's arguments
A_SEQUENCE_HOLD = 0b10000000000000000
attribute_number_to_string: Dict[int, str] = {
diff --git a/mathics/core/evaluation.py b/mathics/core/evaluation.py
index 3c1dfa174..3ac098309 100644
--- a/mathics/core/evaluation.py
+++ b/mathics/core/evaluation.py
@@ -262,9 +262,6 @@ def __init__(
# status of last evaluate
self.exc_result = self.SymbolNull
self.last_eval = None
- # Necesary to handle OneIdentity on
- # lhs in assignment
- self.ignore_oneidentity = False
# Used in ``mathics.builtin.numbers.constants.get_constant`` and
# ``mathics.builtin.numeric.N``.
self._preferred_n_method = []
diff --git a/mathics/core/evaluators.py b/mathics/core/evaluators.py
index 1485e121e..8f0dc1762 100644
--- a/mathics/core/evaluators.py
+++ b/mathics/core/evaluators.py
@@ -21,10 +21,11 @@
)
from mathics.core.convert.sympy import from_sympy
from mathics.core.definitions import PyMathicsLoadException
+from mathics.core.element import BaseElement
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
from mathics.core.number import PrecisionValueError, get_precision
-from mathics.core.symbols import Atom, BaseElement
+from mathics.core.symbols import Atom
from mathics.core.systemsymbols import SymbolMachinePrecision, SymbolN
diff --git a/mathics/core/expression.py b/mathics/core/expression.py
index 04025e0ef..e03d20904 100644
--- a/mathics/core/expression.py
+++ b/mathics/core/expression.py
@@ -37,11 +37,14 @@
Monomial,
NumericOperators,
Symbol,
+ SymbolAbs,
+ SymbolDivide,
SymbolList,
SymbolN,
+ SymbolPlus,
SymbolTimes,
SymbolTrue,
- system_symbols,
+ symbol_set,
)
from mathics.core.systemsymbols import (
SymbolAborted,
@@ -50,9 +53,14 @@
SymbolCondition,
SymbolDirectedInfinity,
SymbolFunction,
+ SymbolMinus,
SymbolPattern,
+ SymbolPower,
SymbolSequence,
+ SymbolSin,
SymbolSlot,
+ SymbolSqrt,
+ SymbolSubtract,
SymbolUnevaluated,
)
@@ -70,16 +78,16 @@
SymbolVerbatim = Symbol("Verbatim")
-symbols_arithmetic_operations = system_symbols(
- "Sqrt",
- "Times",
- "Plus",
- "Subtract",
- "Minus",
- "Power",
- "Abs",
- "Divide",
- "Sin",
+symbols_arithmetic_operations = symbol_set(
+ SymbolAbs,
+ SymbolDivide,
+ SymbolMinus,
+ SymbolPlus,
+ SymbolPower,
+ SymbolSin,
+ SymbolSqrt,
+ SymbolSubtract,
+ SymbolTimes,
)
@@ -530,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 4dd21d6d1..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
@@ -41,7 +42,7 @@ def __init__(
# call_frame = inspect.getouterframes(curframe, 2)
# print("caller name:", call_frame[1][3])
- # from mathics.core.symbols import BaseElement
+ # from mathics.core.element import BaseElement
# for element in elements:
# if not isinstance(element, BaseElement):
# from trepan.api import debug; debug()
@@ -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/pattern.py b/mathics/core/pattern.py
index 39dc1511e..b0a398d58 100644
--- a/mathics/core/pattern.py
+++ b/mathics/core/pattern.py
@@ -2,52 +2,58 @@
# cython: profile=False
# -*- coding: utf-8 -*-
-
-from mathics.core.element import ensure_context
-from mathics.core.expression import Expression
-from mathics.core.symbols import Atom, Symbol, system_symbols
-from mathics.core.systemsymbols import SymbolSequence
+from typing import Optional
+
+from mathics.core.atoms import Integer
+from mathics.core.element import BaseElement, ensure_context
+from mathics.core.evaluation import Evaluation
+from mathics.core.expression import Expression, SymbolDefault
+from mathics.core.symbols import Atom, Symbol, symbol_set
+from mathics.core.systemsymbols import (
+ SymbolAlternatives,
+ SymbolBlank,
+ SymbolBlankNullSequence,
+ SymbolBlankSequence,
+ SymbolCondition,
+ SymbolOptional,
+ SymbolOptionsPattern,
+ SymbolPattern,
+ SymbolPatternTest,
+ SymbolRepeated,
+ SymbolRepeatedNull,
+ SymbolSequence,
+)
from mathics.core.util import subsets, subranges, permutations
from itertools import chain
from mathics.core.attributes import A_FLAT, A_ONE_IDENTITY, A_ORDERLESS
-# from mathics.core.pattern_nocython import (
-# StopGenerator #, Pattern #, ExpressionPattern)
-# from mathics.core import pattern_nocython
-
-
-SYSTEM_SYMBOLS_PATTERNS = system_symbols(
- "Pattern",
- "PatternTest",
- "Condition",
- "Optional",
- "Blank",
- "BlankSequence",
- "BlankNullSequence",
- "Alternatives",
- "OptionsPattern",
- "Repeated",
- "RepeatedNull",
+# FIXME: create definitions in systemsymbols for missing items below.
+SYSTEM_SYMBOLS_PATTERNS = symbol_set(
+ SymbolAlternatives,
+ SymbolBlank,
+ SymbolBlankNullSequence,
+ SymbolBlankSequence,
+ SymbolCondition,
+ SymbolOptional,
+ SymbolOptionsPattern,
+ SymbolPattern,
+ SymbolPatternTest,
+ SymbolRepeated,
+ SymbolRepeatedNull,
)
-
-def Pattern_create(expr):
- from mathics.builtin import pattern_objects
-
- # from mathics.core.pattern import AtomPattern, ExpressionPattern
-
- name = expr.get_head_name()
- pattern_object = pattern_objects.get(name)
- if pattern_object is not None:
- return pattern_object(expr)
- if isinstance(expr, Atom):
- return AtomPattern(expr)
- else:
- return ExpressionPattern(expr)
+pattern_objects = {}
class StopGenerator(Exception):
+ """
+ StopGenerator is the exception raised when
+ an expression matches a pattern.
+ The exception holds the attribute `value`
+ that is used as a return value in `match`.
+ """
+
def __init__(self, value=None):
self.value = value
@@ -70,7 +76,22 @@ class Pattern:
When the pattern matches, the symbol is bound to the parameter ``x``.
"""
- create = staticmethod(Pattern_create)
+ @staticmethod
+ def create(expr: BaseElement) -> "Pattern":
+ """
+ If ``expr`` is listed in ``pattern_object`` return the pattern found there.
+ Otherwise, if ``expr`` is an ``Atom``, create and return ``AtomPattern`` for ``expr``.
+ Otherwise, create and return and ``ExpressionPattern`` for ``expr``.
+ """
+
+ name = expr.get_head_name()
+ pattern_object = pattern_objects.get(name)
+ if pattern_object is not None:
+ return pattern_object(expr)
+ if isinstance(expr, Atom):
+ return AtomPattern(expr)
+ else:
+ return ExpressionPattern(expr)
def match(
self,
@@ -82,22 +103,29 @@ def match(
element_index=None,
element_count=None,
fully=True,
- wrap_oneid=True,
):
+ """
+ Check if the expression matches the pattern (self).
+ If it does, calls `yield_func`.
+ vars collects subexpressions associated to subpatterns.
+ head ?
+ element_index ?
+ element_count ?
+ fully is used in match_elements, for the case of Orderless patterns.
+ """
raise NotImplementedError
- """def match(self, expression, vars, evaluation,
- head=None, element_index=None, element_count=None,
- fully=True, wrap_oneid=True):
- #raise NotImplementedError
- result = []
- def yield_func(vars, rest):
- result.append(vars, rest)
- self._match(yield_func, expression, vars, evaluation, head,
- element_index, element_count, fully, wrap_oneid)
- return result"""
+ def does_match(
+ self,
+ expression: BaseElement,
+ evaluation: Evaluation,
+ vars=Optional[dict],
+ fully: bool = True,
+ ) -> bool:
- def does_match(self, expression, evaluation, vars=None, fully=True):
+ """
+ returns True if `expression` matches self.
+ """
if vars is None:
vars = {}
@@ -187,7 +215,6 @@ def match_symbol(
element_index=None,
element_count=None,
fully=True,
- wrap_oneid=True,
):
if expression is self.atom:
yield_func(vars, None)
@@ -207,7 +234,6 @@ def match(
element_index=None,
element_count=None,
fully=True,
- wrap_oneid=True,
):
if isinstance(expression, Atom) and expression.sameQ(self.atom):
# yield vars, None
@@ -244,7 +270,6 @@ def match(
element_index=None,
element_count=None,
fully=True,
- wrap_oneid=True,
):
evaluation.check_stopped()
attributes = self.head.get_attributes(evaluation.definitions)
@@ -311,8 +336,7 @@ def yield_choice(pre_vars):
# for new_vars, rest in self.match_element( # nopep8
# self.elements[0], self.elements[1:], ([], expression.elements),
# pre_vars, expression, attributes, evaluation, first=True,
- # fully=fully, element_count=len(self.elements),
- # wrap_oneid=expression.get_head_name() != 'System`MakeBoxes'):
+ # fully=fully, element_count=len(self.elements)):
# def yield_element(new_vars, rest):
# yield_func(new_vars, rest)
self.match_element(
@@ -327,7 +351,6 @@ def yield_choice(pre_vars):
first=True,
fully=fully,
element_count=len(self.elements),
- wrap_oneid=expression.get_head_name() != "System`MakeBoxes",
)
# for head_vars, _ in self.head.match(expression.get_head(), vars,
@@ -351,51 +374,88 @@ def yield_head(head_vars, _):
self.head.match(yield_head, expression.get_head(), vars, evaluation)
except StopGenerator_ExpressionPattern_match:
return
- if (
- wrap_oneid
- and not evaluation.ignore_oneidentity
- and A_ONE_IDENTITY & attributes
- and not self.head.expr.sameQ(expression.get_head()) # nopep8
- and not self.head.expr.sameQ(expression)
- ):
- # and not OneIdentity &
- # (expression.get_attributes(evaluation.definitions) |
- # expression.get_head().get_attributes(evaluation.definitions)):
- new_expression = Expression(self.head.expr, expression)
- for element in self.elements:
- element.match_count = element.get_match_count()
- element.candidates = [expression]
- # element.get_match_candidates(
- # new_expression.elements, new_expression, attributes,
- # evaluation, vars)
- if len(element.candidates) < element.match_count[0]:
- return
- # for new_vars, rest in self.match_element(
- # self.elements[0], self.elements[1:],
- # ([], [expression]), vars, new_expression, attributes,
- # evaluation, first=True, fully=fully,
- # element_count=len(self.elements), wrap_oneid=True):
- # def yield_element(new_vars, rest):
- # yield_func(new_vars, rest)
- self.match_element(
+
+ if A_ONE_IDENTITY & attributes:
+ # This is all about the pattern. We do this
+ # each time because at some point we should need
+ # to check the default values each time...
+
+ # This tries to reduce the pattern to a non empty
+ # set of default values, and a single pattern.
+ default_indx = 0
+ optionals = {}
+ new_pattern = None
+ pattern_head = self.head.expr
+ for pat_elem in self.elements:
+ default_indx += 1
+ if isinstance(pat_elem, AtomPattern):
+ if new_pattern is not None:
+ return
+ new_pattern = pat_elem
+ # TODO: check into account the second argument,
+ # and if there is a default value...
+ elif pat_elem.get_head_name() == "System`Optional":
+ if len(pat_elem.elements) == 2:
+ pat, value = pat_elem.elements
+ if pat.get_head_name() == "System`Pattern":
+ key = pat.elements[0].atom.name
+ else:
+ # if the first element of the Optional
+ # is not a `Pattern`, then we need to
+ # store an empty element.
+ key = ""
+ optionals[key] = value
+ elif len(pat_elem.elements) == 1:
+ pat = pat_elem.elements[0]
+ if pat.get_head_name() == "System`Pattern":
+ key = pat.elements[0].atom.name
+ else:
+ key = ""
+ # Now, determine the default value
+ defaultvalue_expr = Expression(
+ SymbolDefault, pattern_head, Integer(default_indx)
+ )
+ value = defaultvalue_expr.evaluate(evaluation)
+ if value.sameQ(defaultvalue_expr):
+ return
+ optionals[key] = value
+ else:
+ return
+ else:
+ if new_pattern is not None:
+ return
+ new_pattern = pat_elem
+
+ # If there is not optional values in the pattern, then
+ # it can not match any expression as a OneIdentity pattern:
+ if len(optionals) == 0:
+ return
+
+ # Remove the empty key and load the default values in vars
+ if "" in optionals:
+ del optionals[""]
+ vars.update(optionals)
+ # Try to match the non-optional element with the expression
+ new_pattern.match(
yield_func,
- self.elements[0],
- self.elements[1:],
- ([], [expression]),
+ expression,
vars,
- new_expression,
- attributes,
evaluation,
- first=True,
+ head=head,
+ element_index=element_index,
+ element_count=element_count,
fully=fully,
- element_count=len(self.elements),
- wrap_oneid=True,
)
- def get_pre_choices(self, yield_func, expression, attributes, vars):
+ def get_pre_choices(self, yield_choice, expression, attributes, vars):
+ """
+ If not Orderless, call yield_choice with vars as the parameter.
+ """
if A_ORDERLESS & attributes:
self.sort()
patterns = self.filter_elements("Pattern")
+ # a dict with entries having patterns with the same name
+ # which are not in vars.
groups = {}
prev_pattern = prev_name = None
for pattern in patterns:
@@ -484,9 +544,9 @@ def yield_next(next):
# for setting in per_name(groups.items(), vars):
# def yield_name(setting):
# yield_func(setting)
- per_name(yield_func, list(groups.items()), vars)
+ per_name(yield_choice, list(groups.items()), vars)
else:
- yield_func(vars)
+ yield_choice(vars)
def __init__(self, expr):
self.head = Pattern.create(expr.head)
@@ -545,7 +605,6 @@ def match_element(
first=False,
fully=True,
depth=1,
- wrap_oneid=True,
):
if rest_expression is None:
@@ -626,6 +685,9 @@ def match_element(
*set_lengths
)
else:
+ # a generator that yields partitions of
+ # candidates as [before | block | after ]
+
sets = subranges(
candidates,
flexible_start=first and not fully,
@@ -633,7 +695,6 @@ def match_element(
less_first=less_first,
*set_lengths
)
-
if rest_elements:
next_element = rest_elements[0]
next_rest_elements = rest_elements[1:]
@@ -680,7 +741,6 @@ def match_yield(new_vars, _):
depth=next_depth,
element_index=next_index,
element_count=element_count,
- wrap_oneid=wrap_oneid,
)
else:
if not fully or (not items_rest[0] and not items_rest[1]):
@@ -696,7 +756,6 @@ def yield_wrapping(item):
head=expression.head,
element_index=element_index,
element_count=element_count,
- wrap_oneid=wrap_oneid,
)
self.get_wrappings(
diff --git a/mathics/core/streams.py b/mathics/core/streams.py
index dfba2f229..69a5122d1 100644
--- a/mathics/core/streams.py
+++ b/mathics/core/streams.py
@@ -43,9 +43,10 @@ def urlsave_tmp(url, location=None, **kwargs):
with open(location, "wb") as fp:
fp.write(r.content)
result = fp.name
+ return result
except Exception:
- result = None
- return result
+ return None
+ return None
def path_search(filename: str) -> Tuple[str, bool]:
diff --git a/mathics/core/symbols.py b/mathics/core/symbols.py
index 9ea745368..beb75ab7e 100644
--- a/mathics/core/symbols.py
+++ b/mathics/core/symbols.py
@@ -3,8 +3,7 @@
import sympy
import time
-import typing
-from typing import Any, Optional
+from typing import Any, FrozenSet, List, Optional, Tuple
from mathics.core.element import (
BaseElement,
@@ -251,7 +250,7 @@ def evaluate_elements(self, evaluation) -> "Atom":
def get_atom_name(self) -> str:
return self.__class__.__name__
- def get_atoms(self, include_heads=True) -> typing.List["Atom"]:
+ def get_atoms(self, include_heads=True) -> List["Atom"]:
return [self]
# We seem to need this because the caller doesn't distinguish something with elements
@@ -667,15 +666,14 @@ def is_uncertain_final_definitions(self, definitions) -> bool:
return False
-# system_symbols('A', 'B', ...) -> [Symbol('System`A'), Symbol('System`B'), ...]
-def system_symbols(*symbols) -> typing.FrozenSet[Symbol]:
+def symbol_set(*symbols: Tuple[Symbol]) -> FrozenSet[Symbol]:
"""
- Return a frozenset of symbols from a list of names (strings).
+ Return a frozenset of symbols from a Symbol arguments.
We will use this in testing membership, so an immutable object is fine.
In 2021, we benchmarked frozenset versus list, tuple, and set and frozenset was the fastest.
"""
- return frozenset(Symbol(s) for s in symbols)
+ return frozenset(symbols)
# Symbols used in this module.
diff --git a/mathics/core/systemsymbols.py b/mathics/core/systemsymbols.py
index b9d9c47fb..a49c30a5d 100644
--- a/mathics/core/systemsymbols.py
+++ b/mathics/core/systemsymbols.py
@@ -35,6 +35,8 @@
SymbolAttributes = Symbol("System`Attributes")
SymbolAutomatic = Symbol("System`Automatic")
SymbolBlank = Symbol("System`Blank")
+SymbolBlankNullSequence = Symbol("System`BlankNullSequence")
+SymbolBlankSequence = Symbol("System`BlankSequence")
SymbolBlend = Symbol("System`Blend")
SymbolBreak = Symbol("System`Break")
SymbolByteArray = Symbol("System`ByteArray")
@@ -60,6 +62,7 @@
SymbolDirectedInfinity = Symbol("System`DirectedInfinity")
SymbolDispatch = Symbol("System`Dispatch")
SymbolDot = Symbol("System`Dot")
+SymbolDownValues = Symbol("System`DownValues")
SymbolE = Symbol("System`E")
SymbolEdgeForm = Symbol("System`EdgeForm")
SymbolEqual = Symbol("System`Equal")
@@ -67,6 +70,7 @@
SymbolExpandAll = Symbol("System`ExpandAll")
SymbolExport = Symbol("System`Export")
SymbolExportString = Symbol("System`ExportString")
+SymbolFaceForm = Symbol("System`FaceForm")
SymbolFactorial = Symbol("System`Factorial")
SymbolFailed = Symbol("System`$Failed")
SymbolFloor = Symbol("System`Floor")
@@ -112,11 +116,12 @@
SymbolMean = Symbol("System`Mean")
SymbolMemberQ = Symbol("System`MemberQ")
SymbolMessageName = Symbol("System`MessageName")
-SymbolMinus = Symbol("System`Minus")
+SymbolMessages = Symbol("System`Messages")
SymbolMinus = Symbol("System`Minus")
SymbolMissing = Symbol("System`Missing")
SymbolN = Symbol("System`N")
SymbolNIntegrate = Symbol("System`NIntegrate")
+SymbolNValues = Symbol("System`NValues")
SymbolNeeds = Symbol("System`Needs")
SymbolNone = Symbol("System`None")
SymbolNorm = Symbol("System`Norm")
@@ -126,13 +131,18 @@
SymbolNumericQ = Symbol("System`NumericQ")
SymbolO = Symbol("System`O")
SymbolOptionValue = Symbol("System`OptionValue")
+SymbolOptional = Symbol("System`Optional")
SymbolOptions = Symbol("System`Options")
+SymbolOptionsPattern = Symbol("System`OptionsPattern")
SymbolOr = Symbol("System`Or")
SymbolOut = Symbol("System`Out")
SymbolOutputForm = Symbol("System`OutputForm")
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")
SymbolPi = Symbol("System`Pi")
SymbolPiecewise = Symbol("System`Piecewise")
@@ -146,6 +156,8 @@
SymbolRe = Symbol("System`Re")
SymbolReal = Symbol("System`Real")
SymbolRealDigits = Symbol("System`RealDigits")
+SymbolRepeated = Symbol("System`Repeated")
+SymbolRepeatedNull = Symbol("System`RepeatedNull")
SymbolReturn = Symbol("System`Return")
SymbolRound = Symbol("System`Round")
SymbolRow = Symbol("System`Row")
@@ -167,6 +179,7 @@
SymbolStringForm = Symbol("System`StringForm")
SymbolStringQ = Symbol("System`StringQ")
SymbolStyle = Symbol("System`Style")
+SymbolSubValues = Symbol("System`SubValues")
SymbolSubsetQ = Symbol("System`SubsetQ")
SymbolSubtract = Symbol("System`Subtract")
SymbolSubscriptBox = Symbol("System`SubscriptBox")
@@ -181,4 +194,5 @@
SymbolUndefined = Symbol("System`Undefined")
SymbolUnequal = Symbol("System`Unequal")
SymbolUnevaluated = Symbol("System`Unevaluated")
+SymbolUpValues = Symbol("System`UpValues")
SymbolXor = Symbol("System`Xor")
diff --git a/mathics/core/util.py b/mathics/core/util.py
index 0dc06064e..b1bfef55e 100644
--- a/mathics/core/util.py
+++ b/mathics/core/util.py
@@ -64,6 +64,12 @@ def decide(chosen, not_chosen, rest, count):
def subranges(
items, min_count, max, flexible_start=False, included=None, less_first=False
):
+ """
+ generator that yields possible divisions of items as
+ ([items_inside],([previos_items],[remaining_items]))
+ with items_inside of variable lengths.
+ If flexible_start, then [previos_items] also has a variable size.
+ """
# TODO: take into account included
if max is None:
diff --git a/test/test_rules_patterns.py b/test/builtin/atomic/test_symbols.py
similarity index 51%
rename from test/test_rules_patterns.py
rename to test/builtin/atomic/test_symbols.py
index b1c7ac476..bba780e0c 100644
--- a/test/test_rules_patterns.py
+++ b/test/builtin/atomic/test_symbols.py
@@ -1,5 +1,9 @@
# -*- coding: utf-8 -*-
-from .helper import check_evaluation
+"""
+Unit tests from mathics.builtin.atomic.symbols.
+"""
+
+from test.helper import check_evaluation
def test_downvalues():
@@ -21,25 +25,3 @@ def test_downvalues():
),
):
check_evaluation(str_expr, str_expected, message)
-
-
-def test_blank():
- for str_expr, str_expected, message in (
- (
- "g[i] /. _[i] :> a",
- "a",
- "Issue #203",
- ),
- ):
- check_evaluation(str_expr, str_expected, message)
-
-
-def test_complex_rule():
- for str_expr, str_expected, message in (
- (
- "a == d b + d c /. a_ x_ + a_ y_ -> a (x + y)",
- "a == (b + c) d",
- "Issue #212",
- ),
- ):
- check_evaluation(str_expr, str_expected, message)
diff --git a/test/builtin/test_assignment.py b/test/builtin/test_assignment.py
index ca775564e..cb4e42d61 100644
--- a/test/builtin/test_assignment.py
+++ b/test/builtin/test_assignment.py
@@ -1,8 +1,17 @@
# -*- coding: utf-8 -*-
+import os
+
import pytest
from test.helper import check_evaluation, session
from mathics_scanner.errors import IncompleteSyntaxError
+DEBUGASSIGN = int(os.environ.get("DEBUGSET", "0")) == 1
+
+if DEBUGASSIGN:
+ skip_or_fail = pytest.mark.xfail
+else:
+ skip_or_fail = pytest.mark.skip
+
str_test_set_with_oneidentity = """
SetAttributes[SUNIndex, {OneIdentity}];
@@ -149,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,
@@ -173,6 +234,81 @@ def test_set_and_clear(str_expr, str_expected, msg):
)
+@pytest.mark.parametrize(
+ ("str_expr", "str_expected", "msg"),
+ [
+ (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]]}",
+ "The arguments on the LHS are evaluated before the assignment",
+ ),
+ (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",
+ ),
+ (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",
+ ),
+ (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",
+ ),
+ (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",
+ ),
+ ],
+)
+@skip_or_fail
+def test_set_and_clear_to_fix(str_expr, str_expected, msg):
+ """
+ Test calls to Set, Clear and ClearAll. If
+ str_expr is None, the session is reset,
+ in a way that the next test run over a fresh
+ environment.
+ """
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=msg,
+ )
+
+
@pytest.mark.parametrize(
("str_expr", "str_expected", "message", "out_msgs"),
[
diff --git a/test/builtin/test_attributes.py b/test/builtin/test_attributes.py
new file mode 100644
index 000000000..175deeaeb
--- /dev/null
+++ b/test/builtin/test_attributes.py
@@ -0,0 +1,206 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests from mathics.builtin.attributes.
+"""
+
+import os
+import pytest
+
+from test.helper import check_evaluation
+
+DEBUGRULESPAT = int(os.environ.get("DEBUGRULESPAT", "0")) == 1
+
+if DEBUGRULESPAT:
+ skip_or_fail = pytest.mark.xfail
+else:
+ skip_or_fail = pytest.mark.skip
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "str_expected", "msg"),
+ [
+ (None, None, None),
+ # F has the attribute, but G doesn't.
+ ("SetAttributes[F, OneIdentity]", "Null", None),
+ ("SetAttributes[r, Flat]", "Null", None),
+ ("SetAttributes[s, Flat]", "Null", None),
+ ("SetAttributes[s, OneIdentity]", "Null", None),
+ ("MatchQ[x, F[y_]]", "False", "With OneIdentity"),
+ ("MatchQ[x, G[y_]]", "False", "Without OneIdentity"),
+ ("MatchQ[x, F[x_:0,y_]]", "True", "With OneIdentity, and Default"),
+ ("MatchQ[x, G[x_:0,y_]]", "False", "Without OneIdentity, and Default"),
+ ("MatchQ[F[x], F[x_:0,y_]]", "True", "With OneIdentity, and Default"),
+ ("MatchQ[G[x], G[x_:0,y_]]", "True", "Without OneIdentity, and Default"),
+ ("MatchQ[F[F[F[x]]], F[x_:0,y_]]", "True", "With OneIdentity, nested"),
+ ("MatchQ[G[G[G[x]]], G[x_:0,y_]]", "True", "Without OneIdentity, nested"),
+ ("MatchQ[F[3, F[F[x]]], F[x_:0,y_]]", "True", "With OneIdentity, nested"),
+ ("MatchQ[G[3, G[G[x]]], G[x_:0,y_]]", "True", "Without OneIdentity, nested"),
+ (
+ "MatchQ[x, F[x1_:0, F[x2_:0,y_]]]",
+ "True",
+ "With OneIdentity, pattern nested",
+ ),
+ (
+ "MatchQ[x, G[x1_:0, G[x2_:0,y_]]]",
+ "False",
+ "With OneIdentity, pattern nested",
+ ),
+ (
+ "MatchQ[x, F[x1___:0, F[x2_:0,y_]]]",
+ "True",
+ "With OneIdentity, pattern nested",
+ ),
+ (
+ "MatchQ[x, G[x1___:0, G[x2_:0,y_]]]",
+ "False",
+ "With OneIdentity, pattern nested",
+ ),
+ ("MatchQ[x, F[F[x2_:0,y_],x1_:0]]", "True", "With OneIdentity, pattern nested"),
+ (
+ "MatchQ[x, G[G[x2_:0,y_],x1_:0]]",
+ "False",
+ "With OneIdentity, pattern nested",
+ ),
+ ("MatchQ[x, F[x_.,y_]]", "False", "With OneIdentity, and Optional, no default"),
+ (
+ "MatchQ[x, G[x_.,y_]]",
+ "False",
+ "Without OneIdentity, and Optional, no default",
+ ),
+ ("Default[F, 1]=1.", "1.", None),
+ ("Default[G, 1]=2.", "2.", None),
+ ("MatchQ[x, F[x_.,y_]]", "True", "With OneIdentity, and Optional, default"),
+ ("MatchQ[x, G[x_.,y_]]", "False", "Without OneIdentity, and Optional, default"),
+ ("MatchQ[F[F[H[y]]],F[x_:0,u_H]]", "False", None),
+ ("MatchQ[G[G[H[y]]],G[x_:0,u_H]]", "False", None),
+ ("MatchQ[F[p, F[p, H[y]]],F[x_:0,u_H]]", "False", None),
+ ("MatchQ[G[p, G[p, H[y]]],G[x_:0,u_H]]", "False", None),
+ (None, None, None),
+ ],
+)
+def test_one_identity(str_expr, str_expected, msg):
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=msg,
+ )
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "str_expected", "msg"),
+ [
+ (None, None, None),
+ # F has the attribute, but G doesn't.
+ ("SetAttributes[F, OneIdentity]", "Null", None),
+ ("SetAttributes[r, Flat]", "Null", None),
+ ("SetAttributes[s, Flat]", "Null", None),
+ ("SetAttributes[s, OneIdentity]", "Null", None),
+ # Replace also takes into account the OneIdentity attribute,
+ # and also modifies the interpretation of the Flat attribute.
+ (
+ "F[a,b,b,c]/.F[x_,x_]->Fp[x]",
+ "F[a, b, b, c]",
+ "https://reference.wolfram.com/language/tutorial/Patterns.html",
+ ),
+ # When fixed, Add the difference between the next two as a
+ # doctest example.
+ (
+ "r[a,b,b,c]/.r[x_,x_]->rp[x]",
+ "r[a, rp[r[b]], c]",
+ "https://reference.wolfram.com/language/tutorial/Patterns.html",
+ ),
+ (
+ "s[a,b,b,c]/.s[x_,x_]->sp[x]",
+ "s[a, rp[b], c]",
+ "https://reference.wolfram.com/language/tutorial/Patterns.html",
+ ),
+ (None, None, None),
+ ],
+)
+@skip_or_fail
+def test_one_identity_stil_failing(str_expr, str_expected, msg):
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=msg,
+ )
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "str_expected", "msg"),
+ [
+ (None, None, None),
+ # F has the attribute, but G doesn't.
+ ("SetAttributes[F, OneIdentity]", "Null", None),
+ ("SetAttributes[r, Flat]", "Null", None),
+ ("SetAttributes[s, Flat]", "Null", None),
+ ("SetAttributes[s, OneIdentity]", "Null", None),
+ ("MatchQ[x, F[y_]]", "False", "With OneIdentity"),
+ ("MatchQ[x, G[y_]]", "False", "Without OneIdentity"),
+ ("MatchQ[x, F[x_:0,y_]]", "True", "With OneIdentity, and Default"),
+ ("MatchQ[x, G[x_:0,y_]]", "False", "Without OneIdentity, and Default"),
+ ("MatchQ[F[x], F[x_:0,y_]]", "True", "With OneIdentity, and Default"),
+ ("MatchQ[G[x], G[x_:0,y_]]", "True", "Without OneIdentity, and Default"),
+ ("MatchQ[F[F[F[x]]], F[x_:0,y_]]", "True", "With OneIdentity, nested"),
+ ("MatchQ[G[G[G[x]]], G[x_:0,y_]]", "True", "Without OneIdentity, nested"),
+ ("MatchQ[F[3, F[F[x]]], F[x_:0,y_]]", "True", "With OneIdentity, nested"),
+ ("MatchQ[G[3, G[G[x]]], G[x_:0,y_]]", "True", "Without OneIdentity, nested"),
+ (
+ "MatchQ[x, F[x1_:0, F[x2_:0,y_]]]",
+ "True",
+ "With OneIdentity, pattern nested",
+ ),
+ (
+ "MatchQ[x, G[x1_:0, G[x2_:0,y_]]]",
+ "False",
+ "With OneIdentity, pattern nested",
+ ),
+ (
+ "MatchQ[x, F[x1___:0, F[x2_:0,y_]]]",
+ "True",
+ "With OneIdentity, pattern nested",
+ ),
+ (
+ "MatchQ[x, G[x1___:0, G[x2_:0,y_]]]",
+ "False",
+ "With OneIdentity, pattern nested",
+ ),
+ ("MatchQ[x, F[F[x2_:0,y_],x1_:0]]", "True", "With OneIdentity, pattern nested"),
+ (
+ "MatchQ[x, G[G[x2_:0,y_],x1_:0]]",
+ "False",
+ "With OneIdentity, pattern nested",
+ ),
+ ("MatchQ[x, F[x_.,y_]]", "False", "With OneIdentity, and Optional, no default"),
+ (
+ "MatchQ[x, G[x_.,y_]]",
+ "False",
+ "Without OneIdentity, and Optional, no default",
+ ),
+ ("Default[F, 1]=1.", "1.", None),
+ ("Default[G, 1]=2.", "2.", None),
+ ("MatchQ[x, F[x_.,y_]]", "True", "With OneIdentity, and Optional, default"),
+ ("MatchQ[x, G[x_.,y_]]", "False", "Without OneIdentity, and Optional, default"),
+ ("MatchQ[F[F[H[y]]],F[x_:0,u_H]]", "False", None),
+ ("MatchQ[G[G[H[y]]],G[x_:0,u_H]]", "False", None),
+ ("MatchQ[F[p, F[p, H[y]]],F[x_:0,u_H]]", "False", None),
+ ("MatchQ[G[p, G[p, H[y]]],G[x_:0,u_H]]", "False", None),
+ (None, None, None),
+ ],
+)
+@skip_or_fail
+def test_one_identity_stil_failing(str_expr, str_expected, msg):
+ check_evaluation(
+ str_expr,
+ str_expected,
+ to_string_expr=True,
+ to_string_expected=True,
+ hold_expected=True,
+ failure_message=msg,
+ )
diff --git a/test/builtin/test_patterns.py b/test/builtin/test_patterns.py
new file mode 100644
index 000000000..9fb3d8a0f
--- /dev/null
+++ b/test/builtin/test_patterns.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+"""
+Unit tests from mathics.builtin.patterns.
+"""
+
+from test.helper import check_evaluation
+
+
+def test_blank():
+ for str_expr, str_expected, message in (
+ (
+ "g[i] /. _[i] :> a",
+ "a",
+ "Issue #203",
+ ),
+ ):
+ check_evaluation(str_expr, str_expected, message)
+
+
+def test_replace_all():
+ for str_expr, str_expected, message in (
+ (
+ "a == d b + d c /. a_ x_ + a_ y_ -> a (x + y)",
+ "a == (b + c) d",
+ "Issue #212",
+ ),
+ ):
+ check_evaluation(str_expr, str_expected, message)
diff --git a/test/test_context.py b/test/test_context.py
index aec68fc7d..480121de0 100644
--- a/test/test_context.py
+++ b/test/test_context.py
@@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-
from .helper import check_evaluation, reset_session
+import pytest
+
from mathics_scanner.errors import IncompleteSyntaxError
@@ -60,37 +62,37 @@ def test_context1():
check_evaluation("bar[]", "42", to_string_expr=False, to_string_expected=False)
-def test_context2():
- nomessage = tuple([])
- for expr, expected, lst_messages, msg in [
+@pytest.mark.parametrize(
+ ("expr", "expected", "lst_messages", "msg"),
+ [
(
"""globalvarY = 37;""",
None,
- nomessage,
+ None,
"set the value of a global symbol",
),
(
"""globalvarZ = 37;""",
None,
- nomessage,
+ None,
"set the value of a global symbol",
),
(
"""BeginPackage["apackage`"];""",
None,
- nomessage,
+ None,
"Start a context. Add it to the context path",
),
(
"""Minus::usage=" usage string setted in the package for Minus";""",
None,
- nomessage,
+ None,
"set the usage string for a protected symbol ->no error",
),
(
"""Minus::mymessage=" custom message string for Minus";""",
None,
- nomessage,
+ None,
"set a custom message for a protected symbol ->no error",
),
(
@@ -106,44 +108,44 @@ def test_context2():
(
"""X::usage = "package variable";""",
None,
- nomessage,
+ None,
"set the usage string for a package variable",
),
(
"""globalvarZ::usage = "a global variable";""",
None,
- nomessage,
+ None,
"set the usage string for a global symbol",
),
(
"""globalvarZ = 57;""",
None,
- nomessage,
+ None,
"reset the value of a global symbol",
),
- ("""B = 6;""", None, nomessage, "Set a symbol value in the package context"),
+ ("""B = 6;""", None, None, "Set a symbol value in the package context"),
(
"""Begin["`implementation`"];""",
None,
- nomessage,
+ None,
"Start a context. Do not add it to the context path",
),
(
"""{Context[A], Context[B], Context[X], Context[globalvarY], Context[globalvarZ]}""",
"""{"apackage`implementation`", "apackage`", "apackage`", "apackage`implementation`", "apackage`"}""",
- nomessage,
+ None,
None, # "context of the variables"
),
(
"""globalvarY::usage = "a global variable";""",
None,
- nomessage,
+ None,
"set the usage string for a global symbol",
),
(
"""globalvarY = 97;""",
None,
- nomessage,
+ None,
"reset the value of a global symbol",
),
(
@@ -159,115 +161,119 @@ def test_context2():
(
"""Plus::usage=" usage string setted in the package for Plus";""",
None,
- nomessage,
+ None,
"set the usage string for a protected symbol ->no error",
),
(
"""Plus::mymessage=" custom message string for Plus";""",
None,
- nomessage,
+ None,
"set a custom message for a protected symbol ->no error",
),
- ("""A = 7;""", None, nomessage, "Set a symbol value in the context"),
- ("""X = 9;""", None, nomessage, "set the value of the package variable"),
- ("""End[];""", None, nomessage, "go back to the previous context"),
+ ("""A = 7;""", None, None, "Set a symbol value in the context"),
+ ("""X = 9;""", None, None, "set the value of the package variable"),
+ ("""End[];""", None, None, "go back to the previous context"),
(
"""{Context[A], Context[B], Context[X], Context[globalvarY], Context[globalvarZ]}""",
"""{"apackage`", "apackage`", "apackage`", "apackage`", "apackage`"}""",
- nomessage,
+ None,
None, # "context of the variables in the package"
),
(
"""EndPackage[];""",
None,
- nomessage,
+ None,
"go back to the previous context. Keep the context in the contextpath",
),
(
"""{Context[A], Context[B], Context[X], Context[globalvarY], Context[globalvarZ]}""",
"""{"apackage`", "apackage`", "apackage`", "apackage`", "apackage`"}""",
- nomessage,
+ None,
None, # "context of the variables at global level"
),
- ("""A""", "A", nomessage, "A is not in any context of the context path. "),
- ("""B""", "6", nomessage, "B is in a context of the context path"),
- ("""Global`globalvarY""", "37", nomessage, ""),
+ ("""A""", "A", None, "A is not in any context of the context path. "),
+ ("""B""", "6", None, "B is in a context of the context path"),
+ ("""Global`globalvarY""", "37", None, ""),
(
"""Global`globalvarY::usage""",
"Global`globalvarY::usage",
- nomessage,
+ None,
"In WMA, the value would be set in the package",
),
- ("""Global`globalvarZ""", "37", nomessage, "the value set inside the package"),
+ ("""Global`globalvarZ""", "37", None, "the value set inside the package"),
(
"""Global`globalvarZ::usage""",
"Global`globalvarZ::usage",
- nomessage,
+ None,
"not affected by the package",
),
- ("""globalvarY""", "apackage`globalvarY", nomessage, ""),
+ ("""globalvarY""", "apackage`globalvarY", None, ""),
(
"""globalvarY::usage""",
"apackage`globalvarY::usage",
- nomessage,
+ None,
"In WMA, the value would be set in the package",
),
- ("""globalvarZ""", "57", nomessage, "the value set inside the package"),
+ ("""globalvarZ""", "57", None, "the value set inside the package"),
(
"""globalvarZ::usage""",
'"a global variable"',
- nomessage,
+ None,
"not affected by the package",
),
- ("""X""", "9", nomessage, "X is in a context of the context path"),
+ ("""X""", "9", None, "X is in a context of the context path"),
(
"""X::usage""",
'"package variable"',
- nomessage,
+ None,
"X is in a context of the context path",
),
(
"""apackage`implementation`A""",
"7",
- nomessage,
+ None,
"get A using its fully qualified name",
),
- ("""apackage`B""", "6", nomessage, "get B using its fully qualified name"),
+ ("""apackage`B""", "6", None, "get B using its fully qualified name"),
(
"""Plus::usage""",
' " usage string setted in the package for Plus" ',
- nomessage,
+ None,
"custom usage for Plus",
),
(
"""Minus::usage""",
'" usage string setted in the package for Minus"',
- nomessage,
+ None,
"custom usage for Minus",
),
(
"""Plus::mymessage""",
'" custom message string for Plus"',
- nomessage,
+ None,
"custom message for Plus",
),
(
"""Minus::mymessage""",
'" custom message string for Minus"',
- nomessage,
+ None,
"custom message for Minus",
),
- ]:
+ (None, None, None, None),
+ ],
+)
+def test_context2(expr, expected, lst_messages, msg):
+ if expr is not None and expected is None:
+ expected = "System`Null"
- if expected is None:
- expected = "System`Null"
- check_evaluation(
- expr,
- expected,
- failure_message=msg,
- to_string_expr=False,
- to_string_expected=False,
- expected_messages=lst_messages,
- hold_expected=True,
- )
- reset_session()
+ if lst_messages is None:
+ lst_messages = tuple([])
+ check_evaluation(
+ expr,
+ expected,
+ failure_message=msg,
+ to_string_expr=False,
+ to_string_expected=False,
+ expected_messages=lst_messages,
+ hold_expected=True,
+ )