提升精神语义动作参数

Posted

技术标签:

【中文标题】提升精神语义动作参数【英文标题】:boost spirit semantic action parameters 【发布时间】:2011-03-05 05:56:23 【问题描述】:

在这个article about boost spirit semantic actions中提到了

实际上还有 2 个参数 被传递:解析器上下文和 对布尔“命中”的引用 范围。解析器上下文是 仅当语义动作有意义 附在右侧某处 手边的规则。我们会看到更多 很快有关这方面的信息。这 布尔值可以设置为 false 语义动作内部无效 回顾比赛,使 解析器失败。

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

【问题讨论】:

【参考方案1】:

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

如您所说,semantic actions 的函数最多可以使用三个参数

    匹配的属性 - 文章中介绍 上下文 - 包含 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 将导致匹配以正常方式失败。如果定义了替代方案,解析器将回溯并尝试按照预期匹配它们。然而,在 boostboost/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() 访问 context 结构模板的 attributelocals 成员。例如,对于上下文变量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;

要修改文章示例以使用第二个参数,请更改函数定义和短语解析调用:

...
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_;

恕我直言,与简短易读的凤凰表达式相比,为每个动作编写一个函数会相当乏味。我很同情巫毒教的观点,但是一旦你使用了 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 语义动作对于直接从输入构建复杂对象非常有帮助。这是可能的,因为您可以在语义操作中调用构造函数和成员函数。

【讨论】:

感谢您的出色解释。你介意我“偷”这个以便在精神网站上重新发布(当然给予应有的学分)吗?您提到的回溯问题是 Spirit 中的错误。第一个替代方案失败后的正确行为应该是第二个替代方案在输入中与第一个替代方案相同的点重新开始。我会看看我能做些什么来解决这个问题。此外,您不应在语义操作中使用 phoenix 占位符。请始终使用相应的 Spirit 占位符,即 qi::_1。 好的,回溯问题现已修复,下个版本(Boost V1.44)会正常。 @hkaiser 很高兴您喜欢它,如果您愿意,请重复使用它。我打算在邮件列表上询问回溯问题,感谢您的照顾。关于占位符的问题:phoenix::_1qi::_1 都定义为 const phoenix::actor&lt;argument&lt;0&gt; &gt;,这可能会改变吗? 谢谢!我会尽快写一篇包含你的东西的文章。没错,phoenix::_1qi::_1 被定义为 const phoenix::actor&lt;argument&lt;0&gt; &gt;,但它们是基于不同的 argument 类型。 phoenix::_1 基于phoenix::argument&lt;0&gt;,而qi::_1 基于spirit::argument&lt;0&gt;。在你的情况下,它只是巧合地证明是相同的。 @lurscher,您给出的示例是解析器(我假设您的意思是&gt;&gt;,而不是&lt;&lt;),但不是规则。因此它没有上下文(语义动作的上下文 arg 是未使用的类型)。您将其分配给规则:rule4 %= rule1 &gt;&gt; lit("(") &gt;&gt; rule2 &gt;&gt; lit(")") &gt;&gt; lit("-&gt;") &gt;&gt; rule3。然后,rule4 有一个上下文,每个组件规则也有自己的上下文。我将编辑我的答案以显示凤凰等价物。

以上是关于提升精神语义动作参数的主要内容,如果未能解决你的问题,请参考以下文章

提升变体复制语义

效果提升28个点 基于领域预训练和对比学习SimCSE的语义检索

AAAI 2022 | 在图像级弱监督语义分割这项CV难题上,字节跳动做到了性能显著提升...

NLP2005年以来大突破语义角色标记深度模型,准确率提升10%

DevOps: 自动部署语义化版本实操

AAAI 2022 | 在图像级弱监督语义分割这项CV难题上,字节跳动做到了性能显著提升...