跨嵌套子例程

时间:2015-06-26 07:47:24

标签: perl

让我们考虑下面的通缉代码。我有process的递归调用,每次递归我都使用本地%context。通过这种方式,当我从通话中返回时,我可以恢复上下文。

sub process {
    my %context; # Local context

    process() if rerun();

    job1();
    job2();

    sub job1() {print $context{foo}}
    sub job2() {print $context{bar}}
}

不幸的是没有像我预期的那样管理嵌套子程序。通过将子例程从进程子例程移到外部,我会遇到问题,因为我再也无法访问%context了。所以我需要将其设置为全局并使用堆栈如下:

my %context;        # Local context
my @context_stack;

sub process {
    push @context_stack, %context;
    %context = undef;

    process() if rerun();

    job1();
    job2();

    %context = pop @context_stack;
}

sub job1() {print $context{foo}}
sub job2() {print $context{bar}}

第三种解决方案是将上下文传递给所有子程序,这些子程序对于非常小的子程序来说可能很烦人。 %context也成为我所有计划的全球。所以我放弃了这个变量的隐私。

my %context;        # Local context
my @context_stack;

sub process {
    push @context_stack, %context;
    %context = undef;

    process() if rerun(\%context);

    job1(\%context);
    job2(\%context);

    %context = pop @context_stack;
}

sub job1() {$context = shift; print $context->{foo}}
sub job2() {$context = shift; print $context->{bar}}

最好的方法是什么?

修改

为了更好地理解我的具体情况,我提供了另一个例子:

process(@ARGV);
exit 0;

sub process {
    my $infile = shift;
    my $outfile = shift;

    open my $fp_in,  '<', $infile;
    open my $fp_out, '>', $outfile;

    LINE: while(<$fp_in>) { 
        remove_c_comment(); 

        say STDERR "File is $infile";

        process($1, "$1.processed") if /#include "(.*?)";
        warning("Huh raisin, no!") if /\braisin/;

        say STDERR "Fill is still $infile";

        print $fp_out $_;
    }

    sub remove_c_comment { s|//.*$|| }
    sub warning { say "[Warning] $infile:$. ".shift() }
}

1 个答案:

答案 0 :(得分:2)

你正在寻找的东西 - 但你可能不知道 - 被称为closure。 (另见:perlref

{
    my %context;

    sub job1 { print $context{foo} };
    sub job2 { print $context{bar} };
    sub init_context{ $context{foo} = 1 };
}

Context在此块中保持私有,但可供所有子例程访问。

作为替代方案 - 您可以从子例程返回代码引用 - 如下所示:

use strict;
use warnings;

sub make_sub_with_context {
    my %context;
    $context{"bar"} = 1;
    return sub { print $context{"bar"}++ };
}

my $job1_with_context = make_sub_with_context();
my $another_job_with_context = make_sub_with_context();
$job1_with_context->();
$job1_with_context->();

$another_job_with_context->();
$another_job_with_context->();
$another_job_with_context->();

这可能是一个更好的例子。

编辑:

继续更新的示例后,您的问题规范似乎是迭代一组文件,并(递归地)遍历引用的文件。

类似于find但遵循include指令。我会指出,通过这样做,你正在做的事情可能会最终导致循环,这是不理想的。

我可以建议采用不同的方法吗?不要递言:

use strict;
use warnings;

my @files_to_process = @ARGV;
my %done;

while ( my $infile = pop @files_to_process ) {
    next if $done{$infile}++;
    open my $fp_in, '<', $infile or die $!;
    open my $fp_out, '>', $infile . ".processed" or die $!;
    while ( my $line = <$fp_in> ) {
        $line =~ s|\/\/.*$||;
        if ( my ($include) = ( $line =~ m/#include "(.*?)"/ ) ) {
            push @files_to_process, $include;
        }
        print {$fp_out} $line;
    }
    close($fp_out);
    close($fp_in);
}

有了更多的想法,以及这个任务需要按照声明顺序处理内容的扩展 - 我会提供 - 也许采取OO方法会有所帮助。类似的东西:

use strict;
use warnings;

package parser;

sub new {
    my ($class) = @_;
    my $self = {};
    bless $self, $class;
    return $self;
}

sub process {
    my ( $self, $infile, $outfile ) = @_;
    open my $fp_in,  '<', $infile;
    open my $fp_out, '>', $outfile;

LINE: while ( my $line = <$fp_in> ) {
        $line =~ s|\/\/.*$||;

        say STDERR "File is $infile";

        if ( my ($includefile) = ( $line =~ m/#include "(.*?)"/ ) ) {
            my $processor = parser->new();
               $processor -> process( $includefile, "$includefile.processed" );
        }
        $self->warning("Huh raisin, no!") if /\braisin/;

        say STDERR "Fill is still $infile";

        print $fp_out $line;
    }
}

package main;

my $processor = parser->new()->process(@ARGV);