Как получить номер текущей строки в инициализаторе многострочного списка тестовых случаев? - PullRequest
0 голосов
/ 24 мая 2018

Есть ли способ надежно получить текущий номер строки во время назначения многострочного списка Perl без явного использования __LINE__?Я храню тестовые случаи в списке и хотел бы пометить каждый из них своим номером строки. * Таким образом, я могу сделать (примерно) ok($_->[1], 'line ' . $_->[0]) for @tests.И, конечно же, я хотел бы сэкономить набор текста по сравнению с указанием __LINE__ в начале каждого теста :).Я не смог найти способ сделать это, и я столкнулся с некоторым запутанным поведением в строках, сообщенных caller.

* Возможно XY, но я не могу найти модульчтобы сделать это.

Обновление Я нашел хак и опубликовал его как ответ .Спасибо @zdim за помощь в рассмотрении проблемы по-другому!

MCVE

Длинный, потому что я пробовал несколько разных вариантов.my_eval, L() и L2{} - это те, которые я пробовал до сих пор - L() был тем, который я изначально надеялся, сработает.Спрыгните на my @testcases, чтобы увидеть, как я их использую.При тестировании скопируйте строку Шебанга.

Вот мой вариант использования без MCVE , если вам интересно.

#!perl
use strict; use warnings; use 5.010;

# Modified from https://www.effectiveperlprogramming.com/2011/06/set-the-line-number-and-filename-of-string-evals/#comment-155 by http://sites.google.com/site/shawnhcorey/
sub my_eval {
    my ( $expr ) = @_;
    my ( undef, $file, $line ) = caller;
    my $code = "# line $line \"$file\"\n" . $expr;

    unless(defined wantarray) {
        eval $code; die $@ if $@;
    } elsif(wantarray) {
        my @retval = eval $code; die $@ if $@; return @retval;
    } else {
        my $retval = eval $code; die $@ if $@; return $retval;
    }
}

sub L {     # Prepend caller's line number
    my (undef, undef, $line) = caller;
    return ["$line", @_];
} #L

sub L2(&) {     # Prepend caller's line number
    my $fn = shift;
    my (undef, undef, $line) = caller;
    return ["$line", &$fn];
} #L2

# List of [line number, item index, expected line number, type]
my @testcases = (
    ([__LINE__,0,32,'LINE']),
    ([__LINE__,1,33,'LINE']),
    (L(2,34,'L()')),
    (L(3,35,'L()')),
    (do { L(4,36,'do {L}') }),
    (do { L(5,37,'do {L}') }),
    (eval { L(6,38,'eval {L}') }),
    (eval { L(7,39,'eval {L}') }),
    (eval "L(8,40,'eval L')"),
    (eval "L(9,41,'eval L')"),
    (my_eval("L(10,42,'my_eval L')")),
    (my_eval("L(11,43,'my_eval L')")),
    (L2{12,44,'L2{}'}),
    (L2{13,45,'L2{}'}),
);

foreach my $idx (0..$#testcases) {
    printf "%2d %-10s line %2d expected %2d %s\n",
            $idx, $testcases[$idx]->[3], $testcases[$idx]->[0],
            $testcases[$idx]->[2],
            ($testcases[$idx]->[0] != $testcases[$idx]->[2]) && '*';
}

Вывод

С добавлением моих комментариев.

 0 LINE       line 32 expected 32
 1 LINE       line 33 expected 33

Использование __LINE__ явно работает нормально, но я ищу сокращение.

 2 L()        line 45 expected 34 *
 3 L()        line 45 expected 35 *

L() использует caller для получения номера строки и сообщает о строке позже в файле (!).

 4 do {L}     line 36 expected 36
 5 do {L}     line 45 expected 37 *

Когда я завершаю вызов L()в do{}, caller возвращает правильный номер строки - но только один раз (!).

 6 eval {L}   line 38 expected 38
 7 eval {L}   line 39 expected 39

Блок eval, что интересно, работает нормально.Однако он не короче __LINE__.

 8 eval L     line  1 expected 40 *
 9 eval L     line  1 expected 41 *

String eval дает номер строки внутри eval (неудивительно)

10 my_eval L  line 45 expected 42 *
11 my_eval L  line 45 expected 43 *

my_eval() являетсястрока eval плюс директива #line, основанная на caller.Позже в файле также указывается номер строки (!).

12 L2{}       line 45 expected 44 *
13 L2{}       line 45 expected 45

L2 совпадает с L, но для него требуется блок, который возвращает список, а не сам список.Также используется caller для номера строки.И это правильно один раз, но не дважды (!).(Возможно, только потому, что это последний элемент - my_eval также сообщается в строке 45).

Итак, что здесь происходит?Я слышал о Deparse и удивляюсь, связано ли это с оптимизацией, но я не знаю достаточно о двигателе, чтобы знать, с чего начать расследование.Я также думаю, что это можно сделать с помощью исходных фильтров или Devel::Declare, но это намного выше моего уровня опыта.

Взять 2

@ zdim ответ меня получилначал думать о свободных интерфейсах, например, как в моем ответе :

$testcases2     # line 26
    ->add(__LINE__,0,27,'LINE')
    ->add(__LINE__,1,28,'LINE')
    ->L(2,29,'L()')
    ->L(3,30,'L()')
    ->L(3,31,'L()')
;

Однако даже они здесь не работают - я получаю строку 26 для каждого из вызовов ->L(),Похоже, что caller видит все цепочки вызовов, поступающие из линии $testcases2->....Ну что ж.Мне все еще интересно знать, почему, если кто-нибудь может меня просветить!

Ответы [ 2 ]

0 голосов
/ 24 мая 2018

Редактировать Этот ответ теперь обернут в CPAN модуль ( GitHub )!


@ zdim's answer заставил меня задуматься о плавных интерфейсах.Ниже приведены два хака , которые работают для моего конкретного случая использования, но это не помогает мне понять поведение, о котором сообщается в вопросе.Если вы можете помочь, пожалуйста, оставьте другой ответ!

Hack 2 (новее) (тот, что сейчас на CPAN)

Я думаю, этот ответ очень близок к минимальному.В Perl вы можете вызывать подпрограмму через ссылку с $ref->(), и вы можете опустить второй и последующий -> в цепочке стрелок.Это означает, например, что вы можете сделать:

my $foo; $foo=sub { say shift; return $foo; };
$foo->(1)
      (2)
      (3);

Хорошо выглядит, верно?Итак, вот MCVE:

#!perl
use strict; use warnings; use 5.010;

package FluentAutoIncList2 {
    sub new {   # call as $class->new(__LINE__); each element is one line
        my $class = shift;
        my $self = bless {lnum => shift // 0, arr => []}, $class;

        # Make a loader that adds an item and returns itself --- not $self
        $self->{loader} = sub { $self->L(@_); return $self->{loader} };

        return $self;
    }
    sub size { return scalar @{ shift->{arr} }; }
    sub last { return shift->size-1; }      # $#

    sub load { goto &{ shift->{loader} } }  # kick off loading

    sub L {     # Push a new record with the next line number on the front
        my $self = shift;
        push @{ $self->{arr} }, [++$self->{lnum}, @_];
        return $self;
    } #L

    sub add {   # just add it
        my $self = shift;
        ++$self->{lnum};    # keep it consistent
        push @{ $self->{arr} }, [@_];
        return $self;
    } #add

} #FluentAutoIncList2

# List of [line number, item index, expected line number, type]
my $testcases = FluentAutoIncList2->new(__LINE__)    # line 28
    ->add(__LINE__,0,36,'LINE')
    ->add(__LINE__,1,37,'LINE');
    $testcases->load(2,38,'load')->     # <== Only need two arrows.
    (3,39,'chain load')                 # <== After that, () are enough.
    (4,40,'chain load')
    (5,41,'chain load')
    (6,42,'chain load')
    (7,43,'chain load')
;

foreach my $idx (0..$testcases->last) {
    printf "%2d %-10s line %2d expected %2d %s\n",
            $idx, $testcases->{arr}->[$idx]->[3],
            $testcases->{arr}->[$idx]->[0],
            $testcases->{arr}->[$idx]->[2],
            ($testcases->{arr}->[$idx]->[0] !=
                $testcases->{arr}->[$idx]->[2]) && '*';
}

Вывод:

 0 LINE       line 36 expected 36
 1 LINE       line 37 expected 37
 2 load       line 38 expected 38
 3 chain load line 39 expected 39
 4 chain load line 40 expected 40
 5 chain load line 41 expected 41
 6 chain load line 42 expected 42
 7 chain load line 43 expected 43

Все строки chain load были загружены нулем лишних символов по сравнению с исходным подходом [x, y].Некоторые накладные расходы, но не очень!

Взлом 1

Код:

Начиная с __LINE__ и предполагая фиксированное количество строк на вызов, счетчик выполниттрюк.Вероятно, это можно сделать более аккуратно с помощью tie.

#!perl
use strict; use warnings; use 5.010;

package FluentAutoIncList {
    sub new {   # call as $class->new(__LINE__); each element is one line
        my $class = shift;
        return bless {lnum => shift // 0, arr => []}, $class;
    }
    sub size { return scalar @{ shift->{arr} }; }
    sub last { return shift->size-1; }      # $#

    sub L {     # Push a new record with the next line number on the front
        my $self = shift;
        push @{ $self->{arr} }, [++$self->{lnum}, @_];
        return $self;
    } #L

    sub add {   # just add it
        my $self = shift;
        ++$self->{lnum};    # keep it consistent
        push @{ $self->{arr} }, [@_];
        return $self;
    } #add

} #FluentAutoIncList

# List of [line number, item index, expected line number, type]
my $testcases = FluentAutoIncList->new(__LINE__)    # line 28
    ->add(__LINE__,0,29,'LINE')
    ->add(__LINE__,1,30,'LINE')
    ->L(2,31,'L()')
    ->L(3,32,'L()')
    ->L(4,33,'L()')
;

foreach my $idx (0..$testcases->last) {
    printf "%2d %-10s line %2d expected %2d %s\n",
            $idx, $testcases->{arr}->[$idx]->[3],
            $testcases->{arr}->[$idx]->[0],
            $testcases->{arr}->[$idx]->[2],
            ($testcases->{arr}->[$idx]->[0] !=
                $testcases->{arr}->[$idx]->[2]) && '*';
}

Вывод:

 0 LINE       line 29 expected 29
 1 LINE       line 30 expected 30
 2 L()        line 31 expected 31
 3 L()        line 32 expected 32
 4 L()        line 33 expected 33
0 голосов
/ 24 мая 2018

caller может получить только номера строк операторов , определенных при компиляции.

Когда я изменяю код на

my @testcases;
push @testcases, ([__LINE__,0,32,'LINE']);
push @testcases, ([__LINE__,1,33,'LINE']);
push @testcases, (L(2,34,'L()'));
push @testcases, (L(3,35,'L()'));
...

с поддержкой номеров строк, это работает (за исключением строковых пропусков).

Итак, с практической стороны, использование caller хорошо с отдельными операторами для вызовов.

Внутренние элементы Perl

номера строк заполняются в дереве операций при компиляции и (мой акцент)

Во время выполнения доступны только номера строк операторов [...]

из сообщения икегами о пермонках .

Это можно увидеть, выполнив perl -MO=Concise script.pl, где строка

2      nextstate(main 25 line_nos.pl:45) v:*,&,{,x*,x&,x$,$,67108864 ->3

предназначена для операции nextstate, которая устанавливает номер строки для caller (и предупреждений).См. этот пост и пример nextstate ниже.

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

См. этот пост для получения информации о соответствующем случае и более подробно.

nextstate пример

Вот многострочная цепочка вызовов функций, проходящая через Deparse (аннотированная):

$ perl -MO=Concise -e '$x
    ->foo()
    ->bar()
    ->bat()'
d  <@> leave[1 ref] vKP/REFC ->(end)
1     <0> enter ->2
2     <;> nextstate(main 1 -e:1) v:{ ->3    <=== the only nextstate
c     <1> entersub[t4] vKRS/TARG ->d
3        <0> pushmark s ->4
a        <1> entersub[t3] sKRMS/LVINTRO,TARG,INARGS ->b
4           <0> pushmark s ->5
8           <1> entersub[t2] sKRMS/LVINTRO,TARG,INARGS ->9
5              <0> pushmark s ->6
-              <1> ex-rv2sv sKM/1 ->7
6                 <#> gvsv[*x] s ->7
7              <.> method_named[PV "foo"] s ->8
9           <.> method_named[PV "bar"] s ->a
b        <.> method_named[PV "bat"] ->c
-e syntax OK

Несмотря на то, что последовательные вызовы находятся в отдельных строках, они являются частью одного и того же оператора, поэтомувсе подключены к одному nextstate.

...