Search code examples
boostboost-spiritboost-spirit-x3

Spirit.X3 : using push_back_container traits with list parser


I have a class having public ctor and some add() method:

class object
{
    object() {}
    template <typename>
    void add(T&& val) { // some adding here}
}

The main question I'm faced is how can I adopt spirit.x3 list parser to use object::add() method instead of std::vector<>::push_back ?

I was easily able to achieve what I need with simple

x3::int_ % ','

parser (live demo) using the following code :

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

namespace x3 = boost::spirit::x3;

namespace parse_to_object
{
    struct object
    {
        using value_type = int;

        object() { std::cout << "object::object() - invoked" << std::endl; }
        void add(value_type val) { _data.push_back(val); }

        std::vector<value_type> _data;
    };

    const x3::rule<struct Test, object> r_ints("r_ints");
    const auto r_ints_def = x3::int_  % ',';
    BOOST_SPIRIT_DEFINE(r_ints);
}

namespace boost { namespace spirit { namespace x3 { namespace traits {        
template<>
struct push_back_container<parse_to_object::object>
{
    template<typename T>
    static bool call(parse_to_object::object& obj, T&& val)
    {
        obj.add(std::move(val));
        return true;
    }
};
}}}}

int main()
{
    const std::string text("1,2,3,4");

    auto begin = std::begin(text);
    const auto end = std::end(text);

    parse_to_object::object result;
    const auto ok = x3::phrase_parse(begin, end, parse_to_object::r_ints,    x3::space, result);

    std::cout << "ok = " << std::boolalpha << (ok && begin == end) << std::endl;
    std::copy(result._data.begin(), result._data.end(), std::ostream_iterator<int>(std::cout, " "));
    return 0;
}

But unfortunately, when I tried more compilcated example like

'{' >> x3::int_ >> ':' >> x3::int_ >> '}') % ','

I'm getting the compilation error (live demo) :

/opt/wandbox/boost-1.67.0/clang-head/include/boost/spirit/home/x3/support/traits/container_traits.hpp:102:45: error: no type named 'iterator' in 'parse_to_object::object' : mpl::identity {};

Could somebody assist with spirit.x3 traits and give some example how to abopt custom class to be used instead of std::vector<> for list parser ?


Solution

  • In the end it's down to a missing include:

    #include <boost/fusion/adapted/std_pair.hpp>
    

    std::pair isn't adapted by default.

    Side note: std::move should be std::forward<T> with "universal references" (or perfect forwarding)

    Live On Coliru

    #define BOOST_SPIRIT_X3_DEBUG
    
    #include <boost/fusion/adapted/std_pair.hpp>
    #include <boost/spirit/home/x3.hpp>
    #include <iostream>
    #include <vector>
    
    namespace x3 = boost::spirit::x3;
    
    namespace parse_to_object
    {
        struct object
        {
            using value_type = std::pair<int,int>;
    
            object() { std::cout << "object::object() - invoked" << std::endl; }
            void add(value_type val) { _data.push_back(std::move(val)); }
    
            std::vector<std::pair<int,int>> _data;
        };
    
        const x3::rule<struct Test, object> r_ints("r_ints");
        const auto r_ints_def = ('{' >> x3::int_ >> ':' >> x3::int_ >> '}') % ',';
        BOOST_SPIRIT_DEFINE(r_ints)
    }
    
    namespace boost { namespace spirit { namespace x3 { namespace traits {
    
        template<> struct push_back_container<parse_to_object::object>
        {
            template<typename T>
            static bool call(parse_to_object::object& obj, T&& val)
            {
                obj.add(std::forward<T>(val));
                return true;
            }
        };
    
    }}}}
    
    int main()
    {
        const std::string text("{1:2},{3:4}");
    
        auto begin = std::begin(text), end = std::end(text);
    
        parse_to_object::object result;
        auto ok = phrase_parse(begin, end, parse_to_object::r_ints >> x3::eoi, x3::space, result);
    
        std::cout << "ok = " << std::boolalpha << ok << "\n";
        for (auto& p : result._data)
            std::cout << "{" << p.first << ", " << p.second << "} ";
        std::cout << "\n";
    }
    

    Prints

    object::object() - invoked
    <r_ints>
      <try>{1:2},{3:4}</try>
      <success></success>
      <attributes></attributes>
    </r_ints>
    ok = true
    {1, 2} {3, 4}