2
Build a line table for CodeObjects, according to PEP-626 / Python 3.11.
4
See https://github.com/python/cpython/blob/1054a755a3016f95fcd24b3ad20e8ed9048b7939/InternalDocs/locations.md
5
See https://github.com/python/cpython/blob/1054a755a3016f95fcd24b3ad20e8ed9048b7939/Python/assemble.c#L192
11
def build_line_table(positions, firstlineno):
12
# positions is a list of four-tuples (start_lineno, end_lineno, start_col_offset, end_col_offset)
14
last_lineno = firstlineno
15
for position_info in positions:
16
last_lineno = encode_single_position(table_bytes, position_info, last_lineno)
17
linetable = ''.join(table_bytes)
20
# Hacky debug helper code for the line table generation.
21
code_obj = build_line_table.__code__.replace(co_linetable=linetable.encode('latin1'), co_firstlineno=firstlineno)
23
print(repr(linetable))
25
print(list(code_obj.co_positions()))
32
def encode_single_position(table_bytes: list, position_info: tuple, last_lineno: cython.int) -> cython.int:
33
start_lineno: cython.int
34
end_lineno: cython.int
35
start_column: cython.int
36
end_column: cython.int
38
start_lineno, end_lineno, start_column, end_column = position_info
39
assert start_lineno >= last_lineno, f"{start_lineno} >= {last_lineno}" # positions should be sorted
41
last_lineno_delta: cython.int = start_lineno - last_lineno
43
if end_lineno == start_lineno:
44
# All in one line, can try short forms.
45
if last_lineno_delta == 0 and start_column < 80 and 0 <= (end_column - start_column) < 16:
46
# Short format (code 0-9): still on same line, small column offset
47
encode_location_short(table_bytes, start_column, end_column)
49
elif 0 <= last_lineno_delta < 3 and start_column < 128 and end_column < 128:
50
# One line format (code 10-12): small line offsets / larger column offsets
51
encode_location_oneline(table_bytes, last_lineno_delta, start_column, end_column)
54
# Store in long format (code 14)
55
encode_location_start(table_bytes, 14)
56
# Since we sort positions, negative line deltas should never occur ==> inline encode_varint_signed()
57
encode_varint(table_bytes, last_lineno_delta << 1)
58
encode_varint(table_bytes, end_lineno - start_lineno)
59
encode_varint(table_bytes, start_column + 1)
60
encode_varint(table_bytes, end_column + 1)
64
@cython.exceptval(-1, check=False)
66
def encode_location_start(table_bytes: list, code: cython.int) -> cython.int:
67
# "Instruction" size is always 1
68
# 128 | (code << 3) | (length - 1)
69
table_bytes.append(chr(128 | (code << 3)))
73
@cython.exceptval(-1, check=False)
75
def encode_location_short(table_bytes: list, start_column: cython.int, end_column: cython.int) -> cython.int:
76
low_bits: cython.int = start_column & 7
77
code: cython.int = start_column >> 3
78
# inlined encode_location_start()
79
table_bytes.append(f"{128 | (code << 3):c}{(low_bits << 4) | (end_column - start_column):c}")
83
@cython.exceptval(-1, check=False)
85
def encode_location_oneline(table_bytes: list, line_delta: cython.int, start_column: cython.int, end_column: cython.int) -> cython.int:
86
code: cython.int = 10 + line_delta
87
# inlined encode_location_start()
88
table_bytes.append(f"{128 | (code << 3):c}{start_column:c}{end_column:c}")
93
# Since we sort positions, negative line deltas should not occur.
95
def encode_varint_signed(table_bytes: list, value: cython.int) -> cython.int:
96
# (unsigned int)(-val) has undefined behavior for INT_MIN
97
uval: cython.uint = cython.cast(cython.uint, value) if cython.compiled else value
99
uval = ((0 - uval) << 1) | 1
102
encode_varint(table_bytes, uval)
106
@cython.exceptval(-1, check=False)
108
def encode_varint(table_bytes: list, value: cython.uint) -> cython.int:
109
assert value > 0 or value == 0
111
table_bytes.append(chr(64 | (value & 63)))
113
table_bytes.append(chr(value))