...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
The example in the previous section was very simplistic. It only recognized data, but did nothing with it. It answered the question: "Did the input match?". Now, we want to extract information from what was parsed. For example, we would want to store the parsed number after a successful match. To do this, you will need semantic actions.
Semantic actions may be attached to any point in the grammar specification.
These actions are C++ functions or function objects that are called whenever
a part of the parser successfully recognizes a portion of the input. Say
you have a parser P
, and
a C++ function F
. You can
make the parser call F
whenever it matches an input by attaching F
:
P[F]
The expression above links F
to the parser, P
.
The function/function object signature depends on the type of the parser
to which it is attached. The parser double_
passes the parsed number. Thus, if we were to attach a function F
to double_
,
we need F
to be declared
as:
void F(double n);
There are actually 2 more arguments being passed (the parser context and a reference to a boolean 'hit' parameter). We don't need these, for now, but we'll see more on these other arguments later. Spirit.Qi allows us to bind a single argument function, like above. The other arguments are simply ignored.
Presented are various ways to attach semantic actions:
Given:
namespace client { namespace qi = boost::spirit::qi; // A plain function void print(int const& i) { std::cout << i << std::endl; } // A member function struct writer { void print(int const& i) const { std::cout << i << std::endl; } }; // A function object struct print_action { void operator()(int const& i, qi::unused_type, qi::unused_type) const { std::cout << i << std::endl; } }; }
Take note that with function objects, we need to have an operator()
with 3 arguments. Since we don't care about the other two, we can use
unused_type
for these.
We'll see more of unused_type
elsewhere. unused_type
is a Spirit supplied support class.
All examples parse inputs of the form:
"{integer}"
An integer inside the curly braces.
The first example shows how to attach a plain function:
parse(first, last, '{' >> int_[&print] >> '}');
What's new? Well int_
is
the sibling of double_
.
I'm sure you can guess what this parser does.
The next example shows how to attach a simple function object:
parse(first, last, '{' >> int_[print_action()] >> '}');
We can use Boost.Bind to 'bind' member functions:
writer w; parse(first, last, '{' >> int_[boost::bind(&writer::print, &w, _1)] >> '}');
Likewise, we can also use Boost.Bind to 'bind' plain functions:
parse(first, last, '{' >> int_[boost::bind(&print, _1)] >> '}');
Yep, we can also use Boost.Lambda:
parse(first, last, '{' >> int_[std::cout << _1 << '\n'] >> '}');
There are more ways to bind semantic action functions, but the examples above are the most common. Attaching semantic actions is the first hurdle one has to tackle when getting started with parsing with Spirit. Familiarize yourself with this task and get intimate with the tools behind it such as Boost.Bind and Boost.Lambda.
The examples above can be found here: ../../example/qi/actions.cpp
Boost.Phoenix, a companion library bundled with Spirit, is specifically suited for binding semantic actions. It is like Boost.Lambda on steroids, with special custom features that make it easy to integrate semantic actions with Spirit. If your requirements go beyond simple to moderate parsing, it is suggested that you use this library. All the following examples in this tutorial will use Boost.Phoenix for semantic actions.
Important | |
---|---|
There are different ways to write semantic actions for Spirit.Qi:
using plain functions, Boost.Bind,
Boost.Lambda, or
Boost.Phoenix.
The latter three allow you to use special placeholders to control parameter
placement (
Generally, for Boost.Bind,
use
For Boost.Lambda use
the placeholders defined in the namespace
For semantic actions written using Boost.Phoenix
use the placeholders defined in the namespace |