# -*- coding: utf-8 -*-
# Copyright © 2017 Apple Inc. All rights reserved.
#
# Use of this source code is governed by a BSD-3-clause license that can
# be found in the LICENSE.txt file or at https://opensource.org/licenses/BSD-3-Clause
'''
Created on Jul 14, 2011

@author: sean
'''
from __future__ import print_function as _
from __future__ import division as _
from __future__ import absolute_import as _
import opcode
import _ast
from ..bytecodetools.instruction import Instruction
from ..asttools.visitors.print_visitor import print_ast
from ..utils import py3op, py2op, py3
AND_JUMPS = ['JUMP_IF_FALSE_OR_POP', 'POP_JUMP_IF_FALSE']
OR_JUMPS = ['JUMP_IF_TRUE_OR_POP', 'POP_JUMP_IF_TRUE']
JUMPS = AND_JUMPS + OR_JUMPS
JUMP_OPS = [opcode.opmap[name] for name in JUMPS]


def split(block, name):
    func = lambda instr: instr.opname == name
    return split_cond(block, func)

def split_cond(block, func, raise_=True):
    block = block[:]

    new_block = []
    while len(block):
        instr = block.pop(0)
        if func(instr):
            return new_block, instr, block
        new_block.append(instr)

    if raise_:
        raise Exception("function found no matching instruction")

    return new_block, None, block

def find_index(lst, func, default=None):
    for i, item in enumerate(lst):
        if func(item):
            return i
    else:
        return default

def rfind_index(lst, func, default=None):
    for i, item in reversed(list(enumerate(lst))):
        if func(item):
            return i
    else:
        return default

def refactor_ifs(stmnt, ifs):
    '''
    for if statements in list comprehension
    '''
    if isinstance(stmnt, _ast.BoolOp):
        test, right = stmnt.values
        if isinstance(stmnt.op, _ast.Or):
            test = _ast.UnaryOp(op=_ast.Not(), operand=test, lineno=0, col_offset=0)

        ifs.append(test)

        return refactor_ifs(right, ifs)
    return stmnt

def parse_logic(struct):

    lineno = struct.lineno

    kw = dict(lineno=lineno, col_offset=0)
    if isinstance(struct.right, LogicalOp):
        ast_right, insert_into = parse_logic(struct.right)
        assert insert_into is None
    else:
        ast_right = struct.right

    parent = struct.parent

    Logic = _ast.Or if struct.flag == 'OR' else _ast.And

    if isinstance(parent, LogicalOp):
        ast_parent, insert_into = parse_logic(struct.parent)

        new_insert_into = [ast_right]
        insert_into.insert(0, _ast.BoolOp(op=Logic(), values=new_insert_into, **kw))
        return ast_parent, new_insert_into

    elif parent is None:
        insert_into = [ast_right]
        return _ast.BoolOp(op=Logic(), values=insert_into, **kw), insert_into

    else:
        bool_op = _ast.BoolOp(op=Logic(), values=[parent, ast_right], **kw)
        return bool_op, None


class ListCompTmp(object):
    def __init__(self, assign, list, ifs, lineno):
        self.assign = assign.nodes[0]
        self.list = list
        self.ifs = ifs
        self.lineno = lineno

class LogicalOp(object):
    def __init__(self, flag, right, parent, lineno):
        self.flag = flag
        self.right = right
        self.parent = parent
        self.lineno = lineno

    def __repr__(self):
        return '%s(%r, parent=%r)' % (self.flag, self.right, self.parent)


class CtrlFlowInstructions(object):

    def split_handlers(self, handlers_blocks):
        handlers = []
        except_instrs = []

        ends = []
        while len(handlers_blocks):

            instr = handlers_blocks.pop(0)
            except_instrs.append(instr)

            if (instr.opname == 'COMPARE_OP') and (instr.arg == 'exception match'):

                jump = handlers_blocks.pop(0)
                assert jump.opname == 'POP_JUMP_IF_FALSE'

                next_handler = jump.oparg

                instr = handlers_blocks.pop(0)
                except_instrs.append(instr)
                instr = handlers_blocks.pop(0)
                except_instrs.append(instr)
                instr = handlers_blocks.pop(0)
                except_instrs.append(instr)

                assert except_instrs[0].opname == 'DUP_TOP'
                assert except_instrs[-3].opname == 'POP_TOP'
                assert except_instrs[-1].opname == 'POP_TOP'

                exec_stmnt = self.decompile_block(except_instrs[1:-4]).stmnt()

                assert len(exec_stmnt) == 1

                exc_type = exec_stmnt[0]


                if except_instrs[-2].opname == 'STORE_NAME':
                    exc_name = _ast.Name(id=except_instrs[-2].arg, ctx=_ast.Store(), lineno=except_instrs[-2].lineno, col_offset=0)
                else:
                    assert except_instrs[-2].opname == 'POP_TOP'
                    exc_name = None

                handler_body = []
                while len(handlers_blocks):
                    instr = handlers_blocks.pop(0)
                    if instr.i == next_handler:
                        handlers_blocks.insert(0, instr)
                        break

                    handler_body.append(instr)

                assert handler_body[-1].opname == 'JUMP_FORWARD'
                ends.append(handler_body[-1].arg)

                exc_body = self.decompile_block(handler_body[:-1]).stmnt()
                if not exc_body:
                    exc_body.append(_ast.Pass(lineno=except_instrs[-2].lineno, col_offset=0))
                #is this for python 3?
                if py3 and exc_name is not None:
                    exc_name = exc_name.id
                    
                handlers.append(_ast.ExceptHandler(type=exc_type, name=exc_name, body=exc_body, lineno=instr.lineno, col_offset=0))

                except_instrs = []

        assert except_instrs[-1].opname == 'END_FINALLY'

        if len(except_instrs) == 1:
            pass
        else:

            assert except_instrs[0].opname == 'POP_TOP'
            assert except_instrs[1].opname == 'POP_TOP'
            assert except_instrs[2].opname == 'POP_TOP'
            assert except_instrs[-2].opname in ['JUMP_FORWARD', 'JUMP_ABSOLUTE'], except_instrs[-2]
            ends.append(except_instrs[-2].arg)
            exc_body = self.decompile_block(except_instrs[3:-2]).stmnt()
            if not exc_body:
                exc_body.append(_ast.Pass(lineno=except_instrs[-2].lineno, col_offset=0))

            handlers.append(_ast.ExceptHandler(type=None, name=None, body=exc_body, lineno=except_instrs[0].lineno, col_offset=0))

            assert all(e == ends[0] for e in ends)

        end = ends[0]

        return end, handlers
    

#    @py3op
    def do_try_except_block(self, block):
        
        while 1:
            instr = block.pop(-1)
            if instr.opname == 'POP_BLOCK':
                break

        try_except = self.decompile_block(block).stmnt()
        
        finally_block = []
        while 1:
            next_instr = self.ilst.pop(0)
            if next_instr.opname == 'END_FINALLY':
                break
            finally_block.append(next_instr)

        finally_ = self.decompile_block(finally_block).stmnt()

        try_finally = _ast.TryFinally(body=try_except, finalbody=finally_)
        
        self.ast_stack.append(try_finally)

#    @py3op
    def do_except_block(self, block):
        
        handler_block = []
        for instr in block:
            if  instr.opname == 'POP_BLOCK':
                break
            handler_block.append(instr)
            
        while 1:
            instr = self.ilst.pop(0)
            if instr.opname == 'END_FINALLY':
                break 

        body = self.decompile_block(handler_block).stmnt()
        
        self.ast_stack.extend(body)
        
#    @py2op
#    def SETUP_FINALLY(self, instr):
#        pass
#
#    @SETUP_FINALLY.py3op
    def SETUP_FINALLY(self, instr):
        to = instr.arg
        try_except_block = self.make_block(to, inclusive=False)
        
        if try_except_block[0].opname == 'SETUP_EXCEPT':
            self.do_try_except_block(try_except_block)
        else:
            self.do_except_block(try_except_block)
            
#            raise Exception()
#        print("try_except_block", try_except_block)
#        
#        finally_block = []
#        while 1:
#            next_instr = self.ilst.pop(0)
#            if next_instr.opname == 'END_FINALLY':
#                break
#            finally_block.append(next_instr)
#
#        print("finally_block", finally_block)
#        
#        finally_ = self.decompile_block(finally_block).stmnt()
#        
#        print("finally_", finally_)
#        print_ast(finally_[0])
#        print()
##        print_ast(finally_[1])
##        print("\n\n")
#        
#        try_except = self.decompile_block(try_except_block).stmnt()
        
    @py2op
    def SETUP_EXCEPT(self, instr):

        to = instr.arg

        try_block = self.make_block(to, inclusive=False)

        assert try_block[-1].opname in ['JUMP_FORWARD', 'JUMP_ABSOLUTE'], try_block[-1] 
        assert try_block[-2].opname == 'POP_BLOCK', try_block[-2]

        try_stmnts = self.decompile_block(try_block[:-2]).stmnt()
        body = try_stmnts

        handlers_blocks = self.make_block(try_block[-1].arg, inclusive=False, raise_=False)

        end, handlers = self.split_handlers(handlers_blocks)
        
        #raise exception in python 3 (python 2 ilst does not include end so else may go beond)
        else_block = self.make_block(end, inclusive=False, raise_=py3)

        else_stmnts = self.decompile_block(else_block).stmnt()
        if else_stmnts:
            else_ = else_stmnts
        else:
            else_ = []

        try_except = _ast.TryExcept(body=body, handlers=handlers, orelse=else_, lineno=instr.lineno, col_offset=0)

        self.ast_stack.append(try_except)

    @SETUP_EXCEPT.py3op
    def SETUP_EXCEPT(self, instr):

        to = instr.arg

        try_block = self.make_block(to, inclusive=False)

        assert try_block[-1].opname in ['JUMP_FORWARD', 'JUMP_ABSOLUTE']
        assert try_block[-2].opname == 'POP_BLOCK'

        try_stmnts = self.decompile_block(try_block[:-2]).stmnt()
        body = try_stmnts

        handlers_blocks = self.make_block(try_block[-1].arg, inclusive=False, raise_=False)

        end, handlers = self.split_handlers(handlers_blocks)

        else_block = self.make_block(end, inclusive=False, raise_=False)
        
        else_stmnts = self.decompile_block(else_block).stmnt()
        
        if else_stmnts:
            else_ = else_stmnts
        else:
            else_ = []

        try_except = _ast.TryExcept(body=body, handlers=handlers, orelse=else_, lineno=instr.lineno, col_offset=0)

        self.ast_stack.append(try_except)
    
    @py3op
    def POP_EXCEPT(self, instr):
        pass
    
    def SETUP_LOOP(self, instr):
        to = instr.arg
        loop_block = self.make_block(to, inclusive=False, raise_=False)

        if 'FOR_ITER' in [ins.opname for ins in loop_block]:
            self.for_loop(loop_block)
        else:
            self.while_loop(instr, loop_block)

    def BREAK_LOOP(self, instr):
        self.ast_stack.append(_ast.Break(lineno=instr.lineno, col_offset=0))

    def for_loop(self, loop_block):
        iter_block, _, body_else_block = split(loop_block, 'GET_ITER')

#        for_iter = body_else_block[0]
        for_iter = body_else_block.pop(0)

        assert for_iter.opname == 'FOR_ITER'

        idx = find_index(body_else_block, lambda instr: instr.opname == 'POP_BLOCK' and for_iter.to == instr.i)

        assert idx is not False

        body_block = body_else_block[:idx]

        else_block = body_else_block[idx + 1:]

        jump_abs = body_block.pop()

        assert jump_abs.opname == 'JUMP_ABSOLUTE' and jump_abs.to == for_iter.i

        iter_stmnt = self.decompile_block(iter_block).stmnt()

        assert len(iter_stmnt) == 1
        iter_stmnt = iter_stmnt[0]
        body_lst = self.decompile_block(body_block[:], stack_items=[None], jump_map={for_iter.i:for_iter.to}).stmnt()

        assign_ = body_lst.pop(0)
        body = body_lst

        if len(else_block) == 0:
            else_ = []
        else:
            else_ = self.decompile_block(else_block[:]).stmnt()

        assign = assign_.targets[0]
        for_ = _ast.For(target=assign, iter=iter_stmnt, body=body, orelse=else_, lineno=iter_stmnt.lineno, col_offset=0)

        self.ast_stack.append(for_)

    def make_list_comp(self, get_iter, for_iter):

        block = self.make_block(for_iter.to, inclusive=False, raise_=False)

        jump_abs = block.pop()

        assert jump_abs.opname == 'JUMP_ABSOLUTE', jump_abs.opname
        jump_map = {for_iter.i:for_iter.to}

        stmnts = self.decompile_block(block, stack_items=[None], jump_map=jump_map).stmnt()

        if len(stmnts) > 1:

            assign = stmnts.pop(0)
    
            assert len(stmnts) == 1
    
            assert isinstance(assign, _ast.Assign)
    
            list_expr = self.ast_stack.pop()
    
            # empty ast.List object
            list_ = self.ast_stack.pop()
    
            ifs = []
            elt = refactor_ifs(stmnts[0], ifs)
    
            assert len(assign.targets) == 1
            generators = [_ast.comprehension(target=assign.targets[0], iter=list_expr, ifs=ifs, lineno=get_iter.lineno, col_offset=0)]
    
            if isinstance(list_, _ast.Assign):
                
                comp = _ast.comprehension(target=list_.targets[0], iter=None, ifs=ifs, lineno=get_iter.lineno, col_offset=0)
                generators.insert(0, comp)
            
            list_comp = _ast.ListComp(elt=elt, generators=generators, lineno=get_iter.lineno, col_offset=0)
        else:
            list_expr = self.ast_stack.pop()
            list_comp = stmnts[0]
            
            generators = list_comp.generators
            
            # empty ast.List object
            list_ = self.ast_stack.pop()
            if not isinstance(list_, _ast.Assign):
                comp = _ast.comprehension(target=list_.targets[0], iter=None, ifs=[], lineno=get_iter.lineno, col_offset=0)
                generators.insert(0, comp)
                
            generators[0].iter = list_expr

        self.ast_stack.append(list_comp)

    def extract_listcomp(self, function, sequence):

        assert len(function.body) == 1
        assert isinstance(function.body[0], _ast.Return)

        value = function.body[0].value

        assert isinstance(value, _ast.ListComp)
        
        generators = list(value.generators)
        for generator in generators:
            if generator.iter.id == '.0':
                generator.iter = sequence
        
        setcomp = _ast.ListComp(elt=value.elt, generators=generators, lineno=value.lineno, col_offset=0)
        self.ast_stack.append(setcomp)
        
    def extract_setcomp(self, function, sequence):
        
        assert len(function.body) == 1
        assert isinstance(function.body[0], _ast.Return)

        value = function.body[0].value

        assert isinstance(value, _ast.ListComp)
        
        generators = list(value.generators)
        for generator in generators:
            if generator.iter.id == '.0':
                generator.iter = sequence
        
        setcomp = _ast.SetComp(elt=value.elt, generators=generators, lineno=value.lineno, col_offset=0)
        self.ast_stack.append(setcomp)

    def extract_dictcomp(self, function, sequence):

        assert len(function.body) == 1
        assert isinstance(function.body[0], _ast.Return)

        value = function.body[0].value

        assert isinstance(value, _ast.ListComp)
        
        generators = list(value.generators)
        for generator in generators:
            if generator.iter.id == '.0':
                generator.iter = sequence
        
        setcomp = _ast.DictComp(key=value.elt[0], value=value.elt[1], generators=generators, lineno=value.lineno, col_offset=0)
        self.ast_stack.append(setcomp)
#
#        assert len(function.code.nodes) == 1
#        assert isinstance(function.code.nodes[0], _ast.Return)
#
#        value = function.code.nodes[0].value
#
#        assert isinstance(value, _ast.ListComp)
#
#        quals = value.quals
#        key, value = value.expr
#
#        for qual in quals:
#            qual.list = sequence
#
#        setcomp = _ast.DictComp(key=key, value=value, generators=quals, lineno=value.lineno, col_offset=0)
#        self.ast_stack.append(setcomp)

    def GET_ITER(self, instr):
        for_iter = self.ilst.pop(0)

        if for_iter.opname == 'CALL_FUNCTION':
            call_function = for_iter
            assert call_function.oparg == 1

            sequence = self.ast_stack.pop()
            function = self.ast_stack.pop()

            if function.name == '<listcomp>':
                self.extract_listcomp(function, sequence)
            elif function.name == '<setcomp>':
                self.extract_setcomp(function, sequence)
            elif function.name == '<dictcomp>':
                self.extract_dictcomp(function, sequence)
            else:
                assert False, function.name

        elif for_iter.opname == 'FOR_ITER':
            self.make_list_comp(instr, for_iter)
        else:
            assert False


    def LIST_APPEND(self, instr):
#        assert instr.oparg == 2
        pass

    def MAP_ADD(self, instr):
        key = self.ast_stack.pop()
        value = self.ast_stack.pop()

        self.ast_stack.append((key, value))
        'NOP'

    def SET_ADD(self, instr):
        'NOP'

    def FOR_ITER(self, instr):
        #set or dict comp
        self.make_list_comp(instr, instr)

    def while_loop(self, instr, loop_block):

        kw = dict(lineno=instr.lineno, col_offset=0)

        loop_block_map = {instr.i:instr.op for instr in loop_block}

        first_i = loop_block[0].i
        
        func = lambda instr: instr.opname == 'JUMP_ABSOLUTE' and instr.oparg == first_i
        body_index = rfind_index(loop_block[:-1], func)
         
        if body_index is None:
            const_while = True
            body_index = len(loop_block) - 1
        else:
            if body_index + 1 < len(loop_block): 
                pop_block = loop_block[body_index + 1]
                const_while = pop_block.opname != 'POP_BLOCK'
                const_else = True
            else:
                const_while = True
                const_else = False
           
        if const_while:
            test = _ast.Num(1, **kw)
            body_ = self.decompile_block(loop_block[:body_index]).stmnt()

            else_block = loop_block[body_index + 1:]
            if else_block:
                else_ = self.decompile_block(else_block).stmnt()
            else:
                else_ = []
        else:
            pop_block = loop_block[body_index + 1]
            
            func = lambda instr: instr.opname in ['POP_JUMP_IF_FALSE', 'POP_JUMP_IF_TRUE'] and instr.oparg == pop_block.i
            idx = rfind_index(loop_block[:body_index], func)
            cond_block = loop_block[:idx]

            iter_stmnt = self.decompile_block(cond_block).stmnt()
            assert len(iter_stmnt) == 1
            test = iter_stmnt[0]
            
            body_ = self.decompile_block(loop_block[idx + 1:body_index]).stmnt()

            else_block = loop_block[body_index + 2:]
            if else_block:
                else_ = self.decompile_block(else_block[:]).stmnt()
            else:
                else_ = []

        while_ = _ast.While(test=test, body=body_, orelse=else_, **kw)

        self.ast_stack.append(while_)


    def gather_jumps(self, jump_instr):
        
        
        to = self.jump_map.get(jump_instr.to, jump_instr.to)
        assert to > jump_instr.i

        and_block = self.make_block(to=to, inclusive=False, raise_=False)

        jump_tos = {to}
        last_len = 0
        old_max = to
        
        while len(jump_tos) != last_len:
            last_len = len(jump_tos)

            for instr in and_block:
                if instr.opname in JUMPS:
                    to = self.jump_map.get(instr.to, instr.to)
                    assert to > jump_instr.i
                    jump_tos.add(to)

            if old_max < max(jump_tos):
                old_max = max(jump_tos)
                new_block = self.make_block(to=old_max, inclusive=False, raise_=False)
                and_block.extend(new_block)

        #print("and_block", and_block)
        return and_block

    def process_logic(self, logic_block):

        if logic_block[0].opname in JUMPS:
            jump_instr = logic_block[0]
            flag = 'OR' if jump_instr.opname in OR_JUMPS else 'AND'
            idx = find_index(logic_block, lambda instr: jump_instr.oparg == instr.i, default=None)

            if idx is None:
                if len(logic_block) == 1:
                    right = None
                else:
                    right = self.process_logic(logic_block[1:])
                parent = None
#                assert False
            else:
                right = self.process_logic(logic_block[1:idx - 1])
                parent = self.process_logic(logic_block[idx - 1:])

#            if right is None:
            return LogicalOp(flag, right, parent, jump_instr.lineno)
        else:
            idx = find_index(logic_block, lambda instr: instr.opname in JUMPS, default=None)

            if idx is None:
                stmnts = self.decompile_block(logic_block).stmnt()
#                assert len(stmnts) == 1
                return stmnts[0]
            else:
                right = logic_block[idx:]
                parent = logic_block[:idx]

                stmnts = self.decompile_block(parent).stmnt()
                assert len(stmnts) == 1
                parent = stmnts[0]

                right = self.process_logic(right)

                assert right.parent is None

                if right.right is None:
                    return parent

                right.parent = parent
                return right


    def logic_ast(self, instr, left, hi):
#        flag = 'OR' if opname[instr.op] in OR_JUMPS else 'AND'

        ast_, insert_into = parse_logic(hi)

        insert_into.insert(0, left)

        return ast_

    def JUMP_IF_TRUE_OR_POP(self, instr):
        left = self.ast_stack.pop()

        and_block = self.gather_jumps(instr)
        hi = self.process_logic([instr] + and_block)
        ast_ = self.logic_ast(instr, left, hi)
        self.ast_stack.append(ast_)

    def make_if(self, instr, left, and_block):
        block = [instr] + and_block[:-1]

        maxmax = max(block, key=lambda ins: (0, 0) if (ins.op not in JUMP_OPS) else (self.jump_map.get(ins.oparg, ins.oparg), ins.i))

        idx = block.index(maxmax)

        assert idx is not None

        hi = self.process_logic(block[:idx + 1])
        
        if hi.right is None and hi.parent is None:
            if instr.opname == 'POP_JUMP_IF_TRUE':
                cond = _ast.UnaryOp(op=_ast.Not(), operand=left, lineno=0, col_offset=0)
            else:
                cond = left

        else:
            cond = self.logic_ast(instr, left, hi)
            
        jump = and_block[-1]
        
        if jump.opname == 'RETURN_VALUE':
            body_block = block[idx + 1:] + [jump]
        else:
            body_block = block[idx + 1:]
            
        body = self.decompile_block(body_block).stmnt()
        
        if jump.is_jump:
            else_block = self.make_block(jump.to, inclusive=False, raise_=False)
        else: # it is a return
            else_block = []

        if len(else_block):
            else_ = self.decompile_block(else_block).stmnt()
#
#            if len(else_lst) == 1 and isinstance(else_lst[0], _ast.If):
#                elif_ = else_lst[0]
#                tests.extend(elif_.tests)
#                else_ = elif_.else_
#            else:
#                else_ = else_lst
        else:
            else_ = []

        if_ = _ast.If(test=cond, body=body, orelse=else_, lineno=instr.lineno, col_offset=0)
        
        self.ast_stack.append(if_)


    def POP_JUMP_IF_TRUE(self, instr):
        
        left = self.ast_stack.pop()

        and_block = self.gather_jumps(instr)

        if and_block[-1].opname in ['JUMP_FORWARD', 'JUMP_ABSOLUTE', 'RETURN_VALUE']:

            self.make_if(instr, left, and_block)
            return
        else:
            hi = self.process_logic([instr] + and_block)
            ast_ = self.logic_ast(instr, left, hi)
            self.ast_stack.append(ast_)

    def POP_JUMP_IF_FALSE(self, instr):
        
        #print("POP_JUMP_IF_FALSE")
        
        left = self.ast_stack.pop()

        and_block = self.gather_jumps(instr)
        #This is an IF statement
        
        if and_block[-1].opname in ['JUMP_FORWARD', 'JUMP_ABSOLUTE', 'RETURN_VALUE']:
            
            #this happens if the function was going to return anyway
            if and_block[-1].opname == 'RETURN_VALUE':
                JUMP_FORWARD = Instruction(and_block[-1].i, 110, lineno=0)
                JUMP_FORWARD.arg = instr.to
                and_block.append(JUMP_FORWARD)
            
            #print()
            #print("make_if", instr, left, and_block)
            #print()
            self.make_if(instr, left, and_block)
            return
        else: #This is an expression
            hi = self.process_logic([instr] + and_block)
            ast_ = self.logic_ast(instr, left, hi)
            self.ast_stack.append(ast_)

    def JUMP_IF_FALSE_OR_POP(self, instr):
        left = self.ast_stack.pop()

        and_block = self.gather_jumps(instr)
        hi = self.process_logic([instr] + and_block)
        ast_ = self.logic_ast(instr, left, hi)
        self.ast_stack.append(ast_)

    def JUMP_ABSOLUTE(self, instr):
        continue_ = _ast.Continue(lineno=instr.lineno, col_offset=0)
        self.ast_stack.append(continue_)

    def JUMP_FORWARD(self, instr):
        pass

    def SETUP_WITH(self, instr):

        with_block = self.make_block(to=instr.to, inclusive=False)

        assert with_block.pop().opname == 'LOAD_CONST'
        assert with_block.pop().opname == 'POP_BLOCK'

        with_cleanup = self.ilst.pop(0)
        assert with_cleanup.opname == 'WITH_CLEANUP'
        end_finally = self.ilst.pop(0)
        assert end_finally.opname == 'END_FINALLY'


        with_ = self.decompile_block(with_block, stack_items=['WITH_BLOCK']).stmnt()

        if isinstance(with_[0], _ast.Assign) and with_[0].value == 'WITH_BLOCK':
            assign = with_.pop(0)
            as_ = assign.targets[0]
        else:
            as_ = None

        body = with_

        expr = self.ast_stack.pop()

        with_ = _ast.With(context_expr=expr, optional_vars=as_, body=body,
                          lineno=instr.lineno, col_offset=0)

        self.ast_stack.append(with_)
        
    def CONTINUE_LOOP(self, instr):
        cont = _ast.Continue(lineno=instr.lineno, col_offset=0)
        self.ast_stack.append(cont)
         
