B::Deparse - Perl 编译器后端,用于生成 Perl 代码
perl -MO=Deparse[,-d][,-fFILE][,-p][,-q][,-l] [,-sLETTERS][,-xLEVEL] prog.pl
B::Deparse 是 Perl 编译器的一个后端模块,它根据 Perl 本身在解析程序后创建的内部编译结构生成 Perl 源代码。B::Deparse 的输出不会与原始源代码完全相同,因为 Perl 不会跟踪注释或空白,并且 Perl 的语法结构与其编译形式之间没有一一对应关系,但它通常会很接近。当您使用 -p 选项时,输出还包括括号,即使它们不是优先级所必需的,这可以使您轻松地查看 Perl 是否按您的意图解析表达式。
虽然 B::Deparse 尽力尝试找出您的原始程序的意图,但语言的某些部分仍然会让它感到困惑;它甚至在 Perl 自身测试套件的某些部分仍然会失败。如果您遇到除下面 BUGS 部分中描述的最常见错误之外的其他错误,您可以通过提交包含小型示例的错误报告来帮助 B::Deparse 的持续开发。
与所有编译器后端选项一样,这些选项必须紧跟在 '-MO=Deparse' 之后,用逗号分隔,但不能有任何空格。
使用 Data::Dumper 输出数据值(当它们以常量形式出现时)。如果没有此选项,B::Deparse 将使用它自己的一些简单例程来实现相同目的。目前,Data::Dumper 对于某些类型的数据(例如具有共享和自引用的复杂结构)更好,而内置例程对于其他数据(例如奇数浮点值)更好。
通常,B::Deparse 会反编译程序的主代码以及在同一文件中定义的所有子程序。要包含在其他文件中定义的子程序,请使用 -f 选项并指定文件名。您可以多次传递 -f 选项,以包含多个辅助文件。(大多数情况下您根本不需要使用它。)您也可以使用此选项来包含在 #line 指令(带有两个参数)的范围内定义的子程序。
根据原始代码的行和文件位置,在输出中添加 '#line' 声明。
打印额外的括号。如果没有此选项,B::Deparse 仅在需要时才在其输出中包含括号,这取决于您的程序结构。使用 -p 时,它会在(几乎)所有合法的情况下使用括号。如果您习惯使用 LISP,或者您想查看 perl 如何解析您的输入,这将很有用。如果您说
if ($var & 0x7f == 65) {print "Gimme an A!"}
print ($which ? $a : $b), "\n";
$name = $ENV{USER} or "Bob";
B::Deparse,-p
将打印
if (($var & 0)) {
print('Gimme an A!')
};
(print(($which ? $a : $b)), '???');
(($name = $ENV{'USER'}) or '???')
这可能不是你想要的('???'
表示 Perl 优化掉了常量值)。
禁用原型检查。使用此选项,所有函数调用都将被解析为没有为它们定义原型。换句话说,
perl -MO=Deparse,-P -e 'sub foo (\@) { 1 } foo @x'
将打印
sub foo (\@) {
1;
}
&foo(\@x);
清楚地表明参数是如何实际传递给 foo
的。
将双引号字符串展开为相应的连接、uc、ucfirst、lc、lcfirst、quotemeta 和 join 组合。例如,打印
print "Hello, $world, @ladies, \u$gentlemen\E, \u\L$me!";
为
print 'Hello, ' . $world . ', ' . join($", @ladies) . ', '
. ucfirst($gentlemen) . ', ' . ucfirst(lc $me . '!');
请注意,展开形式表示 Perl 在内部处理此类构造的方式 - 此选项实际上关闭了 B::Deparse 通常执行的逆向转换。另一方面,请注意 $x = "$y"
与 $x = $y
不同:前者在进行赋值之前将 $y 的值转换为字符串。
调整 B::Deparse 输出的样式。字母应直接位于 's' 之后,没有空格或标点符号。以下选项可用
将 elsif
、else
和 continue
块紧凑在一起。例如,打印
if (...) {
...
} else {
...
}
而不是
if (...) {
...
}
else {
...
}
默认情况下不紧凑在一起。
将行缩进 NUMBER 列的倍数。默认值为 4 列。
对每 8 列缩进使用制表符。默认情况下仅使用空格。例如,如果样式选项为 -si4T,则缩进 3 次的行将以一个制表符和四个空格开头;如果选项为 -si8T,则同一行将以三个制表符开头。
打印STRING 来表示一个无法确定的常量值,因为该常量在优化过程中被移除(助记符:当常量在void 上下文中使用时会发生这种情况)。字符串的结尾用句号标记。该字符串应该是一个有效的 Perl 表达式,通常是一个常量。请注意,除非它是一个数字,否则它可能需要用引号括起来,并且在命令行中,引号需要受到 shell 的保护。一些常用的值包括 0、1、42、''、'foo' 和 'Useless use of constant omitted'(这可能需要是-sv"'Useless use of constant omitted'." 或类似的东西,具体取决于你的 shell)。默认值为 '???'。如果你在使用 B::Deparse 处理一个模块或其他被 require 的文件时,你不应该使用一个计算结果为 false 的值,因为模块末尾的习惯性 true 常量在文件被编译为主程序时将处于 void 上下文。
将传统的语法结构扩展为等效的结构,以暴露其内部操作。LEVEL 应该是一个数字,数字越大,扩展程度越高。与-q一样,这实际上涉及关闭 B::Deparse 正常操作中的特殊情况。
如果LEVEL 至少为 3,for
循环将被转换为等效的 while 循环,其中包含 continue 块;例如
for ($i = 0; $i < 10; ++$i) {
print $i;
}
将转换为
$i = 0;
while ($i < 10) {
print $i;
} continue {
++$i
}
请注意,在少数情况下,这种转换无法完美地还原到源代码中——例如,如果循环的初始化器声明了一个 my 变量,那么它在循环之外将没有正确的范围。
如果LEVEL 至少为 5,use
声明将被转换为包含对 require
和 import
的调用的 BEGIN
块;例如,
use strict 'refs';
将转换为
sub BEGIN {
require strict;
do {
'strict'->import('refs')
};
}
如果LEVEL 至少为 7,if
语句将被转换为使用 &&
、?:
和 do {}
的等效表达式;例如
print 'hi' if $nice;
if ($nice) {
print 'hi';
}
if ($nice) {
print 'hi';
} else {
print 'bye';
}
将转换为
$nice and print 'hi';
$nice and do { print 'hi' };
$nice ? do { print 'hi' } : do { print 'bye' };
长序列的 elsifs 将转换为嵌套的三元运算符,B::Deparse 不知道如何很好地缩进它们。
use B::Deparse;
$deparse = B::Deparse->new("-p", "-sC");
$body = $deparse->coderef2text(\&func);
eval "sub func $body"; # the inverse operation
B::Deparse 也可以在其他 Perl 程序中以子程序为单位使用。
$deparse = B::Deparse->new(OPTIONS)
创建一个对象来存储反解析操作的状态和任何选项。这些选项与可以在命令行上给出的选项相同(参见 "选项");在-MO=Deparse 之后用逗号分隔的选项应该作为单独的字符串给出。
$deparse->ambient_pragmas(strict => 'all', '$[' => $[);
子程序的编译可能会受到一些编译器指令(即pragma)的影响。这些指令是
use strict;
use warnings;
将值赋给特殊变量 $[
使用整数;
使用字节;
使用 utf8;
使用正则表达式;
通常,如果您在存在一个或多个这些 pragma 的情况下对子例程使用 B::Deparse,输出将包含打开相应指令的语句。因此,如果您随后编译 coderef2text 返回的代码,它的行为将与您 deparsed 的子例程相同。
但是,您可能知道您打算在特定上下文中使用结果,其中一些 pragma 已经在作用域内。在这种情况下,您使用 **ambient_pragmas** 方法来描述您希望做出的假设。
并非所有选项目前都有任何有用的效果。有关更多详细信息,请参阅 "BUGS"。
它接受的参数是
接受一个字符串,可能包含多个由空格分隔的值。特殊值“all”和“none”的含义与您期望的一致。
$deparse->ambient_pragmas(strict => 'subs refs');
接受一个数字,即数组基 $[ 的值。已过时:不能为非零值。
如果值为真,则假定相应的 pragma 位于环境作用域中,否则不位于环境作用域中。
接受一个字符串,可能包含一个由空格分隔的值列表。值“all”和“none”是特殊的。也可以在这里传递数组引用。
$deparser->ambient_pragmas(re => 'eval');
接受一个字符串,可能包含一个由空格分隔的值列表。值“all”和“none”再次是特殊的。也可以在这里传递数组引用。
$deparser->ambient_pragmas(warnings => [qw[void io]]);
如果其中一个值为字符串“FATAL”,则该列表中的所有警告都将被视为致命错误,就像 **warnings** pragma 本身一样。如果您需要指定某些警告是致命的,而另一些只是启用的,则可以两次传递 **warnings** 参数
$deparser->ambient_pragmas(
warnings => 'all',
warnings => [FATAL => qw/void io/],
);
有关词法警告的更多信息,请参阅 warnings。
这两个参数用于以特殊变量 $^H 和 ${^WARNING_BITS} 使用的格式指定环境 pragma。
它们主要存在,以便您可以编写类似以下的代码
{ my ($hint_bits, $warning_bits);
BEGIN {($hint_bits, $warning_bits) = ($^H, ${^WARNING_BITS})}
$deparser->ambient_pragmas (
hint_bits => $hint_bits,
warning_bits => $warning_bits,
'$[' => 0 + $[
); }
它指定环境 pragma 恰好是调用时处于作用域内的那些 pragma。
此参数用于指定存储在特殊哈希 %^H 中的环境编译指示。
$body = $deparse->coderef2text(\&func)
$body = $deparse->coderef2text(sub ($$) { ... })
给定一个子程序的引用,返回子程序主体(一个代码块,可选地以括号中的原型开头)的源代码。由于子程序可以没有名称,也可以有多个名称,因此此方法不会返回完整的子程序定义 - 如果您想评估结果,您应该在前面加上 "sub subname ",或者对于匿名函数构造器,加上 "sub "。除非子程序是在 main:: 包中定义的,否则代码将包含一个包声明。
唯一完全支持的编译指示是:use warnings
、use strict
、use bytes
、use integer
和 use feature
。
除了上面列出的那些之外,我们目前无法保证 B::Deparse 会在程序中的正确位置生成编译指示。(具体来说,代码块开头的编译指示通常出现在代码块开始之前。)由于编译指示的效果通常是词法范围的,这意味着编译指示可能控制着与输入文件不同的程序部分。
事实上,以上是更普遍问题的一个具体实例:我们无法保证在完全正确的位置生成 BEGIN 代码块或 use
声明。因此,如果您使用影响编译的模块(例如通过覆盖关键字、重载常量或其他操作),则输出代码可能无法按预期工作。
某些常量在使用或不使用 -d 时都无法正确打印。例如,B::Deparse 和 Data::Dumper 都不知道如何正确打印双值标量,例如
use constant E2BIG => ($!=7); $y = E2BIG; print $y, 0+$y;
use constant H => { "#" => 1 }; H->{"#"};
使用源代码过滤的输入文件可能无法被反编译成可运行的代码,因为它仍然会包含源代码过滤模块的 use 声明,即使生成的代码已经是普通的 Perl 代码,不应该再次过滤。
被优化掉的语句将被渲染为 '???'。这包括具有编译时副作用的语句,例如晦涩的
my $x if 0;
因此,它没有被正确地反解析。
foreach my $i (@_) { 0 }
=>
foreach my $i (@_) { '???' }
在子程序范围之外声明的词法(my)变量在 coderef2text 输出文本中显示为包变量。这是一个棘手的问题,因为 Perl 没有原生机制来引用在不同作用域内定义的词法变量,尽管 PadWalker 是一个不错的起点。
另请参阅 Data::Dump::Streamer,它结合了 B::Deparse 和 PadWalker 来正确序列化闭包。
在非 ASCII 平台(EBCDIC)上可能存在更多错误。
Stephen McCamant <[email protected]>,基于 Malcolm Beattie <[email protected]> 的早期版本,并得到了 Gisle Aas、James Duncan、Albert Dvornik、Robin Houston、Dave Mitchell、Hugo van der Sanden、Gurusamy Sarathy、Nick Ing-Simmons 和 Rafael Garcia-Suarez 的贡献。