Parser Semantic Actions

Usually you don't parse data just to see if they match with a given pattern, but you want take some action that involves the extracted information. This is done in Boost Spirit Qi by the so called parser semantic actions.

An action is nothing more than a C++ function, functor, or lambda function that is called by the parser any time it finds a matching element. The action signature should match the expected prototype relative to the actual parsed element type.

For instance, if a double_ element is parsed, the relative function should be something like
void F(double n);
Actually, there should be other two parameter, that we can safely ignore, and even not put in the function declaration - Spirit Qi takes care of picking up the correct overload - but, as we'll see in a moment, have to be declared as unused when we keep the functor or lambda function approach.

As an example, say that we want to parse a string that should contain an integer between curly brackets, something like "{42}" and, when the integer is detected by the parser, we want a function to be called.

A generic action manager for our problem could be written in this way:
template<typename Fun>
inline void bsGenericAction(const std::string& input, const Fun& fun) // 1.
{
using boost::spirit::qi::int_;

if(!boost::spirit::qi::parse(input.begin(), input.end(), '{' >> int_[fun] >> '}')) // 2.
std::cout << "Bad parsing for " << input << std::endl; // 3.
}

1. It expects in input the string to be parsed and the action that has to be called.
2. Here we are parsing the input using the Spirit Qi parse() function, passing to it the delimiting iterators to the sequence to be checked, and the parser. The function to be called is specified in square brackets.
3. If the parsing fails, we output a log message.

If we want to use as action this free function:
void print(int i) { std::cout << i << std::endl; }
We can use this utility function:
void bsActFFun(const std::string& input)
{
bsGenericAction(input, &print);
}
That should be called like that:
std::string test("{99}");
bsActFFun(test);

If we want to call a member function, like this Writer::print()
class Writer
{
public:
void print(int i) const { std::cout << i << std::endl; }
};
We could use:
void bsActMFun(const std::string& input)
{
Writer w;
auto fun = std::bind(&Writer::print, &w, std::placeholders::_1); // 1.
bsGenericAction(input, fun);
}

1. Maybe is worth spending a few words on the std::bind() usage. With it we are saying to the compiler to use the Writer::print() function associated to the w object, and passing to it the first value that it will be passed to the wrapper. If your compiler does not support yet std::bind(), a C++0x feature, you could rely on the Boost implementation.

Let's consider a functor:
using boost::spirit::qi::unused_type;
class PrintAction
{
public:
void operator()(int i, unused_type, unused_type) const
{
std::cout << i << std::endl;
}
};

As we said above, when using a functor we should specify three parameters. Currently we are interested only in the first one, so we say to Spirit we don't care about the other two in the showed peculiar way.

We use it by calling this function:
void bsActFOb(const std::string& input)
{
bsGenericAction(input, print_action());
}

And finally a lambda function:
void bsActLambda(const std::string& input)
{
using boost::spirit::qi::unused_type;
bsGenericAction(input, [](int i, unused_type, unused_type){ std::cout << i << std::endl; });
}

As a functor, it requires all three parameter specified, even if we don't plan to use them. Your compiler could miss also this feature, this is too a C++0x goodie, and also in this case you could use instead the Boost implementation. Actually, in this case Boost could even look smarter, since it doesn't explicitely ask for the parameters passed to the lambda, we don't have to rely on the unused_type declaration.

The original Boost Spirit Qi documentation provides a supporting C++ source file that I used as a base for this post.

No comments:

Post a Comment