ztik
ztik

Reputation: 3612

boost::spirit::qi rule reduce parsing error

I try to parse a list of integer and double pairs using boost::spirit::qi. An integer precedes and follows to this list. An example of this list is:

20 1 1.3 2 2.3 30

The structure I want to create is:

typedef std::vector< std::pair<int, double> > vec_t;
struct list_s{
  int first;
  std::vector< std::pair<int, double> > list;
  int last;
};

Also I added error handling to my parser using qi::on_error and converting all sequences >> to expects >.

My implementation is:

#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/fusion/adapted/std_pair.hpp>
#include <boost/fusion/include/adapt_struct.hpp>

#include <iostream>
#include <string>
#include <vector>
#include <tuple>

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

typedef std::vector< std::pair<int, double> > vec_t;
struct list_s{
  int first;
  vec_t list;
  int last;
};

BOOST_FUSION_ADAPT_STRUCT(
    list_s,
    (int, first)
    (vec_t, list)
    (int, last)
)

template <typename Iterator>
struct list_parser : qi::grammar<Iterator, list_s(), ascii::space_type >
{
  public:
    list_parser() : list_parser::base_type(r) {
      r = qi::eps >>
        (
          qi::int_
          > *( qi::int_ > qi::double_ )
          > qi::int_
        );

      r.name("r");
      qi::on_error<qi::fail>
        (
         r
         , std::cout
         << phoenix::val("\nError! Expecting ")
         << qi::labels::_4 
         << phoenix::val(" here: \"")
         << phoenix::construct<std::string>(qi::labels::_3, qi::labels::_2)
         << phoenix::val("\"\n\n")
        );
    }
  private:
    qi::rule<Iterator, list_s(), ascii::space_type > r;
};

int main() {
  std::string str("20 1 1.3 2 2.3 30");

  list_s r;
  list_parser<std::string::const_iterator> p;
  std::string::const_iterator iter = str.begin();
  std::string::const_iterator end = str.end();
  if( qi::phrase_parse(iter, end, p, ascii::space, r) ){
    std::cout << r.first << std::endl;
    for(auto& i : r.list) std::cout << i.first << " , " << i.second << std::endl;
    std::cout << r.last << std::endl;
  }
  return 0;
}

When running this example I get

Error! Expecting <real> here: ""

I know I can change the list parsing rule to sequence

*( qi::int_ >> qi::double_ )

to overcome the above error. But then the parsing of 20 1 1.3 2 error 30 is also successful. The result would be:

20
1 , 1.3
2

Any suggestion to overcome this problem?

Upvotes: 2

Views: 214

Answers (1)

sehe
sehe

Reputation: 392833

Expectation points create non-backtracking expectations.

So you cannot accept an alternative branch after it failed.

This means that your last int will be match by the int in (int_ > double_). However that expression requires (>) that double_ follows, and since it doesn't, it throws.

Replace by > there >>.

In fact, it looks like you just want to make sure that the whole input/line is consumed, so make your expectation explicit:

    r = (
            qi::int_
            > *( qi::int_ >> qi::double_ )
            > qi::int_
        )
        > qi::eoi
        ;

Demo

Live On Coliru

Note: also enabled rule debug (implicitly names the rule):

#define BOOST_SPIRIT_DEBUG
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/fusion/include/adapted.hpp>

#include <iostream>

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

typedef std::vector<std::pair<int, double>> vec_t;

struct list_s{
  int first;
  vec_t list;
  int last;
};

BOOST_FUSION_ADAPT_STRUCT(list_s, first, list, last)

template <typename Iterator>
struct list_parser : qi::grammar<Iterator, list_s(), ascii::space_type>
{
  public:
    list_parser() : list_parser::base_type(r) {
        r = (
                qi::int_
                > *( qi::int_ >> qi::double_ )
                > qi::int_
            )
            > qi::eoi
            ;

      BOOST_SPIRIT_DEBUG_NODES((r))

      qi::on_error<qi::fail>
        (
            r
            , std::cout
            << phoenix::val("\nError! Expecting ")
            << qi::labels::_4 
            << phoenix::val(" here: \"")
            << phoenix::construct<std::string>(qi::labels::_3, qi::labels::_2)
            << phoenix::val("\"\n\n")
        );
    }
  private:
    qi::rule<Iterator, list_s(), ascii::space_type > r;
};

int main() {
    std::string str("20 1 1.3 2 2.3 30");

    using It = std::string::const_iterator;

    list_parser<It> p;
    It iter = str.begin(), end = str.end();

    list_s data;
    if (qi::phrase_parse(iter, end, p, ascii::space, data)){
        std::cout << data.first << "\n";

        for(auto& i : data.list) 
            std::cout << i.first << ", " << i.second << "\n";

        std::cout << data.last << "\n";
    }
}

Prints:

20
1, 1.3
2, 2.3
30

And debug output:

<r>
    <try>20 1 1.3 2 2.3 30</try>
    <success></success>
    <attributes>[[20, [[1, 1.3], [2, 2.3]], 30]]</attributes>
</r>

For error input:

Live On Coliru

Error! Expecting <eoi> here: "error 30"

With debug output:

<r>
  <try>20 1 1.3 2 error 30</try>
  <fail/>
</r>

Upvotes: 1

Related Questions