【问题标题】:What are the disadvantages of the Spirit parser-generator framework from boost.org?boost.org 的 Spirit 解析器生成器框架有哪些缺点?
【发布时间】:2010-09-30 17:41:30
【问题描述】:

在几个问题中,我从boost.org 看到了对Spirit parser-generator 框架的建议,但是在cmets 中,使用Spirit 的人不满意。请那些人站出来向我们解释一下使用 Spirit 的缺点或缺点是什么?

【问题讨论】:

标签: c++ parsing boost boost-spirit parser-generator


【解决方案1】:

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

但他们的文档建议将精神用于中小型解析器。完整语言的解析器需要很长时间才能编译。 我将列出三个原因。

  • 无扫描仪解析。虽然它相当简单,但当需要回溯时,它可能会减慢解析器的速度。虽然它是可选的 - 可能会集成词法分析器,请参阅使用 Spirit 构建的 C 预处理器。大约 300 行的语法(包括 .h 和 .cpp 文件)使用 GCC 编译(未优化)为 6M 的文件。内联和最大优化可将其降至约 1,7M。

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

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

更新:我的回复仅限于我对 Spirit 经典的体验,而不是 Spirit V2。我仍然认为 Spirit 会大量基于模板,但现在我只是猜测。

【讨论】:

  • +1 编译时间和代码膨胀...我曾经在一个小项目中使用过 Spirit(解析配置文件),后来出于这些原因决定将其删除。
  • 对于一个真实世界有用的语法,比如十几条规则,编译时间非常慢。
  • @Blaisorblade - 这个答案是指“Spirit classic”还是“Spirit V2”? (见其他答案:stackoverflow.com/questions/432173/…
【解决方案2】:

在 boost 1.41 中,Spirit 的新版本正在发布,它的精神::经典:

经过很长时间的测试(超过 2 Spirit 2.0), Spirit 2.1 最终将与 即将发布的 Boost 1.41 版本。代码 现在非常稳定,准备好了 生产代码。我们正在努力 关于及时完成文档 对于升压 1.41。你可以偷看 文档的当前状态 这里。目前,您可以找到代码 和 Boost SVN 中的文档 树干。如果你有一个新项目 涉及精神,我们强烈推荐 现在从 Spirit 2.1 开始。让我 引用 OvermindDL 的帖子 精神邮件列表:

我可能开始听起来像一个机器人 我多久说一次,但是 Spirit.Classic是古老的,你应该 切换到Spirit2.1,就可以了 你所做的一切都超出了一个伟大的交易 更容易,更少的代码,而且它 执行得更快。例如, Spirit2.1 可以构建你的整个 AST 内联,没有奇怪的覆盖,不需要 之后建立东西,等等......, 所有这些都是一个不错且快速的步骤。你 真的需要更新。看另一个 过去一天的帖子链接到 Spirit2.1 的文档等。精神2.1 目前在 Boost Trunk 中,但将 与 Boost 1.41 一起正式发布, 但在其他方面是完整的。

【讨论】:

  • Spirit X3 重复了这个练习。它的编译速度提高了一个数量级,并且它支持现代 C++(如移动语义),使运行时也更加高效。然而,它放弃了一些很好的支持,使其更适合小型解析器,IMO。
【解决方案3】:

对我来说,最大的问题是 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<

【讨论】:

    【解决方案4】:

    这是我不喜欢的地方:

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

    • AST 生成不佳。对 AST 的解释很差,即使在想尽办法理解 AST 修饰符是如何工作的之后,也很难获得一个易于操作的 AST(即能够很好地映射到问题域的 AST)

    • 它极大地增加了编译时间,即使对于“中等”大小的语法也是如此

    • 语法太重。在 C/C++ 中,您必须复制代码(即在声明和定义之间),这是一个不争的事实。然而,似乎在 boost::spirit 中,当你声明一个语法时,你必须重复一些事情 3 次:D(当你想要 AST 时,这就是我想要的:D)

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

    【讨论】:

    • 不确定,但我认为动态精神不会比现在更好。对于它拥有的额外虚拟呼叫,它可能会慢得多; IIRC,现在只有在进入规则<...>解析器时才会有虚拟调用,本着动态精神,复合解析器与其组件之一之间的每个调用都是虚拟的。
    • 我忘记就文档和 AST 生成同意您的 cmets。当我使用 Spirit 时,文档没有定义一些细节,并且代码似乎给出了不连贯和错误的结果,可能是因为开发人员没有指定它们。
    【解决方案5】:

    我想说最大的问题是缺乏对语法问题的任何诊断或其他帮助。如果您的语法不明确,则解析器可能无法解析您所期望的内容,并且没有很好的方法来注意到这一点。

    【讨论】:

    • 此外,由于过度前瞻,您可能会出现指数回溯,并且在调试时遇到麻烦。一旦我快速破解了一个无法完成解析(在几分钟内)约 100 个标记的表达式的语法。然而(我相信)这不是 Spirit 特有的,而是适用于所有当前的解析器组合库(在大多数函数式语言中可用,如 Haskell 或 Scala),它允许在您的编程语言中定义语法,而不是使用单独的工具。
    猜你喜欢
    • 1970-01-01
    • 2020-05-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多