Semantic actions with Phoenix

In the Boost Spirit Qi documentation, Phoenix is described as a library defining a sort of "lambda on steroids", and its usage is recommended for implementing semantic actions that have a certain degree on complexity.

Here I'll show the difference when using as a semantic action a free function, a standard lambda function and Phoenix.

What we want to do is a parser that would generate a standard complex, or would give an error in case of malformed input. Our expected input is a string containing just a number, "42", or a complex representation with real and imaginary part included in round brackets - as a bonus we could even accept just a number, for the real part only: "(43, 12)" or "(33)".

Our parser should look something like this:
double_[funR] | '(' >> double_[funR] >> -(',' >> double_[funI]) >> ')';

That reads: A double, on which applying the funR semantic action, or an open round bracket followed by a double, on which applying the funR semantic action, followed by no or one block made of a comma followed by a double, on which applying the funI semantic action; the (possibly missing) block is followed by a close round bracket.

Firstly, we are going to implement the semantic action with a free function. This one:
void setValue(double& lhs, const double rhs) { lhs = rhs; }

And we are going to use it in this way:

template <typename Iterator>
bool parseCpxFun(Iterator first, Iterator last, std::complex<double>& c)
{
using boost::spirit::qi::double_;
using boost::spirit::ascii::space;
using boost::spirit::qi::phrase_parse;

double cReal = 0.0;
double cImg = 0.0; // 1.

auto funR = std::bind(&setValue, std::ref(cReal), std::placeholders::_1); // 2.
auto funI = std::bind(&setValue, std::ref(cImg), std::placeholders::_1);

auto expr = double_[funR] | '(' >> double_[funR] >> -(',' >> double_[funI]) >> ')'; // 3.

bool r = phrase_parse(first, last, expr, space); // 4.

if (!r || first != last) // 5.
return false;
c = std::complex<double>(cReal, cImg); // 6.
return r;
}

1. The parser would set the complex components here defined using the semantic action. The real part has always to be specified, so it is not so important to initialize this variable, but the imaginary part could be missing, so it is crucial having it defaulted to zero.
2. I used a few non strictly necessary variable to make the code more readable, funR and funI bind the semantic action, the function setValue(), to the variables that store the temporary real and imaginary parts. Notice the usage of std::ref() to let std::bind() know the variable should be passed by reference. As third parameter we have a placeholder to the parameter passed to the resulting function. Here I use std::bind() and std::ref(), but if your compiler does not support them yet you can substitute them with boost::bind() and boost::ref().
3. That is the parser, described above, how is implemented here, calling the binds to the free function working as semantic action.
4. And here is how we call phrase_parse(), passing a couple of delimiting iterators, the expression to be used as parser, and what to use as separator.
5. We want our parsing to be quite strict.
6. If the parsing succeeded, we create a complex using the components generated by the semantic actions.

We can simplify a bit the code, using lambda functions to implement semantic actions.

We rearrange the code writter for free function, getting rid of the free function itself, and rewriting parser and function used by it in this way:

// ...
using boost::spirit::qi::unused_type; // 1.

double cReal = 0.0;
double cImg = 0.0;

auto setCR = [&cReal](const double value, unused_type, unused_type) { cReal = value; }; // 2.
auto setCI = [&cImg](const double value, unused_type, unused_type) { cImg = value; };

auto expr = double_[setCR] | '(' >> double_[setCR] >> -(',' >> double_[setCI]) >> ')';

1. It's a real nuisance. As we have already seen, Spirit expect that a lambda function, or even a functor, when used as parser semantic action, accepting three parameter. When we just need the first one, we should use this unused_type for the other two parameters.
2. The lambda works on a variable in scope, cReal or cImg, accessed by reference, accepts a value passed by the caller (and other two unused parameters), and then does the required assignment.

Using Phoenix the code gets even simpler, given that Phoenix is based on boost::lambda that has a looser syntax than standard lambda:

using boost::spirit::qi::_1; // 1.
using boost::phoenix::ref; // 2.

double cReal = 0.0;
double cImg = 0.0;

auto setCR = (ref(cReal) = _1); // 3.
auto setCI = (ref(cImg) = _1);

auto expr = double_[setCR] | '(' >> double_[setCR] >> -(',' >> double_[setCI]) >> ')';

1. The Spirit Qi placeholders are used, since that version is designed explicitly for this usage.
2. Ditto for the Phoenix version of ref.
3. The resulting code is so clean that it makes not much sense putting it in a temporary. The only reason why I left it here is to better show the difference with the previous implementations.

I based this post on a C++ source file provided by the original Boost Spirit Qi documentation.

No comments:

Post a Comment