Spec-Zone.ru › Perl 5.28

perlpragma

СОДЕРЖАНИЕ

  • ИМЯ
  • ОПИСАНИЕ
  • Базовый пример
  • Именование ключей
  • Детали реализации

ИМЯ

perlpragma — как написать пользовательский прагма

ОПИСАНИЕ

Прагма — это модуль, который влияет на некоторые аспекты поведения Perl во время компиляции или выполнения, такие как strict или warnings. С Perl 5.10 вы больше не ограничены встроенными прагмами; теперь вы можете создавать пользовательские прагмы, которые изменяют поведение пользовательских функций в лексическом объёме.

Базовый пример

Например, предположим, что вам нужно создать класс, реализующий перегруженные математические операторы, и вы хотите предоставить свой собственный прагма, который работает примерно так же, как use integer;. Вы хотите, чтобы этот код

use MyMaths;

my $l = MyMaths->new(1.2);
my $r = MyMaths->new(3.4);

print "A: ", $l + $r, "\n";

use myint;
print "B: ", $l + $r, "\n";

{
    no myint;
    print "C: ", $l + $r, "\n";
}

print "D: ", $l + $r, "\n";

no myint;
print "E: ", $l + $r, "\n";

выводил

A: 4.6
B: 4
C: 4.6
D: 4
E: 4.6

т.е., где use myint; активен, операции сложения принудительно преобразуются в целые числа, в то время как по умолчанию они не преобразуются, а с помощью no myint; восстанавливается стандартное поведение.

Минимальная реализация пакета MyMaths будет примерно такой:

package MyMaths;
use warnings;
use strict;
use myint();
use overload '+' => sub {
    my ($l, $r) = @_;
    # Pass 1 to check up one call level from here
    if (myint::in_effect(1)) {
        int($$l) + int($$r);
    } else {
        $$l + $$r;
    }
};

sub new {
    my ($class, $value) = @_;
    bless \$value, $class;
}

1;

Обратите внимание, как мы загружаем пользовательский прагма myint с пустым списком () для предотвращения вызова его import.

Взаимодействие с компиляцией Perl происходит внутри пакета myint:

package myint;

use strict;
use warnings;

sub import {
    $^H{"myint/in_effect"} = 1;
}

sub unimport {
    $^H{"myint/in_effect"} = 0;
}

sub in_effect {
    my $level = shift // 0;
    my $hinthash = (caller($level))[10];
    return $hinthash->{"myint/in_effect"};
}

1;

Поскольку прагмы реализуются как модули, как и любой другой модуль, use myint; превращается в

BEGIN {
    require myint;
    myint->import();
}

и no myint; становится

BEGIN {
    require myint;
    myint->unimport();
}

Поэтому процедуры import и unimport вызываются во время компиляции кода пользователя.

Пользовательские прагмы хранят своё состояние, записывая в магический хеш %^H, поэтому эти две процедуры манипулируют им. Информация о состоянии в %^H хранится в дереве синтаксического анализа, и её можно получить только для чтения во время выполнения с помощью caller(), по индексу 10 списка возвращаемых результатов. В примере прагмы получение инкапсулировано в процедуру in_effect(), которая принимает в качестве параметра количество кадров вызовов, чтобы подняться к значению прагмы в скрипте пользователя. Это использует caller() для определения значения $^H{"myint/in_effect"} при вызове каждой строки скрипта пользователя, и, следовательно, предоставляет правильную семантику в подпрограмме, реализующей перегруженное сложение.

Именование ключей

Существует только один %^H, но произвольно много модулей, которые хотят использовать его семантику области видимости. Чтобы избежать конфликтов, они должны использовать разные ключи в хеше. Поэтому принято, чтобы модуль использовал только ключи, которые начинаются с имени модуля (имени его основного пакета) и символа "/". После этого идентификатора модуля остальная часть ключа полностью зависит от модуля: она может содержать любые символы. Например, модуль Foo::Bar должен использовать ключи, такие как Foo::Bar/baz и Foo::Bar/$%/_! . Модули, соблюдающие эту соглашение, взаимодействуют друг с другом без конфликтов.

Ядро Perl использует несколько ключей в %^H, которые не следуют этой конвенции, потому что они существовали до неё. Ключи, которые следуют этой соглашению, не будут конфликтовать с историческими ключами ядра.

Детали реализации

Дерево синтаксического анализа используется между потоками. Это означает, что существует возможность того, что дерево синтаксического анализа переживёт конкретный поток (а следовательно, и экземпляр интерпретатора), который его создал, поэтому истинные скаляры Perl не могут храниться в дереве синтаксического анализа. Вместо этого используется компактная форма, которая может хранить только целые значения (знаковые и беззнаковые), строки или undef - ссылки и значения с плавающей запятой строятся в строковый вид. Если вам нужно сохранить несколько значений или сложные структуры, вы должны их сериализовать, например, с помощью pack. Удаление ключа хеша из %^H регистрируется, и, как всегда, его можно отличить от существования ключа со значением undef с помощью exists.

Не пытайтесь хранить ссылки на структуры данных в качестве целых чисел, которые извлекаются с помощью caller и преобразуются обратно, так как это не будет потокобезопасным. Доступ к структуре будет осуществляться без блокировки (что небезопасно для скаляров Perl), и либо структура должна утечь, либо она должна быть освобождена при завершении создающего потока, что может произойти до удаления ссылающегося на неё дерева синтаксического анализа, если другие потоки переживут его.

© 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/perlpragma

Spec-Zone.ru

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