{ "cells": [ { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "slide" } }, "source": [ "# How Debuggers Work\n", "\n", "Interactive _debuggers_ are tools that allow you to selectively observe the program state during an execution. In this chapter, you will learn how such debuggers work – by building your own debugger." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.604088Z", "iopub.status.busy": "2023-11-12T12:39:53.603970Z", "iopub.status.idle": "2023-11-12T12:39:53.644367Z", "shell.execute_reply": "2023-11-12T12:39:53.644065Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/html": [ "\n", " \n", " " ], "text/plain": [ "" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from bookutils import YouTubeVideo\n", "YouTubeVideo(\"4aZ0t7CWSjA\")" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "**Prerequisites**\n", "\n", "* You should have read the [Chapter on Tracing Executions](Tracer.ipynb).\n", "* Again, knowing a bit of _Python_ is helpful for understanding the code examples in the book." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "button": false, "execution": { "iopub.execute_input": "2023-11-12T12:39:53.666440Z", "iopub.status.busy": "2023-11-12T12:39:53.666228Z", "iopub.status.idle": "2023-11-12T12:39:53.668767Z", "shell.execute_reply": "2023-11-12T12:39:53.668455Z" }, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import bookutils.setup" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.670425Z", "iopub.status.busy": "2023-11-12T12:39:53.670203Z", "iopub.status.idle": "2023-11-12T12:39:53.672072Z", "shell.execute_reply": "2023-11-12T12:39:53.671774Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import sys" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.673714Z", "iopub.status.busy": "2023-11-12T12:39:53.673582Z", "iopub.status.idle": "2023-11-12T12:39:53.771395Z", "shell.execute_reply": "2023-11-12T12:39:53.771022Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from Tracer import Tracer" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "## Synopsis\n", "\n", "\n", "To [use the code provided in this chapter](Importing.ipynb), write\n", "\n", "```python\n", ">>> from debuggingbook.Debugger import \n", "```\n", "\n", "and then make use of the following features.\n", "\n", "\n", "This chapter provides an interactive debugger for Python functions. The debugger is invoked as\n", "\n", "```python\n", "with Debugger():\n", " function_to_be_observed()\n", " ...\n", "```\n", "While running, you can enter _debugger commands_ at the `(debugger)` prompt. Here's an example session:\n", "\n", "```python\n", ">>> with Debugger():\n", ">>> ret = remove_html_markup('abc')\n", "Calling remove_html_markup(s = 'abc')\n", "\n", "```\n", "(debugger) help\n", "```\n", "break -- Set a breakoint in given line. If no line is given, list all breakpoints\n", "continue -- Resume execution\n", "delete -- Delete breakoint in line given by `arg`.\n", " Without given line, clear all breakpoints\n", "help -- Give help on given `command`. If no command is given, give help on all\n", "list -- Show current function. If `arg` is given, show its source code.\n", "print -- Print an expression. If no expression is given, print all variables\n", "quit -- Finish execution\n", "step -- Execute up to the next line\n", "\n", "```\n", "(debugger) break 14\n", "```\n", "Breakpoints: {14}\n", "\n", "```\n", "(debugger) list\n", "```\n", " 1> def remove_html_markup(s): # type: ignore\n", " 2 tag = False\n", " 3 quote = False\n", " 4 out = \"\"\n", " 5 \n", " 6 for c in s:\n", " 7 if c == '<' and not quote:\n", " 8 tag = True\n", " 9 elif c == '>' and not quote:\n", " 10 tag = False\n", " 11 elif c == '\"' or c == \"'\" and tag:\n", " 12 quote = not quote\n", " 13 elif not tag:\n", " 14# out = out + c\n", " 15 \n", " 16 return out\n", "\n", "```\n", "(debugger) continue\n", "```\n", " # tag = False, quote = False, out = '', c = 'a'\n", "14 out = out + c\n", "\n", "```\n", "(debugger) step\n", "```\n", " # out = 'a'\n", "6 for c in s:\n", "\n", "```\n", "(debugger) print out\n", "```\n", "out = 'a'\n", "\n", "```\n", "(debugger) quit\n", "\n", "The `Debugger` class can be easily extended in subclasses. A new method `NAME_command(self, arg)` will be invoked whenever a command named `NAME` is entered, with `arg` holding given command arguments (empty string if none).\n", "\n", "![](PICS/Debugger-synopsis-1.svg)\n", "\n" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": { "button": false, "new_sheet": true, "run_control": { "read_only": false }, "slideshow": { "slide_type": "slide" } }, "source": [ "## Debuggers\n", "\n", "_Interactive Debuggers_ (or short *debuggers*) are tools that allow you to observe program executions. A debugger typically offers the following features:\n", "\n", "* _Run_ the program\n", "* Define _conditions_ under which the execution should _stop_ and hand over control to the debugger. Conditions include\n", " * a particular location is reached\n", " * a particular variable takes a particular value\n", " * a particular variable is accessed\n", " * or some other condition of choice.\n", "* When the program stops, you can _observe_ the current state, including\n", " * the current location\n", " * variables and their values\n", " * the current function and its callers\n", "* When the program stops, you can _step_ through program execution, having it stop at the next instruction again.\n", "* Finally, you can also _resume_ execution to the next stop." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": true, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "This functionality often comes as a _command-line interface_, typing commands at a prompt; or as a _graphical user interface_, selecting commands from the screen. Debuggers can come as standalone tools, or be integrated into a programming environment of choice." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": true, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "Debugger interaction typically follows a _loop_ pattern. First, you identify the location(s) you want to inspect, and tell the debugger to stop execution once one of these _breakpoints_ is reached. Here's a command that could instruct a command-line debugger to stop at Line 239:\n", "\n", "```\n", "(debugger) break 239\n", "(debugger) _\n", "```\n", "\n", "Then you have the debugger resume or start execution. The debugger will stop at the given location.\n", "\n", "```\n", "(debugger) continue\n", "Line 239: s = x\n", "(debugger) _\n", "```\n", "\n", "When it stops at the given location, you use debugger commands to inspect the state (and check whether things are as expected).\n", "\n", "```\n", "(debugger) print s\n", "s = 'abc'\n", "(debugger) _\n", "```\n", "\n", "You can then step through the program, executing more lines.\n", "\n", "```\n", "(debugger) step\n", "Line 240: c = s[0]\n", "(debugger) print c\n", "c = 'a'\n", "(debugger) _\n", "```\n", "\n", "You can also define new stop conditions, investigating other locations, variables, and conditions." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Debugger Interaction\n", "\n", "Let us now show how to build such a debugger. The key idea of an _interactive_ debugger is to set up the _tracing function_ such that it actually _asks_ what to do next, prompting you to enter a _command_. For the sake of simplicity, we collect such a command interactively from a command line, using the Python `input()` function." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Our debugger holds a number of variables to indicate its current status:\n", "* `stepping` is True whenever the user wants to step into the next line.\n", "* `breakpoints` is a set of breakpoints (line numbers)\n", "* `interact` is True while the user stays at one position.\n", "\n", "We also store the current tracing information in three attributes `frame`, `event`, and `arg`. The variable `local_vars` holds local variables." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.773504Z", "iopub.status.busy": "2023-11-12T12:39:53.773364Z", "iopub.status.idle": "2023-11-12T12:39:53.774969Z", "shell.execute_reply": "2023-11-12T12:39:53.774709Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from types import FrameType" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.776356Z", "iopub.status.busy": "2023-11-12T12:39:53.776271Z", "iopub.status.idle": "2023-11-12T12:39:53.778077Z", "shell.execute_reply": "2023-11-12T12:39:53.777788Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "# ignore\n", "from typing import Any, Optional, Callable, Dict, List, Tuple, Set, TextIO" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.779743Z", "iopub.status.busy": "2023-11-12T12:39:53.779654Z", "iopub.status.idle": "2023-11-12T12:39:53.782067Z", "shell.execute_reply": "2023-11-12T12:39:53.781797Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class Debugger(Tracer):\n", " \"\"\"Interactive Debugger\"\"\"\n", "\n", " def __init__(self, *, file: TextIO = sys.stdout) -> None:\n", " \"\"\"Create a new interactive debugger.\"\"\"\n", " self.stepping: bool = True\n", " self.breakpoints: Set[int] = set()\n", " self.interact: bool = True\n", "\n", " self.frame: FrameType\n", " self.event: Optional[str] = None\n", " self.arg: Any = None\n", "\n", " self.local_vars: Dict[str, Any] = {}\n", "\n", " super().__init__(file=file)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "The `traceit()` method is the main entry point for our debugger. If we should stop, we go into user interaction." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.783639Z", "iopub.status.busy": "2023-11-12T12:39:53.783526Z", "iopub.status.idle": "2023-11-12T12:39:53.785661Z", "shell.execute_reply": "2023-11-12T12:39:53.785414Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "class Debugger(Debugger):\n", " def traceit(self, frame: FrameType, event: str, arg: Any) -> None:\n", " \"\"\"Tracing function; called at every line. To be overloaded in subclasses.\"\"\"\n", " self.frame = frame\n", " self.local_vars = frame.f_locals # Dereference exactly once\n", " self.event = event\n", " self.arg = arg\n", "\n", " if self.stop_here():\n", " self.interaction_loop()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "We stop whenever we are stepping through the program or reach a breakpoint:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.787099Z", "iopub.status.busy": "2023-11-12T12:39:53.786997Z", "iopub.status.idle": "2023-11-12T12:39:53.788723Z", "shell.execute_reply": "2023-11-12T12:39:53.788416Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class Debugger(Debugger):\n", " def stop_here(self) -> bool:\n", " \"\"\"Return True if we should stop\"\"\"\n", " return self.stepping or self.frame.f_lineno in self.breakpoints" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Our interaction loop shows the current status, reads in commands, and executes them." ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.790334Z", "iopub.status.busy": "2023-11-12T12:39:53.790207Z", "iopub.status.idle": "2023-11-12T12:39:53.792402Z", "shell.execute_reply": "2023-11-12T12:39:53.792137Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "class Debugger(Debugger):\n", " def interaction_loop(self) -> None:\n", " \"\"\"Interact with the user\"\"\"\n", " self.print_debugger_status(self.frame, self.event, self.arg) # type: ignore\n", "\n", " self.interact = True\n", " while self.interact:\n", " command = input(\"(debugger) \")\n", " self.execute(command) # type: ignore" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "For a moment, let us implement two commands, `step` and `continue`. `step` steps through the program:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.794196Z", "iopub.status.busy": "2023-11-12T12:39:53.793922Z", "iopub.status.idle": "2023-11-12T12:39:53.796245Z", "shell.execute_reply": "2023-11-12T12:39:53.795947Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "class Debugger(Debugger):\n", " def step_command(self, arg: str = \"\") -> None:\n", " \"\"\"Execute up to the next line\"\"\"\n", "\n", " self.stepping = True\n", " self.interact = False" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.797798Z", "iopub.status.busy": "2023-11-12T12:39:53.797671Z", "iopub.status.idle": "2023-11-12T12:39:53.799807Z", "shell.execute_reply": "2023-11-12T12:39:53.799451Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "class Debugger(Debugger):\n", " def continue_command(self, arg: str = \"\") -> None:\n", " \"\"\"Resume execution\"\"\"\n", "\n", " self.stepping = False\n", " self.interact = False" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "The `execute()` method dispatches between these two." ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.801427Z", "iopub.status.busy": "2023-11-12T12:39:53.801293Z", "iopub.status.idle": "2023-11-12T12:39:53.803308Z", "shell.execute_reply": "2023-11-12T12:39:53.803050Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class Debugger(Debugger):\n", " def execute(self, command: str) -> None:\n", " if command.startswith('s'):\n", " self.step_command()\n", " elif command.startswith('c'):\n", " self.continue_command()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Our debugger is now ready to run! Let us invoke it on the buggy `remove_html_markup()` variant from the [Introduction to Debugging](Intro_Debugging.ipynb):" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.804837Z", "iopub.status.busy": "2023-11-12T12:39:53.804726Z", "iopub.status.idle": "2023-11-12T12:39:53.807177Z", "shell.execute_reply": "2023-11-12T12:39:53.806919Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "def remove_html_markup(s): # type: ignore\n", " tag = False\n", " quote = False\n", " out = \"\"\n", "\n", " for c in s:\n", " if c == '<' and not quote:\n", " tag = True\n", " elif c == '>' and not quote:\n", " tag = False\n", " elif c == '\"' or c == \"'\" and tag:\n", " quote = not quote\n", " elif not tag:\n", " out = out + c\n", "\n", " return out" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "We invoke the debugger just like `Tracer`, using a `with` clause. The code\n", "\n", "```python\n", "with Debugger():\n", " remove_html_markup('abc')\n", "```\n", "gives us a debugger prompt\n", "```\n", "(debugger) _\n", "```\n", "where we can enter one of our two commands." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Let us do two steps through the program and then resume execution:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.808788Z", "iopub.status.busy": "2023-11-12T12:39:53.808682Z", "iopub.status.idle": "2023-11-12T12:39:53.810392Z", "shell.execute_reply": "2023-11-12T12:39:53.810121Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from bookutils import input, next_inputs" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.811920Z", "iopub.status.busy": "2023-11-12T12:39:53.811807Z", "iopub.status.idle": "2023-11-12T12:39:53.814157Z", "shell.execute_reply": "2023-11-12T12:39:53.813845Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ "['step', 'step', 'continue']" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# ignore\n", "next_inputs([\"step\", \"step\", \"continue\"])" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.816490Z", "iopub.status.busy": "2023-11-12T12:39:53.816247Z", "iopub.status.idle": "2023-11-12T12:39:53.865273Z", "shell.execute_reply": "2023-11-12T12:39:53.864997Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Calling remove_html_markup(s = 'abc')\n" ] }, { "data": { "text/html": [ "(debugger) step" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "2 tag = False\n" ] }, { "data": { "text/html": [ "(debugger) step" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ " # tag = False\n", "3 quote = False\n" ] }, { "data": { "text/html": [ "(debugger) continue" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "with Debugger():\n", " remove_html_markup('abc')" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.866808Z", "iopub.status.busy": "2023-11-12T12:39:53.866694Z", "iopub.status.idle": "2023-11-12T12:39:53.868271Z", "shell.execute_reply": "2023-11-12T12:39:53.868027Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "# docassert\n", "assert not next_inputs()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Try this out for yourself by running the above invocation in the interactive notebook! If you are reading the Web version, the top menu entry `Resources` -> `Edit as Notebook` will do the trick. Navigate to the above invocation and press `Shift`+`Enter`." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### A Command Dispatcher" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Our `execute()` function is still a bit rudimentary. A true command-line tool should provide means to tell which commands are available (`help`), automatically split arguments, and not stand in line of extensibility." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "We therefore implement a better `execute()` method which does all that. Our revised `execute()` method _inspects_ its class for methods that end in `_command()`, and automatically registers their names as commands. Hence, with the above, we already get `step` and `continue` as possible commands." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### Excursion: Implementing execute()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Let us detail how we implement `execute()`. The `commands()` method returns a list of all commands (as strings) from the class." ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.869914Z", "iopub.status.busy": "2023-11-12T12:39:53.869803Z", "iopub.status.idle": "2023-11-12T12:39:53.871871Z", "shell.execute_reply": "2023-11-12T12:39:53.871604Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "class Debugger(Debugger):\n", " def commands(self) -> List[str]:\n", " \"\"\"Return a list of commands\"\"\"\n", "\n", " cmds = [method.replace('_command', '')\n", " for method in dir(self.__class__)\n", " if method.endswith('_command')]\n", " cmds.sort()\n", " return cmds" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.873454Z", "iopub.status.busy": "2023-11-12T12:39:53.873308Z", "iopub.status.idle": "2023-11-12T12:39:53.875674Z", "shell.execute_reply": "2023-11-12T12:39:53.875359Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "['continue', 'step']" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "d = Debugger()\n", "d.commands()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "The `command_method()` method converts a given command (or its abbrevation) into a method to be called." ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.877494Z", "iopub.status.busy": "2023-11-12T12:39:53.877362Z", "iopub.status.idle": "2023-11-12T12:39:53.880224Z", "shell.execute_reply": "2023-11-12T12:39:53.879967Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class Debugger(Debugger):\n", " def help_command(self, command: str) -> None:\n", " ...\n", "\n", " def command_method(self, command: str) -> Optional[Callable[[str], None]]:\n", " \"\"\"Convert `command` into the method to be called.\n", " If the method is not found, return `None` instead.\"\"\"\n", "\n", " if command.startswith('#'):\n", " return None # Comment\n", "\n", " possible_cmds = [possible_cmd for possible_cmd in self.commands()\n", " if possible_cmd.startswith(command)]\n", " if len(possible_cmds) != 1:\n", " self.help_command(command)\n", " return None\n", "\n", " cmd = possible_cmds[0]\n", " return getattr(self, cmd + '_command')" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.881753Z", "iopub.status.busy": "2023-11-12T12:39:53.881629Z", "iopub.status.idle": "2023-11-12T12:39:53.883938Z", "shell.execute_reply": "2023-11-12T12:39:53.883667Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/plain": [ ">" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "d = Debugger()\n", "d.command_method(\"step\")" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.885360Z", "iopub.status.busy": "2023-11-12T12:39:53.885256Z", "iopub.status.idle": "2023-11-12T12:39:53.887584Z", "shell.execute_reply": "2023-11-12T12:39:53.887285Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ ">" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "d = Debugger()\n", "d.command_method(\"s\")" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "The revised `execute()` method now determines this method and executes it with the given argument." ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.889048Z", "iopub.status.busy": "2023-11-12T12:39:53.888939Z", "iopub.status.idle": "2023-11-12T12:39:53.891196Z", "shell.execute_reply": "2023-11-12T12:39:53.890940Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class Debugger(Debugger):\n", " def execute(self, command: str) -> None:\n", " \"\"\"Execute `command`\"\"\"\n", "\n", " sep = command.find(' ')\n", " if sep > 0:\n", " cmd = command[:sep].strip()\n", " arg = command[sep + 1:].strip()\n", " else:\n", " cmd = command.strip()\n", " arg = \"\"\n", "\n", " method = self.command_method(cmd)\n", " if method:\n", " method(arg)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "If `command_method()` cannot find the command, or finds more than one matching the prefix, it invokes the `help` command providing additional assistance. `help` draws extra info on each command from its documentation string." ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.892670Z", "iopub.status.busy": "2023-11-12T12:39:53.892566Z", "iopub.status.idle": "2023-11-12T12:39:53.895128Z", "shell.execute_reply": "2023-11-12T12:39:53.894852Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class Debugger(Debugger):\n", " def help_command(self, command: str = \"\") -> None:\n", " \"\"\"Give help on given `command`. If no command is given, give help on all\"\"\"\n", "\n", " if command:\n", " possible_cmds = [possible_cmd for possible_cmd in self.commands()\n", " if possible_cmd.startswith(command)]\n", "\n", " if len(possible_cmds) == 0:\n", " self.log(f\"Unknown command {repr(command)}. Possible commands are:\")\n", " possible_cmds = self.commands()\n", " elif len(possible_cmds) > 1:\n", " self.log(f\"Ambiguous command {repr(command)}. Possible expansions are:\")\n", " else:\n", " possible_cmds = self.commands()\n", "\n", " for cmd in possible_cmds:\n", " method = self.command_method(cmd)\n", " self.log(f\"{cmd:10} -- {method.__doc__}\")" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.896532Z", "iopub.status.busy": "2023-11-12T12:39:53.896427Z", "iopub.status.idle": "2023-11-12T12:39:53.898767Z", "shell.execute_reply": "2023-11-12T12:39:53.898484Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "continue -- Resume execution\n", "help -- Give help on given `command`. If no command is given, give help on all\n", "step -- Execute up to the next line\n" ] } ], "source": [ "d = Debugger()\n", "d.execute(\"help\")" ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.900507Z", "iopub.status.busy": "2023-11-12T12:39:53.900403Z", "iopub.status.idle": "2023-11-12T12:39:53.902867Z", "shell.execute_reply": "2023-11-12T12:39:53.902625Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Unknown command 'foo'. Possible commands are:\n", "continue -- Resume execution\n", "help -- Give help on given `command`. If no command is given, give help on all\n", "step -- Execute up to the next line\n" ] } ], "source": [ "d = Debugger()\n", "d.execute(\"foo\")" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### End of Excursion" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Printing Values\n", "\n", "With `execute()`, we can now easily extend our class – all it takes is for a new command `NAME` is a new `NAME_command()` method. Let us start by providing a `print` command to print all variables. We use similar code as for the `Tracer` class in the [chapter on tracing](Tracer.ipynb)." ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.904775Z", "iopub.status.busy": "2023-11-12T12:39:53.904670Z", "iopub.status.idle": "2023-11-12T12:39:53.906652Z", "shell.execute_reply": "2023-11-12T12:39:53.906424Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "class Debugger(Debugger):\n", " def print_command(self, arg: str = \"\") -> None:\n", " \"\"\"Print an expression. If no expression is given, print all variables\"\"\"\n", "\n", " vars = self.local_vars\n", " self.log(\"\\n\".join([f\"{var} = {repr(value)}\" for var, value in vars.items()]))" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.908006Z", "iopub.status.busy": "2023-11-12T12:39:53.907901Z", "iopub.status.idle": "2023-11-12T12:39:53.909610Z", "shell.execute_reply": "2023-11-12T12:39:53.909349Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "# ignore\n", "next_inputs([\"step\", \"step\", \"step\", \"print\", \"continue\"]);" ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.911307Z", "iopub.status.busy": "2023-11-12T12:39:53.911189Z", "iopub.status.idle": "2023-11-12T12:39:53.923342Z", "shell.execute_reply": "2023-11-12T12:39:53.923073Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Calling remove_html_markup(s = 'abc')\n" ] }, { "data": { "text/html": [ "(debugger) step" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "2 tag = False\n" ] }, { "data": { "text/html": [ "(debugger) step" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ " # tag = False\n", "3 quote = False\n" ] }, { "data": { "text/html": [ "(debugger) step" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ " # quote = False\n", "4 out = \"\"\n" ] }, { "data": { "text/html": [ "(debugger) print" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "s = 'abc'\n", "tag = False\n", "quote = False\n" ] }, { "data": { "text/html": [ "(debugger) continue" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "with Debugger():\n", " remove_html_markup('abc')" ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.925103Z", "iopub.status.busy": "2023-11-12T12:39:53.924927Z", "iopub.status.idle": "2023-11-12T12:39:53.926627Z", "shell.execute_reply": "2023-11-12T12:39:53.926397Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "# docassert\n", "assert not next_inputs()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Let us extend `print` such that if an argument is given, it only evaluates and prints out this argument." ] }, { "cell_type": "code", "execution_count": 32, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.928543Z", "iopub.status.busy": "2023-11-12T12:39:53.928371Z", "iopub.status.idle": "2023-11-12T12:39:53.931196Z", "shell.execute_reply": "2023-11-12T12:39:53.930916Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class Debugger(Debugger):\n", " def print_command(self, arg: str = \"\") -> None:\n", " \"\"\"Print an expression. If no expression is given, print all variables\"\"\"\n", "\n", " vars = self.local_vars\n", "\n", " if not arg:\n", " self.log(\"\\n\".join([f\"{var} = {repr(value)}\" for var, value in vars.items()]))\n", " else:\n", " try:\n", " self.log(f\"{arg} = {repr(eval(arg, globals(), vars))}\")\n", " except Exception as err:\n", " self.log(f\"{err.__class__.__name__}: {err}\")" ] }, { "cell_type": "code", "execution_count": 33, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.932852Z", "iopub.status.busy": "2023-11-12T12:39:53.932641Z", "iopub.status.idle": "2023-11-12T12:39:53.934396Z", "shell.execute_reply": "2023-11-12T12:39:53.934117Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "# ignore\n", "next_inputs([\"p s\", \"c\"]);" ] }, { "cell_type": "code", "execution_count": 34, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.936005Z", "iopub.status.busy": "2023-11-12T12:39:53.935823Z", "iopub.status.idle": "2023-11-12T12:39:53.939364Z", "shell.execute_reply": "2023-11-12T12:39:53.938980Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Calling remove_html_markup(s = 'abc')\n" ] }, { "data": { "text/html": [ "(debugger) p s" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "s = 'abc'\n" ] }, { "data": { "text/html": [ "(debugger) c" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "with Debugger():\n", " remove_html_markup('abc')" ] }, { "cell_type": "code", "execution_count": 35, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.940995Z", "iopub.status.busy": "2023-11-12T12:39:53.940898Z", "iopub.status.idle": "2023-11-12T12:39:53.942507Z", "shell.execute_reply": "2023-11-12T12:39:53.942245Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "# docassert\n", "assert not next_inputs()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Note how we would abbreviate commands to speed things up. The argument to `print` can be any Python expression:" ] }, { "cell_type": "code", "execution_count": 36, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.944011Z", "iopub.status.busy": "2023-11-12T12:39:53.943904Z", "iopub.status.idle": "2023-11-12T12:39:53.945621Z", "shell.execute_reply": "2023-11-12T12:39:53.945314Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "# ignore\n", "next_inputs([\"print (s[0], 2 + 2)\", \"continue\"]);" ] }, { "cell_type": "code", "execution_count": 37, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.947083Z", "iopub.status.busy": "2023-11-12T12:39:53.946993Z", "iopub.status.idle": "2023-11-12T12:39:53.950374Z", "shell.execute_reply": "2023-11-12T12:39:53.950031Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Calling remove_html_markup(s = 'abc')\n" ] }, { "data": { "text/html": [ "(debugger) print (s[0], 2 + 2)" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "(s[0], 2 + 2) = ('a', 4)\n" ] }, { "data": { "text/html": [ "(debugger) continue" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "with Debugger():\n", " remove_html_markup('abc')" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Our `help` command also properly lists `print` as a possible command:" ] }, { "cell_type": "code", "execution_count": 38, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.952004Z", "iopub.status.busy": "2023-11-12T12:39:53.951858Z", "iopub.status.idle": "2023-11-12T12:39:53.953471Z", "shell.execute_reply": "2023-11-12T12:39:53.953221Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "# ignore\n", "next_inputs([\"help print\", \"continue\"]);" ] }, { "cell_type": "code", "execution_count": 39, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.954844Z", "iopub.status.busy": "2023-11-12T12:39:53.954754Z", "iopub.status.idle": "2023-11-12T12:39:53.958160Z", "shell.execute_reply": "2023-11-12T12:39:53.957893Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Calling remove_html_markup(s = 'abc')\n" ] }, { "data": { "text/html": [ "(debugger) help print" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "print -- Print an expression. If no expression is given, print all variables\n" ] }, { "data": { "text/html": [ "(debugger) continue" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "with Debugger():\n", " remove_html_markup('abc')" ] }, { "cell_type": "code", "execution_count": 40, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.959523Z", "iopub.status.busy": "2023-11-12T12:39:53.959439Z", "iopub.status.idle": "2023-11-12T12:39:53.961146Z", "shell.execute_reply": "2023-11-12T12:39:53.960895Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "# docassert\n", "assert not next_inputs()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Listing Source Code\n", "\n", "We implement a `list` command that shows the source code of the current function." ] }, { "cell_type": "code", "execution_count": 41, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.962662Z", "iopub.status.busy": "2023-11-12T12:39:53.962556Z", "iopub.status.idle": "2023-11-12T12:39:53.964103Z", "shell.execute_reply": "2023-11-12T12:39:53.963853Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import inspect" ] }, { "cell_type": "code", "execution_count": 42, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.965645Z", "iopub.status.busy": "2023-11-12T12:39:53.965540Z", "iopub.status.idle": "2023-11-12T12:39:53.967211Z", "shell.execute_reply": "2023-11-12T12:39:53.966936Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from bookutils import getsourcelines # like inspect.getsourcelines(), but in color" ] }, { "cell_type": "code", "execution_count": 43, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.968846Z", "iopub.status.busy": "2023-11-12T12:39:53.968647Z", "iopub.status.idle": "2023-11-12T12:39:53.970817Z", "shell.execute_reply": "2023-11-12T12:39:53.970562Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "class Debugger(Debugger):\n", " def list_command(self, arg: str = \"\") -> None:\n", " \"\"\"Show current function.\"\"\"\n", "\n", " source_lines, line_number = getsourcelines(self.frame.f_code)\n", "\n", " for line in source_lines:\n", " self.log(f'{line_number:4} {line}', end='')\n", " line_number += 1" ] }, { "cell_type": "code", "execution_count": 44, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.972287Z", "iopub.status.busy": "2023-11-12T12:39:53.972181Z", "iopub.status.idle": "2023-11-12T12:39:53.973713Z", "shell.execute_reply": "2023-11-12T12:39:53.973480Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "# ignore\n", "next_inputs([\"list\", \"continue\"]);" ] }, { "cell_type": "code", "execution_count": 45, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:53.975092Z", "iopub.status.busy": "2023-11-12T12:39:53.975002Z", "iopub.status.idle": "2023-11-12T12:39:54.047923Z", "shell.execute_reply": "2023-11-12T12:39:54.047524Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Calling remove_html_markup(s = 'abc')\n" ] }, { "data": { "text/html": [ "(debugger) list" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ " 1 \u001b[34mdef\u001b[39;49;00m \u001b[32mremove_html_markup\u001b[39;49;00m(s): \u001b[37m# type: ignore\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", " 2 tag = \u001b[34mFalse\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", " 3 quote = \u001b[34mFalse\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", " 4 out = \u001b[33m\"\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", " 5 \u001b[37m\u001b[39;49;00m\n", " 6 \u001b[34mfor\u001b[39;49;00m c \u001b[35min\u001b[39;49;00m s:\u001b[37m\u001b[39;49;00m\n", " 7 \u001b[34mif\u001b[39;49;00m c == \u001b[33m'\u001b[39;49;00m\u001b[33m<\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m \u001b[35mand\u001b[39;49;00m \u001b[35mnot\u001b[39;49;00m quote:\u001b[37m\u001b[39;49;00m\n", " 8 tag = \u001b[34mTrue\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", " 9 \u001b[34melif\u001b[39;49;00m c == \u001b[33m'\u001b[39;49;00m\u001b[33m>\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m \u001b[35mand\u001b[39;49;00m \u001b[35mnot\u001b[39;49;00m quote:\u001b[37m\u001b[39;49;00m\n", " 10 tag = \u001b[34mFalse\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", " 11 \u001b[34melif\u001b[39;49;00m c == \u001b[33m'\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m \u001b[35mor\u001b[39;49;00m c == \u001b[33m\"\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m \u001b[35mand\u001b[39;49;00m tag:\u001b[37m\u001b[39;49;00m\n", " 12 quote = \u001b[35mnot\u001b[39;49;00m quote\u001b[37m\u001b[39;49;00m\n", " 13 \u001b[34melif\u001b[39;49;00m \u001b[35mnot\u001b[39;49;00m tag:\u001b[37m\u001b[39;49;00m\n", " 14 out = out + c\u001b[37m\u001b[39;49;00m\n", " 15 \u001b[37m\u001b[39;49;00m\n", " 16 \u001b[34mreturn\u001b[39;49;00m out\u001b[37m\u001b[39;49;00m\n" ] }, { "data": { "text/html": [ "(debugger) continue" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "with Debugger():\n", " remove_html_markup('abc')" ] }, { "cell_type": "code", "execution_count": 46, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.053087Z", "iopub.status.busy": "2023-11-12T12:39:54.052929Z", "iopub.status.idle": "2023-11-12T12:39:54.054815Z", "shell.execute_reply": "2023-11-12T12:39:54.054460Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "# docassert\n", "assert not next_inputs()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Setting Breakpoints\n", "\n", "Stepping through the program line by line is a bit cumbersome. We therefore implement _breakpoints_ – a set of lines that cause the program to be interrupted as soon as this line is met." ] }, { "cell_type": "code", "execution_count": 47, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.056670Z", "iopub.status.busy": "2023-11-12T12:39:54.056545Z", "iopub.status.idle": "2023-11-12T12:39:54.058855Z", "shell.execute_reply": "2023-11-12T12:39:54.058548Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "class Debugger(Debugger):\n", " def break_command(self, arg: str = \"\") -> None:\n", " \"\"\"Set a breakoint in given line. If no line is given, list all breakpoints\"\"\"\n", "\n", " if arg:\n", " self.breakpoints.add(int(arg))\n", " self.log(\"Breakpoints:\", self.breakpoints)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Here's an example, setting a breakpoint at the end of the loop:" ] }, { "cell_type": "code", "execution_count": 48, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.060536Z", "iopub.status.busy": "2023-11-12T12:39:54.060417Z", "iopub.status.idle": "2023-11-12T12:39:54.062974Z", "shell.execute_reply": "2023-11-12T12:39:54.062668Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "# ignore\n", "_, remove_html_markup_starting_line_number = \\\n", " inspect.getsourcelines(remove_html_markup)\n", "next_inputs([f\"break {remove_html_markup_starting_line_number + 13}\",\n", " \"continue\", \"print\", \"continue\", \"continue\", \"continue\"]);" ] }, { "cell_type": "code", "execution_count": 49, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.064622Z", "iopub.status.busy": "2023-11-12T12:39:54.064506Z", "iopub.status.idle": "2023-11-12T12:39:54.080874Z", "shell.execute_reply": "2023-11-12T12:39:54.080460Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Calling remove_html_markup(s = 'abc')\n" ] }, { "data": { "text/html": [ "(debugger) break 14" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Breakpoints: {14}\n" ] }, { "data": { "text/html": [ "(debugger) continue" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ " # tag = False, quote = False, out = '', c = 'a'\n", "14 out = out + c\n" ] }, { "data": { "text/html": [ "(debugger) print" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "s = 'abc'\n", "tag = False\n", "quote = False\n", "out = ''\n", "c = 'a'\n" ] }, { "data": { "text/html": [ "(debugger) continue" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ " # out = 'a', c = 'b'\n", "14 out = out + c\n" ] }, { "data": { "text/html": [ "(debugger) continue" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ " # out = 'ab', c = 'c'\n", "14 out = out + c\n" ] }, { "data": { "text/html": [ "(debugger) continue" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "with Debugger():\n", " remove_html_markup('abc')" ] }, { "cell_type": "code", "execution_count": 50, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.082703Z", "iopub.status.busy": "2023-11-12T12:39:54.082550Z", "iopub.status.idle": "2023-11-12T12:39:54.084132Z", "shell.execute_reply": "2023-11-12T12:39:54.083909Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "# docassert\n", "assert not next_inputs()" ] }, { "cell_type": "code", "execution_count": 51, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.085590Z", "iopub.status.busy": "2023-11-12T12:39:54.085507Z", "iopub.status.idle": "2023-11-12T12:39:54.086980Z", "shell.execute_reply": "2023-11-12T12:39:54.086724Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from bookutils import quiz" ] }, { "cell_type": "code", "execution_count": 52, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.088771Z", "iopub.status.busy": "2023-11-12T12:39:54.088636Z", "iopub.status.idle": "2023-11-12T12:39:54.093485Z", "shell.execute_reply": "2023-11-12T12:39:54.093196Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/html": [ "\n", " \n", " \n", " \n", "
\n", "

Quiz

\n", "

\n", "

What happens if we enter the command break 2 + 3?
\n", "

\n", "

\n", "

\n", " \n", " \n", "
\n", " \n", " \n", "
\n", " \n", " \n", "
\n", " \n", " \n", "
\n", " \n", "
\n", "

\n", " \n", " \n", "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 52, "metadata": {}, "output_type": "execute_result" } ], "source": [ "quiz(\"What happens if we enter the command `break 2 + 3`?\",\n", " [\n", " \"A breakpoint is set in Line 2.\",\n", " \"A breakpoint is set in Line 5.\",\n", " \"Two breakpoints are set in Lines 2 and 3.\",\n", " \"The debugger raises a `ValueError` exception.\"\n", " ], '12345 % 7')" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Try it out yourself by executing the above code block!" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Deleting Breakpoints\n", "\n", "To delete breakpoints, we introduce a `delete` command:" ] }, { "cell_type": "code", "execution_count": 53, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.095131Z", "iopub.status.busy": "2023-11-12T12:39:54.095009Z", "iopub.status.idle": "2023-11-12T12:39:54.097446Z", "shell.execute_reply": "2023-11-12T12:39:54.097153Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class Debugger(Debugger):\n", " def delete_command(self, arg: str = \"\") -> None:\n", " \"\"\"Delete breakoint in line given by `arg`.\n", " Without given line, clear all breakpoints\"\"\"\n", "\n", " if arg:\n", " try:\n", " self.breakpoints.remove(int(arg))\n", " except KeyError:\n", " self.log(f\"No such breakpoint: {arg}\")\n", " else:\n", " self.breakpoints = set()\n", " self.log(\"Breakpoints:\", self.breakpoints)" ] }, { "cell_type": "code", "execution_count": 54, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.098905Z", "iopub.status.busy": "2023-11-12T12:39:54.098796Z", "iopub.status.idle": "2023-11-12T12:39:54.100647Z", "shell.execute_reply": "2023-11-12T12:39:54.100354Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "# ignore\n", "next_inputs([f\"break {remove_html_markup_starting_line_number + 15}\",\n", " \"continue\", \"print\",\n", " f\"delete {remove_html_markup_starting_line_number + 15}\",\n", " \"continue\"]);" ] }, { "cell_type": "code", "execution_count": 55, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.102170Z", "iopub.status.busy": "2023-11-12T12:39:54.102052Z", "iopub.status.idle": "2023-11-12T12:39:54.110896Z", "shell.execute_reply": "2023-11-12T12:39:54.110538Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Calling remove_html_markup(s = 'abc')\n" ] }, { "data": { "text/html": [ "(debugger) break 16" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Breakpoints: {16}\n" ] }, { "data": { "text/html": [ "(debugger) continue" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ " # tag = False, quote = False, out = 'abc', c = 'c'\n", "16 return out\n" ] }, { "data": { "text/html": [ "(debugger) print" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "s = 'abc'\n", "tag = False\n", "quote = False\n", "out = 'abc'\n", "c = 'c'\n" ] }, { "data": { "text/html": [ "(debugger) delete 16" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Breakpoints: set()\n" ] }, { "data": { "text/html": [ "(debugger) continue" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "with Debugger():\n", " remove_html_markup('abc')" ] }, { "cell_type": "code", "execution_count": 56, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.112696Z", "iopub.status.busy": "2023-11-12T12:39:54.112600Z", "iopub.status.idle": "2023-11-12T12:39:54.114296Z", "shell.execute_reply": "2023-11-12T12:39:54.114008Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "# docassert\n", "assert not next_inputs()" ] }, { "cell_type": "code", "execution_count": 57, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.115903Z", "iopub.status.busy": "2023-11-12T12:39:54.115812Z", "iopub.status.idle": "2023-11-12T12:39:54.119224Z", "shell.execute_reply": "2023-11-12T12:39:54.118952Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/html": [ "\n", " \n", " \n", " \n", "
\n", "

Quiz

\n", "

\n", "

What does the command delete (without argument) do?
\n", "

\n", "

\n", "

\n", " \n", " \n", "
\n", " \n", " \n", "
\n", " \n", " \n", "
\n", " \n", " \n", "
\n", " \n", "
\n", "

\n", " \n", " \n", "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 57, "metadata": {}, "output_type": "execute_result" } ], "source": [ "quiz(\"What does the command `delete` (without argument) do?\",\n", " [\n", " \"It deletes all breakpoints\",\n", " \"It deletes the source code\",\n", " \"It lists all breakpoints\",\n", " \"It stops execution\"\n", " ],\n", " '[n for n in range(2 // 2, 2 * 2) if n % 2 / 2]'\n", " )" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Listings with Benefits\n", "\n", "Let us extend `list` a bit such that \n", "\n", "1. it can also list a given function, and \n", "2. it shows the current line (`>`) as well as breakpoints (`#`)" ] }, { "cell_type": "code", "execution_count": 58, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.121023Z", "iopub.status.busy": "2023-11-12T12:39:54.120855Z", "iopub.status.idle": "2023-11-12T12:39:54.124298Z", "shell.execute_reply": "2023-11-12T12:39:54.123948Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "class Debugger(Debugger):\n", " def list_command(self, arg: str = \"\") -> None:\n", " \"\"\"Show current function. If `arg` is given, show its source code.\"\"\"\n", "\n", " try:\n", " if arg:\n", " obj = eval(arg)\n", " source_lines, line_number = inspect.getsourcelines(obj)\n", " current_line = -1\n", " else:\n", " source_lines, line_number = \\\n", " getsourcelines(self.frame.f_code)\n", " current_line = self.frame.f_lineno\n", " except Exception as err:\n", " self.log(f\"{err.__class__.__name__}: {err}\")\n", " source_lines = []\n", " line_number = 0\n", "\n", " for line in source_lines:\n", " spacer = ' '\n", " if line_number == current_line:\n", " spacer = '>'\n", " elif line_number in self.breakpoints:\n", " spacer = '#'\n", " self.log(f'{line_number:4}{spacer} {line}', end='')\n", " line_number += 1" ] }, { "cell_type": "code", "execution_count": 59, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.126430Z", "iopub.status.busy": "2023-11-12T12:39:54.126276Z", "iopub.status.idle": "2023-11-12T12:39:54.129146Z", "shell.execute_reply": "2023-11-12T12:39:54.128812Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "# ignore\n", "_, remove_html_markup_starting_line_number = \\\n", " inspect.getsourcelines(remove_html_markup)\n", "next_inputs([f\"break {remove_html_markup_starting_line_number + 13}\",\n", " \"list\", \"continue\", \"delete\", \"list\", \"continue\"]);" ] }, { "cell_type": "code", "execution_count": 60, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.130652Z", "iopub.status.busy": "2023-11-12T12:39:54.130540Z", "iopub.status.idle": "2023-11-12T12:39:54.222381Z", "shell.execute_reply": "2023-11-12T12:39:54.222070Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Calling remove_html_markup(s = 'abc')\n" ] }, { "data": { "text/html": [ "(debugger) break 14" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Breakpoints: {14}\n" ] }, { "data": { "text/html": [ "(debugger) list" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ " 1> \u001b[34mdef\u001b[39;49;00m \u001b[32mremove_html_markup\u001b[39;49;00m(s): \u001b[37m# type: ignore\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", " 2 tag = \u001b[34mFalse\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", " 3 quote = \u001b[34mFalse\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", " 4 out = \u001b[33m\"\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", " 5 \u001b[37m\u001b[39;49;00m\n", " 6 \u001b[34mfor\u001b[39;49;00m c \u001b[35min\u001b[39;49;00m s:\u001b[37m\u001b[39;49;00m\n", " 7 \u001b[34mif\u001b[39;49;00m c == \u001b[33m'\u001b[39;49;00m\u001b[33m<\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m \u001b[35mand\u001b[39;49;00m \u001b[35mnot\u001b[39;49;00m quote:\u001b[37m\u001b[39;49;00m\n", " 8 tag = \u001b[34mTrue\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", " 9 \u001b[34melif\u001b[39;49;00m c == \u001b[33m'\u001b[39;49;00m\u001b[33m>\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m \u001b[35mand\u001b[39;49;00m \u001b[35mnot\u001b[39;49;00m quote:\u001b[37m\u001b[39;49;00m\n", " 10 tag = \u001b[34mFalse\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", " 11 \u001b[34melif\u001b[39;49;00m c == \u001b[33m'\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m \u001b[35mor\u001b[39;49;00m c == \u001b[33m\"\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m \u001b[35mand\u001b[39;49;00m tag:\u001b[37m\u001b[39;49;00m\n", " 12 quote = \u001b[35mnot\u001b[39;49;00m quote\u001b[37m\u001b[39;49;00m\n", " 13 \u001b[34melif\u001b[39;49;00m \u001b[35mnot\u001b[39;49;00m tag:\u001b[37m\u001b[39;49;00m\n", " 14# out = out + c\u001b[37m\u001b[39;49;00m\n", " 15 \u001b[37m\u001b[39;49;00m\n", " 16 \u001b[34mreturn\u001b[39;49;00m out\u001b[37m\u001b[39;49;00m\n" ] }, { "data": { "text/html": [ "(debugger) continue" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ " # tag = False, quote = False, out = '', c = 'a'\n", "14 out = out + c\n" ] }, { "data": { "text/html": [ "(debugger) delete" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Breakpoints: set()\n" ] }, { "data": { "text/html": [ "(debugger) list" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ " 1 \u001b[34mdef\u001b[39;49;00m \u001b[32mremove_html_markup\u001b[39;49;00m(s): \u001b[37m# type: ignore\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", " 2 tag = \u001b[34mFalse\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", " 3 quote = \u001b[34mFalse\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", " 4 out = \u001b[33m\"\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", " 5 \u001b[37m\u001b[39;49;00m\n", " 6 \u001b[34mfor\u001b[39;49;00m c \u001b[35min\u001b[39;49;00m s:\u001b[37m\u001b[39;49;00m\n", " 7 \u001b[34mif\u001b[39;49;00m c == \u001b[33m'\u001b[39;49;00m\u001b[33m<\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m \u001b[35mand\u001b[39;49;00m \u001b[35mnot\u001b[39;49;00m quote:\u001b[37m\u001b[39;49;00m\n", " 8 tag = \u001b[34mTrue\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", " 9 \u001b[34melif\u001b[39;49;00m c == \u001b[33m'\u001b[39;49;00m\u001b[33m>\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m \u001b[35mand\u001b[39;49;00m \u001b[35mnot\u001b[39;49;00m quote:\u001b[37m\u001b[39;49;00m\n", " 10 tag = \u001b[34mFalse\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", " 11 \u001b[34melif\u001b[39;49;00m c == \u001b[33m'\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m \u001b[35mor\u001b[39;49;00m c == \u001b[33m\"\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m \u001b[35mand\u001b[39;49;00m tag:\u001b[37m\u001b[39;49;00m\n", " 12 quote = \u001b[35mnot\u001b[39;49;00m quote\u001b[37m\u001b[39;49;00m\n", " 13 \u001b[34melif\u001b[39;49;00m \u001b[35mnot\u001b[39;49;00m tag:\u001b[37m\u001b[39;49;00m\n", " 14> out = out + c\u001b[37m\u001b[39;49;00m\n", " 15 \u001b[37m\u001b[39;49;00m\n", " 16 \u001b[34mreturn\u001b[39;49;00m out\u001b[37m\u001b[39;49;00m\n" ] }, { "data": { "text/html": [ "(debugger) continue" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "with Debugger():\n", " remove_html_markup('abc')" ] }, { "cell_type": "code", "execution_count": 61, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.227762Z", "iopub.status.busy": "2023-11-12T12:39:54.227438Z", "iopub.status.idle": "2023-11-12T12:39:54.229522Z", "shell.execute_reply": "2023-11-12T12:39:54.229191Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "# docassert\n", "assert not next_inputs()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### Quitting\n", "\n", "In the Python debugger interface, we can only observe, but not alter the control flow. To make sure we can always exit out of our debugging session, we introduce a `quit` command that deletes all breakpoints and resumes execution until the observed function finishes." ] }, { "cell_type": "code", "execution_count": 62, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.231764Z", "iopub.status.busy": "2023-11-12T12:39:54.231607Z", "iopub.status.idle": "2023-11-12T12:39:54.233878Z", "shell.execute_reply": "2023-11-12T12:39:54.233572Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "class Debugger(Debugger):\n", " def quit_command(self, arg: str = \"\") -> None:\n", " \"\"\"Finish execution\"\"\"\n", "\n", " self.breakpoints = set()\n", " self.stepping = False\n", " self.interact = False" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "With this, our command palette is pretty complete, and we can use our debugger to happily inspect Python executions." ] }, { "cell_type": "code", "execution_count": 63, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.235632Z", "iopub.status.busy": "2023-11-12T12:39:54.235356Z", "iopub.status.idle": "2023-11-12T12:39:54.237356Z", "shell.execute_reply": "2023-11-12T12:39:54.237034Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "# ignore\n", "next_inputs([\"help\", \"quit\"]);" ] }, { "cell_type": "code", "execution_count": 64, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.239112Z", "iopub.status.busy": "2023-11-12T12:39:54.238978Z", "iopub.status.idle": "2023-11-12T12:39:54.244841Z", "shell.execute_reply": "2023-11-12T12:39:54.244549Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Calling remove_html_markup(s = 'abc')\n" ] }, { "data": { "text/html": [ "(debugger) help" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "break -- Set a breakoint in given line. If no line is given, list all breakpoints\n", "continue -- Resume execution\n", "delete -- Delete breakoint in line given by `arg`.\n", " Without given line, clear all breakpoints\n", "help -- Give help on given `command`. If no command is given, give help on all\n", "list -- Show current function. If `arg` is given, show its source code.\n", "print -- Print an expression. If no expression is given, print all variables\n", "quit -- Finish execution\n", "step -- Execute up to the next line\n" ] }, { "data": { "text/html": [ "(debugger) quit" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "with Debugger():\n", " remove_html_markup('abc')" ] }, { "cell_type": "code", "execution_count": 65, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.248775Z", "iopub.status.busy": "2023-11-12T12:39:54.248635Z", "iopub.status.idle": "2023-11-12T12:39:54.250552Z", "shell.execute_reply": "2023-11-12T12:39:54.250206Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "# docassert\n", "assert not next_inputs()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Synopsis" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "This chapter provides an interactive debugger for Python functions. The debugger is invoked as\n", "\n", "```python\n", "with Debugger():\n", " function_to_be_observed()\n", " ...\n", "```\n", "While running, you can enter _debugger commands_ at the `(debugger)` prompt. Here's an example session:" ] }, { "cell_type": "code", "execution_count": 66, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.252088Z", "iopub.status.busy": "2023-11-12T12:39:54.251968Z", "iopub.status.idle": "2023-11-12T12:39:54.254249Z", "shell.execute_reply": "2023-11-12T12:39:54.253976Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "# ignore\n", "_, remove_html_markup_starting_line_number = \\\n", " inspect.getsourcelines(remove_html_markup)\n", "next_inputs([\"help\", f\"break {remove_html_markup_starting_line_number + 13}\",\n", " \"list\", \"continue\", \"step\", \"print out\", \"quit\"])\n", "pass" ] }, { "cell_type": "code", "execution_count": 67, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.255788Z", "iopub.status.busy": "2023-11-12T12:39:54.255680Z", "iopub.status.idle": "2023-11-12T12:39:54.310120Z", "shell.execute_reply": "2023-11-12T12:39:54.309753Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Calling remove_html_markup(s = 'abc')\n" ] }, { "data": { "text/html": [ "(debugger) help" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "break -- Set a breakoint in given line. If no line is given, list all breakpoints\n", "continue -- Resume execution\n", "delete -- Delete breakoint in line given by `arg`.\n", " Without given line, clear all breakpoints\n", "help -- Give help on given `command`. If no command is given, give help on all\n", "list -- Show current function. If `arg` is given, show its source code.\n", "print -- Print an expression. If no expression is given, print all variables\n", "quit -- Finish execution\n", "step -- Execute up to the next line\n" ] }, { "data": { "text/html": [ "(debugger) break 14" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Breakpoints: {14}\n" ] }, { "data": { "text/html": [ "(debugger) list" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ " 1> \u001b[34mdef\u001b[39;49;00m \u001b[32mremove_html_markup\u001b[39;49;00m(s): \u001b[37m# type: ignore\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", " 2 tag = \u001b[34mFalse\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", " 3 quote = \u001b[34mFalse\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", " 4 out = \u001b[33m\"\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", " 5 \u001b[37m\u001b[39;49;00m\n", " 6 \u001b[34mfor\u001b[39;49;00m c \u001b[35min\u001b[39;49;00m s:\u001b[37m\u001b[39;49;00m\n", " 7 \u001b[34mif\u001b[39;49;00m c == \u001b[33m'\u001b[39;49;00m\u001b[33m<\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m \u001b[35mand\u001b[39;49;00m \u001b[35mnot\u001b[39;49;00m quote:\u001b[37m\u001b[39;49;00m\n", " 8 tag = \u001b[34mTrue\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", " 9 \u001b[34melif\u001b[39;49;00m c == \u001b[33m'\u001b[39;49;00m\u001b[33m>\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m \u001b[35mand\u001b[39;49;00m \u001b[35mnot\u001b[39;49;00m quote:\u001b[37m\u001b[39;49;00m\n", " 10 tag = \u001b[34mFalse\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", " 11 \u001b[34melif\u001b[39;49;00m c == \u001b[33m'\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m \u001b[35mor\u001b[39;49;00m c == \u001b[33m\"\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m \u001b[35mand\u001b[39;49;00m tag:\u001b[37m\u001b[39;49;00m\n", " 12 quote = \u001b[35mnot\u001b[39;49;00m quote\u001b[37m\u001b[39;49;00m\n", " 13 \u001b[34melif\u001b[39;49;00m \u001b[35mnot\u001b[39;49;00m tag:\u001b[37m\u001b[39;49;00m\n", " 14# out = out + c\u001b[37m\u001b[39;49;00m\n", " 15 \u001b[37m\u001b[39;49;00m\n", " 16 \u001b[34mreturn\u001b[39;49;00m out\u001b[37m\u001b[39;49;00m\n" ] }, { "data": { "text/html": [ "(debugger) continue" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ " # tag = False, quote = False, out = '', c = 'a'\n", "14 out = out + c\n" ] }, { "data": { "text/html": [ "(debugger) step" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ " # out = 'a'\n", "6 for c in s:\n" ] }, { "data": { "text/html": [ "(debugger) print out" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "out = 'a'\n" ] }, { "data": { "text/html": [ "(debugger) quit" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "with Debugger():\n", " ret = remove_html_markup('abc')" ] }, { "cell_type": "code", "execution_count": 68, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.312115Z", "iopub.status.busy": "2023-11-12T12:39:54.311978Z", "iopub.status.idle": "2023-11-12T12:39:54.313607Z", "shell.execute_reply": "2023-11-12T12:39:54.313341Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "# docassert\n", "assert not next_inputs()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "The `Debugger` class can be easily extended in subclasses. A new method `NAME_command(self, arg)` will be invoked whenever a command named `NAME` is entered, with `arg` holding given command arguments (empty string if none)." ] }, { "cell_type": "code", "execution_count": 69, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.315228Z", "iopub.status.busy": "2023-11-12T12:39:54.315119Z", "iopub.status.idle": "2023-11-12T12:39:54.316574Z", "shell.execute_reply": "2023-11-12T12:39:54.316349Z" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "# ignore\n", "from ClassDiagram import display_class_hierarchy" ] }, { "cell_type": "code", "execution_count": 70, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.318862Z", "iopub.status.busy": "2023-11-12T12:39:54.318612Z", "iopub.status.idle": "2023-11-12T12:39:54.818301Z", "shell.execute_reply": "2023-11-12T12:39:54.817890Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Debugger\n", "\n", "\n", "Debugger\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "break_command()\n", "\n", "\n", "\n", "command_method()\n", "\n", "\n", "\n", "commands()\n", "\n", "\n", "\n", "continue_command()\n", "\n", "\n", "\n", "delete_command()\n", "\n", "\n", "\n", "execute()\n", "\n", "\n", "\n", "help_command()\n", "\n", "\n", "\n", "interaction_loop()\n", "\n", "\n", "\n", "list_command()\n", "\n", "\n", "\n", "print_command()\n", "\n", "\n", "\n", "quit_command()\n", "\n", "\n", "\n", "step_command()\n", "\n", "\n", "\n", "stop_here()\n", "\n", "\n", "\n", "traceit()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Tracer\n", "\n", "\n", "Tracer\n", "\n", "\n", "\n", "__enter__()\n", "\n", "\n", "\n", "__exit__()\n", "\n", "\n", "\n", "__init__()\n", "\n", "\n", "\n", "traceit()\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Debugger->Tracer\n", "\n", "\n", "\n", "\n", "\n", "StackInspector\n", "\n", "\n", "StackInspector\n", "\n", "\n", "\n", "_generated_function_cache\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Tracer->StackInspector\n", "\n", "\n", "\n", "\n", "\n", "Legend\n", "Legend\n", "• \n", "public_method()\n", "• \n", "private_method()\n", "• \n", "overloaded_method()\n", "Hover over names to see doc\n", "\n", "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 70, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# ignore\n", "display_class_hierarchy(Debugger, \n", " public_methods=[\n", " Tracer.__init__,\n", " Tracer.__enter__,\n", " Tracer.__exit__,\n", " Tracer.traceit,\n", " Debugger.__init__,\n", " ],\n", " project='debuggingbook')" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": true, "run_control": { "read_only": false }, "slideshow": { "slide_type": "slide" } }, "source": [ "## Lessons Learned\n", "\n", "* _Debugging hooks_ from interpreted languages allow for simple interactive debugging.\n", "* A command-line debugging framework can be very easily extended with additional functionality." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "slide" } }, "source": [ "## Next Steps\n", "\n", "In the next chapter, we will see how [assertions](Assertions.ipynb) check correctness at runtime." ] }, { "attachments": {}, "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Background\n", "\n", "The command-line interface in this chapter is modeled after [GDB, the GNU debugger](https://www.gnu.org/software/gdb/), whose interface in turn goes back to earlier command-line debuggers such as [dbx](https://en.wikipedia.org/wiki/Dbx_%28debugger%29). All modern debuggers build on the functionality and concepts realized in these debuggers, be it breakpoints, stepping through programs, or inspecting program state.\n", "\n", "The concept of time travel debugging (see the Exercises, below) has been invented (and reinvented) many times. One of the most impactful tools comes from King et al. \\cite{King2005}, integrating _a time-traveling virtual machine_ (TTVM) for debugging operating systems, integrated into GDB. The recent [record+replay \"rr\" debugger](https://rr-project.org) also implements time travel debugging on top of the GDB command line debugger; it is applicable for general-purpose programs and available as open source." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": true, "run_control": { "read_only": false }, "slideshow": { "slide_type": "slide" } }, "source": [ "## Exercises\n" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" }, "solution2": "hidden", "solution2_first": true }, "source": [ "### Exercise 1: Changing State\n", "\n", "Some Python implementations allow altering the state by assigning values to `frame.f_locals`. Implement a `assign VAR=VALUE` command that allows changing the value of (local) variable `VAR` to the new value `VALUE`.\n", "\n", "Note: As detailed in [this blog post](https://utcc.utoronto.ca/~cks/space/blog/python/FLocalsAndTraceFunctions), \n", "`frame.f_locals` is re-populated with every access, so assign to our local alias `self.local_vars` instead." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" }, "solution2": "hidden" }, "source": [ "**Solution.** Here is an `assign` command that gets things right on CPython." ] }, { "cell_type": "code", "execution_count": 71, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.820410Z", "iopub.status.busy": "2023-11-12T12:39:54.820251Z", "iopub.status.idle": "2023-11-12T12:39:54.823143Z", "shell.execute_reply": "2023-11-12T12:39:54.822853Z" }, "slideshow": { "slide_type": "skip" }, "solution2": "hidden" }, "outputs": [], "source": [ "class Debugger(Debugger):\n", " def assign_command(self, arg: str) -> None:\n", " \"\"\"Use as 'assign VAR=VALUE'. Assign VALUE to local variable VAR.\"\"\"\n", "\n", " sep = arg.find('=')\n", " if sep > 0:\n", " var = arg[:sep].strip()\n", " expr = arg[sep + 1:].strip()\n", " else:\n", " self.help_command(\"assign\")\n", " return\n", "\n", " vars = self.local_vars\n", " try:\n", " vars[var] = eval(expr, self.frame.f_globals, vars)\n", " except Exception as err:\n", " self.log(f\"{err.__class__.__name__}: {err}\")" ] }, { "cell_type": "code", "execution_count": 72, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.824827Z", "iopub.status.busy": "2023-11-12T12:39:54.824690Z", "iopub.status.idle": "2023-11-12T12:39:54.826615Z", "shell.execute_reply": "2023-11-12T12:39:54.826340Z" }, "slideshow": { "slide_type": "skip" }, "solution2": "hidden" }, "outputs": [], "source": [ "# ignore\n", "next_inputs([\"assign s = 'xyz'\", \"print\", \"step\", \"print\", \"step\",\n", " \"assign tag = True\", \"assign s = 'abc'\", \"print\",\n", " \"step\", \"print\", \"continue\"]);" ] }, { "cell_type": "code", "execution_count": 73, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.828432Z", "iopub.status.busy": "2023-11-12T12:39:54.828289Z", "iopub.status.idle": "2023-11-12T12:39:54.847334Z", "shell.execute_reply": "2023-11-12T12:39:54.847058Z" }, "slideshow": { "slide_type": "skip" }, "solution2": "hidden" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Calling remove_html_markup(s = 'abc')\n" ] }, { "data": { "text/html": [ "(debugger) assign s = 'xyz'" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "(debugger) print" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "s = 'xyz'\n" ] }, { "data": { "text/html": [ "(debugger) step" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ " # s = 'xyz'\n", "2 tag = False\n" ] }, { "data": { "text/html": [ "(debugger) print" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "s = 'xyz'\n" ] }, { "data": { "text/html": [ "(debugger) step" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ " # tag = False\n", "3 quote = False\n" ] }, { "data": { "text/html": [ "(debugger) assign tag = True" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "(debugger) assign s = 'abc'" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "(debugger) print" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "s = 'abc'\n", "tag = True\n" ] }, { "data": { "text/html": [ "(debugger) step" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ " # s = 'abc', tag = True, quote = False\n", "4 out = \"\"\n" ] }, { "data": { "text/html": [ "(debugger) print" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "s = 'abc'\n", "tag = True\n", "quote = False\n" ] }, { "data": { "text/html": [ "(debugger) continue" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "with Debugger():\n", " remove_html_markup('abc')" ] }, { "cell_type": "code", "execution_count": 74, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.849005Z", "iopub.status.busy": "2023-11-12T12:39:54.848868Z", "iopub.status.idle": "2023-11-12T12:39:54.850814Z", "shell.execute_reply": "2023-11-12T12:39:54.850401Z" }, "slideshow": { "slide_type": "skip" }, "solution2": "hidden" }, "outputs": [], "source": [ "# docassert\n", "assert not next_inputs()" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "### Exercise 2: More Commands\n", "\n", "Extending the `Debugger` class with extra features and commands is a breeze. The following commands are inspired from [the GNU command-line debugger (GDB)](https://www.gnu.org/software/gdb/):" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "#### Named breakpoints (\"break\")\n", "\n", "With `break FUNCTION` and `delete FUNCTION`, set and delete a breakpoint at `FUNCTION`." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "#### Step over functions (\"next\")\n", "\n", "When stopped at a function call, the `next` command should execute the entire call, stopping when the function returns. (In contrast, `step` stops at the first line of the function called.)" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "#### Print call stack (\"where\")\n", "\n", "Implement a `where` command that shows the stack of calling functions." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "#### Move up and down the call stack (\"up\" and \"down\")\n", "\n", "After entering the `up` command, explore the source and variables of the _calling_ function rather than the current function. Use `up` repeatedly to move further up the stack. `down` returns to the caller." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "#### Execute until line (\"until\")\n", "\n", "With `until LINE`, resume execution until a line greater than `LINE` is reached. If `LINE` is not given, resume execution until a line greater than the current is reached. This is useful to avoid stepping through multiple loop iterations." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "#### Execute until return (\"finish\")\n", "\n", "With `finish`, resume execution until the current function returns." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "#### Watchpoints (\"watch\")\n", "\n", "With `watch CONDITION`, stop execution as soon as `CONDITION` changes its value. (Use the code from our `EventTracer` class in the [chapter on Tracing](Tracer.ipynb).) `delete CONDITION` removes the watchpoint. Keep in mind that some variable names may not exist at all times." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "### Exercise 3: Time-Travel Debugging\n", "\n", "Rather than inspecting a function at the moment it executes, you can also _record_ the entire state (call stack, local variables, etc.) during execution, and then run an interactive session to step through the recorded execution. Your time travel debugger would be invoked as\n", "\n", "```python\n", "with TimeTravelDebugger():\n", " function_to_be_tracked()\n", " ...\n", "```\n", "\n", "The interaction then starts at the end of the `with` block." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "#### Part 1: Recording Values\n", "\n", "Start with a subclass of `Tracer` from the [chapter on tracing](Tracer.ipynb) (say, `TimeTravelTracer`) to execute a program while recording all values. Keep in mind that recording even only local variables at each step quickly consumes large amounts of memory. As an alternative, consider recording only _changes_ to variables, with the option to restore an entire state from a baseline and later changes." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "#### Part 2: Command Line Interface\n", "\n", "Create `TimeTravelDebugger` as subclass of both `TimeTravelTracer` and `Debugger` to provide a command line interface as with `Debugger`, including additional commands which get you back to earlier states:\n", "\n", "* `back` is like `step`, except that you go one line back\n", "* `restart` gets you to the beginning of the execution\n", "* `rewind` gets you to the beginning of the current function invocation" ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "subslide" } }, "source": [ "#### Part 3: Graphical User Interface\n", "\n", "Create `GUItimeTravelDebugger` to provide a _graphical user interface_ that allows you to explore a recorded execution, using HTML and JavaScript." ] }, { "cell_type": "markdown", "metadata": { "button": false, "new_sheet": false, "run_control": { "read_only": false }, "slideshow": { "slide_type": "fragment" } }, "source": [ "Here's a simple example to get you started. Assume you have recorded the following line numbers and variable values:" ] }, { "cell_type": "code", "execution_count": 75, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.853293Z", "iopub.status.busy": "2023-11-12T12:39:54.853026Z", "iopub.status.idle": "2023-11-12T12:39:54.855702Z", "shell.execute_reply": "2023-11-12T12:39:54.855465Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "recording: List[Tuple[int, Dict[str, Any]]] = [\n", " (10, {'x': 25}),\n", " (11, {'x': 25}),\n", " (12, {'x': 26, 'a': \"abc\"}),\n", " (13, {'x': 26, 'a': \"abc\"}),\n", " (10, {'x': 30}),\n", " (11, {'x': 30}),\n", " (12, {'x': 31, 'a': \"def\"}),\n", " (13, {'x': 31, 'a': \"def\"}),\n", " (10, {'x': 35}),\n", " (11, {'x': 35}),\n", " (12, {'x': 36, 'a': \"ghi\"}),\n", " (13, {'x': 36, 'a': \"ghi\"}),\n", "]" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "Then, the following function will provide a _slider_ that will allow you to explore these values:" ] }, { "cell_type": "code", "execution_count": 76, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.857501Z", "iopub.status.busy": "2023-11-12T12:39:54.857320Z", "iopub.status.idle": "2023-11-12T12:39:54.859127Z", "shell.execute_reply": "2023-11-12T12:39:54.858774Z" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "from bookutils import HTML" ] }, { "cell_type": "code", "execution_count": 77, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.860940Z", "iopub.status.busy": "2023-11-12T12:39:54.860748Z", "iopub.status.idle": "2023-11-12T12:39:54.863532Z", "shell.execute_reply": "2023-11-12T12:39:54.863275Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "def slider(rec: List[Tuple[int, Dict[str, Any]]]) -> str:\n", " lines_over_time = [line for (line, var) in rec]\n", " vars_over_time = []\n", " for (line, vars) in rec:\n", " vars_over_time.append(\", \".join(f\"{var} = {repr(value)}\"\n", " for var, value in vars.items()))\n", "\n", " # print(lines_over_time)\n", " # print(vars_over_time)\n", "\n", " template = f'''\n", "
\n", " \n", " Line {lines_over_time[0]}:\n", " {vars_over_time[0]}\n", "
\n", " \n", " '''\n", " # print(template)\n", " return HTML(template)" ] }, { "cell_type": "code", "execution_count": 78, "metadata": { "execution": { "iopub.execute_input": "2023-11-12T12:39:54.865010Z", "iopub.status.busy": "2023-11-12T12:39:54.864899Z", "iopub.status.idle": "2023-11-12T12:39:54.867110Z", "shell.execute_reply": "2023-11-12T12:39:54.866869Z" }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/html": [ "\n", "
\n", " \n", " Line 10:\n", " x = 25\n", "
\n", " \n", " " ], "text/plain": [ "" ] }, "execution_count": 78, "metadata": {}, "output_type": "execute_result" } ], "source": [ "slider(recording)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "Explore the HTML and JavaScript details of how `slider()` works, and then expand it to a user interface where you can\n", "\n", "* see the current source code (together with the line being executed)\n", "* search for specific events, such as a line being executed or a variable changing its value\n", "\n", "Just like `slider()`, your user interface should come in pure HTML and JavaScript such that it can run in a browser (or a Jupyter notebook) without interacting with a Python program." ] } ], "metadata": { "ipub": { "bibliography": "fuzzingbook.bib", "toc": true }, "kernelspec": { "display_name": "venv", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.2" }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": true, "sideBar": true, "skip_h1_title": true, "title_cell": "", "title_sidebar": "Contents", "toc_cell": false, "toc_position": {}, "toc_section_display": true, "toc_window_display": true }, "toc-autonumbering": false, "toc-showtags": false, "vscode": { "interpreter": { "hash": "0af4f07dd039d1b4e562c7a7d0340393b1c66f50605ac6af30beb81aa23b7ef5" } } }, "nbformat": 4, "nbformat_minor": 4 }