\n",
" "
],
"text/plain": [
""
]
},
"execution_count": 133,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"quiz(\"In the reduced version, what has changed?\",\n",
" [\n",
" \"Comments are deleted\",\n",
" \"Blank lines are deleted\",\n",
" \"Initializations are deleted\",\n",
" \"The assertion is deleted\",\n",
" ], '[(1 ** 0 - -1 ** 0) ** n for n in range(0, 3)]')"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Indeed, Delta Debugging has determined all these as being irrelevant for reproducing the failure – and consequently, has deleted them."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"### Reducing Code Characters"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"We can reduce the code further by removing individual _characters_ rather than lines. To this end, we convert our (already reduced) `remove_html_markup()` code into a list of characters."
]
},
{
"cell_type": "code",
"execution_count": 134,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:40.854152Z",
"iopub.status.busy": "2025-10-26T17:47:40.854070Z",
"iopub.status.idle": "2025-10-26T17:47:40.855890Z",
"shell.execute_reply": "2025-10-26T17:47:40.855626Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"['d', 'e', 'f', ' ', 'r', 'e', 'm', 'o', 'v', 'e', '_', 'h', 't', 'm', 'l', '_', 'm', 'a', 'r', 'k', 'u', 'p', '(', 's', ')', ':', ' ', ' ', '#', ' ']\n"
]
}
],
"source": [
"reduced_assertions_source_characters = list(\"\".join(reduced_assertions_source_lines))\n",
"print(reduced_assertions_source_characters[:30])"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Our `compile_and_test_html_markup()` works (and fails) as before: It still joins the given strings into one and executes them. (Remember that in Python, \"characters\" are simply strings of length one.)"
]
},
{
"cell_type": "code",
"execution_count": 135,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:40.857692Z",
"iopub.status.busy": "2025-10-26T17:47:40.857530Z",
"iopub.status.idle": "2025-10-26T17:47:40.859859Z",
"shell.execute_reply": "2025-10-26T17:47:40.859601Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"Traceback (most recent call last):\n",
" File \"/var/folders/n2/xd9445p97rb3xh7m1dfx8_4h0006ts/T/ipykernel_51600/909985861.py\", line 2, in \n",
" compile_and_test_html_markup(reduced_assertions_source_characters)\n",
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
" File \"/var/folders/n2/xd9445p97rb3xh7m1dfx8_4h0006ts/T/ipykernel_51600/4067432722.py\", line 2, in compile_and_test_html_markup\n",
" compile_and_run(lines +\n",
" ~~~~~~~~~~~~~~~^^^^^^^^\n",
" [\n",
" ^\n",
" ...<3 lines>...\n",
" '''assert remove_html_markup('\"foo\"') == '\"foo\"', \"My Test\"\\n'''\n",
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
" ])\n",
" ^^\n",
" File \"/var/folders/n2/xd9445p97rb3xh7m1dfx8_4h0006ts/T/ipykernel_51600/2075409738.py\", line 3, in compile_and_run\n",
" exec(\"\".join(lines), {'__name__': '',\n",
" ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
" '__package__': 'debuggingbook',\n",
" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
" ...<3 lines>...\n",
" 'Optional': Optional},\n",
" ^^^^^^^^^^^^^^^^^^^^^^\n",
" {})\n",
" ^^^\n",
" File \"\", line 17, in \n",
"AssertionError: My Test (expected)\n"
]
}
],
"source": [
"with ExpectError(AssertionError):\n",
" compile_and_test_html_markup(reduced_assertions_source_characters)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Let's see what Delta Debugging makes of that – and also, how long it takes. The `Timer` class gives us a simple means to measure time."
]
},
{
"cell_type": "code",
"execution_count": 136,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:40.861152Z",
"iopub.status.busy": "2025-10-26T17:47:40.861055Z",
"iopub.status.idle": "2025-10-26T17:47:40.862455Z",
"shell.execute_reply": "2025-10-26T17:47:40.862219Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"from Timer import Timer"
]
},
{
"cell_type": "code",
"execution_count": 137,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:40.863653Z",
"iopub.status.busy": "2025-10-26T17:47:40.863572Z",
"iopub.status.idle": "2025-10-26T17:47:40.865486Z",
"shell.execute_reply": "2025-10-26T17:47:40.865252Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"with DeltaDebugger(log=False) as dd:\n",
" compile_and_test_html_markup(reduced_assertions_source_characters)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Here's the reduced result:"
]
},
{
"cell_type": "code",
"execution_count": 138,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:40.866611Z",
"iopub.status.busy": "2025-10-26T17:47:40.866535Z",
"iopub.status.idle": "2025-10-26T17:47:41.117899Z",
"shell.execute_reply": "2025-10-26T17:47:41.117645Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[34mdef\u001b[39;49;00m\u001b[37m \u001b[39;49;00m\u001b[32mremove_html_markup\u001b[39;49;00m(s):\u001b[37m\u001b[39;49;00m\n",
" tag=\u001b[34mFalse\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
" quote=\u001b[34mFalse\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
" out=\u001b[33m\"\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
" \u001b[34mfor\u001b[39;49;00m c \u001b[35min\u001b[39;49;00m s:\u001b[37m\u001b[39;49;00m\n",
" \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:tag=\u001b[34mTrue\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
" \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:tag=\u001b[34mFalse\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
" \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[35mand\u001b[39;49;00m g:\u001b[35mnot\u001b[39;49;00m quote\u001b[37m\u001b[39;49;00m\n",
" \u001b[34melif\u001b[39;49;00m \u001b[35mnot\u001b[39;49;00m tag:out=out+c\u001b[37m\u001b[39;49;00m\n",
" \u001b[34mreturn\u001b[39;49;00m out\u001b[37m\u001b[39;49;00m"
]
}
],
"source": [
"with Timer() as t:\n",
" further_reduced_assertions_source_characters = dd.min_args()['lines']\n",
"print_content(\"\".join(further_reduced_assertions_source_characters), \".py\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"There's a number of observations we can make about this code.\n",
"\n",
"* All superfluous blanks and even newlines have been removed.\n",
"* As a curiosity, the initialization of `quote` and `out` to `\"\"` is now merged into a single (semantics-preserving) statement.\n",
"* The semantics and effect of `<` and `>` characters is preserved, as mandated by our `RuntimeError` check.\n",
"* Double quotes still have the effect of not being included in the returned value: the remaining `quote` has no effect.\n",
"\n",
"Semantics-wise, this reduced variant still yields the \"original\" failure; the biggest semantic differences, though, are in the condition and code associated with double quotes – which actually also is the location of the defect to be fixed. This is how reducing code can also point to not only _necessary_ locations, but also _defective_ locations."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Mind you that reducing code is not cheap, and especially not if you remove by characters. It has taken `DeltaDebugger` several thousand tests to obtain the result above:"
]
},
{
"cell_type": "code",
"execution_count": 139,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:41.119357Z",
"iopub.status.busy": "2025-10-26T17:47:41.119278Z",
"iopub.status.idle": "2025-10-26T17:47:41.121189Z",
"shell.execute_reply": "2025-10-26T17:47:41.120989Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"1278"
]
},
"execution_count": 139,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"dd.tests"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"And to do so, it even required _several seconds_. This may be little for a human, but from a CPU standpoint, this is an enormous effort."
]
},
{
"cell_type": "code",
"execution_count": 140,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:41.122447Z",
"iopub.status.busy": "2025-10-26T17:47:41.122362Z",
"iopub.status.idle": "2025-10-26T17:47:41.124226Z",
"shell.execute_reply": "2025-10-26T17:47:41.124017Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"0.2381666250003036"
]
},
"execution_count": 140,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"t.elapsed_time()"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"### Reducing Syntax Trees"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"When reducing code (or generally speaking, recursive structures), using a _syntactic_ approach can be a much better alternative to the _line-by-line_ or _character-by-character_ approaches discussed above. The idea is that one represents the input as a _tree_ (rather than a sequence of strings), in which a reducer would work on entire subtrees, deleting or reducing parts of the tree."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"We illustrate this concept on _syntax trees_ representing Python code. Python provides us with simple means to interactively convert code into syntax trees (and back again). So, in order to reduce code, we can\n",
"\n",
"1. _parse_ the program code into a syntax tree (called *abstract syntax tree* or *AST*);\n",
"2. reduce the syntax tree to a minimum, executing it to test reductions; and\n",
"3. _unparse_ the tree to obtain textual code again.\n",
"\n",
"Since transformations on the AST are much less likely to produce syntax errors, reducing ASTs is much more efficient than reducing program code as text."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"In the [chapter on slicing](Slicer.ipynb), we already have seen several examples on how to work with ASTs. In our context, an AST also offers additional possibilities for reducing. Notably, instead of just _deleting_ code fragments, we can also _replace_ them with simpler fragments. For instance, we can replace arithmetic expressions with constants, or conditional statements `if cond: body` with the associated body `body`."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Let us illustrate how this works, again choosing `remove_html_markup()` as our ongoing example. One more time, we create a function with associated test."
]
},
{
"cell_type": "code",
"execution_count": 141,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:41.125552Z",
"iopub.status.busy": "2025-10-26T17:47:41.125480Z",
"iopub.status.idle": "2025-10-26T17:47:41.127027Z",
"shell.execute_reply": "2025-10-26T17:47:41.126822Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"fun_source = inspect.getsource(remove_html_markup)"
]
},
{
"cell_type": "code",
"execution_count": 142,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:41.128224Z",
"iopub.status.busy": "2025-10-26T17:47:41.128154Z",
"iopub.status.idle": "2025-10-26T17:47:41.140376Z",
"shell.execute_reply": "2025-10-26T17:47:41.140164Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[34mdef\u001b[39;49;00m\u001b[37m \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",
" tag = \u001b[34mFalse\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
" quote = \u001b[34mFalse\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
" out = \u001b[33m\"\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
"\u001b[37m\u001b[39;49;00m\n",
" \u001b[34mfor\u001b[39;49;00m c \u001b[35min\u001b[39;49;00m s:\u001b[37m\u001b[39;49;00m\n",
" \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",
" tag = \u001b[34mTrue\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
" \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",
" tag = \u001b[34mFalse\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
" \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",
" quote = \u001b[35mnot\u001b[39;49;00m quote\u001b[37m\u001b[39;49;00m\n",
" \u001b[34melif\u001b[39;49;00m \u001b[35mnot\u001b[39;49;00m tag:\u001b[37m\u001b[39;49;00m\n",
" out = out + c\u001b[37m\u001b[39;49;00m\n",
"\u001b[37m\u001b[39;49;00m\n",
" \u001b[37m# postcondition\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
" \u001b[34massert\u001b[39;49;00m \u001b[33m'\u001b[39;49;00m\u001b[33m<\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m \u001b[35mnot\u001b[39;49;00m \u001b[35min\u001b[39;49;00m out \u001b[35mand\u001b[39;49;00m \u001b[33m'\u001b[39;49;00m\u001b[33m>\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m \u001b[35mnot\u001b[39;49;00m \u001b[35min\u001b[39;49;00m out\u001b[37m\u001b[39;49;00m\n",
"\u001b[37m\u001b[39;49;00m\n",
" \u001b[34mreturn\u001b[39;49;00m out\u001b[37m\u001b[39;49;00m"
]
}
],
"source": [
"print_content(fun_source, '.py')"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"#### From Code to Syntax Trees\n",
"\n",
"Let us parse this piece of code into an AST. This is done by the `ast.parse()` function."
]
},
{
"cell_type": "code",
"execution_count": 143,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:41.141628Z",
"iopub.status.busy": "2025-10-26T17:47:41.141551Z",
"iopub.status.idle": "2025-10-26T17:47:41.142942Z",
"shell.execute_reply": "2025-10-26T17:47:41.142734Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"import ast"
]
},
{
"cell_type": "code",
"execution_count": 144,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:41.144049Z",
"iopub.status.busy": "2025-10-26T17:47:41.143977Z",
"iopub.status.idle": "2025-10-26T17:47:41.145425Z",
"shell.execute_reply": "2025-10-26T17:47:41.145219Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"fun_tree: ast.Module = ast.parse(fun_source)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"The parsed tree contains the function definition:"
]
},
{
"cell_type": "code",
"execution_count": 145,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:41.146615Z",
"iopub.status.busy": "2025-10-26T17:47:41.146531Z",
"iopub.status.idle": "2025-10-26T17:47:41.147881Z",
"shell.execute_reply": "2025-10-26T17:47:41.147661Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"from bookutils import show_ast"
]
},
{
"cell_type": "code",
"execution_count": 146,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:41.149065Z",
"iopub.status.busy": "2025-10-26T17:47:41.148990Z",
"iopub.status.idle": "2025-10-26T17:47:41.532359Z",
"shell.execute_reply": "2025-10-26T17:47:41.532048Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"image/svg+xml": [
""
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"show_ast(fun_tree)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Let us add some tests to this, using the same scheme:"
]
},
{
"cell_type": "code",
"execution_count": 147,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:41.533996Z",
"iopub.status.busy": "2025-10-26T17:47:41.533873Z",
"iopub.status.idle": "2025-10-26T17:47:41.535521Z",
"shell.execute_reply": "2025-10-26T17:47:41.535291Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"test_source = (\n",
" '''if remove_html_markup('bar') != 'bar':\\n''' +\n",
" ''' raise RuntimeError(\"Missing functionality\")\\n''' +\n",
" '''assert remove_html_markup('\"foo\"') == '\"foo\"', \"My Test\"'''\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 148,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:41.536918Z",
"iopub.status.busy": "2025-10-26T17:47:41.536823Z",
"iopub.status.idle": "2025-10-26T17:47:41.538585Z",
"shell.execute_reply": "2025-10-26T17:47:41.538341Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"test_tree: ast.Module = ast.parse(test_source)"
]
},
{
"cell_type": "code",
"execution_count": 149,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:41.540032Z",
"iopub.status.busy": "2025-10-26T17:47:41.539933Z",
"iopub.status.idle": "2025-10-26T17:47:41.553371Z",
"shell.execute_reply": "2025-10-26T17:47:41.553061Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[34mif\u001b[39;49;00m remove_html_markup(\u001b[33m'\u001b[39;49;00m\u001b[33mbar\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m) != \u001b[33m'\u001b[39;49;00m\u001b[33mbar\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m:\u001b[37m\u001b[39;49;00m\n",
" \u001b[34mraise\u001b[39;49;00m \u001b[36mRuntimeError\u001b[39;49;00m(\u001b[33m'\u001b[39;49;00m\u001b[33mMissing functionality\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m)\u001b[37m\u001b[39;49;00m\n",
"\u001b[34massert\u001b[39;49;00m remove_html_markup(\u001b[33m'\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m\u001b[33mfoo\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m) == \u001b[33m'\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m\u001b[33mfoo\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m, \u001b[33m'\u001b[39;49;00m\u001b[33mMy Test\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m\u001b[37m\u001b[39;49;00m"
]
}
],
"source": [
"print_content(ast.unparse(test_tree), '.py')"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"We can merge the function definition tree and the test tree into a single one:"
]
},
{
"cell_type": "code",
"execution_count": 150,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:41.555250Z",
"iopub.status.busy": "2025-10-26T17:47:41.555124Z",
"iopub.status.idle": "2025-10-26T17:47:41.556780Z",
"shell.execute_reply": "2025-10-26T17:47:41.556549Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"import copy"
]
},
{
"cell_type": "code",
"execution_count": 151,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:41.557951Z",
"iopub.status.busy": "2025-10-26T17:47:41.557877Z",
"iopub.status.idle": "2025-10-26T17:47:41.559912Z",
"shell.execute_reply": "2025-10-26T17:47:41.559701Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"fun_test_tree = copy.deepcopy(fun_tree)\n",
"fun_test_tree.body += test_tree.body"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Such a tree can be compiled into a code object, using Python's `compile()` function:"
]
},
{
"cell_type": "code",
"execution_count": 152,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:41.561110Z",
"iopub.status.busy": "2025-10-26T17:47:41.561044Z",
"iopub.status.idle": "2025-10-26T17:47:41.562523Z",
"shell.execute_reply": "2025-10-26T17:47:41.562311Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"fun_test_code = compile(fun_test_tree, '', 'exec')"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"and the resulting code object can be executed directly, using the Python `exec()` function. We see that our test fails as expected."
]
},
{
"cell_type": "code",
"execution_count": 153,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:41.563726Z",
"iopub.status.busy": "2025-10-26T17:47:41.563653Z",
"iopub.status.idle": "2025-10-26T17:47:41.565682Z",
"shell.execute_reply": "2025-10-26T17:47:41.565291Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"Traceback (most recent call last):\n",
" File \"/var/folders/n2/xd9445p97rb3xh7m1dfx8_4h0006ts/T/ipykernel_51600/1290587190.py\", line 2, in \n",
" exec(fun_test_code, {}, {})\n",
" ~~~~^^^^^^^^^^^^^^^^^^^^^^^\n",
" File \"\", line 3, in \n",
"AssertionError: My Test (expected)\n"
]
}
],
"source": [
"with ExpectError(AssertionError):\n",
" exec(fun_test_code, {}, {})"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"#### Traversing Syntax Trees\n",
"\n",
"Our goal is now to reduce this tree (or at least the subtree with the function definition) to a minimum. \n",
"To this end, we manipulate the AST through the `ast` Python module. The [official Python `ast` reference](http://docs.python.org/3/library/ast) is complete, but a bit brief; the documentation [\"Green Tree Snakes - the missing Python AST docs\"](https://greentreesnakes.readthedocs.io/en/latest/) provides an excellent introduction."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"The two means for exploring and changing ASTs are the classes `NodeVisitor` and `NodeTransformer`, respectively. We start with creating a list of all nodes in the tree, using a `NodeVisitor` subclass.\n",
"\n",
"Its `visit()` method is called for every node in the tree, which we achieve by having it return `self.generic_visit()` for the current node. It saves all visited nodes in the `_all_nodes` attribute."
]
},
{
"cell_type": "code",
"execution_count": 154,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:41.567285Z",
"iopub.status.busy": "2025-10-26T17:47:41.567191Z",
"iopub.status.idle": "2025-10-26T17:47:41.568702Z",
"shell.execute_reply": "2025-10-26T17:47:41.568479Z"
},
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"source": [
"from ast import NodeTransformer, NodeVisitor, AST"
]
},
{
"cell_type": "code",
"execution_count": 155,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:41.569907Z",
"iopub.status.busy": "2025-10-26T17:47:41.569835Z",
"iopub.status.idle": "2025-10-26T17:47:41.571893Z",
"shell.execute_reply": "2025-10-26T17:47:41.571667Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"class NodeCollector(NodeVisitor):\n",
" \"\"\"Collect all nodes in an AST.\"\"\"\n",
"\n",
" def __init__(self) -> None:\n",
" super().__init__()\n",
" self._all_nodes: List[AST] = []\n",
"\n",
" def generic_visit(self, node: AST) -> None:\n",
" self._all_nodes.append(node)\n",
" return super().generic_visit(node)\n",
"\n",
" def collect(self, tree: AST) -> List[AST]:\n",
" \"\"\"Return a list of all nodes in tree.\"\"\"\n",
" self._all_nodes = []\n",
" self.visit(tree)\n",
" return self._all_nodes"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"This is how our `NodeCollector()` class produces a list of all nodes:"
]
},
{
"cell_type": "code",
"execution_count": 156,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:41.573101Z",
"iopub.status.busy": "2025-10-26T17:47:41.573027Z",
"iopub.status.idle": "2025-10-26T17:47:41.575055Z",
"shell.execute_reply": "2025-10-26T17:47:41.574810Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"107"
]
},
"execution_count": 156,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"fun_nodes = NodeCollector().collect(fun_tree)\n",
"len(fun_nodes)"
]
},
{
"cell_type": "code",
"execution_count": 157,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:41.576223Z",
"iopub.status.busy": "2025-10-26T17:47:41.576151Z",
"iopub.status.idle": "2025-10-26T17:47:41.578033Z",
"shell.execute_reply": "2025-10-26T17:47:41.577765Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[,\n",
" ,\n",
" ,\n",
" ,\n",
" ,\n",
" ,\n",
" ,\n",
" ,\n",
" ,\n",
" ,\n",
" ,\n",
" ,\n",
" ,\n",
" ,\n",
" ,\n",
" ,\n",
" ,\n",
" ,\n",
" ,\n",
" ,\n",
" ,\n",
" ,\n",
" ,\n",
" ,\n",
" ,\n",
" ,\n",
" ,\n",
" ,\n",
" ,\n",
" ]"
]
},
"execution_count": 157,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"fun_nodes[:30]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Such a list of nodes is what we can feed into Delta Debugging in order to reduce it. The idea is that with every test, we take the tree and for each node in the tree, we check whether it is still in the list – if not, we remove it. Thus, by reducing the list of nodes, we simultaneously reduce the tree as well."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"#### Deleting Nodes\n",
"\n",
"In our next step, we write some code that, given such a list of nodes, _prunes_ the tree such that _only_ elements in the list are still contained. To this end, we proceed in four steps:\n",
"\n",
"1. We traverse the original AST, _marking_ all nodes as \"to be deleted\".\n",
"2. We traverse the given list of nodes, clearing their markers.\n",
"3. We copy the original tree (including the markers) into a new tree – the one to be reduced.\n",
"4. We traverse the new tree, now deleting all marked nodes."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Why do we go through such an extra effort? The reason is that our list of nodes contains references into the _original_ tree – a tree that needs to stay unchanged such that we can reuse it for later. The new tree (the copy) has the same nodes, but at different addresses, so our original references cannot be used anymore. Markers, however, just like any other attributes, are safely copied from the original into the new tree."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"The `NodeMarker()` visitor marks all nodes in a tree:"
]
},
{
"cell_type": "code",
"execution_count": 158,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:41.579402Z",
"iopub.status.busy": "2025-10-26T17:47:41.579332Z",
"iopub.status.idle": "2025-10-26T17:47:41.580798Z",
"shell.execute_reply": "2025-10-26T17:47:41.580560Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"class NodeMarker(NodeVisitor):\n",
" def visit(self, node: AST) -> AST:\n",
" node.marked = True # type: ignore\n",
" return super().generic_visit(node)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"The `NodeReducer()` transformer reduces all marked nodes. If a method `visit_()` is defined, it will be invoked; otherwise, `visit_Node()` is invoked, which _deletes_ the node (and its subtree) by returning `None`. "
]
},
{
"cell_type": "code",
"execution_count": 159,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:41.582017Z",
"iopub.status.busy": "2025-10-26T17:47:41.581921Z",
"iopub.status.idle": "2025-10-26T17:47:41.584239Z",
"shell.execute_reply": "2025-10-26T17:47:41.584001Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"class NodeReducer(NodeTransformer):\n",
" def visit(self, node: AST) -> Any:\n",
" method = 'visit_' + node.__class__.__name__\n",
" visitor = getattr(self, method, self.visit_Node)\n",
" return visitor(node)\n",
"\n",
" def visit_Module(self, node: AST) -> Any:\n",
" # Can't remove modules\n",
" return super().generic_visit(node)\n",
"\n",
" def visit_Node(self, node: AST) -> Any:\n",
" \"\"\"Default visitor for all nodes\"\"\"\n",
" if node.marked: # type: ignore\n",
" return None # delete it\n",
" return super().generic_visit(node)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Our function `copy_and_reduce()` puts these pieces together:"
]
},
{
"cell_type": "code",
"execution_count": 160,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:41.585484Z",
"iopub.status.busy": "2025-10-26T17:47:41.585395Z",
"iopub.status.idle": "2025-10-26T17:47:41.587042Z",
"shell.execute_reply": "2025-10-26T17:47:41.586847Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"def copy_and_reduce(tree: AST, keep_list: List[AST]) -> AST:\n",
" \"\"\"Copy tree, reducing all nodes that are not in keep_list.\"\"\"\n",
"\n",
" # Mark all nodes except those in keep_list\n",
" NodeMarker().visit(tree)\n",
" for node in keep_list:\n",
" # print(\"Clearing\", node)\n",
" node.marked = False # type: ignore\n",
"\n",
" # Copy tree and delete marked nodes\n",
" new_tree = copy.deepcopy(tree)\n",
" NodeReducer().visit(new_tree)\n",
" return new_tree"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Let us apply this in practice. We take the first assignment in our tree..."
]
},
{
"cell_type": "code",
"execution_count": 161,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:41.588289Z",
"iopub.status.busy": "2025-10-26T17:47:41.588216Z",
"iopub.status.idle": "2025-10-26T17:47:41.590077Z",
"shell.execute_reply": "2025-10-26T17:47:41.589871Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
""
]
},
"execution_count": 161,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"fun_nodes[4]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"... whose subtree happens to be the assignment to `tag`:"
]
},
{
"cell_type": "code",
"execution_count": 162,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:41.591207Z",
"iopub.status.busy": "2025-10-26T17:47:41.591137Z",
"iopub.status.idle": "2025-10-26T17:47:41.592949Z",
"shell.execute_reply": "2025-10-26T17:47:41.592737Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"'tag = False'"
]
},
"execution_count": 162,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"ast.unparse(fun_nodes[4])"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"We keep all nodes _except_ for this one."
]
},
{
"cell_type": "code",
"execution_count": 163,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:41.594328Z",
"iopub.status.busy": "2025-10-26T17:47:41.594158Z",
"iopub.status.idle": "2025-10-26T17:47:41.596121Z",
"shell.execute_reply": "2025-10-26T17:47:41.595828Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"keep_list = fun_nodes.copy()\n",
"del keep_list[4]"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Let us now create a copy of the tree in which the assignment is missing:"
]
},
{
"cell_type": "code",
"execution_count": 164,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:41.597563Z",
"iopub.status.busy": "2025-10-26T17:47:41.597471Z",
"iopub.status.idle": "2025-10-26T17:47:41.974943Z",
"shell.execute_reply": "2025-10-26T17:47:41.974597Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"image/svg+xml": [
""
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"new_fun_tree = cast(ast.Module, copy_and_reduce(fun_tree, keep_list))\n",
"show_ast(new_fun_tree)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"The new tree no longer contains the initial assignment to `tag`:"
]
},
{
"cell_type": "code",
"execution_count": 165,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:41.976897Z",
"iopub.status.busy": "2025-10-26T17:47:41.976768Z",
"iopub.status.idle": "2025-10-26T17:47:41.991874Z",
"shell.execute_reply": "2025-10-26T17:47:41.991577Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[34mdef\u001b[39;49;00m\u001b[37m \u001b[39;49;00m\u001b[32mremove_html_markup\u001b[39;49;00m(s):\u001b[37m\u001b[39;49;00m\n",
" quote = \u001b[34mFalse\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
" out = \u001b[33m'\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
" \u001b[34mfor\u001b[39;49;00m c \u001b[35min\u001b[39;49;00m s:\u001b[37m\u001b[39;49;00m\n",
" \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",
" tag = \u001b[34mTrue\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
" \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",
" tag = \u001b[34mFalse\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
" \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",
" quote = \u001b[35mnot\u001b[39;49;00m quote\u001b[37m\u001b[39;49;00m\n",
" \u001b[34melif\u001b[39;49;00m \u001b[35mnot\u001b[39;49;00m tag:\u001b[37m\u001b[39;49;00m\n",
" out = out + c\u001b[37m\u001b[39;49;00m\n",
" \u001b[34massert\u001b[39;49;00m \u001b[33m'\u001b[39;49;00m\u001b[33m<\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m \u001b[35mnot\u001b[39;49;00m \u001b[35min\u001b[39;49;00m out \u001b[35mand\u001b[39;49;00m \u001b[33m'\u001b[39;49;00m\u001b[33m>\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m \u001b[35mnot\u001b[39;49;00m \u001b[35min\u001b[39;49;00m out\u001b[37m\u001b[39;49;00m\n",
" \u001b[34mreturn\u001b[39;49;00m out\u001b[37m\u001b[39;49;00m"
]
}
],
"source": [
"print_content(ast.unparse(new_fun_tree), '.py')"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"If we add our tests and then execute this code, we get an error, as `tag` is now no longer initialized:"
]
},
{
"cell_type": "code",
"execution_count": 166,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:41.993634Z",
"iopub.status.busy": "2025-10-26T17:47:41.993517Z",
"iopub.status.idle": "2025-10-26T17:47:41.995223Z",
"shell.execute_reply": "2025-10-26T17:47:41.994827Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"new_fun_tree.body += test_tree.body"
]
},
{
"cell_type": "code",
"execution_count": 167,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:41.997125Z",
"iopub.status.busy": "2025-10-26T17:47:41.997005Z",
"iopub.status.idle": "2025-10-26T17:47:41.998936Z",
"shell.execute_reply": "2025-10-26T17:47:41.998603Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"fun_code = compile(new_fun_tree, \"\", 'exec')"
]
},
{
"cell_type": "code",
"execution_count": 168,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:42.000336Z",
"iopub.status.busy": "2025-10-26T17:47:42.000237Z",
"iopub.status.idle": "2025-10-26T17:47:42.002099Z",
"shell.execute_reply": "2025-10-26T17:47:42.001851Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"Traceback (most recent call last):\n",
" File \"/var/folders/n2/xd9445p97rb3xh7m1dfx8_4h0006ts/T/ipykernel_51600/2411822553.py\", line 2, in \n",
" exec(fun_code, {}, {})\n",
" ~~~~^^^^^^^^^^^^^^^^^^\n",
" File \"\", line 3, in \n",
" File \"\", line 13, in remove_html_markup\n",
"UnboundLocalError: cannot access local variable 'tag' where it is not associated with a value (expected)\n"
]
}
],
"source": [
"with ExpectError(UnboundLocalError):\n",
" exec(fun_code, {}, {})"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"If we have _no_ node in the keep list, the whole tree gets deleted:"
]
},
{
"cell_type": "code",
"execution_count": 169,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:42.003440Z",
"iopub.status.busy": "2025-10-26T17:47:42.003350Z",
"iopub.status.idle": "2025-10-26T17:47:42.005313Z",
"shell.execute_reply": "2025-10-26T17:47:42.005082Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"empty_tree = copy_and_reduce(fun_tree, [])"
]
},
{
"cell_type": "code",
"execution_count": 170,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:42.006680Z",
"iopub.status.busy": "2025-10-26T17:47:42.006586Z",
"iopub.status.idle": "2025-10-26T17:47:42.008564Z",
"shell.execute_reply": "2025-10-26T17:47:42.008240Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"''"
]
},
"execution_count": 170,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"ast.unparse(empty_tree)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"#### Reducing Trees\n",
"\n",
"We can put all these steps together in a single function. `compile_and_test_ast()` takes a tree and a list of nodes, reduces the tree to those nodes in the list, and then compiles and runs the reduced AST."
]
},
{
"cell_type": "code",
"execution_count": 171,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:42.010521Z",
"iopub.status.busy": "2025-10-26T17:47:42.010394Z",
"iopub.status.idle": "2025-10-26T17:47:42.012793Z",
"shell.execute_reply": "2025-10-26T17:47:42.012496Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"def compile_and_test_ast(tree: ast.Module, keep_list: List[AST], \n",
" test_tree: Optional[ast.Module] = None) -> None:\n",
" new_tree = cast(ast.Module, copy_and_reduce(tree, keep_list))\n",
" # print(ast.unparse(new_tree))\n",
"\n",
" if test_tree is not None:\n",
" new_tree.body += test_tree.body\n",
"\n",
" try:\n",
" code_object = compile(new_tree, '', 'exec')\n",
" except Exception:\n",
" raise SyntaxError(\"Cannot compile\")\n",
"\n",
" exec(code_object, {}, {})"
]
},
{
"cell_type": "code",
"execution_count": 172,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:42.014185Z",
"iopub.status.busy": "2025-10-26T17:47:42.014086Z",
"iopub.status.idle": "2025-10-26T17:47:42.016822Z",
"shell.execute_reply": "2025-10-26T17:47:42.016564Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"Traceback (most recent call last):\n",
" File \"/var/folders/n2/xd9445p97rb3xh7m1dfx8_4h0006ts/T/ipykernel_51600/2588008834.py\", line 2, in \n",
" compile_and_test_ast(fun_tree, fun_nodes, test_tree)\n",
" ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
" File \"/var/folders/n2/xd9445p97rb3xh7m1dfx8_4h0006ts/T/ipykernel_51600/1107678207.py\", line 14, in compile_and_test_ast\n",
" exec(code_object, {}, {})\n",
" ~~~~^^^^^^^^^^^^^^^^^^^^^\n",
" File \"\", line 3, in \n",
"AssertionError: My Test (expected)\n"
]
}
],
"source": [
"with ExpectError(AssertionError):\n",
" compile_and_test_ast(fun_tree, fun_nodes, test_tree)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"When we run our delta debugger on the AST, this is the list of remaining nodes we obtain:"
]
},
{
"cell_type": "code",
"execution_count": 173,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:42.018341Z",
"iopub.status.busy": "2025-10-26T17:47:42.018230Z",
"iopub.status.idle": "2025-10-26T17:47:42.020958Z",
"shell.execute_reply": "2025-10-26T17:47:42.020666Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"with DeltaDebugger() as dd:\n",
" compile_and_test_ast(fun_tree, fun_nodes, test_tree)"
]
},
{
"cell_type": "code",
"execution_count": 174,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:42.022604Z",
"iopub.status.busy": "2025-10-26T17:47:42.022474Z",
"iopub.status.idle": "2025-10-26T17:47:42.224119Z",
"shell.execute_reply": "2025-10-26T17:47:42.223851Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"57"
]
},
"execution_count": 174,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"reduced_nodes = dd.min_args()['keep_list']\n",
"len(reduced_nodes)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"This is the associated tree:"
]
},
{
"cell_type": "code",
"execution_count": 175,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:42.225748Z",
"iopub.status.busy": "2025-10-26T17:47:42.225640Z",
"iopub.status.idle": "2025-10-26T17:47:42.591472Z",
"shell.execute_reply": "2025-10-26T17:47:42.591110Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"image/svg+xml": [
""
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"reduced_fun_tree = copy_and_reduce(fun_tree, reduced_nodes)\n",
"show_ast(reduced_fun_tree)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"And this is its textual representation:"
]
},
{
"cell_type": "code",
"execution_count": 176,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:42.593178Z",
"iopub.status.busy": "2025-10-26T17:47:42.593040Z",
"iopub.status.idle": "2025-10-26T17:47:42.607636Z",
"shell.execute_reply": "2025-10-26T17:47:42.607389Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[34mdef\u001b[39;49;00m\u001b[37m \u001b[39;49;00m\u001b[32mremove_html_markup\u001b[39;49;00m(s):\u001b[37m\u001b[39;49;00m\n",
" tag = \u001b[34mFalse\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
" quote = \u001b[34mFalse\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
" out = \u001b[33m'\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
" \u001b[34mfor\u001b[39;49;00m c \u001b[35min\u001b[39;49;00m s:\u001b[37m\u001b[39;49;00m\n",
" \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",
" tag = \u001b[34mTrue\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
" \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",
" tag = \u001b[34mFalse\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
" \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",
" quote = \u001b[35mnot\u001b[39;49;00m quote\u001b[37m\u001b[39;49;00m\n",
" \u001b[34melif\u001b[39;49;00m \u001b[35mnot\u001b[39;49;00m tag:\u001b[37m\u001b[39;49;00m\n",
" out = out + c\u001b[37m\u001b[39;49;00m\n",
" \u001b[34mreturn\u001b[39;49;00m out\u001b[37m\u001b[39;49;00m"
]
}
],
"source": [
"print_content(ast.unparse(reduced_fun_tree), '.py')"
]
},
{
"cell_type": "code",
"execution_count": 177,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:42.608834Z",
"iopub.status.busy": "2025-10-26T17:47:42.608755Z",
"iopub.status.idle": "2025-10-26T17:47:42.610837Z",
"shell.execute_reply": "2025-10-26T17:47:42.610633Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"310"
]
},
"execution_count": 177,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"dd.tests"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"We see that some code was deleted – notably the assertion at the end – but otherwise, our deletion strategy was not particularly effective. This is because in Python, one cannot simply delete the single statement in a controlled body – this raises a syntax error. One would have to replace it with `pass` (or some other statement with no effect) to stay syntactically valid. Still, the syntax-based reduction would still single out `remove_html_markup()` from the `Assertions` source code – and do so even faster, as it would apply on one definition (rather than one line) after another."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"#### Transforming Nodes\n",
"\n",
"To further boost our syntactic reduction strategy, we implement a set of additional reduction operators. First, as already discussed, we do not simply delete an assignment, but we replace it with a `pass` statement. To obtain the tree for `pass`, we simply parse it and access the subtree."
]
},
{
"cell_type": "code",
"execution_count": 178,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:42.612077Z",
"iopub.status.busy": "2025-10-26T17:47:42.611995Z",
"iopub.status.idle": "2025-10-26T17:47:42.613959Z",
"shell.execute_reply": "2025-10-26T17:47:42.613699Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"class NodeReducer(NodeReducer):\n",
" PASS_TREE = ast.parse(\"pass\").body[0]\n",
"\n",
" def visit_Assign(self, node: ast.Assign) -> AST:\n",
" if node.marked: # type: ignore\n",
" # Replace by pass\n",
" return self.PASS_TREE\n",
" return super().generic_visit(node)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"In a similar vein, we can replace comparison operators with `False`:"
]
},
{
"cell_type": "code",
"execution_count": 179,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:42.615124Z",
"iopub.status.busy": "2025-10-26T17:47:42.615046Z",
"iopub.status.idle": "2025-10-26T17:47:42.616909Z",
"shell.execute_reply": "2025-10-26T17:47:42.616689Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"class NodeReducer(NodeReducer):\n",
" FALSE_TREE = ast.parse(\"False\").body[0].value # type: ignore\n",
"\n",
" def visit_Compare(self, node: ast.Compare) -> AST:\n",
" if node.marked: # type: ignore\n",
" # Replace by False\n",
" return self.FALSE_TREE\n",
" return super().generic_visit(node)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"If we have a Boolean operator, we attempt to replace it with its left operand:"
]
},
{
"cell_type": "code",
"execution_count": 180,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:42.618068Z",
"iopub.status.busy": "2025-10-26T17:47:42.617999Z",
"iopub.status.idle": "2025-10-26T17:47:42.619712Z",
"shell.execute_reply": "2025-10-26T17:47:42.619477Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"class NodeReducer(NodeReducer):\n",
" def visit_BoolOp(self, node: ast.BoolOp) -> AST:\n",
" if node.marked: # type: ignore\n",
" # Replace by left operator\n",
" return node.values[0]\n",
" return super().generic_visit(node)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"And if we find an `If` clause, we attempt to replace it by its body:"
]
},
{
"cell_type": "code",
"execution_count": 181,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:42.620864Z",
"iopub.status.busy": "2025-10-26T17:47:42.620792Z",
"iopub.status.idle": "2025-10-26T17:47:42.622510Z",
"shell.execute_reply": "2025-10-26T17:47:42.622317Z"
},
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"class NodeReducer(NodeReducer):\n",
" def visit_If(self, node: ast.If) -> Union[AST, List[ast.stmt]]:\n",
" if node.marked: # type: ignore\n",
" # Replace by body\n",
" return node.body\n",
" return super().generic_visit(node)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Let us try to reduce our code with these additional reducers enabled:"
]
},
{
"cell_type": "code",
"execution_count": 182,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:42.623665Z",
"iopub.status.busy": "2025-10-26T17:47:42.623581Z",
"iopub.status.idle": "2025-10-26T17:47:42.626053Z",
"shell.execute_reply": "2025-10-26T17:47:42.625860Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"with DeltaDebugger() as dd:\n",
" compile_and_test_ast(fun_tree, fun_nodes, test_tree)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"This is the reduced code we get. We see that all references to `quote` have gone, as has the handling of single quotes – none of this is relevant for the failure:"
]
},
{
"cell_type": "code",
"execution_count": 183,
"metadata": {
"execution": {
"iopub.execute_input": "2025-10-26T17:47:42.627296Z",
"iopub.status.busy": "2025-10-26T17:47:42.627200Z",
"iopub.status.idle": "2025-10-26T17:47:42.791702Z",
"shell.execute_reply": "2025-10-26T17:47:42.791468Z"
},
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[34mdef\u001b[39;49;00m\u001b[37m \u001b[39;49;00m\u001b[32mremove_html_markup\u001b[39;49;00m(s):\u001b[37m\u001b[39;49;00m\n",
" tag = \u001b[34mFalse\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
" \u001b[34mpass\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
" out = \u001b[33m'\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
" \u001b[34mfor\u001b[39;49;00m c \u001b[35min\u001b[39;49;00m s:\u001b[37m\u001b[39;49;00m\n",
" \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[37m\u001b[39;49;00m\n",
" tag = \u001b[34mTrue\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
" \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[37m\u001b[39;49;00m\n",
" tag = \u001b[34mFalse\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
" \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[37m\u001b[39;49;00m\n",
" \u001b[34mpass\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n",
" \u001b[34melif\u001b[39;49;00m \u001b[35mnot\u001b[39;49;00m tag:\u001b[37m\u001b[39;49;00m\n",
" out = out + c\u001b[37m\u001b[39;49;00m\n",
" \u001b[34mreturn\u001b[39;49;00m out\u001b[37m\u001b[39;49;00m"
]
}
],
"source": [
"reduced_nodes = dd.min_args()['keep_list']\n",
"reduced_fun_tree = copy_and_reduce(fun_tree, reduced_nodes)\n",
"print_content(ast.unparse(reduced_fun_tree), '.py')"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Again, the best insights come from comparing this reduced version to the original implementation – and we learn that the problem is not related to the `quote` variable, or to the handling of single quotes; the problem is simply that when the input contains double quotes, these are not added to the final string."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"With our reduction code, however, we only touch the surface of what could actually be possible. So far, we implement exactly one reduction per node – but of course, there are many alternatives an expression or statement could be reduced to. We will explore some of these in the [exercises](#Exercises), below; also be sure to check out the [background](#Background) on code reduction."
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": true,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Lessons Learned\n",
"\n",
"* Reducing failure-inducing inputs to a minimum is helpful for testing and debugging.\n",
"* _Delta debugging_ is a simple and robust algorithm to easily reduce inputs of test cases, as well as their code.\n",
"* Precisely specifying failure conditions helps to avoid false diagnoses."
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Next Steps\n",
"\n",
"Our next chapter focuses on [finding _failure-inducing code changes_](ChangeDebugger.ipynb), using delta debugging and version control systems."
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Background\n",
"\n",
"The \"lexical\" delta debugging algorithm discussed here – both in its simplifying `ddmin` and in its general `dd` form – stem from \\cite{Zeller2002}; actually, `ddmin` is the exact Python implementation as used by Zeller in 2002. The `ddmax` variant was first evaluated in \\cite{Kirschner2020}. This chapter is the first to show how both `ddmin` and `ddmax` can be implemented as small variations of `dd`."
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"The idea of systematically reducing inputs has been discovered a number of times, although not as automatic and generic as delta debugging. \\cite{Slutz1998}, for instance, discusses systematic reduction of SQL statements for SQL databases; the general process as manual work is well described by \\cite{Kernighan1999}."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"The deficits of delta debugging as it comes to syntactically complex inputs were first discussed in *compiler testing*, and _reducing tree inputs_ rather than string inputs was quickly discovered as an alternative. *Hierarchical Delta Debugging* (*HDD*) \\cite{Misherghi2006} applies delta debugging on subtrees of a parse tree, systematically reducing a parse tree to a minimum. _Generalized Tree Reduction_ \\cite{Herfert2017} generalizes this idea to apply arbitrary _patterns_ such as replacing a term by a compatible term in a subtree. Using _grammars_ to reduce inputs was first implemented in the _Perses_ tool \\cite{Sun2018}. [A Python implementation of grammar-based input reduction](https://www.fuzzingbook.org/html/Reducer.html#Grammar-Based-Input-Reduction) is part of \"The Fuzzing Book\"."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"While applying delta debugging to code lines does a decent job, _syntactic_ and especially _language-specific_ approaches can do a much better job for the programming language at hand:\n",
"\n",
"* *C-Reduce* \\cite{Regehr2012} is a reducer specifically targeting the reduction of programming languages. Besides reductions in the style of delta debugging or tree transformations, C-Reduce comes with more than 30 source-to-source transformations that replace aggregates by scalars, remove function parameters at a definition and all call sites, change functions to return `void` and deleting all `return` statements, and many more. While specifically instantiated for the C language (and used for testing C compilers), these principles extend to arbitrary programming languages following an ALGOL-like syntax.\n",
"\n",
"* Kalhauge and Palsberg \\cite{Kalhauge2019} introduce *binary reduction of dependency graphs*, a general solution for reducing arbitrary inputs with dependencies. Their *J-Reduce* tool specifically targets Java programs, and again is much faster than delta debugging and achieves a higher reduction rate."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Reducing inputs also works well in the context of _property-based testing_; that is, generating data structures for individual functions, which can then be reduced (\"shrunk\") upon failure. The [Hypothesis](https://hypothesis.works) fuzzer has a number of type-specific shrinking strategies; this [blog article](https://hypothesis.works/articles/integrated-shrinking/) discusses some of its features."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"This [blog post](https://www.drmaciver.com/2019/01/notes-on-test-case-reduction/) by David McIver contains lots of insights on how to apply reduction in practice, in particular multiple runs with different abstraction levels."
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": true,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Exercises\n",
"\n",
"How to best reduce inputs is still an underdeveloped field of research, with lots of opportunities."
]
},
{
"cell_type": "markdown",
"metadata": {
"button": false,
"new_sheet": false,
"run_control": {
"read_only": false
},
"slideshow": {
"slide_type": "subslide"
},
"solution": "hidden",
"solution2": "hidden",
"solution2_first": true,
"solution_first": true
},
"source": [
"### Exercise 1: Advanced Syntactic Code Reduction\n",
"\n",
"Extend the code in [\"Transforming Nodes\"](#Transforming-Nodes) such that _multiple_ reduction possibilities for a node are being considered. For instance:\n",
"\n",
"* Replace a `BoolOp` node by `True`.\n",
"* Replace a `BoolOp` node by `False`.\n",
"* Replace a `BoolOp` node by its left operand.\n",
"* Replace a `BoolOp` node by its right operand.\n",
"\n",
"or:\n",
"\n",
"* Replace an `If` node by its \"then\" body.\n",
"* Replace an `If` node by its \"else\" body.\n",
"\n",
"or:\n",
"\n",
"* Replace all instances of a variable by a constant.\n",
"\n",
"or:\n",
"\n",
"* Replace expressions by a constant.\n",
"\n",
"Have a look at the [official Python `ast` reference](http://docs.python.org/3/library/ast) for a list of nodes (and some ideas on what to replace them by). The documentation [\"Green Tree Snakes - the missing Python AST docs\"](https://greentreesnakes.readthedocs.io/en/latest/) provides an excellent introduction on visitors and transformers. Make copious use of AST visualization and tests to ensure your syntax trees are still correct.\n",
"\n",
"Strategy-wise, you should first create a list of _possible_ reductions; and then pass to Delta Debugging a \"keep list\" of reductions that should _not_ be applied. When Delta Debugging minimizes this list, it will apply as many reductions as possible."
]
}
],
"metadata": {
"ipub": {
"bibliography": "fuzzingbook.bib",
"toc": true
},
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"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.13.4"
},
"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,
"varInspector": {
"cols": {
"lenName": 16,
"lenType": 16,
"lenVar": 40
},
"kernels_config": {
"python": {
"delete_cmd_postfix": "",
"delete_cmd_prefix": "del ",
"library": "var_list.py",
"varRefreshCmd": "print(var_dic_list())"
},
"r": {
"delete_cmd_postfix": ") ",
"delete_cmd_prefix": "rm(",
"library": "var_list.r",
"varRefreshCmd": "cat(var_dic_list()) "
}
},
"types_to_exclude": [
"module",
"function",
"builtin_function_or_method",
"instance",
"_Feature"
],
"window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 4
}