Boost Spirit模板专精失败

Boost Spirit template specialization failure

下面是我尝试使用 boost::spirit::qi.
编写的非常紧凑的语法版本 环境:VS2013,x86,Boost1.64
当#include 头文件时,编译器抱怨行

rBlock = "{" >> +(rInvocation) >> "}";

日志很长(我只复制了开头和结尾):

more than one partial specialization matches the template argument list ...
...
see reference to function template instantiation 'boost::spirit::qi::rule &boost::spirit::qi::rule::operator =>(const Expr &)' being compiled

我的错误在哪里?

头文件:

//mygrammar.h
#pragma once 
#include <boost/spirit/include/qi.hpp>

namespace myNS
{

    typedef std::string Identifier;
    typedef ::boost::spirit::qi::rule <const char*, Identifier()> myIdentifierRule;

    typedef ::boost::variant<char, int> Expression;
    typedef ::boost::spirit::qi::rule <const char*, Expression()> myExpressionRule;

    struct IdntifierEqArgument
    {
        Identifier ident;
        Expression arg;
    };

    typedef ::boost::variant < IdntifierEqArgument, Expression >  Argument;
    typedef ::boost::spirit::qi::rule <const char*, Argument()> myArgumentRule;

    typedef ::std::vector<Argument> ArgumentList;
    typedef ::boost::spirit::qi::rule <const char*, myNS::ArgumentList()> myArgumentListRule;


    struct Invocation
    {
        Identifier identifier;
        ::boost::optional<ArgumentList> args;
    };
    typedef ::boost::spirit::qi::rule <const char*, Invocation()> myInvocationRule;


    typedef ::std::vector<Invocation> Block;
    typedef ::boost::spirit::qi::rule <const char*, myNS::Block()> myBlockRule;

}
BOOST_FUSION_ADAPT_STRUCT(
    myNS::IdntifierEqArgument,
    (auto, ident)
    (auto, arg)
    );
BOOST_FUSION_ADAPT_STRUCT(
    myNS::Invocation,
    (auto, identifier)
    (auto, args)
    );

namespace myNS
{
    struct myRules
    {
        myIdentifierRule rIdentifier;
        myExpressionRule rExpression;
        myArgumentRule rArgument;
        myArgumentListRule rArgumentList;
        myInvocationRule rInvocation;
        myBlockRule rBlock;

        myRules()
        {
            using namespace ::boost::spirit;
            using namespace ::boost::spirit::qi;

            rIdentifier = as_string[((qi::alpha | '_') >> *(qi::alnum | '_'))]; 
            rExpression = char_ | int_;
            rArgument = (rIdentifier >> "=" >> rExpression) | rExpression;
            rArgumentList = rArgument >> *("," >> rArgument);
            rInvocation = rIdentifier >> "(" >> -rArgumentList >> ")";
            rBlock = "{" >> +(rInvocation) >> "}";
        }
    };
}

我不太确定问题是在哪里触发的,但这显然是属性转发规则中太多歧义的症状。

Conceptually this could be triggered by your attribute types having similar/compatible layouts. In language theory, you're looking at a mismatch between C++'s nominative type system versus the approximation of structural typing in the attribute propagation system. But enough theorism :)

我认为 attr_cast<> 不会在这里拯救你,因为它可能在幕后使用相同的机制和启发式方法。

让我注意到 ArgumentList 可选是......不是很有用(因为空列表已经准确地反映了参数的缺失)。

所以我尝试简化规则:

rArgumentList = -(rArgument % ',');
rInvocation   = rIdentifier >> '(' >> rArgumentList >> ')';

并且声明的属性类型可以简单地ArgumentList而不是boost::optional::ArgumentList

事实证明,这消除了传播到 vector<Invocation> 时的歧义,所以......你得救了。

If this feels "accidental" to you, you should! What would I do if this hadn't removed the ambiguity "by chance"? I'd have created a semantic action to propagate the Invocation by simpler mechanics. There's a good chance that fusion::push_back(_val, _1) or similar would have worked.

See also Boost Spirit: "Semantic actions are evil"?

审查和演示

在清理过的评论中,我展示了一些 fixes/improvements 和一个测试 运行 来转储已解析的 AST。

  1. 将 AST 与解析器分开(您不想在 AST 类型中使用 qi。您特别不想在通用模板库中使用 using namespace 指令)
  2. 不要在 adapt 宏中使用 auto。那不是一个功能。相反,由于您表面上可以使用 C++11,因此请使用基于 C++11 (decltype) 的宏

    BOOST_FUSION_ADAPT_STRUCT(myAST::IdntifierEqArgument, ident,arg);
    BOOST_FUSION_ADAPT_STRUCT(myAST::Invocation, identifier,args);
    
  3. AST 领先(另外,为了清晰起见,更喜欢 c++11):

    namespace myAST {
        using Identifier = std::string;
        using Expression = boost::variant<char, int>;
    
        struct IdntifierEqArgument {
            Identifier ident;
            Expression arg;
        };
    
        using Argument = boost::variant<IdntifierEqArgument, Expression>;
    
        using ArgumentList = std::vector<Argument>;
    
        struct Invocation {
            Identifier identifier;
            ArgumentList args;
        };
    
        using Block = std::vector<Invocation>;
    }
    

    分开定义真好

  4. 关于解析器,

    • 我更喜欢 qi::grammar 约定。另外,
    • 您没有与船长一起声明任何规则。我 "guessed" 从上下文来看,空格在 ExpressionIdentifier.
    • 的规则之外是微不足道的
    • 表情每char_吃一次,所以也会吃')'甚至'3'。我只在测试时和调试后才注意到这一点:

      //#define BOOST_SPIRIT_DEBUG
      BOOST_SPIRIT_DEBUG_NODES((start)(rBlock)(rInvocation)(rIdentifier)(rArgumentList)(rArgument)(rExpression))
      

      我强烈推荐使用这些设施

  5. 总而言之,解析器归结为

    namespace myNS {
        namespace qi = boost::spirit::qi;
    
        template <typename Iterator = char const*>
        struct myRules : qi::grammar<Iterator, myAST::Block()> {
    
            myRules() : myRules::base_type(start) {
                rIdentifier   = qi::raw [(qi::alpha | '_') >> *(qi::alnum | '_')];
                rExpression   = qi::alpha | qi::int_;
                rArgument     = (rIdentifier >> '=' >> rExpression) | rExpression;
                rArgumentList = -(rArgument % ',');
                rInvocation   = rIdentifier >> '(' >> rArgumentList >> ')';
                rBlock        = '{' >> +rInvocation >> '}';
                start         = qi::skip(qi::space) [ rBlock ];
    
                BOOST_SPIRIT_DEBUG_NODES((start)(rBlock)(rInvocation)(rIdentifier)(rArgumentList)(rArgument)(rExpression))
            }
    
          private:
            qi::rule<Iterator, myAST::Block()> start;
            using Skipper = qi::space_type;
    
            qi::rule<Iterator, myAST::Argument(), Skipper>     rArgument;
            qi::rule<Iterator, myAST::ArgumentList(), Skipper> rArgumentList;
            qi::rule<Iterator, myAST::Invocation(), Skipper>   rInvocation;
            qi::rule<Iterator, myAST::Block(), Skipper>        rBlock;
            // implicit lexemes
            qi::rule<Iterator, myAST::Identifier()>   rIdentifier;
            qi::rule<Iterator, myAST::Expression()>   rExpression;
        };
    }
    
  6. 添加测试驱动程序

    int main() {
        std::string const input = R"(
    {
        foo()
        bar(a, b, 42)
        qux(someThing_awful01 = 9)
    }
    )";
        auto f = input.data(), l = f + input.size();
    
        myAST::Block block;
        bool ok = parse(f, l, myNS::myRules<>{}, block);
    
        if (ok) {
            std::cout << "Parse success\n";
    
            for (auto& invocation : block) {
                std::cout << invocation.identifier << "(";
                for (auto& arg : invocation.args) std::cout << arg << ",";
                std::cout << ")\n";
            }
        }
        else
            std::cout << "Parse failed\n";
    
        if (f!=l)
            std::cout << "Remaining unparsed input: '" << std::string(f,l) << "'\n";
    }
    

完成演示

看到了Live On Coliru

//#define BOOST_SPIRIT_DEBUG
#include <boost/spirit/include/qi.hpp>

namespace myAST {
    using Identifier = std::string;
    using Expression = boost::variant<char, int>;

    struct IdntifierEqArgument {
        Identifier ident;
        Expression arg;
    };

    using Argument = boost::variant<IdntifierEqArgument, Expression>;

    using ArgumentList = std::vector<Argument>;

    struct Invocation {
        Identifier identifier;
        ArgumentList args;
    };

    using Block = std::vector<Invocation>;

    // for debug printing
    static inline std::ostream& operator<<(std::ostream& os, myAST::IdntifierEqArgument const& named) {
        return os << named.ident << "=" << named.arg;
    }
}

BOOST_FUSION_ADAPT_STRUCT(myAST::IdntifierEqArgument, ident,arg);
BOOST_FUSION_ADAPT_STRUCT(myAST::Invocation, identifier,args);

namespace myNS {
    namespace qi = boost::spirit::qi;

    template <typename Iterator = char const*>
    struct myRules : qi::grammar<Iterator, myAST::Block()> {

        myRules() : myRules::base_type(start) {
            rIdentifier   = qi::raw [(qi::alpha | '_') >> *(qi::alnum | '_')];
            rExpression   = qi::alpha | qi::int_;
            rArgument     = (rIdentifier >> '=' >> rExpression) | rExpression;
            rArgumentList = -(rArgument % ',');
            rInvocation   = rIdentifier >> '(' >> rArgumentList >> ')';
            rBlock        = '{' >> +rInvocation >> '}';
            start         = qi::skip(qi::space) [ rBlock ];

            BOOST_SPIRIT_DEBUG_NODES((start)(rBlock)(rInvocation)(rIdentifier)(rArgumentList)(rArgument)(rExpression))
        }

      private:
        qi::rule<Iterator, myAST::Block()> start;
        using Skipper = qi::space_type;

        qi::rule<Iterator, myAST::Argument(), Skipper>     rArgument;
        qi::rule<Iterator, myAST::ArgumentList(), Skipper> rArgumentList;
        qi::rule<Iterator, myAST::Invocation(), Skipper>   rInvocation;
        qi::rule<Iterator, myAST::Block(), Skipper>        rBlock;
        // implicit lexemes
        qi::rule<Iterator, myAST::Identifier()>   rIdentifier;
        qi::rule<Iterator, myAST::Expression()>   rExpression;
    };
}

int main() {
    std::string const input = R"(
{
    foo()
    bar(a, b, 42)
    qux(someThing_awful01 = 9)
}
)";
    auto f = input.data(), l = f + input.size();

    myAST::Block block;
    bool ok = parse(f, l, myNS::myRules<>{}, block);

    if (ok) {
        std::cout << "Parse success\n";

        for (auto& invocation : block) {
            std::cout << invocation.identifier << "(";
            for (auto& arg : invocation.args) std::cout << arg << ",";
            std::cout << ")\n";
        }
    }
    else
        std::cout << "Parse failed\n";

    if (f!=l)
        std::cout << "Remaining unparsed input: '" << std::string(f,l) << "'\n";
}

打印输出

Parse success
foo()
bar(a,b,42,)
qux(someThing_awful01=9,)
Remaining unparsed input: '
'