这是一个非常好的问题(也是一罐蠕虫),因为它进入了气和凤凰的界面。我也没有看到一个例子,所以我会在这个方向上稍微扩展一下这篇文章。
正如您所说,语义操作的函数最多可以使用三个参数
- 匹配的属性 - 文章中介绍
- 上下文 - 包含 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 错误会阻止回溯,这会导致奇怪的行为。要看到这一点,请添加 phoenix includeboost/spirit/include/phoenix.hpp
并将表达式更改为
qi::int_[f] | qi::digit[std::cout << qi::_1 << "\n"]
您会期望,当 qi::int 解析器失败时,替代 qi::digit 将匹配输入开头的“1”,但输出为:
匹配整数:'1234'
比赛标志:1
6
回报:1
这6
是输入中第二个 int 的第一个数字,表示使用船长采用替代方案并且没有回溯。另请注意,根据备选方案,匹配被认为是成功的。
一旦 boost 1.44 发布,匹配标志将用于应用可能难以在解析器序列中表达的匹配标准。_pass
请注意,可以使用占位符在 phoenix 表达式中操作匹配标志。
上下文参数
更有趣的参数是第二个参数,它包含 qi-phoenix 接口,或者用 qi 的说法,语义动作的上下文。为了说明这一点,首先检查一个规则:
rule<Iterator, Attribute(Arg1,Arg2,...), qi::locals<Loc1,Loc2,...>, Skipper>
context 参数体现了 Attribute、Arg1、...ArgN 和 qi::locals 模板参数,包装在 boost::spirit::context 模板类型中。该属性与函数参数不同:函数参数属性是解析后的值,而该属性是规则本身的值。语义动作必须将前者映射到后者。这是一个可能的上下文类型的示例(指示了凤凰表达式等效项):
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)
>
>
请注意,返回属性和参数列表采用 lisp 样式列表(cons list)的形式。要在函数中访问这些变量,请使用 fusion::at<>() 访问结构模板的attribute
orlocals
成员。context
例如,对于上下文变量con
//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;
....
这是一个非常简单的示例,它只是将解析的值映射到输出属性值,但扩展应该相当明显。只需使上下文结构模板参数匹配规则输出、输入和本地类型。请注意,解析类型/值与输出类型/值之间的这种直接匹配可以使用自动规则自动完成,在定义规则时使用 a%=
而不是 a :=
qi::rule<std::string::const_iterator,int(void),ascii::space_type>
intRule %= qi::int_;
恕我直言,与简短易读的凤凰表达等价物相比,为每个动作编写一个函数会相当乏味。我很同情巫毒教的观点,但是一旦你使用了 phoenix 一段时间,语义和语法就不是很困难了。
编辑:使用 Phoenix 访问规则上下文
仅当解析器是规则的一部分时才定义上下文变量。将解析器视为使用输入的任何表达式,其中规则将解析器值 (qi::_1) 转换为规则值 (qi::_val)。差异通常很重要,例如当 qi::val 具有需要从 POD 解析值构造的 Class 类型时。下面是一个简单的例子。
假设我们的输入的一部分是三个 CSV 整数 ( x1, x2, x3
) 的序列,我们只关心这三个整数的算术函数 (f = x0 + (x1+x2)*x3 ),其中 x0 是在别处获得的值。一种选择是读入整数并计算函数,或者使用 phoenix 来完成这两项工作。
对于此示例,使用具有输出属性(函数值)、输入 (x0) 和本地(使用规则在各个解析器之间传递信息)的规则。这是完整的示例。
#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 语义动作对于直接从输入构建复杂对象非常有帮助。这是可能的,因为您可以在语义操作中调用构造函数和成员函数。