Введение в грамматики Perl 6. Предисловие и часть I
Предисловие
В Perl 6 появились не только новые регулярные выражения, но и более мощный встроенный в язык инструмент — грамматики.
Сразу нужно оговориться, что регулярные выражения Perl 6 официально называются регексами (regex) — теперь это не разговорное сокращение, а полное название. В контексте грамматик более употребительными окажутся термины правило и токен.
В этой серии публикаций речь будет идти о том, как создать простую грамматику для обработки запросов типа «20 EUR in USD». Регексы, которые потребуется по ходу дела, будут прокомментированы по ходу же дела.
Грамматики Perl 6 можно рассматривать как классы, члены и методы которых являются не функциями, а правилами и токенами, внутри которых записаны регексы, которые, собственно, и будут в нужном порядке применяться к исходным данным.
Еще пара моментов, о которых нужно знать с самого начала. В новых регексах все символы, кроме букв, цифр и подчеркивания считаются метасимволами. Это сильно упрощает ответ на вопрос, какие символы и когда нужно экранировать. Чтобы получить литеральный символ, который не является ни буквой, ни цифрой, ни символом подчеркивания, его нужно экранировать обратным слешем либо помещать в кавычки.
Второй момент — результат сопоставления строки с регексом (или разбор в соответствии с грамматикой) всегда возвращает объект типа Match, обычно доступный в переменной $/.
Часть I. Первый тест
Создадим небольшой пример, который поможет понять, что к чему и перейти к основной задаче. В этом примере созданная программа покажет приглашение ко вводу и станет ожидать целое число (со знаком или нет). Правильность ввода проверяется с помощью грамматики.
use v6;
grammar TestGrammar {
rule TOP {
^ <sign>? <digit>+ $
}
token sign {
'-' | '+'
}
token digit {
<[0..9]>
}
}
while my $string = prompt('> ') {
if TestGrammar.parse($string) {
say "OK";
}
else {
say "Failed";
}
}
Начнем с описания грамматики. Как видно из кода, синтаксис похож на тот, что используется для создания классов, однако требует ключевого слова grammar, за которым следуют название и блок с определением:
grammar TestGrammar {
. . .
}
Внутри блока находятся несколько вложенных блоков, предваренных ключевыми словами rule и token. По сути это именованные регулярные выражения, которые и составляют грамматику.
Отличия между rule и token только в том, что для токенов не действует откат. То есть токен должен совпасть максимально длинно, и если он захватил символы до полного совпадения, то попытки переосмыслить часть совпавших символов не произойдет. Кроме того, в пределах правила и токена по-разному обрабатываются пробелы на входе — токен должен быть единой последовательностью, в точности описанной в грамматике, в правиле же допускаются пробелы между его частями.
Первое правило, которое стоит в начале процесса разбора, должно называться TOP.
rule TOP {
^ <sign>? <digit>+ $
}
Внутри фигурных скобок — регекс, в котором помимо метасимволов в угловых скобках упоминаются другие правила.
Метасимволы ^ и $ привязывают правило, соответственно, к началу и концу строки. Перевод строки, если он был, совпадет с $. А если не было, $ все равно успешно совпадет с концом строки.
Имена в угловых скобках — токены, которые определены далее в грамматике. После каждого из них в правиле TOP стоят метасимволы, определяющие, сколько раз токены могут встретиться. Токен sign (после которого стоит ?) должен совпасть либо один раз, либо отсутствовать в строке. Токен digit (после него стоит +) может многократно повторяться, но обязан присутствовать хотя бы один раз.
Токен sign описывает, какие символы могут быть использованы для обозначения знака числа. Соответствующие символы записаны через разделитель |, который предназначен для описания альтернативных вариантов. Обратите внимание, что и плюс, и минус взяты в кавычки ровно по той причине, что без них они становятся метасимволами, поскольку не являются ни буквами, ни цифрами, ни символом подчеркивания.
token sign {
'-' | '+'
}
Наконец, токен digit описывает, что такое цифра. В нашем определении цифра — символ из класса <[0..9]>. Синтаксис для записи символьных классов — квадратные скобки внутри угловых (а угловые скобки, как видно из предыдущих примеров, используются для того, чтобы упомянуть одно правило внутри другого). Синтаксис для диапазона символов (две точки) совпадает с тем, что используется в самом Perl 6.
token digit {
<[0..9]>
}
Итак, тестовая грамматика описывает строки, в которых записаны целые числа со знаком или без: в начале строки может стоять необязательный знак, за которым следуют одна или несколько цифр.
Оставшаяся часть программы организует бесконечный цикл, в котором запрашивается строка и делается попытка разобрать ее с помощью грамматики.
while my $string = prompt('> ') {
if TestGrammar.parse($string) {
say "OK";
}
else {
say "Failed";
}
}
Цикл while выполняет блок кода до тех пор, пока истинно его условие (в Perl 6 скобки для условия не нужны, хотя по-прежнему нужны для блока кода). В нашем случае условие содержит и объявление переменной my $string, и вызов функции prompt('> ').
Функция prompt выводит на печать свой аргумент — строку '> ' — и ожидает, пока пользователь не введет строку и нажмет Enter. Полученный текст попадает в скаляр $string.
Далее строка передается методу .parse тестовой грамматики, и результат разбора проверяется в условии if (скобки для условия не нужны и здесь):
if TestGrammar.parse($string) {
. . .
}
else {
. . .
}
В зависимости от исхода анализа строки выводится сообщение «OK» или «Failed».
Запускаем программу и пробуем ввести разные строки:
perl6 test.pl> 42 OK > -42 OK > NaN Failed > +36 OK > 36.6 Failed
Программа работает так, как и задумывалась. Ввод пустой строки завершает цикл и одновременно с ним всю программу.
В качестве самостоятельного задания читателю предлагается поэксперементировать со вводом пробелов в разных местах строки и посмотреть на результат.