将 Boost.Spirit.X3 解析器拆分为多个 TU

Splitting Boost.Spirit.X3 parsers into several TUs

本文关键字:拆分 TU Boost Spirit X3      更新时间:2023-10-16

我又在为Boost.Spirit.X3而苦苦挣扎。

我有几个解析器的逻辑组(语句、表达式等(,每个逻辑组都由几个文件表示:

  • group.hpp- 包含typedefs、BOOST_SPIRIT_DECLAREextern变量声明,用于"外部"使用的解析器
  • group_def.hpp- 包括前一个,并包含解析器、BOOST_SPIRIT_DEFINE等的实际定义。
  • group.cpp- 包括前一个并包含显式模板实例化(通过BOOST_SPIRIT_INSTANTIATE(

基本上,它或多或少遵循官方教程中提出的结构。唯一的区别是我的语法要复杂得多,所以我试图将其分成几个翻译单元。然后将所有这些 TU 编译到一个库中,该库又链接到主可执行文件。

我试图在这里做一个"最小"的例子(在 Wandbox 上直播(,因为在这里列出所有文件会很不方便。 由于一些未解决的外部问题,它不起作用,所以,最有可能的是,我错误地实例化了一些东西,但我已经为此花了大约一周的时间,所以我非常绝望,觉得我无法自己处理这个问题。

几个问题和答案:

为什么我更喜欢每个"组"使用三个文件?

首先,因为我试图以这样一种方式制作它,即我不想在任何小的更改上重新编译所有内容(不确定我是否成功了(,所以这个想法是somegroup.hpp只是一个带有声明的"轻量级"标头,somegroup_def.hpp是一个带有定义的标头,somegroup.cpp只是为了创建一个翻译单元。

其次,我将_def.hpp.cpp分开,因为我还将这些_def.hpp文件直接包含在测试中,其中我不仅涵盖extern解析器,还涵盖"内部"辅助解析器。

为什么要使用extern变量?

我也尝试了使用返回解析器的函数(类似于教程中的完成方式(。基本上,这就是它现在的实现和工作方式。我不喜欢它,因为,例如,给定一个解析器lang::parser::import,我必须给函数另一个名称(lang::parser::import_(或将其放在另一个命名空间中(即lang::import(。此外,我喜欢直接使用解析器的方式,就像在精神本身中完成的那样(即没有括号:importvsimport_()(。

我的实际问题如下:

  • 如果我想将解析器分布在多个翻译单元上,如何正确组织结构?
  • 在上面的代码示例中,我到底缺少什么,以至于它没有链接?

我将不胜感激任何帮助。

为什么我更喜欢每个"组"使用三个文件?

我自己也觉得分离成grammar_def.hppgrammar.cpp没用,但这只是一种意见。

为什么我使用外部变量?

我也尝试了使用返回解析器的函数

不要这样做。这将导致静态初始化订单惨败。规则占位符足够轻量级,可以在每个翻译单元中实例化它们。

如果我想将解析器分布在多个翻译单元上,如何正确组织结构?

这是一个关于品味的问题。您所需要的只是在其中一个 TU 中实例化一个规则,并在使用它的所有其他规则中都有一个x3::rule<...>+BOOST_SPIRIT_DECLARE

实现这一目标的最佳方法是将x3::rule<...>.cpp/_def.hpp拆分为单独的标头(将其放入您的"轻量级".hpp中(,并将其包含在需要这些规则的每个 TU 中。

查看 https://github.com/mapnik/mapnik/pull/4072/files 和 https://github.com/boostorg/spirit/pull/493/files

在上面的代码示例中,我到底缺少什么,以至于它没有链接?

  1. 您正在混合std::string::const_iteratorstd::string::iterator迭代器。
  2. 您正在使用一些船长作为解析器(f.ex.document_def = eols >> +megarule >> eols(,但不要使用适当的上下文实例化它们。要么干脆不将它们设为规则,要么使用错误消息中看到的上下文添加BOOST_SPIRIT_INSTANTIATE