Source code for curry.toolchain.flat2icurry.compiler

'''
The name-to-index maps and the ICurry generation, the last two passes.  They
follow ``ICurry.Compiler`` and the map functions of ``ICurry.Options`` of
the Curry package ``icurry``.

Each ICurry name is a triple ``(module, name, index)``.  A constructor's
index is its position in its data type.  A data type's index is its position
in the type list of its module, type synonyms counted.  The public functions
of a module are numbered from 0 in order of appearance.  The private ones,
the lifted functions included, continue the numbering in the order of the
lifted program.
'''

from . import flatcurry as fc, icurrytypes as ic
from .errors import Flat2ICurryError
from ...utility.trampoline import trampoline

__all__ = ['NameMaps', 'flat2icurry', 'demand_of', 'strip_typed', 'var_pos']

[docs] class NameMaps: '''Maps qualified names to their ICurry indices.''' def __init__(self): self.cons = {} # (module, name) -> (arity, position) self.funs = {} # (module, name) -> index
[docs] @classmethod def build(cls, prog, impprogs, liftedprog): ''' Builds the maps for ``prog`` (after newtype elimination), its imports, and the lifted program, whose private functions follow the public ones. ''' maps = cls() for p in [prog] + list(impprogs): maps.add_constructors(p) pubfuns = [fd.name for fd in prog.functions if fd.visibility == fc.Public] for fd in prog.functions: if fd.visibility == fc.Public: maps.funs.setdefault(fd.name, len(maps.funs)) pubset = set(pubfuns) index = len(pubfuns) for fd in liftedprog.functions: if fd.name not in pubset: if fd.name not in maps.funs: maps.funs[fd.name] = index index += 1 for p in impprogs: maps.add_public_functions(p) return maps
[docs] def add_constructors(self, prog): for _, cars in fc.data_decls_of(prog): for pos, (cname, arity) in enumerate(cars): self.cons.setdefault(cname, (arity, pos))
[docs] def add_public_functions(self, prog): index = 0 for fd in prog.functions: if fd.visibility == fc.Public: self.funs.setdefault(fd.name, index) index += 1
[docs] def arity_pos_of_cons(self, qn, fun=''): try: return self.cons[qn] except KeyError: raise Flat2ICurryError( "Function '%s': Internal error in ICurry.Compiler:\n" "arity of constructor %s.%s is unknown" % (fun, qn[0], qn[1]) )
[docs] def pos_of_cons(self, qn, fun=''): return self.arity_pos_of_cons(qn, fun)[1]
[docs] def pos_of_fun(self, qn, fun=''): try: return self.funs[qn] except KeyError: raise Flat2ICurryError( "Function '%s': Internal error in ICurry.Compiler:\n" "arity of operation %s.%s is unknown" % (fun, qn[0], qn[1]) )
[docs] def flat2icurry(maps, prog, icurry_compat=True): ''' Translates a completed and lifted FlatCurry program to ICurry. ``icurry_compat`` keeps the output of icurry 3.1.0 for a type annotation at the root of a rule. See :func:`to_iblock`. ''' types = [] for ti, td in enumerate(prog.types): if isinstance(td, fc.TypeSyn): continue if isinstance(td, fc.TypeNew): raise Flat2ICurryError('ICurry.Compiler: newtype occurred!') mn, tn = td.name types.append(ic.IDataType( (mn, tn, ti) , [((c.name[0], c.name[1], i), c.arity) for i, c in enumerate(td.constructors)] )) functions = [tr_func(maps, fd, icurry_compat) for fd in prog.functions] return ic.IProg(prog.name, list(prog.imports), types, functions)
def tr_vis(vis): return ic.Public if vis == fc.Public else ic.Private def tr_func(maps, fd, icurry_compat=True): mn, fn = fd.name ctx = Context(maps, fd.name, icurry_compat) return ic.IFunction( (mn, fn, maps.pos_of_fun(fd.name, fn)), fd.arity, tr_vis(fd.visibility) , demand_of(fd.rule, icurry_compat), tr_rule(ctx, fd.rule) )
[docs] def strip_typed(e): '''The expression under any type annotations.''' while isinstance(e, fc.Typed): e = e.expr return e
[docs] def demand_of(rule, icurry_compat=True): ''' The argument a rule demands: the one its root case scrutinizes. icurry 3.1.0 does not look through a type annotation around the case. Without ``icurry_compat`` the annotation is removed first. ''' if isinstance(rule, fc.Rule): body = rule.body if icurry_compat else strip_typed(rule.body) if isinstance(body, fc.Case) and isinstance(body.scrutinee, fc.Var): v = body.scrutinee.index if v in rule.args: return [rule.args.index(v)] return []
def tr_rule(ctx, rule): if isinstance(rule, fc.External): return ic.IExternal(rule.name) return ic.IFuncBody(to_iblock(ctx, rule.args, rule.body, 0)) class Context: ''' The maps, the name of the function under translation, and the compatibility flag. ''' def __init__(self, maps, qname, icurry_compat=True): self.maps = maps self.qname = qname self.icurry_compat = icurry_compat def error(self, msg): return Flat2ICurryError("Function '%s': %s" % (self.qname[1], msg)) def is_failed(e): return isinstance(e, fc.Comb) and e.combtype == fc.FuncCall \ and e.name == fc.prelude('failed') and not e.args def to_iblock(ctx, vs, e, root): ''' A block for the rule arguments ``vs`` and body ``e``. ``root`` is the variable that holds the node whose successors are the arguments. A type annotation at the root of a rule, as in ``f x = (e :: t)``, is a defect of icurry 3.1.0. It does not look through the annotation: a case under it is an error, and the bindings of a let or free declaration under it are lost, so the block refers to variables it never declares. With ``ctx.icurry_compat`` the block is built the same way and the output stays byte-identical to icurry. Without it the annotation is removed first. Annotations in nested positions are not affected: lifting moves the expression under them into a new function. ''' if not ctx.icurry_compat: e = strip_typed(e) evars = fc.all_vars(e) evarset = set(evars) casevar = max([0] + evars) + 1 # fresh variable for a complex case argument decls = [ic.IVarDecl(v) for v in vs if v in evarset] if isinstance(e, fc.Free): decls.extend(ic.IFreeDecl(v) for v in e.vars) elif isinstance(e, fc.Let): decls.extend(ic.IVarDecl(v) for v, _ in e.bindings) elif isinstance(e, fc.Case) and not isinstance(e.scrutinee, fc.Var): decls.append(ic.IVarDecl(casevar)) assigns = [ ic.IVarAssign(v, ic.IVarAccess(root, [p])) for p, v in enumerate(vs) if v in evarset ] if isinstance(e, fc.Let): bindings = [(v, to_iexpr(ctx, b)) for v, b in e.bindings] assigns.extend(ic.IVarAssign(v, be) for v, be in bindings) assigns.extend(recursive_assigns(bindings)) elif isinstance(e, fc.Case) and not isinstance(e.scrutinee, fc.Var): assigns.append(ic.IVarAssign(casevar, to_iexpr(ctx, e.scrutinee))) if isinstance(e, fc.Case) and e.branches: carg = e.scrutinee.index if isinstance(e.scrutinee, fc.Var) else casevar if isinstance(e.branches[0].pattern, fc.Pattern): statement = ic.ICaseCons(carg, [tr_pbranch(ctx, carg, br) for br in e.branches]) else: statement = ic.ICaseLit(carg, [tr_lbranch(ctx, carg, br) for br in e.branches]) elif is_failed(e): statement = ic.IExempt else: statement = ic.IReturn(to_iexpr(ctx, e)) return ic.IBlock(decls, assigns, statement) def recursive_assigns(bindings): ''' The node assignments that close the cycles of a recursive let. For each binding, every variable in its expression that names this binding or a later one is assigned after the fact. ''' out = [] for i, (v, be) in enumerate(bindings): later = set(w for w, _ in bindings[i:]) for w, path in var_pos([], be): if w in later: out.append(ic.INodeAssign(v, path, ic.IVar(w))) return out def tr_pbranch(ctx, carg, br): pat = br.pattern if not isinstance(pat, fc.Pattern): raise ctx.error('trPBranch with LPattern') mn, cn = pat.name ar, pos = ctx.maps.arity_pos_of_cons(pat.name, ctx.qname[1]) return ic.IConsBranch((mn, cn, pos), ar, to_iblock(ctx, pat.vars, br.body, carg)) def tr_lbranch(ctx, carg, br): pat = br.pattern if not isinstance(pat, fc.LPattern): raise ctx.error('trLBranch with Pattern') return ic.ILitBranch(tr_lit(pat.literal), to_iblock(ctx, [], br.body, carg)) def to_iexpr(ctx, e): ''' The ICurry expression of a FlatCurry expression. The walk runs on a stack of its own (:func:`utility.trampoline.trampoline`), so a nested application of any depth needs no recursion (issue #125). ''' return trampoline(_to_iexpr(ctx, e)) def _to_iexpr(ctx, e): if isinstance(e, fc.Var): return ic.IVar(e.index) elif isinstance(e, fc.Lit): return ic.ILit(tr_lit(e.literal)) elif isinstance(e, fc.Comb): if e.name == fc.prelude('?') and len(e.args) == 2: lhs = yield _to_iexpr(ctx, e.args[0]) rhs = yield _to_iexpr(ctx, e.args[1]) return ic.IOr(lhs, rhs) mn, fn = e.name args = [] for arg in e.args: arg = yield _to_iexpr(ctx, arg) args.append(arg) ct = e.combtype maps, cur = ctx.maps, ctx.qname[1] if ct == fc.FuncCall: return ic.IFCall((mn, fn, maps.pos_of_fun(e.name, cur)), args) elif ct == fc.ConsCall: return ic.ICCall((mn, fn, maps.pos_of_cons(e.name, cur)), args) elif isinstance(ct, fc.FuncPartCall): return ic.IFPCall((mn, fn, maps.pos_of_fun(e.name, cur)), ct.missing, args) else: return ic.ICPCall((mn, fn, maps.pos_of_cons(e.name, cur)), ct.missing, args) elif isinstance(e, fc.Or): lhs = yield _to_iexpr(ctx, e.lhs) rhs = yield _to_iexpr(ctx, e.rhs) return ic.IOr(lhs, rhs) elif isinstance(e, fc.Typed): return (yield _to_iexpr(ctx, e.expr)) elif isinstance(e, fc.Let): return (yield _to_iexpr(ctx, e.body)) elif isinstance(e, fc.Free): return (yield _to_iexpr(ctx, e.body)) elif isinstance(e, fc.Case): raise ctx.error('toIExpr: Case occurred') raise TypeError('not an expression: %r' % (e,)) def tr_lit(lit): if isinstance(lit, fc.Intc): return ic.IInt(lit.value) elif isinstance(lit, fc.Floatc): return ic.IFloat(lit.value) elif isinstance(lit, fc.Charc): return ic.IChar(lit.value) raise TypeError('not a literal: %r' % (lit,))
[docs] def var_pos(rpos, e): ''' The variables of an ICurry expression with their positions, in the order of the expression. The walk keeps its own stack, so a nested application of any depth needs no recursion. ''' out = [] stack = [(list(rpos), e)] while stack: rpos, e = stack.pop() if isinstance(e, ic.IVar): out.append((e.var, rpos)) elif isinstance(e, (ic.IVarAccess, ic.ILit)): pass elif isinstance(e, (ic.IFCall, ic.ICCall, ic.IFPCall, ic.ICPCall)): for i in reversed(range(len(e.args))): stack.append((rpos + [i], e.args[i])) elif isinstance(e, ic.IOr): stack.append((rpos + [1], e.rhs)) stack.append((rpos + [0], e.lhs)) else: raise TypeError('not an ICurry expression: %r' % (e,)) return out