def destructure(instrs):
    final_instrs = []
    for instr in instrs:
        if isinstance(instr, If86):
            then = destructure(instr.then)
            else_ = destructure(instr.else_)
            then_label = new_temp('then_branch')
            end_label = new_temp('end_if')
            final_instrs += [JumpIf86(then_label)] + else_ + [Jump86(end_label), Label86(then_label)] + then + [Label86(end_label)]
        else: final_instrs.append(instr)
    return final_instrs
def make_or(nodes, last=BOOL_ERR):
    if nodes:
        node = nodes[0]
        ntemp = Name(new_temp('or'))
        return Let(ntemp, node, IfExp(ntemp,
                                      Compare(ntemp, [('==', Const(1))]),
                                      make_or(nodes[1:], ntemp)))
    else:
        return last
 def gen_and(nodes, last):
     if nodes:
         node = explicate(nodes[0])
         ntemp = Name(new_temp('and'))
         return Let(ntemp, node, IfExp(ntemp,
                                       is_false(ntemp),
                                       gen_and(nodes[1:], ntemp)))
     else:
         return last
 def gen_or(nodes, last):
     if nodes:
         node = explicate(nodes[0])
         ntemp = Name(new_temp('or'))
         return Let(ntemp, node, IfExp(ntemp,
                                       is_true(ntemp),
                                       gen_or(nodes[1:], ntemp)))
     else:
         return last
def explicate(ast):
    if isinstance(ast, Module):
        return Module(ast.doc, explicate(ast.node))
    elif isinstance(ast, Stmt):
        nodes = map(explicate, ast.nodes)
        return Stmt(nodes)
    elif isinstance(ast, Printnl):
        return Printnl(map(explicate, ast.nodes), ast.dest)
    elif isinstance(ast, Assign):
        return Assign(map(explicate, ast.nodes), explicate(ast.expr))
    elif isinstance(ast, Discard):
        return Discard(explicate(ast.expr))
    elif isinstance(ast, Add):
        left = explicate(ast.left)
        right = explicate(ast.right)
        ltemp = Name(new_temp('left'))
        rtemp = Name(new_temp('right'))
        return Let(ltemp,
                   left,
                   Let(rtemp,
                       right,
                       IfExp(InjectFrom(INT_TAG, Add((ProjectTo(INT_TAG, ltemp), ProjectTo(INT_TAG, rtemp)))),
                             make_and([make_or([Compare(GetTag(ltemp),
                                              INT_TAG_COMP),
                                      Compare(GetTag(ltemp),
                                              BOOL_TAG_COMP)]),
                                  make_or([Compare(GetTag(rtemp),
                                              INT_TAG_COMP),
                                      Compare(GetTag(rtemp),
                                              BOOL_TAG_COMP)])]),
                             IfExp(InjectFrom(BIG_TAG, CallFunc(Name('add'),
                                                                [ProjectTo(BIG_TAG, ltemp),
                                                                 ProjectTo(BIG_TAG, rtemp)])),
                                   make_and([Compare(GetTag(ltemp),
                                                BIG_TAG_COMP),
                                        Compare(GetTag(rtemp),
                                                BIG_TAG_COMP)]),
                                   DYN_ERR))))
    elif isinstance(ast, UnarySub):
        expr = explicate(ast.expr)
        temp = Name(new_temp('usub'))
        return Let(temp,
                   expr,
                   IfExp(InjectFrom(INT_TAG, UnarySub(ProjectTo(INT_TAG, temp))),
                         make_or([Compare(GetTag(temp), INT_TAG_COMP),
                             Compare(GetTag(temp), BOOL_TAG_COMP)]),
                         DYN_ERR))
    elif isinstance(ast, Compare):
        expr = explicate(ast.expr)
        op, comp = ast.ops[0]
        comp = explicate(comp)
        etemp = Name(new_temp('expr'))
        ctemp = Name(new_temp('comp'))
        func = Name('equal') if op == '==' else Name('not_equal')
        return Let(etemp,
                   expr,
                   Let(ctemp,
                       comp,
                       IfExp(InjectFrom(BOOL_TAG, 
                                        Compare(ProjectTo(INT_TAG, etemp), 
                                                [(op, ProjectTo(INT_TAG, ctemp))])),
                             make_and([make_or([Compare(GetTag(etemp),
                                              INT_TAG_COMP),
                                      Compare(GetTag(etemp),
                                              BOOL_TAG_COMP)]),
                                  make_or([Compare(GetTag(ctemp),
                                              INT_TAG_COMP),
                                      Compare(GetTag(ctemp),
                                              BOOL_TAG_COMP)])]),
                             IfExp(InjectFrom(BOOL_TAG, CallFunc(Name('equal'),
                                                                 [ProjectTo(BIG_TAG, ltemp),
                                                                  ProjectTo(BIG_TAG, rtemp)])),
                                   make_and([Compare(GetTag(ltemp),
                                                BIG_TAG_COMP),
                                        Compare(GetTag(rtemp),
                                                BIG_TAG_COMP)]),
                                   DYN_ERR))))
    elif isinstance(ast, Or): #(nodes)
        def gen_or(nodes, last):
            if nodes:
                node = explicate(nodes[0])
                ntemp = Name(new_temp('or'))
                return Let(ntemp, node, IfExp(ntemp,
                                              is_true(ntemp),
                                              gen_or(nodes[1:], ntemp)))
            else:
                return last
        return gen_or(ast.nodes, DYN_ERR)
    elif isinstance(ast, And):#(nodes)
        def gen_and(nodes, last):
            if nodes:
                node = explicate(nodes[0])
                ntemp = Name(new_temp('and'))
                return Let(ntemp, node, IfExp(ntemp,
                                              is_false(ntemp),
                                              gen_and(nodes[1:], ntemp)))
            else:
                return last
        return gen_and(ast.nodes, DYN_ERR)
    elif isinstance(ast, Not):#(expr)
        expr = explicate(expr)
        etemp = Name(new_temp('not'))
        return Let(etemp, expr, IfExp(InjectFrom(BOOL_TAG, Compare(ProjectTo(BOOL_TAG, etemp), 
                                                                   [('==', Const(0))])),
                                      make_or(Compare(GetTag(etemp), BOOL_TAG_COMP),
                                         Compare(GetTag(etemp), INT_TAG_COMP)),
                                      InjectFrom(BOOL_TAG, Compare(CallFunc(Name('is_true'),[etemp]),
                                                                   [('==', Const(0))]))))
    elif isinstance(ast, List):#(nodes)
        return List(map(explicate, ast.nodes))
    elif isinstance(ast, Dict):#items
        return Dict(map(lambda p: (p[0], explicate(p[1])), ast.items))
    elif isinstance(ast, Subscript):#expr flags subs
        return Subscript(explicate(ast.expr), ast.flags, map(explicate, ast.subs))
    elif isinstance(ast, IfExp):#test then else_
        return IfExp(explicate(ast.then),
                     ProjectTo(BOOL_TAG, explicate(ast.test)),
                     explicate(ast.else_))
    elif isinstance(ast, Const):
        return InjectFrom(INT_TAG, ast)
    else: return ast
def flatten(ast, extra_flat=False):
    if isinstance(ast, Module):
        return Module(ast.doc, flatten(ast.node))
    elif isinstance(ast, Stmt):
        nodes = map(flatten, ast.nodes)
        nodes = sum(nodes, [])
        return Stmt(nodes)
    elif isinstance(ast, Printnl):
        nodes = map(flatten, ast.nodes)
        prints = []
        for (t,l) in nodes:
            if not is_leaf(t):
                temp = new_temp('print')
                l.append(Assign([AssName(temp, 'OP_ASSIGN')], t))
                prints.append(Name(temp))
            else:
                prints.append(t)
        stmts = sum([l for (t, l) in nodes], [])
        return stmts + [Printnl(prints, ast.dest)]
    elif isinstance(ast, Assign):
        nodes = map(flatten, ast.nodes)
        assigns = [t for (t, l) in nodes]
        stmts = sum([l for (t, l) in nodes], [])
        targ_node, targ_stmts = flatten(ast.expr, len(filter(lambda x: isinstance(x, Subscript), assigns)))
        return stmts + targ_stmts + [Assign(assigns, targ_node)]
    elif isinstance(ast, AssName):
        return (ast, [])
    elif isinstance(ast, Discard):
        expr, stmts = flatten(ast.expr)
        return stmts + [Discard(expr)]
    elif isinstance(ast, Const):
        return (ast, [])
    elif isinstance(ast, Name):
        return (ast, [])
    elif isinstance(ast, Add):
        lexpr, lstmts = flatten(ast.left)
        rexpr, rstmts = flatten(ast.right)
        if not is_leaf(lexpr) or extra_flat:
            temp = new_temp("left")
            lstmts.append(Assign([AssName(temp, 'OP_ASSIGN')], lexpr))
            lexpr = Name(temp)
        if not is_leaf(rexpr) or extra_flat:
            temp = new_temp("right")
            rstmts.append(Assign([AssName(temp, 'OP_ASSIGN')], rexpr))
            rexpr = Name(temp)
        return (Add((lexpr, rexpr)), lstmts + rstmts)
    elif isinstance(ast, Compare):
        lexpr, lstmts = flatten(ast.expr)
        rexpr, rstmts = flatten(ast.ops[0][1])
        if not is_leaf(lexpr) or extra_flat:
            temp = new_temp("left")
            lstmts.append(Assign([AssName(temp, 'OP_ASSIGN')], lexpr))
            lexpr = Name(temp)
        if not is_leaf(rexpr) or extra_flat:
            temp = new_temp("right")
            rstmts.append(Assign([AssName(temp, 'OP_ASSIGN')], rexpr))
            rexpr = Name(temp)
        return (Compare(lexpr, [(ast.ops[0][0], rexpr)]), lstmts + rstmts)
    elif isinstance(ast, Subscript):
        lexpr, lstmts = flatten(ast.expr)
        rexpr, rstmts = flatten(ast.subs[0])
        if not is_leaf(lexpr) or extra_flat:
            temp = new_temp("expr")
            lstmts.append(Assign([AssName(temp, 'OP_ASSIGN')], lexpr))
            lexpr = Name(temp)
        if not is_leaf(rexpr) or extra_flat:
            temp = new_temp("sub")
            rstmts.append(Assign([AssName(temp, 'OP_ASSIGN')], rexpr))
            rexpr = Name(temp)
        return (Compare(lexpr, ast.flags, [rexpr]), lstmts + rstmts)
    elif isinstance(ast, UnarySub):
        expr, stmts = flatten(ast.expr)
        if not is_leaf(expr) or extra_flat:
            temp = new_temp("usub")
            stmts.append(Assign([AssName(temp, 'OP_ASSIGN')], expr))
            expr = Name(temp)
        return (UnarySub(expr), stmts)
    elif isinstance(ast, GetTag):
        expr, stmts = flatten(ast.expr)
        if not isinstance(expr, Name):
            temp = new_temp("gettag")
            stmts.append(Assign([AssName(temp, 'OP_ASSIGN')], expr))
            expr = Name(temp)
        return (GetTag(expr), stmts)
    elif isinstance(ast, InjectFrom):
        expr, stmts = flatten(ast.expr)
        if not isinstance(expr, Name):
            temp = new_temp("inject")
            stmts.append(Assign([AssName(temp, 'OP_ASSIGN')], expr))
            expr = Name(temp)
        return (InjectFrom(ast.type, expr), stmts)
    elif isinstance(ast, ProjectTo):
        expr, stmts = flatten(ast.expr)
        if not isinstance(expr, Name):
            temp = new_temp("project")
            stmts.append(Assign([AssName(temp, 'OP_ASSIGN')], expr))
            expr = Name(temp)
        return (ProjectTo(ast.type, expr), stmts)
    elif isinstance(ast, Let):
        expr, stmts = flatten(ast.rhs)
        rest_expr, rest_stmts = flatten(ast.body)
        return (rest_expr, stmts + [Assign([AssName(ast.name.name, 'OP_ASSIGN')], expr)] + rest_stmts)
    elif isinstance(ast, CallFunc):
        expr, stmts = flatten(ast.node)
        if not is_leaf(expr) or extra_flat:
            temp = new_temp("func")
            stmts.append(Assign([AssName(temp, 'OP_ASSIGN')], expr))
            expr = Name(temp)
        args_exprs = []
        args_stmts = []
        for arg in ast.args:
            arg_expr, arg_stmts = flatten(arg)
            if not is_leaf(arg_expr) or extra_flat:
                temp = new_temp("arg")
                arg_stmts.append(Assign([AssName(temp, 'OP_ASSIGN')], arg_expr))
                arg_expr = Name(temp)
            args_exprs.append(arg_expr)
            args_stmts = args_stmts + arg_stmts
        return (CallFunc(expr, args_exprs), stmts + args_stmts)
    elif isinstance(ast, IfExp):
        ftemp = new_temp('if')
        test, tstmts = flatten(ast.then)
        if not is_leaf(test) or extra_flat:
            temp = new_temp("test")
            tstmts.append(Assign([AssName(temp, 'OP_ASSIGN')], test))
            test = Name(temp)
        trexpr, trstmts = flatten(ast.test)
        fexpr, fstmts = flatten(ast.else_)
        trstmts.append(Assign([AssName(ftemp, 'OP_ASSIGN')], trexpr))
        fstmts.append(Assign([AssName(ftemp, 'OP_ASSIGN')], fexpr))
        return (Name(ftemp), tstmts + [IfStmt(test, Stmt(trstmts), Stmt(fstmts))])
    elif isinstance(ast, List):
        lname = new_temp('list')
        stmts = []
        adds = []
        lenname = new_temp('len')
        list_len = InjectFrom(INT_TAG, Const(len(ast.nodes)))
        for i in xrange(len(ast.nodes)):
            node = ast.nodes[i]
            nexpr, nstmts = flatten(node)
            if not is_leaf(nexpr) or extra_flat:
                temp = new_temp("item")
                nstmts.append(Assign([AssName(temp, 'OP_ASSIGN')], nexpr))
                nexpr = Name(temp)
            temp = new_temp('index' + str(i))
            nstmts.append(Assign([AssName(temp, 'OP_ASSIGN')], InjectFrom(INT_TAG, Const(i))))
            stmts = stmts + nstmts
            adds.append(CallFunc(Name('set_subscript'), [Name(lname), Name(temp), nexpr]))
        return (Name(lname), [Assign([AssName(lenname, 'OP_ASSIGN')], list_len),
                              Assign([AssName(lname, 'OP_ASSIGN')], 
                                     CallFunc(Name('create_list'), [Name(lenname)]))] + nstmts + adds)
    elif isinstance(ast, List):
        lname = new_temp('dict')
        stmts = []
        adds = []
        for name, val in ast.items:
            nexpr, nstmts = flatten(name)
            vexpr, vstmts = flatten(val)
            if not is_leaf(nexpr) or extra_flat:
                temp = new_temp("name")
                nstmts.append(Assign([AssName(temp, 'OP_ASSIGN')], nexpr))
                nexpr = Name(temp)
            if not is_leaf(vexpr) or extra_flat:
                temp = new_temp("val")
                vstmts.append(Assign([AssName(temp, 'OP_ASSIGN')], vexpr))
                vexpr = Name(temp)
            stmts = stmts + nstmts + vstmts
            adds.append(CallFunc(Name('set_subscript'), [Name(lname), nexpr, vexpr]))
        return (Name(lname), [Assign([AssName(lname, 'OP_ASSIGN')], 
                                     CallFunc(Name('create_dict'), []))] + nstmts + adds)
    else: raise Exception('Unexpected term to be flattened ' + repr(ast))