...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
In this section we will develop a generator for complex numbers, allowing
to represent a std::complex
either as (real, imag)
(where real
and imag
are the real and imaginary parts
of the complex number) or as a simple real
if the imaginary part happens to be equal to zero. This example will highlight
the power of Spirit.Karma allowing to combine compile
time definition of formatting rules with runtime based decisions which
of the rules to apply. Also this time, we're using Boost.Phoenix
to do the semantic actions.
Our goal is to allow for two different output formats to be applied depending on whether the imaginary part of the complex number is zero or not. Let's write both as a set of alternatives:
'(' << double_ << ", " << double_ << ')' | double_
where the first alternative should be used for numbers having a non-zero
imaginary part, while the second is for real numbers. Generally, alternatives
are tried in the sequence of their definition as long until one of the
expressions (as delimited by '|'
)
succeeds. If no generator expression succeeds the whole alternative fails.
If we left this formatting grammar as is our generator would always choose
the first alternative. We need to add some additional rules allowing to
make the first alternative fail. So, if the first alternative fails the
second one will be chosen instead. The decision about whether to choose
the first alternative has to be made at runtime as only then we actually
know the value of the imaginary part of the complex number. Spirit.Karma
provides us with with a primitive generator eps()
, which is usable as a semantic predicate.
It has the property to 'succeed' generating only if its argument is true
(while it never generates any output on its own).
double imag = ...; // imaginary part eps(imag != 0) << '(' << double_ << ", " << double_ << ')' | double_
If one of the generator elements of a sequence fails the whole sequence will fail. This is exactly what we need, forcing the second alternative to be chosen for complex numbers with imaginary parts equal to zero.
Now on to the full example, this time with the proper semantic actions (the complete cpp file for this example can be found here: complex_number.cpp).
We will use the std::complex
type for this and all subsequent
related examples. And here you can see the full code of the generator allowing
to output a complex number either as a pair of numbers (if the imaginary
part is non-zero) or as a single number (if the complex is a real number):
template <typename OutputIterator> bool generate_complex(OutputIterator sink, std::complex<double> const& c) { using boost::spirit::karma::eps; using boost::spirit::karma::double_; using boost::spirit::karma::_1; using boost::spirit::karma::generate; return generate(sink, // Begin grammar ( eps(c.imag() != 0) << '(' << double_[_1 = c.real()] << ", " << double_[_1 = c.imag()] << ')' | double_[_1 = c.real()] ) // End grammar ); }
The double_
generators
have this semantic action attached:
_1 = n
which passes n
to the first
element of the s generator's attached semantic action. Remember, semantic
actions in Spirit.Karma are called before the corresponding
generator is invoked and they are expected to provide the generator with
the data to be used. The semantic action above assigns the value to be
generated (n
) to the generator
(actually, the attribute of double_
).
_1
is a Phoenix placeholder
referring to the attribute of the semantic action's attached generator.
If you need more information about semantic actions, you may want to read
about them in this section: Semantic
Actions.
These semantic actions are easy to understand but have the unexpected side effect of being slightly less efficient than it could be. In addition they tend to make the formatting grammar less readable. We will see in one of the next sections how it is possible to use other, built-in features of Spirit.Karma to get rid of the semantic actions altogether. When writing your grammars in Spirit you should always try to avoid semantic actions which is often possible. Semantic actions are really powerful tools but grammars tend to be more efficient and readable without them.