..
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``!