Source code for curry.toolchain.plans

from ..backends import IBackend
from . import _curry2icurry, _filenames, _icurry2json
from ..utility import curryname
import collections, itertools, os

__all__ = ['makeplan', 'Plan', 'Stage']

Stage = collections.namedtuple('Stage', ['suffixes', 'step'])

# Stage flags.
UNCONDITIONAL      = 0x0
MAKE_ICURRY        = 0x1
MAKE_JSON          = 0x2
MAKE_TARGET_SOURCE = 0x4
MAKE_TARGET_OBJECT = 0x8
ZIP_JSON           = 0x1000
MAKE_ALL           = MAKE_ICURRY | MAKE_JSON | MAKE_TARGET_SOURCE | MAKE_TARGET_OBJECT

[docs] def makeplan(interp=None, flags=0): ''' Make a ``Plan`` object, which describes the compilation pipeline. If ``interp`` is None, then the plan can only go as far as building ICurry-JSON. Args: interp: An optional interpreter used to assist in building objects. This defines the backend and is needed to perform certain build steps, such as the conversion from JSON to target code. flags: Indicates which build stages are enabled. ''' assert not (flags &~ (MAKE_ALL | ZIP_JSON)) if interp is None: flags &= ~MAKE_TARGET_SOURCE flags &= ~MAKE_TARGET_OBJECT stages = list(_getstages(interp, flags)) return Plan(interp, flags, stages)
def _getstages(interp, flags): json_suffix = '.json.z' if (flags & ZIP_JSON) else '.json' skeleton = [ # Flag Suffixes Step (if enabled) # ------------------ ------------ ----------------------------- (MAKE_ICURRY , ['.curry'] , _curry2icurry.curry2icurry) , (MAKE_JSON , ['.icy'] , _icurry2json.icurry2json) , (MAKE_TARGET_SOURCE, [json_suffix], None) ] if interp is not None: interp.backend.extend_plan_skeleton(interp, skeleton) for flag, suffixes, step_if_enabled in skeleton: step = step_if_enabled if (flags & flag) else None yield Stage(suffixes, step) if step is None: return yield Stage([interp.backend.target_object_suffix], None)
[docs] class Plan(object): ''' A compilation plan. Describes the sequence of stages that comprise compilation. Each stage consists of a set of file suffixes and a step function. The suffixes identify files at that stage. The step function implements the transition to the next stage. ''' def __init__(self, interp, flags, stages): self.interp = interp self.flags = flags self.stages = stages def __str__(self): def lines(): yield 'Build plan with %r step%s:' % (len(self), '' if len(self) == 1 else 's') fmt = ' %-5s %-24s %s' yield fmt % ('Stage', 'Suffixes', 'Step') yield fmt % ('-----', '-' * 24, '-' * 40) for i,st in enumerate(self.stages): yield fmt % ( i , repr(st.suffixes) , getattr(st.step, '__name__', st.step) ) return '\n'.join(lines()) def __len__(self): '''Gives the number of steps in the plan.''' return len(self.stages) - 1
[docs] def filelist(self, curryfile): def gen(): yield curryfile icyfile = _filenames.icurryfilename(curryfile) assert icyfile.endswith('.icy') for suffix in self.suffixes: if suffix == '.curry': continue yield icyfile[:-4] + suffix return list(gen())
[docs] def prune_stale(self, filelist, currypath=None): ''' Removes from ``filelist``, the files of one module in the order of this plan, every file that a step refuses, and every file after it. A file that does not exist is kept, because nothing can be read from it. See ``is_stale``; ``currypath`` is the search path of the imports of the module (``_findcurry.imports_path``). ''' for i, filename in enumerate(filelist): if os.path.isfile(filename) and self.is_stale(filename, currypath): return filelist[:i] return filelist
[docs] def is_stale(self, filename, currypath=None): ''' Tells whether a step of this plan refuses ``filename``. A step refuses a file through its ``is_stale`` method, when it has one; see ``Json2Cpp`` and ``Cpp2So`` of the C++ backend and ``curry2icurry`` of the toolchain. The step of the file's stage, which reads the file, is asked, and so is the step before it, which made the file. A step that answers for both its input and its output tells them apart by the suffix. ``currypath`` is the search path of the imports of the module (``_findcurry.imports_path``), for a step that imports them to answer (``Cpp2So``). ''' position = self.position(filename) steps = [self.stages[position].step] if position: steps.append(self.stages[position - 1].step) for step in steps: is_stale = getattr(step, 'is_stale', None) if is_stale is not None and is_stale(filename, currypath): return True return False
[docs] def restore(self, filename, currypath, **kwds): ''' Asks the step that makes the final product of this plan to place a cached copy of the products of the module of ``filename`` beside its files (the product cache; see ``Cpp2So.restore`` of the C++ backend). Returns the file placed, or None when the step has no ``restore`` method, the cache is off, or the cache holds no entry. ``Maker.make`` asks before each step, so a module whose products are cached is neither generated nor compiled. ``kwds`` are the keywords of the plan (``is_sourcefile`` among them). ''' if len(self.stages) < 2: return None restore = getattr(self.stages[-2].step, 'restore', None) if restore is None: return None return restore(filename, currypath, **kwds)
[docs] def position(self, filename): '''Gives the current position in the plan.''' for i,(suffixes,_) in enumerate(self.stages): if any(filename.endswith(suffix) for suffix in suffixes): return i raise ValueError('file %r is not recognized' % filename)
@property def suffixes(self): '''Returns the sequence of file suffixes in this plan.''' def seq(): for stage in self.stages: for suffix in stage.suffixes: yield suffix return list(seq())