3

有没有办法在不运行 perl 的情况下检查 Perl 程序的语法?众所周知的答案是“不”。如果不启动完整的 perl 运行时来评估导入代码等,您将无法判断程序语法是否正确。

但是如果你想要一个大概的答案呢?一个语法检查器,会说“坏”或“可能”。如果“不好”,那么程序肯定不是有效的 perl 代码(假设是普通的 perl 解释器)。如果“可能”,那么它看起来还可以,但只有 perl 本身才能确定。

一个总是打印“可能”的程序显然是这样一个检查器,但不是一个非常有用的检查器。更好的尝试是使用 PPI。可能有一些有效的 Perl 程序被 PPI 拒绝,但如果发生这种情况,它会被认为是 PPI 错误(我认为)。

题外话:为什么这有用?一种用途可能是 kwalitee 检查。为了捕捉各种“d'oh”时刻,$WORK 的版本控制系统在允许提交之前通过 perl -c 运行所有 Perl 代码。(我不建议将此作为一般做法,只是指出它在我们的站点上很有用。)但是 perl -c 是不安全的,因为它执行代码(必须执行)。使用保守的语法检查器会更安全,但代价是检查器会说“可能”,但实际上程序不是有效的 Perl。

我真正想要的(题外话结束):但事实上,安全并不是我当前申请的动机因素。我对速度感兴趣。有没有办法在启动整个 Perl 解释器之前粗略检查并拒绝格式错误的 Perl 代码?PPI 比 perl 本身慢,所以不是一个好的候选者。您可以编写一个近似的 Perl 语法并使用解析器生成器来构建一个简单的 C 程序,该程序接受或拒绝伪 Perl。

我的应用程序是“增量调试”。您从一个具有特定属性(例如,段错误)的大型程序开始,并在保留该属性的同时删除它的部分。我使用 http://delta.tigris.org/,它以简单的面向行的方式工作。它生成的许多测试用例都不是有效的 Perl 代码。如果可以在完整的 perl 可执行文件启动之前快速消除这些,delta 调试会更快。

由于启动 perl 解释器的开销可能是花费时间的最大部分,您可以实现某种服务器,它在套接字上侦听,接受程序文本,并通过尝试 eval() 返回“坏”或“可能”文本或通过 PPI 运行它。

另一种加快速度的方法是让 perl 更快地失败。通常它会打印它可以找到的所有语法错误和诊断信息。如果它改为停在第一个上,则可以节省一些时间。

但我确实喜欢几乎是 Perl 的语法的想法,它可以通过一个简单的 C 程序来检查。这样的事情存在吗?

(相关:Perl 浅语法检查?即不检查导入的语法,但我的问题更多是关于速度,我很高兴粗略检查将接受一些无效程序,只要它不拒绝有效程序。 )

4

2 回答 2

3

给定源过滤器、原型和 Perl (5.14+) 关键字 API,导入可以从根本上改变哪些语法有效,哪些无效。如果您导入任何东西,那么这样的检查将几乎没有用。

如果您 import nothing,那么您可能可以安全地使用require而不是加载所有外部模块use,并且perl -c会变得快如闪电(因为require在运行时处理)。

PPI在这里并不是特别有用,因为它在解析时采用了一种非常宽容的最佳猜测方法,因此会毫无怨言地接受非常无效的输入:

#!perl
use strict;
use warnings;
use PPI::Document;
use PPI::Dumper;

PPI::Dumper->new(
   PPI::Document->new(\"foo q[}")
)->print;

Perl::Lexer可能会更有帮助,尽管它只会检测到无法被标记化的错误。我之前的例子恰好是其中之一,所以这确实抱怨:

#!perl
use strict;
use warnings;
use Perl::Lexer;

print $_->inspect, $/
   for @{ Perl::Lexer->new->scan_string("foo q[}") };

尽管如此,在词法分析之前应用了 Perl 关键字 API、Devel::Declare 和源过滤器之类的东西,因此如果您导入任何利用这些技术的模块,Perl::Lexer 将被卡住。(这些技术中的任何一种都可以轻松生成foo q[}有效的语法。)

Compiler::LexerCompiler::Parser可能会有一些用处。以下转储核心:

#!perl
use strict;
use warnings;
use Compiler::Lexer;
use Compiler::Parser;

my $t = Compiler::Lexer->new("eg.pl")->tokenize("foo q[}");
my $a = Compiler::Parser->new->parse($t);

如果您将不匹配的引号更正foo q[}foo q[],则它不再转储核心。这似乎是一个结果。;-)

最终,答案取决于您编写的代码类型以及您希望发现的错误类别。perl -c会给你一个相当严格的语法检查。Perl::Lexer 可能更快,但它不会发现大类错误。Compiler::Lexer/Compiler::Parser 将来可能有用,但现在似乎表现不正常。

就个人而言,我会坚持使用perl -c,如果它太慢,请尝试减少在编译时加载的模块数量,以支持运行时加载。

TL;DR:如果你想要静态分析,不要使用 Perl。

于 2014-05-08T14:13:33.497 回答
2

如果您想要的只是快速的可编译性检查,请有一个持续运行的 perl 进程来为您检查每个文件:

perl -MFile::Slurp -lne'print 0+!! eval "sub {" . read_file($_) . "}"'
于 2014-05-08T14:28:54.953 回答