.. Decision Copyright (C) 2019-2020 Benjamin Beddows This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ####################### Debugging Decision Code ####################### .. warning:: If you are embedding into a C++ program, it is very, very recommended that when you include a C header file, you encapsulate it with an ``extern`` block, like so: .. code-block:: cpp extern "C" { #include #include } Introduction ============ As of the 0.3.0 update, you can debug Decision code through the C API! This means that when you compile Decision code in **debug mode** (more on this later), you can tell the debugger to tell you when: * A non-execution wire transfers a value from one socket to another, and what the value transfered was. * An execution wire activates. * A node activates. * A node calls a function or subroutine. * A function or subroutine returns. You can also set breakpoints which, when hit, will pause the debugger. * Node breakpoints are hit when the node is activated. * Wire breakpoints are hit when: * A value is transfered if the wire is a non-execution wire. * The wire is activated if the wire is an execution wire. Compiling in Debug Mode ======================= In order to debug a sheet, you need to compile the sheet in **debug mode**. This will make a couple of changes to the compilation process: * When generating the bytecode, the code generator will also store debugging information alongside the bytecode, e.g. where a node starts in bytecode, which instructions call functions, etc. * Optimisation will no longer occur. This is because we don't want to lose debugging information. For example, say there are two consecutive ``NOT`` instructions in the bytecode, made from two ``Not`` nodes in the Decision code. Optimisation would usually remove these instructions, but when debugging, we still want to know when those two ``Not`` nodes activate! To compile a sheet in debug mode, you need to set the ``debug`` property in a ``CompileOptions`` structure: .. code-block:: c CompileOptions options = DEFAULT_COMPILE_OPTIONS; options.debug = true; // This sheet will now be compiled in debug mode. Sheet *sheet = d_load_file("debug.dc", &options); Debugging Agendas ================= Agendas describe what you want to happen when certain events occur during the debugging process. In code, they are in the form of function pointers: .. doxygentypedef:: OnWireValue :no-link: .. doxygentypedef:: OnExecutionWire :no-link: .. doxygentypedef:: OnNodeActivated :no-link: .. doxygentypedef:: OnCall :no-link: .. doxygentypedef:: OnReturn :no-link: Once you have functions in the above forms, you can then add them to a ``DebugAgenda`` structure: .. code-block:: c DebugAgenda agenda = NO_AGENDA; agenda.onWireValue = &onWireValue; agenda.onExecutionWire = &onExecutionWire; agenda.onNodedActivated = &onNodeActivated; agenda.onCall = &onCall; agenda.onReturn = &onReturn; Breakpoints ----------- If you want to set breakpoints, you can specify where you want the breakpoints to be set: .. code-block:: c DebugNodeBreakpoint nodeBreaks[] = { {sheet, 2}, // The 3rd node in the sheet. {NULL, 0} // Array needs to end with a NULL entry! }; DebugWireBreakpoint wireBreaks[] = { {sheet, {{2, 2}, {3, 1}}}, // The wire that goes from the 3rd socket of // the 3rd node to the 2nd socket of the 4th // node. {NULL, {{0, 0}, {0, 0}}} // Array needs to end with a NULL entry! }; // Don't forget to add them to the agenda! agenda.nodeBreakpoints = nodeBreaks; agenda.wireBreakpoints = wireBreaks; Then, you can tell the debugger to let you know when the breakpoints are hit with the given function pointers: .. doxygentypedef:: OnNodeBreakpoint :no-link: .. doxygentypedef:: OnWireBreakpoint :no-link: Then you can add them to the agenda: .. code-block:: c agenda.onNodeBreakpoint = &onNodeBreakpoint; agenda.onWireBreakpoint = &onWireBreakpoint; Debugging Sessions ================== To create a debugging session, use: .. doxygenfunction:: d_debug_create_session :no-link: You can then start/continue a debugging session with: .. doxygenfunction:: d_debug_continue_session :no-link: .. tip:: You can put ``d_debug_continue_session`` as the condition of a while loop! This way, the session will continue until the sheet exits, and you can process something after each breakpoint hit! Once you are done debugging, you should use: .. doxygenfunction:: d_debug_stop_session :no-link: Partial Example =============== This example just prints out when nodes get activated. .. code-block:: c #include #include #include #include void onNodeActivated(Sheet *sheet, size_t nodeIndex) { Node node = sheet->graph.nodes[nodeIndex]; const NodeDefinition *nodeDef = node.definition; printf("Debug> Node %s on line %zu in sheet %s has been activated!\n", nodeDef->name, node.lineNum, sheet->filePath); } int main() { // Load the sheet in debug mode. CompileOptions options = DEFAULT_COMPILE_OPTIONS; options.debug = true; Sheet *sheet = d_load_file("debug.dc", &options); // Set up the debugging agenda. DebugAgenda agenda = NO_AGENDA; agenda.onNodeActivated = &onNodeActivated; // Create the session. DebugSession session = d_debug_create_session(sheet, agenda); // Keep continuing the session until the sheet exits. while (d_debug_continue_session(&session)) { printf("Debug> A breakpoint was hit!\n"); } // Once we're done, stop the session. d_debug_stop_session(&session); // Free the sheet. d_sheet_free(sheet); return 0; } To see a full example that uses all of the capabilities of the debugger, feel free to look at ``tests/c/debugging.c``!