在几个问题中,我从boost.org看到了关于Spirit解析器生成器框架的建议,但是在评论中,使用 Spirit 的人不满意的抱怨。请那些人站出来向我们其他人解释使用 Spirit 的缺点或缺点是什么?
5 回答
这是一个很酷的主意,我喜欢它;真正学习如何使用 C++ 模板特别有用。
但是他们的文档建议将精神用于中小型解析器。完整语言的解析器需要很长时间才能编译。我将列出三个原因。
无扫描仪解析。虽然它相当简单,但当需要回溯时,它可能会减慢解析器的速度。虽然它是可选的 - 可能会集成词法分析器,请参阅使用 Spirit 构建的 C 预处理器。大约 300 行的语法(包括 .h 和 .cpp 文件)使用 GCC 编译(未优化)为 6M 的文件。内联和最大优化将其降至约 1,7M。
解析慢 - 没有对语法的静态检查,既不提示需要过多的前瞻,也不验证基本错误,例如左递归的使用(这导致递归下降解析器 LL 语法中的无限递归)。然而,左递归并不是一个很难追踪的错误,但是过度的前瞻可能会导致指数解析时间。
大量使用模板——虽然这有一定的优势,但这会影响编译时间和代码大小。此外,语法定义通常必须对所有其他用户可见,这会影响更多的编译时间。通过添加具有正确参数的显式模板实例化,我已经能够将语法移动到 .cpp 文件,但这并不容易。
更新:我的回答仅限于我对 Spirit 经典的体验,而不是 Spirit V2。我仍然希望 Spirit 是基于模板的,但现在我只是猜测。
在 boost 1.41 中,Spirit 的新版本正在发布,它打破了精神::经典:
经过长时间的测试(Spirit 2.0 超过 2 年),Spirit 2.1 最终将与即将发布的 Boost 1.41 版本一起发布。该代码现在非常稳定,可以用于生产代码。我们正在努力及时完成 Boost 1.41 的文档。您可以在此处查看文档的当前状态。目前,您可以在 Boost SVN 主干中找到代码和文档。如果您有一个涉及 Spirit 的新项目,我们强烈建议您现在从 Spirit 2.1 开始。请允许我从 Spirit 邮件列表中引用 OvermindDL 的帖子:
我可能开始听起来像一个机器人,因为我经常这么说,但是 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 修饰符是如何工作的之后,也很难获得易于操作的 AST(即,能够很好地映射到问题域的AST)
它极大地增加了编译时间,即使对于“中等”大小的语法也是如此
语法太重了。在 C/C++ 中,您必须复制代码(即在声明和定义之间),这是一个不争的事实。然而,似乎在 boost::spirit 中,当你声明一个语法<>时,你必须重复一些事情 3 次:D(当你想要 AST 时,这就是我想要的 :D)
除此之外,考虑到 C++ 的局限性,我认为他们在解析器方面做得很好。但我认为他们应该进一步改进它。历史页面描述了在当前“静态”精神之前存在“动态”精神;我想知道它的语法速度有多快,语法有多好。
我想说最大的问题是缺乏对语法问题的任何诊断或其他帮助。如果您的语法不明确,则解析器可能无法解析您所期望的内容,并且没有好的方法可以注意到这一点。