boostspirit语义动作参数

发布于 2024-09-06 06:30:31 字数 436 浏览 15 评论 0原文

在这篇关于提升精神的文章中语义动作提到

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

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

戏蝶舞 2024-09-13 06:30:31

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

正如你所说, 语义动作最多可以使用三个参数

  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;
}

其输出:

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

此示例所做的所有操作是将匹配切换为不匹配,这反映在解析器输出中。根据 hkaiser 的说法,在 boost 1.44 及更高版本中,将匹配标志设置为 false 将导致匹配以正常方式失败。如果定义了替代项,解析器将回溯并尝试按照预期匹配它们。然而,在 boost<=1.43 中,Spirit 错误会阻止回溯,从而导致奇怪的行为。要看到这一点,请添加 phoenix include boost/spirit/include/phoenix.hpp 并将表达式更改为

qi::int_[f] | qi::digit[std::cout << qi::_1 << "\n"]

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

matched integer: '1234'
match flag: 1
6
return: 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 模板类型中。该属性与函数参数不同:函数参数属性是解析后的值,而该属性是规则本身的值。语义动作必须将前者映射到后者。下面是一个可能的上下文类型的示例(指示了 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)
    > 
>

请注意,返回属性和参数列表采用 lisp 样式列表的形式(a 缺点列表)。要在函数中访问这些变量,请使用 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;

要修改文章示例以使用第二个参数,请更改函数定义和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_;

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

This a really good question (and also a can of worms) because it gets at the interface of qi and phoenix. I haven't seen an example either, so I'll extend the article a little in this direction.

As you say, functions for semantic actions can take up to three parameters

  1. Matched attribute - covered in the article
  2. Context - contains the qi-phoenix interface
  3. Match flag - manipulate the match state

Match flag

As the article states, the second parameter is not meaningful unless the expression is part of a rule, so lets start with the third. A placeholder for the second parameter is still needed though and for this use boost::fusion::unused_type. So a modified function from the article to use the third parameter is:

#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;
}

which outputs:

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

All this example does is switch the match to a non-match, which is reflected in the parser output. According to hkaiser, in boost 1.44 and up setting the match flag to false will cause the match to fail in the normal way. If alternatives are defined, the parser will backtrack and attempt to match them as one would expect. However, in boost<=1.43 a Spirit bug prevents backtracking, which causes strange behavior. To see this, add phoenix include boost/spirit/include/phoenix.hpp and change the expression to

qi::int_[f] | qi::digit[std::cout << qi::_1 << "\n"]

You'd expect that, when the qi::int parser fails, the alternative qi::digit to match the beginning of the input at "1", but the output is:

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

The 6 is the first digit of the second int in the input which indicates the alternative is taken using the skipper and without backtracking. Notice also that the match is considered succesful, based on the alternative.

Once boost 1.44 is out, the match flag will be useful for applying match criteria that might be otherwise difficult to express in a parser sequence. Note that the match flag can be manipulated in phoenix expressions using the _pass placeholder.

Context parameter

The more interesting parameter is the second one, which contains the qi-phoenix interface, or in qi parlance, the context of the semantic action. To illustrate this, first examine a rule:

rule<Iterator, Attribute(Arg1,Arg2,...), qi::locals<Loc1,Loc2,...>, Skipper>

The context parameter embodies the Attribute, Arg1, ... ArgN, and qi::locals template paramters, wrapped in a boost::spirit::context template type. This attribute differs from the function parameter: the function parameter attribute is the parsed value, while this attribute is the value of the rule itself. A semantic action must map the former to the latter. Here's an example of a possible context type (phoenix expression equivalents indicated):

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)
    > 
>

Note the return attribute and argument list take the form of a lisp-style list (a cons list). To access these variables within a function, access the attribute or locals members of the context struct template with fusion::at<>(). For example, for a context variable 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;

To modify the article example to use the second argument, change the function definition and phrase_parse calls:

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

This is a very simple example that just maps the parsed value to the output attribute value, but extensions should be fairly apparent. Just make the context struct template parameters match the rule output, input, and local types. Note that this type of a direct match between parsed type/value to output type/value can be done automatically using auto rules, with a %= instead of a = when defining the rule:

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

IMHO, writing a function for each action would be rather tedious, compared to the brief and readable phoenix expression equivalents. I sympathize with the voodoo viewpoint, but once you work with phoenix for a little while, the semantics and syntax aren't terribly difficult.

Edit: Accessing rule context w/ Phoenix

The context variable is only defined when the parser is part of a rule. Think of a parser as being any expression that consumes input, where a rule translates the parser values (qi::_1) into a rule value (qi::_val). The difference is often non-trivial, for example when qi::val has a Class type that needs to be constructed from POD parsed values. Below is a simple example.

Let's say part of our input is a sequence of three CSV integers (x1, x2, x3), and we only care out an arithmetic function of these three integers (f = x0 + (x1+x2)*x3 ), where x0 is a value obtained elsewhere. One option is to read in the integers and calculate the function, or alternatively use phoenix to do both.

For this example, use one rule with an output attribute (the function value), and input (x0), and a local (to pass information between individual parsers with the rule). Here's the full example.

#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;
}

Alternatively, all the ints could be parsed as a vector, and the function evaluated with a single semantic action (the % below is the list operator and elements of the vector are accessed with 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
        ];
....

For the above, if the input is incorrect (two ints instead of three), bad thing could happen at run time, so it would be better to specify the number of parsed values explicitly, so parsing will fail for a bad input. The below uses _1, _2, and _3 to reference the first, second, and third match value:

(qi::int_ >> "," >> qi::int_ >> "," >> qi::int_)
[
    qi::_val = (qi::_1 + qi::_2) * qi::_3 + qi::_r1
];

This is a contrived example, but should give you the idea. I've found phoenix semantic actions really helpful in constructing complex objects directly from input; this is possible because you can call constructors and member functions within semantic actions.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文