betabandido
betabandido

Reputation: 19704

Boost::spirit does not recognize an optional expression

I am learning how to use boost::spirit and I am facing some problems even with quite simple parsers. I am trying to construct a parser that accepts a list of digits (only 0 or 1) separated by colons. The list can have 3 or 4 digits. Thus, 0:0:0 and 1:0:1:0 are valid, while for instance 0:0 or 0:0:0:0:0 are not.

In the following code you can see how I used the optional operator to specify that the first digit may exist or not. It, however, does not work (parsing fails for sequence 0:0:0). Is there anything wrong in the code? I would say it is correct, but again, I just started learning Spirit.

#include <boost/spirit/include/qi.hpp>

namespace qi = boost::spirit::qi;
namespace phoenix = boost::phoenix;

void parse_tuple(const std::string& tuple) {
    using qi::char_;

    auto begin = tuple.begin();
    auto end = tuple.end();

    bool r = qi::parse(begin, end,
            -(char_('0', '1') >> ':') >> 
              char_('0', '1') >> ':' >>
              char_('0', '1') >> ':' >>
              char_('0', '1')
            );
    if (!r || begin != end)
        throw std::runtime_error("wrong format");
}

int main() {
    parse_tuple("0:0:0"); // It fails for this one
    parse_tuple("0:0:0:0");

    try { parse_tuple("0:0"); } catch (...) {
        std::cout << "expected error\n"; }
    try { parse_tuple("0:0:0:0:0"); } catch (...) {
        std::cout << "expected error\n"; }
}

Upvotes: 1

Views: 984

Answers (2)

sehe
sehe

Reputation: 393829

This would be the most straightforward solution:

bool r = qi::parse(begin, end,
        char_("01") > ':' >
        char_("01") > ':' >
        char_("01") > -(':' > char_("01"))
        );

Full sample http://liveworkspace.org/code/3U0QJW$0:

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>

namespace qi = boost::spirit::qi;

void parse_tuple(const std::string& tuple) {
    using qi::char_;

    auto begin = tuple.begin();
    auto end = tuple.end();

    bool r = qi::parse(begin, end,
            char_("01") > ':' >
            char_("01") > ':' >
            char_("01") > -(':' > char_("01"))
            );
    if (!r || begin != end)
        throw std::runtime_error("wrong format");
}

int main() {
    parse_tuple("0:0:0"); // It fails for this one
    parse_tuple("0:0:0:0");

    try { parse_tuple("0:0"); } catch (...) {
        std::cout << "expected error\n"; }
    try { parse_tuple("0:0:0:0:0"); } catch (...) {
        std::cout << "expected error\n"; }
}

Upvotes: 0

sbabbi
sbabbi

Reputation: 11201

Here

bool r = qi::parse(begin, end,
        -(char_('0', '1') >> ':') >> 
          char_('0', '1') >> ':' >>
          char_('0', '1') >> ':' >>
          char_('0', '1')
        );

The optional char_ should be the last one and not the first one. The rules are applied sequentially, so that when you are parsing "0:0:0", the first line of your code (the optional stuff) pass the test, and then your rule expects 3 digits to follow, not two.

In my opinion you should just use the % operator to match a list and check later if you parsed 3 or 4 elements.

EDIT OR use qi::repeat to improve the readability.

Upvotes: 1

Related Questions