Грамматики (grammars) в Perl 6 — огромная бесконечная тема, не имеющая аналогов в других языках программирования. Эта часть языка отлично проработана и используется для парсинга самого Perl 6.
Я планирую возвращаться к этой теме время от времени, а сегодня мы начнем с разбора чисел. Разумеется, это можно сделать с помощью регулярных выражений, но и грамматики отлично подойдут.
Для начала создадим набор тестовых данных:
my @tests = <
1
-1
+1
123
-123
1.2
-1.2
10000000
-10000000
.23
-.23
1e2
1E2
1e-2
1e+2
-1E-2
1.2E3
.2E3
-.2E3
>;
Начнем писать грамматику с простейшего случая, когда все число является лишь последовательностью цифр:
grammar Number {
token TOP {
<number>
}
token number {
<digit>+
}
}
Грамматика начинается с главного токена TOP, который должен совпасть со всей строкой целиком. В данном случае этот токен содержит только токен number, который является быть последовательностью цифр. Правило digit встроено в язык.
Пройдемся по тестовым строкам и разберем их с помощью существующей грамматики:
for @tests -> $value {
my $result = Number.parse($value);
my $check = $result ?? '✓' !! '✗';
say "$check $value";
}
Запускаем программу и смотрим на результаты:
✓ 1 ✗ -1 ✗ +1 ✓ 123 ✗ -123 ✗ 1.2 ✗ -1.2 ✓ 10000000 ✗ -10000000 ✗ .23 ✗ -.23 ✗ 1e2 ✗ 1E2 ✗ 1e-2 ✗ 1e+2 ✗ -1E-2 ✗ 1.2E3 ✗ .2E3 ✗ -.2E3
Есть еще над чем поработать. Начнем с добавления необязательного знака:
grammar Number {
token TOP {
<number>
}
token number {
<sign>?
<digit>+
}
token sign {
'+' | '-'
}
}
Число успешных тестов немного увеличилось:
✓ 1 ✓ -1 ✓ +1 ✓ 123 ✓ -123 ✗ 1.2 ✗ -1.2 ✓ 10000000 ✓ -10000000 ✗ .23 ✗ -.23 ✗ 1e2 ✗ 1E2 ✗ 1e-2 ✗ 1e+2 ✗ -1E-2 ✗ 1.2E3 ✗ .2E3 ✗ -.2E3
Теперь можно добавить десятичную дробь и разделить целую и дробные части числа на отдельные токены. Само число будет собираться из этих частей, каждая из которых необязательна.
grammar Number {
token TOP {
<sign>?
<number>
}
token number {
| <comma> <fractional>
| <integer> <comma> <fractional>
| <integer> <comma>
| <integer>
}
token sign {
'+' | '-'
}
token integer {
<digit>+
}
token fractional {
<digit>+
}
token comma {
'.'
}
}
Здесь я создал несколько альтернатив, чтобы не путаться с модификаторами у отдельных частей, и заодно перенес знак в стартовый токен. Одновременно стало видно, что не хватает тестов для редких, но допустимых случаев, когда у числа есть точка, но нет дробной части. Проверяем:
✓ 1 ✓ -1 ✓ +1 ✓ 123 ✓ -123 ✓ 1.2 ✓ -1.2 ✓ 10000000 ✓ -10000000 ✓ .23 ✓ -.23 ✗ 1e2 ✗ 1E2 ✗ 1e-2 ✗ 1e+2 ✗ -1E-2 ✗ 1.2E3 ✗ .2E3 ✗ -.2E3 ✓ 1. ✓ -2.
Отлично. Добавляем правила для разбора научной записи:
grammar Number {
token TOP {
<sign>?
<number>
[
['e' | 'E'] <sign>? <integer>
]?
}
token number {
| <comma> <fractional>
| <integer> <comma> <fractional>
| <integer> <comma>
| <integer>
}
token sign {
'+' | '-'
}
token integer {
<digit>+
}
token fractional {
<digit>+
}
token comma {
'.'
}
}
Проверка показывает, что все тесты успешно проходят:
✓ 1 ✓ -1 ✓ +1 ✓ 123 ✓ -123 ✓ 1.2 ✓ -1.2 ✓ 10000000 ✓ -10000000 ✓ .23 ✓ -.23 ✓ 1e2 ✓ 1E2 ✓ 1e-2 ✓ 1e+2 ✓ -1E-2 ✓ 1.2E3 ✓ .2E3 ✓ -.2E3 ✓ 1. ✓ -2.
На сегодня это все. Созданная грамматика смогла разобрать все запланированные варианты. План на следующий раз — дополнить грамматику действиями (actions), чтобы разобранную строку превратить в полноценное число.
One thought on “31. Грамматики в Perl 6, часть 1. Разбор чисел”