3

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.

Community
  • 1
  • 1
vines
  • 5,160
  • 1
  • 27
  • 49

3 Answers3

2

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";

}
  • I've just started to dig into boost::proto docs, when I saw your answer :) Yeah, the idea is similar - to make a parser combinator function. With your code I get the same assert as mine `(argument not found (N is out of bounds))`, I suppose it's related to attribute placeholders being used. When I try `param(double)[val_ = 1_ ]`, it compiles, but segfaults sporadically. – vines Oct 01 '12 at 19:28
  • @vines Do my (stupidly simple) rules cause your compiler error? or the error occurs when you input yours? If it's the latter, could you write your rules so I can try to troubleshoot them? –  Oct 01 '12 at 19:35
  • The real problem is assigning to the rules, unrelated to the attributes. In my limited testing I only used `param` directly in `qi::parse` where it works, but I can't use `param` in a rule definition. –  Oct 02 '12 at 16:29
  • 1
    Where were my eyes! I've just understood what I actually wrote: `param(a) > param(b)[ val_ = _2 ]`, no surprise it didn't work! And yes, you're right, rules are uncopyable in c++ sense, I changed `prefixer` to `auto operator() (const U & parser )` and it finally works! Thank you. I'll try now to make it variadic. – vines Oct 02 '12 at 17:08
  • @vines Implemented variadic operator (). I have again missed your update. –  Oct 04 '12 at 14:24
  • I guess that's much more 'spirity' way than mine now :) And @sehe says such a thing would fit into the spirit repository. Maybe propose it? – vines Oct 05 '12 at 00:33
1

If I understand correctly, you are looking for a way to automatically expect or ignore the delimiter expression (dlm)?

Skippers

This is the classic terrain for Skippers in Spirit. This is especially useful if the delimiter is variable, e.g. whitespace (varying amounts of whitespace acceptable);

bool ok = qi::phrase_parse(
      first, last,                // input iterators
      timestamp > cid > double_,  // just specify the expected params
      qi::space);                 // delimiter, e.g. any amount of whitespace

Note the use of phrase_parse to enable grammars with skippers.

Delimited with % parser directive

You could explicitely go and delimit the grammar:

 dlm     = qi::lit(','); // as an example, delimit by single comma
 rule    = timestamp > dlm > cid > dlm > double_;

This is tedious. Something that might work about nicely for you (depending on the amount of input validation that should be performed:

 dlm     = qi::lit(','); // as an example, delimit by single comma
 rule    = (timestamp | cid | double_) % dlm;

(This would result in a vector of variant<timestampt_t, cid_t, double>)

Roll your own

You could roll your own parser directive, similar to karma::delimit, but for input.

The idea is outlined in this documentation article by Hartmut Kaiser:

If you're interested, I could see whether I could make this work as an example (I've not used this before). To be honest, I'm surprised something like this doesn't yet exist, and I think it would be a prime candidate for the Spirit Repository

sehe
  • 374,641
  • 47
  • 450
  • 633
  • Oh, thanks! That are exactly the words I couldn't formulate: *to automatically expect the delimiters*. Also, I'm curious about why did you prefer the permutation operator over alternative? (That solution looks really pretty, but, if I get it right, allows for permutations of parameters, which is unacceptable unless it will fail at least at some point later - in the variant cast, for example). – vines Oct 01 '12 at 15:33
  • @vines To bo honest, I don't remember why I put the permutation in there. I was probably confusing with the skipper-based approach, in which case `a ^ b ^ c` might have been a succinct way to achieve what you wanted. Feel free to forget about it. I will edit the answer to use `|` instead to avoid the confusion :) – sehe Oct 01 '12 at 15:42
1

Here's the solution I'm finally satisfied with. It's based on these three answers:

I've decided not to make it accept arbitrary binary functors though, since I doubt it would have any real-world purpose in context of parsing. So,

#include <boost/proto/deep_copy.hpp>

template <typename D>
struct prefixer
{
    template<typename... T>
    struct TypeOfPrefixedExpr;

    template<typename T>
    struct TypeOfPrefixedExpr<T>
    {
        typedef typename boost::proto::result_of::deep_copy
                 < decltype ( std::declval<D>() > std::declval<T>() ) >::type  type;
    };

    template<typename T, typename... P>
    struct TypeOfPrefixedExpr<T, P...>
    {
        typedef typename boost::proto::result_of::deep_copy
                 < decltype ( std::declval<D>() > std::declval<T>()
                              > std::declval<typename TypeOfPrefixedExpr<P...>::type>() ) >::type  type;
    };



    D dlm_;
    prefixer ( D && dlm ) : dlm_ ( dlm ) {}

    template <typename U>
    typename TypeOfPrefixedExpr<U>::type operator() (U && parser )
    {
        return boost::proto::deep_copy ( dlm_ > parser );
    }

    template <typename U, typename ... Tail>
    typename TypeOfPrefixedExpr<U, Tail...>::type
            operator() (U && parser, Tail && ... tail )
    {
        return boost::proto::deep_copy ( dlm_ > parser > (*this)(tail ...) );
    }
};

template <typename D>
prefixer<D> make_prefixer ( D && dlm )
{
    return prefixer<D> ( std::forward<D>(dlm) );
}

And it's used like this:

auto params = make_prefixer(qi::lit(dlm));

cmd_ID      = params(string) [ _val = lazy_shared<dc::Auth>   (_1) ];

cmd_NAV     = params(timestamp, double_, double_, double_, double_, double_)
              [
                  _val = lazy_shared<dc::Navigation>( _1, _2, _3, _4, _5, _6 )
              ];

cmd_BC      = params(timestamp, cid, double_)
              [
                  _val = lazy_shared<dc::BoardControl>(_1, _2, _3)
              ];
Community
  • 1
  • 1
vines
  • 5,160
  • 1
  • 27
  • 49
  • You should probably accept this answer. Unlike mine it does exactly what you want. –  Feb 15 '13 at 08:51
  • @llonesmiz I did hesitate a bit back then and decided not to do so: I didn't really produce much new value here, but just aggregated several others' findings. The most important one (deep_copy) is yours. – vines Feb 15 '13 at 18:34