Source code for curry.interpreter.compile

'''
Implements Interpreter.compile.
'''

from ..backends.generic.eval import evaluator
from .. import (
    config, exceptions, expressions, icurry, inspect, objects, toolchain, utility
  )
from ..objects import handle
from ..typecheck import defaulting, goals, sigtable
from ..utility.visitation import dispatch
import itertools, os, types

__all__ = ['compile', 'expression_scheme']

COMPILED_NAME = 'compiled_expression'

# The constructor that an expression module with lifted free variables
# declares, ``data SpriteLiftedGoal a = SpriteLiftedGoal a``.  It wraps the
# tuple of the expression and the reported variables, so that curry.eval
# recognizes the root (goals.lifted_goal) by a node that no rewrite replaces.
LIFTED_NAME = 'SpriteLiftedGoal'

# Numbers the anonymous modules: interactive modules (mode 'module' without a
# name) and expression modules.  The counter belongs to the process, not to
# the interpreter, so a name never repeats after reset() or reload().  The C++
# backend resolves a module's symbols against the same-named module loaded
# first, so a reused name could resolve to stale code.  See
# config.expression_modname.
_module_counter = itertools.count()

[docs] @utility.formatDocstring(config.python_package_name()) def compile( interp, string, mode='module', imports=None, exprtype=None, modulename=None ): ''' Compile a string containing Curry code. In mode 'module', the string is interpreted as a Curry module. In mode 'expr', the string is interpreted as a Curry expression. Args: string: A string containing Curry code. mode: Indicates how to interpret the string (see above). imports: A list specifying additional modules to import. The elements can be strings or :class:`CurryModule <{0}.objects.CurryModule>` objects. Import statements will be prepended to the code string and CURRYPATH will be adjusted accordingly. This can be useful when importing dynamically-compiled modules whose names and locations are difficult to obtain. exprtype: A string specifying the expression type. This is what would appear to the right of ``::`` in a Curry type annotation for the expression. Used only in 'expr' mode. Without it, the class constraints of the expression are defaulted with the table of the PAKCS REPL: ``Num`` to ``Int``, ``Fractional`` to ``Float``, ``Monad`` to ``IO``, a lone ``Data`` to ``Bool``; see :mod:`{0}.typecheck.defaulting`. modulename: Specifies the module name. Used only in 'module' mode. The module is placed in :data:`curry.interpreter:Interpreter.modules` under that name. By default, a unique name is chosen, which no later compile reuses. Returns: In 'module' mode, a :class:`CurryModule <{0}.objects.CurryModule>`. In 'expr' mode, a Curry expression. A text that ends in ``where x, y free`` declares free variables, as in the REPL. A variable whose type is absent from the result type of the expression cannot show in a value, so ``curry.eval`` yields each value of such an expression with its binding (:class:`Bindings <{0}.typecheck.goals.Bindings>`); a variable whose type occurs in the result type is left in the value. The REPL of PAKCS prints the binding of every declared variable. With ``exprtype``, or with a comment after ``free``, the clause stays a local declaration and no binding is reported. Raises: ~{0}.exceptions.CompileError: The front end rejects the text, or, in 'expr' mode without ``exprtype``, the table cannot default a class constraint. ''' stmts, currypath = getImportSpecForExpr( interp, [] if imports is None else imports ) if mode == 'module': if exprtype is not None: raise ValueError('%r is only allowed in mode=%r', ('exprtype', 'expr')) if modulename is None: modulename = config.interactive_modname() + str(next(_module_counter)) if modulename in interp.modules: raise ValueError('module %r is already defined' % modulename) string = '%s\n%s' % ('\n'.join(stmts), string) moduleobj, icur = toolchain.str2module( interp, string, currypath , modulename=modulename , keep_temp_files=interp.flags['keep_temp_files'] , postmortem=interp.flags['postmortem'] ) try: return moduleobj finally: if icur.name == config.interactive_modname() \ and icur.name in interp.modules: del interp.modules[icur.name] elif mode == 'expr': if modulename is not None: raise ValueError('%r is only allowed in mode=%r', ('modulename', 'module')) func, freevars, moduleobj = compile_expression( interp, string, stmts, currypath, exprtype=exprtype ) return expression_goal(interp, func, string, freevars, moduleobj) else: raise TypeError('expected mode %r or %r' % ('module', 'expr'))
def compile_expression( interp, string, stmts, currypath, exprtype=None, lift_freevars=True ): ''' Compiles a Curry expression into a module of its own, as the binding ``compiled_expression = <string>``. With ``lift_freevars``, the variables of a trailing ``where x, y free`` become parameters of the binding, as the REPL of PAKCS does; with ``exprtype`` the text is compiled as written under the signature. A module with lifted variables declares the constructor LIFTED_NAME. Returns the symbol of the binding, the names of the lifted variables, and the module. ''' freevars = [] if lift_freevars and not exprtype: string, freevars = goals.split_where_free(string) stmts = list(stmts) if freevars: stmts += ['data %s a = %s a' % (LIFTED_NAME, LIFTED_NAME)] if exprtype: stmts += ['%s :: %s' % (COMPILED_NAME, exprtype)] stmts += ['%s%s = %s' % (COMPILED_NAME, ''.join(' ' + v for v in freevars), string)] curry_code = '\n'.join(stmts) moduleobj, icur = toolchain.str2module( interp, curry_code, currypath , modulename=config.expression_modname(next(_module_counter)) , keep_temp_files=interp.flags['keep_temp_files'] , postmortem=interp.flags['postmortem'] ) # The step functions of the module are compiled now, while the module is # in the registry, where the compiled code resolves the symbols of the # module by name (a lifted lambda, a derived instance method). interp.backend.compile_pending(moduleobj) # The module leaves the registry, but it stays loaded until the # interpreter resets: the goal's graph refers to the module's code and # data (string literals, local functions), and nothing in the goal keeps # the module alive. On the C++ backend, an unloaded module leaves # dangling pointers in any goal compiled from it. The name is unique for # the process, so the modules cannot clash. del interp.modules[icur.name] interp._expression_modules.append(moduleobj) return getattr(moduleobj, '.symbols')[COMPILED_NAME], freevars, moduleobj def expression_goal(interp, func, string, freevars, moduleobj): ''' The expression of a compiled text. The leading parameters of the binding are class dictionaries; the table of the PAKCS REPL defaults them. The parameters after them are the lifted free variables, which get fresh variables. A saturated call takes one rewrite step here, so the result looks like the expression and not like a call of the binding. A lifted variable whose type is absent from the result type is reported with its binding (``goals.absent_from_result``). The expression is then the constructor LIFTED_NAME of the module around the tuple of the call and those variables, so that ``curry.eval`` recognizes the root (``goals.lifted_goal``) and yields :class:`Bindings <curry.typecheck.goals.Bindings>`; a constructor node survives the rewrites of an evaluation, so the expression evaluates again. A variable whose type occurs in the result type is left in the value. ''' nfree = len(freevars) dicts = [] scheme = None if func.info.arity > nfree or nfree: try: scheme = interp.sigtable.lookup(func, required=True) except sigtable.InterfaceError as err: raise exceptions.CompileError( 'expression %r has parameters but no type: %s' % (string, err) ) if scheme.source_arity != nfree: raise exceptions.CompileError( 'expression %r compiled to a function of %d value parameter(s); ' 'add a type annotation (exprtype)' % (string, scheme.source_arity) ) if func.info.arity > nfree: try: defaulted = defaulting.default_scheme( scheme, what='expression %r' % string , hint='add a type annotation (exprtype)' , type_arity=goals.type_arity(interp) ) except defaulting.DefaultingError as err: raise exceptions.CompileError(str(err)) dicts = goals.dictionaries(interp, defaulted) markers = [goals.free_marker() for _ in freevars] # The front end typed the text; the expression module is not in the # registry, so the typed builder could not read its schemes. The untyped # builder keeps the markers as shared unknown nodes. expr = expressions.untyped_expr(interp, func, *dicts, *markers) if inspect.isa_func(expr): evaluator.single_step(interp, expr) reported = goals.absent_from_result(scheme, nfree) if nfree else [] if not reported: return expr ctor, = getattr(moduleobj, '.types')[LIFTED_NAME].constructors goals.register_lifted( interp, ctor, [freevars[i] for i in reported], scheme, string ) return expressions.untyped_expr( interp, ctor, (expr,) + tuple(markers[i] for i in reported) )
[docs] def expression_scheme(interp, string, imports=None): ''' The type scheme of a Curry expression, as the front end infers it for the binding ``compiled_expression = <string>``, before any defaulting; what ``:type`` prints in the REPL. A trailing ``where x free`` stays a local declaration, as in the ``:type`` of PAKCS. ''' stmts, currypath = getImportSpecForExpr( interp, [] if imports is None else imports ) func, _, _ = compile_expression( interp, string, stmts, currypath, lift_freevars=False ) return interp.sigtable.lookup(func, required=True)
def getImportSpecForExpr(interp, modules): ''' Generates the import statements and the CURRYPATH to use when compiling a standalone Curry expression. Args: interp: The interpreter. modules: The list of modules to import. Each one may be a string or a Curry module object. Instead of a list, a module object may be passed. Returns: A pair consisting of a list of Curry import statements and the updated search path. ''' stmts = [] currypath = list(interp.path) if isinstance(modules, objects.CurryModule): modules = [modules] for module in modules: _updateImports(interp, module, stmts, currypath) return stmts, currypath @dispatch.on('module') def _updateImports(interp, module, stmts, currypath): assert False @_updateImports.when(str) def _updateImports(interp, modulename, stmts, currypath): module = interp.modules[modulename] return _updateImports(interp, module, stmts, currypath) @_updateImports.when(types.ModuleType) def _updateImports(interp, module, stmts, currypath): if module.__name__ != '_System': stmts.append('import ' + module.__name__) # If this is a dynamic module, add its directory to the search path. h = handle.Handle(module) tmpd = h.icurry.metadata.get('all.tmpd', None) if tmpd is not None: currypath.insert(0, tmpd) elif not h.is_package and h.icurry.filename: # A module loaded from a file outside the search path, e.g., by the # :load command of the REPL: the front end must find its source. root = sigtable.module_root( os.path.dirname(os.path.abspath(h.icurry.filename)), h.fullname ) if root not in currypath: currypath.insert(0, root)