Perl 6

Введение в грамматики 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

Программа работает так, как и задумывалась. Ввод пустой строки завершает цикл и одновременно с ним всю программу.

В качестве самостоятельного задания читателю предлагается поэксперементировать со вводом пробелов в разных местах строки и посмотреть на результат.

grammar, rule, token, regex — 8 августа 2010