В нескольких вопросах я видел рекомендации для Spirit фреймворк генератора парсеров с сайта boost.org, но потом в комментариях ворчат люди, использующие Spirit, которые не счастливы. Не могли бы эти люди встать и объяснить остальным из нас, в чем недостатки или недостатки использования Духа?
Каковы недостатки фреймворка парсера-генератора Spirit от boost.org?
Ответы (5)
Это довольно крутая идея, и мне она понравилась; было особенно полезно действительно научиться использовать шаблоны C++.
Но их документация рекомендует использовать Spirit для парсеров малого и среднего размера. Синтаксический анализатор для полного языка потребует целую вечность для компиляции. Я назову три причины.
Бессканерный разбор. Хотя это довольно просто, когда требуется возврат, это может замедлить анализатор. Однако это необязательно - лексер может быть интегрирован, см. Препроцессор C, созданный с помощью Spirit. Грамматика из ~300 строк (включая файлы .h и .cpp) компилируется (неоптимизированно) в файл размером 6M с помощью GCC. Встраивание и максимальная оптимизация позволяют сократить это число до ~1,7M.
Медленный синтаксический анализ - нет статической проверки грамматики, ни для намека на необходимость чрезмерного просмотра вперед, ни для проверки основных ошибок, таких как, например, использование левой рекурсии (что приводит к бесконечной рекурсии в LL-грамматиках парсеров с рекурсивным спуском). Однако левая рекурсия не является действительно сложной ошибкой для отслеживания, но чрезмерный просмотр вперед может привести к экспоненциальному увеличению времени синтаксического анализа.
Интенсивное использование шаблонов — хотя это имеет определенные преимущества, это влияет на время компиляции и размер кода. Кроме того, определение грамматики обычно должно быть видно всем другим пользователям, что еще больше влияет на время компиляции. Я смог переместить грамматики в файлы .cpp, добавив явные экземпляры шаблонов с правильными параметрами, но это было непросто.
ОБНОВЛЕНИЕ: мой ответ ограничен моим опытом работы с классическим Spirit, а не с Spirit V2. Я бы по-прежнему ожидал, что Spirit будет в значительной степени основан на шаблонах, но сейчас я просто предполагаю.
В boost 1.41 выходит новая версия Spirit, и она лучше, чем у Spirit::classic:
После долгого пребывания в бета-версии (более 2 лет с Spirit 2.0) Spirit 2.1, наконец, будет выпущен вместе с предстоящим выпуском Boost 1.41. Теперь код очень стабилен и готов к работе. Мы усердно работаем над завершением документации к Boost 1.41. Вы можете просмотреть текущее состояние документации здесь. В настоящее время вы можете найти код и документацию в транке Boost SVN. Если у вас есть новый проект с участием Spirit, мы настоятельно рекомендуем начать с Spirit 2.1 прямо сейчас. Позвольте мне процитировать сообщение OvermindDL из списка рассылки Spirit:
Я могу начать походить на бота из-за того, как часто я говорю это, но Spirit.Classic древний, вам следует переключиться на Spirit2.1, он может делать все, что вы делали выше, ГОРАЗДО проще, намного меньше кода, и он выполняет Быстрее. Например, Spirit2.1 может построить весь ваш AST в режиме реального времени, без каких-либо странных переопределений, без необходимости создавать что-то потом и т. д., и все это как один приятный и быстрый шаг. Вам действительно нужно обновиться. См. другие сообщения за прошлый день для ссылок на документы и тому подобное для Spirit2.1. Spirit2.1 в настоящее время находится в Boost Trunk, но официально будет выпущен вместе с Boost 1.41, но в остальном он завершен.
Для меня самой большой проблемой является то, что выражения в Spirit, как их видит компилятор или отладчик, довольно длинные (ниже я скопировал часть одного выражения в Spirit Classic). Меня пугают эти выражения. Когда я работаю над программой, которая использует Spirit, я боюсь использовать valgrind или печатать обратную трассировку в gdb.
boost::spirit::classic::parser_result<boost::spirit::classic::action<boost::spirit::classic::sequence<boost::spirit::classic::action<boost::spirit::classic::action<optional_suffix_parser<char const*>, boost::spirit::classic::ref_actor<std::vector<std::string, std::allocator<std::string> >, boost::spirit::classic::clear_action> >, boost::spirit::classic::ref_actor<std::vector<int, std::allocator<int> >, boost::spirit::classic::clear_action> >, boost::spirit::classic::sequence<boost::spirit::classic::alternative<boost::spirit::classic::alternative<boost::spirit::classic::action<boost::spirit::classic::contiguous<boost::spirit::classic::sequence<boost::spirit::classic::alternative<boost::spirit::classic::chlit<char>, boost::spirit::classic::chlit<char> >, boost::spirit::classic::positive<boost::spirit::classic::alternative<boost::spirit::classic::alternative<boost::spirit::classic::alnum_parser, boost::spirit::classic::chlit<char> >, boost::spirit::classic::chlit<char> > > > >, boost::spirit::classic::ref_value_actor<std::vector<std::string, std::allocator<std::string> >, boost::spirit::classic::push_back_action> >, boost::spirit::classic::action<boost::spirit::classic::rule<boost::spirit::classic::scanner<char const*, boost::spirit::classic::scanner_policies<boost::spirit::classic::skipper_iteration_policy<boost::spirit::classic::iteration_policy>, boost::spirit::classic::match_policy, boost::spirit::classic::action_policy> >, boost::spirit::classic::nil_t, boost::spirit::classic::nil_t>, boost::spirit::classic::ref_const_ref_actor<std::vector<std::string, std::allocator<std::string> >, std::string, boost::spirit::classic::push_back_action> > >, boost::spirit::classic::contiguous<boost::spirit::classic::sequence<boost::spirit::classic::chlit<char>, boost::spirit::classic::action<boost::spirit::classic::uint_parser<unsigned int, 10, 1u, -1>, boost::spirit::classic::ref_value_actor<std::vector<int, std::allocator<int> >, boost::spirit::classic::push_back_action> > > > >, boost::spirit::classic::kleene_star<boost::spirit::classic::sequence<boost::spirit::classic::chlit<char>, boost::spirit::classic::alternative<boost::spirit::classic::alternative<boost::spirit::classic::action<boost::spirit::classic::contiguous<boost::spirit::classic::sequence<boost::spirit::classic::alternative<boost::spirit::classic::chlit<char>, boost::spirit::classic::chlit<char> >, boost::spirit::classic::positive<boost::spirit::classic::alternative<boost::spirit::classic::alternative<boost::spirit::classic::alnum_parser, boost::spirit::classic::chlit<char> >, boost::spirit::classic::chlit<char> > > > >, boost::spirit::classic::ref_value_actor<std::vector<std::string, std::allocator<std::string> >, boost::spirit::classic::push_back_action> >, boost::spirit::classic::action<boost::spirit::classic::rule<boost::spirit::classic::scanner<char const*, boost::spirit::classic::scanner_policies<boost::spirit::classic::skipper_iteration_policy<boost::spirit::classic::iteration_policy>, boost::spirit::classic::match_policy, boost::spirit::classic::action_policy> >, boost::spirit::classic::nil_t, boost::spirit::classic::nil_t>, boost::spirit::classic::ref_const_ref_actor<std::vector<std::string, std::allocator<std::string> >, std::string, boost::spirit::classic::push_back_action> > >, boost::spirit::classic::contiguous<boost::spirit::classic::sequence<boost::spirit::classic::chlit<char>, boost::spirit::classic::action<boost::spirit::classic::uint_parser<unsigned int, 10, 1u, -1>, boost::spirit::classic::ref_value_actor<std::vector<int, std::allocator<int> >, boost::spirit::classic::push_back_action> > > > > > > > >, void ()(char const, char const*)>, boost::spirit::classic::scanner<char const*, boost::spirit::classic::scanner_policies<boost::spirit::classic::skipper_iteration_policy<boost::spirit::classic::iteration_policy>, boost::spirit::classic::match_policy, boost::spirit::classic::action_policy> > >::type boost::spirit::classic::action<boost::spirit::classic::sequence<boost::spirit::classic::action<boost::spirit::classic::action<
Вот что мне в нем не нравится:
документация ограничена. Существует одна большая веб-страница, где объясняется «все», но в текущих объяснениях не хватает деталей.
плохая генерация АСТ. AST плохо объяснены, и даже после того, как вы ударились головой о стену, чтобы понять, как работают модификаторы AST, трудно получить простой для манипулирования AST (то есть такой, который хорошо соответствует проблемной области)
Это значительно увеличивает время компиляции даже для грамматик "среднего" размера.
Синтаксис слишком тяжеловесен. Это факт жизни, что в C/C++ вы должны дублировать код (т.е. между объявлением и определением). Однако кажется, что в boost::spirit, когда вы объявляете грамматику‹>, вы должны повторять некоторые вещи 3 раза :D (когда вам нужны AST, а это то, что я хочу :D)
Кроме этого, я думаю, что они неплохо поработали с парсером, учитывая ограничения C++. Но я думаю, что они должны улучшить его больше. На странице истории описывается, что до нынешнего «статического» духа существовал «динамический» дух; Мне интересно, насколько быстрее и насколько лучше у него был синтаксис.
Я бы сказал, что самой большой проблемой является отсутствие какой-либо диагностики или другой помощи при проблемах с грамматикой. Если ваша грамматика неоднозначна, синтаксический анализатор может не разобрать то, что вы от него ожидаете, и нет никакого хорошего способа заметить это.