当试图assert()在Perl中实现C的宏时,存在一些基本问题.首先考虑以下代码:
sub assert($$) {
my ($assertion, $failure_msg) = @_;
die $failure_msg unless $assertion;
}
# ...
assert($boolean, $message);
Run Code Online (Sandbox Code Playgroud)
虽然这有效,但它不像C:在C中我会写assert($foo <= $bar),但有了这个实现,我必须写assert($foo <= $bar, '$foo <= $bar'),即重复条件为字符串.
现在我想知道如何有效地实现这一点.easy变量似乎将字符串传递给assert()并用于eval计算字符串,但在评估eval时无法访问变量.即使它可以工作,每次解析和评估条件也会非常低效.
传递表达式时,我不知道如何从中创建一个字符串,特别是在它已经被评估时.
另一个使用assert(sub { $condition })代码ref更容易制作字符串的变体被认为太难看了.
构造assert(sub { (eval $_[0], $_[0]) }->("condition"));与
sub assert($)
{
die "Assertion failed: $_[1]\n" unless $_[0];
}
Run Code Online (Sandbox Code Playgroud)
会怎么做,但打电话很难看.我正在寻找的解决方案是编写条件只检查一次,同时能够重现原始(未评估)条件并有效地评估条件.
那么更优雅的解决方案是什么?显然,如果Perl有一个宏或类似的语法机制允许在编译或评估之前转换输入,那么解决方案会更容易.
使用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;
Run Code Online (Sandbox Code Playgroud)
测试输出:
$ perl dummy.pl
Assertion failed: defined $var at dummy.pl line 26.
Run Code Online (Sandbox Code Playgroud)
CPAN上有一大堆断言模块.这些都是开源的,所以很容易看到它们,看看它们是如何完成的.
Carp :: Assert是一种低魔法的实现.它在其文档中链接到一些更复杂的断言模块,其中一个是我的模块PerlX :: Assert.
使用caller并提取做出断言的源代码行?
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);
Run Code Online (Sandbox Code Playgroud)
输出:
Assertion failed: assert.pl:14: assert(2 + 2 == 5);
Run Code Online (Sandbox Code Playgroud)
如果您使用Carp::croak而不是die,则Perl还将报告堆栈跟踪信息,并确定调用失败的断言的位置。