在perl中的连续eval语句之间共享词法范围

时间:2018-03-10 15:20:23

标签: perl eval lexical-scope

我是否可以将eval ed Perl代码的不同片段共享相同的词法范围并获得其返回值?

背景

Perl的eval命令将字符串计算为Perl代码,并在成功时返回该代码中最后一个语句的值。但是,在代码中创建的词法变量将在代码末尾删除。这意味着当code1的eval结束并且我有第二个代码块code2时,它引用了code1中设置的词法变量,这将失败。

my $code1 = 'my $c = 4';
my $code2 = 'printf "%g\n", $c;';

printf 'evaluated "%s" to %s' . "\n", $code1, eval $code1;
printf 'evaluated "%s"' . "\n", $code2;

产量

evaluated "my $c = 4" to 4
evaluated "printf "%g\n", $c;"

但不是我想要的只包含4的行,因为如果重复使用词法范围,$code2应该使用变量$c。 (我通常同意词法范围仅限于一个eval ed代码的默认值,因此我希望有一些有意识的修改代码才能完成上述工作。)

考虑的方法

我尝试使用use PadWalker qw( peek_my );将词法范围保存在每个代码片段的末尾,旨在将其加载到以下代码段的范围内,但后来我意识到这会使代码的返回值无法访问片段,调用代码需要。

另一个替代方案似乎是模式匹配(可能使用专用解析器)所有my - perl代码中的声明片段并且实际上是动态转换它们,但这相当于一个相当大的任务。 / p>

讨论的模板示例(请参阅注释)

\perlExec{
    use PDL;
    my $v = vpdl [ 1, 2 ];
    my $w = vpdl [ 3, 4 ];
    sub list ($) {
            my $pdl = shift;
            return join ',', map { at( $pdl, $_, 0 ) } 0..1;
    }
}
The vector [ \perlValue{ list $v } ]
plus the vector [ \perlValue{ list $w } ]
makes [ \perlValue{ my $s = $v + $w; list $s } ].

3 个答案:

答案 0 :(得分:4)

也许您想使用Eval::WithLexicals?它完全符合您的要求。它旨在为REPL提供动力,它是纯粹的Perl。您只需创建Eval::WithLexicals的新实例,然后调用$ewl->eval($code)而不是eval $code,变量将在同一对象的连续调用之间保持不变。

答案 1 :(得分:2)

正如我评论的那样,我的感觉是这是一个XY Problem因为可能存在其他解决潜在问题的方法。从评论中的讨论中可以看出,您似乎正在实施自己的模板系统,因此我的第一个建议是查看现有的模板系统,例如Template::Toolkit

如果您仍想坚持使用当前的方法,那么似乎@hobbs已经给出了答案,似乎直接回答了您的问题,Eval::WithLexicals更新:自接受后) 。正如我提到的,我看到了另外两种可能的解决方案第一个对我个人来说最自然的,就是不要在第一个地方使用词汇。当我看到你所展示的代码时:

\perlExec{ my $v = [ 1, 2 ]; }
The vector [ \perlValue{ $v } ]
然后,仅仅因为括号,我不会感到惊讶,每个人都有自己的词汇范围。如果您改为使用包变量,那对我来说似乎更自然。例如,您可以使用eval qq{ package $packname; no strict "vars"; $code }(当然还有strict "vars"被禁用的警告),或者您可以在整个过程中使用完全限定的变量名称($package::v)。

我提到的第二件事是将整个输入文件转换为Perl脚本和eval - 换句话说,编写自己的模板系统。虽然我建议重新发明此轮子作为最后选项,但您确实询问如何根据您的目的调整this code I wrote,所以现在就是这样。以下的一个限制是代码块中的任何大括号必须是平衡的(但是请参见下面的更新),并且由于这是一个有点简单的演示,因此必然会有更多限制。使用风险自负!

use warnings;
use strict;
use feature qw/say state/;
use Data::Dumper;
use Regexp::Common qw/balanced/;
use Capture::Tiny qw/capture_stdout/;
my $DEBUG = 1;

local $/=undef;
while (my $input = <>) {
    my $code = translate($ARGV,$input);
    $DEBUG and say ">>>>> Generated Code:\n", $code, "<<<<<";
    my ($output, $rv) = capture_stdout { eval $code };
    $rv or die "eval failed: ".($@//'unknown error');
    say ">>>>> Output:\n", $output, "<<<<<";
}

sub translate {
    my ($fn,$input) = @_;
    state $packcnt = 1;
    $fn =~ tr/A-Za-z0-9/_/cs;
    my $pack = "Generated".$packcnt++."_$fn";
    my $output = "{ package $pack;\n";
    $output.= "no warnings; no strict;\n";
    $output.= "#line 1 \"$pack\"\n";
    while ( $input=~m{ \G (?<str> .*? ) \\perl(?<type> Exec|Value )
                     (?<code> $RE{balanced}{-parens=>'{}'} ) }xsgc ) {
        my ($str,$type,$code) = @+{qw/str type code/};
        $output.= "print ".perlstr($str).";\n" if length($str);
        ($code) = $code=~/\A\s*\{(.*)\}\s*\z/s or die $code;
        $code .= ";" unless $code=~/;\s*\z/;
        $code = "print do { $code };" if $type eq 'Value';
        $output.= "$code\n";
    }
    my $str = substr $input, pos($input)//0;
    $output.= "print ".perlstr($str).";\n" if length($str);
    $output.= "} # end package $pack\n1;\n";
    return $output;
}

sub perlstr { Data::Dumper->new([''.shift])
    ->Terse(1)->Indent(0)->Useqq(1)->Dump }

输入文件:

\perlExec{
    use warnings; use strict;
    print "Hello, World\n";
    my $v = [ 1, 2 ];
    my $w = [ 3, 4 ];
    sub list ($) {
        my $pdl = shift;
        return join ',', @$pdl;
    }
}
The vector [ \perlValue{ list $v } ]
plus the vector [ \perlValue{ list $w } ]
makes [ \perlValue{ my $s = [@$v + @$w]; list $s } ].

输出:

>>>>> Generated Code:
{ package Generated1_input_txt;
no warnings; no strict;
#line 1 "Generated1_input_txt"

    use warnings; use strict;
    print "Hello, World\n";
    my $v = [ 1, 2 ];
    my $w = [ 3, 4 ];
    sub list ($) {
        my $pdl = shift;
        return join ',', @$pdl;
    }
;
print "\nThe vector [ ";
print do {  list $v ; };
print " ]\nplus the vector [ ";
print do {  list $w ; };
print " ]\nmakes [ ";
print do {  my $s = [@$v + @$w]; list $s ; };
print " ].\n";
} # end package Generated1_input_txt
1;
<<<<<
>>>>> Output:
Hello, World

The vector [ 1,2 ]
plus the vector [ 3,4 ]
makes [ 4 ].
<<<<<

更新:正如@HåkonHægland在评论中所建议的那样,可以使用PPR来解析块。所需的唯一更改是将use Regexp::Common qw/balanced/;替换为use PPR;,并在正则表达式中将(?<code> $RE{balanced}{-parens=>'{}'} )替换为(?<code> (?&PerlBlock) ) $PPR::GRAMMAR - 然后解析器也会处理print "Hello, World }\n";这样的情况!

答案 2 :(得分:0)

这是一种方法:解析模板文件两次。在第一次解析时,将模板中的Perl语句写入临时文件(例如/tmp/MyTemplate.pm),将一些标题代码添加到此文件中,以使其成为有效的Perl模块。同时对\perlValue语句的包变量使用顺序编号,即将第一个\perlValue{ list $v }翻译成例如:our $perl_value1 = list $v;,下一个\perlValue{ list $w }变为our $perl_value2 = list $w;等等在.. ..

然后需要模块:require "/tmp/MyTmplate.pm";然后再次解析模板,从MyTemplate的符号表中提取模板中与Perl代码对应的正确值。例如,要获取\perlValue{ list $v }的值,请使用$MyTemplate::perl_value1等等。