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

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

58. Действия (actions) в грамматиках Perl 6, часть 2

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

class DateStrActions {
    method TOP($/) {
        printf "--> %4i-%02i-%02i\n", $<year>, $<month>, $<day>
    }
}

Методы этого класса должны совпадать с названиями правил и токенов в грамматике. Из вчерашнего примера я убрал код из токена TOP грамматики DateStr и поместил его в метод TOP класса DateStrActions.

Теперь необходимо сообщить о действиях перед парсингом:

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

Все остальное остается неизменным. Программа печатает тот же результат:

2018-02-26 --> 2018-02-26
2018-02-26 --> 2018-02-26
2018-2-26  --> 2018-02-26
26.02.2018 --> 2018-02-26

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

57. Действия (actions) в грамматиках Perl 6, часть 1

Мы уже видели, как сделать грамматику для разбора чисел. Но сами по себе грамматики дают лишь ответ, удовлетворяет ли строка заданным правилам. Обычно еще требуется что-то сделать с имеющимися данными. Для этого к грамматике надо добавить действия (actions).

Рассмотрим простейшее действие на примере грамматики для разбора даты.

grammar DateStr {
    token TOP {
        [
        | <year> <sep> <month> <sep> <day>
        | <day>  <sep> <month> <sep> <year>
        ] {
            printf "--> %4i-%02i-%02i\n", 
                   $<year>, $<month>, $<day>
        }
    }
    token year {
        \d ** 4
    }
    token month {
        \d ** 1..2
    }
    token day {
        \d ** 1..2
    }
    token sep {
        <[-./]>
    }
}

my @tests = <
    2018-02-26
    2018-2-26
    26.02.2018
>;

for @tests -> $t {    
    print "$t\t";
    my $r = DateStr.parse($t);
}

Грамматика разрешает один из двух видов формата даты: YYYY-MM-DD или DD-MM-YYYY. Разделителем может быть дефис или точка.

Как только найдено соответствие, Perl 6 выполняет блок кода — в нашем примере он выделен цветом.

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

Внутри действия совпавшие части доступны как элементы объекта $/, например, $<year>.

Программа успешно разбирает все три примера и печатает даты в едином формате:

2018-02-26 --> 2018-02-26
2018-02-26 --> 2018-02-26
2018-2-26  --> 2018-02-26
26.02.2018 --> 2018-02-26

56. Что такое gist в Perl 6

Когда вы пробуете печатать объект, например: say $x, Perl 6 вызывает метод gist. Этот метод определен для всех встроенных типов — где-то он вызывает метод Str, где-то perl, а где-то формирует особое представление.

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

class X {
    has $.value;

    method gist {
        '[' ~ $!value ~ ']'
    }
}

my $x = X.new(value => 42);

say $x; # [42]
$x.say; # [42]

При обращении к say программа печатает число в квадратных скобках: [42].

Обратите внимание, что при интерполяции в строке, заключенной в двойные кавычки, вызывается другой метод — Str:

say $x.Str; # X<140586830040512>
say "$x";   # X<140586830040512>

Если вам требуется кастомная интерполяция, переопределяйте и метод Str:

class X {
    has $.value;

    method gist {
        '[' ~ $!value ~ ']'
    }
    method Str {
        '"' ~ $!value ~ '"'
    }
}

my $x = X.new(value => 42);

say $x;     # [42]
$x.say;     # [42]

say $x.Str; # "42"
say "$x";   # "42"

 

55. Perl 6 на Raspberry Pi и другие дистрибутивы

Вчера я пробовал установить Rakudo Perl 6 на Orange Pi. Сегодня пробуем Raspberry Pi (третьей версии).

Raspberry Pi

На этот раз все получилось. Я скачал последние исходники Rakudo Star и действовал по инструкции:

perl Configure.pl --backend=moar --gen-moar
make
make install

Компилировалось все безумно долго, но при этом Raspberry была доступна и могла показать загрузку процессора. В общей сложности процесс занял часа полтора-два, но часть этого времени — на то, чтобы вспомнить, что у меня там что-то компилируется.

Вот выдержка из лога:

Stage start      :   0.000
Stage parse      : 767.004
Stage syntaxcheck:   0.000
Stage ast        :   0.000
Stage optimize   : 107.122
Stage mast       : 198.105
Stage mbc        :   8.091

На обычном ноутбуке таких цифр вы не увидите, там все компилируется за минуту-две.

Довольно долгим был и процесс после make install. Видимо, если ставить не Rakudo Star, а просто Rakudo, то все пройдет быстрее, потому что для Rakudo Star устанавливаются и тестируются дополнительные модули.

Но тем не менее, хоп-хоп, запускаем (не дожидаясь окончания действий с модулями):

pi@rpi:~/rakudo-star-2018.01 $ ./perl6 -v
This is Rakudo Star version 2018.01 built on MoarVM version 2018.01
implementing Perl 6.c.

Для других ОС

На официальном сайте есть ссылка на гитхаб, где собраны готовые дистрибутивы для многих платформ:

  • Alpine 3.6 x86_64
  • Alpine 3.7 x86_64
  • Centos 7 x86_64
  • Debian 8 amd64
  • Debian 9 amd64
  • Fedora 25 x86_64
  • Fedora 26 x86_64
  • Fedora 27 x86_64
  • openSUSE 42.3 x86_64
  • Ubuntu 14.04 amd64
  • Ubuntu 16.04 amd64
  • Ubuntu 17.10 amd64
  • Ubuntu 16.04 i386
  • Ubuntu 17.10 i386

 

54. Perl 6 на Orange Pi

Сегодня мы выйдем за пределы одного чистого программирования и возьмем в руки паяльник. Задача на вечер — помигать светодиодом на Orange Pi.

Orange Pi — это одноплатный мини-компьютер, который, видимо, появился как конкурент Raspberry Pi, при этом и меньше размером, и дешевле. За 15 евро вы получаете четырехядерный компьютер размером со спичечный коробок, куда можно поставить Линукс. Ну не чудо ли? Да еще и c Wi-Fi.

Почти все сказанное ниже применимо, разумеется, и к Raspberry Pi.

Для экспериментов я установил дистрибутив ARMBIAN Debian 9, Rakudo Perl 6 и WiringOP (ну еще пришлось припаять планку с контактами).

WiringOP — это библиотека и утилиты для работы с портами ввода-вывода. Устанавливается просто:

git clone https://github.com/zhaolei/WiringOP.git -b h3
 
cd WiringOP
chmod +x ./build
sudo ./build

С перлом же пришлось повозиться. Я хотел скомпилировать свежий Rakudo Star и начал делать все по инструкции. Началось все замечательно, но потом намертво встало, нагрев процессор до 60-70 градусов. При этом невозможно было достучаться до компьютера по SSH, хотя видно было, что оно просто дико тормозит, но работает.

В итоге я не дождался, пока все соберется, и пошел искать готовые порты. Нашлись два имени — perl6 и rakudo — и это дистрибутив версии 2016.12.

# apt-get install perl6

Конечно, декабрь 2016 это уже очень старое, но при этом версия языка 6.c, что в целом нормально: эта текущая версия спецификации Perl 6.

This is Rakudo version 2016.12 built on MoarVM version 2016.12
implementing Perl 6.c.

Теперь пишем программу на Perl 6:

my $pin = 7;
shell("gpio mode $pin OUT");

for True, !* ... * {
    shell("gpio write $pin " ~ +$_);
    sleep 0.5;
}

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

Обратите внимание на интересные моменты в программе.

Во-первых, бесконечный цикл сделан с помощью ленивой последовательности, каждый элемент которой вычисляется как !*, то есть как отрицание предыдущего. В итоге последовательность состоит из бесконечного чередования True и False.

Во-вторых, булевы величины преобразуются в единицу или ноль с помощью префксного оператора: +$_ и затем подставляются в строку, которая выполняется командной оболочкой.

В-третьих, для запуска используется функция shell. Для этой задачи этого более чем достаточно. В идеале нужно было бы написать код, который напрямую устанавливает биты в соответствующих регистрах устройства.

В-четвертых, не забывайте, что sleep в Perl 6 может принимать и нецелые значения.