Можно ли создавать собственные парсеры в Boost.Spirit? - PullRequest
4 голосов
/ 17 августа 2010

Я пытался создать собственный класс Parser в Boost.Spirit (2.3), но это не сработало.Код:

template <class Iter>
class crule : public boost::spirit::qi::parser<crule<Iter> >
{
  rule<Iter> r_;
public:
  crule(const rule<Iter>& r) : r_(r) {}
  template <class T>
  crule(const T& t) : r_(t) {}
  template<class Ctx, class Skip>
  bool parse(Iter& f, const Iter& l, Ctx& context, Skip& skip, typename rule<Iter>::template attribute<Ctx, Iter>::type& attr) const {
    return r_.parse(f, l, context, skip, attr);
  }
  template <class Ctx>
  boost::spirit::info what(Ctx& context) const {
    return r_.what(context);
  }
  template <class Context, class It>
  struct attribute {
    typedef typename rule<Iter>::template attribute<Context, It>::type type;
  };
};

и хотя я (по крайней мере, думаю, что выполнил) все требования , я получаю ошибки, когда пытаюсь использовать этот класс в выражении синтаксического анализа:

shell_grammar.h:134: error: no match for 'operator!' in '!shell_grammar<Iter>::token(boost::spirit::qi::rule<Iter, boost::fusion::unused_type, boost::fusion::unused_type, boost::fusion::unused_type>) [with Iter = __gnu_cxx::__normal_iterator<const char*, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >](boost::spirit::qi::rule<__gnu_cxx::__normal_iterator<const char*, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, boost::fusion::unused_type, boost::fusion::unused_type, boost::fusion::unused_type>(((const boost::spirit::qi::rule<__gnu_cxx::__normal_iterator<const char*, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, boost::fusion::unused_type, boost::fusion::unused_type, boost::fusion::unused_type>&)((const boost::spirit::qi::rule<__gnu_cxx::__normal_iterator<const char*, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, boost::fusion::unused_type, boost::fusion::unused_type, boost::fusion::unused_type>*)(&((shell_grammar<__gnu_cxx::__normal_iterator<const char*, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > >*)this)->shell_grammar<__gnu_cxx::__normal_iterator<const char*, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > >::reserved_words)))))'

shell_grammar.h:134: note: candidates are: operator!(bool) <built-in>

Я пытался взглянуть на реализацию других парсетов (например, not_predicate), но не могу понять, в чем разница, которая заставляет его работать.

Motvation

Причина, по которой я это делаю, связана с этим вопросом .Я хочу разобрать язык оболочки POSIX, который имеет своеобразные лексические правила.В частности, «анализатор шкипера» должен применяться даже в лексемах, но он должен отличаться от синтаксического анализатора «уровня фразы».Это то, что директива lexeme не может сделать, и skip не пропускает заранее (AFAIK), что мне тоже нужно.Поэтому я хочу создать функцию

something token(std::string);

, которая бы возвращала правило, соответствующее токену.Одним из способов является создание моей собственной оболочки rule, которая будет служить терминалом (поскольку один rule не может использоваться для его ссылочной семантики), другой - создание нового синтаксического анализатора (который будет нетерминальным в proto),и реализовать в нем синтаксический анализ токена оболочки.

Ответы [ 3 ]

3 голосов
/ 18 августа 2010

Код, который вы указали, выглядит нормально (по крайней мере, с точки зрения интерфейса реального анализатора).Но чтобы интегрировать собственный анализатор с Spirit, вам нужно проделать больше работы.На веб-сайте Spirit есть пример для пользовательского компонента синтаксического анализа, объясняющего все необходимые шаги здесь .

Мне кажется, что вы без необходимости пытались сделать что-то сложное.Но я не совсем понимаю, чего вы пытаетесь достичь, поэтому я могу ошибаться.Если бы вы объяснили свой вариант использования, я уверен, что мы могли бы предложить более простое решение.

3 голосов
/ 17 августа 2010

Это вполне возможно, но я обнаружил, что это такая же большая работа (и более сложная для отладки), чем просто написание моих собственных лексеров и анализаторов рекурсивного спуска вручную. Даже довольно маленькие грамматики Духа могут занять у меня недели борьбы с компилятором.

В этом сообщении об ошибке указано, с какими проблемами вы столкнулись. Каждый раз, когда вы получаете сообщение об ошибке, это ошибка какого-то экземпляра шаблона глубоко в недрах Духа, с множеством дополнительных слоев шаблонов, добавленных, чтобы запутать дело. Чтобы иметь хоть какую-то надежду на расшифровку сообщений об ошибках, вам, скорее всего, придется разбираться в коде всего объекта.

Я ненавижу критиковать, потому что Дух - это достойное усилие. Я защитил кандидатскую диссертацию по реализации объектно-ориентированного компилятора-генератора , поэтому я фанат этой концепции. Я действительно хотел, чтобы это понравилось, но Spirit слишком сложно использовать кому-либо, кроме серьезных C ++ экспертов.

Чтобы сравнить с тем, что можно сделать, взгляните на проект Ada OpenToken . Возможно, Spirit более гибок, но ошибки компиляции гораздо более чувствительны в OpenToken, и просмотр истории версий на этой странице показывает, что очень большой процент их усилий был потрачен на помощь пользователям в отладке ошибок.

1 голос
/ 20 августа 2010

Кстати, вот к чему я пришел:

Вам нужно зарегистрировать класс как литерал в boost::proto, например:

template <class T>
struct crulexx : public boost::proto::literal<rule<T> >
{
  template <class U>
  crulexx(const U& u) : boost::proto::literal<rule<T> >(rule<T>(u)) {}
};

Это работает для меня в этом тест .Тем не менее, я получил segfaults в другом фрагменте кода, используя его, который мне придется отлаживать.

...