Perl可以“静态”解析吗?

时间:2009-08-14 23:01:07

标签: perl parsing runtime interpreter dynamic-languages

article called "Perl cannot be parsed, a formal proof"正在进行巡视。那么,Perl是否在“运行时”或“编​​译时”决定了其解析代码的含义?

在我读过的一些讨论中,我得到的印象是论据来源于不精确的术语,所以请尝试在答案中定义您的技术术语。我故意没有定义“运行时”,“静态”或“解析”,以便我可以从那些可能以不同方式定义这些术语的人那里获得观点。

编辑:

这与静态分析无关。这是关于Perl行为的理论问题。

5 个答案:

答案 0 :(得分:19)

Perl有一个明确定义的“编译时”阶段,后面是明确定义的“运行时”阶段。但是,有一些方法可以从一个过渡到另一个。许多动态语言都有eval构造,允许在运行时阶段编译新代码;在Perl中,反转也是可能的 - 并且很常见。 BEGIN块(以及由BEGIN引起的隐式use块)在编译时调用临时运行时阶段BEGIN块在编译后立即执行,而不是等待编译单元的其余部分(即当前文件或当前eval)进行编译。由于BEGIN在编译之前的代码运行之前运行,它们几乎可以以任何方式影响以下代码的编译(尽管在实践中,他们做的主要事情是导入或定义子例程,或者启用严格性或警告)。

use Foo;基本上等同于BEGIN { require foo; foo->import(); },要求是(如eval STRING)从运行时调用编译时的方法之一,这意味着我们现在正在编译中 - 在编译时运行时内的时间,整个事情是递归的。

无论如何,解析Perl的可解决性是因为一位代码的编译会受到前一段代码的 execution 的影响(理论上可以做任何事情),我们遇到了一个停止问题的情况;正确解析一般的给定Perl文件的唯一方法是执行它。

答案 1 :(得分:11)

Perl有BEGIN块,它在编译时运行用户Perl代码。此代码可能会影响要编译的其他代码的含义,从而使得“无法”解析Perl。

例如,代码:

sub foo { return "OH HAI" }

是“真的”:

BEGIN {
    *{"${package}::foo"} = sub { return "OH HAI" };
}

这意味着有人可以编写Perl,如:

BEGIN {
    print "Hi user, type the code for foo: ";
    my $code = <>;
    *{"${package}::foo"} = eval $code;
}

显然,没有静态分析工具可以猜出用户在这里输入的代码。 (如果用户说sub ($) {}而不是sub {},它甚至会影响在整个程序的其余部分中对foo的调用的解释,可能会导致解析失败。)

好消息是,不可能的案件非常严重;技术上可行,但在实际代码中几乎肯定无用。因此,如果您正在编写静态分析工具,这可能会给您带来麻烦。

公平地说,每一种值得盐的语言都有这个问题,或类似的东西。举个例子,在这个Lisp代码中抛出你最喜欢的代码walker:

(iter (for i from 1 to 10) (collect i))

您可能无法预测这是一个产生列表的循环,因为iter宏是不透明的,需要特殊的知识才能理解。实际情况是这在理论上很烦人(我无法理解我的代码而不运行它,或者至少运行iter宏,这可能永远不会停止使用此输入),但在实践中非常有用(迭代很容易让程序员编写,而未来的程序员也可以阅读。

最后,很多人认为Perl缺乏静态分析和重构工具,就像Java一样,因为解析它相对困难。我怀疑这是真的,我只是认为没有必要,没有人愿意写它。 (人们需要一个“lint”,所以有Perl :: Critic,例如。)

我需要做Perl生成代码的静态分析(一些用于维护测试计数器和Makefile.PL的emacs宏)运行良好。奇怪的角落案件会丢掉我的代码吗?当然,但即使我可以,我也不会忘记编写无法维护的代码。

答案 2 :(得分:5)

人们用了很多词来解释各个阶段,但这真的很简单。在编译Perl源代码时,perl intrepreter最终可能会运行代码来改变代码的其余部分将如何解析。没有代码的静态分析会错过这个。

在Perlmonks的帖子中,Jeffrey谈到他在The Perl Review中的文章更详细,包括每次运行时都不会以相同方式解析的示例程序。

答案 3 :(得分:3)

Perl有一个编译阶段,但它与代码中的大多数正常编译阶段不同。 Perl的词法分析器将代码转换为标记,然后解析器分析标记以形成操作树。但是,BEGIN {}块可以中断此过程并允许您执行代码。在做use时。所有BEGIN块在其他任何块之前执行,为您提供了设置模块和命名空间的方法。在脚本的整体“编译”期间,您很可能会使用Perl来确定Perl模块完成时的外观。 sub,bare,意味着将它添加到包的glob中,但你不必这样做。例如,这是在模块中设置方法的一种(尽管很奇怪)方法:

package Foo;

use strict;
use warnings;
use List::Util qw/shuffle/;

my @names = qw(foo bar baz bill barn);
my @subs = (
    sub { print "baz!" },
    sub { die; },
    sub { return sub { die } },
);
@names = shuffle @names;
foreach my $index (0..$#subs) {
   no strict 'refs';
   *{$names[$index]} = $subs[$index];
}

1;

来解释它,甚至知道它的作用!它不是很有用,但它不是你可以提前确定的东西。但它是100%有效的perl。虽然这个功能可以被滥用,但它也可以执行很棒的任务,比如以编程方式构建看起来非常相似的复杂子。这也使得很难确切地知道所做的一切。

这并不是说perl脚本不能被'编译' - 在perl中,编译只是在确定,那么模块应该是什么样的。你可以用

做到这一点
perl -c myscript.pl

它会告诉你它是否可以到达开始执行主模块的程度。你不能仅仅通过静态地看待它来知道。

然而,正如PPI所示,我们可以接近。真的很近足够接近做非常有趣的事情,比如(几乎是静态的)代码分析。

然后,

“运行时间”成为所有BEGIN块执行后发生的事情。 (这是一个简化;还有更多内容。请参阅perlmod了解更多。)它仍然运行perl代码,但它是一个单独的执行阶段,在所有更高优先级的块运行之后完成。 / p>

chromatic在他的Modern :: Perl博客上有一些详细的帖子:

答案 4 :(得分:2)

C ++在其模板系统中也存在类似问题,但这并不能阻止编译器编译它。他们将在适用这种论点的角落案件中爆发或永远运行。