关于Boost :: Spirit自动规则行为的困惑

时间:2017-02-02 19:21:13

标签: c++ boost boost-spirit boost-spirit-qi

我刚刚开始使用Boost :: Spirit,并且无法理解以下代码中发生的事情:

#include <cstdio>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>

namespace ph = boost::phoenix;
namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;

template <typename Iterator>
struct TestGrammar : qi::grammar<Iterator, std::string(), ascii::space_type>
{
    qi::rule<Iterator, std::string(), ascii::space_type> expr;
    qi::rule<Iterator, std::string(), ascii::space_type> tag;

    std::string convertTag(std::string& tag)
    {
        printf("Tag: %s\n", tag.c_str());

        tag = "_tag_" + tag;
        return tag;
    }

    TestGrammar()
            : TestGrammar::base_type(expr)
    {
        using qi::_1;
        using qi::as_string;

        using ascii::char_;

        using namespace qi::labels;

        // (1)
        tag %= as_string[+char_] [ ph::bind(&TestGrammar::convertTag, this, _1) ];

        // (2)
        //tag = as_string[+char_] [ _val = ph::bind(&TestGrammar::convertTag, this, _1) ];

        // (3)
        //tag = as_string[+char_] [ _val += ph::bind(&TestGrammar::convertTag, this, _1) ];

        expr = char_('!') >> tag;
    }
};

int main(int argc, char** argv)
{
    using ascii::space;

    std::string str("!abc");

    std::string::const_iterator beg = str.begin();
    std::string::const_iterator end = str.end();

    TestGrammar<std::string::const_iterator> expr;

    std::string res;
    bool r = phrase_parse(beg, end, expr, space, res);

    if (r  &&  beg == end) {
        printf("Matched: %s\n", res.c_str());
    } else {
        printf("Didn't match!\n");
    }

    return 0;
}

此示例应该使用前导&#39;来解析标记(标识符),并以相同的格式将它们吐出,但使用&#34; _tag _&#34;预先贴在标签上(所以&#34;!abc&#34;变成&#34;!_ tag_abc&#34;)。这只是展示我问题的最小例子。

我不明白当我使用(1)中的自动规则运行此代码时会发生什么。而不是预期的输出,我得到&#34; _tag_!abc&#34;,实际上printf()中的convertTag()实际打印&#34;!abc&#34;对于标签。但那是为什么呢?我将_1传递给convertTag(),我认为该as_string[+char_]应该是tag解析的属性,所以为什么它包含了&#39;!&#39;解析完全不同的规则?

当我使用规则(2)代替(我认为它相当于(1))时,我反而得到了#34; _tag_abc&#34;,这似乎已经放弃了最初的&#39;!&# 39;,但为什么?

规则(3)做我想要的,虽然我不知道为什么。

从(2)我觉得在tag规则中覆盖_val实际上会覆盖expr的整个合成属性,还会覆盖_val的整个合成属性。在tag中设置tag只会影响_1的合成属性吗?为什么到底有一个&#39;!&#39;在我的_val中(1)?

//编辑:

糟糕。我只是意识到(2)和(3)可能完全没有意义,因为它将ph :: bind()(不是convertTag()本身)的返回值赋给{{1}},这可能不会做什么我想要(或者是吗?)。不过,问题仍然是为什么(1)没有按照我想要的方式工作。

1 个答案:

答案 0 :(得分:1)

属性受引用约束。由于expr只展示了一个属性,因此同一属性必须绑定到char_('!')tag。这是事实,并解释了所有问题。

Spirit之所以如此,是因为自动属性转换和兼容性规则允许公开({容器} T的解析器序列传播到单个容器中的T属性。你可以这样做,例如解析qi::alpha >> +(qi::alnum | qi::char_('_'))

因此,当您在语义操作中使用该属性时,实际获取绑定引用的值,该值直接来自main std::string res;。添加

    std::cout << "Address: " << &tag << "\n";

std::cout << "Address: " << &res << "\n";

显示它们是相同的:

Address: 0x7fffd54e5d00
Tag: !abc
Address: 0x7fffd54e5d00
Matched: _tag_!abc

查看 Live On Coliru

其他评论:

规则3可以满足您的需求,因为语义操作的存在加上运算符%=赋值的无效会禁用自动属性传播。结果是你获得了一个不同的(临时)字符串,这种行为就是你直觉所期望的。

关于“哎呀”我真的不确定。我认为phx::bindstd::bind的工作方式不同(或者结果再次是“魔术”兼容性规则)。无论如何,我倾向于使用boost::phoenix::function

来避免任何混淆
struct convertTag_f {
    std::string operator()(std::string const& tag) const {
        return "_tag_" + tag;
    }
};
boost::phoenix::function<convertTag_f> convertTag;

TestGrammar() : TestGrammar::base_type(expr)
{
    using namespace qi::labels;

    tag  = qi::as_string[+ascii::char_] [ _val += convertTag(_1) ];
    expr = ascii::char_('!') >> tag;
}

查看 Live On Coliru