Как сделать грамматику в Perl 6

от автора

Грамматика в программировании – это набор правил для разбора текста. Это очень полезная вещь – к примеру, грамматику можно использовать для проверки того, подчиняется ли строка текста конкретным стандартам или нет. У Perl 6 есть встроенная поддержка грамматик. Их настолько просто создавать, что единожды начав, вы обнаружите, что используете их везде.

В последнее время я работал над Module::Minter, простым приложением для создания базовой структуры модуля Perl 6. Мне надо было проверить, что предлагаемое имя модуля соответствует стандартам именования Perl 6.

Имена модулей – это идентификаторы, разделённые двумя двоеточиями. Идентификатор должен начинаться с алфавитного символа (a-z) или подчёркивания, за которым могут идти алфавитно-цифровые символы. Правда, у некоторых модулей может быть только один идентификатор, без двоеточий, а у других их может быть много (HTTP::Server::Async::Plugins::Router::Simple).

Определяем грамматику

В Perl 6 грамматики строятся на регулярках. Мне нужны две: одна для идентификаторов, другая – для разделителей в виде двоеточий. Для идентификаторов я задал:

<[A..Za..z_]> # начинается с буквы или подчёркивания <[A..Za..z0..9]> ** 0..* # ноль или больше алфавитно-цифровых 

Помните, что мы используем регулярки из Perl 6, и тут всё выглядит несколько по-другому. Класс символа определяется <[… ]>, а диапазон определяется оператором… вместо тире. Эта регулярка совпадает с любой первой буквой или подчёркиванием, за которым идёт ноль или более алфавитно-цифровых символов.

С двумя двоеточиями всё проще:

\:\: # пары двоеточий 

Грамматики определяют при помощи ключевого слова grammar, за которым идёт название. Назову-ка я эту грамматику Legal::Module::Name

grammar Legal::Module::Name {   ... } 

Теперь можно добавлять в неё токены-регулярки:

grammar Legal::Module::Name {   token identifier   {     # первый символ -  буква или _      <[A..Za..z_]>     <[A..Za..z0..9]> ** 0..*   }    token separator   {     \:\: # пары двоеточий   } } 

Каждой грамматике нужно задать токен TOP, который обозначает её начало.

grammar Legal::Module::Name {   token TOP   { # идентификатор, за которым идёт ноль или более пар separator - identifier      ^  [] ** 0..* $   }   token identifier   {     # первый символ -  буква или _      <[A..Za..z_]>     <[A..Za..z0..9]> ** 0..*   }    token separator   {     \:\: # пары двоеточий   } } 

Токен TOP определяет, что разрешённое имя модуля начинается с токена identifier, за которым идут ноль или больше пар токенов separator и identifier. Поддерживать такую штуку очень просто – если б я захотел изменить правила так, чтобы разделители содержали тире, я бы обновил регулярку только в одном токене.

Использование грамматики

Метод parse прогоняет грамматику на строке, и в случае успеха возвращает объект match. Следующий код обрабатывает строку $proposed_module_name, и либо выводит объект match, либо сообщение об ошибке.

my $proposed_module_name = 'Super::New::Module'; my $match_obj = Legal::Module::Name.parse($proposed_module_name);  if $match_obj {     say $match_obj; } else {     say 'Да что ж это за имя модуля-то такое, а?!'; }  Вывод:  「Super::New::Module」  identifier => 「Super」  separator => 「::」  identifier => 「New」  separator => 「::」  identifier => 「Module」 

Извлекаем содержимое объекта match

Можно не вываливать всё содержимое объекта match, а извлечь сыгравшие токены. В следующем коде используются именованные регулярки и ключи хэшей.

say $match_obj[0].Str; # Super say $match_obj[1].Str; # New say $match_obj[2].Str; # Module  say $match_obj; # все три 

Action Classes (классы действий)

Perl 6 даёт возможность добавить класс действий, определяющий дополнительное поведение для сыгравших токенов. Допустим, я хочу добавить предупреждение на случай, если в имени модуля содержится слишком много идентификаторов. Сначала я задам класс действий:

class Module::Name::Actions {   method TOP($/)   {     if $.elems > 5     {       warn 'В имени модуля слишком много идентификаторов – может, укоротишь?.. ';     }   } } 

Обычное такое определение класса в Perl 6. Я добавил метод TOP, совпадающий с первым токеном грамматики. Затем я подсчитываю количество совпадений, и если их больше 5, выдаю предупреждение. Выполнение оно не прерывает, но даёт понять пользователю о том, что стоит задуматься над переименованием модуля.

Затем инициализируем класс действий и передадим его в parse как аргумент:

my $actions = Module::Name::Actions.new;  my $match_obj = Legal-Module-Name.parse($proposed_module_name, :actions($actions)); 

Грамматика вызывает соответствующий метод класса действий каждый раз, когда при парсинге встретится подходящий токен. В нашем случае это произойдёт один раз во время парсинга.

Грамматики в Perl 5

И в Perl 5 можно делать грамматики. Для схожего с Perl 6 решения можно посмотреть в сторону Regexp::Grammars или Ingy Döt Net’s Pegex. Отличные реализации можно посмотреть в главе 1 "Mastering Perl" от brian d foy, где содержится пример грамматики для JSON.

ссылка на оригинал статьи http://habrahabr.ru/post/263965/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *