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

Need a way to prefix boost::spirit::qi parser with another one


I have a lot of rules that look like this:

cmd_BC = (dlm > timestamp > dlm > cid > dlm > double_)
         [
             _val = lazy_shared<dc::BoardControl>(_1, _2, _3)
         ];

I want to make it more readable, like:

cmd_BC = param(timestamp) > param(cid) > param(double_)

or even

cmd_BC = params(timestamp, cid, double_)

As sehe pointed out, it boils down to having some means to automatically expect the delimiters. What are the options here? Myself, I see three possibilities, all flawed:

  1. Use a macro. This wouldn't allow for the shorter variadic form.
  2. Write a custom prefix directive. I don't seem to have enough experience in Spirit's clockwork, but if it's actually not that hard, I will try to.
  3. Write a wrapper function. I tried to no luck the following code:

    template <typename T>
    auto param(const T & parser) -> decltype(qi::lit(dlm) > parser)
    {
        return qi::lit(dlm) > parser;
    }
    

    but it doesn't compile, failing at

        // report invalid argument not found (N is out of bounds)
        BOOST_SPIRIT_ASSERT_MSG(
            (N < sequence_size::value),
            index_is_out_of_bounds, ());
    

    I also tried to return (...).alias(), but it didn't compile too.


Solution

  • This solution is not very "spirit-y" and unfortunately "requires C++11" (I'm not sure how to get the required result type in c++03) but it seems to work. Inspired by the example here.

    PS: Oh I didn't see your edit. You have almost the same example.

    Update: Added another test using a semantic action with _1,_2 and _3
    Update2: Changed the signature of operator() and operator[] following vines's advice in the comments.

    Update 3: Added a variadic operator() that constructs a combined parser, and removed operator[] after finding a better solution with boost::proto. Changed slightly the examples.

    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    #include <boost/proto/proto.hpp>
    
    namespace qi = boost::spirit::qi;
    namespace proto = boost::proto;
    namespace phx = boost::phoenix;
    using namespace boost::proto;
    
    //This is a proto grammar/transform that creates the prefixed parser. The parser created depends on the parser passed (if it's a kleene or not)
    // in _make_xxx  "_" corresponds to the supplied parser and "_state" to the delimiter
    struct CreatePrefixedParser: //you can use _make_greater instead of _make_shift_right if you want to use "expectation"
    or_ <
        when < dereference<_>, //If it's a kleene parser...
            _make_shift_right ( //create the parser -> dlm >> *(parser -dlm)
                _state,
                _make_dereference (
                    _make_minus ( _child_c<0> ( _ ),
                        _state ) ) ) > ,
        when < unary_plus<_>, //If it's a +parser
            _make_shift_right ( //create the parser -> dlm >> +(parser -dlm)
                _state,
                _make_unary_plus (
                    _make_minus ( _child_c<0> ( _ ),
                        _state ) ) ) > ,
        otherwise < //if it's any other parser
            _make_shift_right ( //create the parser -> dlm >> (parser -dlm)
                _state,
                _make_minus ( _,
                    _state ) ) >
    > {};
    
    //-------------------------------------------------------------
    //this combines the parsers this way: parser1, parser2, parser3, parser4 -> parser1>>(parser2 >>(parser3 >> parser4)))
    //you can use make_expr<tag::greater> if you want to use "expectation"
    //I have absolutely no idea when "deep_copy" is required but it seems to work this way
    template<typename Delim, typename First, typename ... Rest>
    struct myparser
    {
        static auto combine ( Delim dlm_, const First& first, const Rest&...rest ) ->
        decltype ( make_expr<tag::shift_right> ( CreatePrefixedParser() ( deep_copy ( first ), dlm_ ), myparser<Delim, Rest...>::combine ( dlm_, rest... ) ) )
        {
            return make_expr<tag::shift_right> ( CreatePrefixedParser() ( deep_copy ( first ), dlm_ ), myparser<Delim, Rest...>::combine ( dlm_, rest... ) );
        }
    
    };
    
    template<typename Delim, typename Last>
    struct myparser<Delim, Last>
    {
    
        static auto combine ( Delim dlm_, const Last& last ) -> decltype ( CreatePrefixedParser() ( deep_copy ( last ), dlm_ ) )
        {
            return CreatePrefixedParser() ( deep_copy ( last ), dlm_ );
        }
    };
    //-----------------------------------------------------------------
    
    template <typename T>
    struct prefixer
    {
        T dlm_;
        prefixer ( T dlm ) : dlm_ ( dlm ) {}
    
        template <typename ... Args>
        auto operator() ( const Args&... args ) ->
        decltype ( deep_copy ( myparser<T, Args...>::combine ( dlm_, args... ) ) )
        {
            return deep_copy ( myparser<T, Args...>::combine ( dlm_, args... ) );
        }
    };
    template <typename T>
    prefixer<T> make_prefixer ( T dlm )
    {
        return prefixer<T> ( dlm );
    }
    
    int main()
    {
        std::string test = "lameducklamedog";
    
        std::string::const_iterator f ( test.begin() ), l ( test.end() );
    
        auto param = make_prefixer ( qi::lit ( "lame" ) );
        qi::rule<std::string::const_iterator> dog = qi::lit ( "do" ) > qi::char_ ( 'g' );
        //qi::rule<std::string::const_iterator> duck = qi::lit ( "duck" ) | qi::int_;
        qi::rule<std::string::const_iterator,std::string()> quackdog = (param (*qi::alpha)  >> param( dog ));
    
    
         std::string what;
         if ( qi::parse ( f, l, quackdog, what ) && f == l )
             std::cout << "the duck and the dog are lame, specially the " << what  << std::endl;
         else
             std::cerr << "Uhoh\n" << std::string(f,l) << std::endl;
    
        test = "*-*2.34*-*10*-*0.16*-*12.5";
        std::string::const_iterator f2 ( test.begin() ), l2 ( test.end() );
    
        auto param2 = make_prefixer ( qi::lit ( "*-*" ) );
        double d;
        qi::rule<std::string::const_iterator> myrule = ( param2 ( qi::double_, qi::int_, qi::double_ , qi::double_) ) [phx::ref ( d ) = qi::_1 + qi::_2 + qi::_3 + qi::_4];
    
        if ( qi::parse ( f2, l2, myrule ) && f2 == l2 )
            std::cout << "the sum of the numbers is " << d << std::endl;
        else
            std::cerr << "Uhoh\n";
    
    }