理解 Boost.Spirit 中的列表运算符 (%)

2021-12-24 00:00:00 c++ boost boost-spirit boost-spirit-qi

你能帮我理解 a % b 解析器和它扩展的 a >> 之间的区别吗?*(b >> a) 形式在 Boost.Spirit 中?即使参考手册 表示它们是等价的,

Can you help me understand the difference between the a % b parser and its expanded a >> *(b >> a) form in Boost.Spirit? Even though the reference manual states that they are equivalent,

列表运算符 a % b 是一种二元运算符,它匹配由 ba 的一个或多个重复的列表代码>.这相当于 a >>*(b >> a).

The list operator, a % b, is a binary operator that matches a list of one or more repetitions of a separated by occurrences of b. This is equivalent to a >> *(b >> a).

以下程序会根据使用的程序产生不同的结果:

the following program produces different results depending on which is used:

#include <iostream>
#include <string>
#include <vector>

#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/qi.hpp>

struct Record {
  int id;
  std::vector<int> values;
};

BOOST_FUSION_ADAPT_STRUCT(Record,
  (int, id)
  (std::vector<int>, values)
)

int main() {
  namespace qi = boost::spirit::qi;

  const auto str = std::string{"1: 2, 3, 4"};

  const auto rule1 = qi::int_ >> ':' >> (qi::int_ % ',')                 >> qi::eoi;
  const auto rule2 = qi::int_ >> ':' >> (qi::int_ >> *(',' >> qi::int_)) >> qi::eoi;

  Record record1;
  if (qi::phrase_parse(str.begin(), str.end(), rule1, qi::space, record1)) {
    std::cout << record1.id << ": ";
    for (const auto& value : record1.values) { std::cout << value << ", "; }
    std::cout << '
';
  } else {
    std::cerr << "syntax error
";
  }

  Record record2;
  if (qi::phrase_parse(str.begin(), str.end(), rule2, qi::space, record2)) {
    std::cout << record2.id << ": ";
    for (const auto& value : record2.values) { std::cout << value << ", "; }
    std::cout << '
';
  } else {
    std::cerr << "syntax error
";
  }
}

在 Coliru 上直播

1: 2, 3, 4, 
1: 2, 

rule1rule2 的不同仅在于 rule1 使用列表运算符 ((qi::int_ % ',')) 和 rule2 使用其扩展形式 ((qi::int_ >> *(',' >> qi::int_))).然而,rule1 产生了 1: 2, 3, 4,(如预期的那样)和 rule2 产生了 1: 2,>.我无法理解 rule2 的结果:1) 为什么它与 rule1 的结果不同和 2) 为什么 34 未包含在 record2.values 中,即使 phrase_parse 以某种方式返回 true?

rule1 and rule2 are different only in that rule1 uses the list operator ((qi::int_ % ',')) and rule2 uses its expanded form ((qi::int_ >> *(',' >> qi::int_))). However, rule1 produced 1: 2, 3, 4, (as expected) and rule2 produced 1: 2,. I cannot understand the result of rule2: 1) why is it different from that of rule1 and 2) why were 3 and 4 not included in record2.values even though phrase_parse returned true somehow?

推荐答案

更新 X3 版本已添加

首先,您在这里陷入了一个深陷陷阱:

First off, you fallen into a deep trap here:

Qi 规则不适用于 auto.使用 qi::copy 或仅使用 qi::rule<>.你的程序有未定义的行为,它确实对我来说崩溃了(valgrind 指出了悬空引用的来源).

Qi rules don't work with auto. Use qi::copy or just used qi::rule<>. Your program has undefined behaviour and indeed it crashed for me (valgrind pointed out where the dangling references originated).

所以,首先:

const auto rule = qi::copy(qi::int_ >> ':' >> (qi::int_ % ',')                 >> qi::eoi); 

现在,当你删除程序中的冗余时,你得到:

Now, when you delete the redundancy in the program, you get:

生活在 Coliru

int main() {
    test(qi::copy(qi::int_ >> ':' >> (qi::int_ % ',')));
    test(qi::copy(qi::int_ >> ':' >> (qi::int_ >> *(',' >> qi::int_))));
}

打印

1: 2, 3, 4, 
1: 2, 

原因和解决方法

成功解析的3, 4发生了什么?

好吧,属性传播规则表明qi::int_>>*(',' >> qi::int_) 暴露了一个 tuple;>.为了神奇地 DoTheRightThing(TM) Spirit 不小心将 int 误触发并分配"到属性引用中,而忽略了剩余的 vector.

Well, the attribute propagation rules indicate that qi::int_ >> *(',' >> qi::int_) exposes a tuple<int, vector<int> >. In a bid to magically DoTheRightThing(TM) Spirit accidentally misfires and "assigngs" the int into the attribute reference, ignoring the remaining vector<int>.

如果要将容器属性解析为原子组",请使用qi::as<>:

If you want to make container attributes parse as "an atomic group", use qi::as<>:

test(qi::copy(qi::int_ >> ':' >> qi::as<Record::values_t>() [ qi::int_ >> *(',' >> qi::int_)]));

这里 as<> 作为属性兼容性启发式的障碍,语法知道你的意思:

Here as<> acts as a barrier for the attribute compatibility heuristics and the grammar knows what you meant:

生活在 Coliru 上

#include <iostream>
#include <string>
#include <vector>

#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/qi.hpp>

struct Record {
  int id;
  using values_t = std::vector<int>;
  values_t values;
};

BOOST_FUSION_ADAPT_STRUCT(Record, id, values)

namespace qi = boost::spirit::qi;

template <typename T>
void test(T const& rule) {
    const std::string str = "1: 2, 3, 4";

    Record record;

    if (qi::phrase_parse(str.begin(), str.end(), rule >> qi::eoi, qi::space, record)) {
        std::cout << record.id << ": ";
        for (const auto& value : record.values) { std::cout << value << ", "; }
        std::cout << '
';
    } else {
        std::cerr << "syntax error
";
    }
}

int main() {
    test(qi::copy(qi::int_ >> ':' >> (qi::int_ % ',')));
    test(qi::copy(qi::int_ >> ':' >> (qi::int_ >> *(',' >> qi::int_))));
    test(qi::copy(qi::int_ >> ':' >> qi::as<Record::values_t>() [ qi::int_ >> *(',' >> qi::int_)]));
}

印刷品

1: 2, 3, 4, 
1: 2, 
1: 2, 3, 4, 

相关文章