Debugging

The top-down nature of Spirit makes the generated parser easy to micro-debug using the standard debugger bundled with the C++ compiler we are using. With recursive-descent, the parse traversal utilizes the hardware stack through C++ function call mechanisms. There are no difficult to debug tables or state machines that obscure the parsing logic flow. The stack trace we see in the debugger follows faithfully the hierarchical grammar structure.

Since any production rule can initiate a parse traversal , it is a lot easier to pinpoint the bugs by focusing on one or a few rules. For relatively complex parsing tasks, the same way we write robust C++ programs, it is advisable to develop a grammar iteratively on a per-module basis where each module is a small subset of the complete grammar. That way, we can stress-test individual modules piecemeal until we reach the top-most module. For instance, when developing a scripting language, we can start with expressions, then move on to statements, then functions, upwards until we have a complete grammar. A neat way to write our grammar modules was discussed in the section on Iterators and Parsing.

At some point when the grammar gets quite complicated, it is desirable to visualize the parse traversal and see what's happening. There are some facilities in the framework that aid in the visualisation of the parse traversal for the purpose of debugging. The following macros enable these features.

Debugging Macros
SPIRIT_DEBUG Define this to enable debugging
SPIRIT_DEBUG_RULE(r)
Define this to print some debugging diagnostics for rule r.

Pre-parse: Before entering the rule, the rule name followed by a peek into the data at the current iterator position is printed.

Post-parse: After parsing the rule, the rule name followed by a peek into the data at the current iterator position is printed. Here, '/' before the rule name flags a succesful match while '#' before the rule name flags an unsuccesful match.
SPIRIT_DEBUG_OUT Define this to redirect the debugging diagnostics printout to somewhere else (e.g. a file or stream). Defaults to std::cout.

Here's the original calculator with debugging features enabled:

#define SPIRIT_DEBUG  ///$$$ DEFINE THIS BEFORE ANYTHING ELSE $$$///
#include "boost/spirit/spirit.hpp"

/***/

/*** CALCULATOR GRAMMAR DEFINITIONS HERE ***/

SPIRIT_DEBUG_RULE(integer);
SPIRIT_DEBUG_RULE(group);
SPIRIT_DEBUG_RULE(expr2);
SPIRIT_DEBUG_RULE(expr1);
SPIRIT_DEBUG_RULE(expr);

Now here's a sample session with the calculator.

Type an expression...or [q or Q] to quit

1 + 2
expr: "1 + 2 "
expr1: "1 + 2 "
expr2: "1 + 2 "
integer: "1 + 2 "
push 1
/integer: "+ 2 "
/expr2: "+ 2 "
/expr1: "+ 2 "
expr1: "2 "
expr2: "2 "
integer: "2 "
push 2
/integer: " "
/expr2: " "
/expr1: " "
popped 1 and 2 from the stack. pushing 3 onto the stack.
/expr: " "
parsing succeeded
result = 3

We typed in "1 + 2". Notice that there are two successful branches from the top rule expr. The text in red is generated by the parser's semantic actions while the others are generated by the debug-diagnostics of our rules. Notice how the first integer rule took "1", the first expr1 rule took "+" and finally the second integer rule took "2".