51

in this article about boost spirit semantic actions it is mentioned that

There are actually 2 more arguments being passed: the parser context and a reference to a boolean ‘hit’ parameter. The parser context is meaningful only if the semantic action is attached somewhere to the right hand side of a rule. We will see more information about this shortly. The boolean value can be set to false inside the semantic action invalidates the match in retrospective, making the parser fail.

All fine, but i've been trying to find an example passing a function object as semantic action that uses the other parameters (parser context and hit boolean) but i haven't found any. I would love to see an example using regular functions or function objects, as i barely can grok the phoenix voodoo

4

1 回答 1

65

这是一个非常好的问题(也是一罐蠕虫),因为它进入了气和凤凰的界面。我也没有看到一个例子,所以我会在这个方向上稍微扩展一下这篇文章。

正如您所说,语义操作的函数最多可以使用三个参数

  1. 匹配的属性 - 文章中介绍
  2. 上下文 - 包含 qi-phoenix 接口
  3. 匹配标志 - 操纵匹配状态

比赛标志

正如文章所述,除非表达式是规则的一部分,否则第二个参数没有意义,所以让我们从第三个开始。尽管如此,仍然需要第二个参数的占位符并用于此用途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<>() 访问结构模板的attributeorlocals成员。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 语义动作对于直接从输入构建复杂对象非常有帮助。这是可能的,因为您可以在语义操作中调用构造函数和成员函数。

于 2010-06-18T07:34:49.260 回答