This example is very similar to the others we've seen so far. This one is
different only because it uses a rule
. As an analogy, think
of a parser like char_
or double_
as an individual line of code, and a rule
as a function. Like a
function, a rule
has its own name, and can even be forward declared. Here is how we define
a rule
,
which is analogous to forward declaring a function:
bp::rule<struct doubles, std::vector<double>> doubles = "doubles";
This declares the rule itself. The rule
is a parser, and we can
immediately use it in other parsers. That definition is pretty dense; take
note of these things:
struct
doubles
. Here we've declared
the tag type and used it all in one go; you can also use a previously
declared tag type.
doubles
.
doubles
the
diagnstic text "doubles"
so that Boost.Parser knows how to refer to it when producing a trace
of the parser during debugging.
Ok, so if doubles
is a parser,
what does it do? We define the rule's behavior by defining a separate parser
that by now should look pretty familiar:
auto const doubles_def = bp::double_ % ',';
This is analogous to writing a definition for a forward-declared function.
Note that we used the name doubles_def
.
Right now, the doubles
rule
parser and the doubles_def
non-rule parser have no connection to each other. That's intentional —
we want to be able to define them separately. To connect them, we declare
functions with an interface that Boost.Parser understands, and use the tag
type struct doubles
to connect them together. We use a macro for that:
BOOST_PARSER_DEFINE_RULES(doubles);
This macro expands to the code necessary to make the rule doubles
and its parser doubles_def
work together. The _def
suffix
is a naming convention that this macro relies on to work. The tag type allows
the rule parser, doubles
,
to call one of these overloads when used as a parser.
BOOST_PARSER_DEFINE_RULES
expands to two overloads of a function called parse_rule()
. In the case above, the overloads each
take a struct doubles
parameter (to distinguish them from the other overloads of parse_rule()
for other rules) and parse using doubles_def
.
You will never need to call any overload of parse_rule()
yourself; it is used internally by the
parser that implements rules
, rule_parser
.
Here is the definition of the macro that is expanded for each rule:
#define BOOST_PARSER_DEFINE_IMPL(_, rule_name_) \ template< \ typename Iter, \ typename Sentinel, \ typename Context, \ typename SkipParser> \ decltype(rule_name_)::parser_type::attr_type parse_rule( \ decltype(rule_name_)::parser_type::tag_type *, \ Iter & first, \ Sentinel last, \ Context const & context, \ SkipParser const & skip, \ boost::parser::detail::flags flags, \ bool & success, \ bool & dont_assign) \ { \ auto const & parser = BOOST_PARSER_PP_CAT(rule_name_, _def); \ using attr_t = \ decltype(parser(first, last, context, skip, flags, success)); \ using attr_type = decltype(rule_name_)::parser_type::attr_type; \ if constexpr (boost::parser::detail::is_nope_v<attr_t>) { \ dont_assign = true; \ parser(first, last, context, skip, flags, success); \ return {}; \ } else if constexpr (std::is_same_v<attr_type, attr_t>) { \ return parser(first, last, context, skip, flags, success); \ } else if constexpr (std::is_constructible_v<attr_type, attr_t>) { \ return attr_type( \ parser(first, last, context, skip, flags, success)); \ } else { \ attr_type attr{}; \ parser(first, last, context, skip, flags, success, attr); \ return attr; \ } \ } \ \ template< \ typename Iter, \ typename Sentinel, \ typename Context, \ typename SkipParser, \ typename Attribute> \ void parse_rule( \ decltype(rule_name_)::parser_type::tag_type *, \ Iter & first, \ Sentinel last, \ Context const & context, \ SkipParser const & skip, \ boost::parser::detail::flags flags, \ bool & success, \ bool & dont_assign, \ Attribute & retval) \ { \ auto const & parser = BOOST_PARSER_PP_CAT(rule_name_, _def); \ using attr_t = \ decltype(parser(first, last, context, skip, flags, success)); \ if constexpr (boost::parser::detail::is_nope_v<attr_t>) { \ parser(first, last, context, skip, flags, success); \ } else { \ parser(first, last, context, skip, flags, success, retval); \ } \ }
Now that we have the doubles
parser, we can use it like we might any other parser:
auto const result = bp::parse(input, doubles, bp::ws);
The full program:
#include <boost/parser/parser.hpp> #include <deque> #include <iostream> #include <string> namespace bp = boost::parser; bp::rule<struct doubles, std::vector<double>> doubles = "doubles"; auto const doubles_def = bp::double_ % ','; BOOST_PARSER_DEFINE_RULES(doubles); int main() { std::cout << "Please enter a list of doubles, separated by commas. "; std::string input; std::getline(std::cin, input); auto const result = bp::parse(input, doubles, bp::ws); if (result) { std::cout << "You entered:\n"; for (double x : *result) { std::cout << x << "\n"; } } else { std::cout << "Parse failure.\n"; } }
All this is intended to introduce the notion of rules
. It still may be a bit
unclear why you would want to use rules
. The use cases for, and
lots of detail about, rules
is in a later section,
More About Rules.