Нужен способ префикса парсера boost::spirit::qi с другим

У меня есть много правил, которые выглядят так:

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

Я хочу сделать его более читаемым, например:

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

или даже

cmd_BC = params(timestamp, cid, double_)

Как указал sehe, все сводится к тому, чтобы иметь некоторые средства для автоматического ожидания разделителей. Какие здесь есть варианты? Лично я вижу три возможности, все ошибочные:

  1. Используйте макрос. Это не позволит использовать более короткую вариативную форму.
  2. Напишите собственную префиксную директиву. Кажется, у меня недостаточно опыта работы с часовым механизмом Spirit, но если это на самом деле не так сложно, я попытаюсь.
  3. Напишите функцию-оболочку. Я безуспешно пробовал следующий код:

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

    но он не компилируется, терпит неудачу в

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

    Я также пытался return (...).alias(), но он тоже не скомпилировался.


person vines    schedule 30.09.2012    source источник


Ответы (3)


Это решение не очень «духовное» и, к сожалению, «требует С++ 11» (я не уверен, как получить требуемый тип результата в С++ 03), но, похоже, оно работает. Вдохновленный примером здесь.

PS: О, я не видел вашу правку. У вас почти такой же пример.

Обновление: добавлен еще один тест с использованием семантического действия с _1,_2 и _3
Обновление 2: изменена сигнатура operator() и operator[], следуя совету лозы в комментариях.

Обновление 3: добавлен вариативный оператор(), который создает комбинированный синтаксический анализатор, и удален оператор[] после нахождения лучшего решения с помощью boost::proto. Немного изменил примеры.

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

}
person Community    schedule 01.10.2012
comment
Я только начал копаться в boost::proto docs, когда увидел ваш ответ :) Да, идея похожа - сделать функцию комбинатора парсера. С вашим кодом я получаю то же утверждение, что и мой (argument not found (N is out of bounds)), я полагаю, что это связано с использованием заполнителей атрибутов. Когда я пробую param(double)[val_ = 1_ ], он компилируется, но время от времени segfaults. - person vines; 01.10.2012
comment
@vines Вызывают ли мои (до глупости простые) правила ошибку вашего компилятора? или ошибка возникает при вводе своего? Если последнее, не могли бы вы написать свои правила, чтобы я мог попытаться устранить их? - person ; 01.10.2012
comment
Настоящая проблема заключается в назначении правил, не связанных с атрибутами. В моем ограниченном тестировании я использовал только param непосредственно в qi::parse, где это работает, но я не могу использовать param в определении правила. - person ; 02.10.2012
comment
Где были мои глаза! Я только что понял, что я на самом деле написал: param(a) > param(b)[ val_ = _2 ], неудивительно, что это не сработало! И да, вы правы, правила не копируются в смысле С++, я изменил prefixer на auto operator() (const U & parser ), и наконец-то это работает! Спасибо. Сейчас попробую сделать его вариативным. - person vines; 02.10.2012
comment
@vines Реализован вариативный оператор (). Я снова пропустил ваше обновление. - person ; 04.10.2012
comment
Я думаю, это гораздо более «духовный» способ, чем мой сейчас :) И @sehe говорит, что такая вещь поместится в хранилище духов. Может, предложите? - person vines; 05.10.2012

Если я правильно понимаю, вы ищете способ автоматически ожидать или игнорировать выражение-разделитель (dlm)?

Шкиперы

Это классический ландшафт для Skippers in Spirit. Это особенно полезно, если разделитель является переменным, например. пробелы (допустимо различное количество пробелов);

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

Обратите внимание на использование phrase_parse для включения грамматик со шкиперами.

Разделено директивой парсера %

Вы можете явно пойти и разграничить грамматику:

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

Это утомительно. Что-то, что может хорошо сработать для вас (в зависимости от объема проверки ввода, который должен быть выполнен:

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

(Это приведет к вектору variant<timestampt_t, cid_t, double>)

Сверните свой собственный

Вы можете создать собственную директиву парсера, аналогичную karma::delimit, но для ввода.

Идея изложена в этой статье Хартмута Кайзера:

Если вам интересно, я мог бы посмотреть, смогу ли я сделать эту работу в качестве примера (я не использовал это раньше). Честно говоря, я удивлен, что чего-то подобного еще не существует, и я думаю, что это был бы главный кандидат на Репозиторий духов

person sehe    schedule 30.09.2012
comment
Ой ну спасибо! Это именно те слова, которые я не мог сформулировать: автоматически ожидать разделителей. Кроме того, мне любопытно, почему вы предпочли оператор перестановки альтернативе? (Это решение выглядит очень красиво, но, если я правильно понял, допускает перестановку параметров, что неприемлемо, если только оно не выйдет из строя хотя бы в какой-то момент позже - например, в варианте приведения). - person vines; 01.10.2012
comment
@vines Честно говоря, я не помню, почему я добавил туда перестановку. Я, вероятно, запутался с подходом на основе шкипера, и в этом случае a ^ b ^ c мог бы быть кратким способом достичь того, что вы хотели. Не стесняйтесь забыть об этом. Я отредактирую ответ, чтобы использовать | вместо этого, чтобы избежать путаницы :) - person sehe; 01.10.2012

Вот решение, которым я наконец доволен. Это основано на этих трех ответах:

Однако я решил не заставлять его принимать произвольные бинарные функторы, так как сомневаюсь, что это будет иметь какое-либо реальное назначение в контексте синтаксического анализа. Так,

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

И используется так:

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)
              ];
person vines    schedule 04.10.2012
comment
Вероятно, вы должны принять этот ответ. В отличие от моего, он делает именно то, что вы хотите. - person ; 15.02.2013
comment
@llonesmiz Тогда я немного колебался и решил не делать этого: на самом деле я не производил здесь много новой ценности, а просто объединил результаты нескольких других. Самый важный (deep_copy) — ваш. - person vines; 15.02.2013