Boost.Spirit: porting string pairs from Qi to X3

487 Views Asked by At

I have the following working Qi code:

struct query_grammar
    : public boost::spirit::qi::grammar<Iterator, string_map<std::string>()>
{
  query_grammar() : query_grammar::base_type(query)
  {
    query = pair >> *(boost::spirit::qi::lit('&') >> pair);
    pair = +qchar >> -(boost::spirit::qi::lit('=') >> +qchar);
    qchar = ~boost::spirit::qi::char_("&=");
  }

  boost::spirit::qi::rule<Iterator, std::map<std::string,std::string>()> query;
  boost::spirit::qi::rule<Iterator, std::map<std::string,std::string>::value_type()> pair;
  boost::spirit::qi::rule<Iterator, char()> qchar;
};

I tried porting it to x3:

namespace x3 = boost::spirit::x3;
const x3::rule<class query_char_, char> query_char_ = "query_char";
const x3::rule<class string_pair_, std::map<std::string,std::string>::value_type> string_pair_ = "string_pair";
const x3::rule<class string_map_, std::map<std::string,std::string>> string_map_ = "string_map";

const auto query_char__def = ~boost::spirit::x3::char_("&=");
const auto string_pair__def = +query_char_ >> -(boost::spirit::x3::lit('=') >> +query_char_);
const auto string_map__def = string_pair_ >> *(boost::spirit::x3::lit('&') >> string_pair_);

BOOST_SPIRIT_DEFINE(string_map_)
BOOST_SPIRIT_DEFINE(string_pair_)
BOOST_SPIRIT_DEFINE(query_char_)

but I am getting the following error when trying to parse a string with string_map_ :

/usr/include/boost/spirit/home/x3/support/traits/move_to.hpp:209: erreur : no matching function for call to move_to(const char*&, const char*&, std::pair<std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> >&, boost::mpl::identity<boost::spirit::x3::traits::plain_attribute>::type) 
     detail::move_to(first, last, dest, typename attribute_category<Dest>::type());
     ~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

I saw this answer: Parsing pair of strings fails. Bad spirit x3 grammar and tried to make my string_pair raw but to no avail.


Edit:

this example code from the spirit examples does not compile either so I guess the problem is a bit deeper:

#include <boost/spirit/home/x3.hpp>

namespace x3 = boost::spirit::x3;
int main()
{
  std::string input( "cosmic  pizza  " );
  auto iter = input.begin();
  auto end_iter = input.end();
  std::pair<std::string, std::string> result;
  x3::parse( iter, end_iter, *(~x3::char_(' ')) >> ' ' >> *x3::char_, result);
}
1

There are 1 best solutions below

4
On BEST ANSWER

Qi Fixes

First off, I had to fix the rule declaration with the Qi variant before it could work:

qi::rule<Iterator, std::pair<std::string,std::string>()> pair;

For the simple reason that value_type has pair<key_type const, mapped_type> which is never assignable.

Here's a Qi SSCCE:

Live On Coliru

#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/adapted/std_pair.hpp>
#include <map>

namespace qi = boost::spirit::qi;

template <typename T> using string_map = std::map<T, T>;

template <typename Iterator>
struct query_grammar : public qi::grammar<Iterator, string_map<std::string>()>
{
    query_grammar() : query_grammar::base_type(query)
    {
        qchar = ~qi::char_("&=");
        pair  = +qchar >> -(qi::lit('=') >> +qchar);
        query = pair >> *(qi::lit('&') >> pair);
    }

  private:
    qi::rule<Iterator, std::map<std::string,std::string>()> query;
    qi::rule<Iterator, std::pair<std::string,std::string>()> pair;
    qi::rule<Iterator, char()> qchar;
};

int main() {
    using It = std::string::const_iterator;
    for (std::string const input : { "foo=bar&baz=boo" })
    {
        std::cout << "======= " << input << "\n";
        It f = input.begin(), l = input.end();
        string_map<std::string> sm;
        if (parse(f, l, query_grammar<It>{}, sm)) {
            std::cout << "Parsed " << sm.size() << " pairs\n";
        } else {
            std::cout << "Parse failed\n";
        }

        if (f != l)
            std::cout << "Remaining unparsed: '" << std::string(f,l) << "'\n";
    }
}

Prints

======= foo=bar&baz=boo
Parsed 2 pairs

Qi Improvements

The following simpler grammar seems better:

Live On Coliru

template <typename Iterator, typename T = std::string>
struct query_grammar : public qi::grammar<Iterator, string_map<T>()>
{
    query_grammar() : query_grammar::base_type(query) {
        using namespace qi;
        pair  =  +~char_("&=") >> '=' >> *~char_("&");
        query = pair % '&';
    }

  private:
    qi::rule<Iterator, std::pair<T,T>()> pair;
    qi::rule<Iterator, std::map<T,T>()> query;
};

It accepts empty values (e.g. &q=&x=) and values containing additional =: &q=7==8&rt=bool. It could be significantly more efficient (untested).

X3 version

Without looking at your code, I translated it directly into an X3 version:

Live On Coliru

#include <boost/spirit/home/x3.hpp>
#include <boost/fusion/adapted/std_pair.hpp>
#include <iostream>
#include <map>

namespace x3 = boost::spirit::x3;

template <typename T> using string_map = std::map<T, T>;

namespace grammar {
    using namespace x3;
    auto pair  =  +~char_("&=") >> '=' >> *~char_("&");
    auto query = pair % '&';
}

int main() {
    using It = std::string::const_iterator;
    for (std::string const input : { "foo=bar&baz=boo" })
    {
        std::cout << "======= " << input << "\n";
        It f = input.begin(), l = input.end();
        string_map<std::string> sm;
        if (parse(f, l, grammar::query, sm)) {
            std::cout << "Parsed " << sm.size() << " pairs\n";
        } else {
            std::cout << "Parse failed\n";
        }

        if (f != l)
            std::cout << "Remaining unparsed: '" << std::string(f,l) << "'\n";
    }
}

Which, obviously ( --- ) prints

======= foo=bar&baz=boo
Parsed 2 pairs

X3 Improvements

You should probably want to coerce the attribute types for the rules because automatic attribute propagation can have surprising heuristics.

namespace grammar {

    template <typename T = std::string> auto& query() {
        using namespace x3;

        static const auto s_pair  
            = rule<struct pair_, std::pair<T, T> > {"pair"} 
            = +~char_("&=") >> -('=' >> *~char_("&"));
        static const auto s_query
            = rule<struct query_, std::map<T, T> > {"query"}
            = s_pair % '&';

        return s_query;
    };

}

See it Live On Coliru

What Went wrong?

The X3 version suffered the same problem with const key type in std::map<>::value_type