15

我目前在一个非常复杂的 Perl 架构中工作,我想创建一些调试工具。由于很多行为都涉及匿名子例程,因此我想分析一些行为,而我所要做的就是对子例程的引用。

简而言之,有没有办法打印子例程引用的代码(因为 Perl 被解释它可能仍然可用?)?

4

4 回答 4

27

核心模块B::Deparse提供了这个功能。

use B::Deparse ();

my $deparse = B::Deparse->new;

my $code = sub {print "hello, world!"};

print 'sub ', $deparse->coderef2text($code), "\n";

打印:

sub {
    print 'hello, world!';
}

使用B::Deparse时请务必记住,它返回的是已编译的操作码树的反编译版本,而不是原始源文本。这意味着优化器可以折叠和重写常量、算术表达式和其他结构。

谜题的另一部分是处理封闭的词法变量。如果您正在使用的子例程访问任何外部词法,它们将不会出现在 deparse 的输出中,并且会导致重新编译失败。您可以使用PadWalker模块中的closed_overset_closed_over函数来解决这个问题。

use PadWalker qw/closed_over set_closed_over/;

my $closure = do {
    my $counter = 0;
    sub {$counter++}
};

print $closure->(), ' ' for 1..3; # 0 1 2
print "\n";

my $pad = closed_over $closure; # hash of lexicals

                 # create dummy lexicals for compilation
my $copy = eval 'my ('.join(','=> keys %$pad).');'. 
                'sub '.$deparse->coderef2text($closure);

set_closed_over $copy, $pad;  # replace dummy lexicals with real ones

print $copy->(), ' ' for 1..3; # 3 4 5

最后,如果想知道子程序的真正源代码在哪里,可以使用core B模块:

use B ();
my $meta = B::svref_2object($closure);

print "$closure at ".$meta->FILE.' line '.$meta->GV->LINE."\n";

打印如下内容:

CODE(0x28dcffc) 在 filename.pl 第 21 行
于 2011-04-08T02:40:08.943 回答
15

是的,Data::Dumper可以通过以下方式告诉您引入B::Deparse

#!/usr/bin/perl

use Data::Dumper;
use strict;
use warnings;
$Data::Dumper::Deparse = 1;

my $code = sub { my $a = 42;  print $a ** 2; };

print Dumper $code;

Data::Dumper如果您愿意,还有一个面向对象的接口(在 perldoc 中描述)。

注意:输出的代码与您最初指定的代码不同,但具有相同的语义。

于 2011-04-08T01:22:37.990 回答
7

此外,Devel::Dwarn设置Data::Dumper为默认解析。它很快成为我最喜欢的垃圾车:

perl -MDevel::Dwarn -e "Dwarn { callback => sub { 1+1 } }"

{
  callback => sub {
      2;
  }
}
于 2011-04-08T08:23:30.703 回答
6

对于这类事情,我总是参考在PerlMonks 上跟踪匿名 coderef 的文件名/行号。Randal 有一个想法来标记匿名子例程,以便您可以看到定义它们的位置,我对其进行了扩展。它使用了Eric 发布的一些相同的东西,但更多。

于 2011-04-08T06:56:07.703 回答