了解 Boost.Spirit 中的列表运算符 (%)
Understanding the List Operator (%) in Boost.Spirit
你能帮我理解 a % b
解析器和它在 Boost.Spirit 中扩展的 a >> *(b >> a)
形式之间的区别吗?尽管 the reference manual 声明它们是等价的,
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)
.
根据使用的不同,以下程序会产生不同的结果:
#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";
}
}
1: 2, 3, 4,
1: 2,
rule1
和 rule2
的区别仅在于 rule1
使用列表运算符 ((qi::int_ % ',')
) 而 rule2
使用其扩展形式 ((qi::int_ >> *(',' >> qi::int_))
).但是,rule1
产生了 1: 2, 3, 4,
(正如预期的那样),rule2
产生了 1: 2,
。我无法理解 rule2
的结果:1) 为什么它与 rule1
的结果不同,以及 2) 为什么 3
和 4
不包含在 record2.values
中即使 phrase_parse
以某种方式返回 true?
Update X3 version
首先,你掉进了一个深坑:
Qi 规则不适用于 auto
。使用 qi::copy
或仅使用 qi::rule<>
。你的程序有未定义的行为,确实对我来说它崩溃了(valgrind 指出了悬挂引用的来源)。
所以,首先:
const auto rule = qi::copy(qi::int_ >> ':' >> (qi::int_ % ',') >> qi::eoi);
现在,当你删除程序中的冗余时,你会得到:
重现问题
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<int, vector<int> >
。为了神奇地 DoTheRightThing(TM) Spirit 意外失火并且 "assigngs" int
进入属性引用,忽略剩余的 vector<int>
.
如果要使容器属性解析为 "an atomic group",请使用 qi::as<>
:
test(qi::copy(qi::int_ >> ':' >> qi::as<Record::values_t>() [ qi::int_ >> *(',' >> qi::int_)]));
这里 as<>
作为属性兼容性启发式的障碍,语法知道你的意思:
#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,
因为是时候让大家开始使用Spirit X3(Spirit的新版本),也因为我喜欢挑战msyelf在Spirit X3中完成相应的任务,所以这里是Spirit X3版本。
X3 中 auto
没有问题。
"broken" 案例也表现得更好,触发了这个静态断言:
// 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<>
"directive"(或只是一个函数)将是微不足道的:
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 = {};
}
演示
#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,
你能帮我理解 a % b
解析器和它在 Boost.Spirit 中扩展的 a >> *(b >> a)
形式之间的区别吗?尽管 the reference manual 声明它们是等价的,
The list operator,
a % b
, is a binary operator that matches a list of one or more repetitions ofa
separated by occurrences ofb
. This is equivalent toa >> *(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";
}
}
1: 2, 3, 4,
1: 2,
rule1
和 rule2
的区别仅在于 rule1
使用列表运算符 ((qi::int_ % ',')
) 而 rule2
使用其扩展形式 ((qi::int_ >> *(',' >> qi::int_))
).但是,rule1
产生了 1: 2, 3, 4,
(正如预期的那样),rule2
产生了 1: 2,
。我无法理解 rule2
的结果:1) 为什么它与 rule1
的结果不同,以及 2) 为什么 3
和 4
不包含在 record2.values
中即使 phrase_parse
以某种方式返回 true?
Update X3 version
首先,你掉进了一个深坑:
Qi 规则不适用于 auto
。使用 qi::copy
或仅使用 qi::rule<>
。你的程序有未定义的行为,确实对我来说它崩溃了(valgrind 指出了悬挂引用的来源)。
所以,首先:
const auto rule = qi::copy(qi::int_ >> ':' >> (qi::int_ % ',') >> qi::eoi);
现在,当你删除程序中的冗余时,你会得到:
重现问题
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<int, vector<int> >
。为了神奇地 DoTheRightThing(TM) Spirit 意外失火并且 "assigngs" int
进入属性引用,忽略剩余的 vector<int>
.
如果要使容器属性解析为 "an atomic group",请使用 qi::as<>
:
test(qi::copy(qi::int_ >> ':' >> qi::as<Record::values_t>() [ qi::int_ >> *(',' >> qi::int_)]));
这里 as<>
作为属性兼容性启发式的障碍,语法知道你的意思:
#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,
因为是时候让大家开始使用Spirit X3(Spirit的新版本),也因为我喜欢挑战msyelf在Spirit X3中完成相应的任务,所以这里是Spirit X3版本。
X3 中 auto
没有问题。
"broken" 案例也表现得更好,触发了这个静态断言:
// 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<>
"directive"(或只是一个函数)将是微不足道的:
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 = {};
}
演示
#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,