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

标签 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;
}

这个例子应该解析带有前导 '!' 的标签(标识符),并以相同的格式输出它们,但在标签前面加上“_tag_”(所以“!abc”变成“!_tag_abc”) .这只是展示我的问题的一个最小示例。

我不明白的是,当我使用 (1) 中的自动规则运行这段代码时会发生什么。我得到的不是预期的输出,而是“_tag_!abc”,实际上 convertTag() 中的 printf() 实际上为标签打印了“!abc”。但这是为什么呢?我正在将 _1 传递给 convertTag(),我认为它应该是 as_string[+char_] 解析的属性,所以怎么会它包括 '!'以完全不同的规则解析?

当我改用规则 (2) 时(我认为它等同于 (1)),我得到的是“_tag_abc”,它似乎去掉了开头的“!”,但为什么呢?

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

从 (2) 看来,在 tag 规则中覆盖 _val 实际上不仅覆盖了 tag 的整个合成属性,而且覆盖了 expr 。在tag中设置_val不只会影响tag的合成属性吗?为什么会有一个'!'在 (1) 中我的 _1 中?

//编辑:

糟糕。我刚刚意识到 (2) 和 (3) 可能完全没有意义,因为它将 ph::bind()(而不是 convertTag() 本身)的返回值分配给 _val,这可能确实如此不做我想做的事(或者做吗?)。尽管如此,问题仍然是为什么 (1) 没有按照我想要的方式工作。

最佳答案

属性通过引用绑定(bind)。由于 expr 只有一个属性,因此同一属性必须同时绑定(bind)到 char_('!')标记。这是事实,并解释了所有问题。

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

因此,当您在语义操作中获取属性时,您实际上获取了绑定(bind)引用的值,它直接来自 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

关于c++ - 关于 Boost::Spirit 自动规则行为的困惑,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42010611/

相关文章:

c++ - Boost Spirit QI 与弦乐和跳绳有关的问题

c++ - Boost Spirit 占位符类型转换

c++ - PostgreSQL 内存泄漏

c++ - 帮助学习 3d 相机类(class)

c++ - 在 extern "C" block 中定义一个继承另一个结构的结构是否可以?

c++ - Boost.TTI 无法与 Clang 配合使用

c++ - 如何使用新的 Visual Studio 2013 预览版构建 boost ?

c++ - 如何在 Android NDK 和 STLport 中使用 boost 库(包括 shared_ptr)

C++ - 必须在头文件中定义友元函数吗?

c++ - 求一个parser grammar,用boost spirit qi比较好