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

Share lexical scope between successive eval statements in perl

我能否使 evaled Perl 代码的不同片段共享相同的词法范围并获得它们的 return 值?

背景

Perl 的 eval 命令将字符串作为 Perl 代码求值,成功后 returns 是该代码中最后一条语句的值。但是,在该代码中创建的词法变量在代码末尾被删除。这意味着当 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。 (我通常同意默认情况下,词法范围仅限于一个 evaled 代码,因此我希望需要对代码进行一些有意识的修改才能使上述工作正常进行。)

考虑的方法

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

另一种选择似乎是模式匹配(可能使用专用解析器)所有 my-perl 代码片段中的声明,并基本上即时翻译它们,但这将是一个相当大的任务.

供讨论的模板示例(见评论)

\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 } ].

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

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

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

正如我评论的那样,我的感觉是 XY Problem in that there may be other solutions to the underlying problem. As it turns out from the discussion in the comments, you seem to be implementing your own templating system, so my first suggestion would be to have a look at existing ones, such as maybe 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"; 这样的情况!