PrevUpHomeNext

Rule Parsers

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:

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.

[Note] Note

The existence of rules means that will probably never have to write a low-level parser. You can just put existing parsers together into rules instead.


PrevUpHomeNext