#! /usr/bin/env python
# ______________________________________________________________________

import dis
import opcode

# ______________________________________________________________________
# Module data

hasjump = opcode.hasjrel + opcode.hasjabs
hascbranch = [op for op in hasjump
              if 'IF' in opcode.opname[op]
              or opcode.opname[op] in ('FOR_ITER', 'SETUP_LOOP')]

# Since the actual opcode value may change, manage opcode abstraction
# data by opcode name.

OPCODE_MAP = {
    'BINARY_ADD': (2, 1, None),
    'BINARY_AND': (2, 1, None),
    'BINARY_DIVIDE': (2, 1, None),
    'BINARY_FLOOR_DIVIDE': (2, 1, None),
    'BINARY_LSHIFT': (2, 1, None),
    'BINARY_MODULO': (2, 1, None),
    'BINARY_MULTIPLY': (2, 1, None),
    'BINARY_OR': (2, 1, None),
    'BINARY_POWER': (2, 1, None),
    'BINARY_RSHIFT': (2, 1, None),
    'BINARY_SUBSCR': (2, 1, None),
    'BINARY_SUBTRACT': (2, 1, None),
    'BINARY_TRUE_DIVIDE': (2, 1, None),
    'BINARY_XOR': (2, 1, None),
    'BREAK_LOOP': (0, None, 1),
    'BUILD_CLASS': (None, None, None),
    'BUILD_LIST': (-1, 1, None),
    'BUILD_MAP': (None, None, None),
    'BUILD_SET': (None, None, None),
    'BUILD_SLICE': (None, None, None),
    'BUILD_TUPLE': (-1, 1, None),
    'CALL_FUNCTION': (-2, 1, None),
    'CALL_FUNCTION_KW': (-3, 1, None),
    'CALL_FUNCTION_VAR': (-3, 1, None),
    'CALL_FUNCTION_VAR_KW': (-4, 1, None),
    'COMPARE_OP': (2, 1, None),
    'CONTINUE_LOOP': (None, None, None),
    'DELETE_ATTR': (1, None, 1),
    'DELETE_DEREF': (None, None, None),
    'DELETE_FAST': (0, None, 1),
    'DELETE_GLOBAL': (0, None, 1),
    'DELETE_NAME': (0, None, 1),
    'DELETE_SLICE+0': (1, None, 1),
    'DELETE_SLICE+1': (2, None, 1),
    'DELETE_SLICE+2': (2, None, 1),
    'DELETE_SLICE+3': (3, None, 1),
    'DELETE_SUBSCR': (2, None, 1),
    'DUP_TOP': (None, None, None),
    'DUP_TOPX': (None, None, None),
    'DUP_TOP_TWO': (None, None, None),
    'END_FINALLY': (None, None, None),
    'EXEC_STMT': (None, None, None),
    'EXTENDED_ARG': (None, None, None),
    'FOR_ITER': (1, 1, 1),
    'GET_ITER': (1, 1, None),
    'IMPORT_FROM': (None, None, None),
    'IMPORT_NAME': (None, None, None),
    'IMPORT_STAR': (1, None, 1),
    'INPLACE_ADD': (2, 1, None),
    'INPLACE_AND': (2, 1, None),
    'INPLACE_DIVIDE': (2, 1, None),
    'INPLACE_FLOOR_DIVIDE': (2, 1, None),
    'INPLACE_LSHIFT': (2, 1, None),
    'INPLACE_MODULO': (2, 1, None),
    'INPLACE_MULTIPLY': (2, 1, None),
    'INPLACE_OR': (2, 1, None),
    'INPLACE_POWER': (2, 1, None),
    'INPLACE_RSHIFT': (2, 1, None),
    'INPLACE_SUBTRACT': (2, 1, None),
    'INPLACE_TRUE_DIVIDE': (2, 1, None),
    'INPLACE_XOR': (2, 1, None),
    'JUMP_ABSOLUTE': (0, None, 1),
    'JUMP_FORWARD': (0, None, 1),
    'JUMP_IF_FALSE': (1, 1, 1),
    'JUMP_IF_FALSE_OR_POP': (None, None, None),
    'JUMP_IF_TRUE': (1, 1, 1),
    'JUMP_IF_TRUE_OR_POP': (None, None, None),
    'LIST_APPEND': (2, 0, 1),
    'LOAD_ATTR': (1, 1, None),
    'LOAD_BUILD_CLASS': (None, None, None),
    'LOAD_CLOSURE': (None, None, None),
    'LOAD_CONST': (0, 1, None),
    'LOAD_DEREF': (0, 1, None),
    'LOAD_FAST': (0, 1, None),
    'LOAD_GLOBAL': (0, 1, None),
    'LOAD_LOCALS': (None, None, None),
    'LOAD_NAME': (0, 1, None),
    'MAKE_CLOSURE': (None, None, None),
    'MAKE_FUNCTION': (-2, 1, None),
    'MAP_ADD': (None, None, None),
    'NOP': (0, None, None),
    'POP_BLOCK': (0, None, 1),
    'POP_EXCEPT': (None, None, None),
    'POP_JUMP_IF_FALSE': (1, None, 1),
    'POP_JUMP_IF_TRUE': (1, None, 1),
    'POP_TOP': (1, None, 1),
    'PRINT_EXPR': (1, None, 1),
    'PRINT_ITEM': (1, None, 1),
    'PRINT_ITEM_TO': (2, None, 1),
    'PRINT_NEWLINE': (0, None, 1),
    'PRINT_NEWLINE_TO': (1, None, 1),
    'RAISE_VARARGS': (None, None, None),
    'RETURN_VALUE': (1, None, 1),
    'ROT_FOUR': (None, None, None),
    'ROT_THREE': (None, None, None),
    'ROT_TWO': (None, None, None),
    'SETUP_EXCEPT': (None, None, None),
    'SETUP_FINALLY': (None, None, None),
    'SETUP_LOOP': (None, None, None),
    'SETUP_WITH': (None, None, None),
    'SET_ADD': (None, None, None),
    'SLICE+0': (1, 1, None),
    'SLICE+1': (2, 1, None),
    'SLICE+2': (2, 1, None),
    'SLICE+3': (3, 1, None),
    'STOP_CODE': (None, None, None),
    'STORE_ATTR': (2, None, 1),
    'STORE_DEREF': (1, 0, 1),
    'STORE_FAST': (1, None, 1),
    'STORE_GLOBAL': (1, None, 1),
    'STORE_LOCALS': (None, None, None),
    'STORE_MAP': (1, None, 1),
    'STORE_NAME': (1, None, 1),
    'STORE_SLICE+0': (1, None, 1),
    'STORE_SLICE+1': (2, None, 1),
    'STORE_SLICE+2': (2, None, 1),
    'STORE_SLICE+3': (3, None, 1),
    'STORE_SUBSCR': (3, None, 1),
    'UNARY_CONVERT': (1, 1, None),
    'UNARY_INVERT': (1, 1, None),
    'UNARY_NEGATIVE': (1, 1, None),
    'UNARY_NOT': (1, 1, None),
    'UNARY_POSITIVE': (1, 1, None),
    'UNPACK_EX': (None, None, None),
    'UNPACK_SEQUENCE': (None, None, None),
    'WITH_CLEANUP': (None, None, None),
    'YIELD_VALUE': (1, None, 1),
}

# ______________________________________________________________________
# Module functions

def itercode(code, start = 0):
    """Return a generator of byte-offset, opcode, and argument
    from a byte-code-string
    """
    i = 0
    extended_arg = 0
    if isinstance(code[0], str):
        code = [ord(c) for c in code]
    n = len(code)
    while i < n:
        op = code[i]
        num = i + start
        i = i + 1
        oparg = None
        if op >= opcode.HAVE_ARGUMENT:
            oparg = code[i] + (code[i + 1] * 256) + extended_arg
            extended_arg = 0
            i = i + 2
            if op == opcode.EXTENDED_ARG:
                extended_arg = oparg * 65536

        delta = yield num, op, oparg
        if delta is not None:
            abs_rel, dst = delta
            assert abs_rel == 'abs' or abs_rel == 'rel'
            i = dst if abs_rel == 'abs' else i + dst

# ______________________________________________________________________

def extendlabels(code, labels = None):
    """Extend the set of jump target labels to account for the
    passthrough targets of conditional branches.

    This allows us to create a control flow graph where there is at
    most one branch per basic block.
    """
    if labels is None:
        labels = []
    if isinstance(code[0], str):
        code = [ord(c) for c in code]
    n = len(code)
    i = 0
    while i < n:
        op = code[i]
        i += 1
        if op >= dis.HAVE_ARGUMENT:
            i += 2
            label = -1
            if op in hasjump:
                label = i
            if label >= 0:
                if label not in labels:
                    labels.append(label)
        elif op == opcode.opmap['BREAK_LOOP']:
            if i not in labels:
                labels.append(i)
    return labels

# ______________________________________________________________________

def get_code_object (func):
    return getattr(func, '__code__', getattr(func, 'func_code', None))

# ______________________________________________________________________

def build_basic_blocks (co_obj):
    co_code = co_obj.co_code
    labels = extendlabels(co_code, dis.findlabels(co_code))
    labels.sort()
    blocks = dict((index, list(itercode(co_code[index:next_index], index)))
                  for index, next_index in zip([0] + labels,
                                               labels + [len(co_code)]))
    return blocks

# ______________________________________________________________________
# End of opcode_util.py
