微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

混合来自分离翻译单元的非终端规则 unit1.hunit1.cppmain.cpp未解析的外部符号和解析器上下文分离翻译单元的混合规则

如何解决混合来自分离翻译单元的非终端规则 unit1.hunit1.cppmain.cpp未解析的外部符号和解析器上下文分离翻译单元的混合规则

简介

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

TEST0
直接重用规则(不将其嵌入到另一个规则中)可以正常工作,尽管它是在另一个翻译单元中定义的。这是 X3 文档中众所周知的 X3 program structure 示例。这是下面实时测试中的配置 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++ 生成核心转储(如下面的测试所示)。

重现问题的最小示例

unit1.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 */

unit1.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; }

main.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),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,parser2,addr);
#elif defined TEST2
    bool v = x3::phrase_parse(b,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 中的规则,一切正常?

解决方法

问。为什么这么复杂[...]

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

但是,函数模板依赖于:

  • 规则 ID(“标签类型”)
  • 迭代器类型
  • 上下文(包括 skipper 或 with<> 指令之类的内容)

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

Q. [...] 而如果我删除 parser2 中的规则,一切正常吗?

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

我会尽快查看您的具体代码。

重现

阅读编译器消息

我的编译器用 -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>,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

这将模板特化的 exact 类型参数拼写为 TU 中的 explicitly-instantiate

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

/home/sehe/custom/spirit/include/boost/spirit/home/x3/nonterminal/rule.hpp:135: undefined reference to 布尔 unit1::parse_rule<:spirit::basic_istream_iterator std::char_traits>,boost::spirit::x3::context<:u2 boost::spirit::x3::sequence const boost::spirit::char_encoding::standard boost::spirit::x3::unused_type>,boost::spirit::x3::rule<:u1> > 常量, boost::spirit::x3::context<:spirit::x3::skipper_tag boost::spirit::x3::char_class boost::spirit::x3::space_tag> const,boost::spirit::x3::unused_type> >

(boost::spirit::x3::rule<:u1 unsigned long false>,boost::spirit::basic_istream_iterator&,boost::spirit::basic_istream_iterator const&,boost::spirit::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),decltype(parser1)::attribute_type& attr)
{
    using boost::spirit::x3::unused;
    static auto const def_ = (parser1 = parser1_def);
    return def_.parse(first,attr);
}

对于显式的 ...INSTANTIATE:

template bool parse_rule<iter_t,context_t>(parser1_t rule_,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 *,x3::unused_type>,x3::rule<unit1::u1,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/

1 月 5 日下午 13:12,拉里·埃文斯写道:

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

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

感谢您指出这一点。我在不知不觉中遇到了这个 省略定义分离是一个关键因素。

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

同样,这实际上是一个非常有用的注释。谢谢。

赛特

在该主题的前面部分,我表达了为什么我不喜欢宏机制并且从不在 TU 之间传播我的 X3 规则的原因。现在你可能会欣赏这种情绪:)

解决方法

您可以通过制造正确的上下文类型并实例化(也)来解决:(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,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,context_t)
} // namespace unit2

再看一遍Live On Wandbox

完整列表

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,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,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;
     }
    

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。