Как реализовать assert в Perl? - PullRequest
0 голосов
/ 28 января 2019

При попытке реализовать макрос C assert() в Perl возникает фундаментальная проблема.Сначала рассмотрим этот код:

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 был макрос или сопоставимый синтаксический механизм, который позволяет преобразовывать ввод перед компиляцией или оценкой.

Ответы [ 4 ]

0 голосов
/ 29 января 2019

Одним из подходов к любому виду «утверждений» является использование инфраструктуры тестирования.Он не такой четкий, как в C assert, но в то же время он несравнимо более гибкий и управляемый, в то время как тесты можно свободно встраивать в код, очень похоже на операторы assert.

Несколько очень простыхпримеры

use warnings;
use strict;
use feature 'say';

use Test::More 'no_plan';
Test::More->builder->output('/dev/null');

say "A few examples of tests, scattered around code\n";

like('may be', qr/(?:\w+\s+)?be/, 'regex');
cmp_ok('a', 'eq', 'a ', 'string equality');

my ($x, $y) = (1.7, 13);

cmp_ok($x, '==', $y, '$x == $y');

say "\n'eval' expression in a string so we can see the failing code\n";

my $expr = '$x**2 == $y';
ok(eval $expr, 'Quadratic') || diag explain $expr;  

# ok(eval $expr, $expr);

с выводом

A few examples of tests, scattered around code

#   Failed test 'string equality'
#   at assertion.pl line 19.
#          got: 'a'
#     expected: 'a '
#   Failed test '$x == $y'
#   at assertion.pl line 20.
#          got: 1.7
#     expected: 13

'eval' expression in a string so we can see the failing code

#   Failed test 'Quadratic'
#   at assertion.pl line 26.
# $x**2 == $y
# Looks like you failed 3 tests of 4.

Это всего лишь фрагмент примеров, где последний отвечает на вопрос напрямую.

Модуль Тест:: Подробнее объединяет ряд инструментов;Есть много вариантов, как его использовать и как управлять выходом.См. Test :: Harness и Test :: Builder (используется выше), а также ряд учебных пособий и сообщений SO.

Я не знаю, какВыше eval считается «элегантным», но это действительно переносит вас от единичных и индивидуально заботящихся о операторов типа 1010 * в стиле C к более легко управляемой системе.

Хорошие утверждения предназначены и планируются как системные тесты идокументация кода, но по своей природе не имеет формальной структуры (и поэтому все еще может оказаться разбросанной и произвольной).Когда все сделано таким образом, они поставляются с каркасом и могут управляться и настраиваться с помощью множества инструментов, а также как набор.

0 голосов
/ 28 января 2019

В CPAN загружены модули утверждений.Они с открытым исходным кодом, так что довольно легко посмотреть на них и посмотреть, как они сделаны.

Carp :: Assert - реализация с низким уровнем волшебства.В его документации есть ссылки на несколько более сложных модулей утверждений, одним из которых является мой модуль PerlX :: Assert .

0 голосов
/ 28 января 2019

Используйте 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);

Вывод:

Assertion failed:  assert.pl:14: assert(2 + 2 == 5);

Если вы используете Carp::croak вместо diePerl также сообщит информацию трассировки стека и определит, где было вызвано ошибочное утверждение.

0 голосов
/ 28 января 2019

Использование 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.
...