分裂后提升精神x3的奇异语义行为

时间:2017-01-20 18:41:39

标签: c++ parsing boost-spirit boost-spirit-x3

在我将语法分成推荐的parser.hppparser_def.hppparser.cpp文件后,我发现了一种奇怪的提升精神x3行为。 我的示例gramar解析了一些简单的枚举:

enum = "enum" > identifier > "{" > identifier % "," > "}

这是我的enum语法。 当我不将枚举和标识符解析器拆分为推荐文件时,一切正常,特别是字符串"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

  1. 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)
    
  2. 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;
    
    }
    
  3. 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;
    }
    
    }
    
  4. 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)
    }}
    
  5. 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();
    }
    
  6. 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;
    }
    
    }
    
  7. 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)
    }}
    
  8. 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();
    }
    
  9. 的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);    
    }
    
  10. 这是一个错误,我错过了什么,或者这是预期的行为?

    编辑:here是我的回购,其中包含一个std::logic_error代替expectation_failure

    的示例

2 个答案:

答案 0 :(得分:4)

我找到了这个错误的原因。

这个错误是因为expect指令通过值对它进行解析,这是在parser::impl::identifier初始化程序运行之前。

  

要进行可视化,请想象parser::impl::enum_parser之前运行的parser::impl::identifier的静态初始值设定项。这对编译器来说是有效的。

因此,副本有一个未初始化的name字段,只要期望点尝试使用x3::expectation_failure成员构造which_,该字段就会失败,因为构建{{1}来自std::string的内容是非法的。

总而言之,我担心这里的根本原因是Static Initialization Order Fiasco。我会看看我是否可以修复它并提交PR。

替代方法:

立即解决方法是反向列出源文件的顺序,以便在定义之后使用:

nullptr

请注意,如果这个修复了你的编译器(它在我的编译器上),它是实现定义的。该标准没有指定编译单元之间静态初始化的顺序。

答案 1 :(得分:1)

这是一种对我有效的解决方案,而上述工作流程不起作用。

假设您有文件a.cppa.ha_def.hppb.cppb.hb_def.hpp,... Boost.Spirit X3文档。

基本思想是将*.cpp*_def.hpp文件合并为每个组1个文件。 *.h文件可以并且应该保留。

  1. ls *_def.hpp > parser_def.hpp(假设parser_def.hpp不存在)并以正确的顺序编辑parser_def.hpp#include文件。删除多余的行(添加标题保护等)。目标是parser_def.hpp以正确的顺序包含其他文件。
  2. cat *.cpp > parser.cpp并编辑parser.cpp以使其在语法上正确,然后在顶部用单个#include <*_def.hpp>替换所有#include <parser_def.hpp>行。在构建文件(例如make或cmake)中,将*.cpp文件的编译替换为单个parser.cpp
  3. 您可以摆脱旧的*.cpp文件。

您将失去单独编译文件的便利,但是这将避免“静态初始化顺序”惨败。