Search code examples
c++boost-spirit-x3

repeat to std::tuple with known N at compile time


i want to parse a at compile time specified number of elements. I've tried the repeat()[] directive. The following code shows my case:

 using namespace x3;
 std::tuple<float, float, float> tup;
 std::string str{"0.3 0.2 0.1"};
 auto ret = parse(std::begin(str), std::end(str), repeat(3)[ float_ >> (' ' | eol) ] , tup); 

The compiler error message:

error: static assertion failed: Expecting a single element fusion sequence
             static_assert(traits::has_size<Attribute, 1>::value

It works if i would write it out:

parse(std::begin(str), std::end(str), float_ >> ' ' >> float_ >> ' ' >> float_ ] , tup);

but with a large number of elements it is confusion.

Is there a way to shorten the grammar with a 'repeat' directive ?


Solution

  • As you can see here the synthesized attribute of x3::repeat(3)[x3::float_] is a vector<float> and that does not match your attribute (basically a fusion sequence of size 3). Note that the synthesized attribute does not depend on the value you pass.

    In order to get what you want you'll need another directive, one which type does depend on the value you pass. This directive would then generate a sequence where it's subject is repeated N times (simply "delegating" the work to x3::sequence would make sure that everything works correctly in regards to attribute propagation). I can think of at least two ways this could work: something like repeat<N>[parser] or something like repeat(integral_constant<int,N>)[parser]. In the code below I have chosen the second approach using boost::hana::integral_constant, which allows you to use:

    custom::repeat(3_c)[ x3::float_ >> (' ' | x3::eol | x3::eoi) ]
    

    Full Code (Running on WandBox)

    custom_repeat.hpp

    #include <type_traits>
    #include <boost/spirit/home/x3.hpp> 
    
    namespace custom
    {
        struct repeat_gen
        {
            template <int Size>
            struct repeat_gen_lvl1
            {
    
                //using overloads with integral constants to avoid needing to partially specialize a function
    
                //this actually builds the sequence of parsers
                template <typename Parser,int N>
                auto generate_sequence(Parser const& parser, std::integral_constant<int,N>) const
                {
                    return generate_sequence(parser,std::integral_constant<int,N-1>{}) >> parser;
                }
    
                template <typename Parser>
                auto generate_sequence(Parser const parser,std::integral_constant<int,1>) const
                {
                    return parser;
                }
    
                template<typename Subject>
                auto operator[](Subject const& subject) const
                {
                    //here the actual sequence is generated
                    return generate_sequence(boost::spirit::x3::as_parser(subject), std::integral_constant<int,Size>{});
                }
            };
    
            template <typename IntConstant>
            repeat_gen_lvl1<int(IntConstant::value)>
            operator()(IntConstant) const
            {
                //returns an object that know the size of the wanted sequence and has an operator[] that will capture the subject
                return {};
            }
    
            template <typename IntegerType, typename Enable=std::enable_if_t<std::is_integral<IntegerType>::value> >
            auto operator()(IntegerType n) const
            {
                return boost::spirit::x3::repeat(n);
            }
        };
    
        //this object's only purpose is having an operator()
        auto const repeat = repeat_gen{};
    }
    

    main.cpp

    #include <iostream>
    
    #include <boost/spirit/home/x3.hpp>
    
    #include <boost/fusion/include/std_tuple.hpp>
    #include <boost/fusion/include/std_pair.hpp>
    #include <boost/fusion/include/adapt_struct.hpp>
    #include <boost/fusion/include/io.hpp>
    #include <boost/fusion/include/as_vector.hpp>
    #include <boost/fusion/include/is_sequence.hpp>
    
    #include <boost/hana/integral_constant.hpp>
    
    #include <boost/mpl/int.hpp>
    
    #include "custom_repeat.hpp"
    
    namespace x3 = boost::spirit::x3;
    
    using namespace boost::hana::literals;
    
    template <typename T>
    void print_attr(const std::vector<T>& vec)
    {
        std::cout << "Vector: ";
        for(const auto& elem : vec)
            std::cout << "[" << elem << "]";
    }
    
    template <typename Sequence, typename Enable=std::enable_if_t<boost::fusion::traits::is_sequence<Sequence>::value> >
    void print_attr(const Sequence& seq)
    {
        std::cout << "Sequence: " << boost::fusion::as_vector(seq);
    }
    
    template <typename Attr,typename Parser>
    void parse(const std::string& str, const Parser& parser)
    {
        Attr attr;
        std::string::const_iterator iter = std::begin(str), end = std::end(str);
        bool ret = x3::parse(iter, end, parser, attr);
    
        if(ret && (iter==end))
        {
            std::cout << "Success.\n";
            print_attr(attr);
            std::cout << std::endl;
        }
        else
        {
            std::cout << "Something failed. Unparsed: ->|" << std::string(iter,end) << "|<-" << std::endl;
        }
    }
    
    struct float_holder
    {
        float val;
    };
    
    BOOST_FUSION_ADAPT_STRUCT(float_holder,val);
    
    int main()
    {
        boost::mpl::int_<2> two;
        std::integral_constant<int,1> one;
    
        parse<std::tuple<float,float,float> >("0.3 0.2 0.1", custom::repeat(3_c)[ x3::float_ >> (' ' | x3::eol | x3::eoi) ] );
        parse<std::pair<float,float> >("0.2 0.1", custom::repeat(two)[ x3::float_ >> (' ' | x3::eol | x3::eoi) ] );
        parse<float_holder>("0.1", custom::repeat(one)[ x3::float_ >> (' ' | x3::eol | x3::eoi) ] );
    
        parse<std::vector<float> >("0.3 0.2 0.1", custom::repeat(3)[ x3::float_ >> (' ' | x3::eol | x3::eoi) ] );
    
    }