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

在perl中的连续eval语句之间共享词法范围,perl,eval,lexical-scope,Perl,Eval,Lexical Scope,我是否可以让不同的evaled Perl代码片段共享相同的词法范围,并获得它们的返回值 背景 Perl的eval命令将字符串计算为Perl代码,成功后返回该代码中最后一条语句的值。但是,在该代码中创建的词汇变量会在代码末尾被删除。这意味着当代码1的评估结束,并且我有第二个代码块code2,它引用code1中设置的词法变量时,这将失败 my $code1 = 'my $c = 4'; my $code2 = 'printf "%g\n", $c;'; printf 'evaluated "%s"

我是否可以让不同的evaled Perl代码片段共享相同的词法范围,并获得它们的返回值

背景

Perl的eval命令将字符串计算为Perl代码,成功后返回该代码中最后一条语句的值。但是,在该代码中创建的词汇变量会在代码末尾被删除。这意味着当代码1的评估结束,并且我有第二个代码块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代码,因此我希望需要对代码进行一些有意识的修改,以使上述工作正常进行

考虑的方法

我尝试使用PadWalker qw peek____;在每个代码段的末尾保存词法作用域,目的是将其加载到下面代码段的作用域中,但后来我意识到这将使调用代码所需的代码段返回值无法访问

另一种选择似乎是使用一个专用的解析器进行模式匹配,可能会使用perl代码片段中的所有声明,并在运行中进行转换,但这将相当于一项更大的任务

有关讨论的模板示例,请参见备注


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


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

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


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

也许您想使用?它完全符合你的要求。它被设计为支持REPLs,它是纯Perl的。您只需创建一个Eval::WithLexicals的新实例,然后调用$ewl->Eval$code而不是Eval$code,变量将在对同一对象的连续调用之间保持不变。

也许您想使用?它完全符合你的要求。它被设计为支持REPLs,它是纯Perl的。您只需创建一个Eval::WithLexicals的新实例,然后调用$ewl->Eval$code而不是Eval$code,变量将在对同一对象的连续调用之间保持不变。

正如我所评论的,我的感觉是这是一个问题,因为可能有其他解决方案来解决潜在问题。从评论中的讨论可以看出,您似乎正在实现自己的模板系统,因此我的第一个建议是查看现有的模板系统,例如maybe

如果你仍然想坚持你目前的方法,那么@hobbs给出的答案似乎直接回答了你的问题,更新:自接受以来。正如我提到的,我看到了另外两种可能的解决办法。首先,我个人觉得最自然的是,首先不要使用词汇。当我看到你展示的代码时:

\perlExec{ my $v = [ 1, 2 ]; }
The vector [ \perlValue{ $v } ]
那么,仅仅是因为大括号,我不会惊讶于每一个大括号都有自己的词汇范围。如果改为使用包变量,我会觉得更自然。例如,您可以使用evalqq{package$packname;no strict vars;$code},当然要注意禁用strict vars,或者您可以在整个过程中使用完全限定的变量名$package::v

我提到的第二件事是将整个输入文件翻译成一个Perl脚本并评估它——换句话说,编写您自己的模板系统。虽然我会 我只建议你最后一个选择是重新发明这个轮子,你问过如何适应你的目的,所以就在这里。下面的一个限制是,代码块中的任何大括号都必须是平衡的,但请参见下面的更新,因为这是一个有点简单的演示,所以肯定会有更多的限制。使用风险自负

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在评论中所建议的,可以使用解析出这些块。唯一需要的更改是替换use Regexp::Common qw/balanced/;使用PPR;在正则表达式中替换$RE{balanced}{-parens=>'{}}by&PerlBlock$PPR::GRAMMAR-然后解析器将处理诸如print Hello,World}\n;也是

正如我所评论的,我的感觉是这是一个潜在问题的解决方案。从评论中的讨论可以看出,您似乎正在实现自己的模板系统,因此我的第一个建议是查看现有的模板系统,例如maybe

如果你仍然想坚持你目前的方法,那么@hobbs给出的答案似乎直接回答了你的问题,更新:自接受以来。正如我提到的,我看到了另外两种可能的解决办法。首先,我个人觉得最自然的是,首先不要使用词汇。当我看到你展示的代码时:

\perlExec{ my $v = [ 1, 2 ]; }
The vector [ \perlValue{ $v } ]
那么,仅仅是因为大括号,我不会惊讶于每一个大括号都有自己的词汇范围。如果改为使用包变量,我会觉得更自然。例如,您可以使用evalqq{package$packname;no strict vars;$code},当然要注意禁用strict vars,或者您可以在整个过程中使用完全限定的变量名$package::v

我提到的第二件事是将整个输入文件翻译成一个Perl脚本并评估它——换句话说,编写您自己的模板系统。虽然我只建议您将这个轮子作为最后一个选择进行改造,但您确实问过如何适应您的目的,所以这里就是。下面的一个限制是,代码块中的任何大括号都必须是平衡的,但请参见下面的更新,因为这是一个有点简单的演示,所以肯定会有更多的限制。使用风险自负

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在评论中所建议的,可以使用解析出这些块。唯一需要的更改是替换use Regexp::Common qw/balanced/;使用PPR;在正则表达式中替换$RE{balanced}{-parens=>'{}}by&PerlBlock$PPR::GRAMMAR-然后解析器将处理诸如print Hello,World}\n;也是

我不明白为什么不能把词法变量移到外部范围?{my$c;…;eval$code1;eval$code2;}为什么您首先使用eval,而不是两个子例程?听起来像是个错误。为什么要逐行评估Perl代码?另外,如果你能控制被评估的代码,为什么还要使用词汇表呢?我认为如果你能展示一个演示你所描述的问题的配置模板的最小示例,那会很有帮助。我的怀疑是,有一个不同的/更好的整体解决方案,而不是跨evals保存词汇。我不明白为什么不能将词汇变量移到外部范围?{my$c;…;eval$code1;eval$code2;}为什么您首先使用eval,而不是两个子例程?听起来像是个错误。为什么要逐行评估Perl代码?另外,如果你能控制被评估的代码,为什么还要使用词汇表呢?我认为如果你能展示一个演示你所描述的问题的配置模板的最小示例,那会很有帮助。我的怀疑是,有一个不同/更好的整体解决方案比在evals中保留词汇更好。感谢您为此付出的努力,并提到模板工具包,它在某种程度上也属于这里。然而,当我不久前第一次使用该工具包时,我的印象是perl代码和模板文本之间的距离太远,编辑器无法轻松编写文档。这就是为什么我最终没有走那条路。我认为Eval::WithLexicals完全符合我的问题,因此我接受了这个答案。很好的例子!你觉得这里可以用吗?也就是说:为了提高Perl解析器的健壮性。感谢您在这方面所做的努力,以及提到模板工具包,它在某种程度上也属于这里。然而,当我不久前第一次使用该工具包时,我的印象是perl代码和模板文本之间的距离太远,编辑器无法轻松编写文档。这就是为什么我最终没有走那条路。我认为Eval::WithLexicals完全符合我的问题,因此我接受了这个答案。很好的例子!你觉得这里可以用吗?即:提高Perl解析器的健壮性。。