为什么这个程序是有效的?我试图创建一个语法错误。

525

我在Windows 7上运行ActiveState的32位ActivePerl 5.14.2。我想玩一下Git的预提交挂钩,以检测被检入的程序是否存在语法错误。(不知何故,我刚刚犯了这样一个糟糕的提交)。因此,作为测试程序,我随机写下了以下内容:

use strict;
use warnings;

Syntax error!

exit 0;

然而,它能够编译并且执行没有警告,退出时errorlevel为零。这是如何有效的语法?


138
你刚刚证明了输入随机单词到 Perl 中会产生可运行的程序?!?!? - Peter M
13
@PeterM 并不是随意的单词。我证明了我对 Perl 语法不够了解。现在我知道更多了。 - Bill Ruppert
11
你可能希望使用“无间接”的选项来阻止这些情况的发生。 - LeoNerd
3
这是有史以来最著名的Perl问题。甚至比Schwartz的代码片段更好:whatever / 25 ; # / ; die "this dies!"; - clt60
由语言学家Larry Wall编写的Perl允许作者有很大的创作空间。在perl编程中有一个子类别叫做Perl诗歌,它是有效的Perl表达,超越了计算机的解释:https://www.perlmonks.org/?node_id=1111395 - bbaassssiiee
6个回答

573
Perl有一种称为“间接方法表示法”的语法。它允许使用对象引用来调用方法。
Foo->new($bar)

写成

new Foo $bar

这意味着
Syntax error ! exit 0;

error->Syntax(! exit 0);

或者

error->Syntax(!exit(0));

不仅是有效的语法,而且它不会导致运行时错误,因为首先执行的是 exit(0)


此功能可以通过以下任何一种方式禁用:

no feature qw( indirect );  # Perl 5.32+

use v5.36;                  # Perl 5.36+

no indirect;                # CPAN module

1
好吧,也许是因为我不懂perl,但在许多其他语言中,只有当exit的返回值是布尔值(或其他可以成为!操作数的内容)时,才能起作用,即仅仅是一个表达式是不够的。 - user377628
3
我读到的是"语法错误!退出0;",但我没有考虑到间接调用。花了很多时间才忘记这个问题! - Bill Ruppert
6
@Hassan,这样想,!exit(0)!$x都没有类型错误,因为它们都没有类型。 - ikegami
12
@Hassan,计算机语言有不同的类型。具体来说,数值也有它们自己的类型。运算符和子程序并不仅限于返回特定类型的数值。这种设计以极小的代价提供了非常大的实用性(感谢警告机制)。 - ikegami
6
@Nawaz,实际上这很受欢迎。在使用Java和C ++构建对象的所有人以及大量使用new Classprint $fh ...而不是Class->new(...)$ fh->print(...)的Perl程序员中都会使用它。我承认它会导致奇怪的错误消息,但这也是它的特点。 - ikegami
显示剩余5条评论

118

我不知道为什么,但这就是Perl的结果:

perl -MO=Deparse -w yuck
BEGIN { $^W = 1; }
use warnings;
use strict 'refs';
'error'->Syntax(!exit(0));
yuck syntax OK

看起来解析器认为你在error对象上调用了Syntax方法...这确实很奇怪!


3
这是间接方法调用语法。它(有点)在这里工作是因为 exit(0) 先被评估,使程序在尝试将结果传递给'error'->Syntax()之前退出。 - user149341
7
Perl似乎默认使用"间接(对象)语法",通常使用new Class而不是Class->new()。为了调用方法Syntax,执行了exit函数,因此运行时错误从未发生。 - amon
119
恭喜你,你找到了一个需要添加分号才能使编译失败的程序。 - mob
"use strict"; "use warnings";error->Syntax(! print "hi"); 这段代码在perl -MO=Deparse下是语法正确的,但由于使用了"use warnings",它可能会提示一些问题,因为它可以发现某些模块没有被加载。相反,它会抛出一个运行时错误“Can't locate object method .. ”。 - user4401178

57
你没有收到错误信息的原因是第一行执行的代码是:
exit(0);

因为您在第一行缺少了一个分号:
Syntax error!

编译器会(错误地)猜测这是带有非运算符!的子例程调用。然后,它将执行这个子例程的参数,这恰好是exit(0),此时程序退出并将错误级别设置为0。不会执行其他任何操作,因此不会报告更多的运行时错误。
您会注意到,如果将exit(0)更改为类似于print "Hello world!"之类的内容,则确实会出现错误:
Can't locate object method "Syntax" via package "error" ...

然后您的错误级别将被设置:

> echo %errorlevel%
255

7
编译器不会犯错误地“猜测”。 - Liam Laverty
16
可以。它可能会错误地猜测人类的意思。 - TLP
4
在这个方程式中,人是不正确的因素。编译器只能被看作“正确”或“损坏”的状态。它无法对语言定义或用户意图发表意见。 - Liam Laverty
4
如果编译器能猜测用户在这种情况下的意图,那将是一个相当不错的编译器。但是,由于编译器无法正确猜测,因此它并不能做到。你可能正在对我的陈述进行一些技术行话分析,而这是错误的阅读方式。请注意,我补充说明一下。 - TLP
这不是一个解释器吗?;-) - Rikki
@Rikki 可能是一位解释器。 :P - TLP

35

如上所述,这是由于间接方法调用符号引起的。您可以对此发出警告:

注意:以上内容仅供参考。


use strict;
use warnings;
no indirect;

Syntax error!

exit 0;

生成:

Indirect call of method "Syntax" on object "error" at - line 5.

这需要使用indirect CPAN模块

你也可以使用no indirect "fatal";让程序崩溃(这是我的做法)


从 Perl 5.32 开始,您可以禁用间接特性,而无需添加 CPAN 模块: use v.5:32; no feature 'indirect'; - Rsh

6

试试Perl 6, 它似乎更容易满足您的期望:

===SORRY!=== Error while compiling synerror.p6
Negation metaoperator not followed by valid infix
at synerror.p6:1
------> Syntax error!⏏<EOL>
    expecting any of:
        infix
        infix stopper

3
在这篇论文中,我们旨在回答编程语言社区长期存在的一个问题:是否可能在不创建有效的Perl的情况下涂抹墙壁?
TLDR; 几乎不可能。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接