Skip to content

Latest commit

 

History

History
290 lines (223 loc) · 9.55 KB

index.adoc

File metadata and controls

290 lines (223 loc) · 9.55 KB

Master programming
Лекция №8 (Обзор boost::spirit)

1. Знакомство с boost::spirit

1.1. Из чего состоит грамматика языка?

  • Связующее звено между представлениями одних и тех же сущностей

  • AST — abstract syntax tree

  • EBNF (Extended Backus-Naur Form — язык описания грамматики

  • Рекурсивность, как полнота грамматики

  • PEG (Parsing Expression Grammar) — Domain Specific Embedded Language

Почитать про boost::spirit::x3.

1.2. Концепты spirit-а

x3::rule<ID, Attribute>

  • Правила составляются из парсеров, которые захватывают атрибуты

    • Synthesized attribute — атрибут по умолчанию

    • Compatible attribute — совместимый (конвертируемый) атрибут (std::string и std::vector<char>)

  • Грамматика — набор правил и специальных атрибутов для построения AST

Пример вывода правил
link:images/rule.dot[role=include]

1.3. Примитивные парсеры

  • int_, short_, long_, int_(42), …​ — парсеры целых чисел

  • double_, float_, double_(42.2), …​ — парсеры действительных чисел

  • bool_, true_, false_ — булевы парсеры

  • lit("abc"), char_, char_("A-Za-z"), …​ — литеральные парсеры (точное соответствие строке)

  • alnum, blank, space, lower, …​ — классификаторы

  • parse — разбор выражения

  • phrase_parse — более "тонкий" разбор выражения

namespace x3 = boost::spirit::x3;
std::string_view text{"123546"};
bool parsed = x3::parse(text.begin(), text.end(), x3::int_);

1.4. Основные операторы boost::spirit

Table 1. PEG Operators
Description PEG Spirit X3 Example

Sequence

a b

a >> b

int_ >> ' ' >> double_

Alternative

a | b

a | b

alpha | digit

Zero or more (Kleene)

a*

*a

alpha >> *(',' >> alnum)

One or more (Plus)

a+

+a

+alnum >> '_'

Optional

a?

-a

-alpha >> int_

And-predicate

&a

&a

int_ >> &char_(';')

Not-predicate

!a

~a

~char_('"')

Difference

a - b

"/" >> *(char_ - "/") >> "*/"

Expectation

a > b

lit('c') > "++"

List

a % b

int_ % ','

2. Формирование правил

  • Именованные парсеры

  • Можно указать тип атрибута

  • Позволяет использовать рекурсивность парсеров

  • Предоставляет обработчик ошибок (on_error)

  • Использование пользовательских функций для определения успешного парсинга (on_success)

3. Примеры парсеров

3.1. Последовательный парсинг

link:example1.cpp[role=include]
Output
sys::[{outdir}/example1]

3.2. Парсер файла формата ключ-значение

link:example2.cpp[role=include]
Output
sys::[{outdir}/example2]

3.3. Правила

link:example2.cpp[role=include]

4. Атрибуты

4.1. Получение результата парсинга

  • AST — удобное представление через ассоциативный рекурсивный массив

  • Атрибут — результат, который предоставляет конкретный парсер

  • Литералы не имеют атрибутов

  • Примитивные парсеры (int_, double_, …​) имеют примитивные по типу атрибуты (int, double, …​)

  • Нетерминалы вида x3::rule<ID, A> имеют атрибут A

Table 2. Атрибуты операторов
Оператор Его синтезируемый атрибут

a >> b

tuple<A, B>

a b

boost::variant<A, B>

*a, +a

std::vector<A>

-a

boost::optional<A>

&a, ~a

нет атрибута

a % b

std::vector<A>

4.2. Примеры

link:example3.cpp[role=include]
Output
sys::[{outdir}/example3]

4.3. Разбор с атрибутами примера ключ-значение

std::string_view text{R"(foo: bar,
    gorp : smart ,
    falcou : "crazy frenchman",
    name:sam)"};

auto name = x3::alpha >> *x3::alnum;
auto qoute = '"' >> x3::lexeme[*(~x3::char_('"'))] >> '"';
auto item = x3::rule<class item, std::pair<std::string, std::string>>>{}
          = name >> ':' >> (quote | name);

std::map<std::string, std::string> dict;
x3::phrase_parse(text.begin(), text.end(), item % ',', x3::space, dict);
a: char, b: vector<char> → (a >> b): tuple<char, vector<char>> → vector<char> → string
a: unused, b: vector<char>, c: unused → (a >> b >> c): vector<char> → string
a: string, b: string → (a | b): variant<string, string> → string
a: string, b: unused, c: string → (a >> b >> c): tuple<string, string>
a: pair<string, string>, b: unused → (a % b): vector<pair<string, string>> → map<string, string>

5. Построение грамматик

5.1. Что нужно для полного построения правила?

  1. Тип атрибута

  2. Идентификатор правила

  3. Тип правила

  4. Определение правила

  5. Само правило

struct my_type { ... };
struct my_rule_class;
const x3::rule<my_rule_class, my_type> my_rule_type = "my_rule";
const auto my_rule_def = x3::lexeme[(x3::alpha | '_') >> *(x3::alnum | '_')];
BOOST_SPIRIT_DEFINE(my_rule)

5.3. Что дальше

  • Рекурсивные типы через boost::forward_ast

  • Оператор строгого следования >

  • Явное прописывание генерируемого атрибута: "abc" > x3::attr(10)

  • И где здесь x3??

spiritstructure
Figure 1. Структура boost::spirit
spiritkarmaflow
Figure 2. Разница между qi и karma

6. Пример реального парсера

6.1. CSV

  • CSV — Comma Separated Values (значения, разделённые запятыми)

  • Элементарная таблица/база данных

  • Ещё бывает TSV (tabulated)

  • Формат хранения — текстовый, с расширение csv

6.2. Спецификация

  • CSV состоит из строк (первая строка считается заголовком)

  • Каждая строка заканчивается символом новая строка

  • Ячейки CSV разделяются запятой

  • Каждая ячейка может иметь пробелы и быть заключена в кавычки

  • Если нужно поставить запятую внутри ячейки, нужно обязательно использовать кавычки

link:csv.cpp[role=include]

6.3. Процесс парсинга

  • Пропуск всех пробельных символов

  • Использование x3::phrase_parse

  • Детектирование некорректного синтаксиса и наличие полного парсинга

link:csv.cpp[role=include]

6.4. Типы CSV

  • Ячейка CSV — это строка std::string

  • Каждая строка — это массив ячеек ⇒ std::vector<std::string>

  • Полное CSV — это массив массива ячеек ⇒ std::vector<std::vector<std::string>>

/*
link:csv.cpp[role=include]
 */
link:csv.cpp[role=include]

6.5. Грамматика CSV

  • Просто переписываем EBNF для CSV

  • Там, где компилятор не справляется с автовыведением типа, прописываем явно x3::rule

  • Добавляем дополнительную логику для экранирования символа двойной кавычки

/* EBNF:
link:csv.cpp[role=include]
 */
link:csv.cpp[role=include]

6.6. Полный пример с запуском

link:csv.cpp[role=include]
Output
sys::[{outdir}/csv]

6.7. Отступления от стандарта

  • В конце строки используется \n, а не \r\n

  • Используется символ \ для экранирования кавычек, а не парные двойные кавычки

  • Пустые ячейки не парсятся, например, empty,, должно выдавать 3 ячейки