Search code examples
c++boostboost-spiritboost-spirit-qiboost-spirit-x3

Boost.Spirit: porting string pairs from Qi to X3


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);
}

Solution

  • 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