对 Boost::Spirit 自动规则行为的困惑
Confusion about Boost::Spirit auto-rule behavior
我刚刚开始使用 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() 本身)的 return 值分配给 _val
,这可能不会做我想做的事(或者是吗?)。尽管如此,问题仍然是为什么 (1) 没有按照我想要的方式工作。
属性按引用绑定。由于 expr
只有一个属性,因此同一属性 必须 绑定到 char_('!')
和 tag
。这是事实,并解释了所有问题。
Spirit 同意这一点的原因是因为自动属性转换和兼容性规则允许公开 T
(容器)的解析器序列传播到单个 container-of-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
其他备注:
规则 3 执行您想要的操作,因为存在语义操作加上缺少运算符 %= 赋值会禁用自动属性传播。结果是您得到了一个不同的(临时)字符串,并且行为符合您的直觉预期。
关于 "whoops" 我实际上并不确定。我认为 phx::bind
与 std::bind
的工作方式不同(或者结果再次是 "magic" 兼容性规则)。无论如何,我倾向于使用 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;
}
我刚刚开始使用 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() 本身)的 return 值分配给 _val
,这可能不会做我想做的事(或者是吗?)。尽管如此,问题仍然是为什么 (1) 没有按照我想要的方式工作。
属性按引用绑定。由于 expr
只有一个属性,因此同一属性 必须 绑定到 char_('!')
和 tag
。这是事实,并解释了所有问题。
Spirit 同意这一点的原因是因为自动属性转换和兼容性规则允许公开 T
(容器)的解析器序列传播到单个 container-of-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
其他备注:
规则 3 执行您想要的操作,因为存在语义操作加上缺少运算符 %= 赋值会禁用自动属性传播。结果是您得到了一个不同的(临时)字符串,并且行为符合您的直觉预期。
关于 "whoops" 我实际上并不确定。我认为 phx::bind
与 std::bind
的工作方式不同(或者结果再次是 "magic" 兼容性规则)。无论如何,我倾向于使用 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;
}