内容

名称

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' 之后,用逗号分隔,但不能有任何空格。

-d

使用 Data::Dumper 输出数据值(当它们以常量形式出现时)。如果没有此选项,B::Deparse 将使用它自己的一些简单例程来实现相同目的。目前,Data::Dumper 对于某些类型的数据(例如具有共享和自引用的复杂结构)更好,而内置例程对于其他数据(例如奇数浮点值)更好。

-fFILE

通常,B::Deparse 会反编译程序的主代码以及在同一文件中定义的所有子程序。要包含在其他文件中定义的子程序,请使用 -f 选项并指定文件名。您可以多次传递 -f 选项,以包含多个辅助文件。(大多数情况下您根本不需要使用它。)您也可以使用此选项来包含在 #line 指令(带有两个参数)的范围内定义的子程序。

-l

根据原始代码的行和文件位置,在输出中添加 '#line' 声明。

-p

打印额外的括号。如果没有此选项,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 优化掉了常量值)。

-P

禁用原型检查。使用此选项,所有函数调用都将被解析为没有为它们定义原型。换句话说,

perl -MO=Deparse,-P -e 'sub foo (\@) { 1 } foo @x'

将打印

    sub foo (\@) {
	1;
    }
    &foo(\@x);

清楚地表明参数是如何实际传递给 foo 的。

-q

将双引号字符串展开为相应的连接、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 的值转换为字符串。

-sLETTERS

调整 B::Deparse 输出的样式。字母应直接位于 's' 之后,没有空格或标点符号。以下选项可用

C

elsifelsecontinue 块紧凑在一起。例如,打印

if (...) {
     ...
} else {
     ...
}

而不是

if (...) {
     ...
}
else {
     ...
}

默认情况下不紧凑在一起。

iNUMBER

将行缩进 NUMBER 列的倍数。默认值为 4 列。

T

对每 8 列缩进使用制表符。默认情况下仅使用空格。例如,如果样式选项为 -si4T,则缩进 3 次的行将以一个制表符和四个空格开头;如果选项为 -si8T,则同一行将以三个制表符开头。

vSTRING.

打印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 上下文。

-xLEVEL

将传统的语法结构扩展为等效的结构,以暴露其内部操作。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 声明将被转换为包含对 requireimport 的调用的 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 不知道如何很好地缩进它们。

将 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 程序中以子程序为单位使用。

new

$deparse = B::Deparse->new(OPTIONS)

创建一个对象来存储反解析操作的状态和任何选项。这些选项与可以在命令行上给出的选项相同(参见 "选项");在-MO=Deparse 之后用逗号分隔的选项应该作为单独的字符串给出。

ambient_pragmas

$deparse->ambient_pragmas(strict => 'all', '$[' => $[);

子程序的编译可能会受到一些编译器指令(即pragma)的影响。这些指令是

通常,如果您在存在一个或多个这些 pragma 的情况下对子例程使用 B::Deparse,输出将包含打开相应指令的语句。因此,如果您随后编译 coderef2text 返回的代码,它的行为将与您 deparsed 的子例程相同。

但是,您可能知道您打算在特定上下文中使用结果,其中一些 pragma 已经在作用域内。在这种情况下,您使用 **ambient_pragmas** 方法来描述您希望做出的假设。

并非所有选项目前都有任何有用的效果。有关更多详细信息,请参阅 "BUGS"

它接受的参数是

strict

接受一个字符串,可能包含多个由空格分隔的值。特殊值“all”和“none”的含义与您期望的一致。

$deparse->ambient_pragmas(strict => 'subs refs');
$[

接受一个数字,即数组基 $[ 的值。已过时:不能为非零值。

bytes
utf8
integer

如果值为真,则假定相应的 pragma 位于环境作用域中,否则不位于环境作用域中。

re

接受一个字符串,可能包含一个由空格分隔的值列表。值“all”和“none”是特殊的。也可以在这里传递数组引用。

$deparser->ambient_pragmas(re => 'eval');
warnings

接受一个字符串,可能包含一个由空格分隔的值列表。值“all”和“none”再次是特殊的。也可以在这里传递数组引用。

$deparser->ambient_pragmas(warnings => [qw[void io]]);

如果其中一个值为字符串“FATAL”,则该列表中的所有警告都将被视为致命错误,就像 **warnings** pragma 本身一样。如果您需要指定某些警告是致命的,而另一些只是启用的,则可以两次传递 **warnings** 参数

    $deparser->ambient_pragmas(
	warnings => 'all',
	warnings => [FATAL => qw/void io/],
    );

有关词法警告的更多信息,请参阅 warnings

hint_bits
warning_bits

这两个参数用于以特殊变量 $^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

此参数用于指定存储在特殊哈希 %^H 中的环境编译指示。

coderef2text

$body = $deparse->coderef2text(\&func)
$body = $deparse->coderef2text(sub ($$) { ... })

给定一个子程序的引用,返回子程序主体(一个代码块,可选地以括号中的原型开头)的源代码。由于子程序可以没有名称,也可以有多个名称,因此此方法不会返回完整的子程序定义 - 如果您想评估结果,您应该在前面加上 "sub subname ",或者对于匿名函数构造器,加上 "sub "。除非子程序是在 main:: 包中定义的,否则代码将包含一个包声明。

BUGS

作者

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 的贡献。