diff --git a/examples/symbolic_logic/gries_schneider/test_gs.py b/examples/symbolic_logic/gries_schneider/test_gs.py index 7212ec8ee..9bd5b765a 100644 --- a/examples/symbolic_logic/gries_schneider/test_gs.py +++ b/examples/symbolic_logic/gries_schneider/test_gs.py @@ -5,7 +5,9 @@ from mathics.core.definitions import Definitions from mathics.core.evaluation import Evaluation from mathics.core.parser import MathicsSingleLineFeeder, parse +from mathics.core.system_init import initialize_system +initialize_system() definitions = Definitions(add_builtin=True) for i in range(0, 4): diff --git a/mathics/builtin/__init__.py b/mathics/builtin/__init__.py index e809af4b2..5cc72381d 100755 --- a/mathics/builtin/__init__.py +++ b/mathics/builtin/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """ -Mathics Builtin Functions and Variables. +Mathics Builtin Functions and Variables. Mathics has over a thousand Built-in functions and variables, all of which are defined here. @@ -20,13 +20,10 @@ among other things. """ -import glob import importlib -import inspect -import os.path as osp import pkgutil import re -from typing import List, Optional +from typing import List from mathics.builtin.base import ( Builtin, @@ -36,15 +33,18 @@ mathics_to_python, ) from mathics.core.pattern import pattern_objects +from mathics.core.system_init import get_builtin_pyfiles, name_is_builtin_symbol from mathics.settings import ENABLE_FILES_MODULE -from mathics.version import __version__ # noqa used in loading to check consistency. -# Get a list of files in this directory. We'll exclude from the start -# files with leading characters we don't want like __init__ with its leading underscore. -__py_files__ = [ - osp.basename(f[0:-3]) - for f in glob.glob(osp.join(osp.dirname(__file__), "[a-z]*.py")) -] +__py_files__ = get_builtin_pyfiles() + +mathics_to_sympy = {} # here we have: name -> sympy object +sympy_to_mathics = {} +builtins_list = [] + +builtins_precedence = {} + +system_builtins = {} def add_builtins(new_builtins): @@ -63,35 +63,7 @@ def add_builtins(new_builtins): builtins_precedence[name] = builtin.precedence if isinstance(builtin, PatternObject): pattern_objects[name] = builtin.__class__ - _builtins.update(dict(new_builtins)) - - -def builtins_dict(): - return { - builtin.get_name(): builtin - for modname, builtins in builtins_by_module.items() - for builtin in builtins - } - - -def contribute(definitions): - # let MakeBoxes contribute first - _builtins["System`MakeBoxes"].contribute(definitions) - for name, item in _builtins.items(): - if name != "System`MakeBoxes": - item.contribute(definitions) - - from mathics.core.definitions import Definition - from mathics.core.expression import ensure_context - from mathics.core.parser import all_operator_names - - # All builtins are loaded. Create dummy builtin definitions for - # any remaining operators that don't have them. This allows - # operators like \[Cup] to behave correctly. - for operator in all_operator_names: - if not definitions.have_definition(ensure_context(operator)): - op = ensure_context(operator) - definitions.builtin[op] = Definition(name=op) + system_builtins.update(dict(new_builtins)) def import_builtins(module_names: List[str], submodule_name=None) -> None: @@ -125,48 +97,6 @@ def import_module(module_name: str, import_name: str): import_module(module_name, import_name) -def name_is_builtin_symbol(module, name: str) -> Optional[type]: - """ - Checks if ``name`` should be added to definitions, and return - its associated Builtin class. - - Return ``None`` if the name should not get added to definitions. - """ - if name.startswith("_"): - return None - - module_object = getattr(module, name) - - # Look only at Class objects. - if not inspect.isclass(module_object): - return None - - # FIXME: tests involving module_object.__module__ are fragile and - # Python implementation specific. Figure out how to do this - # via the inspect module which is not implementation specific. - - # Skip those builtins defined in or imported from another module. - if module_object.__module__ != module.__name__: - return None - - # Skip objects in module mathics.builtin.base. - if module_object.__module__ == "mathics.builtin.base": - return None - - # Skip those builtins that are not submodules of mathics.builtin. - if not module_object.__module__.startswith("mathics.builtin."): - return None - - # If it is not a subclass of Builtin, skip it. - if not issubclass(module_object, Builtin): - return None - - # Skip Builtin classes that were explicitly marked for skipping. - if module_object in getattr(module, "DOES_NOT_ADD_BUILTIN_DEFINITION", []): - return None - return module_object - - # FIXME: redo using importlib since that is probably less fragile. exclude_files = {"codetables", "base"} module_names = [ @@ -176,7 +106,6 @@ def name_is_builtin_symbol(module, name: str) -> Optional[type]: modules = [] import_builtins(module_names) -_builtins_list = [] builtins_by_module = {} disable_file_module_names = ( @@ -232,25 +161,8 @@ def name_is_builtin_symbol(module, name: str) -> Optional[type]: # This set the default context for symbols in mathics.builtins if not type(instance).context: type(instance).context = "System`" - _builtins_list.append((instance.get_name(), instance)) - builtins_by_module[module.__name__].append(instance) -mathics_to_sympy = {} # here we have: name -> sympy object -sympy_to_mathics = {} - -builtins_precedence = {} - -new_builtins = _builtins_list - -# FIXME: some magic is going on here.. -_builtins = {} - -add_builtins(new_builtins) + builtins_list.append((instance.get_name(), instance)) + builtins_by_module[module.__name__].append(instance) -display_operators_set = set() -for modname, builtins in builtins_by_module.items(): - for builtin in builtins: - # name = builtin.get_name() - operator = builtin.get_operator_display() - if operator is not None: - display_operators_set.add(operator) +add_builtins(builtins_list) diff --git a/mathics/core/definitions.py b/mathics/core/definitions.py index 731222ea3..6fc56571e 100644 --- a/mathics/core/definitions.py +++ b/mathics/core/definitions.py @@ -21,6 +21,7 @@ Symbol, strip_context, ) +from mathics.core.system_init import contribute from mathics.core.systemsymbols import SymbolGet from mathics_scanner.tokeniser import full_names_pattern @@ -130,7 +131,7 @@ def __init__( self.timing_trace_evaluation = False if add_builtin: - from mathics.builtin import modules, contribute + from mathics.builtin import modules from mathics.settings import ROOT_DIR loaded = False diff --git a/mathics/core/pymathics.py b/mathics/core/pymathics.py index 354404b10..481a48f91 100644 --- a/mathics/core/pymathics.py +++ b/mathics/core/pymathics.py @@ -8,6 +8,7 @@ from mathics.core.evaluation import Evaluation +from mathics.core.system_init import builtins_by_module, name_is_builtin_symbol # This dict probably does not belong here. pymathics = {} @@ -22,7 +23,6 @@ def __init__(self, module): # Why do we need this? def eval_clear_pymathics_modules(): global pymathics - from mathics.builtin import builtins_by_module for key in list(builtins_by_module.keys()): if not key.startswith("mathics."): @@ -61,7 +61,6 @@ def load_pymathics_module(definitions, module): """ from mathics.builtin import ( builtins_by_module, - name_is_builtin_symbol, Builtin, ) diff --git a/mathics/core/system_init.py b/mathics/core/system_init.py new file mode 100644 index 000000000..2e507001f --- /dev/null +++ b/mathics/core/system_init.py @@ -0,0 +1,184 @@ +""" +One-time initialization of Mathics module loading, and creation of built-in functions. +""" + +import glob +import inspect +import os.path as osp +from typing import Optional + +from mathics.core.pattern import pattern_objects + +builtins_precedence = {} +mathics_to_sympy = {} # here we have: name -> sympy object +sympy_to_mathics = {} + +# builtins_by_module maps a full module name, e.g. "mathics.builtin.evaluation" to a +# list of Builtin classes. +builtins_by_module = {} +builtins_list = [] + +# Get a list of files in this directory. We'll exclude from the start +# files with leading characters we don't want like __init__ with its leading underscore. +__py_files__ = [ + osp.basename(f[0:-3]) + for f in glob.glob(osp.join(osp.dirname(__file__), "[a-z]*.py")) +] + +builtins_precedence = {} + +system_builtins = {} + + +def add_builtins(new_builtins): + from mathics.builtin.base import ( + Operator, + PatternObject, + SympyObject, + mathics_to_python, + ) + + for var_name, 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): + builtins_precedence[name] = builtin.precedence + if isinstance(builtin, PatternObject): + pattern_objects[name] = builtin.__class__ + system_builtins.update(dict(new_builtins)) + + +def builtins_dict(): + return { + builtin.get_name(): builtin + for modname, builtins in builtins_by_module.items() + for builtin in builtins + } + + +def contribute(definitions): + # let MakeBoxes contribute first + system_builtins["System`MakeBoxes"].contribute(definitions) + for name, item in system_builtins.items(): + if name != "System`MakeBoxes": + item.contribute(definitions) + + from mathics.core.definitions import Definition + from mathics.core.expression import ensure_context + from mathics.core.parser import all_operator_names + + # All builtins are loaded. Create dummy builtin definitions for + # any remaining operators that don't have them. This allows + # operators like \[Cup] to behave correctly. + for operator in all_operator_names: + if not definitions.have_definition(ensure_context(operator)): + op = ensure_context(operator) + definitions.builtin[op] = Definition(name=op) + + +def create_builtins_by_module(): + from mathics.builtin import modules + from mathics.builtin.base import Builtin + + for module in modules: + builtins_by_module[module.__name__] = [] + module_vars = dir(module) + + for name in module_vars: + builtin_class = name_is_builtin_symbol(module, name) + if builtin_class is not None: + instance = builtin_class(expression=False) + + if isinstance(instance, Builtin): + # This set the default context for symbols in mathics.builtins + if not type(instance).context: + type(instance).context = "System`" + builtins_list.append((instance.get_name(), instance)) + builtins_by_module[module.__name__].append(instance) + add_builtins(builtins_list) + + +def get_builtin_pyfiles() -> tuple: + """ + Return a list of files in this directory. We'll exclude from the start + files with leading characters we don't want like __init__ with its leading underscore. + """ + return tuple( + osp.basename(f[0:-3]) + for f in glob.glob( + osp.join(osp.dirname(__file__), "..", "builtin", "[a-z]*.py") + ) + ) + + +display_operators_set = set() + +# TODO: after doing more, e.g. moving Builtin class processing, +# consider adding an administrative routine to write definitions in +# Python Pickle format (or something suitable. Then we can add a flag +# here to read this in which might be faster. +# +def initialize_system(): + """ + One-time Builtin initialization. + Not much here but more may be added. + """ + create_builtins_by_module() + for modname, builtins in builtins_by_module.items(): + for builtin in builtins: + # name = builtin.get_name() + operator = builtin.get_operator_display() + if operator is not None: + display_operators_set.add(operator) + + +def name_is_builtin_symbol(module, name: str) -> Optional[type]: + """ + Checks if ``name`` should be added to definitions, and return + its associated Builtin class. + + Return ``None`` if the name should not get added to definitions. + """ + from mathics.builtin.base import Builtin + + if name.startswith("_"): + return None + + module_object = getattr(module, name) + + # Look only at Class objects. + if not inspect.isclass(module_object): + return None + + # FIXME: tests involving module_object.__module__ are fragile and + # Python implementation specific. Figure out how to do this + # via the inspect module which is not implementation specific. + + # Skip those builtins defined in or imported from another module. + if module_object.__module__ != module.__name__: + return None + + # Skip objects in module mathics.builtin.base. + if module_object.__module__ == "mathics.builtin.base": + return None + + # Skip those builtins that are not submodules of mathics.builtin. + if not module_object.__module__.startswith("mathics.builtin."): + return None + + # If it is not a subclass of Builtin, skip it. + if not issubclass(module_object, Builtin): + return None + + # Skip Builtin classes that were explicitly marked for skipping. + if module_object in getattr(module, "DOES_NOT_ADD_BUILTIN_DEFINITION", []): + return None + return module_object diff --git a/mathics/doc/common_doc.py b/mathics/doc/common_doc.py index 442c61320..b5e0e2a41 100644 --- a/mathics/doc/common_doc.py +++ b/mathics/doc/common_doc.py @@ -34,11 +34,14 @@ from types import ModuleType from typing import Callable +import mathics.core as core from mathics import builtin from mathics import settings from mathics.builtin.base import check_requires_list from mathics.core.evaluation import Message, Print from mathics.core.util import IS_PYPY +from mathics.core.system_init import name_is_builtin_symbol + from mathics.doc.utils import slugify # These regular expressions pull out information from docstring or text in a file. @@ -830,7 +833,7 @@ def __init__(self, want_sorting=False): ( "Reference of Built-in Symbols", builtin.modules, - builtin.builtins_by_module, + core.system_init.builtins_by_module, True, ) ]: # nopep8 diff --git a/mathics/docpipeline.py b/mathics/docpipeline.py index 01fa2667c..346f46f73 100644 --- a/mathics/docpipeline.py +++ b/mathics/docpipeline.py @@ -24,13 +24,14 @@ from mathics.core.definitions import Definitions from mathics.core.evaluation import Evaluation, Output from mathics.core.parser import MathicsSingleLineFeeder -from mathics.builtin import builtins_dict +from mathics.core.system_init import builtins_dict, initialize_system from mathics import version_string from mathics import settings from mathics.doc.common_doc import MathicsMainDocumentation from mathics.timing import show_lru_cache_statistics +initialize_system() builtins = builtins_dict() diff --git a/mathics/format/mathml.py b/mathics/format/mathml.py index e0281e8ec..fcafe2dc7 100644 --- a/mathics/format/mathml.py +++ b/mathics/format/mathml.py @@ -32,6 +32,7 @@ ) from mathics.core.parser import is_symbol_name from mathics.core.symbols import SymbolTrue +from mathics.core.system_init import display_operators_set as operators def encode_mathml(text: str) -> str: @@ -62,7 +63,6 @@ def encode_mathml(text: str) -> str: def string(self, **options) -> str: - from mathics.builtin import display_operators_set as operators text = self.value diff --git a/mathics/main.py b/mathics/main.py index 3b90a24f6..e5b6668e6 100755 --- a/mathics/main.py +++ b/mathics/main.py @@ -23,6 +23,7 @@ from mathics.core.rules import BuiltinRule from mathics.core.symbols import strip_context, SymbolNull from mathics.core.streams import stream_manager +from mathics.core.system_init import initialize_system from mathics.timing import show_lru_cache_statistics @@ -369,6 +370,7 @@ def dump_tracing_stats(): if args.show_statistics: atexit.register(show_lru_cache_statistics) + initialize_system() definitions = Definitions(add_builtin=True, extension_modules=extension_modules) definitions.set_line_no(0) diff --git a/mathics/session.py b/mathics/session.py index 88a4957b6..ca8c2e845 100644 --- a/mathics/session.py +++ b/mathics/session.py @@ -16,6 +16,7 @@ from mathics.core.parser import parse, MathicsSingleLineFeeder from mathics.core.definitions import Definitions from mathics.core.evaluation import Evaluation +from mathics.core.system_init import initialize_system import mathics.settings @@ -63,6 +64,7 @@ def __init__( form="InputForm", character_encoding=Optional[str], ): + initialize_system() if character_encoding is not None: mathics.settings.SYSTEM_CHARACTER_ENCODING = character_encoding self.form = form diff --git a/test/consistency-and-style/test_duplicate_builtins.py b/test/consistency-and-style/test_duplicate_builtins.py index 56a597c7c..de6faff9a 100644 --- a/test/consistency-and-style/test_duplicate_builtins.py +++ b/test/consistency-and-style/test_duplicate_builtins.py @@ -6,8 +6,9 @@ """ import pytest import os -from mathics.builtin import name_is_builtin_symbol, modules +from mathics.builtin import modules from mathics.builtin.base import Builtin +from mathics.core.system_init import name_is_builtin_symbol @pytest.mark.skipif(