拆分后 boost spirit x3 的奇怪语义行为
Strange semantic behaviour of boost spirit x3 after splitting
在我将语法拆分为推荐的 parser.hpp
、parser_def.hpp
、parser.cpp
文件后,我遇到了 boost spirit x3 的奇怪行为。
我的示例语法解析了一些简单的枚举:
enum = "enum" > identifier > "{" > identifier % "," > "}
这是我的枚举语法。
当我不将枚举和标识符解析器拆分为推荐文件时,一切正常,尤其是字符串 "enum {foo, bar}"
如预期的那样抛出预期失败。
这个例子可以在这里找到:unsplitted working example
但是当我将完全相同的语法拆分到不同的文件中时,解析器会抛出
terminate called after throwing an instance of 'std::logic_error'
what(): basic_string::_M_construct null not valid
试图解析同一个字符串 "enum {foo, bar}"
此示例可在此处找到:splitted strange example
ast.hpp
#pragma once
#include <vector>
#include <string>
#include <boost/fusion/include/adapt_struct.hpp>
namespace ast{
namespace x3 = boost::spirit::x3;
struct Enum {
std::string _name;
std::vector<std::string> _elements;
};
}
BOOST_FUSION_ADAPT_STRUCT(ast::Enum, _name, _elements)
config.hpp
#pragma once
#include <boost/spirit/home/x3.hpp>
namespace parser{
namespace x3 = boost::spirit::x3;
typedef std::string::const_iterator iterator_type;
typedef x3::phrase_parse_context<x3::ascii::space_type>::type context_type;
}
enum.cpp
#include "enum_def.hpp"
#include "config.hpp"
namespace parser { namespace impl {
BOOST_SPIRIT_INSTANTIATE(enum_type, iterator_type, context_type)
}}
namespace parser {
const impl::enum_type& enum_parser()
{
return impl::enum_parser;
}
}
enum_def.hpp
#pragma once
#include "identifier.hpp"
#include "enum.hpp"
#include "ast.hpp"
namespace parser{ namespace impl{
namespace x3=boost::spirit::x3;
const enum_type enum_parser = "enum";
namespace{
const auto& identifier = parser::identifier();
}
auto const enum_parser_def =
"enum"
> identifier
> "{"
> identifier % ","
>"}";
BOOST_SPIRIT_DEFINE(enum_parser)
}}
enum.hpp
#pragma once
#include <boost/spirit/home/x3.hpp>
#include "ast.hpp"
namespace parser{ namespace impl{
namespace x3=boost::spirit::x3;
typedef x3::rule<class enum_class, ast::Enum> enum_type;
BOOST_SPIRIT_DECLARE(enum_type)
}}
namespace parser{
const impl::enum_type& enum_parser();
}
identifier.cpp
#include "identifier_def.hpp"
#include "config.hpp"
namespace parser { namespace impl {
BOOST_SPIRIT_INSTANTIATE(identifier_type, iterator_type, context_type)
}}
namespace parser {
const impl::identifier_type& identifier()
{
return impl::identifier;
}
}
identifier_def.hpp
#pragma once
#include <boost/spirit/home/x3.hpp>
#include "identifier.hpp"
namespace parser{ namespace impl{
namespace x3=boost::spirit::x3;
const identifier_type identifier = "identifier";
auto const identifier_def = x3::lexeme[
((x3::alpha | '_') >> *(x3::alnum | '_'))
];
BOOST_SPIRIT_DEFINE(identifier)
}}
identifier.hpp
#pragma once
#include <boost/spirit/home/x3.hpp>
namespace parser{ namespace impl{
namespace x3=boost::spirit::x3;
typedef x3::rule<class identifier_class, std::string> identifier_type;
BOOST_SPIRIT_DECLARE(identifier_type)
}}
namespace parser{
const impl::identifier_type& identifier();
}
main.cpp
#include <boost/spirit/home/x3.hpp>
#include "ast.hpp"
#include "enum.hpp"
namespace x3 = boost::spirit::x3;
template<typename Parser, typename Attribute>
bool test(const std::string& str, Parser&& p, Attribute&& attr)
{
using iterator_type = std::string::const_iterator;
iterator_type in = str.begin();
iterator_type end = str.end();
bool ret = x3::phrase_parse(in, end, p, x3::ascii::space, attr);
ret &= (in == end);
return ret;
}
int main(){
ast::Enum attr;
test("enum foo{foo,bar}", parser::enum_parser(), attr);
test("enum {foo,bar}", parser::enum_parser(), attr);
}
这是一个错误,我是否遗漏了什么,或者这是预期的行为吗?
编辑:here 是我的回购协议,其中有一个示例抛出 std::logic_error
而不是 expectation_failure
我找到了错误的原因。
这个错误是因为 expect 指令在 parser::impl::identifier
初始化器运行之前按值接受它的主题解析器。
To visualize, imagine the static initializer for parser::impl::enum_parser
running before parser::impl::identifier
. This is valid for a compiler to do.
因此,该副本有一个未初始化的 name
字段,一旦期望点尝试使用 which_
成员构造 x3::expectation_failure
,该字段就会失败,因为构造一个 std::string
来自 nullptr
是非法的。
总而言之,恐怕根本原因是Static Initialization Order Fiasco。我会看看我是否可以修复它并提交 PR。
解决方法:
一个直接的解决方法是倒序列出源文件的顺序,以便在定义之后使用:
set(SOURCE_FILES
identifier.cpp
enum.cpp
main.cpp
)
请注意,如果这在您的编译器上修复了它(在我的编译器上修复了),那就是实现定义的。该标准未指定跨编译单元的静态初始化顺序。
这是一个对我有用的解决方案,但上面的解决方法并不适用。
假设您有 a.cpp
、a.h
、a_def.hpp
、b.cpp
、b.h
、b_def.hpp
、...推荐的文件通过 Boost.Spirit X3 文档。
基本思路是将 *.cpp
和 *_def.hpp
文件合并为每组 1 个文件。 *.h
文件可以而且应该保留。
ls *_def.hpp > parser_def.hpp
(假设 parser_def.hpp
不存在)并以正确的顺序编辑 parser_def.hpp
到 #include
文件。删除冗余行(添加 header 守卫等)目标是 parser_def.hpp
以正确的顺序包含其他文件。
cat *.cpp > parser.cpp
并编辑 parser.cpp
使其在句法上正确,并在顶部用单个 #include <parser_def.hpp>
替换所有 #include <*_def.hpp>
行。在您的构建文件(例如 make 或 cmake)中,将 *.cpp
文件的编译替换为单个 parser.cpp
.
- 您可以删除旧的
*.cpp
文件。
你失去了单独编译文件的便利,但它会避免静态初始化顺序 Fiasco。
在我将语法拆分为推荐的 parser.hpp
、parser_def.hpp
、parser.cpp
文件后,我遇到了 boost spirit x3 的奇怪行为。
我的示例语法解析了一些简单的枚举:
enum = "enum" > identifier > "{" > identifier % "," > "}
这是我的枚举语法。
当我不将枚举和标识符解析器拆分为推荐文件时,一切正常,尤其是字符串 "enum {foo, bar}"
如预期的那样抛出预期失败。
这个例子可以在这里找到:unsplitted working example
但是当我将完全相同的语法拆分到不同的文件中时,解析器会抛出
terminate called after throwing an instance of 'std::logic_error'
what(): basic_string::_M_construct null not valid
试图解析同一个字符串 "enum {foo, bar}"
此示例可在此处找到:splitted strange example
ast.hpp
#pragma once #include <vector> #include <string> #include <boost/fusion/include/adapt_struct.hpp> namespace ast{ namespace x3 = boost::spirit::x3; struct Enum { std::string _name; std::vector<std::string> _elements; }; } BOOST_FUSION_ADAPT_STRUCT(ast::Enum, _name, _elements)
config.hpp
#pragma once #include <boost/spirit/home/x3.hpp> namespace parser{ namespace x3 = boost::spirit::x3; typedef std::string::const_iterator iterator_type; typedef x3::phrase_parse_context<x3::ascii::space_type>::type context_type; }
enum.cpp
#include "enum_def.hpp" #include "config.hpp" namespace parser { namespace impl { BOOST_SPIRIT_INSTANTIATE(enum_type, iterator_type, context_type) }} namespace parser { const impl::enum_type& enum_parser() { return impl::enum_parser; } }
enum_def.hpp
#pragma once #include "identifier.hpp" #include "enum.hpp" #include "ast.hpp" namespace parser{ namespace impl{ namespace x3=boost::spirit::x3; const enum_type enum_parser = "enum"; namespace{ const auto& identifier = parser::identifier(); } auto const enum_parser_def = "enum" > identifier > "{" > identifier % "," >"}"; BOOST_SPIRIT_DEFINE(enum_parser) }}
enum.hpp
#pragma once #include <boost/spirit/home/x3.hpp> #include "ast.hpp" namespace parser{ namespace impl{ namespace x3=boost::spirit::x3; typedef x3::rule<class enum_class, ast::Enum> enum_type; BOOST_SPIRIT_DECLARE(enum_type) }} namespace parser{ const impl::enum_type& enum_parser(); }
identifier.cpp
#include "identifier_def.hpp" #include "config.hpp" namespace parser { namespace impl { BOOST_SPIRIT_INSTANTIATE(identifier_type, iterator_type, context_type) }} namespace parser { const impl::identifier_type& identifier() { return impl::identifier; } }
identifier_def.hpp
#pragma once #include <boost/spirit/home/x3.hpp> #include "identifier.hpp" namespace parser{ namespace impl{ namespace x3=boost::spirit::x3; const identifier_type identifier = "identifier"; auto const identifier_def = x3::lexeme[ ((x3::alpha | '_') >> *(x3::alnum | '_')) ]; BOOST_SPIRIT_DEFINE(identifier) }}
identifier.hpp
#pragma once #include <boost/spirit/home/x3.hpp> namespace parser{ namespace impl{ namespace x3=boost::spirit::x3; typedef x3::rule<class identifier_class, std::string> identifier_type; BOOST_SPIRIT_DECLARE(identifier_type) }} namespace parser{ const impl::identifier_type& identifier(); }
main.cpp
#include <boost/spirit/home/x3.hpp> #include "ast.hpp" #include "enum.hpp" namespace x3 = boost::spirit::x3; template<typename Parser, typename Attribute> bool test(const std::string& str, Parser&& p, Attribute&& attr) { using iterator_type = std::string::const_iterator; iterator_type in = str.begin(); iterator_type end = str.end(); bool ret = x3::phrase_parse(in, end, p, x3::ascii::space, attr); ret &= (in == end); return ret; } int main(){ ast::Enum attr; test("enum foo{foo,bar}", parser::enum_parser(), attr); test("enum {foo,bar}", parser::enum_parser(), attr); }
这是一个错误,我是否遗漏了什么,或者这是预期的行为吗?
编辑:here 是我的回购协议,其中有一个示例抛出 std::logic_error
而不是 expectation_failure
我找到了错误的原因。
这个错误是因为 expect 指令在 parser::impl::identifier
初始化器运行之前按值接受它的主题解析器。
To visualize, imagine the static initializer for
parser::impl::enum_parser
running beforeparser::impl::identifier
. This is valid for a compiler to do.
因此,该副本有一个未初始化的 name
字段,一旦期望点尝试使用 which_
成员构造 x3::expectation_failure
,该字段就会失败,因为构造一个 std::string
来自 nullptr
是非法的。
总而言之,恐怕根本原因是Static Initialization Order Fiasco。我会看看我是否可以修复它并提交 PR。
解决方法:
一个直接的解决方法是倒序列出源文件的顺序,以便在定义之后使用:
set(SOURCE_FILES
identifier.cpp
enum.cpp
main.cpp
)
请注意,如果这在您的编译器上修复了它(在我的编译器上修复了),那就是实现定义的。该标准未指定跨编译单元的静态初始化顺序。
这是一个对我有用的解决方案,但上面的解决方法并不适用。
假设您有 a.cpp
、a.h
、a_def.hpp
、b.cpp
、b.h
、b_def.hpp
、...推荐的文件通过 Boost.Spirit X3 文档。
基本思路是将 *.cpp
和 *_def.hpp
文件合并为每组 1 个文件。 *.h
文件可以而且应该保留。
ls *_def.hpp > parser_def.hpp
(假设parser_def.hpp
不存在)并以正确的顺序编辑parser_def.hpp
到#include
文件。删除冗余行(添加 header 守卫等)目标是parser_def.hpp
以正确的顺序包含其他文件。cat *.cpp > parser.cpp
并编辑parser.cpp
使其在句法上正确,并在顶部用单个#include <parser_def.hpp>
替换所有#include <*_def.hpp>
行。在您的构建文件(例如 make 或 cmake)中,将*.cpp
文件的编译替换为单个parser.cpp
.- 您可以删除旧的
*.cpp
文件。
你失去了单独编译文件的便利,但它会避免静态初始化顺序 Fiasco。