perl - 如何在 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 有一个宏或类似的语法机制,允许在编译或评估之前转换输入,那么解决方案会更容易。
解决方案
使用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.
推荐阅读
- javascript - jquery 和 jquery-ui 不适用于 React
- sql - 在Oracle上的情况下计算列和时如何获得正确的值
- loops - data.table 选项而不是循环
- python - 我们什么时候初始化函数中的函数调用 vs 作为参数?
- php - 遍历文件夹中的文件并根据文件打印html内容
- javascript - 如何删除css中文本的填充颜色?
- django - 我们可以在不使用模型的情况下将数据插入 django 默认数据库 sqlite3 吗?
- asp.net - 对于静态域文件,仍然附加 cookie。怎么解决?ASP.NET , IIS 10
- r - R中图形的折线图?
- ruby-on-rails - 使用 Ruby 3、Rails 6 和 Puma 反向打印堆栈跟踪