Search code examples
c++parsingboostboost-spiritboost-spirit-qi

How to parse text into a struct using boost::spirit?


I'm learning boost::spirit, and I'm trying to read and parse some text into a struct.

For example, "2: 4.6" is parsed as int 2 and double 4.6 in my TestStruct below:

#include <iostream>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/support_istream_iterator.hpp>
#include <boost/fusion/include/std_pair.hpp>
namespace qi = boost::spirit::qi;

struct TestStruct {
  int myint;
  double mydouble;
  TestStruct() {}
  TestStruct(std::pair<int,double> p) : myint(p.first), mydouble(p.second) {}
};

template <typename Iterator, typename Skipper>
struct MyGrammar : qi::grammar<Iterator, TestStruct(), Skipper> {
  MyGrammar() : MyGrammar::base_type(mystruct) {
    mystruct0 = qi::int_ >> ":" >> qi::double_;
    mystruct = mystruct0;
  }
  qi::rule<Iterator, std::pair<int,double>(), Skipper> mystruct0;
  qi::rule<Iterator, TestStruct(), Skipper> mystruct;
};

int main() {
  typedef boost::spirit::istream_iterator It;
  std::cin.unsetf(std::ios::skipws);
  It it(std::cin), end; // input example: "2: 3.4"                                                                              

  MyGrammar<It, qi::space_type> gr;
  TestStruct ts;
  if (qi::phrase_parse(it, end, gr, qi::space, ts) && it == end)
    std::cout << ts.myint << ", " << ts.mydouble << std::endl;
  return 0;
}

It works nicely, but I'm wondering how this code can be simplified?

For example, I would like to get rid of the mystruct0 grammar rule, which is only there to mark the type std::pair<int,double>, which is then used to automatically construct the TestStruct object from the mystruct rule.

I'd also like to be able to get rid of the TestStruct constructor from std::pair, if possible.

So, can the following code somehow made to be compiled? That would be a much nicer solution:

struct TestStruct {
  int myint;
  double mydouble;
  TestStruct() {}
  TestStruct(int i, double d) : myint(i), mydouble(d) {}
};

template <typename Iterator, typename Skipper>
struct MyGrammar : qi::grammar<Iterator, TestStruct(), Skipper> {
  MyGrammar() : MyGrammar::base_type(mystruct) {
    mystruct = qi::int_ >> ":" >> qi::double_;
  }
  qi::rule<Iterator, TestStruct(), Skipper> mystruct;
};

int main() {
  typedef boost::spirit::istream_iterator It;
  std::cin.unsetf(std::ios::skipws);
  It it(std::cin), end; // input example: "2: 3.4"                                                                              

  MyGrammar<It, qi::space_type> gr;
  TestStruct ts;
  if (qi::phrase_parse(it, end, gr, qi::space, ts) && it == end)
    std::cout << ts.myint << ", " << ts.mydouble << std::endl;
  return 0;
}

Unfortunately, the compiler says:

boost_1_49_0/include/boost/spirit/home/qi/detail/assign_to.hpp:123: 
error: no matching function for call to ‘TestStruct::TestStruct(const int&)’

Solution

  • Yes that code can be compiled. In fact, you can do without the constructors: the default (compiler generated) constructor is fine.

    All you need to do is adapt your struct as a fusion sequence. (As a bonus, this works for karma as well.)
    This is precisely the magic that made std::pair work in the first place.

    #include <iostream>
    #include <boost/spirit/include/qi.hpp>
    #include <boost/fusion/adapted/struct.hpp>
    namespace qi = boost::spirit::qi;
    
    struct TestStruct {
        int myint;
        double mydouble;
    };
    
    BOOST_FUSION_ADAPT_STRUCT(TestStruct, (int, myint)(double, mydouble));
    
    template <typename Iterator, typename Skipper>
    struct MyGrammar : qi::grammar<Iterator, TestStruct(), Skipper> {
        MyGrammar() : MyGrammar::base_type(mystruct) {
            mystruct = qi::int_ >> ":" >> qi::double_;
        }
        qi::rule<Iterator, TestStruct(), Skipper> mystruct;
    };
    
    int main() {
        typedef std::string::const_iterator It;
        const std::string input("2: 3.4");
        It it(input.begin()), end(input.end());
    
        MyGrammar<It, qi::space_type> gr;
        TestStruct ts;
    
        if (qi::phrase_parse(it, end, gr, qi::space, ts) && it == end)
            std::cout << ts.myint << ", " << ts.mydouble << std::endl;
    
        return 0;
    }