混合来自分离翻译单元的非终端规则



简介

我正在尝试使用两个非终端规则,而它们未在同一翻译单元中定义。下面提供了一个重现该问题的最小示例,也可以在Coliru 上实时获得

TEST0
直接重用规则(不将其嵌入到另一个规则中)工作正常,尽管它是在另一个翻译单元中定义的。这是 X3 文档中众所周知的 X3 程序结构示例。这是下面实时测试中TEST0的配置。


TEST1我最初避免将BOOST_SPIRIT_DEFINE/DECLARE/INSTANTIATE()宏用于非终端规则之一:

auto const parser2 
= x3::rule<class u2,uint64_t>{"parser2"} 
= "Trace Address: " >> parser1();

这导致了未解决的外部符号链接器错误。令人惊讶的是,缺少罪魁祸首是parser1的符号(而不是parser2的符号),BOOST_XXX宏用于此符号(参见unit1.cpp)。这是配置TEST1

TEST2
然后,我转到了配置TEST2,其中为这两个规则定义了BOOST_XXX宏。此解决方案使用 Visual Studio 2019 (v16.8.3) 编译和运行,但使用 g++ 生成核心转储(如下面的测试所示)。

重现问题的最小示例

单元1.h

#ifndef UNIT1_H
#define UNIT1_H
#include <cstdint>
#include "boost/spirit/home/x3.hpp"
#include "boost/spirit/include/support_istream_iterator.hpp"
namespace x3 = boost::spirit::x3;
using iter_t = boost::spirit::istream_iterator;
using context_t = x3::phrase_parse_context<x3::ascii::space_type>::type;
namespace unit1 {
using parser1_t = x3::rule<class u1, std::uint64_t>;
BOOST_SPIRIT_DECLARE(parser1_t);
}
unit1::parser1_t const& parser1();
#endif /* UNIT1_H */

单元1.cpp

#include "unit1.h"
namespace unit1 {
parser1_t const parser1 = "unit1_rule";
auto const parser1_def = x3::uint_;
BOOST_SPIRIT_DEFINE(parser1)
BOOST_SPIRIT_INSTANTIATE(parser1_t, iter_t, context_t)
}
unit1::parser1_t const& parser1() { return unit1::parser1; }

主.cpp

#include <iostream>
#include "unit1.h"
namespace x3 = boost::spirit::x3;
#define TEST2
#ifdef TEST2
auto const parser2 = x3::rule<class u2, uint64_t>{"parser2"};
auto const parser2_def = "Trace address: " >> parser1();
BOOST_SPIRIT_DECLARE(decltype(parser2))
BOOST_SPIRIT_DEFINE(parser2)
BOOST_SPIRIT_INSTANTIATE(decltype(parser2),iter_t,context_t)
#endif
int main(int argc, char* argv[])
{
std::string input("Trace address: 123434");
std::istringstream i(input);
std::cout << "parsing: " << input << "n";
boost::spirit::istream_iterator b{i >> std::noskipws};
boost::spirit::istream_iterator e{};
uint64_t addr=0;
#ifdef TEST0
bool v = x3::phrase_parse(b, e, "Trace address: " >> parser1(), x3::ascii::space,addr);
#elif defined TEST1
auto const parser2 
= x3::rule<class u2, uint64_t>{ "parser2" } 
= "Trace address: " >> parser1();
bool v = x3::phrase_parse(b, e, parser2, x3::ascii::space,addr);
#elif defined TEST2
bool v = x3::phrase_parse(b, e, parser2, x3::ascii::space,addr);
#endif 
std::cout << "result: " << (v ? "OK" : "Failed") << "n";
std::cout << "result: " << addr << "n";
return v;
}

我觉得我没有正确地做这些事情,这是我的问题:

未解析的外部符号和分析器上下文

在配置TEST1错误消息是对unit1::parse_rule<...>的未定义引用,这意味着parser1未使用正确的上下文进行实例化。好的,但是在这种情况下我应该使用什么上下文?即使我parser2移出main()功能,我也或多或少会遇到同样的问题。当然,我可以展示上下文,并尝试BOOST_SPIRIT_INSTANTIATE()它,但我觉得这不是要走的路。令人惊讶的是,似乎实例化parser2反而解决了这个问题(至少在Visual Studio上)。

混合来自分离翻译单元的规则

为什么它如此复杂,而如果我删除parser2中的规则,一切都可以正常工作?

问:为什么它如此复杂[...]

通过标签类型(规则 ID)将规则定义静态链接到规则的机制很棘手。事实上,这取决于 parse_rule¹ 函数模板的专用化。

但是,函数模板取决于:

  • 规则 ID("标记类型")
  • 迭代器类型
  • 上下文(包括 skipper 或with<>指令等内容)

所有类型必须完全匹配。这是常见的错误来源。

Q.[...] 而如果我删除 parser2 中的规则,一切都可以正常工作?

可能是因为规则定义对编译器可见,可以在此时实例化,或者因为类型匹配,如刚才所述。

我稍后会看看你的具体代码。

重现

  • 测试0 https://wandbox.org/permlink/ElHfvW343nvqiT2p
  • 测试1 https://wandbox.org/permlink/37NgtQvXeAwwdoU6 - 问题
  • 测试2 https://wandbox.org/permlink/HwhqI5v7FEf0I2I7

阅读编译器消息

我的编译器使用 -DTEST1 发出警告:

unit1.h|13 col 5| warning: ‘bool unit1::parse_rule(unit1::parser1_t, Iterator&, const Iterator&, const Context&, boost::spirit::x3::rule<unit1::u1, long unsigned int>::attribute_type&) [with Iterator = boost::spirit::basic_istream_iterator<char>; Context = boost::spirit::x3::context<main()::u2, const boost::spirit::x3::sequence<boost::spirit::x3::literal_string<const char*, boost::spirit::char_encoding::standard, boost::spirit::x3::unused_type>, boost::spirit::x3::rule<unit1::u1, long unsigned int> >, boost::spirit::x3::context<boost::spirit::x3::skipper_tag, const boost::spirit::x3::char_class<boost::spirit::char_encoding::ascii, boost::spirit::x3::space_tag>, boost::spirit::x3::unused_type> >]’ used but never defined

这拼写了模板专用化的确切类型参数,以便在 TU 中显式实例化。

链接器错误拼写缺少的符号:

/home/sehe/custom/spirit/include/boost/spirit/home/x3/nonterminal/rule.hpp:135: undefined reference to布尔 unit1::p arse_rule<boost::spirit::basic_istream_iterator><char,>, boost::spirit::x3::context<main::u2,>, boost::spirit::x3::rule<unit1::u1,>>康斯特, boost::spirit::x3::context<boost::spirit::x3::skipper_tag,> 康斯特,提升::精神::X3::unused_type>>

(boost::spirit::

x3::rule<unit1::u1,>, boost::spirit::basic_istream_iterator<char,>&, 提升::精神::basic_istream_iterator<字符,标准::char_traits> const&, boost::spirit::x3::context<main::u2,>, boost::spirit::x3::rule<unit1::u1,>>康斯特, boost::spirit::x3::context<boost::spirit::x3::skipper_tag,> 康斯特,提升::精神::X3::unused_type>> const&, unsigned long&)''

总而言之,您的任务是比较它们(!!)并注意差异。

阅读宏魔术

扩展宏得到

template <typename Iterator, typename Context> inline bool parse_rule( decltype(parser1) , Iterator& first, Iterator const& last , Context const& context, decltype(parser1)::attribute_type& attr) { using boost::spirit::x3::unused; static auto const def_ = (parser1 = parser1_def); return def_.parse(first, last, context, unused, attr); }
template bool parse_rule<iter_t, context_t>( parser1_t rule_ , iter_t& first, iter_t const& last , context_t const& context, parser1_t::attribute_type&);

这是为了...定义:

template <typename Iterator, typename Context>
inline bool parse_rule(decltype(parser1), Iterator& first,
Iterator const& last, Context const& context,
decltype(parser1)::attribute_type& attr)
{
using boost::spirit::x3::unused;
static auto const def_ = (parser1 = parser1_def);
return def_.parse(first, last, context, unused, attr);
}

对于明确的...例示:

template bool parse_rule<iter_t, context_t>(parser1_t rule_, iter_t& first,
iter_t const& last, context_t const& context,
parser1_t::attribute_type&);

替换类型可以准确显示实例化的内容(请参阅上面的警告)。

其他选项

除了让我的眼睛疲劳之外,我们知道哪些模板类型参数可能是错误的,所以让我们检查一下它们:

  1. 迭 代:

    static_assert(std::is_same_v<iter_t, boost::spirit::istream_iterator>);
    iter_t b{i >> std::noskipws}, e {};
    

    这不是罪魁祸首,编译器证实。

  2. 船长应该是x3::ascii::space_type这似乎也匹配得很好。

  3. 问题必须是背景。现在,让我们从链接器错误中提取上下文:

    bool unit1::parse_rule<...> >
    (x3::rule<unit1::u1, unsigned long, false>, iter_t &, iter_t const &,
    // this is the context:
    x3::context<
    main::u2,
    x3::sequence<x3::literal_string<char const *,
    boost::spirit::char_encoding::standard,
    x3::unused_type>,
    x3::rule<unit1::u1, unsigned long, false>> const,
    x3::context<x3::skipper_tag,
    x3::char_class<boost::spirit::char_encoding::ascii,
    x3::space_tag> const,
    x3::unused_type>> const &,
    // this is the attribtue
    unsigned long &);
    

看起来上下文实际上不是我们所期望的。我认为问题在于 rule2 定义"在眼前",导致包含定义的上下文(这是允许本地 x3::rule 定义而不定义宏魔术的机制)。

我记得最近的一个邮件列表帖子指出了这一点(当时对我来说有点惊讶): https://sourceforge.net/p/spirit/mailman/message/37194823/

05. 1月13:12,拉里·埃文斯写道:

但是,使用BOOST_SPIRIT_DEFINE还有另一个原因。 什么时候 有很多递归规则,BOOST_SPIRIT_DEFINE不是 使用,这会导致更重的模板处理和伴随 编译时间慢。 原因是,没有BOOST_SPIRIT_DEFINE, 规则的定义存储在上下文中,这就是 导致编译时间爆炸。

因此,当您注意到添加时编译时间变慢时请注意这一点 更多递归规则。

感谢您指出这一点。我在没有意识到的情况下遇到了这个 省略定义分离是一个关键因素。

我想它在某些情况下也可以提供缓解 当规则更改船长时导致极端模板递归 (因为上下文在技术上一直不同)。

同样,这实际上是一个非常有帮助的说明。谢谢。

赛斯

在帖子的前面,我表达了为什么我不喜欢宏机制并且从不将我的 X3 规则传播到 TU 的原因。到现在为止,您可能会欣赏这种情绪:)

解决方法

您可以通过制造正确的上下文类型并实例化(以及)来解决此问题:(unit1.h)

struct u2;
using context2_t = x3::context<
u2,
decltype("" >> parser1_t{}) const,
context_t>;
BOOST_SPIRIT_DECLARE(parser1_t)

在CPP中:

BOOST_SPIRIT_DEFINE(parser1)
BOOST_SPIRIT_INSTANTIATE(parser1_t, iter_t, context_t) // optionally
BOOST_SPIRIT_INSTANTIATE(parser1_t, iter_t, context2_t)

毫不奇怪,这有效:https://wandbox.org/permlink/Y6NsKCcIDgiHGJf2

总结

令我自己惊讶的是,我再次学会了一个不喜欢X3的规则分离魔法的理由。但是,如果需要它,您可能不应该混合搭配,而应该定义parser2出线。

namespace unit2 {
parser2_t parser2 = "unit2_rule";
auto const parser2_def = "Trace address: " >> parser1();
BOOST_SPIRIT_DEFINE(parser2)
BOOST_SPIRIT_INSTANTIATE(parser2_t, iter_t, context_t)
} // namespace unit2

再次在魔杖盒上观看直播

完整列表

对于Wandbox的后代:

  • 文件unit1.cpp

    #include "unit1.h"
    namespace unit1 {
    parser1_t parser1 = "unit1_rule";
    auto const parser1_def = x3::uint_;
    BOOST_SPIRIT_DEFINE(parser1)
    BOOST_SPIRIT_INSTANTIATE(parser1_t, iter_t, context_t)
    } // namespace unit1
    unit1::parser1_t const &parser1() { return unit1::parser1; }
    
  • 文件unit1.h

    #ifndef UNIT1_H
    #define UNIT1_H
    #include "boost/spirit/home/x3.hpp"
    #include "boost/spirit/include/support_istream_iterator.hpp"
    #include <cstdint>
    namespace x3    = boost::spirit::x3;
    using iter_t    = boost::spirit::istream_iterator;
    using context_t  = x3::phrase_parse_context<x3::ascii::space_type>::type;
    namespace unit1 {
    using parser1_t = x3::rule<class u1, std::uint64_t> const;
    BOOST_SPIRIT_DECLARE(parser1_t)
    } // namespace unit1
    unit1::parser1_t const &parser1();
    #endif /* UNIT1_H */
    
  • 文件unit2.cpp

    #include "unit2.h"
    #include "unit1.h"
    namespace unit2 {
    parser2_t parser2 = "unit2_rule";
    auto const parser2_def = "Trace address: " >> parser1();
    BOOST_SPIRIT_DEFINE(parser2)
    BOOST_SPIRIT_INSTANTIATE(parser2_t, iter_t, context_t)
    } // namespace unit2
    unit2::parser2_t const &parser2() { return unit2::parser2; }
    
  • 文件unit2.h

    #ifndef UNIT2_H
    #define UNIT2_H
    #include "boost/spirit/home/x3.hpp"
    #include "boost/spirit/include/support_istream_iterator.hpp"
    #include <cstdint>
    namespace x3    = boost::spirit::x3;
    using iter_t    = boost::spirit::istream_iterator;
    using context_t  = x3::phrase_parse_context<x3::ascii::space_type>::type;
    namespace unit2 {
    using parser2_t = x3::rule<class u2, std::uint64_t> const;
    BOOST_SPIRIT_DECLARE(parser2_t)
    } // namespace unit2
    unit2::parser2_t const &parser2();
    #endif /* UNIT2_H */
    
  • 文件main.cpp

    #include "unit2.h"
    #include <iostream>
    namespace x3 = boost::spirit::x3;
    int main() {
    std::string input("Trace address: 123434");
    std::istringstream i(input);
    std::cout << "parsing: " << input << "n";
    static_assert(std::is_same_v<iter_t, boost::spirit::istream_iterator>);
    iter_t b{i >> std::noskipws}, e {};
    uint64_t addr = 0;
    bool v = x3::phrase_parse(b, e, parser2(), x3::ascii::space, addr);
    std::cout << "result: " << (v ? "OK" : "Failed") << "n";
    std::cout << "result: " << addr << "n";
    return v;
    }
    

最新更新