diff --git a/mathics/builtin/assignments/assignment.py b/mathics/builtin/assignments/assignment.py
index 856942f18..7f3008bf1 100644
--- a/mathics/builtin/assignments/assignment.py
+++ b/mathics/builtin/assignments/assignment.py
@@ -4,8 +4,14 @@
"""
-from mathics.builtin.assignments.internals import _SetOperator
from mathics.builtin.base import BinaryOperator, Builtin
+from mathics.core.assignment import (
+ ASSIGNMENT_FUNCTION_MAP,
+ AssignmentException,
+ assign_store_rules_by_tag,
+ normalize_lhs,
+)
+
from mathics.core.attributes import (
A_HOLD_ALL,
A_HOLD_FIRST,
@@ -18,6 +24,39 @@
from mathics.core.systemsymbols import SymbolFailed
+class _SetOperator:
+ """
+ This is the base class for assignment Builtin operators.
+
+ Special cases are determined by the head of the expression. Then
+ they are processed by specific routines, which are poke from
+ the ``ASSIGNMENT_FUNCTION_MAP`` dict.
+ """
+
+ # FIXME:
+ # Assigment is determined by the LHS.
+ # Are there a larger patterns or natural groupings that we are missing?
+ # For example, it might be that it
+ # we can key off of some attributes or other properties of the
+ # LHS of a builtin, instead of listing all of the builtins in that class
+ # (which may miss some).
+ # Below, we key on a string, but Symbol is more correct.
+
+ def assign(self, lhs, rhs, evaluation, tags=None, upset=False):
+ lhs, lookup_name = normalize_lhs(lhs, evaluation)
+ try:
+ # Using a builtin name, find which assignment procedure to perform,
+ # and then call that function.
+ assignment_func = ASSIGNMENT_FUNCTION_MAP.get(lookup_name, None)
+ if assignment_func:
+ return assignment_func(self, lhs, rhs, evaluation, tags, upset)
+
+ return assign_store_rules_by_tag(self, lhs, rhs, evaluation, tags, upset)
+ except AssignmentException:
+
+ return False
+
+
class Set(BinaryOperator, _SetOperator):
"""
diff --git a/mathics/builtin/assignments/clear.py b/mathics/builtin/assignments/clear.py
index e46dd3ab3..8dbda4ff5 100644
--- a/mathics/builtin/assignments/clear.py
+++ b/mathics/builtin/assignments/clear.py
@@ -38,10 +38,9 @@
SymbolUpValues,
)
+from mathics.core.assignment import is_protected
from mathics.core.atoms import String
-from mathics.builtin.assignments.internals import is_protected
-
class Clear(Builtin):
"""
diff --git a/mathics/builtin/assignments/types.py b/mathics/builtin/assignments/types.py
index 6a5ebb99a..3d340f989 100644
--- a/mathics/builtin/assignments/types.py
+++ b/mathics/builtin/assignments/types.py
@@ -8,8 +8,7 @@
from mathics.builtin.base import Builtin
-from mathics.builtin.assignments.internals import get_symbol_values
-
+from mathics.core.assignment import get_symbol_values
from mathics.core.attributes import A_HOLD_ALL, A_PROTECTED
diff --git a/mathics/builtin/assignments/upvalues.py b/mathics/builtin/assignments/upvalues.py
index 817a74272..7cecd29cc 100644
--- a/mathics/builtin/assignments/upvalues.py
+++ b/mathics/builtin/assignments/upvalues.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
-from mathics.builtin.assignments.internals import _SetOperator
+from mathics.builtin.assignments.assignment import _SetOperator
from mathics.builtin.base import BinaryOperator
from mathics.core.attributes import (
A_HOLD_ALL,
diff --git a/mathics/builtin/atomic/symbols.py b/mathics/builtin/atomic/symbols.py
index 753e4e649..fe02ed14e 100644
--- a/mathics/builtin/atomic/symbols.py
+++ b/mathics/builtin/atomic/symbols.py
@@ -7,7 +7,6 @@
import re
-from mathics.builtin.assignments.internals import get_symbol_values
from mathics.builtin.base import (
Builtin,
PrefixOperator,
@@ -16,6 +15,7 @@
from mathics.builtin.atomic.strings import to_regex
+from mathics.core.assignment import get_symbol_values
from mathics.core.atoms import String
from mathics.core.attributes import (
diff --git a/mathics/builtin/attributes.py b/mathics/builtin/attributes.py
index 05a72523a..70a0a3777 100644
--- a/mathics/builtin/attributes.py
+++ b/mathics/builtin/attributes.py
@@ -15,7 +15,7 @@
from mathics.builtin.base import Predefined, Builtin
-from mathics.builtin.assignments.internals import get_symbol_list
+from mathics.core.assignment import get_symbol_list
from mathics.core.atoms import String
from mathics.core.attributes import (
attributes_bitset_to_list,
@@ -745,6 +745,13 @@ def eval(self, symbols, attributes, evaluation):
evaluation.message("Attributes", "attnf", Symbol(value))
return SymbolNull
+ def eval_arg_error(self, args, evaluation):
+ "SetAttributes[args___]"
+ # We should only come here when we don't have 2 args, because
+ # eval() should be called otherwise.
+ nargs = len(args.elements) if isinstance(args, Expression) else 1
+ evaluation.message("SetAttributes", "argrx", "SetAttributes", nargs, 2)
+
class Unprotect(Builtin):
"""
diff --git a/mathics/builtin/base.py b/mathics/builtin/base.py
index b6faf3aac..2e1cbd942 100644
--- a/mathics/builtin/base.py
+++ b/mathics/builtin/base.py
@@ -818,7 +818,7 @@ def get_head_name(self):
def get_lookup_name(self):
return self.get_name()
- def get_sort_key(self) -> tuple:
+ def get_sort_key(self, pattern_sort=False) -> tuple:
return self.to_expression().get_sort_key()
def get_string_value(self):
@@ -826,7 +826,7 @@ def get_string_value(self):
def sameQ(self, expr) -> bool:
"""Mathics SameQ"""
- return expr.sameQ(self)
+ return self.to_expression().sameQ(expr)
def do_format(self, evaluation, format):
return self
diff --git a/mathics/builtin/files_io/filesystem.py b/mathics/builtin/files_io/filesystem.py
index a11299cb5..9cf5da9de 100644
--- a/mathics/builtin/files_io/filesystem.py
+++ b/mathics/builtin/files_io/filesystem.py
@@ -64,7 +64,7 @@
class AbsoluteFileName(Builtin):
"""
- - 'AbsoluteFileName["$name$"]'
+
- 'AbsoluteFileName["$name$"]'
- returns the absolute version of the given filename.
@@ -82,7 +82,7 @@ class AbsoluteFileName(Builtin):
}
summary_text = "absolute path"
- def apply(self, name, evaluation):
+ def eval(self, name, evaluation):
"AbsoluteFileName[name_]"
py_name = name.to_python()
@@ -106,7 +106,7 @@ def apply(self, name, evaluation):
class BaseDirectory(Predefined):
"""
- - '$UserBaseDirectory'
+
- '$UserBaseDirectory'
- returns the folder where user configurations are stored.
@@ -125,13 +125,12 @@ def evaluate(self, evaluation):
class CopyDirectory(Builtin):
"""
- - 'CopyDirectory["$dir1$", "$dir2$"]'
+
- 'CopyDirectory["$dir1$", "$dir2$"]'
- copies directory $dir1$ to $dir2$.
"""
messages = {
- "argr": "called with `1` argument; 2 arguments are expected.",
"fstr": (
"File specification `1` is not a string of " "one or more characters."
),
@@ -140,12 +139,12 @@ class CopyDirectory(Builtin):
}
summary_text = "copy a directory into a new path"
- def apply(self, dirs, evaluation):
+ def eval(self, dirs, evaluation):
"CopyDirectory[dirs__]"
seq = dirs.get_sequence()
if len(seq) != 2:
- evaluation.message("CopyDirectory", "argr", len(seq))
+ evaluation.message("CopyDirectory", "argr", "CopyDirectory", 2)
return
(dir1, dir2) = (s.to_python() for s in seq)
@@ -174,7 +173,7 @@ def apply(self, dirs, evaluation):
class CopyFile(Builtin):
"""
- - 'CopyFile["$file1$", "$file2$"]'
+
- 'CopyFile["$file1$", "$file2$"]'
- copies $file1$ to $file2$.
@@ -192,7 +191,7 @@ class CopyFile(Builtin):
}
summary_text = "copy a file into a new path"
- def apply(self, source, dest, evaluation):
+ def eval(self, source, dest, evaluation):
"CopyFile[source_, dest_]"
py_source = source.to_python()
@@ -233,9 +232,10 @@ def apply(self, source, dest, evaluation):
class CreateDirectory(Builtin):
"""
- - 'CreateDirectory["$dir$"]'
+
- 'CreateDirectory["$dir$"]'
- creates a directory called $dir$.
-
- 'CreateDirectory[]'
+
+
- 'CreateDirectory[]'
- creates a temporary directory.
@@ -261,7 +261,7 @@ class CreateDirectory(Builtin):
}
summary_text = "create a directory"
- def apply(self, dirname, evaluation, options):
+ def eval(self, dirname, evaluation, options):
"CreateDirectory[dirname_, OptionsPattern[CreateDirectory]]"
expr = to_expression("CreateDirectory", dirname)
@@ -285,7 +285,7 @@ def apply(self, dirname, evaluation, options):
return String(osp.abspath(py_dirname))
- def apply_empty(self, evaluation, options):
+ def eval_empty(self, evaluation, options):
"CreateDirectory[OptionsPattern[CreateDirectory]]"
dirname = tempfile.mkdtemp(prefix="m", dir=TMP_DIR)
return String(dirname)
@@ -294,10 +294,11 @@ def apply_empty(self, evaluation, options):
class CreateFile(Builtin):
"""
- - 'CreateFile["filename"]'
-
- Creates a file named "filename" temporary file, but do not open it.
-
- 'CreateFile[]'
-
- Creates a temporary file, but do not open it.
+
- 'CreateFile["filename"]'
+
- Creates a file named "filename" temporary file, but do not open it.
+
+
- 'CreateFile[]'
+
- Creates a temporary file, but do not open it.
"""
@@ -311,7 +312,7 @@ class CreateFile(Builtin):
}
summary_text = "create a file"
- def apply(self, filename, evaluation, **options):
+ def eval(self, filename, evaluation, **options):
"CreateFile[filename_String, OptionsPattern[CreateFile]]"
try:
# TODO: Implement options
@@ -329,14 +330,14 @@ def apply(self, filename, evaluation, **options):
class CreateTemporary(Builtin):
"""
- - 'CreateTemporary[]'
-
- Creates a temporary file, but do not open it.
+
- 'CreateTemporary[]'
+
- Creates a temporary file, but do not open it.
"""
summary_text = "create a temporary file"
- def apply_0(self, evaluation):
+ def eval_0(self, evaluation):
"CreateTemporary[]"
try:
res = create_temporary_file()
@@ -348,7 +349,7 @@ def apply_0(self, evaluation):
class DeleteDirectory(Builtin):
"""
- - 'DeleteDirectory["$dir$"]'
+
- 'DeleteDirectory["$dir$"]'
- deletes a directory called $dir$.
@@ -375,7 +376,7 @@ class DeleteDirectory(Builtin):
}
summary_text = "delete a directory"
- def apply(self, dirname, evaluation, options):
+ def eval(self, dirname, evaluation, options):
"DeleteDirectory[dirname_, OptionsPattern[DeleteDirectory]]"
expr = to_expression("DeleteDirectory", dirname)
@@ -410,9 +411,10 @@ def apply(self, dirname, evaluation, options):
class DeleteFile(Builtin):
"""
- - 'Delete["$file$"]'
+
- 'Delete["$file$"]'
- deletes $file$.
-
- 'Delete[{"$file1$", "$file2$", ...}]'
+
+
- 'Delete[{"$file1$", "$file2$", ...}]'
- deletes a list of files.
@@ -433,7 +435,7 @@ class DeleteFile(Builtin):
}
summary_text = "delete a file"
- def apply(self, filename, evaluation):
+ def eval(self, filename, evaluation):
"DeleteFile[filename_]"
py_path = filename.to_python()
@@ -474,7 +476,7 @@ def apply(self, filename, evaluation):
class Directory(Builtin):
"""
- - 'Directory[]'
+
- 'Directory[]'
- returns the current working directory.
@@ -484,7 +486,7 @@ class Directory(Builtin):
summary_text = "current working directory"
- def apply(self, evaluation):
+ def eval(self, evaluation):
"Directory[]"
result = os.getcwd()
return String(result)
@@ -493,7 +495,7 @@ def apply(self, evaluation):
class DirectoryName(Builtin):
"""
- - 'DirectoryName["$name$"]'
+
- 'DirectoryName["$name$"]'
- extracts the directory name from a filename.
@@ -531,7 +533,7 @@ class DirectoryName(Builtin):
}
summary_text = "directory part of a filename"
- def apply_with_n(self, name, n, evaluation, options):
+ def eval_with_n(self, name, n, evaluation, options):
"DirectoryName[name_, n_, OptionsPattern[DirectoryName]]"
if n is None:
@@ -557,15 +559,15 @@ def apply_with_n(self, name, n, evaluation, options):
return String(result)
- def apply(self, name, evaluation, options):
+ def eval(self, name, evaluation, options):
"DirectoryName[name_, OptionsPattern[DirectoryName]]"
- return self.apply_with_n(name, None, evaluation, options)
+ return self.eval_with_n(name, None, evaluation, options)
class DirectoryStack(Builtin):
"""
- - 'DirectoryStack[]'
+
- 'DirectoryStack[]'
- returns the directory stack.
@@ -575,7 +577,7 @@ class DirectoryStack(Builtin):
summary_text = "list the sequence of current directories in use"
- def apply(self, evaluation):
+ def eval(self, evaluation):
"DirectoryStack[]"
global DIRECTORY_STACK
return from_python(DIRECTORY_STACK)
@@ -584,7 +586,7 @@ def apply(self, evaluation):
class DirectoryQ(Builtin):
"""
- - 'DirectoryQ["$name$"]'
+
- 'DirectoryQ["$name$"]'
- returns 'True' if the directory called $name$ exists and 'False' otherwise.
@@ -607,7 +609,7 @@ class DirectoryQ(Builtin):
}
summary_text = "test whether a path exists and is a directory"
- def apply(self, pathname, evaluation):
+ def eval(self, pathname, evaluation):
"DirectoryQ[pathname_]"
path = pathname.to_python()
@@ -626,7 +628,7 @@ def apply(self, pathname, evaluation):
class ExpandFileName(Builtin):
"""
- - 'ExpandFileName["$name$"]'
+
- 'ExpandFileName["$name$"]'
- expands $name$ to an absolute filename for your system.
@@ -639,7 +641,7 @@ class ExpandFileName(Builtin):
}
summary_text = "absolute path"
- def apply(self, name, evaluation):
+ def eval(self, name, evaluation):
"ExpandFileName[name_]"
py_name = name.to_python()
@@ -657,7 +659,7 @@ def apply(self, name, evaluation):
class File(Builtin):
"""
- - 'File["$file$"]'
+
- 'File["$file$"]'
- is a symbolic representation of an element in the local file system.
"""
@@ -668,7 +670,7 @@ class File(Builtin):
class FileBaseName(Builtin):
"""
- - 'FileBaseName["$file$"]'
+
- 'FileBaseName["$file$"]'
- gives the base name for the specified file name.
@@ -690,7 +692,7 @@ class FileBaseName(Builtin):
}
summary_text = "base name of the file"
- def apply(self, filename, evaluation, options):
+ def eval(self, filename, evaluation, options):
"FileBaseName[filename_String, OptionsPattern[FileBaseName]]"
path = filename.to_python()[1:-1]
@@ -701,7 +703,7 @@ def apply(self, filename, evaluation, options):
class FileByteCount(Builtin):
"""
- - 'FileByteCount[$file$]'
+
- 'FileByteCount[$file$]'
- returns the number of bytes in $file$.
@@ -714,7 +716,7 @@ class FileByteCount(Builtin):
}
summary_text = "length of the file"
- def apply(self, filename, evaluation):
+ def eval(self, filename, evaluation):
"FileByteCount[filename_]"
py_filename = filename.to_python()
if not (
@@ -745,7 +747,7 @@ def apply(self, filename, evaluation):
class FileDate(Builtin):
"""
- - 'FileDate[$file$, $types$]'
+
- 'FileDate[$file$, $types$]'
- returns the time and date at which the file was last modified.
@@ -796,7 +798,7 @@ class FileDate(Builtin):
}
summary_text = "date and time of the last change in a file"
- def apply(self, path, timetype, evaluation):
+ def eval(self, path, timetype, evaluation):
"FileDate[path_, timetype_]"
py_path, is_temparary_file = path_search(path.to_python()[1:-1])
@@ -839,15 +841,15 @@ def apply(self, path, timetype, evaluation):
return to_expression("DateList", Real(result))
- def apply_default(self, path, evaluation):
+ def eval_default(self, path, evaluation):
"FileDate[path_]"
- return self.apply(path, None, evaluation)
+ return self.eval(path, None, evaluation)
class FileExistsQ(Builtin):
"""
- - 'FileExistsQ["$file$"]'
+
- 'FileExistsQ["$file$"]'
- returns 'True' if $file$ exists and 'False' otherwise.
@@ -864,7 +866,7 @@ class FileExistsQ(Builtin):
}
summary_text = "test whether a file exists"
- def apply(self, filename, evaluation):
+ def eval(self, filename, evaluation):
"FileExistsQ[filename_]"
path = filename.to_python()
if not (isinstance(path, str) and path[0] == path[-1] == '"'):
@@ -882,7 +884,7 @@ def apply(self, filename, evaluation):
class FileExtension(Builtin):
"""
- - 'FileExtension["$file$"]'
+
- 'FileExtension["$file$"]'
- gives the extension for the specified file name.
@@ -903,7 +905,7 @@ class FileExtension(Builtin):
}
summary_text = "file extension"
- def apply(self, filename, evaluation, options):
+ def eval(self, filename, evaluation, options):
"FileExtension[filename_String, OptionsPattern[FileExtension]]"
path = filename.to_python()[1:-1]
filename_base, filename_ext = osp.splitext(path)
@@ -914,12 +916,14 @@ def apply(self, filename, evaluation, options):
class FileHash(Builtin):
"""
- - 'FileHash[$file$]'
+
- 'FileHash[$file$]'
- returns an integer hash for the given $file$.
-
- 'FileHash[$file$, $type$]'
+
+
- 'FileHash[$file$, $type$]'
- returns an integer hash of the specified $type$ for the given $file$.
- The types supported are "MD5", "Adler32", "CRC32", "SHA", "SHA224", "SHA256", "SHA384", and "SHA512".
-
- 'FileHash[$file$, $type$, $format$]'
+
+
- 'FileHash[$file$, $type$, $format$]'
- gives a hash code in the specified format.
@@ -961,7 +965,7 @@ class FileHash(Builtin):
}
summary_text = "compute a hash from the content of a file"
- def apply(self, filename, hashtype, format, evaluation):
+ def eval(self, filename, hashtype, format, evaluation):
"FileHash[filename_String, hashtype_String, format_String]"
py_filename = filename.get_string_value()
@@ -985,7 +989,7 @@ def apply(self, filename, hashtype, format, evaluation):
class FileInformation(Builtin):
"""
- - 'FileInformation["$file$"]'
+
- 'FileInformation["$file$"]'
- returns information about $file$.
@@ -1007,7 +1011,7 @@ class FileInformation(Builtin):
class FileNameDepth(Builtin):
"""
- - 'FileNameDepth["$name$"]'
+
- 'FileNameDepth["$name$"]'
- gives the number of path parts in the given filename.
@@ -1065,7 +1069,7 @@ class FileNameJoin(Builtin):
}
summary_text = "join parts into a path"
- def apply(self, pathlist, evaluation, options):
+ def eval(self, pathlist, evaluation, options):
"FileNameJoin[pathlist_List, OptionsPattern[FileNameJoin]]"
py_pathlist = pathlist.to_python()
@@ -1107,7 +1111,7 @@ def apply(self, pathlist, evaluation, options):
class FileType(Builtin):
"""
- - 'FileType["$file$"]'
+
- 'FileType["$file$"]'
- gives the type of a file, a string. This is typically 'File', 'Directory' or 'None'.
@@ -1130,7 +1134,7 @@ class FileType(Builtin):
}
summary_text = "type of a file"
- def apply(self, filename, evaluation):
+ def eval(self, filename, evaluation):
"FileType[filename_]"
if not isinstance(filename, String):
evaluation.message("FileType", "fstr", filename)
@@ -1151,7 +1155,7 @@ def apply(self, filename, evaluation):
class FindFile(Builtin):
"""
- - 'FindFile[$name$]'
+
- 'FindFile[$name$]'
- searches '$Path' for the given filename.
@@ -1175,7 +1179,7 @@ class FindFile(Builtin):
"search the path of of a file in the current directory and its subdirectories"
)
- def apply(self, name, evaluation):
+ def eval(self, name, evaluation):
"FindFile[name_]"
py_name = name.to_python()
@@ -1231,21 +1235,21 @@ class FileNames(Builtin):
}
summary_text = "list file names in the current directory"
- def apply_0(self, evaluation, **options):
+ def eval_0(self, evaluation, **options):
"""FileNames[OptionsPattern[FileNames]]"""
- return self.apply_3(
+ return self.eval_3(
String("*"), String(os.getcwd()), None, evaluation, **options
)
- def apply_1(self, forms, evaluation, **options):
+ def eval_1(self, forms, evaluation, **options):
"""FileNames[forms_, OptionsPattern[FileNames]]"""
- return self.apply_3(forms, String(os.getcwd()), None, evaluation, **options)
+ return self.eval_3(forms, String(os.getcwd()), None, evaluation, **options)
- def apply_2(self, forms, paths, evaluation, **options):
+ def eval_2(self, forms, paths, evaluation, **options):
"""FileNames[forms_, paths_, OptionsPattern[FileNames]]"""
- return self.apply_3(forms, paths, None, evaluation, **options)
+ return self.eval_3(forms, paths, None, evaluation, **options)
- def apply_3(self, forms, paths, n, evaluation, **options):
+ def eval_3(self, forms, paths, n, evaluation, **options):
"""FileNames[forms_, paths_, n_, OptionsPattern[FileNames]]"""
filenames = set()
# Building a list of forms
@@ -1358,7 +1362,7 @@ class FileNameSplit(Builtin):
summary_text = "split the file name in a list of parts"
- def apply(self, filename, evaluation, options):
+ def eval(self, filename, evaluation, options):
"FileNameSplit[filename_String, OptionsPattern[FileNameSplit]]"
path = filename.to_python()[1:-1]
@@ -1419,12 +1423,12 @@ class FileNameTake(Builtin):
}
summary_text = "take a part of the filename"
- def apply(self, filename, evaluation, options):
+ def eval(self, filename, evaluation, options):
"FileNameTake[filename_String, OptionsPattern[FileBaseName]]"
path = pathlib.Path(filename.to_python()[1:-1])
return String(path.name)
- def apply_n(self, filename, n, evaluation, options):
+ def eval_n(self, filename, n, evaluation, options):
"FileNameTake[filename_String, n_Integer, OptionsPattern[FileBaseName]]"
n_int = n.get_int_value()
parts = pathlib.Path(filename.to_python()[1:-1]).parts
@@ -1479,11 +1483,11 @@ class FindList(Builtin):
# TODO: Extra options AnchoredSearch, IgnoreCase RecordSeparators,
# WordSearch, WordSeparators this is probably best done with a regex
- def apply_without_n(self, filename, text, evaluation, options):
+ def eval_without_n(self, filename, text, evaluation, options):
"FindList[filename_, text_, OptionsPattern[FindList]]"
- return self.apply(filename, text, None, evaluation, options)
+ return self.eval(filename, text, None, evaluation, options)
- def apply(self, filename, text, n, evaluation, options):
+ def eval(self, filename, text, n, evaluation, options):
"FindList[filename_, text_, n_, OptionsPattern[FindList]]"
py_text = text.to_python()
py_name = filename.to_python()
@@ -1713,7 +1717,7 @@ class Needs(Builtin):
}
summary_text = "load a package if it is not already loaded"
- def apply(self, context, evaluation):
+ def eval(self, context, evaluation):
"Needs[context_String]"
contextstr = context.get_string_value()
if contextstr == "":
@@ -1789,7 +1793,7 @@ class ParentDirectory(Builtin):
}
summary_text = "parent directory of the current working directory"
- def apply(self, path, evaluation):
+ def eval(self, path, evaluation):
"ParentDirectory[path_]"
if not isinstance(path, String):
@@ -1848,7 +1852,6 @@ class RenameDirectory(Builtin):
"""
messages = {
- "argr": "called with `1` argument; 2 arguments are expected.",
"fstr": (
"File specification `1` is not a string of " "one or more characters."
),
@@ -1857,12 +1860,12 @@ class RenameDirectory(Builtin):
}
summary_text = "change the name of a directory"
- def apply(self, dirs, evaluation):
+ def eval(self, dirs, evaluation):
"RenameDirectory[dirs__]"
seq = dirs.get_sequence()
if len(seq) != 2:
- evaluation.message("RenameDirectory", "argr", len(seq))
+ evaluation.message("RenameDirectory", "argr", "RenameDirectory", 2)
return
(dir1, dir2) = (s.to_python() for s in seq)
@@ -1911,7 +1914,7 @@ class RenameFile(Builtin):
}
summary_text = "change the name of a file"
- def apply(self, source, dest, evaluation):
+ def eval(self, source, dest, evaluation):
"RenameFile[source_, dest_]"
py_source = source.to_python()
@@ -1963,7 +1966,7 @@ class ResetDirectory(Builtin):
}
summary_text = "return to the directory before the last SetDirectory call"
- def apply(self, evaluation):
+ def eval(self, evaluation):
"ResetDirectory[]"
try:
tmp = DIRECTORY_STACK.pop()
@@ -2020,7 +2023,7 @@ class SetDirectory(Builtin):
}
summary_text = "set the working directory"
- def apply(self, path, evaluation):
+ def eval(self, path, evaluation):
"SetDirectory[path_]"
if not isinstance(path, String):
@@ -2097,7 +2100,7 @@ class SetFileDate(Builtin):
}
summary_text = "set the access/modification time of a file in the filesystem"
- def apply(self, filename, datelist, attribute, evaluation):
+ def eval(self, filename, datelist, attribute, evaluation):
"SetFileDate[filename_, datelist_, attribute_]"
py_filename = filename.to_python()
@@ -2170,19 +2173,19 @@ def apply(self, filename, datelist, attribute, evaluation):
os.utime(py_filename, (osp.getatime(py_filename), stattime))
if py_attr == "All":
os.utime(py_filename, (stattime, stattime))
- except OSError as e:
+ except OSError: # as e:
# evaluation.message(...)
return SymbolFailed
return SymbolNull
- def apply_1arg(self, filename, evaluation):
+ def eval_1arg(self, filename, evaluation):
"SetFileDate[filename_]"
- return self.apply(filename, None, None, evaluation)
+ return self.eval(filename, None, None, evaluation)
- def apply_2arg(self, filename, datelist, evaluation):
+ def eval_2arg(self, filename, datelist, evaluation):
"SetFileDate[filename_, datelist_]"
- return self.apply(filename, datelist, None, evaluation)
+ return self.eval(filename, datelist, None, evaluation)
class TemporaryDirectory(Predefined):
@@ -2252,10 +2255,11 @@ def evaluate(self, evaluation):
class URLSave(Builtin):
"""
- - 'URLSave["url"]'
-
- Save "url" in a temporary file.
-
- 'URLSave["url", $filename$]'
-
- Save "url" in $filename$.
+
- 'URLSave["url"]'
+
- Save "url" in a temporary file.
+
+
- 'URLSave["url", $filename$]'
+
- Save "url" in $filename$.
"""
@@ -2265,11 +2269,11 @@ class URLSave(Builtin):
}
summary_text = "save the content of an URL"
- def apply_1(self, url, evaluation, **options):
+ def eval_1(self, url, evaluation, **options):
"URLSave[url_String, OptionsPattern[URLSave]]"
- return self.apply_2(url, None, evaluation, **options)
+ return self.eval_2(url, None, evaluation, **options)
- def apply_2(self, url, filename, evaluation, **options):
+ def eval_2(self, url, filename, evaluation, **options):
"URLSave[url_String, filename_, OptionsPattern[URLSave]]"
url = url.value
if filename is None:
diff --git a/mathics/builtin/lists.py b/mathics/builtin/lists.py
index 004e90d6b..68785ff4b 100644
--- a/mathics/builtin/lists.py
+++ b/mathics/builtin/lists.py
@@ -172,7 +172,7 @@ def check_options(self, expr, evaluation, options):
return evaluation.message("ContainsOnly", "optx", Symbol(key), expr)
return None
- def apply(self, list1, list2, evaluation, options={}):
+ def eval(self, list1, list2, evaluation, options={}):
"ContainsOnly[list1_List, list2_List, OptionsPattern[ContainsOnly]]"
same_test = self.get_option(options, "SameTest", evaluation)
@@ -188,7 +188,7 @@ def sameQ(a, b) -> bool:
return SymbolFalse
return SymbolTrue
- def apply_msg(self, e1, e2, evaluation, options={}):
+ def eval_msg(self, e1, e2, evaluation, options={}):
"ContainsOnly[e1_, e2_, OptionsPattern[ContainsOnly]]"
opts = (
@@ -295,6 +295,8 @@ class Delete(Builtin):
"""
messages = {
+ # FIXME: This message doesn't exist in more modern WMA, and
+ # Delete *can* take more than 2 arguments.
"argr": "Delete called with 1 argument; 2 arguments are expected.",
"argt": "Delete called with `1` arguments; 2 arguments are expected.",
"psl": "Position specification `1` in `2` is not a machine-sized integer or a list of machine-sized integers.",
@@ -302,7 +304,7 @@ class Delete(Builtin):
}
summary_text = "delete elements from a list at given positions"
- def apply_one(self, expr, position: Integer, evaluation):
+ def eval_one(self, expr, position: Integer, evaluation):
"Delete[expr_, position_Integer]"
pos = position.value
try:
@@ -310,7 +312,7 @@ def apply_one(self, expr, position: Integer, evaluation):
except PartRangeError:
evaluation.message("Part", "partw", ListExpression(position), expr)
- def apply(self, expr, positions, evaluation):
+ def eval(self, expr, positions, evaluation):
"Delete[expr_, positions___]"
positions = positions.get_sequence()
if len(positions) > 1:
@@ -443,7 +445,7 @@ class Level(Builtin):
}
summary_text = "parts specified by a given number of indices"
- def apply(self, expr, ls, evaluation, options={}):
+ def eval(self, expr, ls, evaluation, options={}):
"Level[expr_, ls_, OptionsPattern[Level]]"
try:
@@ -509,7 +511,7 @@ class List(Builtin):
attributes = A_LOCKED | A_PROTECTED
summary_text = "specify a list explicitly"
- def apply(self, elements, evaluation):
+ def eval(self, elements, evaluation):
"""List[elements___]"""
# Pick out the elements part of the parameter elements;
# we we will call that `elements_part_of_elements__`.
@@ -518,7 +520,7 @@ def apply(self, elements, evaluation):
elements_part_of_elements__ = elements.get_sequence()
return ListExpression(*elements_part_of_elements__)
- def apply_makeboxes(self, items, f, evaluation):
+ def eval_makeboxes(self, items, f, evaluation):
"""MakeBoxes[{items___},
f:StandardForm|TraditionalForm|OutputForm|InputForm|FullForm]"""
@@ -649,7 +651,7 @@ class Split(Builtin):
}
summary_text = "split into runs of identical elements"
- def apply(self, mlist, test, evaluation):
+ def eval(self, mlist, test, evaluation):
"Split[mlist_, test_]"
expr = Expression(SymbolSplit, mlist, test)
@@ -702,7 +704,7 @@ class SplitBy(Builtin):
summary_text = "split based on values of a function applied to elements"
- def apply(self, mlist, func, evaluation):
+ def eval(self, mlist, func, evaluation):
"SplitBy[mlist_, func_?NotListQ]"
expr = Expression(SymbolSplit, mlist, func)
@@ -727,7 +729,7 @@ def apply(self, mlist, func, evaluation):
outer = structure(mlist.head, inner, evaluation)
return outer([inner(t) for t in result])
- def apply_multiple(self, mlist, funcs, evaluation):
+ def eval_multiple(self, mlist, funcs, evaluation):
"SplitBy[mlist_, funcs_List]"
expr = Expression(SymbolSplit, mlist, funcs)
@@ -737,7 +739,7 @@ def apply_multiple(self, mlist, funcs, evaluation):
result = mlist
for f in funcs.elements[::-1]:
- result = self.apply(result, f, evaluation)
+ result = self.eval(result, f, evaluation)
return result
@@ -781,7 +783,7 @@ class LeafCount(Builtin):
}
summary_text = "the total number of atomic subexpressions"
- def apply(self, expr, evaluation):
+ def eval(self, expr, evaluation):
"LeafCount[expr___]"
from mathics.core.atoms import Rational, Complex
@@ -820,7 +822,7 @@ class _IterationFunction(Builtin):
def get_result(self, items):
pass
- def apply_symbol(self, expr, iterator, evaluation):
+ def eval_symbol(self, expr, iterator, evaluation):
"%(name)s[expr_, iterator_Symbol]"
iterator = iterator.evaluate(evaluation)
if iterator.has_form(["List", "Range", "Sequence"], None):
@@ -830,19 +832,19 @@ def apply_symbol(self, expr, iterator, evaluation):
elif len(elements) == 2:
if elements[1].has_form(["List", "Sequence"], None):
seq = Expression(SymbolSequence, *(elements[1].elements))
- return self.apply_list(expr, elements[0], seq, evaluation)
+ return self.eval_list(expr, elements[0], seq, evaluation)
else:
- return self.apply_range(expr, *elements, evaluation)
+ return self.eval_range(expr, *elements, evaluation)
elif len(elements) == 3:
- return self.apply_iter_nostep(expr, *elements, evaluation)
+ return self.eval_iter_nostep(expr, *elements, evaluation)
elif len(elements) == 4:
- return self.apply_iter(expr, *elements, evaluation)
+ return self.eval_iter(expr, *elements, evaluation)
if self.throw_iterb:
evaluation.message(self.get_name(), "iterb")
return
- def apply_range(self, expr, i, imax, evaluation):
+ def eval_range(self, expr, i, imax, evaluation):
"%(name)s[expr_, {i_Symbol, imax_}]"
imax = imax.evaluate(evaluation)
if imax.has_form("Range", None):
@@ -852,11 +854,11 @@ def apply_range(self, expr, i, imax, evaluation):
return self.apply_list(expr, i, seq, evaluation)
elif imax.has_form("List", None):
seq = Expression(SymbolSequence, *(imax.elements))
- return self.apply_list(expr, i, seq, evaluation)
+ return self.eval_list(expr, i, seq, evaluation)
else:
- return self.apply_iter(expr, i, Integer1, imax, Integer1, evaluation)
+ return self.eval_iter(expr, i, Integer1, imax, Integer1, evaluation)
- def apply_max(self, expr, imax, evaluation):
+ def eval_max(self, expr, imax, evaluation):
"%(name)s[expr_, {imax_}]"
# Even though `imax` should be an integeral value, its type does not
@@ -912,11 +914,11 @@ def do_iteration():
return self.get_result(result)
- def apply_iter_nostep(self, expr, i, imin, imax, evaluation):
+ def eval_iter_nostep(self, expr, i, imin, imax, evaluation):
"%(name)s[expr_, {i_Symbol, imin_, imax_}]"
- return self.apply_iter(expr, i, imin, imax, Integer1, evaluation)
+ return self.eval_iter(expr, i, imin, imax, Integer1, evaluation)
- def apply_iter(self, expr, i, imin, imax, di, evaluation):
+ def eval_iter(self, expr, i, imin, imax, di, evaluation):
"%(name)s[expr_, {i_Symbol, imin_, imax_, di_}]"
if isinstance(self, SympyFunction) and di.get_int_value() == 1:
@@ -977,7 +979,7 @@ def apply_iter(self, expr, i, imin, imax, di, evaluation):
index = Expression(SymbolPlus, index, di).evaluate(evaluation)
return self.get_result(result)
- def apply_list(self, expr, i, items, evaluation):
+ def eval_list(self, expr, i, items, evaluation):
"%(name)s[expr_, {i_Symbol, {items___}}]"
items = items.evaluate(evaluation).get_sequence()
result = []
@@ -1003,7 +1005,7 @@ def apply_list(self, expr, i, items, evaluation):
raise
return self.get_result(result)
- def apply_multi(self, expr, first, sequ, evaluation):
+ def eval_multi(self, expr, first, sequ, evaluation):
"%(name)s[expr_, first_, sequ__]"
sequ = sequ.get_sequence()
@@ -1027,7 +1029,7 @@ class Insert(Builtin):
summary_text = "insert an element at a given position"
- def apply(self, expr, elem, n: Integer, evaluation):
+ def eval(self, expr, elem, n: Integer, evaluation):
"Insert[expr_List, elem_, n_Integer]"
py_n = n.value
@@ -1232,7 +1234,7 @@ class TakeLargestBy(_RankedTakeLargest):
summary_text = "sublist of n largest elements according to a given criteria"
- def apply(self, element, f, n, evaluation, options):
+ def eval(self, element, f, n, evaluation, options):
"TakeLargestBy[element_List, f_, n_, OptionsPattern[TakeLargestBy]]"
return self._compute(element, n, evaluation, options, f=f)
@@ -1256,7 +1258,7 @@ class TakeSmallestBy(_RankedTakeSmallest):
summary_text = "sublist of n largest elements according to a criteria"
- def apply(self, element, f, n, evaluation, options):
+ def eval(self, element, f, n, evaluation, options):
"TakeSmallestBy[element_List, f_, n_, OptionsPattern[TakeSmallestBy]]"
return self._compute(element, n, evaluation, options, f=f)
@@ -1416,7 +1418,7 @@ def levels(k):
)
return None
- def apply_zero(self, element, n, evaluation):
+ def eval_zero(self, element, n, evaluation):
"%(name)s[element_, n_]"
return self._pad(
element,
@@ -1427,7 +1429,7 @@ def apply_zero(self, element, n, evaluation):
lambda: Expression(self.get_name(), element, n),
)
- def apply(self, element, n, x, evaluation):
+ def eval(self, element, n, x, evaluation):
"%(name)s[element_, n_, x_]"
return self._pad(
element,
@@ -1438,7 +1440,7 @@ def apply(self, element, n, x, evaluation):
lambda: Expression(self.get_name(), element, n, x),
)
- def apply_margin(self, element, n, x, m, evaluation):
+ def eval_margin(self, element, n, x, m, evaluation):
"%(name)s[element_, n_, x_, m_]"
return self._pad(
element,
@@ -1820,7 +1822,7 @@ class FindClusters(_Cluster):
summary_text = "divide data into lists of similar elements"
- def apply(self, p, evaluation, options):
+ def eval(self, p, evaluation, options):
"FindClusters[p_, OptionsPattern[%(name)s]]"
return self._cluster(
p,
@@ -1831,7 +1833,7 @@ def apply(self, p, evaluation, options):
Expression(SymbolFindClusters, p, *options_to_rules(options)),
)
- def apply_manual_k(self, p, k: Integer, evaluation, options):
+ def eval_manual_k(self, p, k: Integer, evaluation, options):
"FindClusters[p_, k_Integer, OptionsPattern[%(name)s]]"
return self._cluster(
p,
@@ -1867,7 +1869,7 @@ class ClusteringComponents(_Cluster):
summary_text = "label data with the index of the cluster it is in"
- def apply(self, p, evaluation, options):
+ def eval(self, p, evaluation, options):
"ClusteringComponents[p_, OptionsPattern[%(name)s]]"
return self._cluster(
p,
@@ -1878,7 +1880,7 @@ def apply(self, p, evaluation, options):
Expression(SymbolClusteringComponents, p, *options_to_rules(options)),
)
- def apply_manual_k(self, p, k: Integer, evaluation, options):
+ def eval_manual_k(self, p, k: Integer, evaluation, options):
"ClusteringComponents[p_, k_Integer, OptionsPattern[%(name)s]]"
return self._cluster(
p,
@@ -1941,7 +1943,7 @@ class Nearest(Builtin):
}
summary_text = "the nearest element from a list"
- def apply(self, items, pivot, limit, expression, evaluation, options):
+ def eval(self, items, pivot, limit, expression, evaluation, options):
"Nearest[items_, pivot_, limit_, OptionsPattern[%(name)s]]"
method = self.get_option(options, "Method", evaluation)
@@ -2078,6 +2080,8 @@ class SubsetQ(Builtin):
"""
messages = {
+ # FIXME: This message doesn't exist in more modern WMA, and
+ # Subset *can* take more than 2 arguments.
"argr": "SubsetQ called with 1 argument; 2 arguments are expected.",
"argrx": "SubsetQ called with `1` arguments; 2 arguments are expected.",
"heads": "Heads `1` and `2` at positions 1 and 2 are expected to be the same.",
@@ -2085,7 +2089,7 @@ class SubsetQ(Builtin):
}
summary_text = "test if a list is a subset of another list"
- def apply(self, expr, subset, evaluation):
+ def eval(self, expr, subset, evaluation):
"SubsetQ[expr_, subset___]"
if isinstance(expr, Atom):
diff --git a/mathics/builtin/patterns.py b/mathics/builtin/patterns.py
index 7faba7731..f2b6c5ffd 100644
--- a/mathics/builtin/patterns.py
+++ b/mathics/builtin/patterns.py
@@ -975,7 +975,6 @@ class Pattern_(PatternObject):
"nodef": (
"No default setting found for `1` in " "position `2` when length is `3`."
),
- "argr": "Pattern called with 1 argument; 2 arguments are expected.",
}
rules = {
@@ -1691,7 +1690,7 @@ def __init__(self, rulelist, evaluation):
self._elements = None
self._head = SymbolDispatch
- def get_sort_key(self) -> tuple:
+ def get_sort_key(self, pattern_sort=False) -> tuple:
return self.src.get_sort_key()
def get_atom_name(self):
diff --git a/mathics/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 81%
rename from mathics/builtin/assignments/internals.py
rename to mathics/core/assignment.py
index f00e708d9..3574cca20 100644
--- a/mathics/builtin/assignments/internals.py
+++ b/mathics/core/assignment.py
@@ -1,13 +1,18 @@
# -*- 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.definitions import get_tag_position
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
+from mathics.core.pattern import Pattern
from mathics.core.rules import Rule
from mathics.core.symbols import (
Symbol,
@@ -112,24 +117,25 @@ def assign_store_rules_by_tag(self, lhs, rhs, evaluation, tags, upset=None):
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)
-
+ condition = None
defs = evaluation.definitions
- ignore_protection, tags = process_assign_other(
- self, lhs, rhs, evaluation, tags, upset
+ tags, focus = process_tags_and_upset(tags, upset, self, lhs, evaluation)
+ # TODO: check if we can invert the order, and call this just
+ # in the special cases
+ ignore_protection = process_assign_side_effects(
+ self, lhs, rhs, focus, 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)
-
+ lhs, rhs = process_rhs_conditions(lhs, rhs, evaluation)
count = 0
rule = Rule(lhs, rhs)
position = "up" if upset else None
for tag in tags:
- if rejected_because_protected(self, lhs, tag, evaluation, ignore_protection):
+ if not ignore_protection and rejected_because_protected(
+ self, lhs, tag, evaluation
+ ):
continue
count += 1
defs.add_rule(tag, rule, position=position)
@@ -192,12 +198,12 @@ def normalize_lhs(lhs, evaluation):
Process the lhs in a way that
* if it is a conditional expression, reduce it to
a shallow conditional expression
- ( Conditional[Conditional[...],tst] -> Conditional[uncondlhs, tst])
- with `uncondlhs` the result of strip all the conditions from lhs.
- * if `uncondlhs` is not a `List` or a `Part` expression, evaluate the
+ ( Conditional[Conditional[...],tst] -> Conditional[stripped_lhs, tst])
+ with `stripped_lhs` the result of strip all the conditions from lhs.
+ * if ``stripped_lhs`` is not a ``List`` or a ``Part`` expression, evaluate the
elements.
- returns a tuple with the normalized lhs, and the lookup_name of the head in uncondlhs.
+ returns a tuple with the normalized lhs, and the lookup_name of the head in stripped_lhs.
"""
cond = None
if lhs.get_head() is SymbolCondition:
@@ -243,17 +249,6 @@ def repl_pattern_by_symbol(expr):
# Auxiliary routines
-def rejected_because_protected(self, lhs, tag, evaluation, ignore=False):
- defs = evaluation.definitions
- if not ignore and is_protected(tag, defs):
- if lhs.get_name() == tag:
- evaluation.message(self.get_name(), "wrsym", Symbol(tag))
- else:
- evaluation.message(self.get_name(), "write", Symbol(tag), lhs)
- return True
- return False
-
-
def find_tag_and_check(lhs, tags, evaluation):
name = lhs.get_head_name()
if len(lhs.elements) != 1:
@@ -272,6 +267,17 @@ def find_tag_and_check(lhs, tags, evaluation):
return tag
+def rejected_because_protected(self, lhs, tag, evaluation):
+ defs = evaluation.definitions
+ if is_protected(tag, defs):
+ if lhs.get_name() == tag:
+ evaluation.message(self.get_name(), "wrsym", Symbol(tag))
+ else:
+ evaluation.message(self.get_name(), "write", Symbol(tag), lhs)
+ return True
+ return False
+
+
def unroll_patterns(lhs, rhs, evaluation) -> Tuple[BaseElement, BaseElement]:
"""
Pattern[symb, pat]=rhs -> pat = (rhs/.(symb->pat))
@@ -331,72 +337,47 @@ def unroll_conditions(lhs) -> Tuple[BaseElement, Optional[Expression]]:
# maybe they should be member functions of _SetOperator.
-def process_assign_recursion_limit(lhs, rhs, evaluation):
- """
- Set ownvalue for the $RecursionLimit symbol.
- """
- rhs_int_value = rhs.get_int_value()
- # if (not rhs_int_value or rhs_int_value < 20) and not
- # rhs.get_name() == 'System`Infinity':
- if (
- not rhs_int_value or rhs_int_value < 20 or rhs_int_value > MAX_RECURSION_DEPTH
- ): # nopep8
-
- evaluation.message("$RecursionLimit", "limset", rhs)
- raise AssignmentException(lhs, None)
- try:
- set_python_recursion_limit(rhs_int_value)
- except OverflowError:
- # TODO: Message
- raise AssignmentException(lhs, None)
- return False
-
-
-def process_assign_iteration_limit(lhs, rhs, evaluation):
- """
- Set ownvalue for the $IterationLimit symbol.
- """
-
- rhs_int_value = rhs.get_int_value()
- if (
- not rhs_int_value or rhs_int_value < 20
- ) and not rhs.get_name() == "System`Infinity":
- evaluation.message("$IterationLimit", "limset", rhs)
- raise AssignmentException(lhs, None)
- return False
-
-
-def process_assign_module_number(lhs, rhs, evaluation):
+def process_assign_attributes(self, lhs, rhs, evaluation, tags, upset):
"""
- Set ownvalue for the $ModuleNumber symbol.
+ Process the case where lhs is of the form
+ `Attribute[symbol]`
"""
- rhs_int_value = rhs.get_int_value()
- if not rhs_int_value or rhs_int_value <= 0:
- evaluation.message("$ModuleNumber", "set", rhs)
- raise AssignmentException(lhs, None)
- return False
-
+ name = lhs.get_head_name()
+ if len(lhs.elements) != 1:
+ evaluation.message_args(name, len(lhs.elements), 1)
+ raise AssignmentException(lhs, rhs)
+ tag = lhs.elements[0].get_name()
+ if not tag:
+ evaluation.message(name, "sym", lhs.elements[0], 1)
+ raise AssignmentException(lhs, rhs)
+ if tags is not None and tags != [tag]:
+ evaluation.message(name, "tag", Symbol(name), Symbol(tag))
+ raise AssignmentException(lhs, rhs)
+ attributes_list = get_symbol_list(
+ rhs, lambda item: evaluation.message(name, "sym", item, 1)
+ )
+ if attributes_list is None:
+ raise AssignmentException(lhs, rhs)
+ if A_LOCKED & evaluation.definitions.get_attributes(tag):
+ evaluation.message(name, "locked", Symbol(tag))
+ raise AssignmentException(lhs, rhs)
-def process_assign_line_number_and_history_length(
- self, lhs, rhs, evaluation, tags, upset
-):
- """
- Set ownvalue for the $Line and $HistoryLength symbols.
- """
+ def reduce_attributes_from_list(x: int, y: str) -> int:
+ try:
+ return x | attribute_string_to_number[y]
+ except KeyError:
+ evaluation.message("SetAttributes", "unknowattr", y)
+ return x
- lhs_name = lhs.get_name()
- rhs_int_value = rhs.get_int_value()
- if rhs_int_value is None or rhs_int_value < 0:
- evaluation.message(lhs_name, "intnn", rhs)
- raise AssignmentException(lhs, None)
- return False
+ attributes = reduce(
+ reduce_attributes_from_list,
+ attributes_list,
+ 0,
+ )
+ evaluation.definitions.set_attributes(tag, attributes)
-def process_assign_random_state(self, lhs, rhs, evaluation, tags, upset):
- # TODO: allow setting of legal random states!
- # (but consider pickle's insecurity!)
- evaluation.message("$RandomState", "rndst", rhs)
- raise AssignmentException(lhs, None)
+ return True
def process_assign_context(self, lhs, rhs, evaluation, tags, upset):
@@ -442,6 +423,138 @@ 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, focus = process_tags_and_upset(tags, upset, self, lhs, evaluation, focus)
+ lhs, rhs = process_rhs_conditions(lhs, rhs, evaluation, condition)
+ 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, focus = process_tags_and_upset(tags, upset, self, lhs, evaluation, focus)
+ lhs, rhs = process_rhs_conditions(lhs, rhs, evaluation, condition)
+ 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()
@@ -473,15 +586,36 @@ 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, focus = process_tags_and_upset(tags, upset, self, lhs, evaluation, focus)
+ lhs, rhs = process_rhs_conditions(lhs, rhs, evaluation, condition)
+ 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):
@@ -509,7 +643,6 @@ 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, rhs = unroll_patterns(lhs, rhs, evaluation)
if rhs not in (SymbolTrue, SymbolFalse):
evaluation.message("NumericQ", "set", lhs, rhs)
@@ -549,11 +682,9 @@ def process_assign_n(self, lhs, rhs, evaluation, tags, upset):
nprec = lhs.elements[1]
focus = lhs.elements[0]
lhs = Expression(SymbolN, focus, nprec)
- tags = process_tags_and_upset_dont_allow_custom(
- tags, upset, self, lhs, focus, evaluation
- )
+ tags, focus = process_tags_and_upset(tags, upset, self, lhs, evaluation, focus)
count = 0
- lhs, rhs = process_rhs_conditions(lhs, rhs, condition, evaluation)
+ lhs, rhs = process_rhs_conditions(lhs, rhs, evaluation, condition)
rule = Rule(lhs, rhs)
for tag in tags:
if rejected_because_protected(self, lhs, tag, evaluation):
@@ -563,21 +694,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
-) -> Tuple[bool, list]:
+def process_assign_side_effects(
+ self, lhs, rhs, focus, evaluation, tags=None, upset=False
+) -> bool:
"""
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.
+ The function returns a a bool value.
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.
+ True, meaning that the `Protected` attribute should not be taken into account.
Otherwise, the value is False.
"""
- tags, focus = process_tags_and_upset_allow_custom(
- tags, upset, self, lhs, evaluation
- )
lhs_name = lhs.get_name()
if lhs_name == "System`$RecursionLimit":
@@ -595,150 +723,11 @@ def process_assign_other(
elif lhs_name == "System`$MaxPrecision":
process_assign_maxprecision(self, lhs, rhs, evaluation, tags, upset)
else:
- return False, tags
- return True, tags
-
-
-def process_assign_attributes(self, lhs, rhs, evaluation, tags, upset):
- """
- Process the case where lhs is of the form
- `Attribute[symbol]`
- """
- name = lhs.get_head_name()
- if len(lhs.elements) != 1:
- evaluation.message_args(name, len(lhs.elements), 1)
- raise AssignmentException(lhs, rhs)
- tag = lhs.elements[0].get_name()
- if not tag:
- evaluation.message(name, "sym", lhs.elements[0], 1)
- raise AssignmentException(lhs, rhs)
- if tags is not None and tags != [tag]:
- evaluation.message(name, "tag", Symbol(name), Symbol(tag))
- raise AssignmentException(lhs, rhs)
- attributes_list = get_symbol_list(
- rhs, lambda item: evaluation.message(name, "sym", item, 1)
- )
- if attributes_list is None:
- raise AssignmentException(lhs, rhs)
- if A_LOCKED & evaluation.definitions.get_attributes(tag):
- evaluation.message(name, "locked", Symbol(tag))
- raise AssignmentException(lhs, rhs)
-
- def reduce_attributes_from_list(x: int, y: str) -> int:
- try:
- return x | attribute_string_to_number[y]
- except KeyError:
- evaluation.message("SetAttributes", "unknowattr", y)
- return x
-
- attributes = reduce(
- reduce_attributes_from_list,
- attributes_list,
- 0,
- )
-
- evaluation.definitions.set_attributes(tag, attributes)
-
- return True
-
-
-def process_assign_default(self, lhs, rhs, evaluation, tags, upset):
- lhs, condition = unroll_conditions(lhs)
- lhs, rhs = unroll_patterns(lhs, rhs, evaluation)
- count = 0
- defs = evaluation.definitions
-
- if len(lhs.elements) not in (1, 2, 3):
- evaluation.message_args(SymbolDefault, len(lhs.elements), 1, 2, 3)
- raise AssignmentException(lhs, None)
- focus = lhs.elements[0]
- tags = process_tags_and_upset_dont_allow_custom(
- tags, upset, self, lhs, focus, evaluation
- )
- lhs, rhs = process_rhs_conditions(lhs, rhs, condition, evaluation)
- rule = Rule(lhs, rhs)
- for tag in tags:
- if rejected_because_protected(self, lhs, tag, evaluation):
- continue
- count += 1
- defs.add_default(tag, rule)
- return count > 0
-
-
-def process_assign_format(self, lhs, rhs, evaluation, tags, upset):
- lhs, condition = unroll_conditions(lhs)
- lhs, rhs = unroll_patterns(lhs, rhs, evaluation)
- count = 0
- defs = evaluation.definitions
-
- if len(lhs.elements) not in (1, 2):
- evaluation.message_args("Format", len(lhs.elements), 1, 2)
- raise AssignmentException(lhs, None)
- if len(lhs.elements) == 2:
- form = lhs.elements[1]
- form_name = form.get_name()
- if not form_name:
- evaluation.message("Format", "fttp", lhs.elements[1])
- raise AssignmentException(lhs, None)
- # If the form is not in defs.printforms / defs.outputforms
- # add it.
- for form_list in (defs.outputforms, defs.printforms):
- if form not in form_list:
- form_list.append(form)
- else:
- form_name = [
- "System`StandardForm",
- "System`TraditionalForm",
- "System`OutputForm",
- "System`TeXForm",
- "System`MathMLForm",
- ]
- lhs = focus = lhs.elements[0]
- tags = process_tags_and_upset_dont_allow_custom(
- tags, upset, self, lhs, focus, evaluation
- )
- lhs, rhs = process_rhs_conditions(lhs, rhs, condition, evaluation)
- rule = Rule(lhs, rhs)
- for tag in tags:
- if rejected_because_protected(self, lhs, tag, evaluation):
- continue
- count += 1
- defs.add_format(tag, rule, form_name)
- return count > 0
-
-
-def process_assign_list(self, lhs, rhs, evaluation, tags, upset):
- if not (
- rhs.get_head_name() == "System`List" and len(lhs.elements) == len(rhs.elements)
- ): # nopep8
- evaluation.message(self.get_name(), "shape", lhs, rhs)
return False
- result = True
- for left, right in zip(lhs.elements, rhs.elements):
- if not self.assign(left, right, evaluation):
- result = False
- return result
-
-
-def process_assign_makeboxes(self, lhs, rhs, evaluation, tags, upset):
- # FIXME: the below is a big hack.
- # Currently MakeBoxes boxing is implemented as a bunch of rules.
- # See mathics.builtin.base contribute().
- # I think we want to change this so it works like normal SetDelayed
- # That is:
- # MakeBoxes[CubeRoot, StandardForm] := RadicalBox[3, StandardForm]
- # rather than:
- # MakeBoxes[CubeRoot, StandardForm] -> RadicalBox[3, StandardForm]
-
- makeboxes_rule = Rule(lhs, rhs, system=False)
- definitions = evaluation.definitions
- definitions.add_rule("System`MakeBoxes", makeboxes_rule, "down")
- # makeboxes_defs = evaluation.definitions.builtin["System`MakeBoxes"]
- # makeboxes_defs.add_rule(makeboxes_rule)
return True
-def process_assing_part(self, lhs, rhs, evaluation, tags, upset):
+def process_assign_part(self, lhs, rhs, evaluation, tags, upset):
"""
Special case `A[[i,j,...]]=....`
"""
@@ -762,32 +751,58 @@ def process_assing_part(self, lhs, rhs, evaluation, tags, upset):
return walk_parts([rule.replace], indices, evaluation, rhs)
-def process_assign_messagename(self, lhs, rhs, evaluation, tags, upset):
- lhs, condition = unroll_conditions(lhs)
- lhs, rhs = unroll_patterns(lhs, rhs, evaluation)
- count = 0
- defs = evaluation.definitions
- if len(lhs.elements) != 2:
- evaluation.message_args("MessageName", len(lhs.elements), 2)
+def process_assign_random_state(self, lhs, rhs, evaluation, tags, upset):
+ # TODO: allow setting of legal random states!
+ # (but consider pickle's insecurity!)
+ evaluation.message("$RandomState", "rndst", rhs)
+ raise AssignmentException(lhs, None)
+
+
+def process_assign_recursion_limit(lhs, rhs, evaluation):
+ """
+ Set ownvalue for the $RecursionLimit symbol.
+ """
+ rhs_int_value = rhs.get_int_value()
+ # if (not rhs_int_value or rhs_int_value < 20) and not
+ # rhs.get_name() == 'System`Infinity':
+ if (
+ not rhs_int_value or rhs_int_value < 20 or rhs_int_value > MAX_RECURSION_DEPTH
+ ): # nopep8
+
+ evaluation.message("$RecursionLimit", "limset", rhs)
raise AssignmentException(lhs, None)
- focus = lhs.elements[0]
- tags = process_tags_and_upset_dont_allow_custom(
- tags, upset, self, lhs, focus, evaluation
- )
- lhs, rhs = process_rhs_conditions(lhs, rhs, condition, evaluation)
- rule = Rule(lhs, rhs)
- for tag in tags:
- # Messages can be assigned even if the symbol is protected...
- # if rejected_because_protected(self, lhs, tag, evaluation):
- # continue
- count += 1
- defs.add_message(tag, rule)
- return count > 0
+ try:
+ set_python_recursion_limit(rhs_int_value)
+ except OverflowError:
+ # TODO: Message
+ raise AssignmentException(lhs, None)
+ return False
-def process_rhs_conditions(lhs, rhs, condition, evaluation):
+def process_rhs_conditions(lhs, rhs, evaluation, condition=None):
"""
lhs = Condition[rhs, test] -> Condition[lhs, test] = rhs
+
+ This function is necesary as a temporal patch for a bug
+ in the pattern matching / replacement code.
+
+ In WMA,
+
+ ``f[4]/.(f[x_]:> (p[x]/;x>0))``
+
+ shold return
+
+ ``p[4]``
+
+ while ``f[-4]/.(f[x_]:> (p[x]/;x>0))``
+ would result in ``f[-4]``
+
+ In Mathics, the first case results in
+ ``p[4]/; 4>0``
+ while the second,
+ ``p[-4]/; -4>0``
+
+ This should be handled in the apply method of the BaseRule class.
"""
# To Handle `OptionValue` in `Condition`
rulopc = build_rulopc(lhs.get_head())
@@ -814,8 +829,15 @@ def process_rhs_conditions(lhs, rhs, condition, evaluation):
return lhs, rhs
-def find_tag(focus):
+def find_focus(focus):
+ """
+ Recursively, look for the (true) focus expression, i.e.,
+ the expression after strip it from Condition, Pattern and HoldPattern
+ wrapping expressions.
+ """
name = focus.get_lookup_name()
+ if isinstance(focus, Pattern):
+ return find_focus(focus.expr)
if name == "System`HoldPattern":
if len(focus.elements) == 1:
return find_tag(focus.elements[0])
@@ -823,7 +845,9 @@ def find_tag(focus):
# the message
# "HoldPattern::argx: HoldPattern called with 2 arguments; 1 argument is expected."
# must be shown.
- raise AssignmentException(lhs, None)
+ raise AssignmentException(focus, None)
+ if focus.has_form("System`Condition", 2):
+ return find_focus(focus.elements[0])
if name == "System`Optional":
return None
if name == "System`Pattern" and len(focus.elements) == 2:
@@ -836,21 +860,34 @@ def find_tag(focus):
elems = pat.elements
if len(elems) == 0:
return None
- return find_tag(elems[0])
+ return find_focus(elems[0])
else:
- return find_tag(pat)
- return name
+ return find_focus(pat)
+ return focus
+
+
+def find_tag(focus):
+ focus = find_focus(focus)
+ if focus is None:
+ return None
+ return focus.get_lookup_name()
-def process_tags_and_upset_dont_allow_custom(tags, upset, self, lhs, focus, evaluation):
+def process_tags_and_upset(tags, upset, self, lhs, evaluation, focus=None):
+ if focus is None:
+ allow_custom = True
+ focus = lhs
+ else:
+ allow_custom = False
+ # Ensures that focus is the actual focus of the expression.
+ focus = find_focus(focus)
if (
isinstance(focus, Expression)
and focus.head not in NOT_EVALUATE_ELEMENTS_IN_ASSIGNMENTS
):
focus = focus.evaluate_elements(evaluation)
- name = lhs.get_head_name()
if tags is None and not upset:
name = find_tag(focus)
if name == "":
@@ -858,59 +895,28 @@ def process_tags_and_upset_dont_allow_custom(tags, upset, self, lhs, focus, eval
raise AssignmentException(lhs, None)
tags = [] if name is None else [name]
elif upset:
- name = find_tag(focus)
- tags = [] if name is None else [name]
- else:
- name = find_tag(focus)
- allowed_names = [] if name is None else [name]
- for name in tags:
- if name not in allowed_names:
- evaluation.message(self.get_name(), "tagnfd", Symbol(name))
+ if allow_custom:
+ tags = []
+ if isinstance(focus, Atom):
+ evaluation.message(self.get_name(), "normal")
raise AssignmentException(lhs, None)
-
- if len(tags) == 0:
- evaluation.message(self.get_name(), "nosym", focus)
- raise AssignmentException(lhs, None)
- return tags
-
-
-def process_tags_and_upset_allow_custom(tags, upset, self, lhs, evaluation):
- name = lhs.get_head_name()
- focus = lhs
-
- if isinstance(focus, Expression) and focus.head not in (
- SymbolSet,
- SymbolSetDelayed,
- SymbolUpSet,
- SymbolUpSetDelayed,
- SymbolTagSet,
- SymbolTagSetDelayed,
- SymbolList,
- SymbolPart,
- ):
- focus = focus.evaluate_elements(evaluation)
-
- if tags is None and not upset:
- name = find_tag(focus)
- if name == "":
- evaluation.message(self.get_name(), "setraw", focus)
- raise AssignmentException(lhs, None)
- tags = [] if name is None else [name]
- elif upset:
- tags = []
- if isinstance(focus, Atom):
- evaluation.message(self.get_name(), "normal")
- raise AssignmentException(lhs, None)
- for element in focus.elements:
- name = find_tag(element)
- if name != "" and name is not None:
- tags.append(name)
+ for element in focus.elements:
+ name = find_tag(element)
+ if name != "" and name is not None:
+ tags.append(name)
+ else:
+ name = find_tag(focus)
+ tags = [] if name is None else [name]
else:
- allowed_names = [find_tag(focus)]
- for element in focus.get_elements():
- element_tag = find_tag(element)
- if element_tag is not None and element_tag != "":
- allowed_names.append(element_tag)
+ if allow_custom:
+ allowed_names = [find_tag(focus)]
+ for element in focus.get_elements():
+ element_tag = find_tag(element)
+ if element_tag is not None and element_tag != "":
+ allowed_names.append(element_tag)
+ else:
+ name = find_tag(focus)
+ allowed_names = [] if name is None else [name]
for name in tags:
if name not in allowed_names:
evaluation.message(self.get_name(), "tagnfd", Symbol(name))
@@ -918,59 +924,29 @@ def process_tags_and_upset_allow_custom(tags, upset, self, lhs, evaluation):
if len(tags) == 0:
evaluation.message(self.get_name(), "nosym", focus)
raise AssignmentException(lhs, None)
-
return tags, focus
-class _SetOperator:
- """
- This is the base class for assignment Builtin operators.
-
- Special cases are determined by the head of the expression. Then
- they are processed by specific routines, which are poke from
- the `special_cases` dict.
- """
-
- # There are several idea about how to reimplement this. One possibility
- # would be to use a Symbol instead of a name as the key of this dictionary.
- #
- # Another possibility would be to move the specific function to be a
- # class method associated to the corresponding builtins. In any case,
- # the present implementation should be clear enough to understand the
- # logic.
- #
-
- special_cases = {
- "System`$Context": process_assign_context,
- "System`$ContextPath": process_assign_context_path,
- "System`$RandomState": process_assign_random_state,
- "System`Attributes": process_assign_attributes,
- "System`Default": process_assign_default,
- "System`DefaultValues": process_assign_definition_values,
- "System`DownValues": process_assign_definition_values,
- "System`Format": process_assign_format,
- "System`List": process_assign_list,
- "System`MakeBoxes": process_assign_makeboxes,
- "System`MessageName": process_assign_messagename,
- "System`Messages": process_assign_definition_values,
- "System`N": process_assign_n,
- "System`NValues": process_assign_definition_values,
- "System`NumericQ": process_assign_numericq,
- "System`Options": process_assign_options,
- "System`OwnValues": process_assign_definition_values,
- "System`Part": process_assing_part,
- "System`SubValues": process_assign_definition_values,
- "System`UpValues": process_assign_definition_values,
- }
-
- def assign(self, lhs, rhs, evaluation, tags=None, upset=False):
- lhs, lookup_name = normalize_lhs(lhs, evaluation)
- try:
- # Deal with direct assignation to properties of
- # the definition object
- func = self.special_cases.get(lookup_name, None)
- if func:
- return func(self, lhs, rhs, evaluation, tags, upset)
- return assign_store_rules_by_tag(self, lhs, rhs, evaluation, tags, upset)
- except AssignmentException:
- return False
+# 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/definitions.py b/mathics/core/definitions.py
index def6b1a93..b3c7df20a 100644
--- a/mathics/core/definitions.py
+++ b/mathics/core/definitions.py
@@ -16,6 +16,7 @@
from mathics.core.convert.expression import to_mathics_list
from mathics.core.element import fully_qualified_symbol_name
from mathics.core.expression import Expression
+from mathics.core.pattern import Pattern
from mathics.core.symbols import (
Atom,
Symbol,
@@ -780,6 +781,25 @@ def get_history_length(self):
def get_tag_position(pattern, name) -> Optional[str]:
+ # Strip first the pattern from HoldPattern, Pattern
+ # and Condition wrappings
+ while True:
+ # TODO: Not Atom/Expression,
+ # pattern -> pattern.to_expression()
+ if isinstance(pattern, Pattern):
+ pattern = pattern.expr
+ continue
+ if pattern.has_form("System`HoldPattern", 1):
+ pattern = pattern.elements[0]
+ continue
+ if pattern.has_form("System`Pattern", 2):
+ pattern = pattern.elements[1]
+ continue
+ if pattern.has_form("System`Condition", 2):
+ pattern = pattern.elements[0]
+ continue
+ break
+
if pattern.get_name() == name:
return "own"
elif isinstance(pattern, Atom):
@@ -788,10 +808,8 @@ def get_tag_position(pattern, name) -> Optional[str]:
head_name = pattern.get_head_name()
if head_name == name:
return "down"
- elif head_name == "System`N" and len(pattern.elements) == 2:
+ elif pattern.has_form("System`N", 2):
return "n"
- elif head_name == "System`Condition" and len(pattern.elements) > 0:
- return get_tag_position(pattern.elements[0], name)
elif pattern.get_lookup_name() == name:
return "sub"
else:
@@ -912,6 +930,9 @@ def remove_rule(self, lhs) -> bool:
return False
def __repr__(self) -> str:
+ print("arguments of ", id(self))
+ for p in (self.name, self.downvalues, self.formatvalues, self.attributes):
+ print(p)
s = "".format(
self.name, self.downvalues, self.formatvalues, self.attributes
)
diff --git a/mathics/core/element.py b/mathics/core/element.py
index 26f525311..f061081b6 100644
--- a/mathics/core/element.py
+++ b/mathics/core/element.py
@@ -147,7 +147,7 @@ class KeyComparable:
# FIXME: return type should be a specific kind of Tuple, not a list.
# FIXME: Describe sensible, and easy to follow rules by which one
# can create the kind of tuple for some new kind of element.
- def get_sort_key(self) -> list:
+ def get_sort_key(self, pattern_sort=False) -> list:
"""
This returns a tuple in a way that
it can be used to compare in expressions.
diff --git a/mathics/core/evaluation.py b/mathics/core/evaluation.py
index 3ac098309..212bb15b0 100644
--- a/mathics/core/evaluation.py
+++ b/mathics/core/evaluation.py
@@ -157,7 +157,7 @@ def __init__(self) -> None:
self.is_print = False
self.text = ""
- def get_sort_key(self) -> Tuple[bool, bool, str]:
+ def get_sort_key(self, pattern_sort=False) -> Tuple[bool, bool, str]:
return (self.is_message, self.is_print, self.text)
diff --git a/mathics/core/list.py b/mathics/core/list.py
index ddd36879f..19cb603e3 100644
--- a/mathics/core/list.py
+++ b/mathics/core/list.py
@@ -4,6 +4,7 @@
from typing import Optional, Tuple
from mathics.core.element import ElementsProperties
+from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
from mathics.core.symbols import EvalMixin, Symbol, SymbolList
@@ -85,7 +86,7 @@ def __str__(self) -> str:
return f""
# @timeit
- def evaluate_elements(self, evaluation):
+ def evaluate_elements(self, evaluation: Evaluation) -> Expression:
"""
return a new expression with the same head, and the
evaluable elements evaluated.
diff --git a/mathics/core/rules.py b/mathics/core/rules.py
index 717f9d64c..dac7426d0 100644
--- a/mathics/core/rules.py
+++ b/mathics/core/rules.py
@@ -107,7 +107,7 @@ def yield_match(vars, rest):
def do_replace(self):
raise NotImplementedError
- def get_sort_key(self) -> tuple:
+ def get_sort_key(self, pattern_sort=False) -> tuple:
# FIXME: check if this makes sense:
return tuple((self.system, self.pattern.get_sort_key(True)))
diff --git a/test/builtin/test_assignment.py b/test/builtin/test_assignment.py
index 3af7020c4..9bdef25e4 100644
--- a/test/builtin/test_assignment.py
+++ b/test/builtin/test_assignment.py
@@ -5,13 +5,6 @@
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}];
@@ -174,25 +167,25 @@ def test_setdelayed_oneidentity():
(
"F[x_]:=G[x]; H[F[y_]]:=Q[y]; ClearAll[F]; {H[G[5]],H[F[5]]}",
"{Q[5], H[F[5]]}",
- "The arguments on the LHS are evaluated before the assignment",
+ "Arguments on the LHS are evaluated before the assignment in := after F reset",
),
(None, None, None),
(
"F[x_]:=G[x]; H[F[y_]]^:=Q[y]; ClearAll[F]; {H[G[5]],H[F[5]]}",
"{Q[5], H[F[5]]}",
- "The arguments on the LHS are evaluated before the assignment",
+ "Arguments on the LHS are evaluated before the assignment in ^:= after F reset",
),
(None, None, None),
(
"F[x_]:=G[x]; H[F[y_]]:=Q[y]; ClearAll[G]; {H[G[5]],H[F[5]]}",
"{Q[5], Q[5]}",
- "The arguments on the LHS are evaluated before the assignment",
+ "The arguments on the LHS are evaluated before the assignment in := after G reset",
),
(None, None, None),
(
"F[x_]:=G[x]; H[F[y_]]^:=Q[y]; ClearAll[G]; {H[G[5]],H[F[5]]}",
"{H[G[5]], H[G[5]]}",
- "The arguments on the LHS are evaluated before the assignment",
+ "The arguments on the LHS are evaluated before the assignment in ^:= after G reset",
),
(None, None, None),
(
@@ -215,28 +208,6 @@ def test_setdelayed_oneidentity():
None,
None,
),
- ],
-)
-def test_set_and_clear(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", "msg"),
- [
(None, None, None),
(r"a=b; a=4; {a, b}", "{4, b}", None),
(None, None, None),
@@ -276,7 +247,7 @@ def test_set_and_clear(str_expr, str_expected, msg):
(None, None, None),
(
(
- "A[x_]:=B[x];B[x_]:=F[x_];F[x_]:=G[x];"
+ "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]]}"
),
@@ -289,10 +260,24 @@ def test_set_and_clear(str_expr, str_expected, msg):
"{F[2.], 4.}",
"Assign N rule",
),
+ (None, None, None),
+ # This test is inspirated in CellsToTeX
+ ("SetAttributes[testHoldAll, HoldAll]", "Null", None),
+ (
+ (
+ "addF[sym_Symbol] := ("
+ " functionCall:sym[___] := "
+ " holdallfunc[functionCall]"
+ " )"
+ ),
+ "Null",
+ None,
+ ),
+ ("addF[Q]", "Null", None),
+ ("Q[1]", "holdallfunc[Q[1]]", None),
],
)
-@skip_or_fail
-def test_set_and_clear_to_fix(str_expr, str_expected, msg):
+def test_set_and_clear(str_expr, str_expected, msg):
"""
Test calls to Set, Clear and ClearAll. If
str_expr is None, the session is reset,
diff --git a/test/builtin/test_attributes.py b/test/builtin/test_attributes.py
index 175deeaeb..41f76df66 100644
--- a/test/builtin/test_attributes.py
+++ b/test/builtin/test_attributes.py
@@ -121,7 +121,7 @@ def test_one_identity(str_expr, str_expected, msg):
],
)
@skip_or_fail
-def test_one_identity_stil_failing(str_expr, str_expected, msg):
+def test_one_identity_still_failing(str_expr, str_expected, msg):
check_evaluation(
str_expr,
str_expected,
@@ -204,3 +204,25 @@ def test_one_identity_stil_failing(str_expr, str_expected, msg):
hold_expected=True,
failure_message=msg,
)
+
+
+@pytest.mark.parametrize(
+ ("str_expr", "arg_count"),
+ [
+ ("SetAttributes[F]", 1),
+ ("SetAttributes[]", 0),
+ ("SetAttributes[F, F, F]", 3),
+ ],
+)
+def test_Attributes_wrong_args(str_expr, arg_count):
+ check_evaluation(
+ str_expr=str_expr,
+ str_expected=str_expr,
+ failure_message=f"Arg count mismatch test with {arg_count} args",
+ hold_expected=True,
+ to_string_expr=True,
+ to_string_expected=True,
+ expected_messages=(
+ f"SetAttributes called with {arg_count} arguments; 2 arguments are expected.",
+ ),
+ )