Определите парсеры, параметризованные с помощью субпарсеров в Boost Spirit

Я хотел бы преобразовать некоторый старый рукописный код синтаксического анализа в 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 написать шаблонные парсеры, которые вызывают подпарсеры для типа шаблона?

6

Решение

Как другой ответ почти уже ясно, Qi уже имеет механизм для генерации парсеров на лету, учитывая тип атрибута.

Конечный пользователь сталкивается здесь qi::auto_, qi::auto_ это синтаксический анализатор, вместо грамматика.

Это имеет явные преимущества[1].

  • Прежде всего, это позволяет пользователям использовать парсер внутри грамматики с шкипер по своему выбору, а также, возможно, с помощью qi::locals<>,
  • Так же auto_ Терминал ци выражения уже определен, поэтому нет необходимости совсем создать экземпляр грамматики с использованием подробного списка аргументов шаблона:
  • Наконец, синтаксический анализатор возвращает шаблон выражения, так что никакого стирания типов не происходит, и объединение нескольких синтаксических анализаторов 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_ парсер с любым типом. Прокрутка собственного базового шаблона дает вам больше контроля и позволяет вам (более) легко иметь различные «варианты синтаксического анализа». Тем не менее, в конце концов возможно получить лучшее из обоих миров, так что я бы пошел сначала для удобства.

5

Другие решения

Да, это возможно. Я бы так реализовал

#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;
}
2

По вопросам рекламы [email protected]