【问题标题】:Understanding the List Operator (%) in Boost.Spirit理解 Boost.Spirit 中的列表运算符 (%)
【发布时间】:2016-02-22 08:23:04
【问题描述】:

你能帮我理解a % b 解析器和它在Boost.Spirit 中的扩展a >> *(b >> a) 形式之间的区别吗?尽管the reference manual 声明它们是等价的,

列表运算符a % b 是一个二元运算符,它匹配一个或多个重复的a 的列表,由b 的出现分隔。这相当于a >> *(b >> a)。

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

#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 << '\n';
  } else {
    std::cerr << "syntax error\n";
  }

  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 << '\n';
  } else {
    std::cerr << "syntax error\n";
  }
}

Live on Coliru

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

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

【问题讨论】:

  • 有幸记住圣灵术语的人可以更好地解释它,但它们在匹配的内容上看起来相当,但在他们接受用于存储值的类型上却不一样。第二个似乎适用于(int, int, vector&lt;int&gt;) 而不是(int, vector&lt;int&gt;)。我猜vector&lt;int&gt; 与单个int_ 兼容,然后在存储值时会忽略重复的int_s(在*(…) 内)。
  • 是的,就是这样,看看我的回答@chris。但是代码中有一个大错误,所以就是这样(任何事情都可能发生)
  • @sehe,谢谢,我对 Spirit 很感兴趣,但我还没有机会使用它。 CppCon X3 的演讲非常酷。
  • @chris 这促使我在 Spirit X3 中做相应的测试用例。正如预期的那样,总体上情况要好得多(没有 UB,没有静默属性传播失败,更不用说大大提高了编译时间 :))。见我的second answer
  • 另一个区别是,如果b 有一个属性(在您的示例中不是这种情况),它会在a%b 中被忽略,但在a &gt;&gt;*(b &gt;&gt; a) 中不会被忽略。 a &gt;&gt; *(omit[b] &gt;&gt; a) 会更接近(但与sehe的答案中显示的问题相同)。

标签: c++ boost boost-spirit boost-spirit-qi


【解决方案1】:

更新 X3版本added

首先,你掉进了一个很深的陷阱:

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

所以,首先:

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

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

重现问题

Live On 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_ &gt;&gt; *(',' &gt;&gt; qi::int_) 公开了tuple&lt;int, vector&lt;int&gt; &gt;。为了神奇地 DoTheRightThing(TM) Spirit 意外失火并将int“分配”到属性引用中,而忽略了剩余的vector&lt;int&gt;。

如果您想让容器属性解析为“原子组”,请使用qi::as&lt;&gt;:

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

这里as&lt;&gt; 充当属性兼容性启发式的障碍,语法知道您的意思:

Live On 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 << '\n';
    } else {
        std::cerr << "syntax error\n";
    }
}

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, 

【讨论】:

  • 这是否意味着,当 Qi 尝试将 tuple&lt;int, vector&lt;int&gt;&gt; = {2, {3, 4}} 与 vector&lt;int&gt; = Record::values 匹配时,Qi 恰好将 tuple&lt;int, vector&lt;int&gt;&gt; = {2, {3, 4}} 转换为 vector&lt;int&gt; = {2},而不是预期的 vector&lt;int&gt; = {2, 3, 4},由于一些实现细节?
  • 那么qi::as&lt;&gt;如何解决这个问题?根据the manual,qi::as&lt;&gt; 是关于提交/回滚语义。那么,当解析成功时,不管qi::as&lt;&gt;是否存在,解析器不应该产生相同的结果吗?
  • @user5583413 qi::hold 是关于提交/回滚语义。正如as&lt;&gt;[] 的文档所示:“注意as&lt;T&gt; 隐含地导致提交/回滚语义在本质上类似于保持指令。” 仅此而已。它实际上是一个注释,告诉解析器您希望如何合成容器属性。而 that 是在那里“原子地”制作的。 (措辞有点混乱。)
  • (继续上一条评论)对于tuple&lt;A, vector&lt;A&gt;&gt;,Qi 被意外写入,因此解析器仅将元组的第一个元素存储到其提供的vector&lt;A&gt; 类型的容器属性中,忽略第二个元组的元素 (vector&lt;A&gt;)。 X3 在编译时严格拒绝它。
  • @user5583413 这不是完全相同的情况,但您可以看到对类似问题的解释尝试here 和here。
【解决方案2】:

因为是时候让人们开始使用X3(新版Spirit)了,又因为我喜欢挑战msyelf在Spirit X3中做相应的任务,所以这里是Spirit X3版本。

auto 在 X3 中没有问题。

“破碎”的情况也表现得更好,触发了这个静态断言:

    // If you got an error here, then you are trying to pass
    // a fusion sequence with the wrong number of elements
    // as that expected by the (sequence) parser.
    static_assert(
        fusion::result_of::size<Attribute>::value == (l_size + r_size)
      , "Attribute does not have the expected size."
    );

这不错,对吧?

解决方法似乎不太可读:

test(int_ >> ':' >> (rule<struct _, Record::values_t>{} = (int_ >> *(',' >> int_))));

但如果您愿意,编写自己的as&lt;&gt;“指令”(或只是一个函数)将是微不足道的:

namespace {
    template <typename T>
    struct as_type {
        template <typename Expr>
            auto operator[](Expr&& expr) const {
                return x3::rule<struct _, T>{"as"} = x3::as_parser(std::forward<Expr>(expr));
            }
    };

    template <typename T> static const as_type<T> as = {};
}

演示

Live On Coliru

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

#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/spirit/home/x3.hpp>

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

namespace x3 = boost::spirit::x3;

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

    Record record;

    auto attr = std::tie(record.id, record.values);

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

namespace {
    template <typename T>
    struct as_type {
        template <typename Expr>
            auto operator[](Expr&& expr) const {
                return x3::rule<struct _, T>{"as"} = x3::as_parser(std::forward<Expr>(expr));
            }
    };

    template <typename T> static const as_type<T> as = {};
}

int main() {
    using namespace x3;
    test(int_ >> ':' >> (int_ % ','));
    //test(int_ >> ':' >> (int_ >> *(',' >> int_))); // COMPILER asserts "Attribute does not have the expected size."

    // "clumsy" x3 style workaround
    test(int_ >> ':' >> (rule<struct _, Record::values_t>{} = (int_ >> *(',' >> int_))));

    // using an ad-hoc `as<>` implementation:
    test(int_ >> ':' >> as<Record::values_t>[int_ >> *(',' >> int_)]);
}

打印

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-03-24
    • 2016-05-18
    • 2021-09-03
    • 1970-01-01
    • 2021-09-18
    • 1970-01-01
    • 2019-12-14
    • 2022-12-05
    相关资源
    最近更新 更多