File: bytecode.py

package info (click to toggle)
pydb 1.26-2
  • links: PTS, VCS
  • area: main
  • in suites: buster, stretch
  • size: 2,512 kB
  • ctags: 1,061
  • sloc: python: 4,200; perl: 2,479; lisp: 866; sh: 780; makefile: 633; ansic: 16
file content (94 lines) | stat: -rw-r--r-- 2,770 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
from opcode import *
import dis
import re

def op_at_code_loc(code, loc):
    try:
        op = ord(code[loc])
    except IndexError:
        return 'got IndexError'
    return opname[op]

def op_at_frame(frame, loc=None):
    code = frame.f_code.co_code
    if loc is None: loc = frame.f_lasti
    return op_at_code_loc(code, loc)

def next_opcode(code, offset):
    '''Return the next opcode and offset as a tuple. Tuple (-100,
    -1000) is returned when reaching the end.'''
    n = len(code)
    while offset < n:
        c = code[offset]
        op = ord(c)
        offset += 1
        if op >= HAVE_ARGUMENT:
            offset += 2
            pass
        yield op, offset
        pass
    yield -100, -1000
    pass

def next_linestart(co, offset, count=1):
    linestarts = dict(dis.findlinestarts(co))
    code = co.co_code
    n = len(code)
    contains_cond_jump = False
    for op, offset in next_opcode(code, offset):
        if offset in linestarts: 
            count -= 1
            if 0 == count:
                return linestarts[offset]
            pass
        pass
    return -1000

# FIXME: break out into a code iterator.
def stmt_contains_make_function(co, lineno):
    linestarts = dict(dis.findlinestarts(co))
    code = co.co_code
    found_start = False
    for offset, start_line in linestarts.items():
        if start_line == lineno: 
            found_start = True
            break
        pass
    if not found_start: 
        return False
    for op, offset in next_opcode(code, offset):
        if -1000 == offset or linestarts.get(offset): return False
        opcode = opname[op]
        # print opcode
        if 'MAKE_FUNCTION' == opcode:
            return True
        pass
    return False

# A pattern for a def header seems to be used a couple of times.
_re_def_str = r'^\s*def\s'
_re_def = re.compile(_re_def_str)
def is_def_stmt(line, frame):
    """Return True if we are looking at a def statement"""
    # Should really also check that operand is a code object
    return _re_def.match(line) and op_at_frame(frame)=='LOAD_CONST' \
        and stmt_contains_make_function(frame.f_code, frame.f_lineno)

# Demo stuff above
if __name__=='__main__':
    import inspect
    def sqr(x):
        return x * x
    frame = inspect.currentframe()
    co = frame.f_code
    lineno = frame.f_lineno
    print 'contains MAKE_FUNCTION', stmt_contains_make_function(co, lineno-4)
    print 'contains MAKE_FUNCTION', stmt_contains_make_function(co, lineno)

    print "op at frame: ", op_at_frame(frame)
    print "op at frame, position 2", op_at_frame(frame, 2)
    print "def statement: x=5?: ", is_def_stmt('x=5', frame)
    # Not a "def" statement because frame is wrong spot
    print is_def_stmt('def foo():', frame)

    pass