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}”

此示例可在此处找到:

  • 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)
    
  • 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();
    }
    
  • 标识符_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)
    }}
    
    #pragma一次
    #包括
    #包括“identifier.hpp”
    命名空间分析器{namespace impl{
    名称空间x3=boost::spirit::x3;
    常量标识符\u type identifier=“identifier”;
    自动常量标识符_def=x3::lexeme[
    ((x3::alpha |'')>>*(x3::alnum |''))
    ];
    增强精神定义(标识符)
    }}
    
  • 标识符.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();
    }
    
    #pragma一次
    #包括
    命名空间分析器{namespace impl{
    名称空间x3=boost::spirit::x3;
    typedef x3::规则标识符_类型;
    BOOST_SPIRIT_DECLARE(标识符类型)
    }}
    名称空间分析器{
    常量impl::标识符类型和标识符();
    }
    
  • 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);    
    }
    
    #包括
    #包括“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 
    )