...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
In the previous section we mentioned a very important difference between
parsers and generators. While parsers may be used without 'producing' any
data, generators always need data to generate the output from. We mentioned
one way of passing data to the generator by supplying it as a parameter
to one of the main API functions (for instance generate()
or generate_delimited()
). But sometimes this is not possible
or not desirable.
Very much like for Spirit.Qi we have semantic actions
in Spirit.Karma as well. 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 generator is
about to be invoked. Say you have a generator G
,
and a C++ function F
, you
can make the generator call F
just before it gets invoked by attaching F
:
G[F]
The expression above links F
to the generator, G
.
Semantic actions in Spirit.Qi are invoked after a parser successfully matches its input and the matched value is passed into the semantic action. In Spirit.Karma the opposite happens. Semantic actions are called before its associated generator is invoked. They may provide the data required by the generator.
The function/function object signature depends on the type of the generator
to which it is attached. The generator double_
expects the number to generate. Thus, if we were to attach a function
F
to double_
,
we need F
to be declared
as:
void F(double& n);
where the function is expected to initialize the parameter n
with the value to generate.
Important | |
---|---|
Generally, and more formally, the semantic action
In the example above the function F takes a |
There are actually 2 more arguments being passed (the generator context and a reference to a boolean 'pass' parameter). We don't need these, for now, but we'll see more on these other arguments later. Spirit.Karma allows us to bind a single argument function, like above. The other arguments are simply ignored.
To sum up, the possible signatures for semantic actions are:
void f(Attrib&); void f(Attrib&, Context&); void f(Attrib&, Context&, bool&);
In the following example we present various ways to attach semantic actions:
Let's assume we have:
namespace client { namespace karma = boost::spirit::karma; // A plain function void read_function(int& i) { i = 42; } // A member function struct reader { void print(int& i) const { i = 42; } }; // A function object struct read_action { void operator()(int& i, unused_type, unused_type) const { i = 42; } }; }
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. Get used to it. unused_type
is a Spirit supplied support class. Most of the time it stands for 'I don't
care, just use the appropriate default'.
All following examples generate outputs of the form:
"{integer}"
An integer inside the curly braces.
The first example shows how to attach a plain function:
generate(outiter, '{' << int_[&read_function] << '}');
What's new? Well int_
is
the sibling of double_
.
I'm sure you can guess what this generator does and what type of attribute
it expects.
The next example shows how to attach a simple function object:
generate(outiter, '{' << int_[read_action()] << '}');
We can use Boost.Bind to 'bind' member functions:
reader r; generate(outiter, '{' << int_[boost::bind(&reader::print, &r, _1)] << '}');
Likewise, we can also use Boost.Bind to 'bind' plain functions:
generate(outiter, '{' << int_[boost::bind(&read_function, _1)] << '}');
And last but not least, we can also use Boost.Lambda:
std::stringstream strm("42"); generate(outiter, '{' << int_[strm >> lambda::_1] << '}');
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 generating with Spirit. If you didn't do so yet, it is probably a good idea to familiarize yourself with the tools behind it such as Boost.Bind and Boost.Lambda.
The examples above can be found here: actions.cpp
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 generation, I suggest you use this library. Examples presented henceforth shall be using the Phoenix library exclusively.
Important | |
---|---|
There are different ways to write semantic actions for Spirit.Karma:
using plain functions, Boost.Bind,
Boost.Lambda, or
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 Phoenix
use the placeholders defined in the namespace |