Я хотел бы преобразовать некоторый старый рукописный код синтаксического анализа в Boost Spirit и изучить (больше) духа в процессе. Старый код использует потоки и шаблоны для анализа определений для некоторых типов данных и некоторых контейнеров.
Некоторые типичные форматы:
VECTOR[number_of_items,(item_1, item_2 .... item_n)]
PAIR(p1, p2)
RECT[(left,top)-(right,bottom)]
Point( x, y )
Size( x, y )
Функции синтаксического анализа являются шаблонами с типом элементов в качестве параметра шаблона и используют потоки в качестве входных данных, например,
template<class T> std::istream& operator>>(std::Stream& in, std::vector<T>& v);
template<class T1, class T2> std::istream& operator>>(std::istream& in, std::pair<T1, T2>& p);
template<class T1, class T2> std::istream& operator>>(std::istream& in, RectType<T>& r);
etc.
Синтаксический анализатор (извлечение потока) для векторов вызывает синтаксический анализатор для типа шаблонов.
Используя их, можно анализировать определения целочисленных прямоугольников, двойных прямоугольников и векторов пар строк и целых чисел.
Возможно ли с помощью Spirit написать шаблонные парсеры, которые вызывают подпарсеры для типа шаблона?
Как другой ответ почти уже ясно, Qi уже имеет механизм для генерации парсеров на лету, учитывая тип атрибута.
Конечный пользователь сталкивается здесь qi::auto_
, qi::auto_
это синтаксический анализатор, вместо грамматика.
Это имеет явные преимущества[1].
qi::locals<>
, auto_
Терминал ци выражения уже определен, поэтому нет необходимости совсем создать экземпляр грамматики с использованием подробного списка аргументов шаблона:qi::rule<>
а также qi::grammar<>
понести накладные расходы производительности)Давайте посмотрим, как это используется:
std::vector<std::pair<double, int> > parsed;
bool result_ = qi::phrase_parse(first, last, qi::auto_, qi::space, parsed);
Как вы можете видеть, здесь размещается шкипер, а также «магически» выбирается тот парсер, который соответствует parsed
, Теперь, чтобы получить образец формата из OP, вам нужно подключиться к точке настройки для auto_
синтаксический анализатор:
namespace boost { namespace spirit { namespace traits {
// be careful copying expression templates. Boost trunk has `qi::copy` for this too, now
#define PARSER_DEF(a) using type = decltype(boost::proto::deep_copy(a)); static type call() { return boost::proto::deep_copy(a); }
template<typename T1, typename T2>
struct create_parser<std::pair<T1, T2> >
{
PARSER_DEF('(' >> create_parser<T1>::call() >> ',' >> create_parser<T2>::call() >> ')');
};
template<typename TV, typename... TArgs>
struct create_parser<std::vector<TV, TArgs...> >
{
PARSER_DEF('[' >> qi::omit[qi::uint_] >> ',' >> '(' >> create_parser<TV>::call() % ',' >> ')' >> ']' );
};
#undef PARSER_DEF
} } }
Это буквально все, что нужно. Вот демо, которое анализирует:
VECTOR[ 1 ,
(
PAIR (0.97,
5),
PAIR (1.75,10)
)
]
И печатает проанализированные данные как:
Parsed:
0.97 5
1.75 10
Видеть это Жить на Колиру
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/home/qi.hpp>
namespace qi = boost::spirit::qi;
namespace boost { namespace spirit { namespace traits {
// be careful copying expression templates. Boost trunk has `qi::copy` for this too, now
#define PARSER_DEF(a) using type = decltype(boost::proto::deep_copy(a)); static type call() { return boost::proto::deep_copy(a); }
template<typename T1, typename T2>
struct create_parser<std::pair<T1, T2> >
{
PARSER_DEF(lexeme [ lit("PAIR") ] >> '(' >> create_parser<T1>::call() >> ',' >> create_parser<T2>::call() >> ')');
};
template<typename TV, typename... TArgs>
struct create_parser<std::vector<TV, TArgs...> >
{
PARSER_DEF(lexeme [ lit("VECTOR") ] >> '[' >> qi::omit[qi::uint_] >> ',' >> '(' >> create_parser<TV>::call() % ',' >> ')' >> ']' );
};
#undef PARSER_DEF
} } }
#include <boost/spirit/home/karma.hpp>
namespace karma = boost::spirit::karma;
int main()
{
std::string const input("VECTOR[ 1 ,\n"" ( \n"" PAIR (0.97, \n"" 5), \n"" PAIR (1.75,10) \n"" ) \n""]");
std::cout << input << "\n\n";
auto first = input.begin();
auto last = input.end();
std::vector<std::pair<double, int> > parsed;
bool result_ = qi::phrase_parse(first, last, qi::auto_, qi::space, parsed);
if (first!=last)
std::cout << "Remaining unparsed input: '" << std::string(first, last) << "'\n";
if (result_)
std::cout << "Parsed:\n " << karma::format_delimited(karma::auto_ % karma::eol, " ", parsed) << "\n";
else
std::cout << "Parsing did not succeed\n";
}
[1] Потенциальным недостатком будет то, что точка настройки является фиксированной, и, следовательно, вы сможете связать только 1 auto_
парсер с любым типом. Прокрутка собственного базового шаблона дает вам больше контроля и позволяет вам (более) легко иметь различные «варианты синтаксического анализа». Тем не менее, в конце концов возможно получить лучшее из обоих миров, так что я бы пошел сначала для удобства.
Да, это возможно. Я бы так реализовал
#include <boost/spirit/home/qi.hpp>
namespace qi = boost::spirit::qi;
template < typename _Type, typename _Iterator, typename _Enable = void >
struct parser;
template < typename _Type, typename _Iterator >
struct parser < _Type, _Iterator, typename std::enable_if < std::is_arithmetic<_Type> ::value > ::type > :
qi::grammar<_Iterator, _Type() >
{
parser()
: parser::base_type(impl)
{
impl = qi::create_parser<_Type>() ;
}
qi::rule<_Iterator, _Type()> impl;
};
template < typename _Iterator >
struct parser < double, _Iterator> :
qi::grammar<_Iterator, double() >
{
parser()
: parser::base_type(impl)
{
impl = qi::double_;
}
qi::rule<_Iterator, double()> impl;
};
template < typename _First, typename _Second, typename _Iterator >
struct parser < std::pair<_First, _Second>, _Iterator> :
qi::grammar<_Iterator, std::pair<_First, _Second>() >
{
parser()
: parser::base_type(impl)
{
impl = qi::lit('(') >> first >> ',' >> second >> ')';
}
qi::rule<_Iterator, std::pair<_First, _Second>()> impl;
parser<_First, _Iterator> first;
parser<_Second, _Iterator> second;
};
template < typename _Type, typename _Alloc, typename _Iterator >
struct parser < std::vector<_Type, _Alloc>, _Iterator> :
qi::grammar<_Iterator, std::vector<_Type, _Alloc>() >
{
parser()
: parser::base_type(impl)
{
impl = qi::lit('[') >> qi::omit[qi::uint_] >> ",(" >> item % ',' >> ")]";
}
qi::rule<_Iterator, std::vector<_Type, _Alloc>()> impl;
parser<_Type, _Iterator> item;
};
Пример использования:
int main(int agrc, char *argv[])
{
typedef std::pair<double, int> pair;
using string = std::string;
string input_ = { "[1,((0.97,5),(1.75,10))]" };
string::const_iterator iterator_ = input_.begin();
string::const_iterator end_ = input_.end();
std::vector<pair> pairs_;
bool result_ = qi::parse(iterator_, end_, parser <std::vector<pair>, string::const_iterator> (), pairs_);
return 0;
}