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

use*_*413 6 c++ boost boost-spirit boost-spirit-qi

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

列表运算符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";
  }
}
Run Code Online (Sandbox Code Playgroud)

住在Coliru

1: 2, 3, 4, 
1: 2, 
Run Code Online (Sandbox Code Playgroud)

rule1并且rule2仅在于rule1使用list operator((qi::int_ % ','))并rule2使用其展开的form((qi::int_ >> *(',' >> qi::int_))).然而,rule1产生1: 2, 3, 4,(如预期)和 rule2生产1: 2,.我无法理解的结果rule2:1)为什么是从不同的rule1; 2)为什么都34没有包括在record2.values即使phrase_parse返回真不知何故?

seh*_*ehe 10

更新 X3版本添加

首先,你陷入了陷阱:

齐规则不起作用auto.使用qi::copy或刚刚使用过qi::rule<>.你的程序有不明确的行为,事实上它已经崩溃了(valgrind指出悬挂引用的起源).

所以,首先:

const auto rule = qi::copy(qi::int_ >> ':' >> (qi::int_ % ',')                 >> qi::eoi); 
Run Code Online (Sandbox Code Playgroud)

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

再现问题

Live On Coliru

int main() {
    test(qi::copy(qi::int_ >> ':' >> (qi::int_ % ',')));
    test(qi::copy(qi::int_ >> ':' >> (qi::int_ >> *(',' >> qi::int_))));
}
Run Code Online (Sandbox Code Playgroud)

印花

1: 2, 3, 4, 
1: 2, 
Run Code Online (Sandbox Code Playgroud)

原因和解决方法

发生了什么事3, 4这是成功解析

好吧,属性传播规则表明qi::int_ >> *(',' >> qi::int_)暴露了一个tuple<int, vector<int> >.为了神奇地DoTheRightThing(TM)精神意外地失败并且"分配" int到属性引用中,忽略剩余的vector<int>.

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

test(qi::copy(qi::int_ >> ':' >> qi::as<Record::values_t>() [ qi::int_ >> *(',' >> qi::int_)]));
Run Code Online (Sandbox Code Playgroud)

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

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

打印

1: 2, 3, 4, 
1: 2, 
1: 2, 3, 4, 
Run Code Online (Sandbox Code Playgroud)


seh*_*ehe 10

因为是时候让人们开始使用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."
    );
Run Code Online (Sandbox Code Playgroud)

很好,对吧?

解决方法似乎不太可读:

test(int_ >> ':' >> (rule<struct _, Record::values_t>{} = (int_ >> *(',' >> int_))));
Run Code Online (Sandbox Code Playgroud)

但是as<>,如果您想要编写自己的"指令"(或仅仅是一个函数),那将是微不足道的:

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

DEMO

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

打印

1: 2, 3, 4, 
1: 2, 3, 4, 
1: 2, 3, 4, 
Run Code Online (Sandbox Code Playgroud)