C++ 拆分后boost spirit x3奇怪的语义行为
在我将语法分解成推荐的C++ 拆分后boost spirit x3奇怪的语义行为,c++,parsing,boost-spirit,boost-spirit-x3,C++,Parsing,Boost Spirit,Boost Spirit X3,在我将语法分解成推荐的parser.hpp,parser_def.hpp,parser.cpp文件后,我遇到了boost spirit x3的一个奇怪行为。 我的示例gramar解析了一些简单的枚举: enum = "enum" > identifier > "{" > identifier % "," > "} 这是我的枚举语法。 当我没有将enum和identifier解析器拆分到推荐的文件中时,一切都正常,尤其是字符串“enum{foo,bar}” 按预期抛出预期
parser.hpp
,parser_def.hpp
,parser.cpp
文件后,我遇到了boost spirit x3的一个奇怪行为。
我的示例gramar解析了一些简单的枚举:
enum = "enum" > identifier > "{" > identifier % "," > "}
这是我的枚举语法。
当我没有将enum和identifier解析器拆分到推荐的文件中时,一切都正常,尤其是字符串“enum{foo,bar}”
按预期抛出预期失败。
此示例可在此处找到:
但是当我将完全相同的语法分割成不同的文件时,解析器抛出
terminate called after throwing an instance of 'std::logic_error'
what(): basic_string::_M_construct null not valid
正在尝试分析同一字符串“enum{foo,bar}”
此示例可在此处找到:
#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)
#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)
}}
#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();
}
#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)
}}
#pragma一次
#包括
#包括“identifier.hpp”
命名空间分析器{namespace impl{
名称空间x3=boost::spirit::x3;
常量标识符\u type identifier=“identifier”;
自动常量标识符_def=x3::lexeme[
((x3::alpha |'')>>*(x3::alnum |''))
];
增强精神定义(标识符)
}}
#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();
}
#pragma一次
#包括
命名空间分析器{namespace impl{
名称空间x3=boost::spirit::x3;
typedef x3::规则标识符_类型;
BOOST_SPIRIT_DECLARE(标识符类型)
}}
名称空间分析器{
常量impl::标识符类型和标识符();
}
#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);
}
#包括
#包括“ast.hpp”
#包括“enum.hpp”
名称空间x3=boost::spirit::x3;
模板
bool测试(const std::string&str、解析器&p、属性&attr)
{
使用迭代器\u type=std::string::const\u迭代器;
迭代器_type in=str.begin();
迭代器_type end=str.end();
bool ret=x3::phrase_parse(in,end,p,x3::ascii::space,attr);
ret&=(in==结束);
返回ret;
}
int main(){
ast::枚举属性;
test(“enumfoo{foo,bar}”,parser::enum_parser(),attr);
test(“enum{foo,bar}”,parser::enum_parser(),attr);
}
编辑:我的repo是一个抛出
std::logic\u错误而不是期望\u失败的示例,我已经找到了错误的原因
错误在于expect指令在parser::impl::identifier
初始值设定项运行之前按值接受subject parser
要可视化,请想象在parser::impl::enum_parser
之前运行的parser::impl::identifier
的静态初始值设定项。这对于编译器来说是有效的
因此,副本有一个未初始化的name
字段,该字段在期望点尝试使用which
成员构造x3::expectation\u failure
时失败,因为从nullptr
构造std::string
是非法的
总之,我担心根本原因是。我会看看我是否能修复它并提交一份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
和*\u def.hpp
文件组合成每组一个文件。*.h
文件可以而且应该保留
ls*_def.hpp>parser_def.hpp
(假设parser_def.hpp
不存在)并编辑parser_def.hpp
以正确的顺序包含文件。删除冗余行(添加页眉保护等)。目标是parser_def.hpp
以正确的顺序包含其他文件
cat*.cpp>parser.cpp
并编辑语法正确的parser.cpp
,将所有#include
行替换为顶部的单个#include
。在构建文件(例如make或cmake)中,将*.cpp
文件的编译替换为单个解析器.cpp
您可以删除旧的*.cpp
文件
您失去了单独编译文件的便利性,但它将避免静态初始化顺序的失败。我无法复制它。不是看代码而是对我有用。GCC/clang1.63。顺便说一句,请更方便地打包?魔杖盒中的代码不会重现这种行为?我认为当标识符解析器ist splittet upI说我无法复制它时,问题就出现了。用那个密码。如果你愿意:你不需要enum_parser.hpp和id.hpp来重现逻辑错误,但我会在5分钟后看一看。我无法编译你的示例,我将用一个示例建立一个git repo添加一个在我的机器上工作的解决方法(使用clang和gcc)是的。这是一个棘手的问题。正如您所看到的,它需要复制特定的构建步骤才能重现:)在我的项目中,我有时会遇到这个std::logic_错误,但直到现在才能够如此隔离错误。。。可能是因为源代码顺序的更改将其固定在gcc和clang上。我很高兴这一次它成功了,下面是Pull请求:它不会神奇地“修复”问题,但会有助于在调试构建中注意到这种情况。所以除了更改源代码顺序以避免这种情况外,没有其他方法了?没有符合标准的溶液?
#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();
}
#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);
}
set(SOURCE_FILES
identifier.cpp
enum.cpp
main.cpp
)