The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Debug::Statements - provides an easy way to insert and enable/disable debug statements.

SYNOPSIS

The d() function prints the name of the variable AND its value.

This implementation been optimized to minimize your keystrokes.

Example code

my $myvar = 'some value';
my @list = ('zero', 1, 'two', "3");
my %hash = ('one' => 2, 'three' => 4);

use Debug::Statements;
my $d = 1;
d "Hello world";
d '$myvar';
d '@list %hash';

Output

DEBUG sub mysub:  Hello world
DEBUG sub mysub:  $myvar = 'some value'
DEBUG sub mysub:  @list = [
  'zero',
  1,
  'two',
  '3'
]
DEBUG sub mysub:  %hash = {
  'one' => 2,
  'three' => 4
}

BACKGROUND

Advantages of debug statements

"The most effective debugging tool is still careful thought, coupled with judiciously placed print statements" - Brian Kernighan, Unix for Beginners (1979)

  • Familiarity - everyone has used them.

  • When strategically placed, they show the values of key variables as well as the flow of control.

  • May be left in the code to facilitate debugging, when the code next needs to be enhanced.

  • May be turned on to help remotely debug problems.

  • Printing the names of executing subroutines can be particularly useful when debugging large unfamiliar programs produced by multiple developers over the span of years.

  • Can be used in conjuction with a debugger, which can be used to change variables on-the-fly, step into libraries, or skip/repeat sections of code

  • If the results are saved to a file, file comparisons can be useful during regression testing.

Traditional debug statement example

my $d = 1;
my $myvar = 'some value';
if ($d) { print "DEBUG sub xyz:  \$myvar is $myvar\n" }
use Dumpvalue;
if ($d) { print "\nDEBUG: Dumping \@list:\n"; Dumpvalue->new->dumpValue(\@list) }
if ($d) { print "\nDEBUG: Dumping \%hash:\n"; Dumpvalue->new->dumpValue(\%hash) }

Disadvantages of traditional "print" debug statements

  • Tedious, require many keystrokes to type

  • Reduces readability of the source code.

  • Print statements clutter the standard output

  • Need to be removed or commented out later

  • If some statements are mistakenly left in, the output can cause problems or confusion

  • The next time the code needs to be enhanced, any removed print statements need to be re-inserted or uncommented

Debug::Statements Example

Debug::Statements::d() provides an easy way to insert and enable/disable debug statements.

my $myvar = 'some value';
use Debug::Statements;
my $d = 1;
d '$myvar';

Output

DEBUG sub mysub:  $myvar = 'some value'

This is all you need to know to get started.

FEATURES

Arrays, hashes and refs

d '@list';
d '$list[2]';
d '$list[$i]';
d '%hash';
d '$nestedhash{key}';
d '$nestedhash{$key1}{$key2}';
d '$listref';
d '$arrayref';
d '$arrayref->[2]';
d '$hashref->{key}';
d '$hashref->{$key}';

Plain text can be entered as a comment

d 'Processing...';
d "This comment prints the value of a variable: $myvar";

Multiple debug levels

use Debug::Statements qw(d d2 d0 D);

my $d = 1;
d '$myvar';    # prints
d2 '$myvar';   # does not print since $d < 2

$d = 2;
d '$myvar';    # prints
d2 '$myvar';   # prints

D '$myvar';    # always prints, even if $d is 0 or undef
               # this is useful for short term debugging
               # of existing code

d0 '$myvar';   # same as D

Supports newlines or other characters before/after the variable

d '\n $myvar';
d '\n$myvar\n\n';
d '\n-------\n@list\n--------\n';

Multiple variables can be printed easily

d '$myvar $myvar2 $myvar3';
or
d '$myvar,$myvar2,$myvar3';
or
d '$myvar, $myvar2, $myvar3';
or
d '($myvar, $myvar2, $myvar3)';

Each of these examples prints one line each for $myvar, $myvar2, and $myvar3

Alternate syntax with parentheses

d('$myvar');

OPTIONS

Options may be specifed with an 2nd argment to d()

    b print suBroutine name (on by default)

    c Chomp newline before printing, useful when printing captured $line from a parsed input file

    e print # of Elements contained in top level of the array or hash

    n print line Number $. of the input file

    q treat the string as text, do not try to evaluate it. This is useful if you are parsing another Perl script, and the text contains sigil characters $@%

    r tRuncate output (defaults to 10 lines)

    s Sort contents of arrays (hashes are always sorted)

    t print Timestamp using localtime() and Time::HiRes::gettimeofday()

    x die when code reaches this line

    z compress array and hash dumps to save screen space

Examples

To print $line chomped and with line number and timestamp

d('$line', 'cnt');
   

To print %hash in a compressed format

d('%hash', 'z');

Negating options

To negate an option, capitialize it (use 'B' instead of 'b')

Persistent options

Options are only valid for the current debug statement

To make the current options global (peristent), append a star *

For example, to set timestamp globally

   d('$var', 't*');
	

For example, to unset timestamp globally

'$var', 'T*');

REQUIREMENTS

PadWalker must be installed

In addition, the test suites require Test::Fatal, Test::More, and Test::Output

$d variable

Your code must have a variable '$d' defined to enable the debug statements

Exception: D() does not require the $d variable to exist. It always prints. See "Multiple debug levels" above.

$d was chosen because it is easy to type and intuitive

If your code already uses '$d' for another purpose, this can be changed with Debug::Statements::setFlag()

Your code must not already contain a local subroutine called 'd()', since this function is imported

Consider enabling $d through the command line of your script

use Getopt::Long;
my %opt;
my $d = 0;
GetOptions( \%opt, 'd' => sub{$d=1}, 'dd' => sub{$d=2}, ... );

This provides an easy way for others to set your code into debug mode. They can then capture stdout and email it to you.

Quoting

Calls to d() should use 'single quotes' instead of "double quotes"

Exception: To produce custom output, call d() with double-quotes. As is always the case with double-quotes in Perl, variables will be interpolated into values before entering the d() subroutine.

Example #1

d "Found pattern: $mynum in file $filename";

Output #1

DEBUG sub mysub:  Found pattern asdf in file foo.txt

Example #2

d "Found $key and replaced with $subtable_ref->{$key} on:  $line"

Output #2

DEBUG sub mysub:  Found foo and replaced with bar on:  foobar

Remember that when using escaped \$ \@ \% within "double quotes", this is equivalent to using $ @ % within 'single quotes'

This means that d() will try to print the names and values of those variables.

Functions

The module includes functions which affect global operation

Debug::Statements::enable();             # enable operation (default)
Debug::Statements::disable();            # disable operation, even if $d >= 1
Debug::Statements::setFlag('$yourvar');  # default is '$d'
Debug::Statements::setPrintDebug("");    # default is "DEBUG:  "
Debug::Statements::setTruncate(10);      # default is 10 lines

LIMITATIONS

Not supported

  • Array slices such as $listvar[1:3]

  • Some special variables such as $1 $_ @_ ...but any of these can be printed by using "double quotes", since this will cause Perl to evaluate the expression before calling d(). For example d "@_"

  • The evaluation is of variables does not support the full range of Perl syntax. Most cases work, for example: d '$hash{$key}' However hashes used as hash keys will not work, for example: d '$hash{$hash2{$key}}' As a workaround, use "double quotes": d "\$hash{$hash2{$key}}" instead. The rule is similar for arrays

Additional features

ls()

ls() is also provided for convenience, but not exported by default

use Debug::Statements qw(d d0 d1 d2 d3 D ls);
ls($myfilename);

When $d >= 1, prints an ls -l listing of $myfilename.

Note that ' ' is not used inside ls()

Perl versions

This module has been tested on

  • Linux 5.8.6, 5.8.8, 5.12, 5.14, and 5.20

    It will probably work as far back as 5.8.0

  • Windows 5.20

GORY DETAILS

How it works

PadWalker::peek_my() gets the value of $d and the contents of your variables (from outside its scope!) The variable values are stored in an internal hash reference

It does NOT change the values of your variables.

caller()[3] gets the name of subroutine which encloses your code

Data::Dumper pretty-prints the contents of your variable

Performance

For performance-critical applications, frequent calls to PadWalker::peek_my() and caller() may be too intensive

Solutions

  • Globally disable all functionality by calling Debug::Statements::disable(); The PadWalker and caller functions will not be called. Debug statements will not be printed.

  • OR comment out some of your calls to d() within performance-critical loops

  • OR completely disable this code is to define you own empty d() subroutines.

    #use Debug::Statements qw(d d2);
    sub d{}; sub d2{};

AUTHOR

Chris Koknat 2018 chris.koknat@gmail.com

COPYRIGHT AND LICENSE

This software is copyright (c) 2013-18 by Chris Koknat.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.