首页 > 解决方案 > 如何在 Perl 中实现断言?

问题描述

当试图assert()在 Perl 中实现 C 的宏时,存在一些基本问题。首先考虑这段代码:

sub assert($$) {
   my ($assertion, $failure_msg) = @_;
   die $failure_msg unless $assertion;
}

# ...
assert($boolean, $message);

虽然这可行,但它不像 C:在 C 中我会写assert($foo <= $bar),但使用这个实现我必须写assert($foo <= $bar, '$foo <= $bar'),即重复条件作为字符串。

现在我想知道如何有效地实现这一点。简单的变体似乎将字符串传递给assert()并用于eval评估字符串,但在评估 eval 时无法访问变量。即使它会起作用,它也会非常低效,因为每次都会解析和评估条件。

传递表达式时,我不知道如何从中创建一个字符串,特别是因为它已经被评估过。

另一个变体使用assert(sub { $condition })可能更容易从代码 ref 生成字符串的地方,被认为太丑陋了。

构造assert(sub { (eval $_[0], $_[0]) }->("condition"));

sub assert($)
{
    die "Assertion failed: $_[1]\n" unless $_[0];
}

会做,但很难打电话。我正在寻找的解决方案是编写条件只检查一次,同时能够重现原始(未评估)条件有效地评估条件

那么还有哪些更优雅的解决方案呢?显然,如果 Perl 有一个宏或类似的语法机制,允许在编译或评估之前转换输入,那么解决方案会更容易。

标签: perlevalassert

解决方案


使用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;

测试输出:

$ perl dummy.pl
Assertion failed: defined $var at dummy.pl line 26.

推荐阅读