perlpragma - 如何编写用户 pragma
pragma 是一个模块,它影响 Perl 的编译时或运行时行为的某些方面,例如 strict
或 warnings
。使用 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();
}
因此,import
和 unimport
例程在用户的代码的编译时间调用。
用户指令通过写入神奇哈希 %^H
来存储其状态,因此这两个例程对其进行操作。%^H
中的状态信息存储在 optree 中,并且可以在运行时使用 caller()
以只读方式检索,在返回结果列表的索引 10 处。在示例指令中,检索封装到例程 in_effect()
中,它将作为参数获取调用帧的数量,以查找用户脚本中指令的值。这使用 caller()
来确定 $^H{"myint/in_effect"}
的值,当用户脚本的每一行被调用时,并且因此在实现重载加法的子例程中提供正确的语义。
只有一个 %^H
,但有任意多个希望使用其作用域语义的模块。为了避免相互干扰,它们需要确保在哈希中使用不同的键。因此,模块通常只使用以模块名称(其主包的名称)和“/”字符开头的键。在此模块标识前缀之后,键的其余部分完全取决于模块:它可以包含任何字符。例如,模块 Foo::Bar
应使用诸如 Foo::Bar/baz
和 Foo::Bar/$%/_!
的键。遵循此约定的模块都很好地相互配合。
Perl 内核在 %^H
中使用一些不遵循此约定的键,因为它们早于此约定。遵循约定的键不会与内核的历史键冲突。
optree 在线程之间共享。这意味着 optree 可能比创建它的特定线程(因此也比解释器实例)存在的时间更长,所以真正的 Perl 标量不能存储在 optree 中。相反,使用一种紧凑的形式,它只能存储整数(带符号和不带符号)、字符串或 undef
- 引用和浮点值被字符串化。如果您需要存储多个值或复杂结构,您应该对其进行序列化,例如使用 pack
。从 %^H
中删除哈希键会被记录,并且与使用 exists
存在的键值 undef
的情况一样,可以区分开来。
不要尝试将对数据结构的引用存储为整数,这些整数通过 caller
检索并转换回来,因为这将不是线程安全的。访问将是针对结构的,无需锁定(这对 Perl 的标量来说是不安全的),并且结构必须泄漏,或者必须在其创建线程终止时释放它,如果其他线程比它存活时间更长,则可能在引用它的 optree 被删除之前。