"""Implements the core parts of flow graph creation.
"""

import sys
import collections
import types
import __builtin__

from rpython.tool.error import source_lines
from rpython.rlib import rstackovf
from rpython.flowspace.argument import CallSpec
from rpython.flowspace.model import (Constant, Variable, Block, Link,
    c_last_exception, const, FSException)
from rpython.flowspace.framestate import FrameState
from rpython.flowspace.specialcase import (rpython_print_item,
    rpython_print_newline)
from rpython.flowspace.operation import op
from rpython.flowspace.bytecode import BytecodeCorruption

w_None = const(None)

class FlowingError(Exception):
    """ Signals invalid RPython in the function being analysed"""
    ctx = None

    def __str__(self):
        msg = ["\n"]
        msg += map(str, self.args)
        msg += [""]
        msg += source_lines(self.ctx.graph, None, offset=self.ctx.last_offset)
        return "\n".join(msg)


class StopFlowing(Exception):
    pass


class SpamBlock(Block):
    def __init__(self, framestate):
        Block.__init__(self, framestate.getvariables())
        self.framestate = framestate
        self.dead = False

    def make_recorder(self):
        return BlockRecorder(self)

class EggBlock(Block):
    def __init__(self, inputargs, prevblock, booloutcome):
        Block.__init__(self, inputargs)
        self.prevblock = prevblock
        self.booloutcome = booloutcome

    @property
    def ancestor(self):
        parent = self.prevblock
        while isinstance(parent, EggBlock):
            parent = parent.prevblock
        return parent

    @property
    def dead(self):
        return self.ancestor.dead

    @property
    def framestate(self):
        return self.ancestor.framestate

    def make_recorder(self):
        recorder = BlockRecorder(self)
        curr = self
        while isinstance(curr, EggBlock):
            prev = curr.prevblock
            recorder = Replayer(prev, curr.booloutcome, recorder)
            curr = prev
        return recorder

    def extravars(self, last_exception=None, last_exc_value=None):
        self.last_exception = last_exception

def fixeggblocks(graph):
    for block in graph.iterblocks():
        if isinstance(block, SpamBlock):
            del block.framestate     # memory saver

# ____________________________________________________________

class Recorder(object):
    def append(self, operation):
        raise NotImplementedError

    def guessbool(self, ctx, w_condition):
        raise AssertionError("cannot guessbool(%s)" % (w_condition,))


class BlockRecorder(Recorder):
    # Records all generated operations into a block.

    def __init__(self, block):
        self.crnt_block = block
        # Final frame state after the operations in the block
        # If this is set, no new space op may be recorded.
        self.final_state = None

    def append(self, operation):
        self.crnt_block.operations.append(operation)

    def guessbool(self, ctx, w_condition):
        block = self.crnt_block
        links = []
        for case in [False, True]:
            egg = EggBlock([], block, case)
            ctx.pendingblocks.append(egg)
            link = Link([], egg, case)
            links.append(link)

        block.exitswitch = w_condition
        block.closeblock(*links)
        # forked the graph. Note that False comes before True by default
        # in the exits tuple so that (just in case we need it) we
        # actually have block.exits[False] = elseLink and
        # block.exits[True] = ifLink.
        raise StopFlowing

    def guessexception(self, ctx, *cases):
        block = self.crnt_block
        links = []
        for case in [None] + list(cases):
            if case is not None:
                if case is Exception:
                    last_exc = Variable('last_exception')
                else:
                    last_exc = Constant(case)
                last_exc_value = Variable('last_exc_value')
                vars = [last_exc, last_exc_value]
                vars2 = [Variable(), Variable()]
            else:
                vars = []
                vars2 = []
            egg = EggBlock(vars2, block, case)
            ctx.pendingblocks.append(egg)
            link = Link(vars, egg, case)
            if case is not None:
                link.extravars(last_exception=last_exc, last_exc_value=last_exc_value)
                egg.extravars(last_exception=last_exc)
            links.append(link)

        block.exitswitch = c_last_exception
        block.closeblock(*links)
        raise StopFlowing


class Replayer(Recorder):

    def __init__(self, block, booloutcome, nextreplayer):
        self.crnt_block = block
        self.listtoreplay = block.operations
        self.booloutcome = booloutcome
        self.nextreplayer = nextreplayer
        self.index = 0

    def append(self, operation):
        operation.result = self.listtoreplay[self.index].result
        assert operation == self.listtoreplay[self.index], (
            '\n'.join(["Not generating the same operation sequence:"] +
                      [str(s) for s in self.listtoreplay[:self.index]] +
                      ["  ---> | while repeating we see here"] +
                      ["       | %s" % operation] +
                      [str(s) for s in self.listtoreplay[self.index:]]))
        self.index += 1

    def guessbool(self, ctx, w_condition):
        assert self.index == len(self.listtoreplay)
        ctx.recorder = self.nextreplayer
        return self.booloutcome

    def guessexception(self, ctx, *classes):
        assert self.index == len(self.listtoreplay)
        ctx.recorder = self.nextreplayer
        outcome = self.booloutcome
        if outcome is not None:
            egg = self.nextreplayer.crnt_block
            w_exc_cls, w_exc_value = egg.inputargs[-2:]
            if isinstance(egg.last_exception, Constant):
                w_exc_cls = egg.last_exception
                assert not isinstance(w_exc_cls.value, list)
            raise RaiseImplicit(FSException(w_exc_cls, w_exc_value))

# ____________________________________________________________

_unary_ops = [
    ('UNARY_POSITIVE', op.pos),
    ('UNARY_NEGATIVE', op.neg),
    ('UNARY_CONVERT', op.repr),
    ('UNARY_INVERT', op.invert),
]

def unaryoperation(OPCODE, operation):
    def UNARY_OP(self, *ignored):
        w_1 = self.popvalue()
        w_result = operation(w_1).eval(self)
        self.pushvalue(w_result)
    UNARY_OP.func_name = OPCODE
    return UNARY_OP

_binary_ops = [
    ('BINARY_MULTIPLY', op.mul),
    ('BINARY_TRUE_DIVIDE', op.truediv),
    ('BINARY_FLOOR_DIVIDE', op.floordiv),
    ('BINARY_DIVIDE', op.div),
    ('BINARY_MODULO', op.mod),
    ('BINARY_ADD', op.add),
    ('BINARY_SUBTRACT', op.sub),
    ('BINARY_SUBSCR', op.getitem),
    ('BINARY_LSHIFT', op.lshift),
    ('BINARY_RSHIFT', op.rshift),
    ('BINARY_AND', op.and_),
    ('BINARY_XOR', op.xor),
    ('BINARY_OR', op.or_),
    ('INPLACE_MULTIPLY', op.inplace_mul),
    ('INPLACE_TRUE_DIVIDE', op.inplace_truediv),
    ('INPLACE_FLOOR_DIVIDE', op.inplace_floordiv),
    ('INPLACE_DIVIDE', op.inplace_div),
    ('INPLACE_MODULO', op.inplace_mod),
    ('INPLACE_ADD', op.inplace_add),
    ('INPLACE_SUBTRACT', op.inplace_sub),
    ('INPLACE_LSHIFT', op.inplace_lshift),
    ('INPLACE_RSHIFT', op.inplace_rshift),
    ('INPLACE_AND', op.inplace_and),
    ('INPLACE_XOR', op.inplace_xor),
    ('INPLACE_OR', op.inplace_or),
]

def binaryoperation(OPCODE, operation):
    """NOT_RPYTHON"""
    def BINARY_OP(self, _):
        w_2 = self.popvalue()
        w_1 = self.popvalue()
        w_result = operation(w_1, w_2).eval(self)
        self.pushvalue(w_result)
    BINARY_OP.func_name = OPCODE
    return BINARY_OP

_unsupported_ops = [
    ('BINARY_POWER', "a ** b"),
    ('BUILD_CLASS', 'defining classes inside functions'),
    ('EXEC_STMT', 'exec statement'),
    ('STOP_CODE', '???'),
    ('STORE_NAME', 'modifying globals'),
    ('INPLACE_POWER', 'a **= b'),
    ('LOAD_LOCALS', 'locals()'),
    ('IMPORT_STAR', 'import *'),
    ('MISSING_OPCODE', '???'),
    ('DELETE_GLOBAL', 'modifying globals'),
    ('DELETE_NAME', 'modifying globals'),
    ('DELETE_ATTR', 'deleting attributes'),
]

def unsupportedoperation(OPCODE, msg):
    def UNSUPPORTED(self, *ignored):
        raise FlowingError("%s is not RPython" % (msg,))
    UNSUPPORTED.func_name = OPCODE
    return UNSUPPORTED

compare_method = [
    "cmp_lt",   # "<"
    "cmp_le",   # "<="
    "cmp_eq",   # "=="
    "cmp_ne",   # "!="
    "cmp_gt",   # ">"
    "cmp_ge",   # ">="
    "cmp_in",
    "cmp_not_in",
    "cmp_is",
    "cmp_is_not",
    "cmp_exc_match",
    ]


class FlowContext(object):
    def __init__(self, graph, code):
        self.graph = graph
        func = graph.func
        self.pycode = code
        self.w_globals = Constant(func.func_globals)
        self.blockstack = []

        self.init_closure(func.func_closure)
        self.f_lineno = code.co_firstlineno
        self.last_offset = 0

        self.init_locals_stack(code)

        self.joinpoints = {}

    def init_closure(self, closure):
        if closure is None:
            self.closure = []
        else:
            self.closure = list(closure)
        assert len(self.closure) == len(self.pycode.co_freevars)

    def init_locals_stack(self, code):
        """
        Initialize the locals and the stack.

        The locals are ordered according to self.pycode.signature.
        """
        self.nlocals = code.co_nlocals
        self.locals_w = [None] * code.co_nlocals
        self.stack = []

    @property
    def stackdepth(self):
        return len(self.stack)

    def pushvalue(self, w_object):
        self.stack.append(w_object)

    def popvalue(self):
        return self.stack.pop()

    def peekvalue(self, index_from_top=0):
        # NOTE: top of the stack is peekvalue(0).
        index = ~index_from_top
        return self.stack[index]

    def settopvalue(self, w_object, index_from_top=0):
        index = ~index_from_top
        self.stack[index] = w_object

    def popvalues(self, n):
        if n == 0:
            return []
        values_w = self.stack[-n:]
        del self.stack[-n:]
        return values_w

    def dropvaluesuntil(self, finaldepth):
        del self.stack[finaldepth:]

    def getstate(self, next_offset):
        return FrameState(self.locals_w[:], self.stack[:],
                self.last_exception, self.blockstack[:], next_offset)

    def setstate(self, state):
        """ Reset the context to the given frame state. """
        self.locals_w = state.locals_w[:]
        self.stack = state.stack[:]
        self.last_exception = state.last_exception
        self.blockstack = state.blocklist[:]
        self._normalize_raise_signals()

    def _normalize_raise_signals(self):
        st = self.stack
        for i in range(len(st)):
            if isinstance(st[i], RaiseImplicit):
                st[i] = Raise(st[i].w_exc)

    def guessbool(self, w_condition):
        if isinstance(w_condition, Constant):
            return w_condition.value
        return self.recorder.guessbool(self, w_condition)

    def maybe_merge(self):
        recorder = self.recorder
        if getattr(recorder, 'final_state', None) is not None:
            self.mergeblock(recorder.crnt_block, recorder.final_state)
            raise StopFlowing

    def record(self, spaceop):
        spaceop.offset = self.last_offset
        self.recorder.append(spaceop)

    def do_op(self, op):
        self.maybe_merge()
        self.record(op)
        self.guessexception(op.canraise)
        return op.result

    def guessexception(self, exceptions):
        """
        Catch possible exceptions implicitly.
        """
        if not exceptions:
            return
        # Implicit exceptions are ignored unless they are caught explicitly
        if self.has_exc_handler():
            self.recorder.guessexception(self, *exceptions)

    def has_exc_handler(self):
        return any(isinstance(block, (ExceptBlock, FinallyBlock))
                for block in self.blockstack)

    def build_flow(self):
        graph = self.graph
        self.pendingblocks = collections.deque([graph.startblock])
        while self.pendingblocks:
            block = self.pendingblocks.popleft()
            if not block.dead:
                self.record_block(block)

    def record_block(self, block):
        self.setstate(block.framestate)
        next_offset = block.framestate.next_offset
        self.recorder = block.make_recorder()
        try:
            while True:
                next_offset = self.handle_bytecode(next_offset)
                self.recorder.final_state = self.getstate(next_offset)
        except StopFlowing:
            pass
        except FlowingError as exc:
            if exc.ctx is None:
                exc.ctx = self
            raise

        self.recorder = None

    def mergeblock(self, currentblock, currentstate):
        next_offset = currentstate.next_offset
        # can 'currentstate' be merged with one of the blocks that
        # already exist for this bytecode position?
        candidates = self.joinpoints.setdefault(next_offset, [])
        for block in candidates:
            newstate = block.framestate.union(currentstate)
            if newstate is not None:
                break
        else:
            newblock = self.make_next_block(currentblock, currentstate)
            candidates.insert(0, newblock)
            return

        if newstate.matches(block.framestate):
            outputargs = currentstate.getoutputargs(newstate)
            currentblock.closeblock(Link(outputargs, block))
            return

        newblock = SpamBlock(newstate)
        varnames = self.pycode.co_varnames
        for name, w_value in zip(varnames, newstate.locals_w):
            if isinstance(w_value, Variable):
                w_value.rename(name)
        # unconditionally link the current block to the newblock
        outputargs = currentstate.getoutputargs(newstate)
        link = Link(outputargs, newblock)
        currentblock.closeblock(link)

        # to simplify the graph, we patch the old block to point
        # directly at the new block which is its generalization
        block.dead = True
        block.operations = ()
        block.exitswitch = None
        outputargs = block.framestate.getoutputargs(newstate)
        block.recloseblock(Link(outputargs, newblock))
        candidates.remove(block)

        candidates.insert(0, newblock)
        self.pendingblocks.append(newblock)

    def make_next_block(self, block, state):
        newstate = state.copy()
        newblock = SpamBlock(newstate)
        # unconditionally link the current block to the newblock
        outputargs = state.getoutputargs(newstate)
        link = Link(outputargs, newblock)
        block.closeblock(link)
        self.pendingblocks.append(newblock)
        return newblock

    # hack for unrolling iterables, don't use this
    def replace_in_stack(self, oldvalue, newvalue):
        w_new = Constant(newvalue)
        stack_items_w = self.stack
        for i in range(self.stackdepth - 1, - 1, -1):
            w_v = stack_items_w[i]
            if isinstance(w_v, Constant):
                if w_v.value is oldvalue:
                    # replace the topmost item of the stack that is equal
                    # to 'oldvalue' with 'newvalue'.
                    stack_items_w[i] = w_new
                    break

    def handle_bytecode(self, next_offset):
        self.last_offset = next_offset
        next_offset, methodname, oparg = self.pycode.read(next_offset)
        try:
            offset = getattr(self, methodname)(oparg)
            return offset if offset is not None else next_offset
        except FlowSignal as signal:
            return self.unroll(signal)

    def unroll(self, signal):
        while self.blockstack:
            block = self.blockstack.pop()
            if isinstance(signal, block.handles):
                return block.handle(self, signal)
            block.cleanupstack(self)
        return signal.nomoreblocks(self)

    def getlocalvarname(self, index):
        return self.pycode.co_varnames[index]

    def getconstant_w(self, index):
        return const(self.pycode.consts[index])

    def getname_u(self, index):
        return self.pycode.names[index]

    def getname_w(self, index):
        return Constant(self.pycode.names[index])

    def appcall(self, func, *args_w):
        """Call an app-level RPython function directly"""
        w_func = const(func)
        return self.do_op(op.simple_call(w_func, *args_w))

    def BAD_OPCODE(self, _):
        raise FlowingError("This operation is not RPython")

    def BREAK_LOOP(self, oparg):
        raise Break

    def CONTINUE_LOOP(self, startofloop):
        raise Continue(startofloop)

    def not_(self, w_obj):
        w_bool = op.bool(w_obj).eval(self)
        return const(not self.guessbool(w_bool))

    def UNARY_NOT(self, _):
        w_obj = self.popvalue()
        self.pushvalue(self.not_(w_obj))

    def cmp_lt(self, w_1, w_2):
        return op.lt(w_1, w_2).eval(self)

    def cmp_le(self, w_1, w_2):
        return op.le(w_1, w_2).eval(self)

    def cmp_eq(self, w_1, w_2):
        return op.eq(w_1, w_2).eval(self)

    def cmp_ne(self, w_1, w_2):
        return op.ne(w_1, w_2).eval(self)

    def cmp_gt(self, w_1, w_2):
        return op.gt(w_1, w_2).eval(self)

    def cmp_ge(self, w_1, w_2):
        return op.ge(w_1, w_2).eval(self)

    def cmp_in(self, w_1, w_2):
        return op.contains(w_2, w_1).eval(self)

    def cmp_not_in(self, w_1, w_2):
        return self.not_(self.cmp_in(w_1, w_2))

    def cmp_is(self, w_1, w_2):
        return op.is_(w_1, w_2).eval(self)

    def cmp_is_not(self, w_1, w_2):
        return self.not_(op.is_(w_1, w_2).eval(self))

    def exception_match(self, w_exc_type, w_check_class):
        """Checks if the given exception type matches 'w_check_class'."""
        if not isinstance(w_check_class, Constant):
            raise FlowingError("Non-constant except guard.")
        check_class = w_check_class.value
        if check_class in (NotImplementedError, AssertionError):
            raise FlowingError(
                "Catching %s is not valid in RPython" % check_class.__name__)
        if not isinstance(check_class, tuple):
            # the simple case
            return self.guessbool(op.issubtype(w_exc_type, w_check_class).eval(self))
        # special case for StackOverflow (see rlib/rstackovf.py)
        if check_class == rstackovf.StackOverflow:
            w_real_class = const(rstackovf._StackOverflow)
            return self.guessbool(op.issubtype(w_exc_type, w_real_class).eval(self))
        # checking a tuple of classes
        for klass in w_check_class.value:
            if self.exception_match(w_exc_type, const(klass)):
                return True
        return False

    def cmp_exc_match(self, w_1, w_2):
        return const(self.exception_match(w_1, w_2))

    def COMPARE_OP(self, testnum):
        w_2 = self.popvalue()
        w_1 = self.popvalue()
        w_result = getattr(self, compare_method[testnum])(w_1, w_2)
        self.pushvalue(w_result)

    def exc_from_raise(self, w_arg1, w_arg2):
        """
        Create a wrapped exception from the arguments of a raise statement.

        Returns an FSException object whose w_value is an instance of w_type.
        """
        w_is_type = op.simple_call(const(isinstance), w_arg1, const(type)).eval(self)
        if self.guessbool(w_is_type):
            # this is for all cases of the form (Class, something)
            if self.guessbool(op.is_(w_arg2, w_None).eval(self)):
                # raise Type: we assume we have to instantiate Type
                w_value = op.simple_call(w_arg1).eval(self)
            else:
                w_valuetype = op.type(w_arg2).eval(self)
                if self.guessbool(op.issubtype(w_valuetype, w_arg1).eval(self)):
                    # raise Type, Instance: let etype be the exact type of value
                    w_value = w_arg2
                else:
                    # raise Type, X: assume X is the constructor argument
                    w_value = op.simple_call(w_arg1, w_arg2).eval(self)
        else:
            # the only case left here is (inst, None), from a 'raise inst'.
            if not self.guessbool(op.is_(w_arg2, const(None)).eval(self)):
                exc = TypeError("instance exception may not have a "
                                "separate value")
                raise Raise(const(exc))
            w_value = w_arg1
        w_type = op.type(w_value).eval(self)
        return FSException(w_type, w_value)

    def RAISE_VARARGS(self, nbargs):
        if nbargs == 0:
            if self.last_exception is not None:
                w_exc = self.last_exception
            else:
                w_exc = const(TypeError(
                    "raise: no active exception to re-raise"))
            raise Raise(w_exc)

        if nbargs >= 3:
            self.popvalue()
        if nbargs >= 2:
            w_value = self.popvalue()
            w_type = self.popvalue()
            operror = self.exc_from_raise(w_type, w_value)
        else:
            w_type = self.popvalue()
            operror = self.exc_from_raise(w_type, w_None)
        raise Raise(operror)

    def import_name(self, name, glob=None, loc=None, frm=None, level=-1):
        try:
            mod = __import__(name, glob, loc, frm, level)
        except ImportError as e:
            raise Raise(const(e))
        return const(mod)

    def IMPORT_NAME(self, nameindex):
        modulename = self.getname_u(nameindex)
        glob = self.w_globals.value
        fromlist = self.popvalue().value
        level = self.popvalue().value
        w_obj = self.import_name(modulename, glob, None, fromlist, level)
        self.pushvalue(w_obj)

    def import_from(self, w_module, w_name):
        assert isinstance(w_module, Constant)
        assert isinstance(w_name, Constant)
        try:
            return op.getattr(w_module, w_name).eval(self)
        except FlowingError:
            exc = ImportError("cannot import name '%s'" % w_name.value)
            raise Raise(const(exc))

    def IMPORT_FROM(self, nameindex):
        w_name = self.getname_w(nameindex)
        w_module = self.peekvalue()
        self.pushvalue(self.import_from(w_module, w_name))

    def RETURN_VALUE(self, oparg):
        w_returnvalue = self.popvalue()
        raise Return(w_returnvalue)

    def END_FINALLY(self, oparg):
        # unlike CPython, there are two statically distinct cases: the
        # END_FINALLY might be closing an 'except' block or a 'finally'
        # block.  In the first case, the stack contains three items:
        #   [exception type we are now handling]
        #   [exception value we are now handling]
        #   [Raise]
        # In the case of a finally: block, the stack contains only one
        # item (unlike CPython which can have 1, 2 or 3 items):
        #   [subclass of FlowSignal]
        w_top = self.popvalue()
        if w_top == w_None:
            # finally: block with no unroller active
            return
        elif isinstance(w_top, FlowSignal):
            # case of a finally: block
            raise w_top
        else:
            # case of an except: block.  We popped the exception type
            self.popvalue()        #     Now we pop the exception value
            signal = self.popvalue()
            raise signal

    def POP_BLOCK(self, oparg):
        block = self.blockstack.pop()
        block.cleanupstack(self)  # the block knows how to clean up the value stack

    def JUMP_ABSOLUTE(self, jumpto):
        return jumpto

    def YIELD_VALUE(self, _):
        assert self.pycode.is_generator
        w_result = self.popvalue()
        op.yield_(w_result).eval(self)
        # XXX yield expressions not supported. This will blow up if the value
        # isn't popped straightaway.
        self.pushvalue(None)

    PRINT_EXPR = BAD_OPCODE
    PRINT_ITEM_TO = BAD_OPCODE
    PRINT_NEWLINE_TO = BAD_OPCODE

    def PRINT_ITEM(self, oparg):
        w_item = self.popvalue()
        w_s = op.str(w_item).eval(self)
        self.appcall(rpython_print_item, w_s)

    def PRINT_NEWLINE(self, oparg):
        self.appcall(rpython_print_newline)

    def JUMP_FORWARD(self, target):
        return target

    def JUMP_IF_FALSE(self, target):
        # Python <= 2.6 only
        w_cond = self.peekvalue()
        if not self.guessbool(op.bool(w_cond).eval(self)):
            return target

    def JUMP_IF_TRUE(self, target):
        # Python <= 2.6 only
        w_cond = self.peekvalue()
        if self.guessbool(op.bool(w_cond).eval(self)):
            return target

    def POP_JUMP_IF_FALSE(self, target):
        w_value = self.popvalue()
        if not self.guessbool(op.bool(w_value).eval(self)):
            return target

    def POP_JUMP_IF_TRUE(self, target):
        w_value = self.popvalue()
        if self.guessbool(op.bool(w_value).eval(self)):
            return target

    def JUMP_IF_FALSE_OR_POP(self, target):
        w_value = self.peekvalue()
        if not self.guessbool(op.bool(w_value).eval(self)):
            return target
        self.popvalue()

    def JUMP_IF_TRUE_OR_POP(self, target):
        w_value = self.peekvalue()
        if self.guessbool(op.bool(w_value).eval(self)):
            return target
            return target
        self.popvalue()

    def JUMP_IF_NOT_DEBUG(self, target):
        pass

    def GET_ITER(self, oparg):
        w_iterable = self.popvalue()
        w_iterator = op.iter(w_iterable).eval(self)
        self.pushvalue(w_iterator)

    def FOR_ITER(self, target):
        w_iterator = self.peekvalue()
        self.blockstack.append(IterBlock(self, target))
        w_nextitem = op.next(w_iterator).eval(self)
        self.blockstack.pop()
        self.pushvalue(w_nextitem)

    def SETUP_LOOP(self, target):
        block = LoopBlock(self, target)
        self.blockstack.append(block)

    def SETUP_EXCEPT(self, target):
        block = ExceptBlock(self, target)
        self.blockstack.append(block)

    def SETUP_FINALLY(self, target):
        block = FinallyBlock(self, target)
        self.blockstack.append(block)

    def SETUP_WITH(self, target):
        # A simpler version than the 'real' 2.7 one:
        # directly call manager.__enter__(), don't use special lookup functions
        # which don't make sense on the RPython type system.
        w_manager = self.peekvalue()
        w_exit = op.getattr(w_manager, const("__exit__")).eval(self)
        self.settopvalue(w_exit)
        w_enter = op.getattr(w_manager, const('__enter__')).eval(self)
        w_result = op.simple_call(w_enter).eval(self)
        block = WithBlock(self, target)
        self.blockstack.append(block)
        self.pushvalue(w_result)

    def WITH_CLEANUP(self, oparg):
        # Note: RPython context managers receive None in lieu of tracebacks
        # and cannot suppress the exception.
        unroller = self.popvalue()
        w_exitfunc = self.popvalue()
        self.pushvalue(unroller)

        if isinstance(unroller, Raise):
            w_exc = unroller.w_exc
            # The annotator won't allow to merge exception types with None.
            # Replace it with the exception value...
            op.simple_call(w_exitfunc, w_exc.w_value, w_exc.w_value, w_None
                           ).eval(self)
        else:
            op.simple_call(w_exitfunc, w_None, w_None, w_None).eval(self)

    def LOAD_FAST(self, varindex):
        w_value = self.locals_w[varindex]
        if w_value is None:
            raise FlowingError("Local variable referenced before assignment")
        self.pushvalue(w_value)

    def LOAD_CONST(self, constindex):
        w_const = self.getconstant_w(constindex)
        self.pushvalue(w_const)

    def find_global(self, w_globals, varname):
        try:
            value = w_globals.value[varname]
        except KeyError:
            # not in the globals, now look in the built-ins
            try:
                value = getattr(__builtin__, varname)
            except AttributeError:
                raise FlowingError("global name '%s' is not defined" % varname)
        return const(value)

    def LOAD_GLOBAL(self, nameindex):
        w_result = self.find_global(self.w_globals, self.getname_u(nameindex))
        self.pushvalue(w_result)
    LOAD_NAME = LOAD_GLOBAL

    def LOAD_ATTR(self, nameindex):
        "obj.attributename"
        w_obj = self.popvalue()
        w_attributename = self.getname_w(nameindex)
        w_value = op.getattr(w_obj, w_attributename).eval(self)
        self.pushvalue(w_value)
    LOOKUP_METHOD = LOAD_ATTR

    def LOAD_DEREF(self, varindex):
        cell = self.closure[varindex]
        try:
            content = cell.cell_contents
        except ValueError:
            name = self.pycode.co_freevars[varindex]
            raise FlowingError("Undefined closure variable '%s'" % name)
        self.pushvalue(const(content))

    def STORE_FAST(self, varindex):
        w_newvalue = self.popvalue()
        assert w_newvalue is not None
        self.locals_w[varindex] = w_newvalue
        if isinstance(w_newvalue, Variable):
            w_newvalue.rename(self.getlocalvarname(varindex))

    def STORE_GLOBAL(self, nameindex):
        varname = self.getname_u(nameindex)
        raise FlowingError(
            "Attempting to modify global variable  %r." % (varname))

    def POP_TOP(self, oparg):
        self.popvalue()

    def ROT_TWO(self, oparg):
        w_1 = self.popvalue()
        w_2 = self.popvalue()
        self.pushvalue(w_1)
        self.pushvalue(w_2)

    def ROT_THREE(self, oparg):
        w_1 = self.popvalue()
        w_2 = self.popvalue()
        w_3 = self.popvalue()
        self.pushvalue(w_1)
        self.pushvalue(w_3)
        self.pushvalue(w_2)

    def ROT_FOUR(self, oparg):
        w_1 = self.popvalue()
        w_2 = self.popvalue()
        w_3 = self.popvalue()
        w_4 = self.popvalue()
        self.pushvalue(w_1)
        self.pushvalue(w_4)
        self.pushvalue(w_3)
        self.pushvalue(w_2)

    def DUP_TOP(self, oparg):
        w_1 = self.peekvalue()
        self.pushvalue(w_1)

    def DUP_TOPX(self, itemcount):
        delta = itemcount - 1
        while True:
            itemcount -= 1
            if itemcount < 0:
                break
            w_value = self.peekvalue(delta)
            self.pushvalue(w_value)

    for OPCODE, op in _unary_ops:
        locals()[OPCODE] = unaryoperation(OPCODE, op)

    for OPCODE, op in _binary_ops:
        locals()[OPCODE] = binaryoperation(OPCODE, op)

    for OPCODE, op in _unsupported_ops:
        locals()[OPCODE] = unsupportedoperation(OPCODE, op)

    def BUILD_LIST_FROM_ARG(self, _):
        # This opcode was added with pypy-1.8.  Here is a simpler
        # version, enough for annotation.
        last_val = self.popvalue()
        self.pushvalue(op.newlist().eval(self))
        self.pushvalue(last_val)

    def call_function(self, oparg, w_star=None, w_starstar=None):
        if w_starstar is not None:
            raise FlowingError("Dict-unpacking is not RPython")
        n_arguments = oparg & 0xff
        n_keywords = (oparg >> 8) & 0xff
        keywords = {}
        for _ in range(n_keywords):
            w_value = self.popvalue()
            w_key = self.popvalue()
            key = w_key.value
            keywords[key] = w_value
        arguments = self.popvalues(n_arguments)
        args = CallSpec(arguments, keywords, w_star)
        w_function = self.popvalue()
        if args.keywords or isinstance(args.w_stararg, Variable):
            shape, args_w = args.flatten()
            hlop = op.call_args(w_function, Constant(shape), *args_w)
        else:
            hlop = op.simple_call(w_function, *args.as_list())
        self.pushvalue(hlop.eval(self))

    def CALL_FUNCTION(self, oparg):
        self.call_function(oparg)
    CALL_METHOD = CALL_FUNCTION

    def CALL_FUNCTION_VAR(self, oparg):
        w_varargs = self.popvalue()
        self.call_function(oparg, w_varargs)

    def CALL_FUNCTION_KW(self, oparg):
        w_varkw = self.popvalue()
        self.call_function(oparg, None, w_varkw)

    def CALL_FUNCTION_VAR_KW(self, oparg):
        w_varkw = self.popvalue()
        w_varargs = self.popvalue()
        self.call_function(oparg, w_varargs, w_varkw)

    def newfunction(self, w_code, defaults_w):
        if not all(isinstance(value, Constant) for value in defaults_w):
            raise FlowingError("Dynamically created function must"
                               " have constant default values.")
        code = w_code.value
        globals = self.w_globals.value
        defaults = tuple([default.value for default in defaults_w])
        fn = types.FunctionType(code, globals, code.co_name, defaults)
        return Constant(fn)

    def MAKE_FUNCTION(self, numdefaults):
        w_codeobj = self.popvalue()
        defaults = self.popvalues(numdefaults)
        fn = self.newfunction(w_codeobj, defaults)
        self.pushvalue(fn)

    def STORE_ATTR(self, nameindex):
        "obj.attributename = newvalue"
        w_attributename = self.getname_w(nameindex)
        w_obj = self.popvalue()
        w_newvalue = self.popvalue()
        op.setattr(w_obj, w_attributename, w_newvalue).eval(self)

    def unpack_sequence(self, w_iterable, expected_length):
        w_len = op.len(w_iterable).eval(self)
        w_correct = op.eq(w_len, const(expected_length)).eval(self)
        if not self.guessbool(op.bool(w_correct).eval(self)):
            w_exc = self.exc_from_raise(const(ValueError), const(None))
            raise Raise(w_exc)
        return [op.getitem(w_iterable, const(i)).eval(self)
                for i in range(expected_length)]

    def UNPACK_SEQUENCE(self, itemcount):
        w_iterable = self.popvalue()
        items = self.unpack_sequence(w_iterable, itemcount)
        for w_item in reversed(items):
            self.pushvalue(w_item)

    def slice(self, w_start, w_end):
        w_obj = self.popvalue()
        w_result = op.getslice(w_obj, w_start, w_end).eval(self)
        self.pushvalue(w_result)

    def SLICE_0(self, oparg):
        self.slice(w_None, w_None)

    def SLICE_1(self, oparg):
        w_start = self.popvalue()
        self.slice(w_start, w_None)

    def SLICE_2(self, oparg):
        w_end = self.popvalue()
        self.slice(w_None, w_end)

    def SLICE_3(self, oparg):
        w_end = self.popvalue()
        w_start = self.popvalue()
        self.slice(w_start, w_end)

    def storeslice(self, w_start, w_end):
        w_obj = self.popvalue()
        w_newvalue = self.popvalue()
        op.setslice(w_obj, w_start, w_end, w_newvalue).eval(self)

    def STORE_SLICE_0(self, oparg):
        self.storeslice(w_None, w_None)

    def STORE_SLICE_1(self, oparg):
        w_start = self.popvalue()
        self.storeslice(w_start, w_None)

    def STORE_SLICE_2(self, oparg):
        w_end = self.popvalue()
        self.storeslice(w_None, w_end)

    def STORE_SLICE_3(self, oparg):
        w_end = self.popvalue()
        w_start = self.popvalue()
        self.storeslice(w_start, w_end)

    def deleteslice(self, w_start, w_end):
        w_obj = self.popvalue()
        op.delslice(w_obj, w_start, w_end).eval(self)

    def DELETE_SLICE_0(self, oparg):
        self.deleteslice(w_None, w_None)

    def DELETE_SLICE_1(self, oparg):
        w_start = self.popvalue()
        self.deleteslice(w_start, w_None)

    def DELETE_SLICE_2(self, oparg):
        w_end = self.popvalue()
        self.deleteslice(w_None, w_end)

    def DELETE_SLICE_3(self, oparg):
        w_end = self.popvalue()
        w_start = self.popvalue()
        self.deleteslice(w_start, w_end)

    def LIST_APPEND(self, oparg):
        w_value = self.popvalue()
        if sys.version_info < (2, 7):
            w_list = self.popvalue()
        else:
            w_list = self.peekvalue(oparg - 1)
        w_append_meth = op.getattr(w_list, const('append')).eval(self)
        op.simple_call(w_append_meth, w_value).eval(self)

    def DELETE_FAST(self, varindex):
        if self.locals_w[varindex] is None:
            varname = self.getlocalvarname(varindex)
            message = "local variable '%s' referenced before assignment"
            raise UnboundLocalError(message, varname)
        self.locals_w[varindex] = None

    def STORE_MAP(self, oparg):
        w_key = self.popvalue()
        w_value = self.popvalue()
        w_dict = self.peekvalue()
        op.setitem(w_dict, w_key, w_value).eval(self)

    def STORE_SUBSCR(self, oparg):
        "obj[subscr] = newvalue"
        w_subscr = self.popvalue()
        w_obj = self.popvalue()
        w_newvalue = self.popvalue()
        op.setitem(w_obj, w_subscr, w_newvalue).eval(self)

    def BUILD_SLICE(self, numargs):
        if numargs == 3:
            w_step = self.popvalue()
        elif numargs == 2:
            w_step = w_None
        else:
            raise BytecodeCorruption
        w_end = self.popvalue()
        w_start = self.popvalue()
        w_slice = op.newslice(w_start, w_end, w_step).eval(self)
        self.pushvalue(w_slice)

    def DELETE_SUBSCR(self, oparg):
        "del obj[subscr]"
        w_subscr = self.popvalue()
        w_obj = self.popvalue()
        op.delitem(w_obj, w_subscr).eval(self)

    def BUILD_TUPLE(self, itemcount):
        items = self.popvalues(itemcount)
        w_tuple = op.newtuple(*items).eval(self)
        self.pushvalue(w_tuple)

    def BUILD_LIST(self, itemcount):
        items = self.popvalues(itemcount)
        w_list = op.newlist(*items).eval(self)
        self.pushvalue(w_list)

    def BUILD_MAP(self, itemcount):
        w_dict = op.newdict().eval(self)
        self.pushvalue(w_dict)

    def NOP(self, *args):
        pass

    # XXX Unimplemented 2.7 opcodes ----------------

    # Set literals, set comprehensions

    def BUILD_SET(self, oparg):
        raise NotImplementedError("BUILD_SET")

    def SET_ADD(self, oparg):
        raise NotImplementedError("SET_ADD")

    # Dict comprehensions

    def MAP_ADD(self, oparg):
        raise NotImplementedError("MAP_ADD")

    # Closures

    STORE_DEREF = BAD_OPCODE
    LOAD_CLOSURE = BAD_OPCODE
    MAKE_CLOSURE = BAD_OPCODE

### Frame blocks ###

class FlowSignal(Exception):
    """Abstract base class for translator-level objects that instruct the
    interpreter to change the control flow and the block stack.

    The concrete subclasses correspond to the various values WHY_XXX
    values of the why_code enumeration in ceval.c:

                WHY_NOT,        OK, not this one :-)
                WHY_EXCEPTION,  Raise
                WHY_RERAISE,    implemented differently, see Reraise
                WHY_RETURN,     Return
                WHY_BREAK,      Break
                WHY_CONTINUE,   Continue
                WHY_YIELD       not needed
    """
    def nomoreblocks(self, ctx):
        raise BytecodeCorruption("misplaced bytecode - should not return")

    def __eq__(self, other):
        return type(other) is type(self) and other.args == self.args


class Return(FlowSignal):
    """Signals a 'return' statement.
    Argument is the wrapped object to return.
    """
    def __init__(self, w_value):
        self.w_value = w_value

    def nomoreblocks(self, ctx):
        w_result = self.w_value
        link = Link([w_result], ctx.graph.returnblock)
        ctx.recorder.crnt_block.closeblock(link)
        raise StopFlowing

    @property
    def args(self):
        return [self.w_value]

    @staticmethod
    def rebuild(w_value):
        return Return(w_value)

class Raise(FlowSignal):
    """Signals an application-level exception
    (i.e. an OperationException)."""

    def __init__(self, w_exc):
        self.w_exc = w_exc

    def nomoreblocks(self, ctx):
        w_exc = self.w_exc
        if w_exc.w_type == const(ImportError):
            msg = 'import statement always raises %s' % self
            raise ImportError(msg)
        link = Link([w_exc.w_type, w_exc.w_value], ctx.graph.exceptblock)
        ctx.recorder.crnt_block.closeblock(link)
        raise StopFlowing

    @property
    def args(self):
        return [self.w_exc.w_type, self.w_exc.w_value]

    @classmethod
    def rebuild(cls, w_type, w_value):
        return cls(FSException(w_type, w_value))

class RaiseImplicit(Raise):
    """Signals an exception raised implicitly"""
    def nomoreblocks(self, ctx):
        w_exc = self.w_exc
        if isinstance(w_exc.w_type, Constant):
            exc_cls = w_exc.w_type.value
        else:
            exc_cls = Exception
        msg = "implicit %s shouldn't occur" % exc_cls.__name__
        w_type = Constant(AssertionError)
        w_value = Constant(AssertionError(msg))
        link = Link([w_type, w_value], ctx.graph.exceptblock)
        ctx.recorder.crnt_block.closeblock(link)
        raise StopFlowing


class Break(FlowSignal):
    """Signals a 'break' statement."""

    @property
    def args(self):
        return []

    @staticmethod
    def rebuild():
        return Break.singleton

Break.singleton = Break()

class Continue(FlowSignal):
    """Signals a 'continue' statement.
    Argument is the bytecode position of the beginning of the loop."""

    def __init__(self, jump_to):
        self.jump_to = jump_to

    @property
    def args(self):
        return [const(self.jump_to)]

    @staticmethod
    def rebuild(w_jump_to):
        return Continue(w_jump_to.value)


class FrameBlock(object):
    """Abstract base class for frame blocks from the blockstack,
    used by the SETUP_XXX and POP_BLOCK opcodes."""

    def __init__(self, ctx, handlerposition):
        self.handlerposition = handlerposition
        self.stackdepth = ctx.stackdepth

    def __eq__(self, other):
        return (self.__class__ is other.__class__ and
                self.handlerposition == other.handlerposition and
                self.stackdepth == other.stackdepth)

    def __ne__(self, other):
        return not (self == other)

    def __hash__(self):
        return hash((self.handlerposition, self.stackdepth))

    def cleanupstack(self, ctx):
        ctx.dropvaluesuntil(self.stackdepth)

    def handle(self, ctx, unroller):
        raise NotImplementedError

class LoopBlock(FrameBlock):
    """A loop block.  Stores the end-of-loop pointer in case of 'break'."""

    handles = (Break, Continue)

    def handle(self, ctx, unroller):
        if isinstance(unroller, Continue):
            # re-push the loop block without cleaning up the value stack,
            # and jump to the beginning of the loop, stored in the
            # exception's argument
            ctx.blockstack.append(self)
            return unroller.jump_to
        else:
            # jump to the end of the loop
            self.cleanupstack(ctx)
            return self.handlerposition

class ExceptBlock(FrameBlock):
    """An try:except: block.  Stores the position of the exception handler."""

    handles = Raise

    def handle(self, ctx, unroller):
        # push the exception to the value stack for inspection by the
        # exception handler (the code after the except:)
        self.cleanupstack(ctx)
        assert isinstance(unroller, Raise)
        w_exc = unroller.w_exc
        # the stack setup is slightly different than in CPython:
        # instead of the traceback, we store the unroller object,
        # wrapped.
        ctx.pushvalue(unroller)
        ctx.pushvalue(w_exc.w_value)
        ctx.pushvalue(w_exc.w_type)
        ctx.last_exception = w_exc
        return self.handlerposition   # jump to the handler

class IterBlock(ExceptBlock):
    """A pseudo-block to catch the StopIteration inside FOR_ITER"""
    def handle(self, ctx, unroller):
        w_exc = unroller.w_exc
        if ctx.exception_match(w_exc.w_type, const(StopIteration)):
            ctx.popvalue()
            return self.handlerposition
        else:
            return ctx.unroll(unroller)

class FinallyBlock(FrameBlock):
    """A try:finally: block.  Stores the position of the exception handler."""

    handles = FlowSignal

    def handle(self, ctx, unroller):
        # any abnormal reason for unrolling a finally: triggers the end of
        # the block unrolling and the entering the finally: handler.
        self.cleanupstack(ctx)
        ctx.pushvalue(unroller)
        return self.handlerposition   # jump to the handler


class WithBlock(FinallyBlock):

    def handle(self, ctx, unroller):
        return FinallyBlock.handle(self, ctx, unroller)
