When trying to implement C's assert()
macro in Perl, there is some fundamental problem. Consider this code first:
sub assert($$) {
my ($assertion, $failure_msg) = @_;
die $failure_msg unless $assertion;
}
# ...
assert($boolean, $message);
While this works, it's not like C: In C I'd write assert($foo <= $bar)
, but with this implementation I'd have to write assert($foo <= $bar, '$foo <= $bar')
, i.e. repeat the condition as string.
Now I wonder how to implement this efficiently. The easy variant seems to pass the string to assert()
and use eval
to evaluate the string, but you can't access the variables when evaluating eval. Even if it would work, it would be quite inefficient as the condition is parsed and evaluated each time.
When passing the expression, I have no idea how to make a string from it, especially as it's evaluated already.
Another variant using assert(sub { $condition })
where it's likely easier to make a string from the code ref, is considered too ugly.
The construct assert(sub { (eval $_[0], $_[0]) }->("condition"));
with
sub assert($)
{
die "Assertion failed: $_[1]\n" unless $_[0];
}
would do, but is ugly to call. The solution I am looking for is to write the condition to check only once, while being able to reproduce the original (non-evaluated) condition and efficiently evaluate the condition.
So what are more elegant solutions? Obviously solutions would be easier if Perl had a macro or comparable syntax mechanism that allows transforming the input before compiling or evaluating.
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.