Skip to content

Commit 1e0b1ab

Browse files
Add support for syntax highlighting(via themes).
Highlights opcode's name, arguments, exception table labels.
1 parent 25a10b6 commit 1e0b1ab

2 files changed

Lines changed: 95 additions & 5 deletions

File tree

Lib/_colorize.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,88 @@ class Unittest(ThemeSection):
344344
reset: str = ANSIColors.RESET
345345

346346

347+
@dataclass(frozen=True, kw_only=True)
348+
class Dis(ThemeSection):
349+
label_bg: str = ANSIColors.BACKGROUND_BLUE
350+
label_fg: str = ANSIColors.BLACK
351+
exception_label: str = ANSIColors.CYAN
352+
argument_detail: str = ANSIColors.GREY
353+
354+
op_stack: str = ANSIColors.BOLD_YELLOW
355+
op_load_store: str = ANSIColors.BOLD_CYAN
356+
op_call_return: str = ANSIColors.BOLD_MAGENTA
357+
op_binary_unary: str = ANSIColors.BOLD_BLUE
358+
op_control_flow: str = ANSIColors.BOLD_GREEN
359+
op_build: str = ANSIColors.BOLD_WHITE
360+
op_exceptions: str = ANSIColors.BOLD_RED
361+
op_other: str = ANSIColors.GREY
362+
363+
reset: str = ANSIColors.RESET
364+
365+
def color_by_opname(self, opname: str) -> str:
366+
if opname in (
367+
"POP_TOP",
368+
"POP_ITER",
369+
"END_FOR",
370+
"END_SEND",
371+
"COPY",
372+
"SWAP",
373+
"PUSH_NULL",
374+
"PUSH_EXC_INFO",
375+
"NOP",
376+
"CACHE",
377+
):
378+
return self.op_stack
379+
380+
if opname.startswith(("LOAD_", "STORE_", "DELETE_", "IMPORT_")):
381+
return self.op_load_store
382+
383+
if opname.startswith(("CALL", "RETURN")) or opname in (
384+
"YIELD_VALUE",
385+
"MAKE_FUNCTION",
386+
"SET_FUNCTION_ATTRIBUTE",
387+
"RESUME",
388+
):
389+
return self.op_call_return
390+
391+
if opname.startswith(("BINARY_", "UNARY_")) or opname in (
392+
"COMPARE_OP",
393+
"IS_OP",
394+
"CONTAINS_OP",
395+
"GET_ITER",
396+
"GET_YIELD_FROM_ITER",
397+
"TO_BOOL",
398+
"DELETE_SUBSCR",
399+
):
400+
return self.op_binary_unary
401+
402+
if opname.startswith(("JUMP_", "POP_JUMP_", "FOR_ITER")) or opname in (
403+
"SEND",
404+
"GET_AWAITABLE",
405+
"GET_AITER",
406+
"GET_ANEXT",
407+
"END_ASYNC_FOR",
408+
"CLEANUP_THROW",
409+
):
410+
return self.op_control_flow
411+
412+
if opname.startswith(
413+
("BUILD_", "LIST_", "DICT_", "UNPACK_")
414+
) or opname in ("SET_ADD", "MAP_ADD", "SET_UPDATE"):
415+
return self.op_build
416+
417+
if opname.startswith(("SETUP_", "CHECK_")) or opname in (
418+
"POP_EXCEPT",
419+
"RERAISE",
420+
"WITH_EXCEPT_START",
421+
"RAISE_VARARGS",
422+
"POP_BLOCK",
423+
):
424+
return self.op_exceptions
425+
426+
return self.op_other
427+
428+
347429
@dataclass(frozen=True, kw_only=True)
348430
class Theme:
349431
"""A suite of themes for all sections of Python.
@@ -357,6 +439,7 @@ class Theme:
357439
syntax: Syntax = field(default_factory=Syntax)
358440
traceback: Traceback = field(default_factory=Traceback)
359441
unittest: Unittest = field(default_factory=Unittest)
442+
dis: Dis = field(default_factory=Dis)
360443

361444
def copy_with(
362445
self,
@@ -397,6 +480,7 @@ def no_colors(cls) -> Self:
397480
syntax=Syntax.no_colors(),
398481
traceback=Traceback.no_colors(),
399482
unittest=Unittest.no_colors(),
483+
dis=Dis.no_colors(),
400484
)
401485

402486

Lib/dis.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,9 @@ def __str__(self):
436436
formatter.print_instruction(self, False)
437437
return output.getvalue()
438438

439+
def get_dis_theme():
440+
from _colorize import get_theme
441+
return get_theme().dis
439442

440443
class Formatter:
441444

@@ -478,8 +481,9 @@ def print_instruction(self, instr, mark_as_current=False):
478481
False, None, None, instr.positions),
479482
False)
480483

481-
def print_instruction_line(self, instr, mark_as_current):
484+
def print_instruction_line(self, instr: Instruction, mark_as_current: bool) -> None:
482485
"""Format instruction details for inclusion in disassembly output."""
486+
theme = get_dis_theme()
483487
lineno_width = self.lineno_width
484488
offset_width = self.offset_width
485489
label_width = self.label_width
@@ -527,7 +531,7 @@ def print_instruction_line(self, instr, mark_as_current):
527531
else:
528532
fields.append(' ')
529533
# Column: Opcode name
530-
fields.append(instr.opname.ljust(_OPNAME_WIDTH))
534+
fields.append(f"{theme.color_by_opname(instr.opname)}{instr.opname.ljust(_OPNAME_WIDTH)}{theme.reset}")
531535
# Column: Opcode argument
532536
if instr.arg is not None:
533537
# If opname is longer than _OPNAME_WIDTH, we allow it to overflow into
@@ -537,19 +541,20 @@ def print_instruction_line(self, instr, mark_as_current):
537541
fields.append(repr(instr.arg).rjust(_OPARG_WIDTH - opname_excess))
538542
# Column: Opcode argument details
539543
if instr.argrepr:
540-
fields.append('(' + instr.argrepr + ')')
544+
fields.append(f'{theme.argument_detail}(' + instr.argrepr + f'){theme.reset}')
541545
print(' '.join(fields).rstrip(), file=self.file)
542546

543547
def print_exception_table(self, exception_entries):
544548
file = self.file
549+
theme = get_dis_theme()
545550
if exception_entries:
546551
print("ExceptionTable:", file=file)
547552
for entry in exception_entries:
548553
lasti = " lasti" if entry.lasti else ""
549554
start = entry.start_label
550555
end = entry.end_label
551556
target = entry.target_label
552-
print(f" L{start} to L{end} -> L{target} [{entry.depth}]{lasti}", file=file)
557+
print(f" {theme.exception_label}L{start}{theme.reset} to {theme.exception_label}L{end}{theme.reset} -> {theme.exception_label}L{target}{theme.reset} [{entry.depth}]{lasti}", file=file)
553558

554559

555560
class ArgResolver:
@@ -833,13 +838,14 @@ def disassemble(co, lasti=-1, *, file=None, show_caches=False, adaptive=False,
833838

834839
def _disassemble_recursive(co, *, file=None, depth=None, show_caches=False, adaptive=False, show_offsets=False, show_positions=False):
835840
disassemble(co, file=file, show_caches=show_caches, adaptive=adaptive, show_offsets=show_offsets, show_positions=show_positions)
841+
theme = get_dis_theme()
836842
if depth is None or depth > 0:
837843
if depth is not None:
838844
depth = depth - 1
839845
for x in co.co_consts:
840846
if hasattr(x, 'co_code'):
841847
print(file=file)
842-
print("Disassembly of %r:" % (x,), file=file)
848+
print(f"{theme.label_bg}{theme.label_fg}Disassembly of {x!r}:{theme.reset}", file=file)
843849
_disassemble_recursive(
844850
x, file=file, depth=depth, show_caches=show_caches,
845851
adaptive=adaptive, show_offsets=show_offsets, show_positions=show_positions

0 commit comments

Comments
 (0)