Римские цифры

  1. Таблица символов
  2. правила
  3. грамматики
  4. Давайте разберемся!

Этот пример демонстрирует:

  • таблица символов
  • правило
  • грамматика
Таблица символов

Таблица символов содержит словарь символов, где каждый символ представляет собой последовательность символов (char, wchar_t, int, перечисление и т. Д.). Класс шаблона, параметризованный типом символа, может эффективно работать с 8, 16, 32 и даже 64-битными символами. Изменяемые данные типа T связаны с каждым символом.

Традиционно управление таблицей символов поддерживается отдельно вне грамматики BNF посредством семантических действий. Вопреки стандартной практике символы класса таблицы символов Spirit являются синтаксическим анализатором. Объект, который может использоваться где угодно в спецификации грамматики EBNF. Это пример динамического парсера. Динамический синтаксический анализатор характеризуется своей способностью изменять свое поведение во время выполнения. Первоначально, пустой объект символов ничего не соответствует. В любое время символы могут быть добавлены или удалены, таким образом, динамически изменяя его поведение.

Каждая запись в таблице символов имеет связанный слот изменяемых данных. В связи с этим можно рассматривать таблицу символов как ассоциативный контейнер (или карту) пар ключ-значение, где ключи являются строками.

Класс символов ожидает два параметра шаблона. Первый параметр указывает тип символа символов. Второй указывает тип данных, связанный с каждым символом: его атрибут.

Вот парсер для римских сотен (100..900), использующий таблицу символов. Помните, что данные, связанные с каждым слотом, являются атрибутом синтаксического анализатора (который передается приложенным семантическим действиям).

struct сотни_: qi :: symbols <char, unsigned> {сотни_ () {add ("C", 100) ("CC", 200) ("CCC", 300) ("CD", 400) ("D" 500) («DC», 600) («DCC», 700) («DCCC», 800) («CM», 900); }} сотни;

Вот парсер для римских десятков (10..90):

struct ten_: qi :: symbols <char, unsigned> {ten_ () {add ("X", 10) ("XX", 20) ("XXX", 30) ("XL", 40) ("L" , 50) ("LX", 60) ("LXX", 70) ("LXXX", 80) ("XC", 90); }} десятки;

и, наконец, для тех (1..9):

struct ones_: qi :: symbols <char, unsigned> {ones_ () {add ("I", 1) ("II", 2) ("III", 3) ("IV", 4) ("V" , 5) («VI», 6) («VII», 7) («VIII», 8) («IX», 9); }} из них;

Теперь мы можем использовать сотни, десятки и единицы где угодно в наших выражениях парсера. Все они парсеры.

правила

До сих пор мы вставляли выражения парсера, передавая их непосредственно в функцию фразу_парса. Выражение превращается во временный безымянный синтаксический анализатор, который передается в функцию фразу_parse, используется, а затем уничтожается. Это хорошо для небольших парсеров. Когда выражения усложняются, вы хотите разбить выражения на более мелкие, более простые для понимания части, назвать их и ссылаться на них из других выражений синтаксического анализатора по имени.

Выражение парсера может быть назначено так называемому «правилу». Существуют различные способы объявления правил. Самая простая форма:

rule <Iterator> r;

Как минимум, правилу нужно знать тип итератора, с которым оно будет работать. Это правило нельзя использовать с фразу_parse. Он может использоваться только с функцией разбора - версия, которая не пропускает пробелы (не имеет аргумента шкипера). Если вы хотите, чтобы он пропускал пробелы, вам нужно передать парсер пропуска типа, как в следующей форме:

правило <Iterator, Skipper> r;

Пример:

rule <std :: string :: iterator, space_type> r;

Этот тип правила может использоваться как для фразы_парсета, так и для анализа.

Для нашего следующего примера есть еще одна форма правила, о которой вы должны знать:

rule <Итератор, Подпись> r;

или же

rule <Итератор, Подпись, Шкипер> r; Совет

Все аргументы шаблона правила после Iterator могут быть предоставлены в любом порядке.

Подпись определяет атрибуты правила. Вы видели, что наши парсеры могут иметь атрибут. Напомним, что парсер double_ имеет атрибут double. Чтобы быть точным, это синтезированные атрибуты. Парсер "синтезирует" значение атрибута. Думайте о них как о функциях, возвращающих значения.

Есть еще один тип атрибута, называемый «унаследованным» атрибутом. Пока они нам не понадобятся, но хорошо, что вы знаете о таких атрибутах. Вы можете думать о них как о аргументах функции. И, правильно, подпись правила является сигнатурой функции вида:

результат (argN, argN, ..., argN)

После объявления правила вы можете назначить ему любое выражение синтаксического анализатора. Пример:

r = double_ >> * (',' >> double_);
грамматики

Грамматика инкапсулирует одно или несколько правил. Он имеет те же параметры шаблона, что и правило. Вы объявляете грамматику:

  1. получение структуры (или класса) из шаблона класса грамматики
  2. объявить одно или несколько правил в качестве переменных-членов
  3. инициализировать базовый класс грамматики, задав ему правило запуска (первое правило, которое вызывается, когда грамматика начинает анализ)
  4. инициализировать ваши правила в вашем конструкторе

Римская цифровая грамматика - очень хороший и простой пример грамматики:

template <typename Iterator> struct roman: qi :: grammar <Iterator, unsigned ()> {roman (): roman :: base_type (start) {using qi :: eps; используя qi :: lit; используя qi :: _val; используя qi :: _1; используя ascii :: char_; start = eps [_val = 0] >> (+ горит ('M') [_val + = 1000] || сотни [_val + = _1] || десятки [_val + = _1] || единицы [_val + = _1 ]); } qi :: rule <Iterator, unsigned ()> start; };

На что обратить внимание:

  • Подпись грамматики и правила запуска - unsigned (). Он имеет синтезированный атрибут (возвращаемое значение) типа unsigned без унаследованных атрибутов (аргументов).
  • Мы не указали скип-парсер. Мы не хотим переходить между цифрами.
  • roman :: base_type - это typedef для грамматики <Iterator, unsigned ()>. Если роман не был шаблоном, вы могли бы просто написать: base_type (start)
  • Лучше сделать ваши грамматические шаблоны такими, чтобы их можно было повторно использовать для разных типов итераторов.
  • _val другой Феникс заполнитель, представляющий синтезированный атрибут правила.
  • eps - это специальный анализатор духа, который не потребляет ввода, но всегда успешен. Мы используем его для инициализации _val, синтезированного атрибута правила, равным нулю, прежде чем что-либо еще. Фактический парсер начинается с + char_ ('M'), анализируя римские тысячи. Использование eps таким образом хорошо для предварительной и последующей инициализации.
  • Выражение a || b читает: соответствует a или b и в последовательности. То есть, если и a, и b совпадают, он должен быть в последовательности; это эквивалентно >> - b | б, но более эффективно.
Давайте разберемся!
bool r = parse (iter, end, roman_parser, result); if (r && iter == end) {std :: cout << "------------------------- \ n"; std :: cout << "Синтаксический анализ завершен успешно \ n"; std :: cout << "result =" << result << std :: endl; std :: cout << "------------------------- \ n"; } else {std :: string rest (iter, end); std :: cout << "------------------------- \ n"; std :: cout << "Сбой синтаксического анализа \ n"; std :: cout << "остановился на: \": "<< rest <<" \ "\ n"; std :: cout << "------------------------- \ n"; }

roman_parser - это объект типа roman, наш анализатор римских цифр. На этот раз мы используем не пропускающую версию функций разбора. Мы не хотим пропускать пробелы! Мы также передаем атрибут unsigned result, который получит проанализированное значение.

Полный файл cpp для этого примера можно найти здесь: ../../example/qi/roman.cpp