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 | |
---|---|
Attaching a semantic action to a parser removes its attribute. That is,
|
There are some other forms for semantic actions, when they are used inside
of rules
.
See More About Rules
for details.