提升精神语义动作参数

lur*_*her 52 c++ parsing boost-spirit boost-spirit-qi semantics

这篇关于提升精神语义行为的文章中提到了这一点

实际上还有2个参数被传递:解析器上下文和对布尔"命中"参数的引用.仅当语义操作附加到规则右侧的某个位置时,解析器上下文才有意义.我们很快就会看到更多相关信息.在语义操作中,布尔值可以设置为false,从而使回溯中的匹配失效,从而使解析器失败.

一切都很好,但我一直试图找到一个示例传递函数对象作为语义动作使用其他参数(解析器上下文和命中布尔)但我还没有找到任何.我很想看到一个使用常规函数或函数对象的例子,因为我几乎无法理解凤凰伏都教

aca*_*bot 66

这是一个非常好的问题(也是一种蠕虫),因为它在qi和phoenix的界面上.我也没有看过一个例子,所以我会在这方面稍微扩展一下这篇文章.

如你所说,语义动作的函数最多可以包含三个参数

  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;
}
Run Code Online (Sandbox Code Playgroud)

哪个输出:

matched integer: '1234'
match flag: 1
return: 0

所有这个例子都是将匹配切换为不匹配,这反映在解析器输出中.根据hkaiser,在boost 1.44及更高版本中将match标志设置为false将导致匹配以正常方式失败.如果定义了替代方案,解析器将回溯并尝试按照预期匹配它们.但是,在boost <= 1.43时,Spirit bug会阻止回溯,这会导致奇怪的行为.要查看此内容,请添加phoenix include boost/spirit/include/phoenix.hpp并将表达式更改为

qi::int_[f] | qi::digit[std::cout << qi::_1 << "\n"]
Run Code Online (Sandbox Code Playgroud)

你期望,当qi :: int解析器失败时,替代的qi :: digit匹配输入的开头为"1",但输出为:

matched integer: '1234'
match flag: 1
6
return: 1

6是输入中第二个int的第一个数字,表示使用船长并且没有回溯的替代方案.另请注意,根据替代方案,匹配被认为是成功的.

一旦提升1.44,则匹配标志将用于应用可能在解析器序列中难以表达的匹配标准.请注意,可以使用_pass占位符在phoenix表达式中操作匹配标志.

上下文参数

更有趣的参数是第二个,它包含qi-phoenix接口,或者在qi用语中,包含语义动作的上下文.为了说明这一点,首先要检查一条规则:

rule<Iterator, Attribute(Arg1,Arg2,...), qi::locals<Loc1,Loc2,...>, Skipper>
Run Code Online (Sandbox Code Playgroud)

context参数包含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)
    > 
>
Run Code Online (Sandbox Code Playgroud)

请注意,返回属性和参数列表采用lisp样式列表(缺点列表)的形式.要在函数中访问这些变量,请使用fusion :: at <>()访问struct模板attribute或其locals成员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;
Run Code Online (Sandbox Code Playgroud)

要修改文章示例以使用第二个参数,请更改函数定义和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;
....
Run Code Online (Sandbox Code Playgroud)

这是一个非常简单的示例,它只是将解析后的值映射到输出属性值,但扩展应该相当明显.只需使上下文结构模板参数与规则输出,输入和本地类型匹配即可.请注意,这种类型的解析类型/值与输出类型/值之间的直接匹配可以使用自动规则自动完成,%=而不是=在定义规则时:

qi::rule<std::string::const_iterator,int(void),ascii::space_type> 
    intRule %= qi::int_;
Run Code Online (Sandbox Code Playgroud)

恕我直言,与简短易读的凤凰表达等价物相比,为每个动作编写函数将相当繁琐.我同情伏都教的观点,但是一旦你和凤凰一起工作了一段时间,语义和语法并不是非常困难.

编辑:访问凤凰城的规则上下文

仅当解析器是规则的一部分时才定义上下文变量.将解析器视为任何消耗输入的表达式,其中规则将解析器值(qi :: _ 1)转换为规则值(qi :: _ val).差异通常是非平凡的,例如当qi :: val具有需要从POD解析值构造的Class类型时.下面是一个简单的例子.

假设我们输入的一部分是三个CSV整数(x1, x2, x3)的序列,我们只关注这三个整数的算术函数(f = x0 +(x1 + x2)*x3),其中x0是在别处获得的值.一种选择是读入整数并计算函数,或者使用凤凰来做两者.

对于此示例,使用一个带有输出属性(函数值)的规则,输入(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;
}
Run Code Online (Sandbox Code Playgroud)

或者,可以将所有int解析为向量,并使用单个语义操作评估函数(%下面是列表运算符,使用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
        ];
....
Run Code Online (Sandbox Code Playgroud)

对于上面的内容,如果输入不正确(两个整数而不是三个),运行时可能会发生不好的事情,因此最好明确指定解析值的数量,因此解析将因输入错误而失败.以下使用_1,, _2_3引用第一,第二和第三个匹配值:

(qi::int_ >> "," >> qi::int_ >> "," >> qi::int_)
[
    qi::_val = (qi::_1 + qi::_2) * qi::_3 + qi::_r1
];
Run Code Online (Sandbox Code Playgroud)

这是一个人为的例子,但应该给你一个想法.我发现凤凰语义动作对于直接从输入构造复杂对象非常有帮助; 这是可能的,因为您可以在语义操作中调用构造函数和成员函数.

  • @lurscher,你给出的例子是一个解析器(我假设你的意思是`>>`,而不是`<<`),但不是规则.因此它没有上下文(语义动作的上下文arg是未使用的类型).您将其分配给规则:`rule4%= rule1 >> lit("(")>> rule2 >> lit(")")>> lit(" - >")>> rule3`.然后,rule4有一个上下文,每个组件规则也有自己的上下文.我将编辑我的答案以显示凤凰等效物. (4认同)
  • 谢谢你的出色解释.你介意我"窃取"这个,以便在精神网站上重新发布它(当然给予应有的信用)?你提到的回溯问题是Spirit中的一个错误.在第一个替代方案失败后的正确行为应该是第二个替代方案在输入中的第一个替代方案的同一点开始.我会看到我能做些什么来解决这个问题.此外,您不应该在语义操作中使用凤凰占位符.请始终使用相应的Spirit占位符,即qi :: _ 1. (3认同)