尽管如此,我一直在尝试找到一个使用其他参数(解析器上下文和hit布尔值)的函数对象作为语义动作的例子,但我没有找到任何例子。 我想看到使用常规函数或者函数对象的例子,因为我对Phoenix不太理解。
这是一个非常好的问题(也是个棘手的问题),因为它涉及到qi和phoenix之间的接口。我还没有看到过例子,所以我会在这个方向上扩展一下文章。
正如你所说,语义动作的函数可以有三个参数:
匹配标志
正如文章所述,除非表达式是规则的一部分,否则第二个参数是没有意义的,所以让我们从第三个开始。虽然第二个参数的占位符仍然需要而且使用 boost::fusion::unused_type
。所以,一个修改过的函数来使用第三个参数是:
#include <boost/spirit/include/qi.hpp>
#include <string>
#include <iostream>
void f(int attribute, const boost::fusion::unused_type& it, bool& mFlag){
//output parameters
std::cout << "matched integer: '" << attribute << "'" << std::endl
<< "match flag: " << mFlag << std::endl;
//fiddle with match flag
mFlag = false;
}
namespace qi = boost::spirit::qi;
int main(void){
std::string input("1234 6543");
std::string::const_iterator begin = input.begin(), end = input.end();
bool returnVal = qi::phrase_parse(begin, end, qi::int_[f], qi::space);
std::cout << "return: " << returnVal << std::endl;
return 0;
}
输出结果如下:
匹配到的整数: '1234' 匹配标志: 1 返回值: 0
这个例子所做的就是将匹配转换为不匹配,这反映在解析器输出中。根据hkaiser的说法,在boost 1.44及以上版本中将匹配标志设置为false将导致正常方式下匹配失败。如果定义了备选项,解析器将回溯并尝试像预期的那样将它们匹配。然而,在boost<=1.43中,Spirit bug阻止了回溯,这会导致奇怪的行为。要查看此内容,请添加phoenix include boost/spirit/include/phoenix.hpp
并将表达式更改为:
qi::int_[f] | qi::digit[std::cout << qi::_1 << "\n"]
matched integer: '1234' match flag: 1 6 return: 1其中的
6
是输入中第二个整数的第一个数字,这表明备选项使用了skipper且没有回溯。请注意,基于备选项,匹配被认为是成功的。_pass
占位符在phoenix表达式中操作匹配标志。
上下文参数
更有趣的参数是第二个参数,它包含了qi-phoenix接口,或者在qi术语中,语义动作的上下文。为了说明这一点,首先看一条规则:rule<Iterator, Attribute(Arg1,Arg2,...), qi::locals<Loc1,Loc2,...>, Skipper>
上下文参数包含Attribute、Arg1...ArgN和qi::locals模板参数,这些参数被包装在boost::spirit::context模板类型中。与函数参数不同,函数参数属性是解析后的值,而此属性是规则本身的值。语义动作必须将前者映射到后者。以下是可能的上下文类型示例(使用Phoenix表达式等效方式):
using namespace boost;
spirit::context< //context template
fusion::cons<
int&, //return int attribute (phoenix: _val)
fusion::cons<
char&, //char argument1 (phoenix: _r1)
fusion::cons<
float&, //float argument2 (phoenix: _r2)
fusion::nil //end of cons list
>,
>,
>,
fusion::vector2< //locals container
char, //char local (phoenix: _a)
unsigned int //unsigned int local (phoenix: _b)
>
>
//assign return attribute
fusion::at_c<0>(con.attributes) = 1;
//get the second rule argument
float arg2 = fusion::at_c<2>(con.attributes);
//assign the first local
fusion::at_c<1>(con.locals) = 42;
要修改文章示例以使用第二个参数,请更改函数定义和phrase_parse调用:
...
typedef
boost::spirit::context<
boost::fusion::cons<int&, boost::fusion::nil>,
boost::fusion::vector0<>
> f_context;
void f(int attribute, const f_context& con, bool& mFlag){
std::cout << "matched integer: '" << attribute << "'" << std::endl
<< "match flag: " << mFlag << std::endl;
//assign output attribute from parsed value
boost::fusion::at_c<0>(con.attributes) = attribute;
}
...
int matchedInt;
qi::rule<std::string::const_iterator,int(void),ascii::space_type>
intRule = qi::int_[f];
qi::phrase_parse(begin, end, intRule, ascii::space, matchedInt);
std::cout << "matched: " << matchedInt << std::endl;
....
这是一个非常简单的例子,只是将解析后的值映射到输出属性值,但扩展应该相当明显。只需使上下文结构体模板参数与规则输出、输入和本地类型匹配即可。请注意,使用自动规则时,可以使用%=
而不是=
来定义规则,从而实现解析类型/值与输出类型/值之间的直接匹配:
qi::rule<std::string::const_iterator,int(void),ascii::space_type>
intRule %= qi::int_;
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <string>
#include <iostream>
namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;
int main(void){
std::string input("1234, 6543, 42");
std::string::const_iterator begin = input.begin(), end = input.end();
qi::rule<
std::string::const_iterator,
int(int), //output (_val) and input (_r1)
qi::locals<int>, //local int (_a)
ascii::space_type
>
intRule =
qi::int_[qi::_a = qi::_1] //local = x1
>> ","
>> qi::int_[qi::_a += qi::_1] //local = x1 + x2
>> ","
>> qi::int_
[
qi::_val = qi::_a*qi::_1 + qi::_r1 //output = local*x3 + x0
];
int ruleValue, x0 = 10;
qi::phrase_parse(begin, end, intRule(x0), ascii::space, ruleValue);
std::cout << "rule value: " << ruleValue << std::endl;
return 0;
}
另一种方法是将所有整数解析为向量,然后使用单个语义动作(下面的 %
是列表运算符,并使用 phoenix::at 访问向量的元素)来评估函数:
namespace ph = boost::phoenix;
...
qi::rule<
std::string::const_iterator,
int(int),
ascii::space_type
>
intRule =
(qi::int_ % ",")
[
qi::_val = (ph::at(qi::_1,0) + ph::at(qi::_1,1))
* ph::at(qi::_1,2) + qi::_r1
];
....
_1
、_2
和_3
来分别引用第一个、第二个和第三个匹配值:(qi::int_ >> "," >> qi::int_ >> "," >> qi::int_)
[
qi::_val = (qi::_1 + qi::_2) * qi::_3 + qi::_r1
];
这只是一个虚构的例子,但应该能让你理解。我发现Phoenix语义动作在直接从输入构造复杂对象方面非常有帮助;这是可能的,因为在语义动作中可以调用构造函数和成员函数。
phoenix::_1
和qi::_1
都被定义为const phoenix::actor<argument<0>>
,这会改变吗? - academicRobotphoenix::_1
和qi::_1
都被定义为constphoenix::actor<argument<0>>
,但它们基于不同的argument<>类型。phoenix::_1
基于phoenix::argument<0>
,而qi::_1
基于spirit::argument<0>
。在你的情况下,这只是巧合而已。 - hkaiser>>
而不是<<
),但它不是一个规则。因此它没有上下文(语义操作的上下文参数是未使用的类型)。你将它分配给一个规则:rule4 %= rule1 >> lit("(") >> rule2 >> lit(")") >> lit("->") >> rule3
。然后,rule4 有一个上下文,每个组成规则都有自己的上下文。我会编辑我的答案以展示 phoenix 的等效方法。 - academicRobot