31. Грамматики в Perl 6, часть 1. Разбор чисел

Грамматики (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. Разбор чисел”

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

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