'''
A Curry interpreter.
The Interpreter manages compilation and evaluation of Curry code. Each
instance has a separate copy of the settings and runtime.
'''
__all__ = ['Interpreter']
from .. import backends, config, exceptions, icurry, typecheck, utility
from ..backends.generic.eval import evaluator as _evaluator
from . import flags as _flagmod, import_
from ..objects.handle import getHandle
import importlib
from ..utility.binding import binding
from ..utility import curryname, formatDocstring
import logging, os, sys
logger = logging.getLogger(__name__)
[docs]
@utility.formatDocstring(config.python_package_name())
class Interpreter(object):
'''
Represents one instance of a Curry system.
Key data and methods are as follows:
Data:
:data:`flags`
Configuration flags.
:data:`modules`
Imported Curry modules.
:data:`path`
The Curry search path.
:data:`prelude`
The built-in Curry module ``Prelude``.
:data:`setfunctions`
The built-in Curry module ``Control.SetFunctions``.
:data:`sigtable`
The signature table: the type schemes of the loaded symbols.
Methods:
:meth:`compile`
Compile a string containing Curry code.
:meth:`eval`
Evaluate a Curry expression.
:meth:`expr`
Build a Curry expression.
:meth:`import_`
Import a Curry module.
:meth:`load`
Load compiled Curry.
:meth:`module`
Look up a module by name.
:meth:`reset`
Soft-reset the interpreter.
:meth:`save`
Save compiled Curry.
:meth:`stats`
Report the statistics of the run.
:meth:`symbol`
Look up a symbol by name.
:meth:`topython`
Convert Curry data to Python.
:meth:`type`
Look up a data type by name.
'''
def __new__(cls, flags={}):
self = object.__new__(cls)
self._flags = _flagmod.get_default_flags()
bad_flags = set(flags) - set(self._flags)
if bad_flags:
raise ValueError('unknown flag%s: %s' % (
's' if len(bad_flags) > 1 else ''
, ', '.join(map(repr, bad_flags))
))
# The flags start from the environment, as the flags of the global
# interpreter do: SPRITE_ROTATION, then SPRITE_INTERPRETER_FLAGS, then
# the argument (flags.getflags). So a fresh interpreter runs as the
# process does unless its argument says otherwise.
self._flags.update(_flagmod.getflags(flags))
_flagmod.check_flags(self._flags)
self._backend = backends.IBackend(self._flags['backend'])
self._modules = {}
self._path = []
# The steps and forks of the evaluations of this interpreter; see stats.
self._evaluation_totals = _evaluator.EvaluationTotals()
self._sigtable = typecheck.SignatureTable(self)
self.reset() # set remaining attributes.
return self
@property
@utility.formatDocstring(config.python_package_name())
def flags(self):
'''
A dict containing the configuration flags. Change its entries in
place to change the behavior of the Curry system; the attribute cannot
be assigned. The flag ``backend`` is read when the interpreter is
made, so a later change of it has no effect. See
:mod:`{0}.interpreter.flags`.
'''
return self._flags
@property
@utility.formatDocstring(config.python_package_name())
def modules(self):
'''
A dict containing the imported Curry modules. Maps module names
to :class:`CurryModule <{0}.objects.CurryModule>` objects.
'''
return self._modules
@property
def path(self):
'''
A list of strings specifying the Curry search path. Change it in
place to adjust where to search for Curry files. An assignment to the
attribute replaces the entries of the same list.
'''
return self._path
@path.setter
def path(self, values):
self._path[:] = [str(x) for x in values]
@property
def backend(self):
'''The backend object associated with this interpreter.'''
return self._backend
@property
def prelude(self):
'''The built-in module Prelude.'''
if not hasattr(self, '__preludelib'):
self.__preludelib = self.module('Prelude')
return self.__preludelib
@property
def setfunctions(self):
'''The built-in module Control.SetFunctions.'''
if not hasattr(self, '__setflib'):
self.__setflib = self.module('Control.SetFunctions')
return self.__setflib
@property
@utility.formatDocstring(config.python_package_name())
def sigtable(self):
'''
The signature table, a :class:`SignatureTable
<{0}.typecheck.SignatureTable>`: the type schemes of the loaded symbols,
read on demand from the FlatCurry interfaces. :meth:`reset` clears it.
'''
return self._sigtable
[docs]
def reset(self):
'''
Soft-resets the interpreter.
Clears the loaded modules, except the Prelude and the packages.
Restores I/O streams to their defaults, resets the Curry path from the
environment, releases the expression modules, and clears the signature
table. The names of anonymous modules are not reused; see compile.py.
This is much faster than building a new interpreter, which loads the
Prelude.
'''
self.stdin = sys.stdin
self.stdout = sys.stdout
self.stderr = sys.stderr
self.automodules = config.syslibs()
self._expression_modules = [] # see compile.py, mode 'expr'
self._lifted_goals = {} # see typecheck.goals.register_lifted
self._unlink_modules()
self.path[:] = config.currypath(reset=True)
self.backend.init_interpreter_state(self)
self._sigtable.clear()
def _unlink_modules(self):
'''
Unlinks the loaded modules, except the Prelude and the packages. The
backend hands the tables of each module to the runtime
(IBackend.module_unlinked), so a module of the same name made later
runs its own code while an object of the old module is alive (issue
#114). ``reset`` and ``reload`` call this.
'''
for name, module in list(self.modules.items()):
module = getHandle(module)
if not module.is_package and name != 'Prelude':
module.unlink(self)
[docs]
def module(self, name):
'''Look up a module by name.'''
try:
return self.modules[name]
except KeyError:
if name in self.automodules:
return self.import_(name)
raise exceptions.ModuleLookupError('Curry module %r not found' % name)
[docs]
def symbol(self, name, modulename=None):
'''
Look up a symbol by its fully-qualified name, ``'Prelude.+'``. The
name splits at its first dot into a module and a name in it, so
``'Data.List.nub'`` needs the package ``Data`` loaded, as an import of
``Data.List`` leaves it. The second parameter has no effect.
'''
modulename, _, objname = name.partition('.')
moduleobj = self.module(modulename)
return getHandle(moduleobj).getsymbol(objname)
[docs]
def type(self, name):
'''Returns the constructor info tables for the named type.'''
modulename, _, name = name.partition('.')
moduleobj = self.module(modulename)
return getHandle(moduleobj).gettype(name)
# Externally-implemented methods.
from .compile import compile
from .conversions import currytype, topython, unbox
from ..expressions import describe, expr, raw_expr, typeof
from .eval import eval
from .import_ import import_
from .loadsave import load, save
from .optimize import optimize
from .stats import stats
unbox = staticmethod(unbox)
@formatDocstring(config.python_package_name())
def reload(name, flags={}):
'''Hard-resets the interpreter found in module ``{}``.'''
flags = _flagmod.getflags(flags)
envflags = ','.join('%s:%s' % (str(k), str(v)) for k,v in flags.items())
with binding(os.environ, 'SPRITE_INTERPRETER_FLAGS', envflags):
this = sys.modules[name]
old = getattr(this, '_interpreter_', None)
if isinstance(old, Interpreter):
# The modules of the interpreter replaced leave it as a reset
# unlinks them, before the new interpreter imports: a module of the
# same name then takes its tables back and runs its own code while
# an object of the old module is alive (issue #114).
old._unlink_modules()
importlib.reload(this)