1# -*- coding: utf-8 -*- 2""" 3Mathics Built-in Functions and Variables. 4 5Mathics has over a thousand Built-in Functions and variables, all of which are defined here. 6""" 7 8import glob 9import importlib 10import pkgutil 11import re 12import os.path as osp 13from mathics.settings import ENABLE_FILES_MODULE 14from mathics.version import __version__ # noqa used in loading to check consistency. 15 16from typing import List 17 18# Get a list of files in this directory. We'll exclude from the start 19# files with leading characters we don't want like __init__ with its leading underscore. 20__py_files__ = [ 21 osp.basename(f[0:-3]) 22 for f in glob.glob(osp.join(osp.dirname(__file__), "[a-z]*.py")) 23] 24 25from mathics.builtin.base import ( 26 Builtin, 27 SympyObject, 28 Operator, 29 PatternObject, 30) 31 32def add_builtins(new_builtins): 33 for var_name, builtin in new_builtins: 34 name = builtin.get_name() 35 if hasattr(builtin, "python_equivalent"): 36 # print("XXX0", builtin.python_equivalent) 37 mathics_to_python[name] = builtin.python_equivalent 38 39 if isinstance(builtin, SympyObject): 40 mathics_to_sympy[name] = builtin 41 for sympy_name in builtin.get_sympy_names(): 42 ### print("XXX1", sympy_name) 43 sympy_to_mathics[sympy_name] = builtin 44 if isinstance(builtin, Operator): 45 builtins_precedence[name] = builtin.precedence 46 if isinstance(builtin, PatternObject): 47 pattern_objects[name] = builtin.__class__ 48 _builtins.update(dict(new_builtins)) 49 50def builtins_dict(): 51 return { 52 builtin.get_name(): builtin 53 for modname, builtins in builtins_by_module.items() 54 for builtin in builtins 55 } 56 57def contribute(definitions): 58 # let MakeBoxes contribute first 59 _builtins["System`MakeBoxes"].contribute(definitions) 60 for name, item in _builtins.items(): 61 if name != "System`MakeBoxes": 62 item.contribute(definitions) 63 64 from mathics.core.expression import ensure_context 65 from mathics.core.parser import all_operator_names 66 from mathics.core.definitions import Definition 67 68 # All builtins are loaded. Create dummy builtin definitions for 69 # any remaining operators that don't have them. This allows 70 # operators like \[Cup] to behave correctly. 71 for operator in all_operator_names: 72 if not definitions.have_definition(ensure_context(operator)): 73 op = ensure_context(operator) 74 definitions.builtin[op] = Definition(name=op) 75 76def get_module_doc(module): 77 doc = module.__doc__ 78 if doc is not None: 79 doc = doc.strip() 80 if doc: 81 title = doc.splitlines()[0] 82 text = "\n".join(doc.splitlines()[1:]) 83 else: 84 title = module.__name__ 85 for prefix in ("mathics.builtin.", "mathics.optional."): 86 if title.startswith(prefix): 87 title = title[len(prefix) :] 88 title = title.capitalize() 89 text = "" 90 return title, text 91 92def import_builtins(module_names: List[str], submodule_name=None) -> None: 93 """ 94 Imports the list of Mathics Built-in modules so that inside 95 Mathics we have these Builtin Functions, like Plus[], List[] are defined. 96 97 """ 98 def import_module(module_name: str, import_name: str): 99 try: 100 module = importlib.import_module(import_name) 101 except Exception as e: 102 print(e) 103 print(f" Not able to load {module_name}. Check your installation.") 104 print(f" mathics.builtin loads from {__file__[:-11]}") 105 return None 106 107 if __version__ != module.__version__: 108 print( 109 f"Version {module.__version__} in the module does not match top-level Mathics version {__version__}" 110 ) 111 if module: 112 modules.append(module) 113 114 if submodule_name: 115 import_module(submodule_name, f"mathics.builtin.{submodule_name}") 116 117 for module_name in module_names: 118 import_name = ( 119 f"mathics.builtin.{submodule_name}.{module_name}" 120 if submodule_name 121 else f"mathics.builtin.{module_name}" 122 ) 123 import_module(module_name, import_name) 124 125 126def is_builtin(var): 127 if var == Builtin: 128 return True 129 if hasattr(var, "__bases__"): 130 return any(is_builtin(base) for base in var.__bases__) 131 return False 132 133 134# FIXME: redo using importlib since that is probably less fragile. 135exclude_files = set(("codetables", "base")) 136module_names = [ 137 f for f in __py_files__ if re.match("^[a-z0-9]+$", f) if f not in exclude_files 138] 139 140modules = [] 141import_builtins(module_names) 142 143_builtins = [] 144builtins_by_module = {} 145 146disable_file_module_names = [] if ENABLE_FILES_MODULE else ["files_io.files", "files_io.importexport"] 147 148for subdir in ("drawing", "files_io", "numbers", "specialfns", "fileformats"): 149 import_name = f"{__name__}.{subdir}" 150 151 if import_name in disable_file_module_names: 152 continue 153 154 builtin_module = importlib.import_module(import_name) 155 submodule_names = [ 156 modname 157 for importer, modname, ispkg in pkgutil.iter_modules(builtin_module.__path__) 158 ] 159 # print("XXX3", submodule_names) 160 import_builtins(submodule_names, subdir) 161 162for module in modules: 163 builtins_by_module[module.__name__] = [] 164 vars = dir(module) 165 for name in vars: 166 var = getattr(module, name) 167 if ( 168 hasattr(var, "__module__") 169 and var.__module__.startswith("mathics.builtin.") 170 and var.__module__ != "mathics.builtin.base" 171 and is_builtin(var) 172 and not name.startswith("_") 173 and var.__module__ == module.__name__ 174 ): # nopep8 175 176 instance = var(expression=False) 177 178 if isinstance(instance, Builtin): 179 # This set the default context for symbols in mathics.builtins 180 if not type(instance).context: 181 type(instance).context = "System`" 182 _builtins.append((instance.get_name(), instance)) 183 builtins_by_module[module.__name__].append(instance) 184 185 186mathics_to_sympy = {} # here we have: name -> sympy object 187mathics_to_python = {} # here we have: name -> string 188sympy_to_mathics = {} 189 190pattern_objects = {} 191builtins_precedence = {} 192 193new_builtins = _builtins 194 195# FIXME: some magic is going on here.. 196_builtins = {} 197 198add_builtins(new_builtins) 199