Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perl can't use last with the while statement modifier

Tags:

perl

Perl can:

perl -le '$d=1; while(1){ $d/=2 and ++$i or last } print "i=$i"'

But not:

perl -le '$d=1; $d/=2 and ++$i or last while 1; print "i=$i"'

which results in:

Can't "last" outside a loop block at -e line 1.

Is this the general case or is it something in this particular statement that prevents last here? Didn't perl used be able to do last with statement modifiers (while/until/for at the end) in earlier versions? I tested on v5.26 now and don't have old versions readily available.

like image 505
Kjetil S. Avatar asked Dec 19 '20 14:12

Kjetil S.


2 Answers

Not all statement modifiers.

$ perl -M5.010 -e'last while 1; say "ok"'
Can't "last" outside a loop block at -e line 1.

$ perl -M5.010 -e'last until 0; say "ok"'
Can't "last" outside a loop block at -e line 1.

$ perl -M5.010 -e'last for 1; say "ok"'
ok

Famously, it doesn't work in the special case do BLOCK while EXPR; either.

$ perl -M5.010 -e'do { last } while 1; say "ok"'
Can't "last" outside a loop block at -e line 1.

I don't know why it works for for and not the others.


Not new.

$ for v in 10 12 14 16 18 20 22 24 26 28 30 32; do
   printf '5.%s:\n' $v
   5.${v}t/bin/perl -M5.010 -e'last while 1; say "ok"'
done
5.10:
Can't "last" outside a loop block at -e line 1.
5.12:
Can't "last" outside a loop block at -e line 1.
5.14:
Can't "last" outside a loop block at -e line 1.
5.16:
Can't "last" outside a loop block at -e line 1.
5.18:
Can't "last" outside a loop block at -e line 1.
5.20:
Can't "last" outside a loop block at -e line 1.
5.22:
Can't "last" outside a loop block at -e line 1.
5.24:
Can't "last" outside a loop block at -e line 1.
5.26:
Can't "last" outside a loop block at -e line 1.
5.28:
Can't "last" outside a loop block at -e line 1.
5.30:
Can't "last" outside a loop block at -e line 1.
5.32:
Can't "last" outside a loop block at -e line 1.
like image 94
ikegami Avatar answered Oct 06 '22 10:10

ikegami


Perl doesn't create a redoable scope for postfix loops as an optimization. Building the scope and making it reinitializable is slightly expensive. That's why we have postfix versions to avoid it.

$ perl -Mdiagnostics -e '$d=1; $d/=2 and ++$i or last while 1; print "i=$i"'
Can't "last" outside a loop block at -e line 1 (#1)
    (F) A "last" statement was executed to break out of the current block,
    except that there's this itty bitty problem called there isn't a current
    block.  Note that an "if" or "else" block doesn't count as a "loopish"
    block, as doesn't a block given to sort(), map() or grep().  You can
    usually double the curlies to get the same effect though, because the
    inner curlies will be considered a block that loops once.  See
    "last" in perlfunc.

Uncaught exception from user code:
        Can't "last" outside a loop block at -e line 1.

You can subvert this behavior by wrapping the whole statement in a block which will then become the target for last.

$ perl -e '$d=1; { $d/=2 and ++$i or last while 1; } print "i=$i"'
i=1074

It's surprising indeed that postfix foreach accepts last. The deparse doesn't show anything out of the ordinary.

It seems that postfix foreach is broken because this happens! :D

$ perl -Mdiagnostics -e '$d=1; $x=2; ($d/=2 and ++$i or last) foreach 1; print "i=$i, x=$x\n"'
i=1, x=2


$ perl -Mdiagnostics -e '$d=1; $x=2; ((my $x=3), $d/=2 and ++$i or last)
foreach 1; print "i=$i, x=$x\n"'
Name "main::x" used only once: possible typo at -e line 1 (#1)
    (W once) Typographical errors often show up as unique variable
    names.  If you had a good reason for having a unique name, then
    just mention it again somehow to suppress the message.  The our
    declaration is also provided for this purpose.

    NOTE: This warning detects package symbols that have been used
    only once.  This means lexical variables will never trigger this
    warning.  It also means that all of the package variables $c, @c,
    %c, as well as *c, &c, sub c{}, c(), and c (the filehandle or
    format) are considered the same; if a program uses $c only once
    but also uses any of the others it will not trigger this warning.
    Symbols beginning with an underscore and symbols using special
    identifiers (q.v. perldata) are exempt from this warning.

Use of uninitialized value $x in concatenation (.) or string at -e line 1 (#2)
    (W uninitialized) An undefined value was used as if it were already
    defined.  It was interpreted as a "" or a 0, but maybe it was a mistake.
    To suppress this warning assign a defined value to your variables.

    To help you figure out what was undefined, perl will try to tell you
    the name of the variable (if any) that was undefined.  In some cases
    it cannot do this, so it also tells you what operation you used the
    undefined value in.  Note, however, that perl optimizes your program
    and the operation displayed in the warning may not necessarily appear
    literally in your program.  For example, "that $foo" is usually
    optimized into "that " . $foo, and the warning will refer to the
    concatenation (.) operator, even though there is no . in
    your program.

i=1, x=

Minimalist example:

$ perl -W -e '$x=2; (my $x=3) foreach 1; print "x=$x\n"'
Name "main::x" used only once: possible typo at -e line 1.
Use of uninitialized value $x in concatenation (.) or string at -e line 1.
x=

I tried this on 5.22 and 5.30.

like image 35
lordadmira Avatar answered Oct 06 '22 08:10

lordadmira