How to implement assert in Perl?
Use B::Deparse?
#!/usr/bin/perl
use strict;
use warnings;
use B::Deparse;
my $deparser = B::Deparse->new();
sub assert(&) {
my($condfunc) = @_;
my @caller = caller();
unless ($condfunc->()) {
my $src = $deparser->coderef2text($condfunc);
$src =~ s/^\s*use\s.*$//mg;
$src =~ s/^\s+(.+?)/$1/mg;
$src =~ s/(.+?)\s+$/$1/mg;
$src =~ s/[\r\n]+/ /mg;
$src =~ s/^\{\s*(.+?)\s*\}$/$1/g;
$src =~ s/;$//mg;
die "Assertion failed: $src at $caller[1] line $caller[2].\n";
}
}
my $var;
assert { 1 };
#assert { 0 };
assert { defined($var) };
exit 0;
Test output:
$ perl dummy.pl
Assertion failed: defined $var at dummy.pl line 26.
There are a load of assertion modules on CPAN. These are open source, so it's pretty easy to peek at them and see how they're done.
Carp::Assert is a low-magic implementation. It has links to a few more complicated assertion modules in its documentation, one of which is my module PerlX::Assert.
Use caller
and extract the line of source code that made the assertion?
sub assert {
my ($condition, $msg) = @_;
return if $condition;
if (!$msg) {
my ($pkg, $file, $line) = caller(0);
open my $fh, "<", $file;
my @lines = <$fh>;
close $fh;
$msg = "$file:$line: " . $lines[$line - 1];
}
die "Assertion failed: $msg";
}
assert(2 + 2 == 5);
Output:
Assertion failed: assert.pl:14: assert(2 + 2 == 5);
If you use Carp::croak
instead of die
, Perl will also report stack trace information and identify where the failing assertion was called.