65. Модификаторы (adverbs) в регексах Perl 6, часть 1

В регексах Perl 6 по-прежнему есть модификаторы, однако они теперь выглядят и называются иначе. Называются они теперь adverbs (наречия) и ставятся после двоеточия не после, а до регекса.

Adverbs имеют короткое и длинное имена. Давайте посмотрим на имеющиеся предложения. Все приведенные ниже примеры показаны как часть диалога в режиме REPL.

:i или :ignorecase

Игнорирование регистра букв.

> 'Moscow' ~~ m:i/moscow/
「Moscow」
> 'Moscow' ~~ m/moscow/
False

:s или :sigspace

Включение режима значимости пробелов. В целом этот флаг противоположен по действию модификатору /x из Perl 5. По умолчанию пробелы в регексе считаются незначимыми.

> 'alpha beta' ~~ / a\S+ b\S+ /
Nil

> 'alpha beta' ~~ / a\S+ ' ' b\S+ /
「alpha beta」

> 'alpha beta' ~~ m:s/ a\S+ b\S+ /
「alpha beta」

:g или :global

Искать глобально (но без пересечения) и возвращать несколько результатов.

> 'text' ~~ /\w/
「t」

> 'text' ~~ m:g/\w/
(「t」 「e」 「x」 「t」)

 

64. Тернарный оператор в Perl 6

В Perl 6 по-прежнему присутствует тернарный оператор, но выглядит он теперь иначе:

my $var = -10;
my $abs = $var < 0 ?? -$var !! $var;
say $abs;

Остальное все понятно — оператор делает ровно то же, что и ? ! в Perl 5. Однако, при попытке написать по-старому, вы получите сообщение об ошибке:

$ perl6 ternary.pl 
===SORRY!=== Error while compiling /Users/ash/ternary.pl
Unsupported use of ? and : for the ternary conditional operator;
in Perl 6 please use ?? and !!
at /Users/ash/ternary.pl:2
------> my $abs = $var < 0 ?⏏ -$var : $var;

Интересно, что в самых первых реализациях Perl 6 тернарный оператор выглядел как ?? ::. На этот случай тоже предусмотрено сообщение:

$ perl6 ternary.pl 
===SORRY!=== Error while compiling /Users/ash/ternary.pl
Please use !! rather than ::
at /Users/ash/ternary.pl:2
------> my $abs = $var < 0 ?? -$var :⏏: $var;
 expecting any of:
 colon pair

 

63. Тип данных enum в Perl 6

В Perl 6 есть тип данных для создания перечислений — enum. Его использовать предельно просто:

enum colours <red yellow green>;

Так мы создали тип и три значения, которые теперь доступны как константы, но без сигилов:

say red;
say green;

На печати появятся названия как они есть:

red
green

Если необходимы числовые значения, используйте метод Int:

say red.Int;    # 0
say yellow.Int; # 1
say green.Int;  # 2

По умолчанию отсчет ведется с нуля, но это можно изменить, указав нужное значение первому элементу перечисления:

enum colours (red => 10, 'yellow', 'green');

say red;        # red
say green;      # green
say yellow;     # yellow

say red.Int;    # 10
say yellow.Int; # 11
say green.Int;  # 12

Обратите внимание, что Perl 6 понял, что первое значение — это пара red => 10, а дальше идут две строки, которые он не попытался объединить в пару yellow => 'green'.

Если необходимо, то можно сэкономить на кавычках, но при этом использовать чуть иные цитирующие кавычки:

enum colours << :red(10) yellow green >>;

Разумеется, можно присвоить свои значения каждому элементу:

enum colours (red => 10, yellow => 20, green => 30);

say red.Int;    # 10
say yellow.Int; # 20
say green.Int;  # 30

 

62. Установка таймаутов в Perl 6

В Perl 5 таймауты устанавливали через сигналы (по крайней мере в моей практике, это был самый понятный способ). В Perl 6 для таймаутов можно воспользоваться промисами.

Давайте запустим бесконечный цикл и попытаемся завершить программу через две секунды.

Вот цикл. Время от времени он выводит счетчик на печать.

for 1 .. * {
    .say if $_ %% 100_000;
}

Получив управление, этот цикл его никогда не отдаст. Поэтому, разумеется, таймер надо поставить до того, как мы дойдем до цикла:

Promise.in(2).then({
    exit;
});

for 1 .. * {
    .say if $_ %% 100_000;
}

Метод Promise.in создает промис, который будет сдержан через заданное число секунд. На этот промис мы — с помощью метода then — добавляем другой промис, который будет сразу выполнен по окончанию первого. Тело промиса — вызов exit.

Запускаем и смотрим:

$ time perl6 timeout.pl
100000
200000
300000
. . .
3700000
3800000
3900000

real 0m2.196s
user 0m2.120s
sys 0m0.068s

Программа успела досчитать до четырех миллионов и завершилась через две секунды.

Для сравнения — программа на Perl 5:

use v5.10;

alarm 2;
$SIG{ALRM} = sub {
    exit;
};

for (my $c = 1; ; $c++) {
    say $c unless $c % 1_000_000;
}

(За две секунды она успевает досчитать до 40 миллионов на том же компьютере, но это другая история.)

61. Метод IO в Perl 6

Работа с файлами в Perl 6 будет очень простой, если знать и пользоваться методом IO, который можно вызывать, например, на строках:

'file.txt'.IO

Метод возвращает объект типа IO::Path, в котором есть методы для работы с файлами. Вот несколько полезных примеров:

Чтение всего содержимого файла в переменную:

my $content = 'file.txt'.IO.slurp;
say $content;

Прочитать файл построчно и сохранить все строки в массиве:

my @lines = 'file.txt'.IO.lines;
dd @lines; # Array @lines = ["Hello", "World", "", "After empty line", "End"]

Проверка наличия файла:

say 'OK' if 'file.txt'.IO.e;

Является ли путь каталогом?

say 'is dir' if '..'.IO.d;

Выделить путь, название и расширение:

my $f = '/Users/ash/file.txt'.IO;
say $f.dirname;   # /Users/ash
say $f.basename;  # file.txt
say $f.extension; # txt

 

60. Модуль HTTP::UserAgent в Perl 6

Давайте посмотрим, «как скачать страницу из интернета» на Perl 6. Один из удобных способов — воспользоваться модулем HTTP::UserAgent, чей интерфейс очень похож на тот, что был в Perl 5.

Установка модуля с помощью утилиты zef:

$ zef install HTTP::UserAgent

Есть смысл сразу установить поддержку SSL, чтобы иметь возможность ходить на https-адреса:

$ zef install IO::Socket::SSL

Теперь нам доступен весь мир. Пишем программу, которая будет загружать и печать код указанной страницы:

use HTTP::UserAgent;

sub MAIN($url) {
    my $ua = HTTP::UserAgent.new;
    my $response = $ua.get($url);

    say $response.status-line;
    say $response.content if $response.is-success;
}

Обратите внимание, что автор модуля использует в именах методов дефисы, а не символы подчеркивания: status-line, is-success.

Пробуем:

$ perl6 ua.pl https://perl6.ru/content | less

200 OK
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="profile" href="http://gmpg.org/xfn/11">

<title>Содержание – Вечерний Perl 6</title>

 

59. Действия (actions) в грамматиках, часть 3

Сегодня — заключительная часть рассказа о грамматике для разбора даты. В прошлый раз мы добавили класс с действиями, теперь ради упражнения добавим в этот класс атрибуты (то, что в других языках называют данными класса).

Заодно чуть усложним грамматику и научим ее парсить даты, записанные в виде 28.02.18:

token TOP {
    | <year> <sep> <month> <sep> <day>
    | <day>  <sep> <month> <sep> <year>
    | <n>    <sep> <month> <sep> <nn>
 }

. . .

token n {
    \d ** 1..2
}
token nn {
    \d ** 2
}

Класс действий теперь должен уметь различать ситуации, когда дата записана с полным годом, а когда год представлен только двумя цифрами.

Дату будем хранить в хеше %date, что немного избыточно для этой задачи, но хорошо иллюстрирует, как действовать, когда акции требуют сохранения промежуточных данных.

class DateStrActions {
    has %date;

    method TOP($/) {
        if %date<nn> {
            if %date<nn> > 35 {
                %date<year> = 1900 + %date<nn>;
            }
            else {
                %date<year> = 2000 + %date<nn>;
            }
            %date<day> = %date<n>;
        }
        printf "--> %4i-%02i-%02i\n", 
               %date<year>, %date<month>, %date<day>
    }
    method year($/) {
        %date<year> = ~$/;
    }
    method month($/) {
        %date<month> = ~$/;
    }
    method day($/) {
        %date<day> = ~$/;
    }
    method nn($/) {
        %date<nn> = ~$/;
    }
    method n($/) {
        %date<n> = ~$/;
    }
}

Здесь действия должны проявить сообразительность, чтобы правильно оценить, к какому столетию относится год.

Теперь самое важное. Поскольку в классе появился атрибут, компилятору потребуется выделить под него место. Для этого от вас требуется создать объект, а не просто передать имя класса:

my $r = DateStr.parse($t, :actions(DateStrActions.new()));

Тестируем код на разных датах:

$ perl6 actions2.pl
2018-02-26 --> 2018-02-26
2019-3-27  --> 2019-03-27
28.04.2020 --> 2020-04-28
30.05.21   --> 2021-05-30
15.06.99   --> 1999-06-15

Полный код вы этого варианта программы можете найти на гитхабе.