Spec-Zone.ru › Perl 5.28

perlmod

СОДЕРЖАНИЕ

  • ИМЯ
  • ОПИСАНИЕ
    • Этот документ тот, что вам нужен?
    • Пакеты
    • Таблицы символов
    • BEGIN, UNITCHECK, CHECK, INIT и END
    • Классы Perl
    • Модули Perl
    • Как сделать ваш модуль потокобезопасным
  • СМОТРИТЕ ТАКЖЕ

ИМЯ

perlmod - Модули Perl (пакеты и таблицы символов)

ОПИСАНИЕ

Этот документ тот, что вам нужен?

Возможно, искомая информация содержится в других документах:

Этот документ

Пакеты Perl, пространства имен и информация о классах.

perlnewmod

Руководство по созданию нового модуля.

perlmodstyle

Рекомендации по созданию нового модуля.

Пакеты

В отличие от Perl 4, в котором все переменные были динамическими и использовали одно глобальное пространство имен, что создавало проблемы с поддержкой, Perl 5 предоставляет два механизма для защиты кода от перезаписи переменных другим кодом: лексически-ограниченные переменные, созданные с помощью my или state, и именованные глобальные переменные, доступные через vars pragma или our ключевое слово. Любая глобальная переменная считается частью пространства имен и может быть обращена через "полную квалификацию". Напротив, любая лексически-ограниченная переменная считается частью этого лексического пространства и не имеет "полной квалификации".

В Perl пространства имен называются "пакетами", а package объявление сообщает компилятору, какое пространство имен нужно добавить как префикс к our переменным и неквалифицированным динамическим именам. Это защищает от случайной перезаписи и предоставляет интерфейс для преднамеренной перезаписи глобальных динамических переменных, объявленных и используемых в других областях видимости или пакетах, если это необходимо.

Область действия package объявления простирается от самого объявления до конца содержащего блока, eval, или файла, что произойдет раньше (такая же область действия, как у операторов my(), our(), state() и local(), а также эффект экспериментального "алиасинга ссылок", который может измениться), или до следующего package объявления. Неквалифицированные динамические идентификаторы будут в этом пространстве имен, за исключением нескольких идентификаторов, которые, если они не квалифицированы, по умолчанию относятся к главному пакету вместо текущего, как описано ниже. package утверждение затрагивает только динамические глобальные символы, включая имена подпрограмм и переменные, к которым вы применяли local(), но не лексические переменные, созданные с помощью my(), our() или state().

Обычно package утверждение является первым объявлением в файле, включенном в программу с помощью одного из do, require, или use операторов. Вы можете переключаться между пакетами в нескольких местах: package не оказывает никакого действия помимо указания таблицы символов, которую компилятор будет использовать для динамических символов до конца данного блока или до следующего package утверждения. Вы можете ссылаться на переменные и дескрипторы файлов в других пакетах, добавляя к идентификатору имя пакета и двойное двоеточие: $Package::Variable. Если имя пакета пустое, предполагается пакет main. То есть, $::sail эквивалентно $main::sail.

Старым разделителем пакетов была одиночная кавычка, но двойное двоеточие сейчас предпочтительнее, отчасти потому, что оно лучше читается человеком, а отчасти потому, что оно лучше читается макросами emacs. Это также заставляет программистов C++ чувствовать себя так, как будто они понимают, что происходит – в отличие от использования одинарной кавычки в качестве разделителя, который был добавлен для того, чтобы программисты Ada чувствовали себя уверенно. Поскольку устаревшая синтаксическая конструкция по-прежнему поддерживается для обратной совместимости, если вы попытаетесь использовать строку типа "This is $owner's house", вы будете обращаться к $owner::s; то есть, к переменной $s в пакете owner, что, вероятно, не то, что вы имели в виду. Используйте фигурные скобки для устранения неоднозначности, как в "This is ${owner}'s house".

Пакеты могут содержать разделители пакетов, как в $OUTER::INNER::var. Однако это не влияет на порядок поиска имен. Нет относительных пакетов: все символы либо локальны для текущего пакета, либо должны быть полностью квалифицированы от имени внешнего пакета. Например, внутри пакета OUTER $INNER::var не ссылается на $OUTER::INNER::var. INNER относится к совершенно отдельному глобальному пакету. Привычка рассматривать имена пакетов как иерархию очень распространена, но язык никоим образом не навязывает ее.

Только идентификаторы, начинающиеся с букв (или подчеркивания), хранятся в таблице символов пакета. Все остальные символы сохраняются в пакете main, включая все переменные с пунктуацией, такие как $_. Кроме того, когда неквалифицированы, идентификаторы STDIN, STDOUT, STDERR, ARGV, ARGVOUT, ENV, INC и SIG принудительно помещаются в пакет main, даже когда они используются для других целей, кроме встроенных. Если у вас есть пакет с именем m, s, или y, вы не можете использовать полную форму идентификатора, потому что она будет интерпретироваться как поиск по шаблону, подстановка или транслитерация.

Переменные, начинающиеся с подчеркивания, ранее принудительно помещались в пакет main, но мы посчитали более полезным, чтобы авторы пакетов могли использовать ведущее подчеркивание для обозначения частных переменных и имен методов. Однако переменные и функции с именем, содержащим только одно _, такие как $_ и sub _, все равно принудительно помещаются в пакет main. См. также "Синтаксис имен переменных" в perlvar.

eval строки компилируются в пакете, в котором был выполнен eval(). (Присваивания $SIG{}, однако, предполагают, что указанный обработчик сигналов находится в пакете main. Укажите имя обработчика сигналов, если вам нужен обработчик сигналов в пакете.) Для примера рассмотрите perldb.pl в библиотеке Perl. Она изначально переключается в пакет DB, чтобы отладчик не вмешивался в переменные программы, которую вы пытаетесь отладить. Однако в разных точках она временно возвращается в пакет main для оценки различных выражений в контексте пакета main (или откуда вы пришли). См. perldebug.

Специальный символ __PACKAGE__ содержит текущий пакет, но (легко) не может использоваться для построения имен переменных. После того, как my($foo) скрыл пакетную переменную $foo, к ней все еще можно получить доступ, не зная, в каком пакете вы находитесь, как ${__PACKAGE__.'::foo'}.

См. perlsub для других проблем с областью видимости, связанных с my() и local(), и perlref относительно замыканий.

Таблицы символов

Таблица символов пакета хранится в хеше с его именем и двумя двоеточиями в конце. Имя основной таблицы символов, таким образом, %main::, или %:: для краткости. Аналогично, имя таблицы символов для вложенного пакета, упомянутого ранее, называется %OUTER::INNER::.

Значение в каждом элементе хеша – то, на что вы ссылаетесь, когда используете *name типглоб-нотацию.

local *main::foo    = *main::bar;

Это можно использовать, например, для вывода всех переменных в пакете. Стандартная, но устаревшая библиотека dumpvar.pl и модуль CPAN Devel::Symdump используют это.

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

Присваивание типглобу выполняет операцию алиасинга, т.е.

*dick = *richard;

приводит к тому, что переменные, подпрограммы, форматы и дескрипторы файлов и каталогов, доступные через идентификатор richard, также станут доступны через идентификатор dick. Если вы хотите создать алиас только для определенной переменной или подпрограммы, присвойте ссылку вместо этого:

*dick = \$richard;

Это делает $richard и $dick одинаковыми переменными, но оставляет @richard и @dick отдельными массивами. Сложно, да?

Есть одно тонкое отличие между следующими утверждениями:

*foo = *bar;
*foo = \$bar;

*foo = *bar делает сами типглобы синонимичными, а *foo = \$bar делает части SCALAR двух разных типглобов ссылками на одно и то же скалярное значение. Это означает, что следующий код:

$bar = 1;
*foo = \$bar;       # Make $foo an alias for $bar

{
    local $bar = 2; # Restrict changes to block
    print $foo;     # Prints '1'!
}

Выведет '1', потому что $foo содержит ссылку на исходный $bar. Тот, что был сохранен local() и который будет восстановлен по окончании блока. Так как к переменным обращаются через типглоб, вы можете использовать *foo = *bar для создания алиаса, который можно локализовать. (Но имейте в виду, что это означает, что у вас не может быть отдельных @foo и @bar, и т.д.)

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

@EXPORT = qw($FOO); # Usual form, can't be localized
@EXPORT = qw(*FOO); # Can be localized

Вы можете обойти первый случай, используя полное имя ($Package::FOO) там, где вам нужна локальная переменная, или переопределяя ее, указав *FOO = *Package::FOO в вашем скрипте.

Механизм *x = \$y может использоваться для передачи и возврата дешёвых ссылок в или из подпрограмм, если вы не хотите копировать всё содержимое. Он работает только при присваивании динамическим переменным, а не лексическим.

%some_hash = ();                    # can't be my()
*some_hash = fn( \%another_hash );
sub fn {
    local *hashsym = shift;
    # now use %hashsym normally, and you
    # will affect the caller's %another_hash
    my %nhash = (); # do what you want
    return \%nhash;
}

По возвращении ссылка перезапишет ячейку хеша в таблице символов, указанной типглобом *some_hash. Это довольно хитрый способ передавать ссылки дёшево, когда вам не нужно помнить, что нужно явным образом разыменовывать переменные.

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

*PI = \3.14159265358979;

Теперь вы не можете изменить $PI, что, вероятно, хорошо. Это не то же самое, что постоянная подпрограмма, которая может быть оптимизирована во время компиляции. Постоянная подпрограмма – это подпрограмма, прототипированная без аргументов и возвращающая константное выражение. См. perlsub для получения подробной информации об этом. Пragma use constant является удобным сокращением для таких случаев.

END_OF_DOCUMENT_MARKER

Можно использовать *foo{PACKAGE} и *foo{NAME} для определения имени и пакета, к которому принадлежит запись *foo в таблице символов. Это может быть полезно в подпрограмме, которая получает типглобы в качестве аргументов:

sub identify_typeglob {
    my $glob = shift;
    print 'You gave me ', *{$glob}{PACKAGE},
        '::', *{$glob}{NAME}, "\n";
}
identify_typeglob *foo;
identify_typeglob *bar::baz;

Это выведет

You gave me main::foo
You gave me bar::baz

Обозначение *foo{THING} также можно использовать для получения ссылок на отдельные элементы *foo. См. perlref.

Определения подпрограмм (и объявления, собственно) не обязательно должны находиться в пакете, в котором они хранятся в таблице символов. Вы можете определить подпрограмму вне своего пакета, явно указав имя подпрограммы:

package main;
sub Some_package::foo { ... }   # &foo defined in Some_package

Это просто сокращение для присваивания типглоба во время компиляции:

BEGIN { *Some_package::foo = sub { ... } }

и не равносильно написанию:

{
    package Some_package;
    sub foo { ... }
}

В первых двух версиях тело подпрограммы находится в основном пакете, а не в пакете Some_package. Поэтому что-то вроде этого:

package main;

$Some_package::name = "fred";
$main::name = "barney";

sub Some_package::foo {
    print "in ", __PACKAGE__, ": \$name is '$name'\n";
}

Some_package::foo();

выведет:

in main: $name is 'barney'

а не:

in Some_package: $name is 'fred'

Это также имеет последствия для использования квалификатора SUPER:: (см. perlobj).

BEGIN, UNITCHECK, CHECK, INIT и END

Пять блоков кода со специальными именами выполняются в начале и в конце работы программы Perl. Это блоки BEGIN, UNITCHECK, CHECK, INIT, и END.

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

Блок кода BEGIN выполняется как можно скорее, то есть в момент его полного определения, даже перед обработкой остальной части содержащего файла (или строки). В файле (или строке, обработанной eval) может быть несколько блоков BEGIN; они будут выполняться в порядке их определения. Так как блок кода BEGIN выполняется немедленно, он может подключать определения подпрограмм и подобных элементов из других файлов, чтобы они были видимы для остальной части процесса компиляции и выполнения. После выполнения блока BEGIN, он немедленно становится неопределённым, а весь использованный им код возвращается в пул памяти Perl.

Блок кода END выполняется как можно позже, то есть после завершения выполнения программы perl и непосредственно перед выходом интерпретатора, даже если выход происходит в результате функции die(). (Но не если он трансформируется в другую программу посредством exec, или если его вытесняет сигнал — вам нужно поймать это самим (если вы можете)). В файле может быть несколько блоков END — они будут выполняться в обратном порядке определения; то есть: последний, попавший, — первый, выполнившийся (LIFO). Блоки END не выполняются, если вы запускаете Perl с переключателем -c, или если компиляция завершается ошибкой.

Обратите внимание, что блоки кода END не выполняются в конце строки eval(): если какие-либо блоки кода END созданы в строке eval(), они будут выполняться так же, как и любой другой блок кода END этого пакета в порядке LIFO непосредственно перед выходом интерпретатора.

Внутри блока кода END, $? содержит значение, которое программа передаст exit(). Вы можете изменить $? для изменения значения выхода программы. Следите, чтобы случайно не изменить $? (например, выполняя что-либо через system).

Внутри блока END, значение ${^GLOBAL_PHASE} будет "END".

UNITCHECK, CHECK и INIT блоки кода полезны для захвата перехода между фазой компиляции и фазой выполнения основной программы.

Блоки UNITCHECK выполняются сразу после компиляции единицы, которая их определила. Основной файл программы и каждый модуль, который он загружает, являются единицами компиляции, как и строки evals, код, скомпилированный во время выполнения с помощью конструкции (?{ }) в регулярном выражении, вызовы к do FILE, require FILE, и код после переключателя -e на командной строке.

Блоки BEGIN и UNITCHECK не напрямую связаны с фазой интерпретатора. Они могут быть созданы и выполнены в любой фазе.

Блоки кода CHECK выполняются сразу после завершения начальной фазы компиляции Perl и перед началом выполнения, в порядке LIFO. Блоки кода CHECK используются в наборе компиляторов Perl для сохранения состояния программы после компиляции.

Внутри блока CHECK, значение ${^GLOBAL_PHASE} будет "CHECK".

Блоки INIT выполняются непосредственно перед началом выполнения Perl, в порядке "первый вошел, первый вышел" (FIFO).

Внутри блока INIT, значение ${^GLOBAL_PHASE} будет "INIT".

Блоки CHECK и INIT в коде, скомпилированном функциями require, строками do или строками eval, не будут выполнены, если они появятся после завершения основной фазы компиляции; это может быть проблемой в mod_perl и других устойчивых средах, которые используют эти функции для загрузки кода во время выполнения.

При использовании переключателей -n и -p в Perl, BEGIN и END работают так же, как и в awk, как частный случай. Оба блока BEGIN и CHECK выполняются при использовании переключателя -c для проверки синтаксиса только компиляцией, хотя ваш основной код не выполняется.

Программа begincheck в конечном итоге проясняет все:

#!/usr/bin/perl

# begincheck

print         "10. Ordinary code runs at runtime.\n";

END { print   "16.   So this is the end of the tale.\n" }
INIT { print  " 7. INIT blocks run FIFO just before runtime.\n" }
UNITCHECK {
  print       " 4.   And therefore before any CHECK blocks.\n"
}
CHECK { print " 6.   So this is the sixth line.\n" }

print         "11.   It runs in order, of course.\n";

BEGIN { print " 1. BEGIN blocks run FIFO during compilation.\n" }
END { print   "15.   Read perlmod for the rest of the story.\n" }
CHECK { print " 5. CHECK blocks run LIFO after all compilation.\n" }
INIT { print  " 8.   Run this again, using Perl's -c switch.\n" }

print         "12.   This is anti-obfuscated code.\n";

END { print   "14. END blocks run LIFO at quitting time.\n" }
BEGIN { print " 2.   So this line comes out second.\n" }
UNITCHECK {
 print " 3. UNITCHECK blocks run LIFO after each file is compiled.\n"
}
INIT { print  " 9.   You'll see the difference right away.\n" }

print         "13.   It only _looks_ like it should be confusing.\n";

__END__

Классы Perl

В Perl нет специального синтаксиса для классов, но пакет может действовать как класс, если он предоставляет подпрограммы, которые выполняют роль методов. Такой пакет также может унаследовать некоторые методы от другого класса (пакета), перечислив имена других пакетов в своём глобальном массиве @ISA (который должен быть глобальным для пакета, а не лексическим).

Подробнее об этом см. perlootut и perlobj.

Модули Perl

Модуль — это просто набор связанных функций в библиотечном файле, то есть пакет Perl с тем же именем, что и файл. Он специально разработан для повторного использования другими модулями или программами. Он может делать это, предоставляя механизм экспорта некоторых своих символов в таблицу символов любого пакета, использующего его, или может действовать как определение класса и неявно делать доступными свои семантики через вызовы методов к классу и его объектам без явного экспорта чего-либо. Или может делать и то, и другое.

Например, чтобы начать традиционный, не ориентированный на ООП модуль под названием Some::Module, создайте файл под названием Some/Module.pm и начните с этого шаблона:

package Some::Module;  # assumes Some/Module.pm

use strict;
use warnings;

BEGIN {
    require Exporter;

    # set the version for version checking
    our $VERSION     = 1.00;

    # Inherit from Exporter to export functions and variables
    our @ISA         = qw(Exporter);

    # Functions and variables which are exported by default
    our @EXPORT      = qw(func1 func2);

    # Functions and variables which can be optionally exported
    our @EXPORT_OK   = qw($Var1 %Hashit func3);
}

# exported package globals go here
our $Var1    = '';
our %Hashit  = ();

# non-exported package globals go here
# (they are still accessible as $Some::Module::stuff)
our @more    = ();
our $stuff   = '';

# file-private lexicals go here, before any functions which use them
my $priv_var    = '';
my %secret_hash = ();

# here's a file-private function as a closure,
# callable as $priv_func->();
my $priv_func = sub {
    ...
};

# make all your functions, whether exported or not;
# remember to put something interesting in the {} stubs
sub func1      { ... }
sub func2      { ... }

# this one isn't exported, but could be called directly
# as Some::Module::func3()
sub func3      { ... }

END { ... }       # module clean-up code here (global destructor)

1;  # don't forget to return a true value from the file

Затем переходите к объявлению и использованию своих переменных в функциях без каких-либо квалификаций. Подробности о механике и вопросах стиля при создании модулей см. в Exporter и perlmodlib.

Модули Perl включаются в вашу программу, написав

use Module;

или

use Module LIST;

Это точно эквивалентно

BEGIN { require 'Module.pm'; 'Module'->import; }

или

BEGIN { require 'Module.pm'; 'Module'->import( LIST ); }

В качестве специального случая

use Module ();

точно эквивалентно

BEGIN { require 'Module.pm'; }

Все файлы модулей Perl имеют расширение .pm. Оператор use предполагает это, так что вам не нужно указывать "Module.pm" в кавычках. Это также помогает отличать новые модули от старых файлов .pl и .ph. Имена модулей также пишутся с заглавной буквы, если они не используются как прагмы; прагмы — это, по сути, директивы компилятора, и их иногда называют «прагматическими модулями» (или даже «прагматами», если вы классик).

Два утверждения:

require SomeModule;
require "SomeModule.pm";

отличаются друг от друга двумя способами. В первом случае все двойные двоеточия в имени модуля, такие как Some::Module, переводятся в разделитель каталогов вашей системы, обычно "/". Во втором случае этого не происходит, и его нужно указывать буквально. Другое различие заключается в том, что первое require указывает компилятору, что использование косвенной нотации объекта, связанной с "SomeModule", как в $ob = purge SomeModule, является вызовом метода, а не вызовом функции. (Да, это действительно может иметь значение.)

Поскольку утверждение use подразумевает блок BEGIN, импорт семантики происходит сразу же после компиляции утверждения use, до компиляции остальной части файла. Именно так это работает как механизм прагмы, а также как модули могут объявлять подпрограммы, которые затем видны как списки или унарные операторы для остальной части текущего файла. Это не сработает, если вы используете require вместо use. С require вы можете столкнуться с этой проблемой:

require Cwd;                # make Cwd:: accessible
$here = Cwd::getcwd();

use Cwd;                    # import names from Cwd::
$here = getcwd();

require Cwd;                # make Cwd:: accessible
$here = getcwd();           # oops! no main::getcwd()

В целом, use Module () рекомендуется вместо require Module, потому что это определяет доступность модуля во время компиляции, а не посередине выполнения вашей программы. Исключением является случай, когда два модуля каждый пытаются use друг друга, и каждый также вызывает функцию из другого модуля. В этом случае проще использовать require.

Пакеты Perl могут быть вложены внутри других имён пакетов, поэтому мы можем иметь имена пакетов, содержащие ::. Но если бы мы использовали это имя пакета непосредственно в качестве имени файла, это привело бы к громоздким или невозможным именам файлов на некоторых системах. Таким образом, если имя модуля, скажем, Text::Soundex, то его определение фактически находится в библиотечном файле Text/Soundex.pm.

Модули Perl всегда имеют файл .pm, но также могут быть динамически подключаемые исполняемые файлы (часто заканчивающиеся на .so) или автоматически загружаемые определения подпрограмм (часто заканчивающиеся на .al), связанные с модулем. В таком случае они будут полностью прозрачны для пользователя модуля. Ответственность файла .pm заключается в загрузке (или организации автоматической загрузки) любой дополнительной функциональности. Например, хотя модуль POSIX случается, что делает и динамическую загрузку, и автоматическую загрузку, пользователь может просто указать use POSIX для получения всего.

Создание потокобезопасного модуля

Perl поддерживает тип потоков, называемые интерпретаторскими потоками (interpreter threads — ithreads). Эти потоки могут использоваться явно и неявно.

Потоки Ithreads работают, клонируя дерево данных, чтобы данные не разделялись между различными потоками. Эти потоки могут быть использованы с помощью модуля threads или с помощью fork() на win32 (поддержка имитации fork()). При клонировании потока все данные Perl клонируются, однако не-Perl данные не могут быть клонированы автоматически. Perl после версии 5.8.0 имеет поддержку специальной подпрограммы CLONE. В CLONE можно выполнять любые необходимые действия, например, обрабатывать клонирование не-Perl данных, если это необходимо. CLONE будет вызываться один раз как метод класса для каждого пакета, в котором он определен (или унаследован). Он будет вызываться в контексте нового потока, поэтому все изменения производятся в новой области. В настоящее время CLONE вызывается без параметров, кроме имени пакета-вызывающего, но код не должен предполагать, что это останется неизменным, так как в будущем могут быть переданы дополнительные параметры для предоставления дополнительной информации о состоянии клонирования.

Если вы хотите клонировать все объекты, вам нужно отслеживать их по пакетам. Это просто делается с помощью хэша и Scalar::Util::weaken().

Perl после версии 5.8.7 имеет поддержку специальной подпрограммы CLONE_SKIP. Как и CLONE, CLONE_SKIP вызывается один раз на пакет; однако, она вызывается непосредственно перед началом клонирования и в контексте родительского потока. Если она возвращает истинное значение, то объекты этого класса не будут клонированы; или, точнее, они будут скопированы как неблаженные, неопределенные значения. Например: если в родительском потоке есть две ссылки на один блаженный хэш, то в дочернем потоке будет две ссылки на одно неопределенное скалярное значение вместо этого. Это предоставляет простой механизм для обеспечения потокобезопасности модуля; просто добавьте sub CLONE_SKIP { 1 } в начало класса, и DESTROY() теперь будет вызываться только один раз на объект. Конечно, если дочернему потоку нужно использовать объекты, то потребуется более сложный подход.

Как и CLONE, CLONE_SKIP в настоящее время вызывается без параметров, кроме имени пакета-вызывающего, хотя это может измениться. Аналогично, чтобы обеспечить возможность будущего расширения, возвращаемое значение должно быть одиночным значением 0 или 1.

СМОТРИТЕ ТАКЖЕ

См. perlmodlib для общих вопросов стиля, связанных с созданием модулей и классов Perl, а также для описания стандартной библиотеки и CPAN, Exporter для работы механизма импорта/экспорта Perl, perlootut и perlobj для углубленного изучения создания классов, perlobj для справочного документа по объектам, perlsub для объяснения функций и области видимости и perlxstut и perlguts для получения дополнительной информации о написании модулей расширения.

© 1993–2020 Larry Wall and others
Licensed under the GNU General Public License version 1 or later, or the Artistic License.
The Perl logo is a trademark of the Perl Foundation.
https://perldoc.perl.org/5.28.3/perlmod

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API