Мой ввод состоит из нескольких ключей, пар значений, например:
A=1, B=2, C=3, ..., A=4
Я хочу разобрать вход в следующий тип:
std::map< char, std::vector< int > > m
Значения для равных ключей должны быть добавлены к вектору. Таким образом, анализируемый вывод должен быть равен:
m['A']={1,4};
m['B']={2};
m['C']={3};
Какое самое простое решение с использованием ‘boost :: spirit :: qi’?
Вот один из способов сделать это:
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/include/vector.hpp>
#include <boost/fusion/include/at_c.hpp>
#include <iostream>
#include <utility>
#include <string>
#include <vector>
#include <map>
namespace qi = boost::spirit::qi;
namespace fusion = boost::fusion;
int main()
{
std::string str = "A=1, B=2, C=3, A=4";
std::map< char, std::vector< int > > m;
auto inserter = [&m](fusion::vector< char, int > const& parsed,
qi::unused_type, qi::unused_type)
{
m[fusion::at_c< 0 >(parsed)].push_back(fusion::at_c< 1 >(parsed));
};
auto it = str.begin(), end = str.end();
bool res = qi::phrase_parse(it, end,
((qi::char_ >> '=' >> qi::int_)[inserter]) % ',',
qi::space);
if (res && it == end)
std::cout << "Parsing complete" << std::endl;
else
std::cout << "Parsing incomplete" << std::endl;
for (auto const& elem : m)
{
std::cout << "m['" << elem.first << "'] = {";
for (auto value : elem.second)
std::cout << " " << value;
std::cout << " }" << std::endl;
}
return 0;
}
Несколько комментариев о реализации:
qi::phrase_parse
является алгоритмом Boost.Spirit, который принимает пару итераторов, парсер и парсер пропуска и запускает парсеры на входе, обозначенном итераторами. В процессе обновляет начинающий итератор (it
в этом примере), чтобы он указывал на конец потребляемого ввода при возврате. Возвращенный res
значение указывает, были ли синтаксические анализаторы успешными (то есть использованный вход мог быть успешно проанализирован). Есть и другие формы qi::phrase_parse
которые позволяют извлекать атрибуты (которые являются проанализированными данными в терминах Boost.Spirit), но мы здесь не используем атрибуты, потому что у вас есть особое требование к результирующей структуре контейнера.
Парсер пропуска используется для пропуска частей ввода между элементами основного парсера. В этом случае, qi::space
означает, что любые пробельные символы будут игнорироваться при вводе, например, «A = 1» и «A = 1» могут быть проанализированы одинаково. Есть qi::parse
семейство алгоритмов, которые не имеют парсера пропуска и поэтому требуют, чтобы основной парсер обрабатывал весь ввод без пропусков.
(qi::char_ >> '=' >> qi::int_)
часть основного синтаксического анализатора соответствует один символ, за которым следует знак равенства, а затем целое число со знаком. Знак равенства выражается в виде литерала (т. Е. Он эквивалентен qi::lit('=')
синтаксический анализатор), что означает, что он соответствует только входным данным, но не приводит к анализу данных. Поэтому результатом этого синтаксического анализатора является атрибут, представляющий собой последовательность двух элементов — символа и целого числа.
% ','
часть синтаксического анализатора является анализатор списка, который анализирует любое количество частей ввода, описанных парсером слева (который является парсером, описанным выше), разделенных частями, описанными парсером справа (то есть с запятыми в нашем случае). Как и прежде, запятая — это буквальный синтаксический анализатор, поэтому он не производит вывод.
[inserter]
часть является семантическое действие, которая является функцией, которая вызывается парсером каждый раз, когда совпадает с частью входной строки. Парсер передает все свои проанализированные выходные данные в качестве первого аргумента этой функции. В нашем случае семантическое действие присоединяется к парсеру, описанному в пуле № 3, что означает, что передается последовательность символов и целое число. Boost.Spirit использует fusion::vector
передать эти данные. Два других аргумента семантического действия в этом примере не используются и могут быть проигнорированы.
inserter
Функция в этом примере является лямбда-функцией, но это может быть любой другой тип объекта функции, включая обычную функцию, функцию, сгенерированную std::bind
и т. д. Важной частью является то, что он имеет указанную подпись и что тип его первого аргумента совместим с атрибутом синтаксического анализатора, к которому он присоединен как семантическое действие. Итак, если бы у нас был другой парсер в пуле № 3, этот аргумент пришлось бы изменить соответствующим образом.
fusion::at_c< N >()
в inserter
получает элемент вектора по индексу N
, Это очень похоже на std::get< N >()
применительно к std::tuple
,
Других решений пока нет …