diff --git a/mathics/core/load_builtin.py b/mathics/core/load_builtin.py index ba9dfaf61..ddb4f6da6 100755 --- a/mathics/core/load_builtin.py +++ b/mathics/core/load_builtin.py @@ -27,13 +27,15 @@ # is initialized via below import_builtins modules mathics3_builtins_modules: List[ModuleType] = [] +# Used in definition_contribute _builtins = {} # builtins_by_module gives a way of mapping a Python module name # e.g. 'mathics.builtin.arithmetic' to the list of Builtin class instances # that appear inside that module, e.g. for key 'mathics.builtin.arithmetic' we # have: -# [, , Dict[str, list]: + """ + Process modules listed in ``modules` which contain Builtin classes so that + for each module the classes contained in them are is imported in the Python sense, + but also that we have information added to module variable ``builtins_by_module``. + """ for module in modules: - add_builtins_from_builtin_module(module, builtins_list) - add_builtins(builtins_list) + add_builtins_from_builtin_module(module) return builtins_by_module -# The fact that we are importing inside here, suggests add_builtins -# should get moved elsewhere. -def add_builtins(new_builtins): - from mathics.core.builtin import ( - Operator, - PatternObject, - SympyObject, - mathics_to_python, - ) - - for _, builtin in new_builtins: - name = builtin.get_name() - if hasattr(builtin, "python_equivalent"): - # print("XXX0", builtin.python_equivalent) - mathics_to_python[name] = builtin.python_equivalent - - if isinstance(builtin, SympyObject): - mathics_to_sympy[name] = builtin - for sympy_name in builtin.get_sympy_names(): - # print("XXX1", sympy_name) - sympy_to_mathics[sympy_name] = builtin - if isinstance(builtin, Operator): - assert builtin.precedence is not None - builtins_precedence[Symbol(name)] = builtin.precedence - if isinstance(builtin, PatternObject): - pattern_objects[name] = builtin.__class__ - _builtins.update(dict(new_builtins)) - - def builtins_dict(builtins_by_module): return { builtin.get_name(): builtin @@ -140,10 +116,19 @@ def get_module_names(builtin_path: str, exclude_files: set) -> list: return [f for f in py_files if f not in exclude_files] -def import_and_load_builtins(): +def import_and_load_builtins(exclude_files: Optional[Set[str]] = None, clean_all=False): """ Imports Builtin modules in mathics.builtin and add rules, and definitions from that. """ + + # If clean_all, clean the _builtin and builtins_by_module to start + # with a fresh instance of this module. + # TODO: check if we need to clean the other dicts and lists. + if clean_all: + global mathics3_builtins_modules, _builtins + _builtins = {} + mathics3_builtins_modules = [] + builtin_path = osp.join( osp.dirname( __file__, @@ -151,7 +136,10 @@ def import_and_load_builtins(): "..", "builtin", ) - exclude_files = {"codetables", "base"} + + if exclude_files is None: + exclude_files = {"codetables"} + module_names = get_module_names(builtin_path, exclude_files) import_builtins(module_names, mathics3_builtins_modules) @@ -164,7 +152,7 @@ def import_and_load_builtins(): disable_file_module_names = set() if ENABLE_FILES_MODULE else {"files_io"} subdirectory_list = next(os.walk(builtin_path))[1] - subdirectories = set(subdirectory_list) - set("__pycache__") + subdirectories = set(subdirectory_list) - (set("__pycache__") | exclude_files) import_builtin_subdirectories( subdirectories, disable_file_module_names, mathics3_builtins_modules ) @@ -172,7 +160,9 @@ def import_and_load_builtins(): add_builtins_from_builtin_modules(mathics3_builtins_modules) -def import_builtin_module(import_name: str, modules: List[ModuleType]): +def import_builtin_module( + import_name: str, modules: List[ModuleType] +) -> Optional[ModuleType]: """ Imports ``the list of Mathics3 Built-in modules so that inside Mathics3 Builtin Functions, like Plus[], List[] are defined. @@ -189,6 +179,7 @@ def import_builtin_module(import_name: str, modules: List[ModuleType]): if module: modules.append(module) + return module # TODO: When we drop Python 3.7, @@ -218,7 +209,7 @@ def import_builtins( def import_builtin_subdirectories( - subdirectories: Set[str], disable_file_module_names: set, modules + subdirectories: Set[str], disable_file_module_names: set, modules: List[ModuleType] ): """ Runs import_builtisn on the each subdirectory in ``subdirectories`` that inside @@ -299,11 +290,48 @@ def name_is_builtin_symbol(module: ModuleType, name: str) -> Optional[type]: return module_object -def update_display_operators_set(builtin_instance): +def update_builtin_properties(builtin_instance): """ + Update, where appropriate, various internal builtin tables: + * mathics_to_python[name], + * mathics_to_sympy[name], + * builtins_precedence[name] + * pattern_object[name] + + Information to store comes from ``builtin``. + If builtin_instance is an operator of some kind, add that to the set of opererator strings ``display_operators_set``. + + Finally add {name: builtin} to global _builtins """ + # The fact that we are importing inside here, suggests + # this function might get moved elsewhere. + from mathics.core.builtin import ( + Operator, + PatternObject, + SympyObject, + mathics_to_python, + ) + + name = builtin_instance.get_name() + if hasattr(builtin_instance, "python_equivalent"): + # print("XXX0", builtin_instance.python_equivalent) + mathics_to_python[name] = builtin_instance.python_equivalent + + if isinstance(builtin_instance, SympyObject): + mathics_to_sympy[name] = builtin_instance + for sympy_name in builtin_instance.get_sympy_names(): + # print("XXX1", sympy_name) + sympy_to_mathics[sympy_name] = builtin_instance + if isinstance(builtin_instance, Operator): + assert builtin_instance.precedence is not None + builtins_precedence[Symbol(name)] = builtin_instance.precedence + if isinstance(builtin_instance, PatternObject): + pattern_objects[name] = builtin_instance.__class__ + operator = builtin_instance.get_operator_display() if operator is not None: display_operators_set.add(operator) + + _builtins.update({name: builtin_instance}) diff --git a/test/core/test_load_builtin.py b/test/core/test_load_builtin.py new file mode 100644 index 000000000..21b6349ab --- /dev/null +++ b/test/core/test_load_builtin.py @@ -0,0 +1,42 @@ +import importlib + +from mathics.core.load_builtin import ( + _builtins, + add_builtins_from_builtin_module, + import_and_load_builtins, +) +from mathics.session import MathicsSession + + +def test_add_builtins_from_builtin_module(): + """ + Test that add_builtins_from_module() loads a single Builtin module + and updates definitions. + """ + # Set up a session with all but one module. + # Then evaluate a builtin in that module and see that we + # now have the function defined. + + # First, load in many modules except quantum_mechanics. + _builtins = {} + import_and_load_builtins(exclude_files={"quantum_mechanics"}, clean_all=True) + + # Create a session, evaluate an expression using a missing Builtin function + # and see that it is not defined... + session = MathicsSession(character_encoding="ASCII") + assert str(session.evaluate("PauliMatrix[0]")) == "Global`PauliMatrix[0]" + assert ( + str(session.evaluate("SixJSymbol[{1,2,3}, {1,2,3}]")) + == "Global`SixJSymbol[{1,2,3}, {1,2,3}]" + ) + # Finally add in the module and see that when we use Builtin functions + # in that module work. + angular_module = importlib.import_module( + "mathics.builtin.quantum_mechanics.angular" + ) + add_builtins_from_builtin_module(angular_module) + + # Note that adding more builtins does not update the session, so we need a new one. + session = MathicsSession(character_encoding="ASCII") + assert str(session.evaluate("PauliMatrix[0]")) == "{{1,0},{0,1}}" + assert str(session.evaluate("SixJSymbol[{1, 2, 3}, {1, 2, 3}]")) == "1/105"