Boost Spirit - это объектно-ориентированный, рекурсивный парсер и библиотека генерации выходных данных для C++. Он позволяет писать грамматики и описания форматов, используя формат, похожий на Extended Backus Naur Form (EBNF).непосредственно на C++. Эти встроенные грамматические спецификации могут свободно смешиваться с другим кодом C++ и, благодаря генеративной мощности шаблонов C++, немедленно исполняются. В ретроспективе обычные компиляторы-компиляторы или парсер-генераторы должны выполнять дополнительный этап перевода с исходного кода EBNF на код C или C++.
Синтаксис и семантика API библиотек непосредственно формируют доменные встроенные языки (DSEL). Фактически, Spirit предоставляет пользователю 3 различных DSEL:
- один для создания парсерных грамматик,
- один для спецификации требуемых токенов, которые будут использоваться для парсинга,
- и один для описания требуемых выходных форматов.
Поскольку целевые грамматики ввода и форматы вывода полностью написаны на C++, нам не нужны какие-либо отдельные инструменты для компиляции, предварительного процесса или интеграции их в процесс сборки.Spiritобеспечивает бесшовную интеграцию процесса разбора и генерации результатов с другим кодом C++. Это часто позволяет получить более простой и эффективный код.
Полностью отнесены как созданные парсеры, так и генераторы, что позволяет легко строить и обрабатывать иерархические структуры данных в памяти. Эти структуры данных напоминают структуру входных данных и могут непосредственно использоваться для генерации произвольно отформатированного вывода.
На рисункениже изображена общая структура библиотеки Boost Spirit. Библиотека состоит из 4 основных частей:
- Дух.Классика: Это почти неизменная кодовая база, взятая из бывшего дистрибутива Boost Spirit V1.8. Он был перемещен в пространство имён boost::spirit::classic. Для обеспечения полной совместимости с существующим кодом с помощью Spirit V1.8 был добавлен специальный уровень совместимости.
- Дух. Ци: Это библиотека парсеров, позволяющая строить рекурсивные парсеры спуска. Обнаруженный доменно-специфический язык может быть использован для описания грамматики для реализации и правил хранения разобранной информации.
- Дух.Лекс: Это библиотека, которую можно использовать для создания токенизаторов (лексеров). Специфический язык домена, открытыйSpirit.Lex, позволяет определять регулярные выражения, используемые для сопоставления токенов (создавать определения токенов), ассоциировать эти регулярные выражения с кодом, который должен выполняться всякий раз, когда они совпадают, и добавлять определения токенов в лексический анализатор.
- Дух.Карма: Это библиотека генераторов, позволяющая создавать код для рекурсивного спуска, форматирования выходных данных по типу. Обнаруженный доменный язык почти эквивалентен языку описания парсера, используемому вSpirit.Qi, за исключением того, что он используется для описания требуемого формата вывода для генерации из заданной структуры данных.
Три компонента,Spirit.Qi,Spirit.KarmaиSpirit.Lexпредназначены для использования либо отдельно, либо вместе. Общая методология заключается в использовании последовательности токенов, генерируемойSpirit.Lexв качестве входа для парсера, генерируемогоSpirit.Qi. На противоположной стороне уравнения иерархические структуры данных, генерируемыеSpirit.Qi, используются для выходных генераторов, созданных с использованиемSpirit.Karma. Однако ничто не мешает вам использовать любой из этих компонентов самостоятельно.
На рисункениже показан типичный поток данных некоторого ввода, преобразуемого в некоторое внутреннее представление. После некоторого (необязательного) преобразования эти данные преобразуются обратно в некоторое другое внешнее представление. Картина подчеркивает место Духа в этом потоке преобразования данных.
Spirit.Qi— это подбиблиотека Spirit, занимающаяся генерацией парсеров на основе заданной целевой грамматики (по сути, описание формата входных данных для чтения).
Простой фрагмент грамматики EBNF:
group ::= '(' expression ')'
factor ::= integer | group
term ::= factor (('*' factor) | ('/' factor))*
expression ::= term (('+' term) | ('-' term))*
Это приближено с использованием возможностей подбиблиотеки ДухаЦи, как видно из этого фрагмента кода:
group = '(' >> expression >> ')';
factor = integer | group;
term = factor >> *(('*' >> factor) | ('/' >> factor));
expression = term >> *(('+' >> term) | ('-' >> term));
Благодаря волшебству шаблонов выражений, это совершенно действительный и исполняемый код C++. Правило производства<expression
>на самом деле является объектом, который имеет функцию члена<parse
>, которая выполняет работу с исходным кодом, написанным в грамматике, которую мы только что объявили. Да, это калькулятор. На данный момент мы упростим, пропустив типовые декларации и определение правила<integer
>, на которое ссылается<factor
>. Правило производства<expression
>в нашей спецификации грамматики, традиционно называемое символом<start
>, может распознавать входы, такие как:
12345
-12345
+12345
1 + 2
1 * 2
1/2 + 3/4
1 + 2 + 3 + 4
1 * 2 * 3 * 4
(1 + 2) * (3 + 4)
(-1 + 2) * (3 + -4)
1 + ((6 * 200) - 20) / 6
(1 + (2 + (3 + (4 + 5))))
Мы изменили оригинальный синтаксис EBNF. Это делается в соответствии с правилами синтаксиса C++. В частности, мы видим обилие сдвига >>операторов. Поскольку в C++ нет «пустых» операторов, просто невозможно написать что-то вроде:
a b
Как видно из математического синтаксиса, например, для обозначения умножения или, в нашем случае, как видно из синтаксиса EBNF для обозначения секвенирования (b следует за a).Spirit.Qiиспользует для этой цели оператора сдвига<>>
>. Мы берем оператора<>>
>со стрелками, указывающими направо, чтобы означать «следует». Так мы пишем:
a >> b
Альтернативный оператор<|
>и скобки<()
>остаются как есть. Оператор присваивания<=
>используется вместо EBNF<::=
>. И последнее, но не менее важное: звезда Клин<*
>, которая в данном случае является оператором постфикса в EBNF, становится префиксом. Вместо:
a*
Мы пишем:
*a
Поскольку в C/C++ нет постфиксных звезд<*
>. Наконец, мы заканчиваем каждое правило вездесущим полуколоном<;
>.
Spirit не только позволяет описывать структуру входа, но и позволяет точно так же определять формат вывода для ваших данных на основе единого синтаксиса и совместимой семантики.
Предположим, что нам нужно создать текстовое представление из простой структуры данных, такой как<std::vector<int>
>. Обычный код может выглядеть так:
std::vector<int> v (initialize_and_fill());
std::vector<int>::iterator end = v.end();
for (std::vector<int>::iterator it = v.begin(); it != end; ++it)
std::cout << *it << std::endl;
Это не очень гибкий и довольно трудно поддерживать, когда дело доходит до изменения требуемого формата вывода. Подбиблиотека SpiritKarmaпозволяет очень гибко задавать выходные форматы произвольных структур данных. Следующая запись:Кармаописание формата используется для создания того же вывода, что и традиционный код выше:
*(int_ << eol)
Вот еще несколько примеров описания форматов для различных представлений одного и того же вывода<std::vector<int>
>:
Table 2. Different output formats for `std::vector<int>`
Формат
|
Пример
|
Описание
|
<'['<<
*(int_
<<',')<<
']' > |
<[1,8,10,] > |
Запятая разделила список целых чисел |
<*('('
<<int_
<<')'
<<',') > |
<(1),(8),(10), > |
Запятая разделила список целых чисел в скобках |
<*hex > |
<18a > |
Список шестнадцатеричных чисел |
<*(double_
<<',') > |
<1.0,8.0,10.0, > |
Список чисел с плавающей запятой |
В дальнейшем мы увидим в этой документации, как можно избежать печати следа<','
>.
В целом, синтаксис аналогиченSpirit.Qiза исключением того, что мы используем оператор<<<
>для конкатенации вывода. Это должно быть легко понять, поскольку оно следует конвенциям, используемым в потоках ввода-вывода стандарта.
Еще одна важная особенностьSpirit.Karmaпозволяет полностью отделить тип данных от выходного формата. Вы можете использовать один и тот же формат вывода с различными типами данных, если они соответствуют концептуально. В следующей таблице приведены некоторые соответствующие примеры.
Table 3. Different data types usable with the output format `*(int_ <<
eol)`
Тип данных
|
Описание
|
<inti[4] > |
Решетки стиля C |
<std::vector<int> > |
Стандартный вектор |
<std::list<int> > |
Стандартный список |
<boost::array<long,20> > |
Увеличить массив |