A Look at Conditional Compiling of Diagnostics
From time to time, I add diagnostic messages to be printed by my code so I can track what the program is doing. I often wondered if there was a way to get Perl to conditionally compile these statements and to get it to do so automatically. Here's how:
Conditional Compiling
Perl conditional compiles code if the condition of an if
statement is constant.
Here is an example of the print
being compiled out:
$ perl -e'if( 0 ) { print"Hello\n"; }'
We can see what is actually happening with Deparse
:
$ perl -MO=Deparse -e'if( 0 ) { print"Hello\n"; }' '???'; -e syntax OK
Perl replaces the if
statement with '???';
,
which does nothing. If we can the 0
in the if
to
a 1
:
$ perl -MO=Deparse -e'if( 1 ) { print"Hello\n"; }' do { print "Hello\n" }; -e syntax OK
The if
statement is replaced with a do
.
But we don't want to have to go thru all the code and replace 0s with 1s to see the diagnostics and back again to get rid of them. Is there a way to do this from one place? Yes, there is.
Inline Subroutines
Perl will in-line subroutines if they are prototyped with no parameters and consist of only a constant. This, for example:
$ perl -MO=Deparse -e'sub hello () { "Hello"; }; print hello, "\n";' sub hello () { 'Hello' } print 'Hello', "\n"; -e syntax OK
The subroutine call gets replaced with its contents, in this case, the
string, 'Hello'
.
If fact, that's how the pragmatic, constant
works:
perl -MO=Deparse -e'use constant hello => "Hello"; print hello, "\n";' use constant ('hello', 'Hello'); print 'Hello', "\n"; -e syntax OK
Conditional Diagnostics
Putting what we learned above into a script, we can have conditional diagnostics:
#!/usr/bin/env perl use strict; use warnings; use constant _DIAGNOSTIC => 0; if( _DIAGNOSTIC ){ print "hello\n"; }
Running it thru Deparse
shows:
$ perl -MO=Deparse myscript use constant ('_DIAGNOSTIC', 0); use warnings; use strict; '???'; myscript syntax OK
And changing the 0 to 1:
$ perl -MO=Deparse myscript use constant ('_DIAGNOSTIC', 1); use warnings; use strict; do { print "hello\n" }; myscript syntax OK
So, now we can control the diagnostics messages from one place. But this is still not good enough. The problem being is that you have to remember to change all the 1s back to 0s before you put the code into production, and you know that isn't always going to happen. Is there a simple way to toggle diagnostics externally? Well, yes there is.
It's All in the Name
Change myscript
to this:
#!/usr/bin/env perl use strict; use warnings; use constant _DIAGNOSTIC => $0 =~ /_D(?:\.pl)?$/ ? 1 : 0; if( _DIAGNOSTIC ){ print "hello\n"; }
The complex expression for _DIAGNOSITC
is the name of the
script contained in $0
which is matched to a _D
(with an optional .pl
) at the end of its name. If it is, then
1 is used as the constant, else 0.
Now create a symbolic link in the directory:
$ ln -s ./myscript myscript_D
The results of Deparse
of myscript
:
$ perl -MO=Deparse myscript use constant ('_DIAGNOSTIC', $0 =~ /_D(?:\.pl)?$/ ? 1 : 0); use warnings; use strict; '???'; myscript syntax OK
And of myscript_D
:
$ perl -MO=Deparse myscript_D use constant ('_DIAGNOSTIC', $0 =~ /_D(?:\.pl)?$/ ? 1 : 0); use warnings; use strict; do { print "hello\n" }; myscript_D syntax OK
Summary
With knowledge of Perl's workings, as discovered using
Deparse
, it is possible to create conditionally compiled
diagnostics that don't require constant editing of the code. And always
having a non-diagnostic, production quality code in your code.
Update
Thanks to luben for a better way to invoke the diagnostics via an environment variable. myscript
becomes:
#!/usr/bin/env perl use strict; use warnings; use constant DEBUG = $ENV{DEBUG}; if( DEBUG ){ print "hello\n"; }
To run with the diagnostics:
DEBUG=1 myscript
This comment has been removed by the author.
ReplyDeleteWhat about:
ReplyDeleteperl -MO=Deparse -E 'use constant DEBUG => $ENV{DEBUG}; say "Diagnostics" if DEBUG'
I prefer conditionally modifying the behavour using environment variables. I think it is the standard way.
PS: Deleted the previous comment because it contained a typo