![]() |
![]() ![]() ![]() ![]() ![]() |
![]() |
User manualBoost , The Boost C++ Libraries BoostBook Documentation Subset , Chapter 22. Boost.Metaparse
|
![]() |
Note |
---|---|
Обратите внимание, что< |
Он работает, однако, это довольно неэффективно: он имеет петлю, анализирующую целые числа один за другим, создавая список типов, а затем он петляет по этому списку типов, чтобы суммировать результат. Использование шаблонных метапрограмм в ваших приложениях может оказать серьезное влияние на использование памяти компилятора и скорость компиляции, поэтому я рекомендую быть осторожным с этими вещами.
Metaparse предлагает более эффективные способы достижения того же результата. Вам не нужны два цикла: вы можете объединить их вместе и добавить каждое число в резюме сразу после его разбора. Метапарс предлагает<foldl
>для этого.
<foldl
>Вы указываете:
int_token
>)int_<0>
>)sum_op
>).Наш парсер может быть реализован таким образом:
using better_sum_parser = foldl<int_token, mpl::int_<0>, sum_op>;
Как видите, реализация парсера более компактна. Вот диаграмма, показывающая, что происходит, когда вы используете этот парсер для анализа некоторых входных данных:
Как видите, не только реализация парсера более компактна, но и достигает того же результата, делая меньше. Он анализирует вход, применяя<int_token
>многократно, как и предыдущее решение. Но он дает конечный результат без составления списка типов в качестве внутреннего шага. Вот как это работает внутри:
Он суммирует результаты повторного применения<int_token
>с использованием<sum_op
>. Эта реализация более эффективна. Он принимает пустую строку как действительный вход: сумма его<0
>. Это может быть полезно для вас, и в этом случае вы готовы. Если вы не хотите принимать его, вы можете использовать<foldl1
>вместо<foldl
>. То же самое, но он отвергает пустой вход. (Метапарс предлагает<repeated1
>также, если вы выбираете первый подход и хотите отклонить пустую строку)
![]() |
Note |
---|---|
Обратите внимание, что если вы читаете это руководство впервые, вы, вероятно, хотите пропустить этот раздел и продолжитьВведение foldl_start_with_parser |
Возможно, вы заметили, что Metaparse предлагает<foldr
>. Разница между<foldl
>и<foldr
>— это направление, в котором резюмируются результаты.<l
>означаетслеваи<r
>означаетсправа. Вот диаграмма, показывающая, как работает<better_sum_parser
>, если он реализован с использованием<foldr
>:
Как вы можете видеть, это очень похоже на использование<foldl
>, но результаты, полученные из отдельных приложений<int_token
>, суммируются в порядке справа налево. Поскольку<sum_op
>является дополнением, это не влияет на конечный результат, но в других случаях это может повлиять.
![]() |
Note |
---|---|
Обратите внимание, что реализация< |
Как и следовало ожидать, Metaparse также предлагает<foldr1
>, который складывается справа и отклоняет пустой вход.
Давайте изменим грамматику нашего маленького языка. Вместо списка чисел, давайте предположим, что числа разделены символом<+
>. Наш примерный вклад становится следующим:
BOOST_METAPARSE_STRING("11 + 13 + 3 + 21")
Сопоставить его с<foldl
>или<repeated
>трудно: перед каждым элементомдолжен быть<+
>символ, за исключениемпервого. Ни одна из уже введенных конструкций повторения не предлагает способа обработки первого элемента по-другому.
Если мы на мгновение забудем о первом числе, остальная часть ввода будет<"+ 13 + 3 + 21"
>. Это можно легко разобрать на<foldl
>(или<repeated
>):
using plus_token = token<lit_c<'+'>>; using plus_int = last_of<plus_token, int_token>; using sum_parser2 = foldl<plus_int, int_<0>, sum_op>;
Он использует<plus_int
>, то есть<last_of
><<plus_token,int_token>
>в качестве парсера, который используется неоднократно для получения чисел. Он делает следующее:
plus_token
>для разбора<+
>символа и любого белого пространства, которое может следовать за ним.int_token
>Для определения числаlast_of
>, чтобы использовать оба парсера в порядке и сохранить только результат использования второго (результат разбора символа<+
>отбрасывается — нам все равно).Таким образом<last_of
><<plus_token,int_token>
>возвращает значение числа в результате разбора, как это делал наш предыдущий парсер<int_token
>. Из-за этого он может быть использован в качестве замены 263 в предыдущем примере, и мы получаем парсер для нашего обновленного языка. По крайней мере, для всех, кроме первого.
Этот<foldl
>не может разобрать первый элемент, потому что он ожидает символ<+
>перед каждым числом. Вы можете подумать о том, чтобы сделать символ<+
>необязательным в приведенном выше подходе. Это позволяет парсеру принимать<"11
+ 13 3 21"
>, а символ<+
>теперь является необязательнымповсюду.
То, что вы могли бы сделать, это сравнять первый элемент с<int_token
>, остальные элементы с вышеупомянутым<foldl
>-решением и добавить результат из двух. Это остается упражнением для читателя.
Metaparse предлагает<foldl_start_with_parser
>реализовать это.<foldl_start_with_parser
>то же, что<foldl
>. Разница в том, что вместо начального значения для объединения элементов списка с ним требуетсяначальный парсер:
using plus_token = token<lit_c<'+'>>; using plus_int = last_of<plus_token, int_token>; using sum_parser3 = foldl_start_with_parser<plus_int, int_token, sum_op>;
<foldl_start_with_parser
>начинается с применения этого начального парсера и использует результат, который он возвращает в качестве начального значения для складывания. Это то же самое, что<foldl
>после этого. На следующей диаграмме показано, как его можно использовать для разбора списка чисел, разделенных символами<+
>:
Как показано на диаграмме, он начинает разбор списка чисел с<int_token
>, использует его значение в качестве начального значения для складывания (ранее подходы использовали значение<int_<0>
>в качестве этого начального значения). Затем он анализирует все элементы списка, используя<plus_int
>несколько раз.
![]() |
Note |
---|---|
Обратите внимание, что если вы читаете это руководство впервые, вы, вероятно, хотите пропустить этот раздел и попробовать создать парсеры, используя< |
<foldl_start_with_parser
>имеет своюот правойпары,<foldr_start_with_parser
>. Он использует те же элементы, что и<foldl_start_with_parser
>, но в другом порядке. Вот парсер для нашего примера языка, реализованного с<foldr_start_with_parser
>:
using plus_token = token<lit_c<'+'>>; using int_plus = first_of<int_token, plus_token>; using sum_parser4 = foldr_start_with_parser<int_plus, int_token, sum_op>;
Обратите внимание, что он использует<int_plus
>вместо<plus_int
>. Это связано с тем, что парсер, из которого исходное значение складывания исходит, используется после того, как<int_plus
>разобрал вход столько раз, сколько мог. Это может показаться странным в первый раз, но следующая диаграмма должна помочь вам понять, как это работает:
Как вы можете видеть, он начинается с парсера, который применяется многократно на входе, поэтому вместо многократного разбора<plus_token
int_token
>нам нужно многократно разбирать<int_tokenplus_token
>. За последним числом не следует<+
>, поэтому<int_plus
>не может его разобрать и он останавливает итерацию.<foldr_start_with_parser
>затем использует другой парсер<int_token
>для разбора ввода. Он преуспевает, и результат, который он возвращает, используется в качестве начального значения для складывания справа.
![]() |
Note |
---|---|
Обратите внимание, что, как следует из вышеприведенного описания, реализация< |
Используя парсер, построенный с помощью<foldl_start_with_parser
>, мы можем анализировать вход, когда вход правильный. Однако это не всегда так. Рассмотрим, например, следующие данные:
BOOST_METAPARSE_STRING("11 + 13 + 3 + 21 +")
Это некорректное выражение. Однако, если мы разберем его с помощью парсера<foldl_start_with_parser
>на основе, представленного ранее<sum_parser3
>, он принимает вход и результат<48
>. Это связано с тем, что<foldl_start_with_parser
>анализирует входдо тех пор, пока он может. Он разбирает первые<int_token
><11
>, а затем начинает разбирать<plus_int
>элементы<+13
>,<+3
>,<+21
>. Проанализировав все это, он пытается разобрать оставшийся<" +"
>вход, используя<plus_int
>, который выходит из строя и, следовательно,<foldl_start_with_parser
>останавливается после<+21
>.
Проблема в том, что парсер анализирует самое длинное подвыражение, начиная с самого начала, что представляет собой действительное выражение. Остальное игнорируется. Парсер может быть обернут<entire_input
>, чтобы в конце отклонить выражения с недействительными дополнительными символами, однако это не сделает сообщение об ошибке полезным.<entire_input
>может только сказать автору недействительного выражения, что после<+
21
>что-то не так.
Метапарс предоставляет<foldl_reject_incomplete_start_with_parser
>, который делает то же самое, что<foldl_start_with_parser
>, за исключением того, что, как только дальнейшие повторения не найдены, он проверяет, гдеповторяющийся парсер (в нашем примере<plus_int
>) выходит из строя. Когда он может сделать какой-либо прогресс (например, он находит символ<+
>), то<foldl_reject_incomplete_start_with_parser
>предполагает, что автор выражения намеревался сделать повторение более длинным, но сделал ошибку и распространяет сообщение об ошибке, исходящее из этого последнего нарушенного выражения.
На рисунке показано, как<foldl_reject_incomplete_start_with_parser
>разбирает пример недействительного ввода и как он выходит из строя. Это может быть использовано для лучшего сообщения об ошибках от парсеров.
Другие складные парсеры также имеют свою версию<f
>(например,<foldr_reject_incomplete
>,<foldl_reject_incomplete1
>и т. Д.).
Как вы могли заметить, существует множество различных складных парсерных комбинаторов. Чтобы помочь вам найти правильный, используется следующая конвенция именования:
![]() |
Note |
---|---|
Заметим, что нет< |
Парсеры, построенные с использованием Metaparse, представляют собой шаблонные метапрограммы, анализирующие текст (или код) во время компиляции. Вот список вещей, которые могут быть «результатом» анализа:
printf
>и возврат списка типов (например,<boost::mpl::vector
>) ожидаемых аргументов.boost::xpressive::sregex
>объектов. Рассмотрим<regex
>пример Метапарса.compile_to_native_code
>пример Метапарса в качестве примера для этого.meta_hs
>пример Метапарса в качестве примера для этого.Metaparse предоставляет способ определения грамматики в синтаксисе, который напоминает EBNF. Шаблон<grammar
>может использоваться для определения грамматики. Его можно использовать следующим образом:
grammar<BOOST_METAPARSE_STRING("plus_exp")> ::import<BOOST_METAPARSE_STRING("int_token"), token<int_>>::type ::rule<BOOST_METAPARSE_STRING("ws ::= (' ' | '\n' | '\r' | '\t')*")>::type ::rule<BOOST_METAPARSE_STRING("plus_token ::= '+' ws"), front<_1>>::type ::rule<BOOST_METAPARSE_STRING("plus_exp ::= int_token (plus_token int_token)*"), plus_action>::type
Приведенный выше код определяет парсер из определения грамматики. Символом начала грамматики является<plus_exp
>. Линии, начинающиеся с<::rule
>, определяют правила. Правила необязательно имеют семантическое действие, которое представляет собой класс метафункций, преобразующий результат разбора после применения правила. Существующие парсеры могут быть привязаны к именам и использоваться в правилах путем их импорта. Линии, начинающиеся с<::import
>, связывают существующие парсеры с именами.
Результатом определения грамматики является парсер, который может быть предоставлен другим парсерным комбинаторам или использоваться непосредственно. Учитывая, что грамматики могут импортировать существующие парсеры и создавать новые, они также являются парсерными комбинаторами.
Metaparse основан на шаблонном метапрограммировании, однако C++11 предоставляет<constexpr
>, который также можно использовать для разбора во время компиляции. В то время как реализация парсеров на основе<constexpr
>проще для разработчика C++, поскольку его синтаксис напоминает обычный синтаксис языка, результат разбора должен быть<constexpr
>значением. Парсеры на основе шаблонного метапрограммирования могут строить типы в результате парсинга. Эти типы могут быть упакованными<constexpr
>значениями, но могут быть классами метафункций, классами со статическими функциями, которые можно назвать во время выполнения и т.д.
Когда парсер, построенный с помощью Metaparse, нуждается в подпарсере для обработки части входного текста и генерации значения<constexpr
>в результате парсинга, можно реализовать подпарсер на основе функций<constexpr
>. Метапарс может быть интегрирован с ними и поднять их результаты в C++ шаблон метапрограммирования. Пример, демонстрирующий эту особенность, можно найти среди примеров<constexpr_parser
>. Эта возможность позволяет интегрировать Metaparse с библиотеками анализа на основе<constexpr
>.
Можно писать парсеры дляконтекстных свободных грамматикс помощью Metaparse. Однако это не самая общая категория грамматик, которую можно использовать. Поскольку Метапарс является очень расширяемой структурой, неясно, что следует считать пределом самого Метапарса. Например, Metaparse предоставляет<accept_when
>парсерный комбинатор. Он может использоваться для предоставления произвольных предикатов для включения/отключения конкретного правила. Можно зайти так далеко, что предоставить машине Тьюринга (какметафункция) всю грамматику в качестве предиката, так что можно построить парсеры длянеограниченных грамматик, которые можно анализировать с помощью машины Тьюринга. Обратите внимание, что такой парсер не будет считаться парсером, построенным с помощью Metaparse, однако неясно, как далеко может зайти решение и все еще рассматриваться с использованием Metaparse.
Метапарс предполагает, что парсеры являютсядетерминированными, поскольку они имеют только «один» результат. Конечно, можно написать парсеры и комбинаторы, которые возвращают набор (или список или какой-либо другой контейнер) результатов в качестве этого «одного» результата, но это можно рассматривать как создание новой библиотеки парсера. Нет четкой границы для метапарса.
Метапарс поддерживает построениенисходящих парсеровилевая рекурсияне поддерживается, так как это привело бы к бесконечной рекурсии.Поддерживается правая рекурсия, однако в большинстве случаевитеративные парсерные комбинаторыобеспечивают лучшие альтернативы.
Статья User manual раздела The Boost C++ Libraries BoostBook Documentation Subset Chapter 22. Boost.Metaparse может быть полезна для разработчиков на c++ и boost.
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.
:: Главная :: Chapter 22. Boost.Metaparse ::
реклама |