Понимание оператора списка (%) в Boost.Spirit
Можете ли вы помочь мне понять разницу между a % b
парсер и его расширен a >> *(b >> a)
форма в Boost.Spirit? Хотя в справочном руководстве говорится, что они эквивалентны,
Оператор списка,
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";
}
}
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
вернули как-нибудь?
2 ответа
Обновленная версия X3 добавлена
Прежде всего, вы попали в глубокую ловушку здесь:
Правила ци не работают с auto
, использование qi::copy
или просто использовал qi::rule<>
, Ваша программа имеет неопределенное поведение, и она действительно сработала для меня (Вальгринд указал, откуда возникли висячие ссылки).
Итак, во-первых:
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 случайно пропускает огонь и "назначает" int
в ссылку на атрибут, игнорируя оставшиеся vector<int>
,
Если вы хотите, чтобы атрибуты контейнера анализировались как "атомарная группа", используйте 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,
Поскольку пришло время начать людей с 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<>
"директива" (или просто функция), если вы хотите:
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 = {};
}
DEMO
#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,