内容

名称

perlpragma - 如何编写用户 pragma

说明

pragma 是一个模块,它影响 Perl 的编译时或运行时行为的某些方面,例如 strictwarnings。使用 Perl 5.10,你不再局限于内置的 pragma;现在可以创建用户 pragma,以修改词法作用域内用户函数的行为。

基本示例

例如,假设你需要创建一个类来实现重载的数学运算符,并且希望提供自己的 pragma,其功能与 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 v5.36;
    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;

请注意,我们如何使用空列表 () 加载用户 pragma myint 以防止调用其 import

与 Perl 编译的交互发生在包 myint

package myint;

use v5.36;

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;

由于 pragma 是作为模块实现的,就像任何其他模块一样,use myint; 变成

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

no myint;

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

因此,importunimport 例程在用户的代码的编译时间调用。

用户指令通过写入神奇哈希 %^H 来存储其状态,因此这两个例程对其进行操作。%^H 中的状态信息存储在 optree 中,并且可以在运行时使用 caller() 以只读方式检索,在返回结果列表的索引 10 处。在示例指令中,检索封装到例程 in_effect() 中,它将作为参数获取调用帧的数量,以查找用户脚本中指令的值。这使用 caller() 来确定 $^H{"myint/in_effect"} 的值,当用户脚本的每一行被调用时,并且因此在实现重载加法的子例程中提供正确的语义。

键命名

只有一个 %^H,但有任意多个希望使用其作用域语义的模块。为了避免相互干扰,它们需要确保在哈希中使用不同的键。因此,模块通常只使用以模块名称(其主包的名称)和“/”字符开头的键。在此模块标识前缀之后,键的其余部分完全取决于模块:它可以包含任何字符。例如,模块 Foo::Bar 应使用诸如 Foo::Bar/bazFoo::Bar/$%/_! 的键。遵循此约定的模块都很好地相互配合。

Perl 内核在 %^H 中使用一些不遵循此约定的键,因为它们早于此约定。遵循约定的键不会与内核的历史键冲突。

实现细节

optree 在线程之间共享。这意味着 optree 可能比创建它的特定线程(因此也比解释器实例)存在的时间更长,所以真正的 Perl 标量不能存储在 optree 中。相反,使用一种紧凑的形式,它只能存储整数(带符号和不带符号)、字符串或 undef - 引用和浮点值被字符串化。如果您需要存储多个值或复杂结构,您应该对其进行序列化,例如使用 pack。从 %^H 中删除哈希键会被记录,并且与使用 exists 存在的键值 undef 的情况一样,可以区分开来。

不要尝试将对数据结构的引用存储为整数,这些整数通过 caller 检索并转换回来,因为这将不是线程安全的。访问将是针对结构的,无需锁定(这对 Perl 的标量来说是不安全的),并且结构必须泄漏,或者必须在其创建线程终止时释放它,如果其他线程比它存活时间更长,则可能在引用它的 optree 被删除之前。