def test_dump_state(capsys): dump_state(VirtualMachine(), Settings(volume=VOLUME_VERBOSE)) assert ( capsys.readouterr().err == """\ Virtual machine state after execution (HERA v2.4.0): R1 = 0x0000 = 0 R2 = 0x0000 = 0 R3 = 0x0000 = 0 R4 = 0x0000 = 0 R5 = 0x0000 = 0 R6 = 0x0000 = 0 R7 = 0x0000 = 0 R8 = 0x0000 = 0 R9 = 0x0000 = 0 R10 = 0x0000 = 0 R11 = 0x0000 = 0 R12 = 0x0000 = 0 R13 = 0x0000 = 0 R14 = 0x0000 = 0 R15 = 0x0000 = 0 Carry-block flag is OFF Carry flag is OFF Overflow flag is OFF Zero flag is OFF Sign flag is OFF """ )
def __init__(self, program: Program, settings: Settings) -> None: self.settings = settings self.program = program self.symbol_table = program.symbol_table # A map from instruction numbers (i.e., possible values of the program counter) # to human-readable line numbers. self.breakpoints = {} # type: Dict[int, str] self.vm = VirtualMachine() # How many CALLs without RETURNs? self.calls = 0 # Back-up of the debugger's state, to implement the "undo" command. Implicitly # defines a linked list that traverses the debugger's entire history. Set to # None when no operations have been performed. self.old = None # type: Optional[Debugger] # In the future, step-by-step execution of data operations may be supported, but # for now they're just executed before interactive debugging starts. for data_op in self.program.data: data_op.execute(self.vm)
def vm(): return VirtualMachine()
class Debugger: """ A class for debugging. External users should generally use the `debug` function in `shell.py` instead of instantiating this class directly. """ def __init__(self, program: Program, settings: Settings) -> None: self.settings = settings self.program = program self.symbol_table = program.symbol_table # A map from instruction numbers (i.e., possible values of the program counter) # to human-readable line numbers. self.breakpoints = {} # type: Dict[int, str] self.vm = VirtualMachine() # How many CALLs without RETURNs? self.calls = 0 # Back-up of the debugger's state, to implement the "undo" command. Implicitly # defines a linked list that traverses the debugger's entire history. Set to # None when no operations have been performed. self.old = None # type: Optional[Debugger] # In the future, step-by-step execution of data operations may be supported, but # for now they're just executed before interactive debugging starts. for data_op in self.program.data: data_op.execute(self.vm) def save(self) -> None: self.old = copy.copy(self) self.old.symbol_table = self.program.symbol_table.copy() self.old.breakpoints = self.breakpoints.copy() self.old.vm = self.vm.copy() def get_breakpoints(self) -> "Dict[int, str]": """Get the breakpoints dictionary.""" return self.breakpoints def set_breakpoint(self, b: int) -> None: """Set a breakpoint at the given instruction number (not line number).""" self.breakpoints[b] = self.instruction_number_to_location(b) def at_breakpoint(self) -> bool: """Return True if the debugger is currently at a breakpoint.""" return not self.finished() and self.vm.pc in self.breakpoints def next(self, *, step: bool) -> None: """ Advance the debugger by one original instruction. When `step` is True and the current operation is a CALL, the entire function call up to the matching RETURN is executed. When `step` is False, just the CALL operation is executed, and the debugger is left on the first operation of the body of the function. """ if self.finished(): return if not step and self.program.code[self.vm.pc].original.name == "CALL": calls = self.calls # step=True prevents infinite regress. self.next(step=True) while (not self.finished() and not self.at_breakpoint() and self.calls > calls): self.next(step=True) else: for real_op in self.real_ops(): if real_op.name == "CALL": self.calls += 1 elif real_op.name == "RETURN": self.calls -= 1 real_op.execute(self.vm) def reset(self) -> None: """Reset the internal state of the debugger.""" self.vm.reset() def op(self, index=None) -> AbstractOperation: """ Return the original operation at the given index, which defaults to the current program counter. """ if index is None: index = self.vm.pc return self.program.code[index].original def real_ops(self) -> "List[AbstractOperation]": """ Return the real operations that correspond to the current original operation. """ original = self.op() end = self.vm.pc while end < len(self.program.code) and self.op(end) == original: end += 1 return self.program.code[self.vm.pc:end] def location_to_instruction_number(self, b: str) -> int: """Resolve a user-supplied location string into an instruction number.""" if b == ".": return self.vm.pc if ":" in b: path, lineno = b.split(":", maxsplit=1) else: path = self.op().loc.path lineno = b try: lineno_as_int = int(lineno) except ValueError: try: opno = self.program.symbol_table[b] assert isinstance(opno, Label) return opno except (KeyError, AssertionError): raise ValueError( "could not locate label `{}`.".format(b)) from None else: for pc, op in enumerate(self.program.code): if op.loc.path == path and op.loc.line == lineno_as_int: return pc raise ValueError("could not find corresponding line.") def instruction_number_to_location(self, b: int, *, append_label=True) -> str: """ Turn an instruction number into a human-readable location string with the file path and line number. """ op = self.program.code[b].original or self.program.code[b] path = "<stdin>" if op.loc.path == "-" else op.loc.path loc = path + ":" + str(op.loc.line) if append_label: # Look for a label corresponding to the breakpoint. label = self.find_label(b) if label is not None: return "{} ({})".format(loc, label) return loc def find_label(self, ino: int) -> "Optional[str]": """Find a label, if one exists, corresponding to the instruction number.""" for symbol, value in self.program.symbol_table.items(): if value == ino and isinstance(value, Label): return symbol return None def finished(self) -> bool: """Return True if the debugger has finished executing the program.""" return self.vm.halted or self.vm.pc >= len(self.program.code) def empty(self) -> bool: """Return True if the debugged program is empty.""" return len(self.program.code) == 0
def vm(): return VirtualMachine(Settings(color=False))