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.", + ), + )