| """ |
| Build a line table for CodeObjects, according to PEP-626 / Python 3.11. |
| |
| See https://github.com/python/cpython/blob/1054a755a3016f95fcd24b3ad20e8ed9048b7939/InternalDocs/locations.md |
| See https://github.com/python/cpython/blob/1054a755a3016f95fcd24b3ad20e8ed9048b7939/Python/assemble.c#L192 |
| """ |
|
|
| import cython |
|
|
|
|
| def build_line_table(positions, firstlineno): |
| |
| table_bytes = [] |
| last_lineno = firstlineno |
| for position_info in positions: |
| last_lineno = encode_single_position(table_bytes, position_info, last_lineno) |
| linetable = ''.join(table_bytes) |
|
|
| """ |
| # Hacky debug helper code for the line table generation. |
| code_obj = build_line_table.__code__.replace(co_linetable=linetable.encode('latin1'), co_firstlineno=firstlineno) |
| print() |
| print(repr(linetable)) |
| print(positions) |
| print(list(code_obj.co_positions())) |
| """ |
|
|
| return linetable |
|
|
|
|
| @cython.cfunc |
| def encode_single_position(table_bytes: list, position_info: tuple, last_lineno: cython.int) -> cython.int: |
| start_lineno: cython.int |
| end_lineno: cython.int |
| start_column: cython.int |
| end_column: cython.int |
|
|
| start_lineno, end_lineno, start_column, end_column = position_info |
| assert start_lineno >= last_lineno, f"{start_lineno} >= {last_lineno}" |
|
|
| last_lineno_delta: cython.int = start_lineno - last_lineno |
|
|
| if end_lineno == start_lineno: |
| |
| if last_lineno_delta == 0 and start_column < 80 and 0 <= (end_column - start_column) < 16: |
| |
| encode_location_short(table_bytes, start_column, end_column) |
| return end_lineno |
| elif 0 <= last_lineno_delta < 3 and start_column < 128 and end_column < 128: |
| |
| encode_location_oneline(table_bytes, last_lineno_delta, start_column, end_column) |
| return end_lineno |
|
|
| |
| encode_location_start(table_bytes, 14) |
| |
| encode_varint(table_bytes, last_lineno_delta << 1) |
| encode_varint(table_bytes, end_lineno - start_lineno) |
| encode_varint(table_bytes, start_column + 1) |
| encode_varint(table_bytes, end_column + 1) |
| return end_lineno |
|
|
|
|
| @cython.exceptval(-1, check=False) |
| @cython.cfunc |
| def encode_location_start(table_bytes: list, code: cython.int) -> cython.int: |
| |
| |
| table_bytes.append(chr(128 | (code << 3))) |
| return 0 |
|
|
|
|
| @cython.exceptval(-1, check=False) |
| @cython.cfunc |
| def encode_location_short(table_bytes: list, start_column: cython.int, end_column: cython.int) -> cython.int: |
| low_bits: cython.int = start_column & 7 |
| code: cython.int = start_column >> 3 |
| |
| table_bytes.append(f"{128 | (code << 3):c}{(low_bits << 4) | (end_column - start_column):c}") |
| return 0 |
|
|
|
|
| @cython.exceptval(-1, check=False) |
| @cython.cfunc |
| def encode_location_oneline(table_bytes: list, line_delta: cython.int, start_column: cython.int, end_column: cython.int) -> cython.int: |
| code: cython.int = 10 + line_delta |
| |
| table_bytes.append(f"{128 | (code << 3):c}{start_column:c}{end_column:c}") |
| return 0 |
|
|
|
|
| """ |
| # Since we sort positions, negative line deltas should not occur. |
| @cython.cfunc |
| def encode_varint_signed(table_bytes: list, value: cython.int) -> cython.int: |
| # (unsigned int)(-val) has undefined behavior for INT_MIN |
| uval: cython.uint = cython.cast(cython.uint, value) if cython.compiled else value |
| if value < 0: |
| uval = ((0 - uval) << 1) | 1 |
| else: |
| uval = uval << 1 |
| encode_varint(table_bytes, uval) |
| """ |
|
|
|
|
| @cython.exceptval(-1, check=False) |
| @cython.cfunc |
| def encode_varint(table_bytes: list, value: cython.uint) -> cython.int: |
| assert value > 0 or value == 0 |
| while value >= 64: |
| table_bytes.append(chr(64 | (value & 63))) |
| value >>= 6 |
| table_bytes.append(chr(value)) |
| return 0 |
|
|