摘要令我惊讶的是,我再次学习了不喜欢X3的规则分离魔术的原因.但是,如果需要,则可能不应该混合使用,而应该离线定义 parser2 . namespace unit2 {parser2_t parser2 ="unit2_rule";auto const parser2_def =跟踪地址:">>parser1();BOOST_SPIRIT_DEFINE(parser2)BOOST_SPIRIT_INSTANTIATE(parser2_t,iter_t,context_t)}//命名空间unit2 看到它 再次在魔盒上生活 完整列表对于 Wandbox : 文件 unit1.cpp #include"unit1.h"命名空间unit1 {parser1_t parser1 ="unit1_rule";自动const parser1_def = x3 :: uint_;BOOST_SPIRIT_DEFINE(parser1)BOOST_SPIRIT_INSTANTIATE(parser1_t,iter_t,context_t)}//命名空间unit1unit1 :: parser1_t const& parser1(){return unit1 :: parser1;} 文件 unit1.h #ifndef UNIT1_H#定义UNIT1_H#include"boost/spirit/home/x3.hpp"#include"boost/spirit/include/support_istream_iterator.hpp"#include< cstdint>名称空间x3 = boost :: spirit :: x3;使用iter_t = boost :: spirit :: istream_iterator;使用context_t = x3 :: phrase_parse_context< x3 :: ascii :: space_type> :: type;命名空间unit1 {使用parser1_t = x3 :: rule< class u1,std :: uint64_t>const;BOOST_SPIRIT_DECLARE(parser1_t)}//命名空间unit1unit1 :: parser1_t const& parser1();#endif/* UNIT1_H */ 文件 unit2.cpp #include"unit2.h"#include"unit1.h"命名空间unit2 {parser2_t parser2 ="unit2_rule";auto const parser2_def =跟踪地址:">>parser1();BOOST_SPIRIT_DEFINE(parser2)BOOST_SPIRIT_INSTANTIATE(parser2_t,iter_t,context_t)}//命名空间unit2unit2 :: parser2_t const& parser2(){return unit2 :: parser2;} 文件 unit2.h #ifndef UNIT2_H#定义UNIT2_H#include"boost/spirit/home/x3.hpp"#include"boost/spirit/include/support_istream_iterator.hpp"#include< cstdint>名称空间x3 = boost :: spirit :: x3;使用iter_t = boost :: spirit :: istream_iterator;使用context_t = x3 :: phrase_parse_context< x3 :: ascii :: space_type> :: type;命名空间unit2 {使用parser2_t = x3 :: rule< class u2,std :: uint64_t>const;BOOST_SPIRIT_DECLARE(parser2_t)}//命名空间unit2unit2 :: parser2_t const& parser2();#endif/* UNIT2_H */ 文件 main.cpp #include"unit2.h"#include< iostream>名称空间x3 = boost :: spirit :: x3;int main(){std :: string input(追踪地址:123434");std :: istringstream i(input);std :: cout<<"解析:"<<输入<<"\ 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<<"结果:"<<(v?"OK":"Failed")<<"\ n";std :: cout<<"结果:"<<addr<<"\ n";返回v;} IntroductionI am trying to use two non-terminal rules while they are not defined in the same translation unit. A minimal example reproducing the issue is provided below, and is also available live on ColiruTEST0Re-using a rule directly (without embedding it into another rule) works OK, despite it is defined in another translation unit. This is the well known X3 program structure example from X3 documentation. This is the configuration TEST0 in the live test below.TEST1I initially avoided the use of the BOOST_SPIRIT_DEFINE/DECLARE/INSTANTIATE() macros for one of the non terminal rule with:auto const parser2 = x3::rule<class u2,uint64_t>{"parser2"} = "Trace Address: " >> parser1();which resulted in an unresolved external symbol linker error. Surprisingly, the missing culprit is the parser1's symbol (and not parser2's), for which the BOOST_XXX macros are used (see unit1.cpp). This is the configuration TEST1TEST2I then moved to configuration TEST2 where BOOST_XXX macros are defined for the two rules. This solution compiles and runs with Visual Studio 2019 (v16.8.3) but produces a core dump with g++ (as can been seen on the test below).Minimal example reproducing the issueunit1.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),iter_t,context_t)#endifint 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;}I feel I am not doing these things correctly, here are my questions:Unresolved external symbols and parser ContextIn configuration TEST1 the error message is undefined reference to unit1::parse_rule<...> which means the parser1 is not instantiated with the right context. OK, but then what context shall I use in such situation ? Even if I move parser2 out of the main() function, I get more or less the same issue. I can display the context of course, and try to BOOST_SPIRIT_INSTANTIATE() with it but I feel this is not the way to go. Surprisingly, it seems instantiating the parser2 instead, solves the issue (on Visual Studio at least)Mixing rules from separated translation unitsWhy is it so complicated, whereas if I remove the rule in parser2, every thing works ok ? 解决方案 The machinary to statically link rule definitions to rules by their tag-type (rule-id) is tricky. It in fact hinges on there being a specialization of a parse_rule¹ function template.However, the function template depends on:the rule id ("tag type")iterator typethe context (includes things like skipper or with<> directives)All of the types must match exactly. This is a frequent source of error.Likely because either the rule definition is visible to the compiler to instantiate at that point, or alternatively because the types match up as just described.I'll look at your specific code shortly.REPROTEST0 https://wandbox.org/permlink/ElHfvW343nvqiT2pTEST1 https://wandbox.org/permlink/37NgtQvXeAwwdoU6 - problemTEST2 https://wandbox.org/permlink/HwhqI5v7FEf0I2I7Reading The Compiler MessagesMy compiler warns with -DTEST1:This spells the exact type arguments for the template specialization to explicitly-instantiate in a TU.The linker error spells the missing symbol:All in all your task is to compare them (!!) and note the discrepancy.Reading The Macro MagicExpanding the macros getstemplate <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&);Which is for the ...DEFINE: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);}And for the explicit ...INSTANTIATE: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&);Substituting out the types shows exactly what is instantiated (see the warning above).Other OptionsShort of straining my eyes, we know what template type params could be wrong, so let's check them:iterator:static_assert(std::is_same_v<iter_t, boost::spirit::istream_iterator>);iter_t b{i >> std::noskipws}, e {};This was not the culprit, the compiler confirms.The skipper ought to be x3::ascii::space_type which also seems to match up fine.The problem must be the context. Now let's extract the context from the linker error: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 &);Doesn't look like the context is actually what we expect. I reckon the problem is that the rule2 definition is "in sight" leading to the context containing the definition (this is the mechanism that allows local x3::rule definitions without define macro magic at all).On di, 05. jan 13:12, Larry Evans wrote:Thanks for pointing this out. I've run into this without realizingthat omitting the definition-separation was a critical factor.I guess then that it also could provide relief in some cases thatcause extreme template recursion when the rules change skipper(Because the context keeps being technically different).Again, this is actually a very helpful note. Thanks.SethEarlier in the thread I express reasons why I dislike the macro machinery and never spread my X3 rules across TUs. By now you might appreciate that sentiment :)WorkaroundsYou could workaround by manufacturing a correct context type and instantiate that (as well): (unit1.h)struct u2;using context2_t = x3::context< u2, decltype("" >> parser1_t{}) const, context_t>;BOOST_SPIRIT_DECLARE(parser1_t)And in the cpp:BOOST_SPIRIT_DEFINE(parser1)BOOST_SPIRIT_INSTANTIATE(parser1_t, iter_t, context_t) // optionallyBOOST_SPIRIT_INSTANTIATE(parser1_t, iter_t, context2_t)Not surprisingly, this works: https://wandbox.org/permlink/Y6NsKCcIDgiHGJf2SummaryTo my own surprise, I once again learn a reason to dislike X3's rule separation magic. However, if you need it, you should probably not mix and match, but define parser2 out-of-line as well.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 unit2See it Live On Wandbox againFull ListingsFor posterity from Wandbox:File 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; }File 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 */File 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; }File 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 */File 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; } 这篇关于混合来自分离翻译单元的非终结规则的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持! 上岸,阿里云! 09-05 08:17