Save the following program to /tmp/quine.pl
Illegal division by zero at /tmp/quine.pl line 1.
Run it with perl /tmp/quine.pl
and it prints its own source code.
It’s easy to make a “cheating quine” in many languages, where a syntax error in the source provokes the parser to emit an error message that matches the source. I posted several cheating quine examples on Twitter including
File "quine.py", line 1
File "quine.py", line 1
^
IndentationError: unexpected indent
[ addendum 2023-03-05: Rob Pike attributes this kind of quine to Ron Hardin ]
The Perl quine at the start of this post is a different kind of cheat:
the program parses OK, and it runs briefly until the division by zero
error is raised. It is quite sensitive to details of the filename: for
example ./quine.pl
does not work.
This error message is a program?!
This little program gets into a lot of perl’s do-what-I-mean parsing.
The /
character is quite context-sensitive, and can be parsed as a
division operator or the start of a regex. Small perturbations of this
program make it into a regex parse error rather than runnable code. In
this case both /
appear in an operator context.
The other non-words in this program are 1.
, which is just a number,
and .
which is the concatenation operator.
So what do the words mean?
Bare words in Perl can be subroutine names, method names, package or class names, or (in non-strict mode) un-delimited strings, and maybe other things I have forgotten!
Perl also has an unusual method invocation syntax called “indirect object syntax” which has the form
method object args
most frequently seen looking like
print $filehandle "message";
my $instance = new Class(args);
although Perl’s preferred syntax is
$filehandle->print("message");
my $instance = Class->new(args);
The perlobj
documentation says
To parse this code, Perl uses a heuristic based on what package names it has seen, what subroutines exist in the current package, what barewords it has previously seen, and other input. Needless to say, heuristics can produce very surprising results!
How does it parse?
Starting from the right,
pl line 1.
is parsed as the method call
line->pl(1.)
where line
is a package (class) name and pl
is the method.
In the middle of the program, at
, tmp
, and quine
are parsed
as barewords, i.e. strings. The expression parses as:
(("at" / "tmp") / "quine") . line->pl(1.)
On the left there are two nested indirect object method calls,
division->Illegal(zero->by( ... ))
The innermost expression, which gets evaluated first, is
"at" / "tmp"
And this immediately raises a division by zero exception.