PrevUpHomeNext

Semantic Actions

Like all parsing systems (lex & yacc, Boost.Spirit, etc.), Boost.Parser has a mechanism for associating semantic actions with different parts of the parse. Here is nearly the same program as we saw in the previous example, except that it is implemented in terms of a semantic action that appends each parsed double to a result, instead of automatically building and returning the result. To do this, we replace the double_ from the previous example with double_[action]; action is our semantic action:

#include <boost/parser/parser.hpp>

#include <iostream>
#include <string>


namespace bp = boost::parser;

int main()
{
    std::cout << "Enter a list of doubles, separated by commas. ";
    std::string input;
    std::getline(std::cin, input);

    std::vector<double> result;
    auto const action = [&result](auto & ctx) {
        std::cout << "Got one!\n";
        result.push_back(_attr(ctx));
    };
    auto const action_parser = bp::double_[action];
    auto const success = bp::parse(input, action_parser % ',', bp::ws);

    if (success) {
        std::cout << "You entered:\n";
        for (double x : result) {
            std::cout << x << "\n";
        }
    } else {
        std::cout << "Parse failure.\n";
    }
}

Run in a shell, it looks like this:

$ example/semantic_actions
Enter a list of doubles, separated by commas. 4,3
Got one!
Got one!
You entered:
4
3

In Boost.Parser, semantic actions are implemented in terms of invocable objects that take a single parameter to a parse-context object. The parse-context object represents the current state of the parse. In the example we used this lambda as our invocable:

auto const action = [&result](auto & ctx) {
    std::cout << "Got one!\n";
    result.push_back(_attr(ctx));
};

We're both printing a message to std::cout and recording a parsed result in the lambda. It could do both, either, or neither of these things if you like. The way we get the parsed double in the lambda is by asking the parse context for it. _attr(ctx) is how you ask the parse context for the attribute produced by the parser to which the semantic action is attached. There are lots of functions like _attr() that can be used to access the state in the parse context. We'll cover more of them later on. The Parse Context defines what exactly the parse context is and how it works.

Note that you can't write an unadorned lambda directly as a semantic action. Otherwise, the compile will see two '[' characters and think it's about to parse an attribute. Parentheses fix this:

p[([](auto & ctx){/*...*/})]

Before you do this, note that the lambdas that you write as semantic actions are almost always generic (having an auto & ctx parameter), and so are very frequently re-usable. Most semantic action lambdas you write should be written out-of-line, and given a good name. Even when they are not reused, named lambdas keep your parsers smaller and easier to read.

[Important] Important

Attaching a semantic action to a parser removes its attribute. That is, ATTR(p[a]) is always the special no-attribute type none, regardless of what type ATTR(p) is.

Semantic actions inside rules

There are some other forms for semantic actions, when they are used inside of rules. See More About Rules for details.


PrevUpHomeNext