boost.org的Spirit解析器生成框架有哪些缺点?

42

我在几个问题中看到了关于boost.orgSpirit解析器生成框架的推荐,但是在评论中,有使用Spirit的人不满意。那些人能站出来向我们解释一下使用Spirit的缺点或不足吗?


5
请参考以下翻译:请看Eric Niebler的有关Boost视频中的精彩文本处理:http://video.google.de/videoplay?docid=3723782552647089226 。这里还有幻灯片:http://www.nwcpp.org/Downloads/2007/Text_Processing_With_Boost.ppt 。祝您观看愉快! - Johannes Schaub - litb
我认为工作视频在这里 https://www.youtube.com/watch?v=6eVtvQ3IEfc /cc @JohannesSchaub-litb - sehe
5个回答

37

这是一个相当酷的想法,我喜欢它;尤其有用的是真正学习如何使用C++ 模板。

但他们的文档建议在小到中等规模的解析器中使用spirit。一个完整语言的解析器需要花费很长时间来编译。 我将列出三个原因。

  • 无扫描仪解析。虽然它很简单,但在需要回溯时可能会使解析器变慢。不过这是可选的 - 词法分析器可以被集成,在使用Spirit构建的C预处理器中可以看到。一个包括.h和.cpp文件的约300行语法(包括两者)编译(未优化)成一个6M的文件,使用GCC进行内联和最大优化可将其降至约1.7M。

  • 解析速度慢 - 语法没有静态检查,既不能提示所需的超额前瞻,也不能验证基本错误,例如使用左递归(这导致递归下降解析器LL语法中的无限递归)。虽然左递归不是真正难以跟踪的错误,但过多的前瞻可能会导致指数级的解析时间。

  • 大量使用模板 - 虽然这具有某些优点,但这会影响编译时间和代码大小。此外,语法定义通常必须可见于所有其他用户,更影响编译时间。 我已经能够通过添加正确参数的显式模板实例来将语法移动到.cpp文件中,但这并不容易。

更新:我的回复仅限于我对Spirit classic的经验,而不是Spirit V2。我仍然希望Spirit基于模板,但现在只是猜测。


1
编译时间和代码膨胀+1...我曾经在一个小项目中使用过Spirit(解析配置文件),但后来因为这些原因决定将其删除。 - Nils Pipenbrinck
一个现实世界中有用的语法,比如超过十几条规则,编译时间会非常慢。 - ravenspoint
@Blaisorblade - 这个回答是指“Spirit classic”还是“Spirit V2”?(请参见其他答案:https://dev59.com/j3RC5IYBdhLWcg3wCMg6#1591930) - Martin Ba

24

在Boost 1.41中,将发布Spirit的新版本,而且它击败了spirit::classic:

经过长时间的测试(使用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中可以更轻松地完成您上面所做的所有事情,需要更少的代码,并且执行速度更快。例如,Spirit2.1可以内联构建整个AST,无需奇怪的重写,无需事后建立,等等...,全部作为一个不错且快速的步骤。您确实需要更新。 参见过去一天的其他帖子,获取Spirit2.1的文档等相关链接。Spirit2.1目前在Boost Trunk中,但是它已经完成并将随Boost 1.41一同正式发布。


1
Spirit X3 重复了这个练习。它的编译速度提高了一个数量级,并支持现代 C++(例如,移动语义),使运行时性能更加出色。然而,它放弃了一些不错的支持,使其更加适用于小型解析器,我认为。 - sehe

19

对我来说,最大的问题是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, boost::spirit::classic::ref_actor >, boost::spirit::classic::clear_action> >, boost::spirit::classic::ref_actor >, 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, boost::spirit::classic::chlit >, boost::spirit::classic::positive< boost::spirit::classic::alternative< boost::spirit::classic::alternative< boost::spirit::classic::alnum_parser, boost::spirit::classic::chlit >, boost::spirit::classic::chlit > > > >, boost::spirit::classic::ref_value_actor >, boost::spirit::classic::push_back_action> >, boost::spirit::classic::action< boost::spirit::classic::rule< boost::spirit::classic::scanner, 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::string, boost::spirit::classic::push_back_action> > >, boost::spirit::classic::contiguous< boost::spirit::classic::sequence< boost::spirit::classic::chlit, boost::spirit::classic::action< boost::spirit::classic::uint_parser, boost::spirit::classic::ref_value_actor >, boost::spirit::classic::push_back_action> > > > >, boost::spirit::classic::kleene_star< boost::spirit::classic::sequence< boost::spirit::classic::chlit, 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, boost::spirit::classic::chlit >, boost::spirit::classic::positive< boost::spirit::classic::alternative< boost::spirit::classic::alternative< boost::spirit::classic::alnum_parser, boost::spirit::classic::chlit >, boost::spirit::classic::chlit > > > >, boost::spirit::classic::ref_value_actor >, boost::spirit::classic::push_back_action> >, boost::spirit::classic::action< boost::spirit::classic::rule< boost::spirit::classic::scanner, 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::string, boost::spirit::classic::push_back_action> > >, boost::spirit::classic::contiguous< boost::spirit::classic::sequence< boost::spirit::classic::chlit, boost::spirit::classic::action< boost::spirit::classic::uint_parser, boost::spirit::classic::ref_value_actor >, boost::spirit::classic::push_back_action> > > > > > > > >, void (*)(char const, char const*)>, boost::spirit::classic::scanner, boost::spirit::

14

以下是我不喜欢它的原因:

  • 文档有限。虽然有一个大网页解释了“一切”,但目前的解释缺乏细节。

  • AST生成质量较差。AST的解释很差,即使你努力理解AST修饰符的工作方式,也难以获得易于操作的AST(即映射到问题域的AST)。

  • 它会极大地增加编译时间,即使对于“中等”规模的语法

  • 语法过于笨重。在C/C++中,你必须复制代码之间的部分(即声明和定义)。然而,在boost::spirit中,当你声明grammar <>时,你必须重复某些内容3次:D(当你想要ASTs,这正是我想要的:D)

除此之外,我认为他们在解析器方面做得相当不错,考虑到C++的限制。但我认为他们应该进一步改进。历史页面描述了当前“静态”spirit之前存在一个“动态”的spirit;我在想它有多快,有多好的语法。


1
我不确定,但我认为动态精神可能比当前的更好。它可能会因为额外的虚拟调用而变得更慢;如果我没记错的话,现在只有在进入规则解析器时才会有虚拟调用,在动态精神中,每个复合解析器和其组件之间的调用都将是虚拟的。 - Blaisorblade
1
我忘记同意你对文档和AST生成的评论。当我使用Spirit时,一些细节没有在文档中定义,代码似乎会给出关于这些不连贯和有缺陷的结果,也许是因为开发人员没有明确指定它们。 - Blaisorblade

4

我认为最大的问题是缺乏对语法问题的任何诊断或其他帮助。如果你的语法含糊不清,解析器可能无法解析你期望的内容,而且没有好的方法来注意到这一点。


此外,由于过度的前瞻,您可能会遇到指数回溯问题,并且难以进行调试。曾经我快速地拼凑了一个语法,但在几分钟内无法完成对大约100个标记的1个表达式的解析。然而,我认为这不仅适用于Spirit,而且适用于所有当前的解析器组合库(在大多数函数式语言中都可用,如Haskell或Scala),它们允许您在编程语言中定义语法,而不是使用单独的工具。 - Blaisorblade

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接