C++ Blog

Boost.Spirit: Semantic Actions

Posted in boost, Uncategorized by Umesh Sirsiwal on January 1, 2010

Note that this post applies to Spirit.Classic or 2.0.

In the first two posts we introduced basic parsing techniques defined by Spirit. The parsing is only useful, if we can do something with parsing results. In Spirit you achieve this with semantic actions.

Semantic actions are expected to use functional programming paradigms. The most basic semantic action has the following prototype:

 void f(IteratorT first, IteratorT last);

or as functor

  struct my_functor
    {
        void operator()(IteratorT first, IteratorT last) const;
    };

Here is an example of simple action from Spirit user guide:

 void
    my_action(char const* first, char const* last)
    {
        std::string str(first, last);
        std::cout << str << std::endl;
    }

Applying the action is rather simple. You specify them in [] after the rule. From previous e-mail address parser, you can write:

 
    r = *(mailTo | anychar_p);
    mailTo = "mailTo:" >> emailAddress[&my_action];
    emailAddress = lexeme_d[ +alnum_p >> '@' >> +alnum_p >> *('.' >> +alnum_p)];

my_action will be called with iterator pointing to start and end of the parsed e-mail address. The above action will result in printing all the e-mail addresses in the input.

In a lot of cases, it is wasteful to call actions with the Iterators pointing to first and last. After all, Spirit has just parsed the contents. For this purpose the boost defines specialized actions. For example

  void func(NumT val);

or equivalent functor

struct fctr
    {
        void operator()(NumT val) const;
    };

can be applied to any numeric parsers (real_p, ureal_p, int_p, uint_p). Similar actions exist for other other types of parsers. Please check Spirit guide for details.

The complete program looks like:

#include <boost/spirit/core.hpp>
#include <iostream>
using namespace boost::spirit;

void
my_action(const char* first, const char* last)
{
std::string str(first, last);
std::cout << str << std::endl;
}

struct my_grammar : public grammar<my_grammar>
{
template <typename ScannerT>
struct definition
{
rule<ScannerT>  r, mailTo, emailAddress;
definition(my_grammar const& self)  {
r = *(mailTo | anychar_p);
mailTo = “mailTo:” >> emailAddress[&my_action];
emailAddress = lexeme_d[ +alnum_p >> ‘@’ >> +alnum_p >> *(‘.’ >> +alnum_p)];
}
rule<ScannerT> const& start() const { return r; }
};
};

int main(){
const char* str = “mailTo:a@b.com  test mailTo:d@f.com”;
my_grammar g;
if (parse(str, str + strlen(str), g, space_p).full)
std::cout << “parsing succeeded\n”;
else
std:: cout << “parsing failed\n”;
return 0;
}
In addition, there is a large selection of pre-defined actions. You an find them here.

Advertisements