This page was generated by Text::SmartLinks v0.01 at 2010-09-03 07:01:27 GMT.
(syn r32126)
[ Index of Synopses ]
Synopsis 4: Blocks and Statements
Larry Wall <larry@wall.org>
Created: 19 Aug 2004
Last Modified: 14 Jul 2010
Version: 102
This document summarizes Apocalypse 4, which covers the block and statement syntax of Perl.
Control flow is a dynamic feature of all computer programming languages, but languages differ in the extent to which control flow is attached to declarative features of the language, which are often known as "static" or "lexical". We use the phrase "lexical scoping" in its industry-standard meaning to indicate those blocks that surround the current textual location. More abstractly, any declarations associated with those textual blocks are also considered to be part of the lexical scope, and this is where the term earns the "lexical" part of its name, in the sense that lexical scoping actually does define the "lexicon" for the current chunk of code, insofar as the definitions of variables and routines create a local domain-specific language.
We also use the term "dynamic scoping" in the standard fashion to indicate the nested call frames that are created and destroyed every time a function or method is called. In most interesting programs the dynamic scopes are nested quite differently from the lexical scopes, so it's important to distinguish carefully which kind of scoping we're talking about.
Further compounding the difficulty is that every dynamic scope's outer call frame is associated with a lexical scope somewhere, so you can't just consider one kind of scoping or the other in isolation. Many constructs define a particular interplay of lexical and dynamic features. For instance, unlike normal lexically scope variables, dynamic variables search up the dynamic call stack for a variable of a particular name, but at each "stop" along the way, they are actually looking in the lexical "pad" associated with that particular dynamic scope's call frame.
In Perl 6, control flow is designed to do what the user expects most of the time, but this implies that we must consider the declarative nature of labels and blocks and combine those with the dynamic nature of the call stack. For instance, a return statement always returns from the lexically scoped subroutine that surrounds it. But to do that, it may eventually have to peel back any number of layers of dynamic call frames internal to the subroutine's current call frame. The lexical scope supplies the declared target for the dynamic operation. There does not seem to be a prevailing term in the industry for this, so we've coined the term lexotic to refer to these strange operations that perform a dynamic operation with a lexical target in mind. Lexotic operators in Perl 6 include:
return
next
last
redo
goto
Some of these operators also fall back to a purely dynamic interpretation if the lexotic interpretation doesn't work. For instance, next will prefer to exit a loop lexotically, but if there is no loop with an appropriate label in the lexical context, it will then scan upward dynamically through the call frames for any loop with the appropriate label, even though that loop will not be lexically visible. Lexotic and dynamic control flow is implemented by a system of control exceptions. For the lexotic return of next, the control exception will contain the identity of the loop scope to be exited (since the label was already "used up" to discover that identity), but for the dynamic fallback, the exception will contain only the loop label to be matched dynamically. See "Control Exceptions" below.
From t/spec/S02-builtin_data_types/anon_block.t lines 16–26: (skip)
-
| # L<S04/"The Relationship of Blocks and Declarations">
|
| # L<S06/"Anonymous subroutines">
|
| # anon blocks
|
| my $anon_sub = sub { 1 };
|
| isa_ok($anon_sub, Sub);
|
| is($anon_sub(), 1, 'sub { } works');
|
|
|
| my $anon_sub_w_arg = sub ($arg) { 1 + $arg };
|
| isa_ok($anon_sub_w_arg, Sub);
|
| is($anon_sub_w_arg(3), 4, 'sub ($arg) {} works');
|
|
|
Every block is a closure. (That is, in the abstract, they're all anonymous subroutines that take a snapshot of their lexical environment.) How a block is invoked and how its results are used are matters of context, but closures all work the same on the inside.
From t/spec/S06-signature/closure-over-parameters.t lines 8–9: (skip)
-
| # L<S04/The Relationship of Blocks and Declarations/"Every block is a
|
| # closure">
|
Blocks are delimited by curlies, or by the beginning and end of the current compilation unit (either the current file or the current eval string). Unlike in Perl 5, there are (by policy) no implicit blocks around standard control structures. (You could write a macro that violates this, but resist the urge.) Variables that mediate between an outer statement and an inner block (such as loop variables) should generally be declared as formal parameters to that block. There are three ways to declare formal parameters to a closure.
From t/spec/S04-statements/no-implicit-block.t lines 7–66: (skip)
-
| # L<S04/The Relationship of Blocks and Declarations/"no implicit blocks" around
|
| # "standard control structures">
|
| {
|
| my $y;
|
| if (my $x = 2) == 2 {
|
| $y = $x + 3;
|
| }
|
| is $x, 2, '$x assigned in if\'s condition';
|
| is $y, 5, '$y assigned in if\'s body';
|
| }
|
|
|
| {
|
| my $y;
|
| unless (my $x = 2) != 2 {
|
| $y = $x + 3;
|
| }
|
| is $x, 2, '$x assigned in unless\'s condition';
|
| is $y, 5, '$y assigned in unless\'s body';
|
| }
|
|
|
| {
|
| my $y;
|
| given my $x = 2 {
|
| when 2 { $y = $x + 3; }
|
| }
|
| is $x, 2, '$x assigned in given\'s condition';
|
| is $y, 5, '$y assigned in given\'s body';
|
| }
|
|
|
| {
|
| my $y;
|
| while my $x = 2 {
|
| $y = $x + 3;
|
| last;
|
| }
|
| is $x, 2, '$x assigned in while\'s condition';
|
| is $y, 5, '$y assigned in while\'s body';
|
| }
|
|
|
| {
|
| my $y;
|
| for my @a = 1..3 {
|
| $y = @a[1] + 3;
|
| last;
|
| }
|
| is ~@a, '1 2 3', '@a assigned in for\'s condition';
|
| is $y, 5, '$y assigned in for\'s body';
|
| }
|
|
|
| {
|
| my $y;
|
| loop (my $x = 2; $x < 10; $x++) {
|
| $y = $x + 3;
|
| last;
|
| }
|
| is $x, 2, '$x assigned in loop\'s condition';
|
| is $y, 5, '$y assigned in loop\'s body';
|
| }
|
|
|
| # vim: ft=perl6
|
$func = sub ($a, $b) { .print if $a eq $b }; # standard sub declaration
$func = -> $a, $b { .print if $a eq $b }; # a "pointy" block
$func = { .print if $^a eq $^b } # placeholder arguments
A bare closure (except the block associated with a conditional statement) without placeholder arguments that uses $_ (either explicitly or implicitly) is treated as though $_ were a formal parameter:
From t/spec/S04-declarations/implicit-parameter.t lines 5–56: (skip)
-
| # L<S04/The Relationship of Blocks and Declarations/A bare closure
|
| # (except the block associated with a conditional statement)>
|
|
|
| {
|
| # test with explicit $_
|
| my $f1 = { 2*$_ };
|
| is $f1(2), 4, 'Block with explicit $_ has one formal parameter';
|
| }
|
|
|
| {
|
| # test with implicit $_
|
| my $f2 = { .sqrt };
|
| is_approx $f2(4), 2, 'Block with implicit $_ has one formal parameter';
|
| }
|
|
|
| {
|
| # { } has implicit signature ($_ is rw = $OUTER::_)
|
|
|
| $_ = 'Hello';
|
| #?pugs todo 'feature'
|
| is(try { { $_ }.() }, 'Hello', '$_ in bare block defaults to outer');
|
| is({ $_ }.('Goodbye'), 'Goodbye', 'but it is only a default');
|
| is({ 42 }.(), 42, 'no implicit $_ usage checking');
|
| is({ 42 }.('Goodbye'), 42, '$_ gets assigned but is not used');
|
|
|
| is(({ $_ }.arity), 0, '{$_} is arity 0, of course');
|
| is(({ .say }.arity), 0, 'Blocks that uses $_ implicitly have arity 0');
|
| is(({ $_ }.count), 1, '{$_} is count 1');
|
| is(({ .say }.count), 1, 'Blocks that uses $_ implicitly have count 1');
|
| }
|
|
|
| {
|
| #?pugs 4 todo 'pointy blocks'
|
| $_ = 'Ack';
|
| dies_ok({ (-> { "Boo!" }).(42) }, '-> {} is arity 0');
|
| dies_ok({ (-> { $_ }).(42) }, 'Even when we use $_>');
|
|
|
| #?rakudo 2 todo 'pointy blocks and $_'
|
| is((-> { $_ }).(), 'Ack!', '$_ is lexical here');
|
| is(-> $a { $_ }.(42), 'Ack!', 'Even with parameters (?)');
|
| is(-> $_ { $_ }.(42), 42, 'But not when the parameter is $_');
|
|
|
| eval_dies_ok( 'sub () { -> { $^a }.() }', 'Placeholders not allowed in ->');
|
|
|
| is(-> { }.arity, 0, '->{} is arity 0, again');
|
| }
|
|
|
| {
|
| eval_dies_ok('sub { $^foo }.(42)', 'Placeholders not allowed in sub()');
|
| }
|
|
|
| # vim: ft=perl6
|
$func = { .print if $_ }; # Same as: $func = <-> $_ { .print if $_ };
$func("printme");
In any case, all formal parameters are the equivalent of my variables within the block. See S06 for more on function parameters.
Except for such formal parameter declarations, all lexically scoped declarations are visible from the point of declaration to the end of the enclosing block. Period. Lexicals may not "leak" from a block to any other external scope (at least, not without some explicit aliasing action on the part of the block, such as exportation of a symbol from a module). The "point of declaration" is the moment the compiler sees "my $foo", not the end of the statement as in Perl 5, so
From t/spec/S04-declarations/my.t lines 6–79: (skip)
-
| #L<S04/The Relationship of Blocks and Declarations/"declarations, all
|
| # lexically scoped declarations are visible">
|
| {
|
|
|
| #?rakudo todo 'lexicals bug; RT #61838'
|
| eval_dies_ok('$x; my $x = 42', 'my() variable not yet visible prior to declaration');
|
| is(eval('my $x = 42; $x'), 42, 'my() variable is visible now (2)');
|
| }
|
|
|
|
|
| {
|
| my $ret = 42;
|
| eval_dies_ok '$ret = $x ~ my $x;', 'my() variable not yet visible (1)';
|
| is $ret, 42, 'my() variable not yet visible (2)';
|
| }
|
|
|
| {
|
| my $ret = 42;
|
| lives_ok { $ret = (my $x) ~ $x }, 'my() variable is visible (1)';
|
| #?rakudo todo 'scoping bug'
|
| is $ret, "", 'my() variable is visible (2)';
|
| }
|
|
|
| {
|
| sub answer { 42 }
|
| my &fortytwo = &answer;
|
| is &fortytwo(), 42, 'my variable with & sigil works (1)';
|
| is fortytwo(), 42, 'my variable with & sigil works (2)';
|
| }
|
|
|
| {
|
| my $was_in_sub;
|
| my &foo := -> $arg { $was_in_sub = $arg };
|
| foo(42);
|
| is $was_in_sub, 42, 'calling a lexically defined my()-code var worked';
|
| }
|
|
|
| eval_dies_ok 'foo(42)', 'my &foo is lexically scoped';
|
|
|
| {
|
| is(do {my $a = 3; $a}, 3, 'do{my $a = 3; $a} works');
|
| is(do {1; my $a = 3; $a}, 3, 'do{1; my $a = 3; $a} works');
|
| }
|
|
|
| eval_lives_ok 'my $x = my $y = 0;', '"my $x = my $y = 0" parses';
|
|
|
| #?rakudo skip 'fatal redeclarations'
|
| {
|
| my $test = "value should still be set for arg, even if there's a later my";
|
| sub foo2 (*%p) {
|
| is(%p<a>, 'b', $test);
|
| my %p;
|
| }
|
| foo2(a => 'b');
|
| }
|
|
|
| my $a = 1;
|
| ok($a, '$a is available in this scope');
|
|
|
| if (1) { # create a new lexical scope
|
| ok($a, '$a is available in this scope');
|
| my $b = 1;
|
| ok($b, '$b is available in this scope');
|
| }
|
| eval_dies_ok '$b', '$b is not available in this scope';
|
|
|
| # changing a lexical within a block retains the changed value
|
| my $c = 1;
|
| if (1) { # create a new lexical scope
|
| is($c, 1, '$c is still the same outer value');
|
| $c = 2;
|
| }
|
| is($c, 2, '$c is available, and the outer value has been changed');
|
|
|
my $x = $x;
will no longer see the value of the outer $x; you'll need to say either
my $x = $OUTER::x;
or
my $x = OUTER::<$x>;
instead.
If you declare a lexical twice in the same scope, it is the same lexical:
From t/spec/S04-declarations/multiple.t lines 5–33: (skip)
-
| # L<S04/The Relationship of Blocks and Declarations/"If you declare a lexical
|
| # twice in the same scope">
|
|
|
| #?rakudo todo "is this catching the warning?"
|
| eval_lives_ok 'my $x; my $x',
|
| 'it is legal to declare my $x twice in the same scope.';
|
|
|
| eval_lives_ok 'state $x; state $x',
|
| 'it is legal to declare state $x twice in the same scope.';
|
|
|
| #?rakudo skip 'binding'
|
| {
|
| my $x = 2;
|
| my $y := $x;
|
| my $x = 3;
|
| is $y, 3, 'Two lexicals with the name in same scope are the same variable';
|
| }
|
|
|
| # this is not exactly S04 material
|
| eval_dies_ok 'sub foo {1; }; sub foo($x) {1; };',
|
| 'multiple declarations need multi or proto';
|
|
|
| eval_dies_ok 'only sub foo {1; }; sub foo($x) {1; };',
|
| 'multiple declarations need multi or proto';
|
|
|
| eval_lives_ok 'proto foo {1; }; sub foo {1; }; sub foo($x) {1; };',
|
| 'multiple declarations need multi or proto';
|
|
|
| # vim: ft=perl6
|
my $x;
my $x;
By default the second declaration will get a compiler warning. You may suppress this by modifying the first declaration with proto:
my proto $x;
...
while my $x = @x.shift {...} # no warning
while my $x = @x.shift {...} # no warning
If you've referred to $x prior to the first declaration, and the compiler tentatively bound it to $OUTER::x, then it's an error to declare it, and the compiler is required to complain at that point. If such use can't be detected because it is hidden in an eval, then it is erroneous, since the eval() compiler might bind to either $OUTER::x or the subsequently declared "my $x".
From t/spec/S04-declarations/my.t lines 80–273: (skip)
-
| # L<S04/The Relationship of Blocks and Declarations/prior to the first declaration>
|
|
|
| my $d = 1;
|
| { # create a new lexical scope
|
| is($d, 1, '$d is still the outer $d');
|
| { # create another new lexical scope
|
| my $d = 2;
|
| is($d, 2, '$d is now the lexical (inner) $d');
|
| }
|
| }
|
| is($d, 1, '$d has not changed');
|
|
|
| # eval() introduces new lexical scope
|
| is( eval('
|
| my $d = 1;
|
| {
|
| my $d = 3
|
| }
|
| $d;
|
| '), 1, '$d is available, and the outer value has not changed' );
|
|
|
| {
|
| # check closures with functions
|
| my $func;
|
| my $func2;
|
| if (1) { # create a new lexical scope
|
| my $e = 0;
|
| $func = sub { $e++ }; # one to inc
|
| $func2 = sub { $e }; # one to access it
|
| }
|
|
|
| eval_dies_ok '$e', '$e is not available in this scope';
|
| is($func2(), 0, '$func2() just returns the $e lexical which is held by the closure');
|
| $func();
|
| is($func2(), 1, '$func() increments the $e lexical which is held by the closure');
|
| $func();
|
| is($func2(), 2, '... and one more time just to be sure');
|
| }
|
|
|
| # check my as simultaneous lvalue and rvalue
|
|
|
| is(eval('my $e1 = my $e2 = 42'), 42, 'can parse squinting my value');
|
| is(eval('my $e1 = my $e2 = 42; $e1'), 42, 'can capture squinting my value');
|
| is(eval('my $e1 = my $e2 = 42; $e2'), 42, 'can set squinting my variable');
|
| is(eval('my $x = 1, my $y = 2; $y'), 2, 'precedence of my wrt = and ,');
|
|
|
| # test that my (@array, @otherarray) correctly declares
|
| # and initializes both arrays
|
| {
|
| my (@a, @b);
|
| lives_ok { @a.push(2) }, 'Can use @a';
|
| lives_ok { @b.push(3) }, 'Can use @b';
|
| is ~@a, '2', 'push actually worked on @a';
|
| is ~@b, '3', 'push actually worked on @b';
|
| }
|
|
|
| my $result;
|
| my $x = 0;
|
| {
|
| while my $x = 1 { $result = $x; last };
|
| is $result, 1, 'my in while cond seen from body';
|
| }
|
|
|
| is(eval('while my $x = 1 { last }; $x'), 1, 'my in while cond seen after');
|
|
|
| is(eval('if my $x = 1 { $x } else { 0 }'), 1, 'my in if cond seen from then');
|
| is(eval('if not my $x = 1 { 0 } else { $x }'), 1, 'my in if cond seen from else');
|
| is(eval('if my $x = 1 { 0 } else { 0 }; $x'), 1, 'my in if cond seen after');
|
|
|
| # check proper scoping of my in loop initializer
|
|
|
| is(eval('loop (my $x = 1, my $y = 2; $x > 0; $x--) { $result = $x; last }; $result'), 1, '1st my in loop cond seen from body');
|
| is(eval('loop (my $x = 1, my $y = 2; $x > 0; $x--) { $result = $y; last }; $result'), 2, '2nd my in loop cond seen from body');
|
| is(eval('loop (my $x = 1, my $y = 2; $x > 0; $x--) { last }; $x'), 1, '1st my in loop cond seen after');
|
| is(eval('loop (my $x = 1, my $y = 2; $x > 0; $x--) { last }; $y'), 2, '2nd my in loop cond seen after');
|
|
|
|
|
| # check that declaring lexical twice is noop
|
| #?rakudo skip 'fatal redeclarations'
|
| {
|
| my $f;
|
| $f = 5;
|
| my $f;
|
| is($f, 5, "two lexicals declared in scope is noop");
|
| }
|
|
|
| my $z = 42;
|
| {
|
| my $z = $z;
|
| ok( $z.notdef, 'my $z = $z; can not see the value of the outer $z');
|
| }
|
|
|
| # interaction of my and eval
|
| # yes, it's weird... but that's the way it is
|
| # http://irclog.perlgeek.de/perl6/2009-03-19#i_1001177
|
| {
|
| sub eval_elsewhere($str) {
|
| eval $str;
|
| }
|
| my $x = 4;
|
| is eval_elsewhere('$x + 1'), 5,
|
| 'eval() knows the pad where it is launched from';
|
|
|
| ok eval_elsewhere('!$y.defined'),
|
| '... but initialization of variables might still happen afterwards';
|
|
|
| # don't remove this line, or eval() will complain about
|
| # $y not being declared
|
| my $y = 4;
|
| }
|
|
|
| # &variables don't need to be pre-declared
|
| {
|
| #?rakudo todo '&-sigiled variables'
|
| eval_lives_ok '&x; 1', '&x does not need to be pre-declared';
|
| eval_dies_ok '&x()', '&x() dies when empty';
|
| }
|
|
|
| # RT #62766
|
| {
|
| eval_lives_ok 'my $a;my $x if 0;$a = $x', 'my $x if 0';
|
|
|
| #?rakudo skip 'infinite loop? (noauto)'
|
| eval_lives_ok 'my $a;do { 1/0; my $x; CATCH { $a = $x.defined } }';
|
|
|
| {
|
| #?rakudo 2 todo 'OUTER and SETTING'
|
| ok eval('not OUTER::<$x>.defined'), 'OUTER::<$x>';
|
| ok eval('not SETTING:<$x>.defined'), 'SETTING::<$x>';
|
| my $x;
|
| }
|
|
|
| {
|
| my $a;
|
| #?rakudo skip 'infinite loop? (noauto)'
|
| eval_lives_ok 'do { 1/0;my Int $x;CATCH { $a = ?($x ~~ Int) } }';
|
| #?rakudo todo 'previous test skipped'
|
| ok $a, 'unreached declaration in effect at block start';
|
| }
|
|
|
| # XXX As I write this, this does not die right. more testing needed.
|
| dies_ok { my Int $x = "abc" }, 'type error';
|
| #?rakudo todo 'type error not caught'
|
| dies_ok { eval '$x = "abc"'; my Int $x; }, 'also a type error';
|
| }
|
|
|
| {
|
| ok declare_later().notdef,
|
| 'Can access variable returned from a named closure that is declared below the calling position';
|
| my $x;
|
| sub declare_later {
|
| $x;
|
| }
|
| }
|
|
|
| # used to be RT #76366, #76466
|
| {
|
| #?rakudo skip 'RT 76466'
|
| nok access_lexical_a().defined,
|
| 'can call our-sub that accesses a lexical before the block was run';
|
| {
|
| my $a = 42;
|
| our sub access_lexical_a() { $a }
|
| }
|
| is access_lexical_a(), 42,
|
| 'can call our-sub that accesses a lexical after the block was run';
|
|
|
| }
|
|
|
| eval_lives_ok 'my (%h?)', 'my (%h?) lives';
|
|
|
| #RT 63588
|
| #?rakudo todo 'global my variables are not visible inside class declarations'
|
| eval_lives_ok 'my $x = 3; class A { has .$y = $x; }; say A.new.y',
|
| 'global scoped variables are visible inside class definitions';
|
|
|
| #RT #72814
|
|
|
| {
|
| #?rakudo skip 'RT 72814'
|
| lives_ok {my ::a $a}, 'typing a my-declared variable as ::a works.'; #OK not used
|
| }
|
|
|
| # RT #72946
|
| {
|
| is ( my $ = 'foo' ), 'foo',
|
| 'declaration of anonymous Scalar';
|
| is ( my @ = 'foo', 'bar', 'baz' ), ['foo', 'bar', 'baz'],
|
| 'declaration of anonymous Array';
|
| is ( my % = 'foo' => 1, 'bar' => 2, 'baz' => 3 ), {'foo' => 1, 'bar' => 2, 'baz' => 3},
|
| 'declaration of anonymous Hash';
|
| }
|
|
|
| # vim: ft=perl6
|
As in Perl 5, "our $foo" introduces a lexically scoped alias for a variable in the current package.
The new constant declarator introduces a compile-time constant, either a variable or named value, which may be initialized with a pseudo-assignment:
From t/spec/S04-declarations/constant.t lines 7–210: (skip)
-
| # L<S04/The Relationship of Blocks and Declarations/"The new constant declarator">
|
|
|
| # Following tests test whether the declaration succeeded.
|
| #?pugs todo 'feature'
|
| {
|
| constant foo = 42;
|
|
|
| ok foo == 42, "declaring a sigilless constant using 'constant' works";
|
| dies_ok { foo = 3 }, "can't reassign to a sigil-less constant";
|
| }
|
|
|
| {
|
| my $ok;
|
|
|
| constant $bar = 42;
|
| ok $bar == 42, "declaring a constant with a sigil using 'constant' works";
|
| dies_ok { $bar = 2 }, "Can't reassign to a sigiled constant";
|
| }
|
|
|
| # RT #69740
|
| {
|
| eval_dies_ok 'constant ($a, $b) = (3, 4)', 'constant no longer takes list';
|
| }
|
|
|
|
|
| {
|
| {
|
| constant foo2 = 42;
|
| }
|
| eval_dies_ok 'foo2 == 42', 'constants are lexically scoped';
|
| }
|
|
|
| {
|
| constant foo3 = 42;
|
| lives_ok { my foo3 $x = 42 }, 'constant can be used as a type constraint';
|
| dies_ok { my foo3 $x = 43 }, 'constant used as a type constraint enforces';
|
| dies_ok { my foo3 $x = 42; $x =43 }, 'constant used as a type constraint enforces';
|
| }
|
|
|
| {
|
| my $ok;
|
|
|
| constant $foo = 582;
|
| constant $bar = $foo;
|
| $ok = $bar == 582;
|
|
|
| ok $ok, "declaring a constant in terms of another constant works";
|
| }
|
|
|
| #?rakudo skip 'RT 66636: package-scoped constant'
|
| {
|
| package ConstantTest {
|
| constant yak = 'shaving';
|
| }
|
| is ConstantTest::yak, 'shaving', 'constant is "our"-scoped';
|
| }
|
|
|
| #?rakudo skip 'RT 66636: package-scoped constant'
|
| {
|
| package ConstantTest2 {
|
| our constant yak = 'shaving';
|
| }
|
| is ConstantTest2::yak, 'shaving', 'constant can be explicitly "our"-scoped';
|
| }
|
|
|
| #?rakudo skip "probably can't parse yet"
|
| {
|
| package ConstantTest3 {
|
| my constant yak = 'shaving';
|
| }
|
| ok !ConstantTest3::yak.defined, 'constant can be explicitly "my"-scoped';
|
| }
|
|
|
| #?rakudo skip 'COMPILING'
|
| {
|
| my $ok;
|
|
|
| constant $foo = 8224;
|
| constant $bar = COMPILING::<$foo>;
|
| $ok = $bar == 8224;
|
|
|
| ok $ok, "declaring a constant in terms of COMPILING constant works";
|
| }
|
|
|
| {
|
| my $ok;
|
|
|
| constant %foo = { :a(582) };
|
| constant $bar = %foo<a>;
|
| $ok = $bar == 582;
|
|
|
| ok $ok, "declaring a constant in terms of hash constant works";
|
| }
|
|
|
| #?rakudo skip 'COMPILING'
|
| {
|
| my $ok;
|
|
|
| constant %foo = { :b(8224) };
|
| constant $bar = COMPILING::<%foo><b>;
|
| $ok = $bar == 8224;
|
|
|
| ok $ok, "declaring a constant in terms of COMPILING hash constant works";
|
| }
|
|
|
| {
|
| my $ok;
|
|
|
| constant @foo = 0, 582;
|
| constant $bar = @foo[1];
|
| $ok = $bar == 582;
|
|
|
| ok $ok, "declaring a constant in terms of array constant works";
|
| }
|
|
|
| #?rakudo skip 'COMPILING'
|
| {
|
| my $ok;
|
|
|
| constant @foo = [ 1, 2, 8224 ];
|
| constant $bar = COMPILING::<@foo>[2];
|
| $ok = $bar == 8224;
|
|
|
| ok $ok, "declaring a constant in terms of COMPILING hash constant works";
|
| }
|
|
|
| {
|
| my $ok;
|
|
|
| my Num constant baz = 42;
|
| $ok = baz == 42;
|
|
|
| ok $ok, "declaring a sigilless constant with a type specification using 'constant' works";
|
| }
|
|
|
| #?rakudo skip 'unicode constant name'
|
| {
|
| my $ok;
|
|
|
| constant λ = 42;
|
| $ok = λ == 42;
|
|
|
| ok $ok, "declaring an Unicode constant using 'constant' works";
|
| }
|
|
|
| # Following tests test whether the constants are actually constant.
|
| #?pugs todo 'feature'
|
| {
|
| my $ok;
|
|
|
| constant grtz = 42;
|
| $ok++ if grtz == 42;
|
|
|
| try { grtz = 23 };
|
| $ok++ if $!;
|
| $ok++ if grtz == 42;
|
|
|
| is $ok, 3, "a constant declared using 'constant' is actually constant (1)";
|
| }
|
|
|
| #?rakudo skip 'binding'
|
| #?pugs todo 'feature'
|
| {
|
| my $ok;
|
|
|
| constant baka = 42;
|
| $ok++ if baka == 42;
|
|
|
| try { baka := 23 };
|
| $ok++ if $!;
|
| $ok++ if baka == 42;
|
|
|
| is $ok, 3, "a constant declared using 'constant' is actually constant (2)";
|
| }
|
|
|
| #?pugs todo 'feature'
|
| {
|
| my $ok;
|
|
|
| constant wobble = 42;
|
| $ok++ if wobble == 42;
|
|
|
| try { wobble++ };
|
| $ok++ if $!;
|
| $ok++ if wobble == 42;
|
|
|
| is $ok, 3, "a constant declared using 'constant' is actually constant (3)";
|
| }
|
|
|
| #?rakudo skip 'binding'
|
| #?pugs todo 'feature'
|
| {
|
| my $ok;
|
|
|
| constant wibble = 42;
|
| $ok++ if wibble == 42;
|
|
|
| try { wibble := { 23 } };
|
| $ok++ if $!;
|
| $ok++ if wibble == 42;
|
|
|
| is $ok, 3, "a constant declared using 'constant' is actually constant (4)";
|
| }
|
|
|
constant $pi of Int = 3;
my Num constant π = atan2(2,2) * 4;
The initializing expression is evaluated at BEGIN time. Constants (and enums) default to our scoping so they can be accessed from outside the package.
From t/spec/S04-declarations/constant.t lines 211–278: (skip)
-
| # L<S04/The Relationship of Blocks and Declarations/The initializing
|
| # expression is evaluated at BEGIN time.>
|
| #?rakudo skip 'BEGIN and outer lexicals'
|
| {
|
| my $ok;
|
|
|
| my $foo = 42;
|
| BEGIN { $foo = 23 }
|
| constant timecheck = $foo;
|
| $ok++ if timecheck == 23;
|
|
|
| #?pugs todo 'feature'
|
| ok $ok, "the initializing values for constants are evaluated at compile-time";
|
| }
|
|
|
| # RT #64522
|
| {
|
| constant $x = 64522;
|
| dies_ok { $x += 2 }, 'dies: constant += n';
|
| is $x, 64522, 'constant after += has not changed';
|
|
|
| sub con { 64522 }
|
| #?rakudo todo '++constant_returning_sub()'
|
| dies_ok { ++con }, "constant-returning sub won't increment";
|
| is con, 64522, 'constant-returning sub after ++ has not changed';
|
| }
|
|
|
| # identities -- can't assign to constant even if it doesn't change it.
|
| {
|
| constant $change = 'alteration';
|
|
|
| dies_ok { $change ~= '' }, 'append nothing to a constant';
|
| dies_ok { $change = 'alteration' }, 'assign constant its own value';
|
| my $t = $change;
|
| dies_ok { $change = $t }, 'assign constant its own value from var';
|
| dies_ok { $change = 'alter' ~ 'ation' },
|
| 'assign constant its own value from expression';
|
|
|
| constant $five = 5;
|
|
|
| dies_ok { $five += 0 }, 'add zero to constant number';
|
| dies_ok { $five *= 1 }, 'multiply constant number by 1';
|
| dies_ok { $five = 5 }, 'assign constant its own value';
|
| my $faux_five = $five;
|
| dies_ok { $five = $faux_five },
|
| 'assign constant its own value from variable';
|
| dies_ok { $five = 2 + 3 },
|
| 'assign constant its own value from expression';
|
| }
|
|
|
| #?rakudo skip 'RT 69967'
|
| {
|
| constant C = 6;
|
| class A {
|
| constant B = 5;
|
| has $.x = B;
|
| has $.y = A::B;
|
| has $.z = C;
|
| }
|
|
|
| is A.new.x, 5, 'Can declare and use a constant in a class';
|
| is A.new.y, 5, 'Can declare and use a constant with FQN in a class';
|
| is A.new.z, 6, 'Can use outer constants in a class';
|
| }
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
There is a new state declarator that introduces a lexically scoped variable like my does, but with a lifetime that persists for the life of the closure, so that it keeps its value from the end of one call to the beginning of the next. Separate clones of the closure get separate state variables. However, recursive calls to the same clone use the same state variable.
From t/spec/S04-declarations/state.t lines 7–36: (skip)
-
| # L<S04/The Relationship of Blocks and Declarations/There is a new state declarator that introduces>
|
|
|
| # RT #67040 -- state initialized with //= instead of =
|
| # (I've put this test here since it gets buggered by later tests
|
| # unless RT #67058 has been fixed.)
|
| {
|
| sub rt67040 {
|
| state $x //= 17;
|
| $x++;
|
| return $x;
|
| }
|
|
|
| is rt67040(), 18, 'Assignment to state variable with //= works.';
|
| is rt67040(), 19, 'Assignment to state variable with //= happens once.';
|
| }
|
|
|
| # state() inside subs
|
| {
|
| sub inc () {
|
| state $svar;
|
| $svar++;
|
| return $svar;
|
| };
|
|
|
| is(inc(), 1, "state() works inside subs (#1)");
|
| is(inc(), 2, "state() works inside subs (#2)");
|
| is(inc(), 3, "state() works inside subs (#3)");
|
| }
|
|
|
| # state() inside coderefs
|
Perl 5's "local" function has been renamed to temp to better reflect what it does. There is also a let function that sets a hypothetical value. It works exactly like temp, except that the value will be restored only if the current block exits unsuccessfully. (See Definition of Success below for more.) temp and let temporize or hypotheticalize the value or the variable depending on whether you do assignment or binding. One other difference from Perl 5 is that the default is not to undefine a variable. So
From t/spec/S04-blocks-and-statements/let.t lines 7–64: (skip)
-
| # L<S04/The Relationship of Blocks and Declarations/There is also a let function>
|
| # L<S04/Definition of Success>
|
| # let() should not restore the variable if the block exited successfully
|
| # (returned a true value).
|
| {
|
| my $a = 42;
|
| {
|
| is(eval('let $a = 23; $a'), 23, "let() changed the variable (1)");
|
| 1;
|
| }
|
| is $a, 23, "let() should not restore the variable, as our block exited succesfully (1)";
|
| }
|
|
|
| # let() should restore the variable if the block failed (returned a false
|
| # value).
|
| {
|
| my $a = 42;
|
| {
|
| is(eval('let $a = 23; $a'), 23, "let() changed the variable (1)");
|
| Mu;
|
| }
|
| is $a, 42, "let() should restore the variable, as our block failed";
|
| }
|
|
|
| # Test that let() restores the variable at scope exit, not at subroutine
|
| # entry. (This might be a possibly bug.)
|
| {
|
| my $a = 42;
|
| my $get_a = { $a };
|
| {
|
| is(eval('let $a = 23; $a'), 23, "let() changed the variable (2-1)");
|
| is $get_a(), 23, "let() changed the variable (2-2)";
|
| 1;
|
| }
|
| is $a, 23, "let() should not restore the variable, as our block exited succesfully (2)";
|
| }
|
|
|
| # Test that let() restores variable even when not exited regularly (using a
|
| # (possibly implicit) call to return()), but when left because of an exception.
|
| {
|
| my $a = 42;
|
| try {
|
| is(eval('let $a = 23; $a'), 23, "let() changed the variable in a try block");
|
| die 57;
|
| };
|
| is $a, 42, "let() restored the variable, the block was exited using an exception";
|
| }
|
|
|
| {
|
| my @array = (0, 1, 2);
|
| {
|
| is(eval('let @array[1] = 42; @array[1]'), 42, "let() changed our array element");
|
| Mu;
|
| }
|
| is @array[1], 1, "let() restored our array element";
|
| }
|
|
|
| # vim: ft=perl6
|
From t/spec/S04-blocks-and-statements/temp.t lines 7–102: (skip)
-
| # L<S04/The Relationship of Blocks and Declarations/function has been renamed>
|
| {
|
| my $a = 42;
|
| {
|
| is(eval('temp $a = 23; $a'), 23, "temp() changed the variable (1)");
|
| }
|
| is $a, 42, "temp() restored the variable (1)";
|
| }
|
|
|
| # Test that temp() restores the variable at scope exit, not at subroutine
|
| # entry.
|
| {
|
| my $a = 42;
|
| my $get_a = { $a };
|
| {
|
| is(eval('temp $a = 23; $a'), 23, "temp() changed the variable (2-1)");
|
| is $get_a(), 23, "temp() changed the variable (2-2)";
|
| }
|
| is $a, 42, "temp() restored the variable (2)";
|
| }
|
|
|
| # temp() shouldn't change the variable containers
|
| {
|
| my $a = 42;
|
| my $get_a = { $a };
|
| {
|
| ok(eval('temp $a = 23; $a =:= $get_a()'), "temp() shouldn't change the variable containers");
|
| }
|
| }
|
|
|
| {
|
| our $pkgvar = 42;
|
| {
|
| is(eval(q/temp $pkgvar = 'not 42'; $pkgvar/), 'not 42', "temp() changed the package variable (3-1)");
|
| }
|
| is $pkgvar, 42, "temp() restored the package variable (3-2)";
|
| }
|
|
|
| # Test that temp() restores variable even when not exited regularly (using a
|
| # (possibly implicit) call to return()), but when left because of an exception.
|
| {
|
| my $a = 42;
|
| try {
|
| is(eval('temp $a = 23; $a'), 23, "temp() changed the variable in a try block");
|
| die 57;
|
| };
|
| is $a, 42, "temp() restored the variable, the block was exited using an exception";
|
| }
|
|
|
| eval('
|
| {
|
| my @array = (0, 1, 2);
|
| {
|
| temp @array[1] = 42;
|
| is @array[1], 42, "temp() changed our array element";
|
| }
|
| is @array[1], 1, "temp() restored our array element";
|
| }
|
| "1 - delete this line when the parsefail eval() is removed";
|
| ') or skip(2, "parsefail: temp \@array[1]");
|
|
|
| eval('
|
| {
|
| my %hash = (:a(1), :b(2), :c(3));
|
| {
|
| temp %hash<b> = 42;
|
| is %hash<b>, 42, "temp() changed our hash element";
|
| }
|
| is %hash<b>, 2, "temp() restored our array element";
|
| }
|
| "1 - delete this line when the parsefail eval() is removed";
|
| ') or skip(2, "parsefail: temp \%hash<b>");
|
|
|
| eval('
|
| {
|
| my $struct = [
|
| "doesnt_matter",
|
| {
|
| doesnt_matter => "doesnt_matter",
|
| key => [
|
| "doesnt_matter",
|
| 42,
|
| ],
|
| },
|
| ];
|
|
|
| {
|
| temp $struct[1]<key>[1] = 23;
|
| is $struct[1]<key>[1], 23, "temp() changed our nested arrayref/hashref element";
|
| }
|
| is $struct[1]<key>[1], 1, "temp() restored our nested arrayref/hashref element";
|
| }
|
| "1 - delete this line when the parsefail eval() is removed";
|
| ') or skip(2, "parsefail: temp \$struct[1]<key>[1]");
|
|
|
| # Block TEMP{}
|
temp $x;
causes $x to start with its current value. Use
temp undefine $x;
to get the Perl 5 behavior.
Note that temporizations that are undone upon scope exit must be prepared to be redone if a continuation within that scope is taken.
In the absence of explicit control flow terminating the block early, the return value of a block is the value of its final statement. This is defined as the textually last statement of its top-level list of statements; any statements embedded within those top-level statements are in their own lower-level list of statements and, while they may be a final statement in their subscope, they're not considered the final statement of the outer block in question.
This is subtly different from Perl 5's behavior, which was to return the value of the last expression evaluated, even if that expression was just a conditional. Unlike in Perl 5, if a final statement in Perl 6 is a conditional that does not execute any of its branches, it doesn't matter what the value of the condional is, the value of that conditional statement is always Nil. If there are no statements in the block at all, the result is also Nil.
A line ending with a closing brace "}", followed by nothing but whitespace or comments, will terminate a statement if an end of statement can occur there. That is, these two statements are equivalent:
From t/spec/S04-statements/terminator.t lines 7–57: (skip)
-
| # L<S04/"Statement-ending blocks"/"will terminate a statement">
|
|
|
| # the 'empty statement' case responsible for the creation of this test file
|
| eval_lives_ok(';', 'empty statement');
|
|
|
| eval_lives_ok('my $x = 2', 'simple statement no semi');
|
| eval_lives_ok('my $x =
|
| 9', 'simple statement on two lines no semi');
|
| eval_lives_ok('my $x = 2;', 'simple statement with semi');
|
| eval_lives_ok('{my $x = 2}', 'end of closure terminator');
|
| eval_lives_ok('{my $x =
|
| 2;}', 'double terminator');
|
| eval_lives_ok(';my $x = 2;{my $x = 2;;};', 'extra terminators');
|
|
|
| eval_dies_ok('{my $x = 2;', 'open closure');
|
| eval_dies_ok('my $x = ', 'incomplete expression');
|
|
|
| #?rakudo skip 'parsing do { ... } + 1'
|
| {
|
| my $x = do {
|
| 10
|
| } + 1;
|
|
|
| is($x, 11, "'} + 1' is in a single statement");
|
|
|
| my $y = do {
|
| 10
|
| }
|
| + 1;
|
|
|
| is($y, 10, "}\\n + 1 are two statements");
|
|
|
| my $z = [];
|
| eval q'
|
| $z = [ do { 1 }
|
| + 2 ];
|
| ';
|
|
|
| #?pugs todo 'parsing'
|
| is($z[0], 3, 'auto-curly doesn\'t apply unless we\'re at top level');
|
| }
|
|
|
| # There's *no* ";" before the "\n", but pugs parsed it nevertheless!
|
| # (and there s no infix:<is> either)
|
| eval_dies_ok "42 if 23\nis 50; 1",
|
| "if postfix modifier and is() is parsed correctly";
|
|
|
| # not sure this belong here, suggestions for better places are welcome
|
| eval_dies_ok '(1) { $foo = 2 }', 'parens do not eat spaces after them';
|
|
|
| # vim: ft=perl6
|
my $x = sub { 3 }
my $x = sub { 3 };
Since bracketed expressions consider their insides to be statements, this works out consistently even where you might expect problems:
my $x = [
sub { 3 }, # this comma is not optional
sub { 3 } # the statement inside [] terminates here
];
my $hash = {
1 => { 2 => 3, 4 => 5 }, # OK
2 => { 6 => 7, 8 => 9 } # OK, terminates inner statement
};
Because subroutine declarations are expressions, not statements, this is now invalid:
From t/spec/S02-whitespace_and_comments/unspace.t lines 186–190: (skip)
-
| # L<S04/"Statement-ending blocks"/"Because subroutine declarations are expressions">
|
| #XXX probably shouldn't be in this file...
|
|
|
| eval_dies_ok('sub f { 3 } sub g { 3 }', 'semicolon or newline required between blocks');
|
|
|
sub f { 3 } sub g { 3 } # two terms occur in a row
But these two are valid:
sub f { 3 }; sub g { 3 };
sub f { 3 }; sub g { 3 } # the trailing semicolon is optional
Though certain control statements could conceivably be parsed in a self-contained way, for visual consistency all statement-terminating blocks that end in the middle of a line must be terminated by semicolon unless they are naturally terminated by some other statement terminator:
while yin() { yang() } say "done"; # ILLEGAL
while yin() { yang() }; say "done"; # okay, explicit semicolon
@yy := [ while yin() { yang() } ]; # okay within outer [...]
while yin() { yang() } ==> sort # okay, ==> separates statements
From t/01-sanity/07-range.t lines 3–4: (skip)
-
| # L<S04/Conditional statements>
|
| # should be: L<S02/Lists/The C<< .. >> operator now constructs a>
|
From t/01-sanity/04-if.t lines 5–12: (skip)
-
| # L<S04/Conditional statements>
|
|
|
| say '1..2';
|
|
|
| my $x = '0';
|
|
|
| if ($x eq $x) { say 'ok 1' } else { say 'not ok 1' }
|
| if ($x ne $x) { say 'not ok 2' } else { say 'ok 2' }
|
From t/spec/S04-statements/if.t lines 12–120: (skip)
-
| # L<S04/Conditional statements>
|
|
|
| plan 26;
|
|
|
| my $x = 'test';
|
| if ($x eq $x) { pass('if ($x eq $x) {} works'); } else { flunk('if ($x eq $x) {} failed'); }
|
| if ($x ne $x) { flunk('if ($x ne $x) {} failed'); } else { pass('if ($x ne $x) {} works'); }
|
| if (1) { pass('if (1) {} works'); } else { flunk('if (1) {} failed'); }
|
| if (0) { flunk('if (0) {} failed'); } else { pass('if (0) {} works'); }
|
| if (Mu) { flunk('if (Mu) {} failed'); } else { pass('if (Mu) {} works'); }
|
|
|
| {
|
| # die called in the condition part of an if statement should die immediately
|
| # rather than being evaluated as true
|
| my $foo = 1;
|
| try { if (die "should die") { $foo = 3 } else { $foo = 2; } };
|
| #say '# $foo = ' ~ $foo;
|
| is $foo, 1, "die should stop execution immediately.";
|
| }
|
|
|
| {
|
| my $foo = 1; # just in case
|
| if 1 > 2 { $foo = 2 } else { $foo = 3 };
|
| is $foo, 3, 'if with no parens';
|
| };
|
|
|
| # if...elsif
|
| {
|
| my $foo = 1;
|
| if (1) { $foo = 2 } elsif (1) { $foo = 3 };
|
| is $foo, 2, 'if (1) {} elsif (1) {}';
|
| }
|
|
|
| {
|
| my $foo = 1;
|
| if (1) { $foo = 2 } elsif (0) { $foo = 3 };
|
| is $foo, 2, 'if (1) {} elsif (0) {}';
|
| }
|
|
|
| {
|
| my $foo = 1;
|
| if (0) { $foo = 2 } elsif (1) { $foo = 3 };
|
| is $foo, 3, 'if (0) {} elsif (1) {}';
|
| }
|
|
|
| {
|
| my $foo = 1;
|
| if (0) { $foo = 2 } elsif (0) { $foo = 3 };
|
| is $foo, 1, 'if (0) {} elsif (0) {}';
|
| }
|
|
|
| # if...elsif...else
|
|
|
| {
|
| my $foo = 1;
|
| if (0) { $foo = 2 } elsif (0) { $foo = 3 } else { $foo = 4 };
|
| is $foo, 4;
|
| }
|
|
|
| {
|
| my $foo = 1;
|
| if (1) { $foo = 2 } elsif (0) { $foo = 3 } else { $foo = 4 };
|
| is $foo, 2;
|
| }
|
|
|
| {
|
| my $foo = 1;
|
| if (1) { $foo = 2 } elsif (1) { $foo = 3 } else { $foo = 4 };
|
| is $foo, 2;
|
| }
|
|
|
| {
|
| my $foo = 1;
|
| if (0) { $foo = 2 } elsif (1) { $foo = 3 } else { $foo = 4 };
|
| is $foo, 3;
|
| }
|
|
|
| {
|
| my $foo = 1;
|
| if ({ 1 > 0 }) { $foo = 2 } else { $foo = 3 };
|
| is $foo, 2, 'if with parens, and closure as cond';
|
| }
|
|
|
| {
|
| my $var = 9;
|
| my sub func( $a, $b, $c ) { $var }; #OK not used
|
| if func 1, 2, 3 { $var = 4 } else { $var = 5 };
|
| is $var, 4, 'if with no parens, and call a function without parenthesis';
|
| }
|
|
|
| # I'm not sure where this should go
|
|
|
| {
|
| is(
|
| eval('if ( my $x = 2 ) == 2 { $x; }'),
|
| 2,
|
| "'my' variable within 'if' conditional");
|
| }
|
|
|
| {
|
| eval_dies_ok('if 1; 2', '"if" requires a block');
|
| }
|
|
|
|
|
| {# .... if condition;
|
| my $var = 5 if 1;
|
| is $var, 5, ' <action> if <cond> ; - works';
|
| }
|
|
|
The if and unless statements work much as they do in Perl 5. However, you may omit the parentheses on the conditional:
From t/spec/S04-statements/unless.t lines 13–66: (skip)
-
| # L<S04/Conditional statements/unless statements
|
| # work as in Perl 5>
|
|
|
| my $x = 'test';
|
| {
|
| my $found = 0;
|
| unless $x ne $x { $found = 1; };
|
| ok($found, 'unless $x ne $x works');
|
| }
|
|
|
| {
|
| my $found = 1;
|
| unless $x eq $x { $found = 0; }
|
| ok($found, 'unless $x eq $x is not executed');
|
| }
|
|
|
| {
|
| my $found = 0;
|
| unless 0 { $found = 1; }
|
| ok($found, 'unless 0 is executed');
|
| }
|
|
|
| {
|
| my $found = 1;
|
| unless 1 { $found = 0; }
|
| ok($found, 'unless 1 is not executed');
|
| }
|
|
|
| {
|
| my $found = 0;
|
| unless Mu { $found = 1; }
|
| ok($found, 'unless undefined is executed');
|
| }
|
|
|
| # with parentheses
|
| {
|
| my $found = 0;
|
| unless ($x ne $x) { $found = 1; };
|
| ok($found, 'unless ($x ne $x) works');
|
| }
|
|
|
| {
|
| my $found = 1;
|
| unless (5+2) { $found = 0; }
|
| ok($found, 'unless (5+2) is not executer');
|
| }
|
|
|
| # die called in the condition part of an if statement should die immediately
|
| # rather than being evaluated as a boolean
|
| my $foo = 1;
|
| try { unless (die "should die") { $foo = 3 }};
|
| #say '# $foo = ' ~ $foo;
|
| is $foo, 1, "die should stop execution immediately.";
|
|
|
if $foo == 123 {
...
}
elsif $foo == 321 {
...
}
else {
...
}
The result of a conditional statement is the result of the block chosen to execute. If the conditional does not execute any branch, the return value is Nil.
The unless statement does not allow an elsif or else in Perl 6.
From t/spec/S04-statements/unless.t lines 67–76: (skip)
-
| # L<S04/Conditional statements/"The unless statement does not allow an elsif">
|
|
|
| eval_dies_ok(
|
| ' unless 1 { 2 } else { 3 } ',
|
| 'no else allowed in unless');
|
| eval_dies_ok(
|
| ' unless 1 { 2 } elsif 4 { 3 } ',
|
| 'no elsif allowed in unless');
|
|
|
| # vim: ft=perl6
|
The value of the conditional expression may be optionally bound to a closure parameter:
From t/spec/S04-statements/if.t lines 121–154: (skip)
-
| # L<S04/"Conditional statements"/The value of the conditional expression may be optionally bound to a closure parameter>
|
| {
|
| my ($got, $a_val, $b_val);
|
| my sub testa { $a_val };
|
| my sub testb { $b_val };
|
|
|
| $a_val = 'truea';
|
| $b_val = 0;
|
| if testa() -> $a { $got = $a }
|
| elsif testb() -> $b { $got = $b }
|
| else -> $c { $got = $c }
|
| is $got, 'truea', 'if test() -> $a { } binding';
|
|
|
| $a_val = 0;
|
| $b_val = 'trueb';
|
| if testa() -> $a { $got = $a }
|
| elsif testb() -> $b { $got = $b }
|
| else -> $c { $got = $c }
|
| is $got, 'trueb', 'elsif test() -> $b { } binding';
|
|
|
| $a_val = '';
|
| $b_val = 0;
|
| if testa() -> $a { $got = $a }
|
| elsif testb() -> $b { $got = $b }
|
| else -> $c { $got = $c }
|
| is $got, 0, 'else -> $c { } binding previous elsif';
|
|
|
| $a_val = '';
|
| $b_val = 0;
|
| if testa() -> $a { $got = $a }
|
| else -> $c { $got = $c }
|
| is $got, '', 'else -> $c { } binding previous if';
|
| }
|
|
|
if testa() -> $a { say $a }
elsif testb() -> $b { say $b }
else -> $b { say $b }
Note that the value being evaluated for truth and subsequently bound is not necessarily a value of type Bool. (All normal types in Perl may be evaluated for truth. In fact, this construct would be relatively useless if you could bind only boolean values as parameters, since within the closure you already know whether it evaluated to true or false.) Binding within an else automatically binds the value tested by the previous if or elsif, which, while known to be false, might nevertheless be an interesting value of false. (By similar reasoning, an unless allows binding of a false parameter.)
An explicit placeholder may also be used:
if blahblah() { return $^it }
However, use of $_ with a conditional statement's block is not considered sufficiently explicit to turn a 0-ary block into a 1-ary function, so both these methods use the same invocant:
if .haste { .waste }
(Contrast with a non-conditional statement such as:
for .haste { .waste }
where each call to the block would bind a new invocant for the .waste method, each of which is likely different from the original invocant to the .haste method.)
Conditional statement modifiers work as in Perl 5. So do the implicit conditionals implied by short-circuit operators. Note though that the contents of parens or brackets is parsed as a semicolon-separated list of statements, so you can say:
From t/spec/S04-statement-modifiers/unless.t lines 9–41: (skip)
-
| # L<S04/"Conditional statements"/Conditional statement modifiers work as in Perl 5>
|
| {
|
| my $a = 1;
|
| $a = 4 unless 'a' eq 'a';
|
| is($a, 1, "post unless");
|
| }
|
|
|
| {
|
| my $a = 1;
|
| $a = 5 unless 'a' eq 'b';
|
| is($a, 5, "post unless");
|
| }
|
|
|
| {
|
| my $answer = 1;
|
| my @x = 41, (42 unless $answer), 43;
|
| my @y = 41, (!$answer ?? 42 !! ()), 43;
|
| my @z = 41, 43;
|
| is @y, @z, "sanity check";
|
| is @x, @y, "unless expr on true cond";
|
| }
|
|
|
| {
|
| my $answer = 0;
|
| my @x = 41, (42 unless $answer), 43;
|
| my @y = 41, (!$answer ?? 42 !! ()), 43;
|
| my @z = 41, 42, 43;
|
| is @y, @z, "sanity check";
|
| is @x, @y, "unless expr on false cond";
|
| }
|
|
|
|
|
| # vim: ft=perl6
|
From t/spec/S04-statement-modifiers/values_in_bool_context.t lines 7–91: (skip)
-
| # L<S04/Conditional statements/Conditional statement
|
| # modifiers work as in Perl 5>
|
|
|
| ## scalar checking ##
|
|
|
| {
|
| my $var = 20;
|
|
|
| my ($a, $b, $c, $d, $e, $f, $g, $h);
|
|
|
| $a = 1 if 1;
|
| $b = 1 if 0;
|
| $c = 1 if "true";
|
| $d = 1 if "";
|
| $e = 1 if "1";
|
| $f = 1 if "0";
|
| $g = 1 if Mu;
|
| $h = 1 if $var;
|
|
|
| ok $a, 'literal in bool context - numeric true value';
|
| ok !$b, 'literal in bool context - numeric false value';
|
| ok $c, 'literal in bool context - string true value';
|
| ok !$d, 'literal in bool context - string false value';
|
| ok $e, 'literal in bool context - stringified true value';
|
| ok !$f, 'literal in bool context - stringified false value';
|
| ok !$g, 'literal in bool context - undefined value';
|
| ok $h, 'literal in bool context - scalar variable';
|
| }
|
|
|
| ## array checking ##
|
|
|
| {
|
| my @array = (1, 0, "true", "", "1", "0", Mu);
|
|
|
| my ($a, $b, $c, $d, $e, $f, $g, $h);
|
|
|
| $a = 1 if @array[0];
|
| $b = 1 if @array[1];
|
| $c = 1 if @array[2];
|
| $d = 1 if @array[3];
|
| $e = 1 if @array[4];
|
| $f = 1 if @array[5];
|
| $g = 1 if @array[6];
|
| $h = 1 if @array;
|
|
|
| ok $a, 'array in bool context - numeric true value';
|
| ok !$b, 'array in bool context - numeric false value';
|
| ok $c, 'array in bool context - string true value';
|
| ok !$d, 'array in bool context - string false value';
|
| ok $e, 'array in bool context - stringified true value';
|
| ok !$f, 'array in bool context - stringified false value';
|
| ok !$g, 'array in bool context - undefined value';
|
| ok $h, 'array in bool context array as a whole';
|
| }
|
|
|
| ## hash checking ##
|
|
|
| {
|
| my %hash = (
|
| 0 => 1, 1 => 0, 2 => "true",
|
| 3 => "", 4 => "1", 5 => "0", 6 => Mu
|
| );
|
|
|
| my ($a, $b, $c, $d, $e, $f, $g, $h);
|
|
|
| $a = 1 if %hash{0};
|
| $b = 1 if %hash{1};
|
| $c = 1 if %hash{2};
|
| $d = 1 if %hash{3};
|
| $e = 1 if %hash{4};
|
| $f = 1 if %hash{5};
|
| $g = 1 if %hash{6};
|
| $h = 1 if %hash;
|
|
|
| ok $a, 'hash in bool context - numeric true value';
|
| ok !$b, 'hash in bool context - numeric false value';
|
| ok $c, 'hash in bool context - string true value';
|
| ok !$d, 'hash in bool context - string false value';
|
| ok $e, 'hash in bool context - stringified true value';
|
| ok !$f, 'hash in bool context - stringified false value';
|
| ok !$g, 'hash in bool context - undefined value';
|
| ok $h, 'hash in bool context - hash as a whole';
|
| }
|
|
|
| # vim: ft=perl6
|
From t/spec/S04-statement-modifiers/until.t lines 7–38: (skip)
-
| # L<S04/"Conditional statements"/Conditional statement modifiers work as in Perl 5>
|
|
|
| # test simple the ``until'' statement modifier
|
| {
|
| my $a=0;
|
| $a += 1 until $a >= 10;
|
| is($a, 10, "post until");
|
| }
|
|
|
| # test the ``until'' statement modifier
|
| {
|
| my ($a, $b);
|
| $a=0; $b=0;
|
| $a += $b += 1 until $b >= 10;
|
| is($a, 55, "post until");
|
| }
|
|
|
| {
|
| my @a = ('a', 'b', 'a');
|
| my $a = 'b';
|
| $a ~= ', ' ~ shift @a until !+@a;
|
| is($a, "b, a, b, a", "post until");
|
| }
|
|
|
| {
|
| my @a = 'a'..'e';
|
| my $a = 0;
|
| $a++ until shift(@a) eq 'c';
|
| is($a, 2, "post until");
|
| }
|
|
|
| # vim: ft=perl6
|
From t/spec/S04-statement-modifiers/for.t lines 7–77: (skip)
-
| # L<S04/"Conditional statements"/Conditional statement modifiers work as in Perl 5>
|
|
|
| # test the for statement modifier
|
| {
|
| my $a = '';
|
| $a ~= $_ for ('a', 'b', 'a', 'b', 'a');
|
| is($a, "ababa", "post for with parens");
|
| }
|
|
|
| # without parens
|
| {
|
| my $a = '';
|
| $a ~= $_ for 'a', 'b', 'a', 'b', 'a';
|
| is($a, "ababa", "post for without parens");
|
| }
|
|
|
| {
|
| my $a = 0;
|
| $a += $_ for (1 .. 10);
|
| is($a, 55, "post for 1 .. 10 with parens");
|
| }
|
|
|
| {
|
| my $a = 0;
|
| $a += $_ for 1 .. 10;
|
| is($a, 55, "post for 1 .. 10 without parens");
|
| }
|
|
|
| {
|
| my @a = (5, 7, 9);
|
| my $a = 3;
|
| $a *= $_ for @a;
|
| is($a, 3 * 5 * 7 * 9, "post for array");
|
| }
|
|
|
| {
|
| my @a = (5, 7, 9);
|
| my $i = 5;
|
| my sub check(Int $n){
|
| is($n, $i, "sub Int with post for");
|
| $i += 2;
|
| }
|
| check $_ for @a;
|
| }
|
|
|
| {
|
| my $a = "";
|
| $a ~= "<$_>" for "hello";
|
| is $a, "<hello>", 'iterating one constant element works';
|
| }
|
|
|
| {
|
| my $a = ""; my $v = "hello";
|
| $a ~= "<$_>" for $v;
|
| is $a, "<hello>", 'iterating one variable element works';
|
| }
|
|
|
| #?rakudo todo '{ ... } for 1..3 should execute the closure'
|
| {
|
| my $a = 0;
|
| { $a++ } for 1..3;
|
| is $a, 3, 'the closure was called';
|
| }
|
|
|
| #?rakudo todo '{ ... } for 1..3 should execute the closure'
|
| {
|
| my $a = 0;
|
| -> $i { $a += $i } for 1..3;
|
| is $a, 6, 'the closure was called';
|
| }
|
|
|
From t/spec/S04-statement-modifiers/given.t lines 7–28: (skip)
-
| # L<S04/"Conditional statements"/Conditional statement modifiers work as in Perl 5>
|
|
|
| # test the ``given'' statement modifier
|
| {
|
| my $a = 0;
|
| $a = $_ given 2 * 3;
|
| is($a, 6, "post given");
|
| }
|
|
|
| # test the ``given'' statement modifier
|
| {
|
| my $a;
|
| $a = $_ given 2 * 3;
|
| is($a, 6, "post given");
|
| }
|
|
|
| {
|
| my $a = '';
|
| $a = $_ given 'a';
|
| is($a, 'a', "post given");
|
| }
|
|
|
From t/spec/S04-statement-modifiers/if.t lines 7–75: (skip)
-
| # L<S04/"Conditional statements"/Conditional statement modifiers work as in Perl 5>
|
|
|
| # test the if statement modifier
|
| {
|
| my $a = 1;
|
| $a = 2 if 'a' eq 'a';
|
| is($a, 2, "post if");
|
| }
|
|
|
| {
|
| my $a = 1;
|
| $a = 3 if 'a' eq 'b';
|
| is($a, 1, "post if");
|
| }
|
|
|
| {
|
| my $answer = 1;
|
| my @x = 41, (42 if $answer), 43;
|
| my @y = 41, ($answer ?? 42 !! ()), 43;
|
| my @z = 41, 42, 43;
|
| is @y, @z, "sanity check";
|
| is @x, @y, "if expr on true cond";
|
| }
|
|
|
| {
|
| my $answer = 0;
|
| my @x = 41, (42 if $answer), 43;
|
| my @y = 41, ($answer ?? 42 !! ()), 43;
|
| my @z = 41, 43;
|
| is @y, @z, "sanity check";
|
| is @x, @y, "if expr on false cond";
|
| }
|
|
|
|
|
| #testing else part of the operator
|
| {
|
| my $answer = 0;
|
| my $x = $answer ?? 42 !! 43;
|
| is $x, 43, "?? || sanity check";
|
| }
|
|
|
| {
|
| sub foo() {
|
| return if 1;
|
| 123;
|
| }
|
|
|
| my $ok = 1;
|
| for foo() -> @foo {
|
| $ok = 0;
|
| }
|
| ok $ok, "condition in statement level respects context"
|
| }
|
|
|
| {
|
| my $x = (3 if 1);
|
| my $y = (3 if 0);
|
| is $x, 3, '(3 if 1) worked in scalar context';
|
| ok !$y, 'my $y = (3 if 0) leaves $y false';
|
| }
|
|
|
| # return value of false 'if' should be Nil
|
| # see http://rt.perl.org/rt3/Ticket/Display.html?id=66544
|
|
|
| {
|
| is (42 if 0), Nil, '"$something if 0" is Nil';
|
| }
|
|
|
| # vim: ft=perl6
|
From t/spec/S04-statement-modifiers/while.t lines 7–46: (skip)
-
| # L<S04/"Conditional statements"/Conditional statement modifiers work as in Perl 5>
|
|
|
| # simple while modifier test
|
| {
|
| my $a = 0;
|
| $a += 1 while $a < 10;
|
| is($a, 10, "post simple while modifier");
|
| }
|
|
|
| # simple while modifier test
|
| #?rakudo skip 'Use of type object as value in numeric context'
|
| {
|
| my $a;
|
| $a += 1 while $a < 10;
|
| is($a, 10, "post simple while modifier");
|
| }
|
|
|
| # test the ``while'' statement modifier
|
| {
|
| my $a = 0;
|
| my $b = 0;
|
| $a += $b += 1 while $b < 10;
|
| is($a, 55, "post while");
|
| }
|
|
|
| {
|
| my @a = 'b'..'d';
|
| my $a = 'a';
|
| $a ~= ', ' ~ shift @a while @a;
|
| is($a, "a, b, c, d", "post while");
|
| }
|
|
|
| {
|
| my @a = 'a'..'e';
|
| my $a = 0;
|
| ++$a while shift(@a) ne 'd';
|
| is($a, 3, "post while");
|
| }
|
|
|
| # vim: ft=perl6
|
@x = 41, (42 if $answer), 43;
and that is equivalent to:
@x = 41, ($answer ?? 42 !! Nil), 43
Looping statement modifiers are the same as in Perl 5 except that, for ease of writing list comprehensions, a looping statement modifier is allowed to contain a single conditional statement modifier:
@evens = ($_ * 2 if .odd for 0..100);
Loop modifiers next, last, and redo also work as in Perl 5. However, the labelled forms use method call syntax: LABEL.next, etc. The .next and .last methods take an optional argument giving the final value of that loop iteration. So the old next LINE syntax is still allowed but is really short for next LINE: using indirect object syntax. Any block object can be used, not just labels, so to return a value from this iteration of the current block you can say:
From t/spec/S04-statements/next.t lines 5–161: (skip)
-
| # L<S04/"Loop statements"/next>
|
|
|
| =begin pod
|
| next
|
| next if <condition>;
|
| <condition> and next;
|
| next <label>;
|
| next in nested loops
|
| next <label> in nested loops
|
|
|
| =end pod
|
|
|
| plan 12;
|
|
|
| # test for loops with next
|
|
|
| {
|
| my $tracker=0; for 1..2 { next; $tracker++;}
|
| is(
|
| $tracker,
|
| 0,
|
| "tracker is 0 because next before increment",
|
| );
|
| }
|
|
|
| {
|
| my $tracker = 0; for 1..5 { next unless 2 < $_ < 4; $tracker = $_;}
|
| is(
|
| $tracker,
|
| 3,
|
| "... nothing before or after 3 (next unless <cond>)",
|
| );
|
| }
|
|
|
| {
|
| my $tracker = 0; for 1..5 { $_ > 3 && next; $tracker = $_;}
|
| is(
|
| $tracker,
|
| 3,
|
| "... nothing after 3 (<cond> && next)",
|
| );
|
| }
|
|
|
| {
|
| my $tracker = 0; for 1..5 { $_ > 3 and next; $tracker = $_;}
|
| is(
|
| $tracker,
|
| 3,
|
| "... nothing after 3 (<cond> and next)",
|
| );
|
| }
|
|
|
| #?rakudo skip 'next BLOCK'
|
| {
|
| my $tracker="err";
|
| $tracker = 0; DONE: for 1..2 { next DONE; $tracker++;};
|
| is(
|
| $tracker,
|
| 0,
|
| "tracker is 0 because next before increment",
|
| );
|
| }
|
|
|
| {
|
| my $tracker=0; for 1..5 -> $out {for 10..11 -> $in { next if $out > 2; $tracker = $in + $out;}}
|
| is($tracker,
|
| 13,
|
| 'inner loop skips once inner is run twice (next inside nested loops)',
|
| );
|
| }
|
|
|
| #?rakudo skip 'next LOOP'
|
| {
|
| my $tracker="err";
|
| $tracker = 0;
|
| OUT: for 1..2 {
|
| IN: for 1..2 {
|
| next OUT;
|
| $tracker++;
|
| }
|
| }
|
| is(
|
| $tracker,
|
| 0,
|
| "tracker is 0 because next before increment in nested loop",
|
| );
|
| }
|
|
|
| =begin pod
|
|
|
| Check that C<next> works on the correct loop/block
|
|
|
| =end pod
|
|
|
| {
|
| my $foo = '';
|
| for 1..2 -> $a {
|
| $foo ~= "A";
|
| for 1..2 -> $b {
|
| $foo ~= "B";
|
| next; # works on higher level loop, should work on inner
|
| }
|
| }
|
| is($foo, "ABBABB", "next works on inner loop of 2");
|
| }
|
|
|
| {
|
| my $bar = '';
|
| for 1..2 -> $a {
|
| $bar ~= "A";
|
| for 1..2 -> $b {
|
| $bar ~= "B";
|
| for 1..2 -> $c {
|
| $bar ~= "C";
|
| next; # same thing
|
| }
|
| }
|
| }
|
| is($bar, "ABCCBCCABCCBCC", "next works on inner loop of 3");
|
| }
|
|
|
| {
|
| my @log;
|
| my $i = 0;
|
| while ++$i < 2 {
|
| push @log, "before";
|
| next;
|
| push @log, "after";
|
| }
|
|
|
| is(~@log, "before", "statements after next are not executed");
|
| }
|
|
|
| {
|
| my $i = 0;
|
|
|
| for 1, 1, 0, 1, 0, 1 -> $x {
|
| if ($x) { next }
|
| $i++;
|
| }
|
|
|
| is($i, 2, '$i++ executed only twice, because next ')
|
| }
|
|
|
| {
|
| my $i = 0;
|
| my $j;
|
|
|
| loop ($j = 0; $j < 6; $j++) {
|
| if ($j % 2 == 0) { next }
|
| $i++;
|
| }
|
|
|
| is($i, 3, '$i++ was not executed when next was called before it in loop {}');
|
| }
|
|
|
| # vim: ft=perl6
|
From t/spec/S04-statements/redo.t lines 4–112: (skip)
-
| # L<S04/"Loop statements"/redo>
|
| plan 10;
|
|
|
| {
|
| my $i = 0;
|
| while (defined($i)) { if (++$i < 3) { redo }; last }
|
| is($i, 3, "redo caused reapplication of block");
|
| }
|
|
|
| {
|
| my @log;
|
| my $i;
|
| while ++$i < 5 {
|
| push @log, "before";
|
| if (++$i == 2) {
|
| redo;
|
| } else {
|
| push @log, "no_redo";
|
| }
|
| push @log, "after";
|
| }
|
|
|
| is(~@log, "before before no_redo after before no_redo after", "statements after redo are not executed");
|
| }
|
|
|
| {
|
| my $i = 0;
|
| my $j = 0;
|
|
|
| for (1, 0) -> $x {
|
| if ($x && (++$i % 2 == 0)) { redo };
|
| $j++;
|
| }
|
|
|
| is($j, 2, '$j++ encountered twice');
|
| is($i, 1, '$i++ encountered once');
|
| }
|
|
|
|
|
| {
|
| my $i = 0;
|
| my $j = 0;
|
|
|
| for (1, 0, 1, 0) -> $x {
|
| if ($x && (++$i % 2 == 0)) { redo };
|
| $j++;
|
| }
|
|
|
| is($j, 4, '$j++ encountered four times');
|
| is($i, 3, '$i++ encountered three times');
|
| }
|
|
|
|
|
| {
|
| my $i = 0;
|
| my $j;
|
|
|
| loop ($j = 0; $j < 4; $j++) {
|
| if ($j % 2 == 0 and $i++ % 2 == 0) { redo }
|
| $i-=2;
|
| }
|
|
|
| is($j, 4, '$j unaltered by the fiasco');
|
| is($i, -4, '$i incremented and decremented correct number of times');
|
| }
|
|
|
| {
|
| # rubicon TestLoopStuff.rb
|
| # def testRedoWithFor
|
| # sum = 0
|
| # for i in 1..10
|
| # sum += i
|
| # i -= 1
|
| # if i > 0
|
| # redo
|
| # end
|
| # end
|
| # assert_equal(220, sum)
|
| # end
|
| my $stopping = 100;
|
| my $sum = 0;
|
| for 1..10 -> $i is copy {
|
| $sum += $i;
|
| $i -= 1;
|
| last if !$stopping--;
|
| if $i > 0 {
|
| redo
|
| }
|
| }
|
| say $sum;
|
| # pugs, rakudo and perl5 independently agree that this should be
|
| # 201, not 220 as the ruby example says.
|
| # that's because the ruby example doesn't have the 'is copy' trait.
|
| is($sum, 201, "testRedoWithFor");
|
|
|
| $stopping = 100;
|
| $sum = 0;
|
| my $j = 1;
|
| my $i;
|
| while do {$i = $j; $j++ <= 10} {
|
| $sum += $i;
|
| $i -= 1;
|
| last if !$stopping--;
|
| if $i > 0 { redo }
|
| }
|
| is($sum, 220, "test redo with while");
|
| }
|
|
|
| # vim: ft=perl6
|
From t/spec/S04-statements/last.t lines 5–104: (skip)
-
| # L<S04/Loop statements/last>
|
|
|
| =begin description
|
|
|
| last
|
| last if <condition>;
|
| <condition> and last;
|
| last <label>;
|
| last in nested loops
|
| last <label> in nested loops
|
|
|
| =end description
|
|
|
| plan 8;
|
|
|
| # test for loops with last
|
|
|
| {
|
| my $tracker = 0;
|
| for 1 .. 5 {
|
| $tracker = $_;
|
| last;
|
| }
|
| is($tracker, 1, '... our loop only got to 1 (last)');
|
| }
|
|
|
| {
|
| sub mylast { last; };
|
| my $tracker = 0;
|
| for 1 .. 5 {
|
| $tracker = $_;
|
| mylast();
|
| };
|
| is $tracker, 1, 'can last() outside a subroutine and a for-loop';
|
| }
|
|
|
|
|
| {
|
| my $tracker = 0;
|
| for 1 .. 5 {
|
| $tracker = $_;
|
| last if $_ == 3;
|
| }
|
| is($tracker, 3, '... our loop only got to 3 (last if <cond>)');
|
| }
|
|
|
| {
|
| my $tracker = 0;
|
| for 1 .. 5 {
|
| $tracker = $_;
|
| $_ == 3 && last;
|
| }
|
| is($tracker, 3, '... our loop only got to 3 (<cond> && last)');
|
| }
|
|
|
| {
|
| my $tracker = 0;
|
| for 1 .. 5 {
|
| $tracker = $_;
|
| $_ == 3 and last;
|
| }
|
| is($tracker, 3, '... our loop only got to 3 (<cond> and last)');
|
| }
|
|
|
| #?pugs skip 'last LABEL'
|
| #?rakudo skip 'last LABEL'
|
| {
|
| my $var = 0;
|
| DONE: for (1..2) {
|
| last DONE;
|
| $var++;
|
| };
|
| is($var, 0, "var is 0 because last before increment")
|
| }
|
|
|
| {
|
| my $tracker = 0;
|
| for (1 .. 5) -> $out {
|
| for (10 .. 11) -> $in {
|
| $tracker = $in + $out;
|
| last;
|
| }
|
| }
|
| is($tracker, 15, 'our inner loop only runs once per (last inside nested loops)');
|
| }
|
|
|
| #?pugs skip 'last LABEL'
|
| #?rakudo skip 'last LABEL'
|
| {
|
| my $var = 0;
|
| OUT: for (1..2) {
|
| IN: for (1..2) {
|
| last OUT;
|
| }
|
| $var++;
|
| };
|
| is($var, 0, "var is 0 because last before increment in nested loop");
|
| }
|
|
|
| # vim: ft=perl6
|
&?BLOCK.next($retval);
[Conjecture: a bare next($retval) function could be taught to do the same, as long as $retval isn't a loop label. Presumably multiple dispatch could sort this out.]
There is no longer a continue block. Instead, use a NEXT block within the body of the loop. See below.
The value of a loop statement is the list of values from each iteration. Each iteration's value is returned as a single "argument" object. See S02 for a long definition of argument, but in short, it's either an ordinary object or a parcel containing multiple values.
Normal flat list context ignores parcel boundaries and flattens the list. Slice context turns any parcel objects into Seq objects.
Iterations that return Nil (such as by calling next with no extra return arguments) return that Nil as the next value, which will therefore disappear when interpolated in flat context, but will interpolate a null Seq into slice context.
For finer-grained control of which iterations return values, use gather and take.
Since the final expression in a subroutine returns its value, it's possible to accidentally return a loop's return value when you were only evaluating the loop for its side effects. If you do not wish to accidentally return a list from the final loop statement in a subroutine, place an explicit return statement after it, use a sink statement prefix on the loop itself.
From t/spec/S04-statements/while.t lines 3–48: (skip)
-
| # L<S04/The C<while> and C<until> statements>
|
|
|
| use Test;
|
|
|
| plan 9;
|
|
|
| {
|
| my $i = 0;
|
| while $i < 5 { $i++; };
|
| is($i, 5, 'while $i < 5 {} works');
|
| }
|
|
|
| {
|
| my $i = 0;
|
| while 5 > $i { $i++; };
|
| is($i, 5, 'while 5 > $i {} works');
|
| }
|
|
|
| # with parens
|
| {
|
| my $i = 0;
|
| while ($i < 5) { $i++; };
|
| is($i, 5, 'while ($i < 5) {} works');
|
| }
|
|
|
| {
|
| my $i = 0;
|
| while (5 > $i) { $i++; };
|
| is($i, 5, 'while (5 > $i) {} works');
|
| }
|
|
|
| # single value
|
| {
|
| my $j = 0;
|
| while 0 { $j++; };
|
| is($j, 0, 'while 0 {...} works');
|
| }
|
|
|
| {
|
| my $k = 0;
|
| while $k { $k++; };
|
| is($k, 0, 'while $var {...} works');
|
| }
|
|
|
|
|
| #?mildew skip 1
|
The while and until statements work as in Perl 5, except that you may leave out the parentheses around the conditional:
From t/spec/S04-statements/until.t lines 6–33: (skip)
-
| # L<S04/The C<while> and C<until> statements/while statements
|
| # work as in 5>
|
| {
|
| my $i = 0;
|
| until $i >= 5 { $i++; };
|
| is($i, 5, 'until $i >= 5 {} works');
|
| }
|
|
|
| {
|
| my $i = 0;
|
| until 5 <= $i { $i++; };
|
| is($i, 5, 'until 5 <= $i {} works');
|
| }
|
|
|
| # with parens
|
| {
|
| my $i = 0;
|
| until ($i >= 5) { $i++; };
|
| is($i, 5, 'until ($i >= 5) {} works');
|
| }
|
|
|
| {
|
| my $i = 0;
|
| until (5 <= $i) { $i++; };
|
| is($i, 5, 'until (5 <= $i) {} works');
|
| }
|
|
|
| # vim: ft=perl6
|
while $bar < 100 {
...
}
As with conditionals, you may optionally bind the result of the conditional expression to a parameter of the block:
while something() -> $thing {
...
}
while something() { ... $^thing ... }
Nothing is ever bound implicitly, however, and many conditionals would simply bind True or False in an uninteresting fashion. This mechanism is really only good for objects that know how to return a boolean value and still remain themselves. In general, for most iterated solutions you should consider using a for loop instead (see below). In particular, we now generally use for to iterate filehandles.
Unlike in Perl 5, applying a statement modifier to a do block is specifically disallowed:
do {
...
} while $x < 10; # ILLEGAL
Instead, you should write the more Pascal-like repeat loop:
From t/spec/S04-statements/repeat.t lines 7–24: (skip)
-
| # L<S04/The C<repeat> statement/"more Pascal-like repeat loop">
|
|
|
| {
|
| my $x = 0; repeat { $x++ } while $x < 10;
|
| is($x, 10, 'repeat {} while');
|
| }
|
|
|
| {
|
| my $x = 1; repeat { $x++ } while 0;
|
| is($x, 2, 'ensure repeat {} while runs at least once');
|
| }
|
|
|
| {
|
| my $x = 0;
|
| repeat { $x++; redo if $x < 10 } while 0;
|
| is($x, 10, 'redo works in repeat');
|
| }
|
|
|
repeat {
...
} while $x < 10;
or equivalently:
From t/spec/S04-statements/repeat.t lines 25–41: (skip)
-
| # L<S04/The C<repeat> statement/"or equivalently">
|
|
|
| {
|
| my $x = 0; repeat { $x++ } until $x >= 10;
|
| is($x, 10, 'repeat {} until');
|
| }
|
|
|
| {
|
| my $x = 1; repeat { $x++ } until 1;
|
| is($x, 2, 'ensure repeat {} until runs at least once');
|
| }
|
|
|
| {
|
| my $x = 0; repeat { $x++; redo if $x < 10 } until 1;
|
| is($x, 10, 'redo works in repeat {} until');
|
| }
|
|
|
repeat {
...
} until $x >= 10;
Unlike Perl 5's do-while loop, this is a real loop block now, so next, last, and redo work as expected. The loop conditional on a repeat block is required, so it will be recognized even if you put it on a line by its own:
From t/spec/S04-statements/repeat.t lines 42–64: (skip)
-
| # L<S04/The C<repeat> statement/"loop conditional" on
|
| # "repeat block" required>
|
| {
|
| my $x = 0;
|
| repeat {
|
| $x++;
|
| $x += 2;
|
| } while $x < 10;
|
|
|
| is $x, 12, 'repeat with "} while"';
|
| }
|
|
|
| {
|
| my $x = 0;
|
| repeat {
|
| $x++;
|
| $x += 2;
|
| }
|
| while $x < 10;
|
|
|
| is $x, 12, 'repeat with "}\n while"';
|
| }
|
|
|
repeat
{
...
}
while $x < 10;
However, that's likely to be visually confused with a following while loop at the best of times, so it's also allowed to put the loop conditional at the front, with the same meaning. (The repeat keyword forces the conditional to be evaluated at the end of the loop, so it's still C's do-while semantics.) Therefore, even under GNU style rules, the previous example may be rewritten into a very clear:
From t/spec/S04-statements/repeat.t lines 65–85: (skip)
-
| # L<S04/The C<repeat> statement/put "loop conditional" "at the front">
|
| {
|
| my $x = 0; repeat while $x < 10 { $x++ }
|
| is($x, 10, 'repeat {} while');
|
| }
|
|
|
| {
|
| my $x = 1; repeat while 0 { $x++ }
|
| is($x, 2, 'ensure repeat {} while runs at least once');
|
| }
|
|
|
| {
|
| my $x = 0; repeat while 0 { $x++; redo if $x < 10 };
|
| is($x, 10, 'redo works in repeat');
|
| }
|
|
|
| {
|
| my $x = 0; repeat until $x >= 10 { $x++ }
|
| is($x, 10, 'repeat until {}');
|
| }
|
|
|
repeat while $x < 10
{
...
}
or equivalently:
repeat until $x >= 10
{
...
}
As with an ordinary while, you may optionally bind the result of the conditional expression to a parameter of the block:
From t/spec/S04-statements/repeat.t lines 86–106: (skip)
-
| # L<S04/The C<repeat> statement/"bind the result">
|
| #?rakudo skip 'point block on loop'
|
| {
|
| my $x = 0; repeat until $x >= 10 -> $another_x {
|
| pass('repeat until with binding starts undefined') unless $another_x.defined;
|
| $x++
|
| }
|
| is($x, 10, 'repeat until -> {}');
|
| }
|
|
|
| {
|
| my $x = 1; repeat until 1 { $x++ }
|
| is($x, 2, 'ensure repeat until {} runs at least once');
|
| }
|
|
|
| {
|
| my $x = 0; repeat until 1 { $x++; redo if $x < 10 };
|
| is($x, 10, 'redo works in repeat until {}');
|
| }
|
|
|
| # vim: ft=perl6
|
repeat -> $thing {
...
} while something();
or
repeat while something() -> $thing {
...
}
Since the loop executes once before evaluating the condition, the bound parameter will be undefined that first time through the loop.
From t/spec/S04-statements/loop.t lines 5–96: (skip)
-
| # L<S04/The general loop statement>
|
|
|
| =begin kwid
|
|
|
| loop statement tests
|
|
|
|
|
| =end kwid
|
|
|
| plan 12;
|
|
|
| # basic loop
|
|
|
| {
|
| my $i = 0;
|
| is($i, 0, 'verify our starting condition');
|
| loop ($i = 0; $i < 10; $i++) {}
|
| is($i, 10, 'verify our ending condition');
|
| }
|
|
|
| # loop with last
|
| {
|
| my $i = 0;
|
| is($i, 0, 'verify our starting condition');
|
| loop ($i = 0; $i < 10; $i++) {
|
| if $i == 5 {
|
| last;
|
| }
|
| }
|
| is($i, 5, 'verify our ending condition');
|
| }
|
|
|
| # infinite loop
|
|
|
| {
|
| my $i = 0;
|
| is($i, 0, 'verify our starting condition');
|
| loop (;;) { $i++; last; }
|
| is($i, 1, 'verify our ending condition');
|
| }
|
|
|
| # declare variable $j inside loop
|
| {
|
| my $count = 0;
|
| is($count, 0, 'verify our starting condition');
|
| loop (my $j = 0; $j < 10; $j++) { $count++; };
|
| is($count, 10, 'verify our ending condition');
|
| }
|
|
|
| # Ensure condition is tested on the first iteration
|
| {
|
| my $never_did_body = 1;
|
| loop (;0;)
|
| {
|
| $never_did_body = 0;
|
| }
|
| ok($never_did_body, "loop with an initially-false condition executes 0 times");
|
| }
|
|
|
| # Loop with next should still execute the continue expression
|
| {
|
| my $i;
|
| my $continued;
|
| loop ($i = 0;; $continued = 1)
|
| {
|
| last if $i;
|
| $i++;
|
| next;
|
| }
|
| ok($continued, "next performs a loop's continue expression");
|
| }
|
|
|
| {
|
| my $loopvar = 0;
|
|
|
| loop {
|
| last if ++$loopvar == 3;
|
| }
|
| is($loopvar, 3, "bare loop exited after 3 iterations");
|
| }
|
|
|
| {
|
| my $rt65962 = 'did not loop';
|
|
|
| loop ( my $a = 1, my $b = 2; $a < 5; $a++, $b++ ) {
|
| $rt65962 = "$a $b";
|
| }
|
|
|
| is $rt65962, '4 5', 'loop with two variables in init works';
|
| }
|
|
|
| # vim: ft=perl6
|
The loop statement is the C-style for loop in disguise:
loop ($i = 0; $i < 10; $i++) {
...
}
As in C, the parentheses are required if you supply the 3-part spec; however, the 3-part loop spec may be entirely omitted to write an infinite loop. That is,
loop {...}
is equivalent to the Cish idiom:
loop (;;) {...}
From t/spec/S04-statements/for-scope.t lines 5–60: (skip)
-
| # L<S04/The C<for> statement>
|
|
|
| plan 15;
|
|
|
| # Implicit $_
|
| for 1, 2 {
|
| my $inside = '';
|
| for 1 .. 3 { $inside ~= $_; }
|
| is($inside, "123", "lexical scalar properly initialized, round $_");
|
| }
|
|
|
| for 1, 2 {
|
| my @inside;
|
| for 1 .. 3 { push @inside, $_; }
|
| is(@inside.join, "123", "lexical array properly initialized, round $_");
|
| }
|
|
|
| # Explicit $_
|
| for 1, 2 {
|
| my $inside = '';
|
| for 1 .. 3 -> $_ { $inside ~= $_; }
|
| is($inside, "123", "lexical scalar properly initialized, round $_, explicit \$_");
|
| }
|
|
|
| for 1, 2 {
|
| my @inside;
|
| for 1 .. 3 -> $_ { push @inside, $_; }
|
| is(@inside.join, "123", "lexical array properly initialized, round $_, explicit \$_");
|
| }
|
|
|
| # Explicit $_
|
| for 1, 2 -> $_ {
|
| my $inside = '';
|
| for 1 .. 3 -> $_ { $inside ~= $_; }
|
| is($inside, "123", "lexical scalar properly initialized, round $_, two explicit \$_s");
|
| }
|
|
|
| for 1, 2 -> $_ {
|
| my @inside;
|
| for 1 .. 3 -> $_ { push @inside, $_; }
|
| is(@inside.join, "123", "lexical array properly initialized, round $_, two explicit \$_s");
|
| }
|
|
|
| {
|
| sub respect(*@a) {
|
| my @b = ();
|
| @b.push($_) for @a;
|
| return @b.elems;
|
| }
|
|
|
| is respect(1,2,3), 3, 'a for loop inside a sub loops over each of the elements';
|
| is respect([1,2,3]), 1, '... but only over one array ref';
|
| is respect( my @a = 1, 2, 3 ), 3, '...and when the array is declared in the argument list';
|
| }
|
|
|
| # vim: ft=perl6
|
From t/spec/S04-statements/map-and-sort-in-for.t lines 5–57: (skip)
-
| # L<S04/The C<for> statement>
|
| # L<S32::Containers/List/=item map>
|
| # L<S32::Containers/List/=item sort>
|
|
|
| plan 4;
|
|
|
| # works
|
| {
|
| my @array = <1 2 3 4>;
|
| my $output='';
|
|
|
| for (map { 1 }, @array) -> $elem {
|
| $output ~= "$elem,";
|
| }
|
|
|
| is $output, "1,1,1,1,", "map works in for";
|
| }
|
|
|
| # works, too
|
| {
|
| my @array = <1 2 3 4>;
|
| my $output='';
|
|
|
| for sort @array -> $elem {
|
| $output ~= "$elem,";
|
| }
|
|
|
| is $output, "1,2,3,4,", "sort works in for";
|
| }
|
|
|
| {
|
| my @array = <1 2 3 4>;
|
| my $output='';
|
|
|
| for (map { 1 }, sort @array) -> $elem {
|
| $output ~= "$elem,";
|
| }
|
|
|
| is $output, "1,1,1,1,", "map and sort work in for";
|
| }
|
|
|
| {
|
| my @array = <1 2 3 4>;
|
| my $output='';
|
|
|
| for (map { $_ * 2 }, sort @array) -> $elem {
|
| $output ~= "$elem,";
|
| }
|
|
|
| is $output, "2,4,6,8,", "map and sort work in for";
|
| }
|
|
|
| # vim: ft=perl6
|
From t/spec/S04-statements/for_with_only_one_item.t lines 5–87: (skip)
-
| # L<S04/The C<for> statement>
|
|
|
| # Test primarily aimed at PIL2JS
|
|
|
| plan 9;
|
|
|
| # sanity tests
|
| {
|
| my $res='';
|
|
|
| for <a b c> { $res ~= $_ }
|
| is $res, "abc", "for works with an <...> array literal";
|
| }
|
|
|
| {
|
| my $res='';
|
|
|
| for (<a b c>) { $res ~= $_ }
|
| is $res, "abc", "for works with an (<...>) array literal";
|
| }
|
|
|
| # for with only one item, a constant
|
| {
|
| my $res='';
|
|
|
| for ("a",) { $res ~= $_ }
|
| is $res, "a", "for works with an (a_single_constant,) array literal";
|
| }
|
|
|
| {
|
| my $res='';
|
|
|
| for ("a") { $res ~= $_ }
|
| is $res, "a", "for works with (a_single_constant)";
|
| }
|
|
|
| {
|
| my $res='';
|
|
|
| for "a" { $res ~= $_ }
|
| is $res, "a", "for works with \"a_single_constant\"";
|
| }
|
|
|
| # for with only one item, an arrayref
|
| # See thread "for $arrayref {...}" on p6l started by Ingo Blechschmidt,
|
| # L<"http://www.nntp.perl.org/group/perl.perl6.language/22970">
|
| {
|
| my $arrayref = [1,2,3];
|
|
|
| my $count=0;
|
| for ($arrayref,) { $count++ }
|
|
|
| is $count, 1, 'for ($arrayref,) {...} executes the loop body only once';
|
| }
|
|
|
| {
|
| my $arrayref = [1,2,3];
|
|
|
| my $count=0;
|
| for ($arrayref) { $count++ }
|
|
|
| is $count, 1, 'for ($arrayref) {...} executes the loop body only once';
|
| }
|
|
|
| {
|
| my $arrayref = [1,2,3];
|
|
|
| my $count=0;
|
| for $arrayref { $count++ }
|
|
|
| is $count, 1, 'for $arrayref {...} executes the loop body only once';
|
| }
|
|
|
| # RT #73400
|
| {
|
| my $capture = \[1,2,3];
|
| my $count = 0;
|
| for $capture { $count++ }
|
|
|
| is $count, 1, 'for $capture {...} executes the loop body only once';
|
| }
|
|
|
| # vim: ft=perl6
|
There is no foreach statement any more. It's always spelled for in Perl 6, so it always takes a list as an argument:
From t/spec/S04-statements/for.t lines 19–275: (skip)
-
| # L<S04/The C<for> statement/"no foreach statement any more">
|
| {
|
| my $times_run = 0;
|
| eval_dies_ok 'foreach 1..10 { $times_run++ }; 1', "foreach is gone";
|
| eval_dies_ok 'foreach (1..10) { $times_run++}; 1',
|
| "foreach is gone, even with parens";
|
| is $times_run, 0, "foreach doesn't work";
|
| }
|
|
|
| ## for with plain old range operator w/out parens
|
|
|
| {
|
| my $a = "";
|
| for 0 .. 5 { $a = $a ~ $_; };
|
| is($a, '012345', 'for 0..5 {} works');
|
| }
|
|
|
| # ... with pointy blocks
|
|
|
| {
|
| my $b = "";
|
| for 0 .. 5 -> $_ { $b = $b ~ $_; };
|
| is($b, '012345', 'for 0 .. 5 -> {} works');
|
| }
|
|
|
| #?pugs eval 'todo: slice context'
|
| #?rakudo skip 'slice context'
|
| {
|
| my $str;
|
| my @a = 1..3;
|
| my @b = 5..6;
|
| for zip(@a; @b) -> $x, $y {
|
| $str ~= "($x $y)";
|
| }
|
| is $str, "(1 5)(2 4)(3 6)", 'for zip(@a; @b) -> $x, $y works';
|
| }
|
|
|
| # ... with referential sub
|
| #?rakudo skip 'class accessing outer lexical'
|
| {
|
| my $d = '';
|
| augment class Int {
|
| method some_meth_1 {
|
| $d = $d ~ self
|
| }
|
| };
|
| for 0 .. 5 { .some_meth_1 };
|
| is($d, '012345', 'for 0 .. 5 { .some_sub } works');
|
| }
|
|
|
| ## and now with parens around the range operator
|
| {
|
| my $e = "";
|
| for (0 .. 5) { $e = $e ~ $_; };
|
| is($e, '012345', 'for () {} works');
|
| }
|
|
|
| # ... with pointy blocks
|
| {
|
| my $f = "";
|
| for (0 .. 5) -> $_ { $f = $f ~ $_; };
|
| is($f, '012345', 'for () -> {} works');
|
| }
|
|
|
| # ... with implicit topic
|
|
|
| {
|
| $_ = "GLOBAL VALUE";
|
| for "INNER VALUE" {
|
| is( .lc, "inner value", "Implicit default topic is seen by lc()");
|
| };
|
| is($_,"GLOBAL VALUE","After the loop the implicit topic gets restored");
|
| }
|
|
|
| {
|
| # as statement modifier
|
| $_ = "GLOBAL VALUE";
|
| is( .lc, "inner value", "Implicit default topic is seen by lc()" )
|
| for "INNER VALUE";
|
| is($_,"GLOBAL VALUE","After the loop the implicit topic gets restored");
|
| }
|
|
|
| ## and now for with 'topical' variables
|
|
|
| # ... w/out parens
|
|
|
| my $i = "";
|
| for 0 .. 5 -> $topic { $i = $i ~ $topic; };
|
| is($i, '012345', 'for 0 .. 5 -> $topic {} works');
|
|
|
| # ... with parens
|
|
|
| my $j = "";
|
| for (0 .. 5) -> $topic { $j = $j ~ $topic; };
|
| is($j, '012345', 'for () -> $topic {} works');
|
|
|
|
|
| ## for with @array operator w/out parens
|
|
|
| my @array_k = (0 .. 5);
|
| my $k = "";
|
| for @array_k { $k = $k ~ $_; };
|
| is($k, '012345', 'for @array {} works');
|
|
|
| # ... with pointy blocks
|
|
|
| my @array_l = (0 .. 5);
|
| my $l = "";
|
| for @array_l -> $_ { $l = $l ~ $_; };
|
| is($l, '012345', 'for @array -> {} works');
|
|
|
| ## and now with parens around the @array
|
|
|
| my @array_o = (0 .. 5);
|
| my $o = "";
|
| for (@array_o) { $o = $o ~ $_; };
|
| is($o, '012345', 'for (@array) {} works');
|
|
|
| # ... with pointy blocks
|
| {
|
| my @array_p = (0 .. 5);
|
| my $p = "";
|
| for (@array_p) -> $_ { $p = $p ~ $_; };
|
| is($p, '012345', 'for (@array) -> {} works');
|
| }
|
|
|
| my @elems = <a b c d e>;
|
|
|
| {
|
| my @a;
|
| for (@elems) {
|
| push @a, $_;
|
| }
|
| my @e = <a b c d e>;
|
| is(@a, @e, 'for (@a) { ... $_ ... } iterates all elems');
|
| }
|
|
|
| {
|
| my @a;
|
| for (@elems) -> $_ { push @a, $_ };
|
| my @e = @elems;
|
| is(@a, @e, 'for (@a)->$_ { ... $_ ... } iterates all elems' );
|
| }
|
|
|
| {
|
| my @a;
|
| for (@elems) { push @a, $_, $_; }
|
| my @e = <a a b b c c d d e e>;
|
| is(@a, @e, 'for (@a) { ... $_ ... $_ ... } iterates all elems, not just odd');
|
| }
|
|
|
| # "for @a -> $var" is ro by default.
|
| #?rakudo skip "<-> is confusing the parser, I think"
|
| {
|
| my @a = <1 2 3 4>;
|
|
|
| eval_dies_ok('for @a -> $elem {$elem = 5}', '-> $var is ro by default');
|
|
|
| for @a <-> $elem {$elem++;}
|
| is(@a, <2 3 4 5>, '<-> $var is rw');
|
|
|
| for @a <-> $first, $second {$first++; $second++}
|
| is(@a, <3 4 5 6>, '<-> $var, $var2 works');
|
| }
|
|
|
| # for with "is rw"
|
| {
|
| my @array_s = (0..2);
|
| my @s = (1..3);
|
| for @array_s { $_++ };
|
| is(@array_s, @s, 'for @array { $_++ }');
|
| }
|
|
|
| {
|
| my @array = <a b c d>;
|
| for @array { $_ ~= "c" }
|
| is ~@array, "ac bc cc dc",
|
| 'mutating $_ in for works';
|
| }
|
|
|
| {
|
| my @array_t = (0..2);
|
| my @t = (1..3);
|
| for @array_t -> $val is rw { $val++ };
|
| is(@array_t, @t, 'for @array -> $val is rw { $val++ }');
|
| }
|
|
|
| #?pugs eval 'todo'
|
| #?rakudo skip "is rw NYI"
|
| {
|
| my @array_v = (0..2);
|
| my @v = (1..3);
|
| for @array_v.values -> $val is rw { $val++ };
|
| is(@array_v, @v, 'for @array.values -> $val is rw { $val++ }');
|
| }
|
|
|
| #?pugs eval 'todo'
|
| #?rakudo skip "is rw NYI"
|
| {
|
| my @array_kv = (0..2);
|
| my @kv = (1..3);
|
| for @array_kv.kv -> $key, $val is rw { $val++ };
|
| is(@array_kv, @kv, 'for @array.kv -> $key, $val is rw { $val++ }');
|
| }
|
|
|
| #?pugs eval 'todo'
|
| #?rakudo skip "is rw NYI"
|
| {
|
| my %hash_v = ( a => 1, b => 2, c => 3 );
|
| my %v = ( a => 2, b => 3, c => 4 );
|
| for %hash_v.values -> $val is rw { $val++ };
|
| is(%hash_v, %v, 'for %hash.values -> $val is rw { $val++ }');
|
| }
|
|
|
| #?pugs eval 'todo'
|
| #?rakudo skip "is rw NYI"
|
| {
|
| my %hash_kv = ( a => 1, b => 2, c => 3 );
|
| my %kv = ( a => 2, b => 3, c => 4 );
|
| try { for %hash_kv.kv -> $key, $val is rw { $val++ }; };
|
| is( %hash_kv, %kv, 'for %hash.kv -> $key, $val is rw { $val++ }');
|
| }
|
|
|
| # .key //= ++$i for @array1;
|
| class TestClass{ has $.key is rw };
|
|
|
| #?rakudo skip '[+] NYI'
|
| {
|
| my @array1 = (TestClass.new(:key<1>),TestClass.new());
|
|
|
| my $i = 0;
|
| my $sum1 = [+] @array1.map: { $_.key };
|
| #?pugs todo 'bug'
|
| is( $sum1, 2, '.key //= ++$i for @array1;' );
|
|
|
| }
|
|
|
| # .key = 1 for @array1;
|
| {
|
| my @array1 = (TestClass.new(),TestClass.new(:key<2>));
|
|
|
| .key = 1 for @array1;
|
| my $sum1 = [+] @array1.map: { $_.key };
|
| is($sum1, 2, '.key = 1 for @array1;');
|
| }
|
|
|
| # $_.key = 1 for @array1;
|
| {
|
| my @array1 = (TestClass.new(),TestClass.new(:key<2>));
|
|
|
| $_.key = 1 for @array1;
|
| my $sum1 = [+] @array1.map: { $_.key };
|
| is( $sum1, 2, '$_.key = 1 for @array1;');
|
|
|
| }
|
|
|
| # rw scalars
|
for @foo { .print }
As mentioned earlier, the loop variable is named by passing a parameter to the closure:
for @foo -> $item { print $item }
Multiple parameters may be passed, in which case the list is traversed more than one element at a time:
for %hash.kv -> $key, $value { print "$key => $value\n" }
To process two arrays in parallel use the zip function to generate a list that can be bound to the corresponding number of parameters:
for zip(@a;@b) -> $a, $b { print "[$a, $b]\n" }
for @a Z @b -> $a, $b { print "[$a, $b]\n" } # same thing
The list is evaluated lazily by default, so instead of using a while to read a file a line at a time as you would in Perl 5:
while (my $line = <STDIN>) {...}
in Perl 6 you should use a for instead:
for $*IN.lines -> $line {...}
This has the added benefit of limiting the scope of the $line parameter to the block it's bound to. (The while's declaration of $line continues to be visible past the end of the block. Remember, no implicit block scopes.) It is also possible to write
From t/spec/S04-statements/while.t lines 49–70: (skip)
-
| # L<S04/The C<for> statement/It is also possible to write>
|
| # while ... -> $x {...}
|
| {
|
| my @array = 1..5;
|
| my $str = "";
|
| while @array.pop -> $x {
|
| $str ~= $x;
|
| }
|
| is $str, '54321', 'while ... -> $x {...} worked (1)';
|
| }
|
|
|
| #?mildew skip 1
|
| {
|
| my @array = 0..5;
|
| my $str = "";
|
| while pop @array -> $x {
|
| $str ~= $x;
|
| }
|
| is $str, '54321', 'while ... -> $x {...} worked (2)';
|
| }
|
|
|
| #?mildew skip 1
|
while $*IN.get -> $line {...}
However, this is likely to fail on autochomped filehandles, so use the for loop instead.
Note also that Perl 5's special rule causing
while (<>) {...}
to automatically assign to $_ is not carried over to Perl 6. That should now be written:
for lines() {...}
which is short for
for lines($*ARGFILES) {...}
Arguments bound to the formal parameters of a pointy block are by default readonly within the block. You can declare a parameter read/write by including the "is rw" trait. The following treats every other value in @values as modifiable:
From t/spec/S04-blocks-and-statements/pointy-rw.t lines 9–65: (skip)
-
| # L<S04/The C<for> statement/by including the is rw trait>
|
|
|
| =end pod
|
|
|
| plan 10;
|
|
|
| #?pugs 8 todo 'rw aliasing'
|
|
|
| {
|
| my %h = 1..4;
|
| lives_ok {
|
| for %h.values -> $v is rw { $v += 1 }
|
| }, 'aliases returned by %hash.values should be rw (1)';
|
|
|
| is %h<3>, 5, 'aliases returned by %hash.values should be rw (2)';
|
| }
|
|
|
| {
|
| my %h = 1..4;
|
| lives_ok {
|
| for %h.values <-> $v { $v += 1 }
|
| }, 'aliases returned by %hash.values should be rw (<->) (1)';
|
|
|
| is %h<3>, 5, 'aliases returned by %hash.values should be rw (<->) (2)';
|
| }
|
|
|
| {
|
| my @a = 1..4;
|
| lives_ok {
|
| for @a.values -> $v is rw { $v += 1 }
|
| }, 'aliases returned by @array.values should be rw (1)';
|
|
|
| is @a[2], 4, 'aliases returned by @array.values should be rw (2)';
|
| }
|
|
|
| {
|
| my $pair = (a => 42);
|
| lives_ok {
|
| for $pair.value -> $v is rw { $v += 1 }
|
| }, 'aliases returned by $pair.values should be rw (1)';
|
|
|
| is $pair.value, 43, 'aliases returned by $pair.values should be rw (2)';
|
| }
|
|
|
| {
|
| my $var = 42;
|
| my $pair = (a => $var);
|
| lives_ok {
|
| for $pair.value -> $v is rw { $v += 1 }
|
| }, 'aliases returned by $pair.values should be rw (1)';
|
|
|
| is $pair.value, 43, 'aliases returned by $pair.values should be rw (2)';
|
| }
|
|
|
| # (currently this dies with "Can't modify constant item: VInt 2")
|
|
|
| # vim: ft=perl6
|
for @values -> $even is rw, $odd { ... }
In the case where you want all your parameters to default to rw, you may use the visually suggestive double-ended arrow to indicate that values flow both ways:
for @values <-> $even, $odd { ... }
This is equivalent to
for @values -> $even is rw, $odd is rw { ... }
If you rely on $_ as the implicit parameter to a block, then $_ is considered read/write by default. That is, the construct:
From t/spec/S04-statements/for.t lines 276–357: (skip)
-
| #L<S04/The C<for> statement/implicit parameter to block read/write "by default">
|
| {
|
| my ($a, $b, $c) = 0..2;
|
| try { for ($a, $b, $c) { $_++ } };
|
| is( [$a,$b,$c], [1,2,3], 'for ($a,$b,$c) { $_++ }');
|
|
|
| ($a, $b, $c) = 0..2;
|
| try { for ($a, $b, $c) -> $x is rw { $x++ } };
|
| is( [$a,$b,$c], [1,2,3], 'for ($a,$b,$c) -> $x is rw { $x++ }');
|
| }
|
|
|
| # list context
|
|
|
| {
|
| my $a = '';
|
| for 1..3, 4..6 { $a ~= $_.WHAT };
|
| is($a, 'Int()Int()Int()Int()Int()Int()', 'List context');
|
|
|
| $a = '';
|
| for [1..3, 4..6] { $a ~= $_.WHAT };
|
| is($a, 'Array()', 'List context');
|
|
|
| $a = '';
|
| for [1..3], [4..6] { $a ~= $_.WHAT };
|
| is($a, 'Array()Array()', 'List context');
|
| }
|
|
|
| {
|
| # this was a rakudo bug with mixed 'for' and recursion, which seems to
|
| # confuse some lexical pads or the like, see RT #58392
|
| my $gather = '';
|
| sub f($l) {
|
| if $l <= 0 {
|
| return $l;
|
| }
|
| $gather ~= $l;
|
| for 1..3 {
|
| f($l-1);
|
| $gather ~= '.';
|
| }
|
| }
|
| f(2);
|
|
|
| is $gather, '21....1....1....', 'Can mix recursion and for';
|
| }
|
|
|
| # grep and sort in for - these were pugs bugs once, so let's
|
| # keep them as regression tests
|
|
|
| {
|
| my @array = <1 2 3 4>;
|
| my $output = '';
|
|
|
| for (grep { 1 }, @array) -> $elem {
|
| $output ~= "$elem,";
|
| }
|
|
|
| is $output, "1,2,3,4,", "grep works in for";
|
| }
|
|
|
| {
|
| my @array = <1 2 3 4>;
|
| my $output = '';
|
|
|
| for @array.sort -> $elem {
|
| $output ~= "$elem,";
|
| }
|
|
|
| is $output, "1,2,3,4,", "sort works in for";
|
| }
|
|
|
| {
|
| my @array = <1 2 3 4>;
|
| my $output = '';
|
|
|
| for (grep { 1 }, @array.sort) -> $elem {
|
| $output ~= "$elem,";
|
| }
|
|
|
| is $output, "1,2,3,4,", "grep and sort work in for";
|
| }
|
|
|
for @foo {...}
is actually short for:
for @foo <-> $_ {...}
so you can modify the current list element in that case.
When used as statement modifiers on implicit blocks (thunks), for and given privately temporize the current value of $_ for the left side of the statement and restore the original value at loop exit:
From t/spec/S04-statement-modifiers/for.t lines 78–86: (skip)
-
| # L<S04/The C<for> statement/for and given privately temporize>
|
| {
|
| my $i = 0;
|
| $_ = 10;
|
| $i += $_ for 1..3;
|
| is $_, 10, 'outer $_ did not get updated in lhs of for';
|
| is $i, 1+2+3, 'postfix for worked';
|
| }
|
|
|
From t/spec/S04-statement-modifiers/for.t lines 87–107: (skip)
-
| # L<S04/The C<for> statement/When used as statement modifiers on implicit blocks>
|
|
|
| {
|
| $_ = 42;
|
| my @trace;
|
| @trace.push: $_ for 2, 3;
|
| is @trace.join(':'), '2:3', 'statement modifier "for" sets $_ correctl';
|
| is $_, 42, '"for" statement modifier restored $_ of outer block';
|
| }
|
|
|
| # RT 66622
|
| {
|
| my $rt66622 = 66622 for 1, 2, 3;
|
| is $rt66622, 66622, 'statement modifier "for" makes no implicit block';
|
| }
|
|
|
| eval_dies_ok '1 for <a b> for <c d>;', 'double statement-modifying for is not allowed';
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
From t/spec/S04-statement-modifiers/given.t lines 29–38: (skip)
-
| # L<S04/The C<for> statement/for and given privately temporize>
|
| {
|
| my $i = 0;
|
| $_ = 10;
|
| $i += $_ given $_+3;
|
| is $_, 10, 'outer $_ did not get updated in lhs of given';
|
| is $i, 13, 'postfix given worked';
|
| }
|
|
|
| # vim: ft=perl6
|
$_ = 42;
.say # 42
.say for 1,2,3; # 1,2,3
.say; # 42
The previous value of $_ is not available within the loop. If you want it to be available, you must rewrite it as an explicit block using curlies:
{ say OUTER::<$_>, $_ } for 1,2,3; # 421,422,423
No temporization is necessary with the explicit form since $_ is a formal parameter to the block. Likewise, temporization is never needed for statement_control:<for> because it always calls a closure.
In Perl 5, a bare block is deemed to be a do-once loop. In Perl 6, the bare block is not a do-once. Instead do {...} is the do-once loop (which is another reason you can't put a statement modifier on it; use repeat for a test-at-the-end loop).
From t/spec/S04-statements/do.t lines 7–26: (skip)
-
| # L<S04/The do-once loop/"can't" put "statement modifier">
|
| # Note in accordance with STD, conditionals are OK, loops are not.
|
| eval_dies_ok 'my $i = 1; do { $i++ } while $i < 5;',
|
| "'do' can't take the 'while' modifier";
|
|
|
| eval_dies_ok 'my $i = 1; do { $i++ } until $i > 4;',
|
| "'do' can't take the 'until' modifier";
|
|
|
| eval_dies_ok 'my $i; do { $i++ } for 1..3;',
|
| "'do' can't take the 'for' modifier";
|
|
|
| eval_dies_ok 'my $i; do { $i++ } given $i;',
|
| "'do' can't take the 'given' modifier";
|
|
|
| eval_lives_ok 'my $i; do { $i++ } unless $i;',
|
| "'do' can take the 'unless' modifier";
|
|
|
| eval_lives_ok 'my $i = 1; do { $i++ } if $i;',
|
| "'do' can take the 'if' modifier";
|
|
|
From t/spec/S04-statements/do.t lines 124–135: (skip)
-
| # L<S04/The do-once loop/"bare block is not a do-once">
|
| {
|
| eval_dies_ok 'my $i; { $i++; next; $i--; }',
|
| "bare block can't take 'next'";
|
|
|
| eval_dies_ok 'my $i; { $i++; last; $i--; }',
|
| "bare block can't take 'last'";
|
|
|
| eval_dies_ok 'my $i; { $i++; redo; $i--; }',
|
| "bare block can't take 'last'";
|
| }
|
|
|
For any statement, prefixing with a do allows you to return the value of that statement and use it in an expression:
From t/spec/S04-statements/do.t lines 27–63: (skip)
-
| # L<S04/The do-once loop/statement "prefixing with" do>
|
| {
|
| my $x;
|
| my ($a, $b, $c) = 'a' .. 'c';
|
|
|
| $x = do if $a { $b } else { $c };
|
| is $x, 'b', "prefixing 'if' statement with 'do' (then)";
|
|
|
| $x = do if !$a { $b } else { $c };
|
| is $x, 'c', "prefixing 'if' statement with 'do' (else)";
|
| }
|
|
|
| =begin comment
|
| If the final statement is a conditional which does not execute
|
| any branch, the return value is undefined in item context and ()
|
| in list context.
|
| =end comment
|
| #?rakudo skip 'if returning Nil'
|
| {
|
| my $x = do if 0 { 1 } elsif 0 { 2 };
|
| ok !$x.defined, 'when if does not execute any branch, return undefined';
|
| }
|
|
|
| {
|
| my $ret = do given 3 {
|
| when 3 { 1 }
|
| };
|
| is($ret, 1, 'do STMT works');
|
| }
|
|
|
| {
|
| my $ret = do { given 3 {
|
| when 3 { 1 }
|
| } };
|
| is($ret, 1, 'do { STMT } works');
|
| }
|
|
|
$x = do if $a { $b } else { $c };
This construct only allows you to attach a single statement to the end of an expression. If you want to continue the expression after the statement, or if you want to attach multiple statements, you must either use the curly form or surround the entire expression in brackets of some sort:
@primesquares = (do $_ if prime($_) for 1..100) »**» 2;
Since a bare expression may be used as a statement, you may use do on an expression, but its only effect is to function as an unmatched left parenthesis, much like the $ operator in Haskell. That is, precedence decisions do not cross a do boundary, and the missing "right paren" is assumed at the next statement terminator or unmatched bracket. A do is unnecessary immediately after any opening bracket as the syntax inside brackets is a semicolon-separated list of statements, so the above can in fact be written:
From t/spec/S04-statements/do.t lines 64–82: (skip)
-
| # L<S04/The do-once loop/"you may use" do "on an expression">
|
| {
|
| my $ret = do 42;
|
| is($ret, 42, 'do EXPR should also work (single number)');
|
|
|
| $ret = do 3 + 2;
|
| is($ret, 5, 'do EXPR should also work (simple + expr)');
|
|
|
| $ret = do do 5;
|
| is($ret, 5, 'nested do (1)');
|
|
|
| $ret = do {do 5};
|
| is($ret, 5, 'nested do (2)');
|
|
|
| # precedence decisions do not cross a do boundary
|
| $ret = 2 * do 2 + 5;
|
| is($ret, 14, 'do affects precedence correctly');
|
| }
|
|
|
@primesquares = ($_ if prime($_) for 1..100) »**» 2;
This basically gives us list comprehensions as rvalue expressions:
(for 1..100 { $_ if prime($_)}).say
Another consequence of this is that any block just inside a left parenthesis is immediately called like a bare block, so a multidimensional list comprehension may be written using a block with multiple parameters fed by a for modifier:
@names = (-> $name, $num { "$name.$num" } for 'a'..'zzz' X 1..100);
or equivalently, using placeholders:
@names = ({ "$^name.$^num" } for 'a'..'zzz' X 1..100);
Since do is defined as going in front of a statement, it follows that it can always be followed by a statement label. This is particularly useful for the do-once block, since it is officially a loop and can take therefore loop control statements.
From t/spec/S04-statements/do.t lines 83–123: (skip)
-
| # L<S04/The do-once loop/"can take" "loop control statements">
|
| #?rakudo skip 'next() should also work on do blocks (?)'
|
| {
|
| my $i;
|
| do {
|
| $i++;
|
| next;
|
| $i--;
|
| };
|
| is $i, 1, "'next' works in 'do' block";
|
| }
|
|
|
| #?rakudo skip 'last not implemented'
|
| {
|
| is eval('
|
| my $i;
|
| do {
|
| $i++;
|
| last;
|
| $i--;
|
| };
|
| $i;
|
| '), 1, "'last' works in 'do' block";
|
| }
|
|
|
| # IRC notes:
|
| # <agentzh> audreyt: btw, can i use redo in the do-once loop?
|
| # <audreyt> it can, and it will redo it
|
| #?rakudo skip 'redo not implemented'
|
| {
|
| is eval('
|
| my $i;
|
| do {
|
| $i++;
|
| redo if $i < 3;
|
| $i--;
|
| };
|
| $i;
|
| '), 2, "'redo' works in 'do' block";
|
| }
|
|
|
Although a bare block occurring as a single statement is no longer a do-once loop, it still executes immediately as in Perl 5, as if it were immediately dereferenced with a .() postfix, so within such a block CALLER:: refers to the dynamic scope associated with the lexical scope surrounding the block.
If you wish to return a closure from a function, you must use an explicit prefix such as return or sub or ->.
sub f1
{
# lots of stuff ...
{ say "I'm a closure." }
}
my $x1= f1; # fall-off return is result of the say, not the closure.
sub f2
{
# lots of stuff ...
return { say "I'm a closure." }
}
my $x2= f2; # returns a Block object.
Use of a placeholder parameter in statement-level blocks triggers a syntax error, because the parameter is not out front where it can be seen. However, it's not an error when prefixed by a do, or when followed by a statement modifier:
# Syntax error: Statement-level placeholder block
{ say $^x };
# Not a syntax error, though $x doesn't get the argument it wants
do { say $^x };
# Not an error: Equivalent to "for 1..10 -> $x { say $x }"
{ say $^x } for 1..10;
# Not an error: Equivalent to "if foo() -> $x { say $x }"
{ say $^x } if foo();
From t/spec/S04-statements/gather.t lines 8–156: (skip)
-
| # L<S04/The C<gather> statement prefix/>
|
|
|
| # Standard gather
|
| {
|
| my @a;
|
| my $i;
|
|
|
| @a := gather {
|
| $i = 1;
|
| for (1 .. 5) -> $j {
|
| take $j;
|
| }
|
| };
|
|
|
| ok(!$i, "not yet gathered");
|
| is(+@a, 5, "5 elements gathered");
|
| ok($i, "gather code executed");
|
| is(@a[0], 1, "first elem taken");
|
| is(@a[*-1], 5, "last elem taken");
|
| };
|
|
|
| # Nested gathers, two levels
|
| {
|
| my @outer = gather {
|
| for 1..3 -> $i {
|
| my @inner = gather {
|
| take $_ for 1..3;
|
| };
|
|
|
| take "$i:" ~ @inner.join(',');
|
| }
|
| };
|
|
|
| is ~@outer, "1:1,2,3 2:1,2,3 3:1,2,3", "nested gather works (two levels)";
|
| }
|
|
|
| # Nested gathers, three levels
|
| {
|
| my @outer = gather {
|
| for 1..2 -> $i {
|
| my @inner = gather {
|
| for 1..2 -> $j {
|
| my @inner_inner = gather {
|
| take $_ for 1..2;
|
| };
|
| take "$j:" ~ @inner_inner.join(',');
|
| }
|
| };
|
| take "$i:" ~ @inner.join(';');
|
| }
|
| };
|
|
|
| is ~@outer, "1:1:1,2;2:1,2 2:1:1,2;2:1,2", "nested gather works (three levels)";
|
| }
|
|
|
| # take on lists, multiple takes per loop
|
| {
|
| my @outer = gather {
|
| my @l = (1, 2, 3);
|
| take 5;
|
| take @l;
|
| take 5;
|
| };
|
|
|
| is ~@outer, "5 1 2 3 5", "take on lists and multiple takes work";
|
| }
|
|
|
| # gather scopes dynamiclly, not lexically
|
| {
|
| my $dynamic_take = sub { take 7 };
|
| my @outer = gather {
|
| $dynamic_take();
|
| take 1;
|
| };
|
|
|
| is ~@outer, "7 1", "gather scopes dynamically, not lexically";
|
| }
|
|
|
| # take on array-ref
|
| {
|
| my @list = gather { take [1,2,3]; take [4,5,6];};
|
| my @list2 = ([1,2,3],[4,5,6]);
|
| is @list.perl, @list2.perl , "gather array-refs";
|
| }
|
|
|
| # gather statement prefix
|
| {
|
| my @out = gather for 1..5 {
|
| take $_;
|
| };
|
|
|
| is ~@out, "1 2 3 4 5", "gather as a statement_prefix";
|
| }
|
|
|
| # lazy gather
|
| {
|
| my $count = 0;
|
| my @list := gather {
|
| for 1 .. 10 -> $a {
|
| take $a;
|
| $count++
|
| }
|
| };
|
| my $result = @list[2];
|
| is($count, 2, "gather is lazy");
|
| }
|
|
|
| {
|
| my @list = gather {
|
| my $v = 1;
|
| while $v <= 10 {
|
| take $v if $v % 2 == 0;
|
| $v++;
|
| }
|
| };
|
| is ~@list, "2 4 6 8 10", "gather with nested while";
|
| }
|
|
|
| {
|
| my @list = gather {
|
| loop (my $v = 1; $v <= 10; $v++)
|
| {
|
| take $v if $v % 2 == 0;
|
| }
|
| };
|
| is ~@list, "2 4 6 8 10", "gather with nested loop";
|
| }
|
|
|
| {
|
| is (gather { take 1, 2, 3; take 4, 5, 6; }).elems, 2,
|
| 'take with multiple arguments produces one item each';
|
|
|
| is (gather { take 1, 2, 3; take 4, 5, 6; }).flat.elems, 6,
|
| 'take with multiple arguments .flat tens out';
|
| }
|
|
|
| {
|
| my sub grep-div(@a, $n) {
|
| gather for @a {
|
| take $_ if $_ %% $n;
|
| }
|
| }
|
|
|
| my @evens := grep-div((1...*), 2);
|
| is ~grep-div(@evens, 3).munch(16), ~grep-div((1...100), 6), "Nested identical gathers";
|
| }
|
|
|
|
|
| # vim: ft=perl6
|
A variant of do is gather. Like do, it is followed by a statement or block, and executes it once. Unlike do, it evaluates the statement or block in sink (void) context; its return value is instead specified by calling the take list prefix operator one or more times within the dynamic scope of the gather. The take function's signature is like that of return; while having the syntax of a list operator, it merely returns a single item or "argument" (see S02 for definition).
If you take multiple items in a comma list (since it is, after all, a list operator), they will be wrapped up in a Parcel object for return as the next argument. No additional context is applied by the take operator, since all context is lazy in Perl 6. The flattening or slicing of any such returned parcel will be dependent on how the gather's return iterator is iterated (with .get vs .getarg).
The value returned by the take to the take's own context is that same returned argument (which is ignored when the take is in sink context). Regardless of the take's immediate context, the object returned is also added to the list of values being gathered, which is returned by the gather as a lazy list (that is, an iterator, really), with each argument element of that list corresponding to one take.
Any parcels in the returned list are normally flattened when bound into flat context. When bound into a slice context, however, the parcel objects become real Seq objects that keep their identity as discrete sublists. The eventual binding context thus determines whether to throw away or keep the groupings resulting from each individual take call. Most list contexts are flat rather than sliced, so the boundaries between individual take calls usually disappear. (FLAT is an acronym meaning Flat Lists Are Typical. :)
Because gather evaluates its block or statement in sink context, this typically causes the take function to be evaluated in sink context. However, a take function that is not in sink context gathers its return objects en passant and also returns them unchanged. This makes it easy to keep track of what you last "took":
my @uniq = gather for @list {
state $previous = take $_;
next if $_ === $previous;
$previous = take $_;
}
The take function essentially has two contexts simultaneously, the context in which the gather is operating, and the context in which the take is operating. These need not be identical contexts, since they may bind or coerce the resulting parcels differently:
my @y;
@x = gather for 1..2 { # flat context for list of parcels
my ($y) := take $_, $_ * 10; # item context promotes parcel to seq
push @y, $y;
}
# @x contains 4 Ints: 1,10,2,20 flattened by list assignment to @x
# @y contains 2 Seqs: Seq(1,10),Seq(2,20) sliced by binding to positional $y
Likewise, we can just remember the gather's result parcel by binding and later coercing it:
my |$c := gather for 1..2 {
take $_, $_ * 10;
}
# $c.flat produces 1,10,2,20 -- flatten fully into a list of Ints.
# $c.slice produces Seq(1,10),Seq(2,20) -- list of Seqs, a 2-D list.
# $c.item produces Seq((1,10),(2,20)) -- coerced to Seq of unresolved Parcels
Note that the take itself is in sink context in this example because the for loop is in the sink context provided inside the gather.
A gather is not considered a loop, but it is easy to combine with a loop statement as in the examples above.
The take operation may be defined internally using resumable control exceptions, or dynamic variables, or pigeons carrying clay tablets. The choice any particular implementation makes is specifically not part of the definition of Perl 6, and you should not rely on it in portable code.
From t/spec/S04-statements/lift.t lines 6–79: (skip)
-
| # L<S04/The C<lift> statement prefix>
|
|
|
| # lift normal multi subs
|
|
|
| {
|
| # the multi being lifted
|
| multi sub mt(Any $x) { 'Any' } #OK not used
|
|
|
| multi sub lt1() { lift mt('String') }
|
| multi sub lt2() { lift mt(['Array']) }
|
|
|
| is lt1(), 'Any', 'lift basic sanity (String)';
|
| is lt2(), 'Any', 'lift basic sanity (Array)';
|
|
|
| # introduce a scope with another lexical multi
|
| {
|
| my multi sub mt(Str $x) { 'Str' } #OK not used
|
| is lt1(), 'Str', "lift picked up multis from caller's scope (Str)";
|
| is lt2(), 'Any', "lift still considers outer multis";
|
| }
|
| }
|
|
|
| # lift operators
|
| {
|
| proto prefix:<``> (Any $x) { die "no multi" };
|
| multi sub lt3() { lift ``'String' };
|
| multi sub lt4() { lift ``4 };
|
|
|
| {
|
| my multi sub prefix:<``>(Str $x) { 'Str ``' }; #OK not used
|
| my multi sub prefix:<``>(Int $x) { 'Int ``' }; #OK not used
|
| is lt3(), 'Str ``',
|
| "lifted operator picked up multi from caller's scope (Str)";
|
| is lt4(), 'Int ``',
|
| "lifted operator picked up multi from caller's scope (Int)";
|
| }
|
| eval_dies_ok '``"foo"', "Dies when no callable multi is in scope";
|
| }
|
|
|
| # lift with user defined infix and prefix operators
|
|
|
| {
|
| proto infix:<ceq>(Any $a, Any $b) is equiv(&infix:<eq>) {
|
| lift ~$a eq ~$b
|
| }
|
| multi infix:<ceq>(Str $a, Str $b) {$a eq $b}
|
|
|
| {
|
| my multi infix:<eq>(Str $a, Str $b) {
|
| $a.elems == $b.elems;
|
| }
|
| ok 'a' ceq 'b', 'infix:<ceq> picked up lifted infix:<eq> (+)';
|
| ok !('a' ceq 'aa'), 'infix:<ceq> picked up lifted infix:<eq> (-)';
|
| }
|
|
|
| {
|
| my multi sub prefix:<~>(Int $x where 0..4) {
|
| my @conf = <A B C D E>;
|
| @conf($x);
|
| }
|
| ok 'A' ceq 1, 'infix:<ceq> picked up lifted prefix:<~> (+)';
|
| ok !('A' ceq 2), 'infix:<ceq> picked up lifted prefix:<~> (-)';
|
| }
|
|
|
| # default operations: no user defined ~ and eq or ceq
|
| ok 'a' ceq 'a', 'basic operation (+)';
|
| ok !('a' ceq 'b'), 'basic operation (-)';
|
| # with coercion
|
| ok '1' ceq 1, 'basic operation with coercion (+)';
|
| ok !('1' ceq 2), 'basic operation with coercion (-)';
|
| }
|
|
|
| {
|
| # I hope I understood this part of specs correctly:
|
When writing generic multi routines you often want to write a bit of code whose meaning is dependent on the linguistic context of the caller. It's somewhat like virtual methods where the actual call depends on the type of the invocant, but here the "invocant" is really the lexical scope of the caller, and the virtual calls are name bindings. Within a lift, special rules apply to how names are looked up. Only names defined in the lexical scope of the immediately surrounding routine are considered concrete. All other names (including implicit names of operators) are looked up in the lexical scope of the caller when we actually know who the caller is at run time. (Note the caller can vary from call to call!)
This applies to anything that needs to be looked up at compile time, including names of variables, and named values such as types and subs.
Through this mechanism, a generic multi can redirect execution to a more specific version, but the candidate list for this redirection is determined by the caller, not by the lexical scope of the multi, which can't see the caller's lexical scope except through the CALLER:: pseudo package. For example, Perl forces generic eq to coerce to string comparison, like this:
proto infix:<eq> (Any $a, Any $b) { lift ~$a eq ~$b } # user's eq, user's ~
multi infix:<eq> (Whatever, Any $b) { -> $a { lift $a eq $b } } # user's eq
multi infix:<eq> (Any $a, Whatever) { -> $b { lift $a eq $b } } # user's eq
multi infix:<eq> (&f:($), Any $b) { -> $a { lift f($a) eq $b } } # user's eq
multi infix:<eq> (Str $a, Str $b) { !Str::leg($a, $b) } # primitive leg, primitive !
Note that in each piece of lifted code there are references to variables defined in the multi, such as $a, $b, and &f. These are taken at face value. Everything else within a lift is assumed to mean something in the caller's linguistic context. (This implies that there are some errors that would ordinarily be found at compile time that cannot be found until we know what the caller's lexical scope looks like at run time. That's okay.)
From t/spec/S04-statements/lift.t lines 80–93: (skip)
-
| # L<S04/The C<lift> statement prefix/"Everything else within a lift">
|
| # etc.
|
| # IMHO that means that it's OK to use undeclared variables in a lift:
|
| sub f { lift $a + $b };
|
| {
|
| my $a is context = 3;
|
| my $b is context = 4;
|
| is f(), 7, 'Can pick up context variables from the caller';
|
| }
|
| eval_dies_ok 'f()',
|
| 'It is an error if the lifted variables are not declared in the caller';
|
| }
|
|
|
| # vim: ft=perl6
|
Other similar forms, where a keyword is followed by code to be controlled by it, may also take bare statements, including try, quietly, contend, async, lazy, and sink. These constructs establish a dynamic scope without necessarily establishing a lexical scope. (You can always establish a lexical scope explicitly by using the block form of argument.) As statement introducers, all these keywords must be followed by whitespace. (You can say something like try({...}), but then you are calling the try() function using function call syntax instead, and since Perl does not supply such a function, it will be assumed to be a user-defined function.) For purposes of flow control, none of these forms are considered loops, but they may easily be applied to a normal loop.
From t/spec/S04-statements/lazy.t lines 5–62: (skip)
-
| # L<S04/Other C<do>-like forms/lazy>
|
|
|
| plan 15;
|
|
|
| {
|
| my $was_in_lazy;
|
|
|
| my $var = lazy { $was_in_lazy++; 42 };
|
|
|
| ok !$was_in_lazy, 'our lazy block wasn\'t yet executed (1)';
|
|
|
| is $var, 42, 'our lazy var has the correct value';
|
| ok $was_in_lazy, 'our lazy block was executed';
|
|
|
| is $var, 42, 'our lazy var still has the correct value';
|
| is $was_in_lazy, 1, 'our lazy block was not executed again';
|
| }
|
|
|
| # Same, but passing the lazy value around before accessing it:
|
| {
|
| my $was_in_lazy;
|
|
|
| my $var = lazy { $was_in_lazy++; 42 };
|
| my $sub = -> Num $v, Bool $access { $access and +$v };
|
|
|
| ok !$was_in_lazy, 'our lazy block wasn\'t yet executed (2)';
|
| $sub($var, 0);
|
| ok !$was_in_lazy, 'our lazy block has still not been executed';
|
| $sub($var, 1);
|
| ok $was_in_lazy, 'our lazy block has been executed now';
|
| }
|
|
|
| # dies_ok/lives_ok tests:
|
| {
|
| my $was_in_lazy;
|
| my $lazy = lazy { $was_in_lazy++; 42 };
|
| lives_ok { $lazy = 23 }, "reassigning our var containing a lazy worked (1)";
|
| is $lazy, 23, "reassigning our var containing a lazy worked (2)";
|
| ok !$was_in_lazy, "reassigning our var containing a lazy worked (3)";
|
| }
|
|
|
| {
|
| my $was_in_lazy;
|
| my $lazy = lazy { $was_in_lazy++; 42 };
|
| lives_ok { $lazy := 23 }, "rebinding our var containing a lazy worked (1)";
|
| is $lazy, 23, "rebinding our var containing a lazy worked (2)";
|
| ok !$was_in_lazy, "rebinding our var containing a lazy worked (3)";
|
| }
|
|
|
| {
|
| dies_ok { (lazy { 42 }) = 23 },
|
| "directly assigning to a lazy var does not work";
|
| }
|
|
|
| # Arguably, we should remove the $was_in_lazy tests, as it doesn't really
|
| # matter when the lazy {...} block is actually executed.
|
|
|
| # vim: ft=perl6
|
Note that any construct in the statement_prefix category defines special syntax. If followed by a block it does not parse as a list operator or even as a prefix unary; it will never look for any additional expression following the block. In particular,
foo( try {...}, 2, 3 )
calls the foo function with three arguments. And
do {...} + 1
add 1 to the result of the do block. On the other hand, if a statement_prefix is followed by a non-block statement, all nested blockless statement_prefixes will terminate at the same statement ending:
do do do foo(); bar 43;
is parsed as:
do { do { do { foo(); }}}; bar(43);
A switch statement is a means of topicalizing, so the switch keyword is the English topicalizer, given. The keyword for individual cases is when:
given EXPR {
when EXPR { ... }
when EXPR { ... }
default { ... }
}
The current topic is always aliased to the special variable $_. The given block is just one way to set the current topic, but a switch statement can be any block that sets $_, including a for loop (assuming one of its loop variables is bound to $_) or the body of a method (if you have declared the invocant as $_). So switching behavior is actually caused by the when statements in the block, not by the nature of the block itself. A when statement implicitly does a "smart match" between the current topic ($_) and the argument of the when. If the smart match succeeds, when's associated block is executed, and the innermost surrounding block that has $_ as one of its formal parameters (either explicit or implicit) is automatically broken out of. (If that is not the block you wish to leave, you must use the LABEL.leave method (or some other control exception such as return or next) to be more specific, since the compiler may find it difficult to guess which surrounding construct was intended as the actual topicalizer.) The value of the inner block is returned as the value of the outer block.
If the smart match fails, control proceeds the next statement normally, which may or may not be a when statement. Since when statements are presumed to be executed in order like normal statements, it's not required that all the statements in a switch block be when statements (though it helps the optimizer to have a sequence of contiguous when statements, because then it can arrange to jump directly to the first appropriate test that might possibly match.)
From t/spec/S04-statements/given.t lines 26–236: (skip)
-
| # L<S04/Switch statements/If the smart match fails, control proceeds the
|
| # next statement>
|
| my ($two, $five, $int, $unreached);
|
|
|
| given 5 {
|
| when 2 { $two = 1 }
|
| when 5 { $five = 1; proceed }
|
| when Int { $int = 1 }
|
| when 5 { $unreached = 1 }
|
| }
|
|
|
| ok(!$two, "5 is not two");
|
| ok($five, "5 is five");
|
| ok($int, "short fell-through to next true when using 'proceed'");
|
| ok(!$unreached, "but didn't do so normally");
|
| };
|
|
|
| {
|
| my $foo;
|
| given "foo" {
|
| when "foo" {
|
| when /^f/ {
|
| $foo = 1
|
| }
|
| }
|
| }
|
|
|
| ok($foo, "foo was found in nested when");
|
| };
|
|
|
|
|
| # from apocalypse 4
|
| #?rakudo skip 'parsefail on each(... ; ...)'
|
| {
|
| # simple example L<S04/"Switch statements" /You don't have to use an explicit default/>
|
| for each(("T", "E", 5) ; (10, 11, 5)) -> $digit, $expected {
|
| my $result_a = do given $digit {
|
| when "T" { 10 }
|
| when "E" { 11 }
|
| $digit
|
| };
|
|
|
| my $result_b = do given $digit {
|
| when "T" { 10 }
|
| when "E" { 11 }
|
| default { $digit }
|
| };
|
|
|
| is($result_a, $expected, "result of $digit using implicit default {} is $expected");
|
| is($result_b, $expected, "result of $digit using explicit default {} is $expected");
|
| }
|
| }
|
|
|
| {
|
| # interleaved code L<S04/"Switch statements" /which may or may not be a when statement/>
|
| my ($b_one, $b_two, $b_three, $panic);
|
| given 2 {
|
| $b_one = 1;
|
| when 1 { }
|
| $b_two = 1;
|
| when 2 { }
|
| $b_three = 1;
|
| default { }
|
| $panic = 1;
|
| }
|
|
|
| ok($b_one, "interleaved 1");
|
| ok($b_two, "interleaved 2 is the last one");
|
| ok(!$b_three, "inteleraved 3 not executed");
|
| ok(!$panic, 'never ever execute something after a default {}');
|
| };
|
|
|
| {
|
| # topic not given by 'given' L<S04/"Switch statements" /including a for loop/>
|
| my ($b_one, $b_two, $b_three,$panic) = (0,0,0,0);
|
| for <1 2 3> {
|
| when 1 {$b_one = 1}
|
| when 2 {$b_two = 1}
|
| when 3 {$b_three = 1}
|
| default {$panic =1}
|
| }
|
| ok($b_one, "first iteration");
|
| ok($b_two, "second iteration");
|
| ok($b_three, "third iteration");
|
| ok(!$panic,"should not fall into default in this case");
|
| }
|
|
|
| #?rakudo skip "Cannot assign to $_ in Rakudo"
|
| {
|
| my $foo = 1;
|
| given 1 {
|
| $_ = 2;
|
| when 2 { $foo = 2; }
|
| when 1 { $foo = 3; }
|
| default { $foo = 4; }
|
| }
|
| is($foo, 2, 'Assign new value to $_ inside topicalizer');
|
| }
|
|
|
| {
|
| my ($foo, $bar) = (1, 0);
|
| given 1 {
|
| when 1 { $foo = 2; proceed; $foo = 3; }
|
| when 2 { $foo = 4; }
|
| default { $bar = 1; }
|
| $foo = 5;
|
| };
|
| is($foo, 2, 'proceed aborts when block');
|
| ok($bar, 'proceed does not prevent default');
|
| }
|
|
|
| {
|
| my ($foo, $bar) = (1, 0);
|
| given 1 {
|
| when 1 { $foo = 2; succeed; $foo = 3; }
|
| when 2 { $foo = 4; }
|
| default { $bar = 1 }
|
| $foo = 5;
|
| };
|
| is($foo, 2, 'succeed aborts when');
|
| ok(!$bar, 'succeed prevents default');
|
| }
|
|
|
| {
|
| my ($foo, $bar, $baz, $bad) = (0, 0, -1, 0);
|
| my $quux = 0;
|
| for 0, 1, 2 {
|
| when 0 { $foo++; proceed }
|
| when 1 { $bar++; succeed }
|
| when 2 { $quux++; }
|
| default { $baz = $_ }
|
| $bad = 1;
|
| };
|
| is($foo, 1, 'first iteration');
|
| is($bar, 1, 'second iteration');
|
| is($baz, 0, 'proceed worked');
|
| is($quux, 1, "succeed didn't abort loop");
|
| ok(!$bad, "default didn't fall through");
|
| }
|
|
|
|
|
| # given returns the correct value:
|
| {
|
| sub ret_test($arg) {
|
| given $arg {
|
| when "a" { "A" }
|
| when "b" { "B" }
|
| }
|
| }
|
|
|
| is( ret_test("a"), "A", "given returns the correct value (1)" );
|
| is( ret_test("b"), "B", "given returns the correct value (2)" );
|
| }
|
|
|
| # given/succeed returns the correct value:
|
| {
|
| sub ret_test($arg) {
|
| given $arg {
|
| when "a" { succeed "A"; 'X'; }
|
| when "b" { succeed "B"; 'X'; }
|
| }
|
| }
|
|
|
| is( ret_test("a"), "A", "given returns the correct value (1)" );
|
| is( ret_test("b"), "B", "given returns the correct value (2)" );
|
| }
|
|
|
| # given/when and junctions
|
| {
|
| my $any = 0;
|
| my $all = 0;
|
| my $one = 0;
|
| given 1 {
|
| when any(1 .. 3) { $any = 1; }
|
| }
|
| given 1 {
|
| when all(1) { $all = 1; }
|
| }
|
| given 1 {
|
| when one(1) { $one = 1; }
|
| }
|
| is($any, 1, 'when any');
|
| is($all, 1, 'when all');
|
| is($one, 1, 'when one');
|
| }
|
|
|
| # given + objects
|
| {
|
| class TestIt { method passit { 1; }; has %.testing is rw; };
|
| my $passed = 0;
|
| ok( eval('given TestIt.new { $_.passit; };'), '$_. method calls' );
|
| ok( eval('given TestIt.new { .passit; };'), '. method calls' );
|
| ok( eval('given TestIt.new { $_.testing<a> = 1; };'),'$_. attribute access' );
|
| ok( eval('given TestIt.new { .testing<a> = 1; };'), '. attribute access' );
|
| my $t = TestIt.new;
|
| given $t { when TestIt { $passed = 1;} };
|
| is($passed, 1,'when Type {}');
|
| #?rakudo skip ".isa(TestIt) goes kaboom"
|
| {
|
| $passed = 0;
|
| given $t { when .isa(TestIt) { $passed = 1;}};
|
| is($passed, 1,'when .isa(Type) {}');
|
| }
|
| $passed = 0;
|
| given $t { when TestIt { $passed = 1; }};
|
| is($passed, 1,'when Type {}');
|
| }
|
|
|
| #?rakudo skip '.so NYI'
|
| {
|
| # given + true
|
The default case:
default {...}
is exactly equivalent to
From t/spec/S04-statements/given.t lines 237–311: (skip)
-
| # L<S04/"Switch statements" /"is exactly equivalent to">
|
| my @input = (0, 1);
|
| my @got;
|
|
|
| for @input -> $x {
|
| given $x {
|
| when .so { push @got, "true" }
|
| default { push @got, "false" }
|
| }
|
| }
|
|
|
| #?rakudo 1 todo '.so in given does not work'
|
| is(@got.join(","), "false,true", 'given { when .so { } }');
|
| }
|
|
|
| # given + hash deref
|
| {
|
| my %h;
|
| given %h { .{'key'} = 'value'; }
|
| ok(%h{'key'} eq 'value', 'given and hash deref using .{}');
|
| given %h { .<key> = "value"; }
|
| ok(%h{'key'} eq 'value', 'given and hash deref using .<>');
|
| }
|
|
|
| # given + 0-arg closure
|
| {
|
| my $x = 0;
|
| given 41 {
|
| when { $_ == 49 } { diag "this really shouldn't happen"; $x = 49 }
|
| when { $_ == 41 } { $x++ }
|
| }
|
| ok $x, 'given tests 0-arg closures for truth';
|
| }
|
|
|
| # given + 1-arg closure
|
| {
|
| my $x;
|
| given 41 {
|
| when -> $t { $t == 49 } { diag "this really shouldn't happen"; $x = 49 }
|
| when -> $t { $t == 41 } { $x++ }
|
| }
|
| ok $x, 'given tests 1-arg closures for truth';
|
| }
|
|
|
| # given + n>1-arg closure (should fail)
|
| {
|
| dies_ok {
|
| given 41 {
|
| when -> $t, $r { $t == $r } { ... }
|
| }
|
| }, 'fail on arities > 1';
|
| }
|
|
|
| # given + 0-arg sub
|
| {
|
| my $x = 41;
|
| sub always_true { Bool::True }
|
| given 1 {
|
| when &always_true { $x++ }
|
| }
|
| is $x, 42, 'given tests 0-arg subs for truth';
|
| }
|
|
|
| # given + 1-arg sub
|
| {
|
| my $x = 41;
|
| sub maybe_true ($value) { $value eq "mytopic" }
|
| given "mytopic" {
|
| when &maybe_true { $x++ }
|
| }
|
| is $x, 42, 'given tests 1-arg subs for truth';
|
| }
|
|
|
|
|
| # vim: ft=perl6
|
when * {...}
Because when statements are executed in order, the default must come last. You don't have to use an explicit default--you can just fall off the last when into ordinary code. But use of a default block is good documentation.
If you use a for loop with a parameter named $_ (either explicitly or implicitly), that parameter can function as the topic of any when statements within the loop.
You can explicitly break out of a when block (and its surrounding topicalizer block) early using the succeed verb. More precisely, it first scans outward (lexically) for the innermost containing when block. From there it continues to scan outward to find the innermost block outside the when that uses $_ as one of its formal parameters, either explicitly or implicitly. (Note that both of these scans are done at compile time; if the scans fail, it's a compile-time semantic error.) Typically, such an outer block will be the block of a given or a for statement, but any block that sets the topic in its signature can be broken out of. At run time, succeed uses a control exception to scan up the dynamic chain to find the call frame belonging to that same outer block, and when it has found that frame, it does a .leave on it to unwind the call frames. If any arguments are supplied to the succeed function, they are passed out via the leave method. Since leaving a block is considered a successful return, breaking out of one with succeed is also considered a successful return for the purposes of KEEP and UNDO.
The implicit break of a normal when block works the same way, returning the value of the entire block (normally from its last statement) via an implicit succeed.
You can explicitly leave a when block and go to the next statement following the when by using proceed. (Note that, unlike C's idea of "falling through", subsequent when conditions are evaluated. To jump into the next when block without testing its condition, you must use a goto. But generally that means you should refactor instead.)
If you have a switch that is the main block of a for loop, and you break out of the switch either implicitly or explicitly (that is, the switch "succeeds"), control merely goes to the end of that block, and thence on to the next iteration of the loop. You must use last (or some more violent control exception such as return) to break out of the entire loop early. Of course, an explicit next might be clearer than a break if you really want to go directly to the next iteration. On the other hand, succeed can take an optional argument giving the value for that iteration of the loop. As with the .leave method, there is also a .succeed method to break from a labelled block functioning as a switch:
OUTER.succeed($retval)
There is a when statement modifier, but it does not have any breakout semantics; it is merely a smartmatch against the current topic. That is,
doit() when 42;
is exactly equivalent to
doit() if $_ ~~ 42;
This is particularly useful for list comprehensions:
@lucky = ($_ when /7/ for 1..100);
Unlike many other languages, Perl 6 specifies exception handlers by placing a CATCH block within that block that is having its exceptions handled.
The Perl 6 equivalent to Perl 5's eval {...} is try {...}. (Perl 6's eval function only evaluates strings, not blocks.) A try block by default has a CATCH block that handles all fatal exceptions by ignoring them. If you define a CATCH block within the try, it replaces the default CATCH. It also makes the try keyword redundant, because any block can function as a try block if you put a CATCH block within it.
From t/spec/S29-context/eval.t lines 35–71: (skip)
-
| # L<S04/Exception handlers/Perl 6's eval function only evaluates strings, not blocks.>
|
| dies_ok({eval {42}}, 'block eval is gone');
|
|
|
| # RT #63978, eval didn't work in methods
|
| {
|
| class EvalTester1 {
|
| method e($s) { eval $s };
|
| }
|
| is EvalTester1.e('5'), 5, 'eval works inside class methods';
|
| is EvalTester1.new.e('5'), 5, 'eval works inside instance methods';
|
| }
|
|
|
| {
|
| my $x = 5;
|
| class EvalTester2 {
|
| method e($s) { eval "$s + \$x" };
|
| }
|
| is EvalTester2.e('1'), 6,
|
| 'eval works inside class methods, with outer lexicals';
|
| is EvalTester2.new.e('1'), 6,
|
| 'eval works inside instance methods, with outer lexicals';
|
| }
|
|
|
| #?rakudo skip 'eval(Buf)'
|
| is eval("'møp'".encode('UTF-8')), 'møp', 'eval(Buf)';
|
|
|
| {
|
| #?rakudo skip 'eval coerce to string'
|
| is eval 88, 88, 'eval of non-string works';
|
|
|
| my $number = 2;
|
| #?rakudo skip 'eval coerce to string'
|
| is eval $number, $number, 'eval of non-string variable works';
|
| }
|
|
|
|
|
| # vim: ft=perl6
|
From t/spec/S04-exception-handlers/catch.t lines 15–22: (skip)
-
| # L<S04/"Exception handlers"/If you define a CATCH block within the try, it replaces the default CATCH>
|
|
|
| #?rakudo 2 todo 'empty CATCH block'
|
| dies_ok { die 'blah'; CATCH {} }, 'Empty CATCH rethrows exception';
|
| dies_ok { try {die 'blah'; CATCH {}} }, 'CATCH in try overrides default exception handling';
|
|
|
|
|
|
|
From t/spec/S04-exception-handlers/catch.t lines 23–46: (skip)
-
| # L<S04/"Exception handlers"/any block can function as a try block if you put a CATCH block within it>
|
|
|
| lives_ok { die 'blah'; CATCH {default {}} }, 'Closure with CATCH {default {}} ignores exceptions';
|
| lives_ok { do {die 'blah'; CATCH {default {}}}; }, 'do block with CATCH {default {}} ignores exceptions';
|
|
|
| {
|
| my $f = sub { die 'blah'; CATCH {default {}} };
|
| lives_ok $f, 'Subroutine with CATCH {default {}} ignores exceptions';
|
|
|
| $f = sub ($x) {
|
| if $x {
|
| die 'blah';
|
| CATCH { default {} }
|
| }
|
| else {
|
| die 'blah';
|
| }
|
| };
|
| lives_ok { $f(1) }, 'if block with CATCH {default {}} ignores exceptions...';
|
| dies_ok { $f(0) }, "...but the CATCH doesn't affect exceptions thrown in an attached else";
|
| }
|
|
|
|
|
|
|
An exception handler is just a switch statement on an implicit topic supplied within the CATCH block. That implicit topic is the current exception object, also known as $!. Inside the CATCH block, it's also bound to $_, since it's the topic. Because of smart matching, ordinary when statements are sufficiently powerful to pattern match the current exception against classes or patterns or numbers without any special syntax for exception handlers. If none of the cases in the CATCH handles the exception, the exception is rethrown. To ignore all unhandled exceptions, use an empty default case. (In other words, there is an implicit die $! just inside the end of the CATCH block. Handled exceptions break out past this implicit rethrow.) Hence, CATCH is unlike all other switch statements in that it treats code inside a default block differently from code that's after all the when blocks but not in a default block.
From t/spec/S04-exception-handlers/catch.t lines 47–159: (skip)
-
| #L<S04/"Exception handlers"/An exception handler is just a switch statement>
|
|
|
| #unless eval 'Exception.new' {
|
| # skip_rest "No Exception objects"; exit;
|
| #}
|
|
|
| {
|
| # exception classes
|
| class Naughty is Exception {};
|
|
|
| my ($not_died, $caught);
|
| {
|
| die Naughty("error");
|
|
|
| $not_died = 1;
|
|
|
| CATCH {
|
| when Naughty {
|
| $caught = 1;
|
| }
|
| }
|
| };
|
|
|
| ok(!$not_died, "did not live after death");
|
| #?pugs 1 todo
|
| #?rakudo todo 'smart matching against exception'
|
| ok($caught, "caught exception of class Naughty");
|
| };
|
|
|
| {
|
| # exception superclass
|
| class Naughty::Specific is Naughty {};
|
| class Naughty::Other is Naughty {};
|
|
|
| my ($other, $naughty);
|
| {
|
| die Naughty::Specific("error");
|
|
|
| CATCH {
|
| when Naughty::Other {
|
| $other = 1;
|
| }
|
| when Naughty {
|
| $naughty = 1;
|
| }
|
| }
|
| };
|
|
|
| ok(!$other, "did not catch sibling error class");
|
| #?pugs 1 todo
|
| #?rakudo todo 'smart matching against exception'
|
| ok($naughty, "caught superclass");
|
| };
|
|
|
| {
|
| # uncaught class
|
| class Dandy is Exception {};
|
|
|
| my ($naughty, $lived);
|
| eval '
|
| {
|
| die Dandy("error");
|
|
|
| CATCH {
|
| when Naughty {
|
| $naughty = 1;
|
| }
|
| }
|
| };
|
| $lived = 1;
|
| ';
|
|
|
| #?rakudo todo 'CATCH'
|
| ok(!$lived, "did not live past uncaught throw");
|
| ok(!$naughty, "did not get caught by wrong handler");
|
| ok(~WHAT($!), '$! is an object');
|
| #?pugs skip 'bug'
|
| #?rakudo todo 'Exception types'
|
| is(WHAT($!), Dandy, ".. of the right class");
|
| };
|
|
|
| #?rakudo skip 'llops'
|
| {
|
| my $s = '';
|
| die 3;
|
| CATCH {
|
| when 1 {$s ~= 'a';}
|
| when 2 {$s ~= 'b';}
|
| when 3 {$s ~= 'c';}
|
| when 4 {$s ~= 'd';}
|
| default {$s ~= 'z';}
|
| }
|
|
|
| is $s, 'c', 'Caught number';
|
| };
|
|
|
| {
|
| my $catches = 0;
|
| sub rt63430 {
|
| {
|
| return 63430;
|
| CATCH { return 73313 if ! $catches++; }
|
| }
|
| }
|
|
|
| is rt63430().perl, 63430.perl, 'can call rt63430() and examine the result';
|
| is rt63430(), 63430, 'CATCH does not intercept return from bare block';
|
| #?rakudo skip 'invalid since other calls were skipped (noauto)'
|
| is $catches, 0, 'CATCH block never invoked';
|
| };
|
|
|
|
|
|
|
More specifically, when you write:
CATCH {
when Mumble {...}
default {...}
}
you're really calling into a catch lambda that looks like:
-> $! {
my $SUCCEEDED = 1; # assume we will handle it
given $! {
when Mumble {...}
default {...}
$SUCCEEDED = 0; # unassume we handled it
}
# the user may handle exception either by
# 1. pattern matching in the given
# 2. explicitly setting $!.handled = 1
$!.handled = 1 if $SUCCEEDED;
# conjecture: this might be enforced by the exception thrower instead
if $!.handled {
$!.wrap-die("Pending exceptions not handled") unless all($!.pending».handled);
}
$!;
}
The exception thrower looks up the call stack for a catch lambda that returns the exception object as handled, and then it is happy, and unwinds the stack to that point. If the exception is returned as not handled. the exception thrower keeps looking for a higher dynamic scope for a spot to unwind to. Note that any die in the catch lambda rethrows outside the lambda as a new exception, wrapping up the old exception in its new pending list. In this case the lambda never finishes executing. Resumable exceptions may or may not leave normally depending on the implementation. If continuations are used, the $!.resume call will simply goto the continuation in question, and the lambda's callframe is abandoned. Resumable exceptions may also be implemented by simply marking the $! exception as "resumed", in which case the original exception thrower simply returns to the code that threw the resumable exception, rather than unwinding before returning.
A CATCH block sees the lexical scope in which it was defined, but its caller is the dynamic location that threw the exception. That is, the stack is not unwound until some exception handler chooses to unwind it by "handling" the exception in question. So logically, if the CATCH block throws its own exception, you would expect the CATCH block to catch its own exception recursively forever. However, a CATCH must not behave that way, so we say that a CATCH block never attempts to handle any exception thrown within its own dynamic scope. (Otherwise any die would cause an infinite loop.)
From t/spec/S04-exception-handlers/catch.t lines 160–225: (skip)
-
| # L<S04/"Exception handlers"/a CATCH block never attempts to handle any exception thrown within its own dynamic scope>
|
|
|
| {
|
| my $catches = 0;
|
| try {
|
| {
|
| die 'catch!';
|
| CATCH { default {die 'caught' if ! $catches++;} }
|
| };
|
| }
|
|
|
| #?rakudo 2 todo 'CATCH block catching its own exceptions (RT #64262)'
|
| is $catches, 1, "CATCH doesn't catch exceptions thrown in its own lexical scope";
|
|
|
| $catches = 0;
|
| my $f = { die 'caught' if ! $catches++; };
|
| try {
|
| {
|
| die 'catch!';
|
| CATCH { default {$f()} }
|
| };
|
| }
|
|
|
| is $catches, 1, "CATCH doesn't catch exceptions thrown in its own dynamic scope";
|
|
|
| my $s = '';
|
| {
|
| die 'alpha';
|
| CATCH {
|
| default {
|
| $s ~= 'a';
|
| die 'beta';
|
| }
|
| CATCH {
|
| default { $s ~= 'b'; }
|
| }
|
| }
|
| };
|
|
|
| is $s, 'ab', 'CATCH directly nested in CATCH catches exceptions thrown in the outer CATCH';
|
|
|
| $s = '';
|
| {
|
| die 'alpha';
|
| CATCH {
|
| default {
|
| $s ~= 'a';
|
| die 'beta';
|
| CATCH {
|
| default { $s ~= 'b'; }
|
| }
|
| }
|
| }
|
| };
|
|
|
| is $s, 'ab', 'CATCH indirectly nested in CATCH catches exceptions thrown in the outer CATCH';
|
| };
|
|
|
| # RT #62264
|
| {
|
| try { die "Goodbye cruel world!" };
|
| ok $!.^isa(Exception), '$!.^isa works';
|
| }
|
|
|
|
|
| # vim: ft=perl6
|
Any attempt to throw a fatal exception past an already active exception handler must guarantee to steal the existing fatal exception (plus any pending exceptions it contains) and add all those to the new exception's pending list. (This does not apply to control exceptions described in the next section.) When the new exception is handled, it must also deal with the list of pending exceptions, or the wrap-die mentioned above will throw a "Pending exceptions not handled" at that point. Even this does not discard the pending exceptions, so in the final outermost message, all non-handled exceptions are guaranteed to be listed.
From t/spec/S04-statements/return.t lines 7–56: (skip)
-
| #L<S04/"Control Exceptions">
|
|
|
| =begin pod
|
|
|
| Basic tests for "return"
|
|
|
| =end pod
|
|
|
| sub bar { return }
|
| is(bar(), Nil, '... bare return statement returned Nil');
|
|
|
| sub bar2 { return() }
|
| is(bar2(), Nil, '... bare return statement w/ parens returned Nil');
|
|
|
| sub baz { return 10 if 1; }
|
| is(baz(), 10, '... return worked with a statement modifier');
|
|
|
| sub foobar { return if 1; };
|
| is(foobar(), Nil, '... bare return worked with a statement modifier');
|
|
|
| sub foobar2 { return() if 1; }
|
| is(foobar2(), Nil, '... bare return worked with a statement modifier');
|
|
|
| my $should_ret_empty_list1 = sub { return; 1 };
|
| is $should_ret_empty_list1().elems, 0, "our sub returned an empty list (1)";
|
|
|
| sub return_1 { return 1; }
|
| is(return_1(), 1, '... return_1() returned 1 correctly');
|
|
|
| is( try { sub foo { my $x = 1; while $x-- { return 24; }; return 42; }; foo() }, 24, 'return in while');
|
|
|
| # S04: "A return always exits from the lexically surrounding sub or method definition"
|
| {
|
| eval_dies_ok('return 1', 'bare return fails');
|
| eval_dies_ok('for 1 {return 2}', 'cannot return out of a bare for block');
|
| eval_dies_ok('my $i = 1; while $i-- {return 3}', 'cannot return out of a bare while');
|
| eval_dies_ok('my $i = 0; until $i++ {return 4}', 'cannot return out of a bare until');
|
| eval_dies_ok('loop (my $i = 0; $i < 1; $i++) {return 5}', 'cannot return out of a bare loop');
|
| # XXX: Not 100% sure on this one
|
| eval_dies_ok('do {return 5}', 'cannot return out of a do block');
|
| }
|
|
|
| {
|
| # In an ancient version of pugs the sub below didn't return anything
|
| sub list_return { return (42, 1) }
|
| my $bar = ~list_return();
|
| is($bar, '42 1', 'Should not return empty string');
|
| }
|
|
|
| # vim: ft=perl6
|
All abnormal control flow is, in the general case, handled by the exception mechanism (which is likely to be optimized away in specific cases.) Here "abnormal" means any transfer of control outward that is not just falling off the end of a block. A return, for example, is considered a form of abnormal control flow, since it can jump out of multiple levels of closures to the end of the scope of the current subroutine definition. Loop commands like next are abnormal, but looping because you hit the end of the block is not. The implicit break of a when block is abnormal.
A CATCH block handles only "bad" exceptions, and lets control exceptions pass unhindered. Control exceptions may be caught with a CONTROL block. Generally you don't need to worry about this unless you're defining a control construct. You may have one CATCH block and one CONTROL block, since some user-defined constructs may wish to supply an implicit CONTROL block to your closure, but let you define your own CATCH block.
A return always exits from the lexically surrounding sub or method definition (that is, from a function officially declared with the sub, method, or submethod keywords). Pointy blocks and bare closures are transparent to return, in that the return statement still means &?ROUTINE.leave from the Routine that existed in dynamic scope when the closure was cloned.
It is illegal to return from the closure if that Routine no longer owns a call frame in the current call stack.
To return a value (to the dynamical caller) from any pointy block or bare closure, you either just let the block return the value of its final expression, or you can use leave, which comes in both function and method forms. The function (or listop) form always exits from the innermost block, returning its arguments as the final value of the block exactly as return does. The method form will leave any block in the dynamic scope that can be named as an object and that responds to the .leave method.
Hence, the leave function:
leave(1,2,3)
is really just short for:
&?BLOCK.leave(1,2,3)
To return from your immediate caller, you can say:
caller.leave(1,2,3)
Further call frames up the caller stack may be located by use of the callframe function:
callframe({ .labels.any eq 'LINE' }).leave(1,2,3);
By default the innermost call frame matching the selection criteria will be exited. This can be a bit cumbersome, so in the particular case of labels, the label that is already visible in the current lexical scope is considered a kind of pseudo object specifying a potential dynamic context. If instead of the above you say:
LINE.leave(1,2,3)
it was always exit from your lexically scoped LINE loop, even if some inner dynamic scope you can't see happens to also have that label. (In other words, it's lexotic.) If the LINE label is visible but you aren't actually in a dynamic scope controlled by that label, an exception is thrown. (If the LINE is not visible, it would have been caught earlier at compile time since LINE would likely be a bareword.)
In theory, any user-defined control construct can catch any control exception it likes. However, there have to be some culturally enforced standards on which constructs capture which exceptions. Much like return may only return from an "official" subroutine or method, a loop exit like next should be caught by the construct the user expects it to be caught by. In particular, if the user labels a loop with a specific label, and calls a loop control from within the lexical scope of that loop, and if that call mentions the outer loop's label, then that outer loop is the one that must be controlled. In other words, it first tries this form:
LINE.leave(1,2,3)
If there is no such lexically scoped outer loop in the current subroutine, then a fallback search is made outward through the dynamic scopes in the same way Perl 5 does. (The difference between Perl 5 and Perl 6 in this respect arises only because Perl 5 didn't have user-defined control structures, hence the sub's lexical scope was always the innermost dynamic scope, so the preference to the lexical scope in the current sub was implicit. For Perl 6 we have to make this preference for lexotic behavior explicit.)
Warnings are produced in Perl 6 by throwing a resumable control exception to the outermost scope, which by default prints the warning and resumes the exception by extracting a resume continuation from the exception, which must be supplied by the warn() function (or equivalent). Exceptions are not resumable in Perl 6 unless the exception object does the Resumable role. (Note that fatal exception types can do the Resumable role even if thrown via fail()--when uncaught they just hit the outermost fatal handler instead of the outermost warning handler, so some inner scope has to explicitly treat them as warnings and resume them.)
Since warnings are processed using the standard control exception mechanism, they may be intercepted and either suppressed or fatalized anywhere within the dynamic scope by supplying a suitable CONTROL block. This dynamic control is orthogonal to any lexically scoped warning controls, which merely decide whether to call warn() in the first place.
As with calls to return, the warning control exception is an abstraction that the compiler is free to optimize away (along with the associated continuation) when the compiler or runtime can determine that the semantics would be preserved by merely printing out the error and going on. Since all exception handlers run in the dynamic scope of the throw, that reduces to simply returning from the warn function most of the time. See previous section for discussion of ways to return from catch lambdas. The control lambda is logically separate from the catch lambda, though an implementation is allowed to combine them if it is careful to retain separate semantics for catch and control exceptions.
From t/spec/S04-statements/goto.t lines 5–81: (skip)
-
| # L<S04/"The goto statement">
|
|
|
| =begin description
|
|
|
| Tests for the goto() builtin
|
|
|
| We have "phases" to make sure the gotos didn't run wild.
|
|
|
| =end description
|
|
|
|
|
| our $phase;
|
|
|
| sub test1_ok { return 1 }
|
| sub test1 {
|
| &test1_ok.nextwith();
|
| return 0;
|
| }
|
| ok(test1(), "&sub.nextwith does");
|
| is(++$phase, 1, "phase completed");
|
|
|
| # the same, but with subs declared after the call.
|
|
|
| sub test2 {
|
| &test2_ok.nextwith();
|
| return 0;
|
| }
|
| sub test2_ok { return 1 }
|
| ok(test2(), "&sub.nextwith does (forward reference)");
|
| is(++$phase, 2, "phase completed");
|
|
|
| ok(test3(), "&sub.nextwith does (real forward reference)");
|
| sub test3 {
|
| &test3_ok.nextwith();
|
| return 0;
|
| }
|
| sub test3_ok { 1 }
|
| is(++$phase, 3, "phase completed");
|
|
|
| is(moose(), $?LINE, "regular call to moose() is consistent");
|
| is(foo(), $?LINE, "goto eliminates call stack frames");
|
|
|
| sub foo {
|
| &moose.nextwith();
|
| }
|
|
|
| sub moose {
|
| $?CALLER::LINE;
|
| }
|
|
|
| is(++$phase, 4, "phase completed");
|
|
|
| # Simple test case to get support for goto LABEL in pugs
|
| # Source for the syntax: S06 "The leave function"
|
| # > last COUNT;
|
|
|
| our $test5 = 1;
|
| eval q{ goto SKIP5; };
|
| $test5 = 0;
|
| SKIP5:
|
| #?pugs todo 'feature'
|
| is($test5, 1, "goto label");
|
|
|
| is(++$phase, 5, "phase completed");
|
|
|
| # this one tests "goto EXPR" syntax. pugs treats "last EXPR" as "last;" in r14915.
|
|
|
| our $test6 = 1;
|
| eval q{ goto 'SK' ~ 'IP6'; };
|
| $test6 = 0;
|
| SKIP6:
|
| #?pugs todo 'feature'
|
| is($test6, 1, "goto expr");
|
|
|
| is(++$phase, 6, "phase completed");
|
|
|
| # vim: ft=perl6
|
In addition to next, last, and redo, Perl 6 also supports goto. As with ordinary loop controls, the label is searched for first lexically within the current subroutine, then dynamically outside of it. Unlike with loop controls, however, scanning a scope includes a scan of any lexical scopes included within the current candidate scope. As in Perl 5, it is possible to goto into a lexical scope, but only for lexical scopes that require no special initialization of parameters. (Initialization of ordinary variables does not count--presumably the presence of a label will prevent code-movement optimizations past the label.) So, for instance, it's always possible to goto into the next case of a when or into either the "then" or "else" branch of a conditional. You may not go into a given or a for, though, because that would bypass a formal parameter binding (not to mention list generation in the case of for). (Note: the implicit default binding of an outer $_ to an inner $_ can be emulated for a bare block, so that doesn't fall under the prohibition on bypassing formal binding.)
As in Perl 5, many built-in functions simply return an undefined value when you ask for a value out of range, or the function fails somehow. Perl 6 has Failure objects, any of which refers to an unthrown Exception object in $! and knows whether it has been handled or not. $! contains one main exception, the most recent, plus an internal list of unhandled exceptions that may be accessed via the .pending method. Whenever a new exception is stored in $!, it becomes the new main exception, and if the old main exception is not marked as handled, it is pushed onto the internal list of unhandled exceptions.
From t/spec/S04-exceptions/pending.t lines 8–22: (skip)
-
| # L<S04/Exceptions/"internal list of unhandled exceptions">
|
|
|
| {
|
| try { die 'OMG' }
|
| ok $! ~~ Exception, '$! has an exception';
|
| #?rakudo 2 skip '$!.pending'
|
| ok $!.pending ~~ List, '$!.pending returns a List';
|
| is $!.pending, (), '$! there are no exceptions pending';
|
|
|
| undefine $!;
|
| ok ! $!, '$! has been cleared';
|
| }
|
|
|
| sub fail_it { fail $^a }
|
|
|
If you test a Failure for .defined or .Bool, it causes $! to mark the main exception as handled; the exception acts as a relatively harmless undefined value thereafter. Any other use of the Failure object to extract a normal value will throw its associated exception immediately. (The Failure may, however, be stored in any container whose type allows the Failure role to be mixed in.) The .handled method returns False on failures that have not been handled. It returns True for handled exceptions and for all non-Failure objects. (That is, it is a Mu method, not a Failure method. Only Failure objects need to store the actual status however; other types just return True.)
From t/spec/S04-exceptions/pending.t lines 23–41: (skip)
-
| # L<S04/Exceptions/"test a Failure for .defined or .Bool">
|
|
|
| {
|
| my @fails = ( fail_it(1), fail_it(2), fail_it(3), fail_it(4) );
|
|
|
| ok all(@fails) ~~ Failure, '@fails is full of fail';
|
| ok $! !~~ Exception, 'fails do not enter $!';
|
| #?rakudo 11 skip '$!.pending'
|
| is +($!.pending.grep( ! *.handled )), 4,
|
| '$!.pending has three unhandled exceptions';
|
|
|
| ok ! @fails[0].handled, 'fail 0 is not handled';
|
| ok @fails[0].not, 'fail 0 is not true';
|
| ok @fails[0].handled, 'fail 0 is now handled';
|
|
|
| ok ! @fails[1].handled, 'fail 1 is not handled';
|
| ok ! @fails[1].defined, 'fail 1 is not defined';
|
| ok @fails[1].handled, 'fail 1 is now handled';
|
|
|
From t/spec/S04-exceptions/pending.t lines 74–84: (skip)
-
| # L<S04/Exceptions/"a Mu method, not a Failure method">
|
|
|
| {
|
| my $win = Mu.new;
|
| #?rakudo skip '$object.handled'
|
| ok $win.handled, '.handled method is true for all Mus';
|
| }
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
The .handled method is rw, so you may mark an exception as handled by assigning True to it. Note however that
From t/spec/S04-exceptions/pending.t lines 42–54: (skip)
-
| # L<S04/Exceptions/"The .handled method is rw">
|
|
|
| ok ! @fails[2].handled, 'fail 2 is not handled';
|
| lives_ok { @fails[2].handled = 1 }, 'assign to .handled';
|
| ok @fails[2].handled, 'fail 2 is now handled';
|
|
|
| is +($!.pending.grep( ! *.handled )), 1,
|
| '$!.pending has one unhandled exception';
|
|
|
| undefine $!;
|
| ok ! $!, '$! has been cleared';
|
| }
|
|
|
$!.handled = 1;
marks only the main exception as handled. To mark them all as handled you must access them individually via the .pending method.
A bare die/fail takes $! as the default argument specifying the exception to be thrown or propagated outward to the caller's $!.
From t/spec/S02-magicals/dollar_bang.t lines 13–77: (skip)
-
| # L<S04/"Exceptions"/A bare die/fail takes $! as the default argument>
|
|
|
| eval 'nonexisting_subroutine()';
|
| ok defined($!), 'nonexisting sub in eval makes $! defined';
|
| eval 'nonexisting_subroutine()';
|
| ok $!, 'Calling a nonexisting subroutine sets $!';
|
| try { 1 };
|
| nok $!.defined, 'successfull try { } resets $!';
|
|
|
| try { 1.nonexisting_method; };
|
| ok $!.defined, 'Calling a nonexisting method defines $!';
|
| try { 1.nonexisting_method; };
|
| ok $!, 'Calling a nonexisting smethod sets $!';
|
|
|
| my $called;
|
| sub foo(Str $s) { return $called++ }; #OK not used
|
| my @a;
|
| try { foo(@a,@a) };
|
| ok $!, 'Calling a subroutine with a nonmatching signature sets $!';
|
| ok !$called, 'The subroutine also was not called';
|
|
|
| try { 1 div 0 };
|
| ok $!, 'Dividing one by zero sets $!';
|
|
|
| sub incr ( $a is rw ) { $a++ };
|
| try { incr(19) };
|
| ok $!, 'Modifying a constant sets $!';
|
|
|
| try {
|
| try {
|
| die 'qwerty';
|
| }
|
| ok ~($!) ~~ /qwerty/, 'die sets $! properly';
|
| die; # use the default argument
|
| }
|
| #?rakudo todo 'stringification of $!'
|
| ok ~($!) ~~ /qwerty/, 'die without argument uses $! properly';
|
|
|
| # RT #70011
|
| {
|
| undefine $!;
|
| try { die('goodbye'); }
|
| ok defined( $!.perl ), '$! has working Perl 6 object methods after try';
|
| ok ($!.WHAT ~~ Exception), '$! is Exception object after try';
|
| # - S04-statements/try.t tests $! being set after try.
|
| # - S29-context/die.t tests $! being set after die.
|
| # - also tested more generically above.
|
| # So no need to test the value of #! again here.
|
| #is $!, 'goodbye', '$! has correct value after try';
|
| ok ($!), '$! as boolean works (true)';
|
|
|
| eval q[ die('farewell'); ];
|
| ok defined($!.perl), '$! has working Perl 6 object methods after eval';
|
| ok ($!.WHAT ~~ Exception), '$! is Exception object after eval';
|
| # Although S29-context/die.t tests $! being set after die, it's not
|
| # from within an eval, so we test the eval/die combination here.
|
| # As that file (and also S04-statements/try.t) do equality comparisons
|
| # rather than pattern matches, we check equality here, too.
|
| is $!, 'farewell', '$! has correct value after eval';
|
|
|
| try { 1; }
|
| ok (! $!), '$! as boolean works (false)';
|
| }
|
|
|
| # vim: ft=perl6
|
Because the dynamic variable $! contains all exceptions collected in the current lexical scope, saying die $! will rethrow all those exceptions as the new thrown exception, keeping the same structure of main exception and list of unhandled exceptions. (The $! seen in a CATCH block is specially bound to this in-flight exception as the block's initial value for $!, but it may be modified by additional failures as can any other block's $! value.) A fail likewise moves all $! exceptions up into CALLER::<$!> before returning the current exception as normal return of a Failure.
At scope exit, $! discards all handled exceptions from itself, then if there are any remaining unhandled exceptions, either as the main exception or as any listed unhandled exception, it calls die to throw those exceptions as a single new exception, which may then be caught with a CATCH block in the current (or caller's) scope. The new main exception is the most recent one, with any older unhandled exceptions attached as pending.
From t/spec/S04-exceptions/pending.t lines 55–73: (skip)
-
| # L<S04/Exceptions/"At scope exit,">
|
|
|
| {
|
| my $fails_thrown = 0;
|
| {
|
| my @throwable = ( fail_it(1), fail_it(2), fail_it(3) );
|
| @throwable[1].handled = 1;
|
| CATCH {
|
| default {
|
| $fails_thrown += +($!.pending);
|
| }
|
| }
|
| }
|
| is $fails_thrown, 2, 'unhandled Failures in $! at block exit are thrown';
|
|
|
| undefine $!;
|
| ok ! $!, '$! has been cleared';
|
| }
|
|
|
You can cause built-ins to automatically throw exceptions on failure using
use fatal;
The fail function responds to the caller's use fatal state. It either returns an unthrown exception, or throws the exception. Before you get too happy about this pragma, note that Perl 6 contains various parallel processing primitives that will tend to get blown up prematurely by thrown exceptions. Unthrown exceptions are meant to provide a failsoft mechanism in which failures can be treated as data and dealt with one by one, without aborting execution of what may be perfectly valid parallel computations. If you don't deal with the failures as data, then the block exit semantics will eventually trigger a thrown exception.
From t/spec/S04-exceptions/fail.t lines 7–52: (skip)
-
| # L<S04/Exceptions/The fail function>
|
|
|
| {
|
| # "use fatal" is not standard, so we don't have to disable it here
|
| my $was_after_fail = 0;
|
| my $was_before_fail = 0;
|
| my $sub = sub { $was_before_fail++; my $exception = fail 42; $was_after_fail++ }; #OK not used
|
|
|
| my $unthrown_exception = $sub();
|
| # Note: We don't further access $unthrown_exception, so it doesn't get thrown
|
| is $was_before_fail, 1, "fail() doesn't cause our sub to not get executed";
|
| is $was_after_fail, 0, "fail() causes our sub to return (1)";
|
| }
|
|
|
| {
|
| my $was_after_fail = 0;
|
| my $was_after_sub = 0;
|
| my $sub = sub { fail 42; $was_after_fail++ };
|
|
|
| use fatal;
|
| try { $sub(); $was_after_sub++ };
|
|
|
| is $was_after_fail, 0, "fail() causes our sub to return (2)";
|
| is $was_after_sub, 0, "fail() causes our try to die";
|
| }
|
|
|
| # RT #64990
|
| {
|
| our Int sub rt64990 { fail() }
|
| ok rt64990() ~~ Failure, 'sub typed Int can fail()';
|
|
|
| our Int sub repeat { return fail() }
|
| ok repeat() ~~ Failure, 'sub typed Int can return Failure';
|
| }
|
|
|
| # RT #70229
|
| {
|
| sub rt70229 { return fail() }
|
| my $rt70229 = rt70229();
|
| ok $rt70229 ~~ Failure, 'got a Failure';
|
| dies_ok { ~$rt70229 }, 'attempt to stringify Failure dies';
|
| }
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
In any case, the overriding design principle here is that no unhandled exception is ever dropped on the floor, but propagated outward through subsequent $! variables until it is handled. If that never happens, the implicit outermost exception handler will eventually decide to abort and print all unhandled exceptions found in the $! that it is responsible for.
From t/spec/S04-phasers/in-loop.t lines 19–74: (skip)
-
| # L<S04/"Phasers">
|
|
|
| {
|
| my $str;
|
|
|
| for 1..10 -> $i {
|
| last if $i > 3;
|
| $str ~= "($i a)";
|
| next if $i % 2 == 1;
|
| $str ~= "($i b)";
|
| LAST { $str ~= "($i Lst)" }
|
| LEAVE { $str ~= "($i Lv)" }
|
| NEXT { $str ~= "($i N)" }
|
| FIRST { $str ~= "($i F)" }
|
| ENTER { $str ~= "($i E)" }
|
| }
|
|
|
| is $str, "(1 F)(1 E)(1 a)" ~ "(1 N)(1 Lv)" ~
|
| "(2 E)(2 a)(2 b)(2 N)(2 Lv)" ~
|
| "(3 E)(3 a)" ~ "(3 N)(3 Lv)" ~
|
| "(4 E)" ~ "(4 Lv)(4 Lst)",
|
| 'trait blocks work properly in for loop';
|
| }
|
|
|
| {
|
| my $str;
|
|
|
| for 1..10 -> $i {
|
| last if $i > 3;
|
| $str ~= "($i a)";
|
|
|
| ENTER { $str ~= "($i E1)" }
|
| LAST { $str ~= "($i Lst1)" }
|
| FIRST { $str ~= "($i F1)" }
|
| LEAVE { $str ~= "($i Lv1)" }
|
|
|
| next if $i % 2 == 1;
|
| $str ~= "($i b)";
|
|
|
| LAST { $str ~= "($i Lst2)" }
|
| NEXT { $str ~= "($i N1)" }
|
| FIRST { $str ~= "($i F2)" }
|
| LEAVE { $str ~= "($i Lv2)" }
|
| ENTER { $str ~= "($i E2)" }
|
| NEXT { $str ~= "($i N2)" }
|
| }
|
|
|
| is $str,
|
| "(1 F1)(1 F2)(1 E1)(1 E2)(1 a)" ~ "(1 N2)(1 N1)" ~ "(1 Lv2)(1 Lv1)" ~
|
| "(2 E1)(2 E2)(2 a)(2 b)(2 N2)(2 N1)" ~ "(2 Lv2)(2 Lv1)" ~
|
| "(3 E1)(3 E2)(3 a)" ~ "(3 N2)(3 N1)" ~ "(3 Lv2)(3 Lv1)" ~
|
| "(4 E1)(4 E2)" ~ "(4 Lv2)(4 Lv1)" ~ "(4 Lst2)(4 Lst1)",
|
| 'trait blocks work properly in for loop';
|
| }
|
|
|
| # vim: ft=perl6
|
A CATCH block is just a trait of the closure containing it, and is automatically called at the appropriate moment. These auto-called blocks are known as phasers, since they generally mark the transition from one phase of computing to another. For instance, a CHECK block is called at the end of compiling a compilation unit. Other kinds of phasers can be installed as well; these are automatically called at various times as appropriate, and some of them respond to various control exceptions and exit values:
BEGIN {...}* at compile time, ASAP, only ever runs once
CHECK {...}* at compile time, ALAP, only ever runs once
From t/spec/S04-phasers/check.t lines 7–37: (skip)
-
| # L<S04/"Phasers"/CHECK "at compile time" ALAP>
|
| # CHECK {...} block in "void" context
|
| {
|
| my $str = '';
|
| BEGIN { $str ~= "begin1 "; }
|
| CHECK { $str ~= "check "; }
|
| BEGIN { $str ~= "begin2 "; }
|
|
|
| is $str, "begin1 begin2 check ", "check blocks run after begin blocks";
|
| }
|
|
|
| {
|
| my $str = '';
|
| CHECK { $str ~= "check1 "; }
|
| BEGIN { $str ~= "begin "; }
|
| CHECK { $str ~= "check2 "; }
|
|
|
| is $str, "begin check2 check1 ", "check blocks run in reverse order";
|
| }
|
|
|
| # CHECK {...} blocks as rvalues
|
| {
|
| my $str = '';
|
| my $handle = { my $retval = CHECK { $str ~= 'C' } };
|
|
|
| is $handle(), 'C', 'our CHECK {...} block returned the correct var (1)';
|
| is $handle(), 'C', 'our CHECK {...} block returned the correct var (2)';
|
| is $str, 'C', 'our rvalue CHECK {...} block was executed exactly once';
|
| }
|
|
|
| # vim: ft=perl6
|
INIT {...}* at run time, ASAP, only ever runs once
From t/spec/S04-phasers/init.t lines 7–56: (skip)
-
| # L<S04/"Phasers"/INIT "at run time" ASAP>
|
| # INIT {...} blocks in "void" context
|
| {
|
| my $str;
|
| is $str, "begin1 begin2 init ", "init blocks run after begin blocks";
|
|
|
| BEGIN { $str ~= "begin1 "; }
|
| INIT { $str ~= "init "; }
|
| BEGIN { $str ~= "begin2 "; }
|
| }
|
|
|
| {
|
| my $str;
|
| is $str, "check2 check1 init ", "init blocks run after check blocks";
|
|
|
| CHECK { $str ~= "check1 "; }
|
| INIT { $str ~= "init "; }
|
| CHECK { $str ~= "check2 "; }
|
| }
|
|
|
| {
|
| my $str;
|
| is $str, "begin init1 init2 ", "init blocks run in forward order";
|
|
|
| INIT { $str ~= "init1 "; }
|
| BEGIN { $str ~= "begin "; }
|
| INIT { $str ~= "init2 "; }
|
| }
|
|
|
| # INIT {...} blocks as rvalues
|
| {
|
| my $str;
|
| my $handle = { my $retval = INIT { $str ~= 'I' } };
|
|
|
| is $str, 'I', 'our INIT {...} block already gets called';
|
| is $handle(), 'I', 'our INIT {...} block returned the correct var (1)';
|
| is $handle(), 'I', 'our INIT {...} block returned the correct var (2)';
|
| is $str, 'I', 'our rvalue INIT {...} block was executed exactly once';
|
| }
|
|
|
| # IRC note:
|
| # <TimToady1> also, the INIT's settings are going to get wiped
|
| # out when the my is executed, so you probably just
|
| # end up with 'o'
|
| {
|
| my $str = 'o';
|
| INIT { $str ~= 'i' }
|
| is $str, 'o', 'the value set by INIT {} wiped out by the initializer of $str';
|
| }
|
|
|
END {...} at run time, ALAP, only ever runs once
From t/spec/S04-phasers/ascending-order.t lines 11–62: (skip)
-
| # L<S04/Phasers/END "at run time" ALAP>
|
|
|
| my $var;
|
| my ($var_at_begin, $var_at_check, $var_at_init, $var_at_start, $var_at_enter);
|
| my $eof_var;
|
|
|
| $var = 13;
|
|
|
| my $hist;
|
|
|
| # XXX check if BEGIN blocks do have to remember side effects
|
| BEGIN {
|
| $hist ~= 'begin ';
|
| $var_at_begin = $var;
|
| }
|
|
|
| CHECK {
|
| $hist ~= 'check ';
|
| $var_at_check = $var;
|
| }
|
|
|
| INIT {
|
| $hist ~= 'init ';
|
| $var_at_init = $var;
|
| }
|
|
|
| ENTER {
|
| $hist ~= 'enter ';
|
| $var_at_enter = $var;
|
| }
|
|
|
| START {
|
| $hist ~= 'start ';
|
| $var_at_start = $var + 1;
|
| }
|
|
|
| END {
|
| # tests for END blocks:
|
| is $var, 13, '$var gets initialized at END time';
|
| is $eof_var, 29, '$eof_var gets assigned at END time';
|
| }
|
|
|
| is $hist, 'begin check init start ', 'BEGIN {} runs only once';
|
| ok $var_at_begin.notdef, 'BEGIN {...} ran at compile time';
|
| ok $var_at_check.notdef, 'CHECK {...} ran at compile time';
|
| ok $var_at_init.notdef, 'INIT {...} ran at runtime, but ASAP';
|
| ok $var_at_enter.notdef, 'ENTER {...} at runtime, but before the mainline body';
|
| is $var_at_start, 14, 'START {...} at runtime, just in time';
|
|
|
| $eof_var = 29;
|
|
|
| # vim: ft=perl6
|
From t/spec/S04-phasers/interpolate.t lines 11–42: (skip)
-
| # L<S04/Phasers/END "at run time" ALAP>
|
|
|
| # IRC log:
|
| # ----------------------------------------------------------------
|
| # agentzh question: should BEGIN blocks interpolated in double-quoted
|
| # strings be fired at compile-time or run-time?
|
| # for example, say "This is { BEGIN { say 'hi' } }";
|
| # audreyt compile time.
|
| # qq is not eval.
|
|
|
| my $hist;
|
|
|
| END {
|
| is $hist, 'BCISE', 'interpolated END {...} executed';
|
| }
|
|
|
| ok "{ END { $hist ~= 'E' } }".notdef,
|
| 'END {...} not yet executed';
|
|
|
| is "{ START { $hist ~= 'S' } }", "BCIS",
|
| 'START {...} fired at run-time, entry time of the mainline code';
|
|
|
| is "{ INIT { $hist ~= 'I' } }", 'BCI',
|
| 'INIT {...} fired at the beginning of runtime';
|
|
|
| is "{ CHECK { $hist ~= 'C' } }", "BC",
|
| 'CHECK {...} fired at compile-time, ALAP';
|
|
|
| is "{ BEGIN { $hist ~= 'B' } }", "B",
|
| 'BEGIN {...} fired at compile-time, ASAP';
|
|
|
| # vim: ft=perl6
|
From t/spec/S04-phasers/descending-order.t lines 11–55: (skip)
-
| # L<S04/Phasers/END "at run time" ALAP>
|
|
|
| my $var;
|
| my ($var_at_enter, $var_at_init, $var_at_check, $var_at_begin);
|
| my $eof_var;
|
|
|
| $var = 13;
|
|
|
| my $hist;
|
|
|
| END {
|
| # tests for END blocks:
|
| is $var, 13, '$var gets initialized at END time';
|
| is $eof_var, 29, '$eof_var gets assigned at END time';
|
| }
|
|
|
| ENTER {
|
| $hist ~= 'enter ';
|
| $var_at_enter = $var;
|
| }
|
|
|
| INIT {
|
| $hist ~= 'init ';
|
| $var_at_init = $var;
|
| }
|
|
|
| CHECK {
|
| $hist ~= 'check ';
|
| $var_at_check = $var;
|
| }
|
|
|
| BEGIN {
|
| $hist ~= 'begin ';
|
| $var_at_begin = $var;
|
| }
|
|
|
| is $hist, 'begin check init ', 'BEGIN {} runs only once';
|
| ok $var_at_begin.notdef, 'BEGIN {...} ran at compile time';
|
| ok $var_at_check.notdef, 'CHECK {...} ran at compile time';
|
| ok $var_at_init.notdef, 'INIT {...} ran at runtime, but ASAP';
|
| ok $var_at_enter.notdef, 'ENTER {...} at runtime, but before the mainline body';
|
|
|
| $eof_var = 29;
|
|
|
| # vim: ft=perl6
|
From t/spec/S04-exceptions/die-and-end.t lines 3–12: (skip)
-
| # L<S04/Phasers/END>
|
|
|
| use Test;
|
| plan 1;
|
|
|
| my $x = 0;
|
| eval 'END { $x = 1 }; die "fatal";';
|
| is $x, 1, 'die() does not prevent END block from being run';
|
|
|
| # vim: ft=perl6
|
START {...}* on first ever execution, once per closure clone
From t/spec/S04-phasers/start.t lines 38–46: (skip)
-
| # L<S04/"Phasers"/START "on first ever execution">
|
| {
|
| my $str ~= 'o';
|
| {
|
| START { $str ~= 'i' }
|
| }
|
| is $str, 'oi', 'START {} runs when we first try to use a block';
|
| }
|
|
|
ENTER {...}* at every block entry time, repeats on loop blocks.
From t/spec/S04-phasers/enter-leave.t lines 7–54: (skip)
-
| # L<S04/Phasers/ENTER "at every block entry time">
|
| # L<S04/Phasers/LEAVE "at every block exit time">
|
|
|
| {
|
| my $str;
|
| my sub foo ($x, $y) {
|
| ENTER { $str ~= "(" }
|
| LEAVE { $str ~= ")" }
|
| $str ~= "$x,$y";
|
| }
|
| foo(3,4);
|
| is $str, '(3,4)';
|
| foo(-1,2);
|
| is $str, '(3,4)(-1,2)';
|
| }
|
|
|
| # reversed order
|
| {
|
| my $str;
|
| my sub foo ($x, $y) {
|
| $str ~= "$x,$y";
|
| LEAVE { $str ~= ")" }
|
| ENTER { $str ~= "(" }
|
| }
|
| foo(7,-8);
|
| is $str, '(7,-8)';
|
| foo(5,0);
|
| is $str, '(7,-8)(5,0)';
|
| }
|
|
|
| # multiple ENTER and LEAVE blocks
|
| {
|
| my $str;
|
| {
|
| ENTER { $str ~= '[' }
|
| LEAVE { $str ~= ']' }
|
|
|
| $str ~= 21;
|
|
|
| ENTER { $str ~= '(' }
|
| LEAVE { $str ~= ')' }
|
|
|
| ENTER { $str ~= '{' }
|
| LEAVE { $str ~= '}' }
|
| }
|
| is $str, '[({21})]', 'multiple ENTER/LEAVE worked';
|
| }
|
|
|
From t/spec/S04-phasers/enter-leave.t lines 55–65: (skip)
-
| # L<S04/Phasers/ENTER "repeats on loop blocks">
|
| {
|
| my $str;
|
| for 1..2 {
|
| $str ~= ',';
|
| ENTER { $str ~= "E$_" }
|
| LEAVE { $str ~= "L$_ " }
|
| }
|
| is $str, 'E1,L1 E2,L2 ', 'ENTER/LEAVE repeats on loop blocks';
|
| }
|
|
|
LEAVE {...} at every block exit time (even stack unwinds from exceptions)
From t/spec/S04-phasers/enter-leave.t lines 8–54: (skip)
-
| # L<S04/Phasers/LEAVE "at every block exit time">
|
|
|
| {
|
| my $str;
|
| my sub foo ($x, $y) {
|
| ENTER { $str ~= "(" }
|
| LEAVE { $str ~= ")" }
|
| $str ~= "$x,$y";
|
| }
|
| foo(3,4);
|
| is $str, '(3,4)';
|
| foo(-1,2);
|
| is $str, '(3,4)(-1,2)';
|
| }
|
|
|
| # reversed order
|
| {
|
| my $str;
|
| my sub foo ($x, $y) {
|
| $str ~= "$x,$y";
|
| LEAVE { $str ~= ")" }
|
| ENTER { $str ~= "(" }
|
| }
|
| foo(7,-8);
|
| is $str, '(7,-8)';
|
| foo(5,0);
|
| is $str, '(7,-8)(5,0)';
|
| }
|
|
|
| # multiple ENTER and LEAVE blocks
|
| {
|
| my $str;
|
| {
|
| ENTER { $str ~= '[' }
|
| LEAVE { $str ~= ']' }
|
|
|
| $str ~= 21;
|
|
|
| ENTER { $str ~= '(' }
|
| LEAVE { $str ~= ')' }
|
|
|
| ENTER { $str ~= '{' }
|
| LEAVE { $str ~= '}' }
|
| }
|
| is $str, '[({21})]', 'multiple ENTER/LEAVE worked';
|
| }
|
|
|
From t/spec/S04-phasers/enter-leave.t lines 66–183: (skip)
-
| # L<S04/Phasers/LEAVE "at every block exit time">
|
|
|
| # named sub:
|
| {
|
| my $str;
|
| my sub is_even ($x) {
|
| return 1 if $x % 2 == 0;
|
| return 0;
|
| LEAVE { $str ~= $x }
|
| }
|
| is is_even(3), 0, 'basic sanity check (1)';
|
| is $str, '3', 'LEAVE executed at the 1st explicit return';
|
| is is_even(2), 1, 'basic sanity check (2)';
|
| is $str, '32', 'LEAVE executed at the 2nd explicit return';
|
| }
|
|
|
| # normal closure:
|
| {
|
| is eval(q{
|
| my $a;
|
| {
|
| leave;
|
| $a = 100;
|
| LEAVE { $a++ }
|
| }
|
| $a;
|
| }), 1, 'leave triggers LEAVE {}';
|
| }
|
|
|
| {
|
| my $str;
|
| try {
|
| ENTER { $str ~= '(' }
|
| LEAVE { $str ~= ')' }
|
| $str ~= 'x';
|
| die 'foo';
|
| }
|
| is $str, '(x)', 'die calls LEAVE blocks';
|
| }
|
|
|
| {
|
| my $str;
|
| try {
|
| LEAVE { $str ~= $! // '<undef>' }
|
| die 'foo';
|
| }
|
| ok $str ~~ /foo/, '$! set in LEAVE if exception thrown';
|
| }
|
|
|
| {
|
| my $str;
|
| {
|
| LEAVE { $str ~= (defined $! ?? 'yes' !! 'no') }
|
| try { die 'foo' }
|
| $str ~= (defined $! ?? 'aye' !! 'nay');
|
| }
|
| is $str, 'ayeno', '$! not set in LEAVE if exception not thrown';
|
| }
|
|
|
| {
|
| my $str;
|
| try {
|
| $str ~= '(';
|
| try {
|
| ENTER { die 'foo' }
|
| $str ~= 'x';
|
| }
|
| $str ~= ')';
|
| }
|
| is $str, '()', 'die in ENTER caught by try';
|
| }
|
|
|
| {
|
| my $str;
|
| try {
|
| $str ~= '(';
|
| try {
|
| LEAVE { die 'foo' }
|
| $str ~= 'x';
|
| }
|
| $str ~= ')';
|
| }
|
| is $str, '(x)', 'die in LEAVE caught by try';
|
| }
|
|
|
| {
|
| my $str;
|
| try {
|
| $str ~= '(';
|
| try {
|
| ENTER { $str ~= '['; die 'foo' }
|
| LEAVE { $str ~= ']' }
|
| $str ~= 'x';
|
| }
|
| $str ~= ')';
|
| }
|
| is $str, '([])', 'die in ENTER calls LEAVE';
|
| }
|
|
|
| {
|
| my $str;
|
| try {
|
| ENTER { $str ~= '1'; die 'foo' }
|
| ENTER { $str ~= '2' }
|
| }
|
| is $str, '1', 'die aborts ENTER queue';
|
| }
|
|
|
| {
|
| my $str;
|
| try {
|
| LEAVE { $str ~= '1' }
|
| LEAVE { $str ~= '2'; die 'foo' }
|
| }
|
| is $str, '21', 'die doesn\'t abort LEAVE queue';
|
| }
|
|
|
| # vim: ft=perl6
|
KEEP {...} at every successful block exit, part of LEAVE queue
From t/spec/S04-phasers/keep-undo.t lines 7–27: (skip)
-
| # L<S04/Phasers/KEEP "at every successful block exit">
|
| # L<S04/Phasers/UNDO "at every unsuccessful block exit">
|
|
|
| {
|
| my $str;
|
| my sub is_pos ($n) {
|
| return (($n > 0) ?? 1 !! Mu);
|
| KEEP { $str ~= "$n > 0 " }
|
| UNDO { $str ~= "$n <= 0 " }
|
| }
|
|
|
| ok is_pos(1), 'is_pos worked for 1';
|
| is $str, '1 > 0 ', 'KEEP ran as expected';
|
|
|
| ok !is_pos(0), 'is_pos worked for 0';
|
| is $str, '1 > 0 0 <= 0 ', 'UNDO worked as expected';
|
|
|
| ok !is_pos(-1), 'is_pos worked for 0';
|
| is $str, '1 > 0 0 <= 0 -1 <= 0 ', 'UNDO worked as expected';
|
| }
|
|
|
UNDO {...} at every unsuccessful block exit, part of LEAVE queue
From t/spec/S04-phasers/keep-undo.t lines 8–27: (skip)
-
| # L<S04/Phasers/UNDO "at every unsuccessful block exit">
|
|
|
| {
|
| my $str;
|
| my sub is_pos ($n) {
|
| return (($n > 0) ?? 1 !! Mu);
|
| KEEP { $str ~= "$n > 0 " }
|
| UNDO { $str ~= "$n <= 0 " }
|
| }
|
|
|
| ok is_pos(1), 'is_pos worked for 1';
|
| is $str, '1 > 0 ', 'KEEP ran as expected';
|
|
|
| ok !is_pos(0), 'is_pos worked for 0';
|
| is $str, '1 > 0 0 <= 0 ', 'UNDO worked as expected';
|
|
|
| ok !is_pos(-1), 'is_pos worked for 0';
|
| is $str, '1 > 0 0 <= 0 -1 <= 0 ', 'UNDO worked as expected';
|
| }
|
|
|
FIRST {...}* at loop initialization time, before any ENTER
From t/spec/S04-phasers/first.t lines 7–24: (skip)
-
| # L<S04/Phasers/FIRST "at loop initialization time">
|
| {
|
| my $str = '';
|
| for 1..2 {
|
| FIRST { $str ~= $_ }
|
| }
|
| is $str, 1, 'FIRST only ran once';
|
| }
|
|
|
| {
|
| my ($a, $a_in_first);
|
| for 1..2 {
|
| $a++;
|
| FIRST { $a_in_first = $a }
|
| }
|
| ok $a_in_first.notdef, 'FIRST {} ran before the loop body';
|
| }
|
|
|
From t/spec/S04-phasers/first.t lines 36–47: (skip)
-
| # L<S04/Phasers/FIRST "at loop initialization time" "before any ENTER">
|
| {
|
| my $str = '';
|
| for 1..2 {
|
| FIRST { $str ~= 'f1' }
|
| ENTER { $str ~= 'e' }
|
| FIRST { $str ~= 'f2' }
|
| }
|
| is $str, 'f1f2ee', 'FIRST {} ran before ENTER {}';
|
| }
|
|
|
| # vim: ft=perl6
|
NEXT {...} at loop continuation time, before any LEAVE
From t/spec/S04-phasers/next.t lines 98–118: (skip)
-
| # L<S04/Phasers/NEXT "before any LEAVE">
|
|
|
| {
|
| my $str = '';
|
| for 1..2 {
|
| NEXT { $str ~= 'n' }
|
| LEAVE { $str ~= 'l' }
|
| }
|
| is $str, 'nlnl', 'NEXT {} ran before LEAVE {} (1)';
|
| }
|
|
|
| # reversed order
|
| {
|
| my $str = '';
|
| for 1..2 {
|
| LEAVE { $str ~= 'l' }
|
| NEXT { $str ~= 'n' }
|
| }
|
| is $str, 'nlnl', 'NEXT {} ran before LEAVE {} (2)';
|
| }
|
|
|
From t/spec/S04-phasers/next.t lines 119–153: (skip)
-
| # L<S04/Phasers/NEXT "at loop continuation time">
|
|
|
| # L<http://groups.google.com/group/perl.perl6.language/msg/07370316d32890dd>
|
|
|
| {
|
| my $str = '';
|
| my $n = 0;
|
| my $i;
|
| while $n < 5 {
|
| NEXT { ++$n } # this gets run second (LIFO)
|
| NEXT { $str ~= $n } # this gets run first (LIFO)
|
| last if $i++ > 100; # recursion prevension
|
| }
|
| is $str, '01234', 'NEXT {} ran in reversed order';
|
| }
|
|
|
| {
|
| my $str = '';
|
| loop (my $n = 0; $n < 5; ++$n) {
|
| NEXT { $str ~= $n }
|
| }
|
| is $str, '01234', 'NEXT {} works in loop (;;) {}';
|
| }
|
|
|
| {
|
| my @x = 0..4;
|
| my $str = '';
|
| for @x {
|
| NEXT { $str ~= $_; }
|
| }
|
|
|
| is($str, '01234', 'NEXT {} works in for loop');
|
| }
|
|
|
| # vim: ft=perl6
|
LAST {...} at loop termination time, after any LEAVE
PRE {...} assert precondition at every block entry, before ENTER
From t/spec/S04-phasers/pre-post.t lines 6–206: (skip)
-
| # L<S04/Phasers/"assert precondition at every block ">
|
| # L<S06/Subroutine traits/PRE/POST>
|
| #
|
| # TODO:
|
| # * Multiple inheritance + PRE/POST blocks
|
|
|
| plan 25;
|
|
|
| sub foo(Num $i) {
|
| PRE {
|
| $i < 5
|
| }
|
| return 1;
|
| }
|
|
|
| sub bar(Int $i) {
|
| return 1;
|
| POST {
|
| $i < 5;
|
| }
|
| }
|
|
|
| lives_ok { foo(2) }, 'sub with PRE compiles and runs';
|
| lives_ok { bar(3) }, 'sub with POST compiles and runs';
|
|
|
| dies_ok { foo(10) }, 'Violated PRE throws (catchable) exception';
|
| dies_ok { bar(10) }, 'Violated POST throws (catchable) exception';
|
|
|
| # multiple PREs und POSTs
|
|
|
| sub baz (Num $i) {
|
| PRE {
|
| $i > 0
|
| }
|
| PRE {
|
| $i < 23
|
| }
|
| return 1;
|
| }
|
| lives_ok { baz(2) }, 'sub with two PREs compiles and runs';
|
|
|
| dies_ok { baz(-1)}, 'sub with two PREs fails when first is violated';
|
| dies_ok { baz(42)}, 'sub with two PREs fails when second is violated';
|
|
|
|
|
| sub qox (Num $i) {
|
| return 1;
|
| POST {
|
| $i > 0
|
| }
|
| POST {
|
| $i < 42
|
| }
|
| }
|
|
|
| lives_ok({ qox(23) }, "sub with two POSTs compiles and runs");
|
| dies_ok( { qox(-1) }, "sub with two POSTs fails if first POST is violated");
|
| dies_ok( { qox(123)}, "sub with two POSTs fails if second POST is violated");
|
|
|
| # inheritance
|
|
|
| class PRE_Parent {
|
| method test(Num $i) {
|
| PRE {
|
| $i < 23
|
| }
|
| return 1;
|
| }
|
| }
|
|
|
| class PRE_Child is PRE_Parent {
|
| method test(Num $i){
|
| PRE {
|
| $i > 0;
|
| }
|
| return 1;
|
| }
|
| }
|
|
|
| my $foo = PRE_Child.new;
|
|
|
| lives_ok { $foo.test(5) }, 'PRE in methods compiles and runs';
|
| dies_ok { $foo.test(-42) }, 'PRE in child throws';
|
| dies_ok { $foo.test(78) }, 'PRE in parent throws';
|
|
|
|
|
| class POST_Parent {
|
| method test(Num $i) {
|
| return 1;
|
| POST {
|
| $i > 23
|
| }
|
| }
|
| }
|
|
|
| class POST_Child is POST_Parent {
|
| method test(Num $i){
|
| return 1;
|
| POST {
|
| $i < -23
|
| }
|
| }
|
| }
|
| my $mp = POST_Child.new;
|
|
|
| lives_ok { $mp.test(-42) }, "It's enough if we satisfy one of the POST blocks (Child)";
|
| lives_ok { $mp.test(42) }, "It's enough if we satisfy one of the POST blocks (Parent)";
|
| dies_ok { $mp.test(12) }, 'Violating poth POST blocks throws an error';
|
|
|
| class Another {
|
| method test(Num $x) {
|
| return 3 * $x;
|
| POST {
|
| $_ > 4
|
| }
|
| }
|
| }
|
|
|
| my $pt = Another.new;
|
| lives_ok { $pt.test(2) }, 'POST receives return value as $_ (succeess)';
|
| dies_ok { $pt.test(1) }, 'POST receives return value as $_ (failure)';
|
|
|
| {
|
| my $str;
|
| {
|
| PRE { $str ~= '('; 1 }
|
| POST { $str ~= ')'; 1 }
|
| $str ~= 'x';
|
| }
|
| is $str, '(x)', 'PRE and POST run on ordinary blocks';
|
| }
|
|
|
| {
|
| my $str;
|
| {
|
| POST { $str ~= ')'; 1 }
|
| LEAVE { $str ~= ']' }
|
| ENTER { $str ~= '[' }
|
| PRE { $str ~= '('; 1 }
|
| $str ~= 'x';
|
| }
|
| is $str, '([x])', 'PRE/POST run outside ENTER/LEAVE';
|
| }
|
|
|
| {
|
| my $str;
|
| try {
|
| {
|
| PRE { $str ~= '('; 0 }
|
| PRE { $str ~= '*'; 1 }
|
| ENTER { $str ~= '[' }
|
| $str ~= 'x';
|
| LEAVE { $str ~= ']' }
|
| POST { $str ~= ')'; 1 }
|
| }
|
| }
|
| is $str, '(', 'failing PRE runs nothing else';
|
| }
|
|
|
| {
|
| my $str;
|
| try {
|
| {
|
| POST { $str ~= 'x'; 0 }
|
| LEAVE { $str ~= 'y' }
|
| POST { $str ~= 'z'; 1 }
|
| }
|
| }
|
| is $str, 'yx', 'failing POST runs LEAVE but not more POSTs';
|
| }
|
|
|
| {
|
| my $str;
|
| try {
|
| POST { $str ~= $! // '<undef>'; 1 }
|
| die 'foo';
|
| }
|
| ok $str ~~ /foo/, 'POST runs on exception, with correct $!';
|
| }
|
|
|
| {
|
| my $str;
|
| try {
|
| POST { $str ~= (defined $! ?? 'yes' !! 'no'); 1 }
|
| try { die 'foo' }
|
| $str ~= (defined $! ?? 'aye' !! 'nay');
|
| }
|
| is $str, 'ayeno', 'POST has undefined $! on no exception';
|
| }
|
|
|
| {
|
| try {
|
| POST { 0 }
|
| die 'foo';
|
| }
|
| ok $! ~~ /foo/, 'failing POST on exception doesn\'t replace $!';
|
| # XXX
|
| # is $!.pending.[-1], 'a POST exception', 'does push onto $!.pending';
|
| }
|
|
|
| # vim: ft=perl6
|
POST {...} assert postcondition at every block exit, after LEAVE
CATCH {...} catch exceptions, before LEAVE
CONTROL {...} catch control exceptions, before LEAVE
Those marked with a * can also be used within an expression:
From t/spec/S04-phasers/rvalue.t lines 10–78: (skip)
-
| # L<S04/Phasers/"marked with a *" "used within" expression>
|
|
|
| {
|
| my $x = BEGIN { 8 };
|
| is $x, 8, 'BEGIN block as expression';
|
|
|
| # test that built-ins are available within a BEGIN block:
|
| my $y = BEGIN { ucfirst 'moin' };
|
| is $y, 'Moin', 'can access built-in functions in BEGIN blocks';
|
|
|
| my $z = BEGIN { 'moin'.ucfirst };
|
| is $z, 'Moin', 'can access built-in methods in BEGIN blocks';
|
| }
|
|
|
| {
|
| my $x = BEGIN 8;
|
| is $x, 8, 'BEGIN statement prefix as expression';
|
|
|
| # test that built-ins are available within a BEGIN block:
|
| my $y = BEGIN ucfirst 'moin';
|
| is $y, 'Moin', 'can access built-in functions in BEGIN statement prefix';
|
|
|
| my $z = BEGIN 'moin'.ucfirst;
|
| is $z, 'Moin', 'can access built-in methods in BEGIN statement prefix';
|
| }
|
|
|
| #?rakudo skip 'lexicals in phasers'
|
| {
|
| my $hist = '';
|
|
|
| # Test INIT {} as rval:
|
|
|
| my $init_val;
|
| my $init = {
|
| $init_val = INIT { $hist ~= 'I' };
|
| }
|
|
|
| is $init(), 'BCI', 'INIT {} runs only once';
|
| is $init_val, 'BCI', 'INIT {} as rval is its ret val';
|
| is $init(), 'BCI', 'INIT {} runs only once';
|
|
|
| # Test CHECK {} as rval:
|
|
|
| my $check_val;
|
| my $check = {
|
| $check_val = CHECK { $hist ~= 'C' };
|
| }
|
|
|
| is $check(), 'BC', 'CHECK {} runs only once';
|
| is $check_val, 'BC', 'CHECK {} as rval is its ret val';
|
| is $check(), 'BC', 'CHECK {} runs only once';
|
|
|
| # Test BEGIN {} as rval:
|
|
|
| my $begin_val;
|
| my $begin = {
|
| $begin_val = BEGIN { $hist ~= 'B' };
|
| }
|
|
|
| is $begin(), 'B', 'BEGIN {} runs only once';
|
| is $begin_val, 'B', 'BEGIN {} as rval is its ret val';
|
| is $begin(), 'B', 'BEGIN {} runs only once';
|
|
|
| # Test END {} as rval:
|
|
|
| ok !eval 'my $end_val = END { 3 }', "END {} can't be used as a rvalue";
|
| }
|
|
|
| # vim: ft=perl6
|
my $compiletime = BEGIN { localtime };
our $temphandle = START { maketemp() };
As with other statement prefixes, these value-producing constructs may be placed in front of either a block or a statement:
my $compiletime = BEGIN localtime;
our $temphandle = START maketemp();
In fact, most of these phasers will take either a block or a statement (known as a blast in the vernacular). The statement form can be particularly useful to expose a lexically scoped declaration to the surrounding lexical scope without "trapping" it inside a block.
Hence these declare the same variables with the same scope as the preceding example, but run the statements as a whole at the indicated time:
BEGIN my $compiletime = localtime;
START our $temphandle = maketemp();
(Note, however, that the value of a variable calculated at compile time may not persist under run-time cloning of any surrounding closure.)
Most of the non-value-producing phasers may also be so used:
END say my $accumulator;
Note, however, that
END say my $accumulator = 0;
sets the variable to 0 at END time, since that is when the "my" declaration is actually executed. Only argumentless phasers may use the statement form. This means that CATCH and CONTROL always require a block, since they take an argument that sets $_ to the current topic, so that the innards are able to behave as a switch statement. (If bare statements were allowed, the temporary binding of $_ would leak out past the end of the CATCH or CONTROL, with unpredictable and quite possibly dire consequences. Exception handlers are supposed to reduce uncertainty, not increase it.)
Code that is generated at run time can still fire off CHECK and INIT phasers, though of course those phasers can't do things that would require travel back in time. You need a wormhole for that.
From t/spec/S04-phasers/in-eval.t lines 9–101: (skip)
-
| # L<S04/Phasers/Code "generated at run time" "still fire off"
|
| # "can't" "travel back in time" >
|
|
|
| my ($handle);
|
|
|
| our $h;
|
|
|
| {
|
| my $h;
|
|
|
| eval '$handle = { $h ~= "1"; START { $h ~= "F" }; $h ~= "2" }';
|
| ok $! !~~ Exception, 'eval START {...} works';
|
|
|
| ok $h.notdef, 'START {...} has not run yet';
|
| lives_ok { $handle() }, 'can run code with START block';
|
| is $h, '1F2', 'START {...} fired';
|
| lives_ok { $handle() }, 'can run code with START block again';
|
| is $h, '1F212', 'START {...} fired only once';
|
|
|
| # test that it runs again for a clone of $handle
|
| $h = '';
|
| my $start_clone = $handle.clone;
|
| is $h, '', 'cloning code does not run anything';
|
| lives_ok { $start_clone() }, 'can run clone of code with START block';
|
| #?rakudo todo 'clone of code with START should run START again'
|
| is $h, '1F2', 'START {...} fired again for the clone';
|
| lives_ok { $start_clone() }, 'can run clone of START block code again';
|
| #?rakudo todo 'clone of code with START should not run START again'
|
| is $h, '1F212', 'cloned START {...} fired only once';
|
| }
|
|
|
| {
|
| my $h;
|
|
|
| eval '$handle = { $h =~ "r"; INIT { $h ~= "I" }; $h ~= "R" }';
|
| ok $! !~~ Exception, 'eval INIT {...} works';
|
| ok $h.notdef, 'INIT did not run at compile time';
|
| #?rakudo 4 todo 'Could not find non-existent sub INIT'
|
| lives_ok { $handle() }, 'can run code with INIT block';
|
| is $h, 'IrR', 'INIT {...} fires at run-time';
|
| lives_ok { $handle() }, 'can run code with INIT block again';
|
| is $h, 'IrRrR', 'INIT runs only once';
|
|
|
| # TODO: test that it does not run again for a clone of $handle (?)
|
| }
|
|
|
| {
|
| our $h = Mu;
|
|
|
| eval '$handle = { our $h ~= "1"; CHECK { our $h ~= "C" };'
|
| ~ ' our $h ~= "2"; BEGIN { our $h ~= "B" }; our $h ~= "3" }';
|
| ok $! !~~ Exception, 'eval CHECK {...} (and BEGIN {...}) works';
|
|
|
| #?rakudo 5 todo 'Could not find non-existent sub CHECK'
|
| is $h, 'BC', 'CHECK and BEGIN blocks ran before run time';
|
| lives_ok { $handle() }, 'can run code with CHECK and BEGIN blocks';
|
| is $h, 'BC123', 'CHECK {...} runs at compile time after BEGIN';
|
| lives_ok { $handle() }, 'can run code with CHECK and BEGIN again';
|
| is $h, 'BC123123', 'CHECK runs once';
|
| }
|
|
|
| {
|
| our $h = Mu;
|
|
|
| eval '$handle = { our $h ~= "1"; BEGIN { our $h ~= "B" }; our $h ~= "2" }';
|
| ok $! !~~ Exception, 'eval BEGIN {...} works';
|
|
|
| is $h, 'B', 'BEGIN ran before run time';
|
| lives_ok { $handle() }, 'can run code with BEGIN block';
|
| is $h, 'B12', 'BEGIN does not run again at run time';
|
| }
|
|
|
| #?rakudo skip 'test harness does not see test result in END'
|
| {
|
| END {
|
| is our $end, '12E', 'the END {...} in eval has run already';
|
| }
|
| }
|
|
|
| {
|
| our $end = Mu;
|
|
|
| eval '$handle = { our $end ~= "1"; END { our $end ~= "E" }; our $end ~= "2" }';
|
| ok $! !~~ Exception, 'eval END {...} works';
|
|
|
| ok $end.notdef, 'END {} has not run yet';
|
| lives_ok { $handle() }, 'can call code with END block';
|
| is $end, '12', 'END {} does not run at run time either';
|
| }
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
Some of these phasers also have corresponding traits that can be set on variables. These have the advantage of passing the variable in question into the closure as its topic:
my $r will start { .set_random_seed() };
our $h will enter { .rememberit() } will undo { .forgetit() };
Apart from CATCH and CONTROL, which can only occur once, most of these can occur multiple times within the block. So they aren't really traits, exactly--they add themselves onto a list stored in the actual trait (except for START, which executes inline). So if you examine the ENTER trait of a block, you'll find that it's really a list of phasers rather than a single phaser.
From t/spec/S04-phasers/keep-undo.t lines 46–72: (skip)
-
| # L<S04/Phasers/"can occur multiple times">
|
|
|
| # multiple KEEP/UNDO
|
| {
|
| my $str;
|
| {
|
| KEEP { $str ~= 'K1 ' }
|
| KEEP { $str ~= 'K2 ' }
|
| UNDO { $str ~= 'U1 ' }
|
| UNDO { $str ~= 'U2 ' }
|
| 1;
|
| }
|
| is $str, 'K2 K1 ', '2 KEEP blocks triggered';
|
| }
|
|
|
| {
|
| my $str;
|
| {
|
| KEEP { $str ~= 'K1 ' }
|
| KEEP { $str ~= 'K2 ' }
|
| UNDO { $str ~= 'U1 ' }
|
| UNDO { $str ~= 'U2 ' }
|
| }
|
| is $str, 'U2 U1 ', '2 UNDO blocks triggered';
|
| }
|
|
|
| # vim: ft=perl6
|
From t/spec/S04-phasers/first.t lines 25–35: (skip)
-
| # L<S04/Phasers/can occur multiple times>
|
| {
|
| my $str = '';
|
| for 1..2 {
|
| FIRST { $str ~= $_ }
|
| FIRST { $str ~= ':' }
|
| FIRST { $str ~= ' ' }
|
| }
|
| is $str, '1: ', 'multiple FIRST {} ran in order';
|
| }
|
|
|
From t/spec/S04-phasers/start.t lines 47–79: (skip)
-
| # L<S04/"Phasers"/START "executes inline">
|
|
|
| # Execute the tests twice to make sure that START binds to
|
| # the lexical scope, not the lexical position.
|
| for <first second> {
|
| my $sub = {
|
| my $str = 'o';
|
| START { $str ~= 'I' };
|
| START { $str ~= 'i' };
|
| ":$str";
|
| };
|
|
|
| is $sub(), ':oIi', "START block set \$str to 3 ($_ time)";
|
| is $sub(), ':o', "START wasn't invoked again (1-1) ($_ time)";
|
| is $sub(), ':o', "START wasn't invoked again (1-2) ($_ time)";
|
| }
|
|
|
| # Some behavior occurs where START does not close over the correct
|
| # pad when closures are cloned
|
|
|
| my $ran;
|
| for <first second> {
|
| my $str = 'bana';
|
| $ran = 0;
|
| my $sub = {
|
| START { $ran++; $str ~= 'na' };
|
| };
|
|
|
| $sub(); $sub();
|
| is $ran, 1, "START block ran exactly once ($_ time)";
|
| is $str, 'banana', "START block modified the correct variable ($_ time)";
|
| }
|
|
|
From t/spec/S04-phasers/multiple.t lines 11–40: (skip)
-
| # L<S04/Phasers/"occur multiple times">
|
| # IRC log:
|
| # [05:41] <agentzh> TimToady: S04 doesn't discuss the running order
|
| # of multiple phasers (say, two END {} in
|
| # the same scope), so should we assume it's the
|
| # same as in Perl 5?
|
| # [05:41] <TimToady> yes
|
|
|
| my $hist;
|
|
|
| END { is $hist, 'B b c C I i S s end End ', 'running order of multiple phasers' }
|
|
|
| END { $hist ~= 'End ' }
|
| END { $hist ~= 'end ' }
|
|
|
| START { $hist ~= 'S ' }
|
| START { $hist ~= 's ' }
|
|
|
| INIT { $hist ~= 'I ' }
|
| INIT { $hist ~= 'i ' }
|
|
|
| CHECK { $hist ~= 'C ' }
|
| CHECK { $hist ~= 'c ' }
|
|
|
| BEGIN { $hist ~= 'B ' }
|
| BEGIN { $hist ~= 'b ' }
|
|
|
| is $hist, 'B b c C I i S s ', 'running order of multiple phasers';
|
|
|
| # vim: ft=perl6
|
The semantics of INIT and START are not equivalent to each other in the case of cloned closures. An INIT only runs once for all copies of a cloned closure. A START runs separately for each clone, so separate clones can keep separate state variables:
From t/spec/S04-phasers/start.t lines 7–22: (skip)
-
| # L<S04/"Phasers"/START "runs separately for each clone">
|
| #?rakudo todo '$_ inside START has some issues, it seems'
|
| {
|
| is(eval(q{{
|
| my $str;
|
| for 1..2 {
|
| my $sub = {
|
| START { $str ~= $_ };
|
| };
|
| $sub();
|
| $sub();
|
| }
|
| $str;
|
| }}), '12');
|
| };
|
|
|
From t/spec/S04-phasers/init.t lines 65–74: (skip)
-
| # L<S04/Phasers/INIT "runs once for all copies of" "cloned closure">
|
| {
|
| my $var;
|
| for <first second> {
|
| my $sub = { INIT { $var++ } };
|
| is $var, 1, "INIT has run exactly once ($_ time)";
|
| }
|
| }
|
|
|
| # vim: ft=perl6
|
our $i = 0;
...
$func = { state $x will start { $x = $i++ }; dostuff($i) };
But state automatically applies "start" semantics to any initializer, so this also works:
From t/spec/S04-declarations/state.t lines 37–292: (skip)
-
| # L<S04/Phasers/"semantics to any initializer, so this also works">
|
| {
|
| my $gen = {
|
| # Note: The following line is only executed once, because it's equivalent
|
| # to
|
| # state $svar will first { 42 };
|
| state $svar = 42;
|
| my $ret = { $svar++ };
|
| };
|
|
|
| my $a = $gen(); # $svar == 42
|
| $a(); $a(); # $svar == 44
|
| my $b = $gen(); # $svar == 44
|
|
|
| is $b(), 44, "state() works inside coderefs";
|
| }
|
|
|
| # state() inside for-loops
|
| {
|
| for 1,2,3 -> $val {
|
| state $svar;
|
| $svar++;
|
|
|
| # Only check on last run
|
| if $val == 3 {
|
| is $svar, 3, "state() works inside for-loops";
|
| }
|
| }
|
| }
|
|
|
| # state with arrays.
|
| {
|
| my @bar = 1,2,3;
|
| sub swatest {
|
| state (@foo) = @bar;
|
| my $x = @foo.join('|');
|
| @foo[0]++;
|
| return $x
|
| }
|
| is swatest(), '1|2|3', 'array state initialized correctly';
|
| is swatest(), '2|2|3', 'array state retained between calls';
|
| }
|
|
|
| # state with arrays.
|
| {
|
| sub swainit_sub { 1,2,3 }
|
| sub swatest2 {
|
| state (@foo) = swainit_sub();
|
| my $x = @foo.join('|');
|
| @foo[0]++;
|
| return $x
|
| }
|
| is swatest2(), '1|2|3', 'array state initialized from call correctly';
|
| is swatest2(), '2|2|3', 'array state retained between calls';
|
| }
|
|
|
| # (state @foo) = @bar differs from state @foo = @bar
|
| {
|
| my @bar = 1,2,3;
|
| sub swatest3 {
|
| (state @foo) = @bar;
|
| my $x = @foo.join('|');
|
| @foo[0]++;
|
| return $x
|
| }
|
| is swatest3(), '1|2|3', '(state @foo) = @bar is not state @foo = @bar';
|
| is swatest3(), '1|2|3', '(state @foo) = @bar is not state @foo = @bar';
|
| }
|
|
|
| # RHS of state is only run once per init
|
| {
|
| my $rhs_calls = 0;
|
| sub impure_rhs {
|
| state $x = do { $rhs_calls++ } #OK not used
|
| }
|
| impure_rhs() for 1..3;
|
| is $rhs_calls, 1, 'RHS of state $x = ... only called once';
|
| }
|
|
|
| # state will first {...}
|
| #?pugs eval "parse error"
|
| #?rakudo skip 'will first { ... }'
|
| {
|
| my ($a, $b);
|
| my $gen = {
|
| state $svar will first { 42 };
|
| -> { $svar++ };
|
| }
|
| $a = $gen(); # $svar == 42
|
| $a(); $a(); # $svar == 44
|
| $b = $gen()(); # $svar == 44
|
|
|
| is $b, 44, 'state will first {...} works';
|
| }
|
|
|
| # Return of a reference to a state() var
|
| #?rakudo skip 'references'
|
| {
|
| my $gen = {
|
| state $svar = 42;
|
| \$svar;
|
| };
|
|
|
| my $svar_ref = $gen();
|
| $$svar_ref++; $$svar_ref++;
|
|
|
| $svar_ref = $gen();
|
| #?pugs todo "state bug"
|
| is $$svar_ref, 44, "reference to a state() var";
|
| }
|
|
|
| # Anonymous state vars
|
| # L<http://groups.google.de/group/perl.perl6.language/msg/07aefb88f5fc8429>
|
| #?pugs todo 'anonymous state vars'
|
| #?rakudo skip 'references and anonymous state vars'
|
| {
|
| # XXX -- currently this is parsed as \&state()
|
| my $gen = eval '{ try { \state } }';
|
| $gen //= sub { my $x; \$x };
|
|
|
| my $svar_ref = $gen(); # $svar == 0
|
| try { $$svar_ref++; $$svar_ref++ }; # $svar == 2
|
|
|
| $svar_ref = $gen(); # $svar == 2
|
| is try { $$svar_ref }, 2, "anonymous state() vars";
|
| }
|
|
|
| # L<http://www.nntp.perl.org/group/perl.perl6.language/20888>
|
| # ("Re: Declaration and definition of state() vars" from Larry)
|
| #?pugs eval 'Parse error'
|
| {
|
| my ($a, $b);
|
| my $gen = {
|
| (state $svar) = 42;
|
| my $ret = { $svar++ };
|
| };
|
|
|
| $a = $gen(); # $svar == 42
|
| $a(); $a(); # $svar == 44
|
| $b = $gen()(); # $svar == 42
|
| is $b, 42, "state() and parens"; # svar == 43
|
| }
|
|
|
| # state() inside regular expressions
|
| #?rakudo skip 'embedded closures in regexen'
|
| {
|
| my $str = "abc";
|
|
|
| my $re = {
|
| # Perl 5 RE, as we don't want to force people to install Parrot ATM. (The
|
| # test passes when using the Perl 6 RE, too.)
|
| $str ~~ s:Perl5/^(.)/{
|
| state $svar;
|
| ++$svar;
|
| }/;
|
| };
|
| $re();
|
| $re();
|
| $re();
|
| is +$str, 3, "state() inside regular expressions works";
|
| }
|
|
|
| # state() inside subs, chained declaration
|
| {
|
| sub step () {
|
| state $svar = state $svar2 = 42;
|
| $svar++;
|
| $svar2--;
|
| return (+$svar, +$svar2);
|
| };
|
|
|
| is(step().join('|'), "43|41", "chained state (#1)");
|
| is(step().join('|'), "44|40", "chained state (#2)");
|
| }
|
|
|
| # state in cloned closures
|
| {
|
| for <first second> {
|
| my $code = {
|
| state $foo = 42;
|
| ++$foo;
|
| };
|
|
|
| is $code(), 43, "state was initialized properly ($_ time)";
|
| is $code(), 44, "state keeps its value across calls ($_ time)";
|
| }
|
| }
|
|
|
| # state with multiple explicit calls to clone - a little bit subtle
|
| {
|
| my $i = 0;
|
| my $func = { state $x = $i++; $x };
|
| my ($a, $b) = $func.clone, $func.clone;
|
| is $a(), 0, 'state was initialized correctly for clone 1';
|
| is $b(), 1, 'state was initialized correctly for clone 2';
|
| is $a(), 0, 'state between clones is independent';
|
| }
|
|
|
| # recursive state with list assignment initialization happens only first time
|
| {
|
| my $seensize;
|
| my sub fib (Int $n) {
|
| state @seen = 0,1,1;
|
| $seensize = +@seen;
|
| @seen[$n] //= fib($n-1) + fib($n-2);
|
| }
|
| is fib(10), 55, "fib 10 works";
|
| is $seensize, 11, "list assignment state in fib memoizes";
|
| }
|
|
|
| # recursive state with [list] assignment initialization happens only first time
|
| #?rakudo skip '@$foo syntax'
|
| {
|
| my $seensize;
|
| my sub fib (Int $n) {
|
| state $seen = [0,1,1];
|
| $seensize = +@$seen;
|
| $seen[$n] //= fib($n-1) + fib($n-2);
|
| }
|
| is fib(10), 55, "fib 2 works";
|
| is $seensize, 11, "[list] assignment state in fib memoizes";
|
| }
|
|
|
|
|
| {
|
| # now we're just being plain evil:
|
| subset A of Int where { $_ < state $x++ };
|
| my A $y = -4;
|
| # the compiler could have done some checks somehwere, so
|
| # pick a reasonably high number
|
| dies_ok { $y = 900000 }, 'growing subset types rejects too high values';
|
| lives_ok { $y = 1 }, 'the state variable in subset types works (1)';
|
| lives_ok { $y = 2 }, 'the state variable in subset types works (2)';
|
| lives_ok { $y = 3 }, 'the state variable in subset types works (3)';
|
| }
|
|
|
| # Test for RT #67058
|
| sub bughunt1 { (state $svar) } #OK not used
|
| {
|
| sub bughunt2 { state $x //= 17; ++$x }
|
| is bughunt2(), 18,
|
| 'a state variable in parens works with a state variable with //= init';
|
| }
|
|
|
| {
|
| # http://irclog.perlgeek.de/perl6/2010-04-27#i_2269848
|
| my @tracker;
|
| for (1..3) {
|
| my $x = sub { state $s++; @tracker.push: $s }
|
| $x();
|
| };
|
| is @tracker.join('|'), '1|1|1',
|
| 'state var in anonymous closure in loop is not shared';
|
| }
|
|
|
| # vim: ft=perl6
|
$func = { state $x = $i++; dostuff($i) }
Each subsequent clone gets an initial state that is one higher than the previous, and each clone maintains its own state of $x, because that's what state variables do.
Even in the absence of closure cloning, INIT runs before the mainline code, while START puts off the initialization till the last possible moment, then runs exactly once, and caches its value for all subsequent calls (assuming it wasn't called in sink context, in which case the START is evaluated once only for its side effects). In particular, this means that START can make use of any parameters passed in on the first call, whereas INIT cannot.
From t/spec/S04-phasers/start.t lines 23–37: (skip)
-
| # L<S04/"Phasers"/START "puts off" initialization till
|
| # "last possible moment">
|
| {
|
| my $var;
|
| my $sub = sub ($x) { START { $var += $x } };
|
|
|
| ok $var.notdef, 'START {...} has not run yet';
|
|
|
| $sub(2);
|
| is $var, 2, 'START {} has executed';
|
|
|
| $sub(3);
|
| is $var, 2, "START {} only runs once for each clone";
|
| }
|
|
|
From t/spec/S04-phasers/start.t lines 80–108: (skip)
-
| # L<S04/"Phasers"/START "caches its value for all subsequent calls">
|
| {
|
| my $was_in_start;
|
| my $sub = {
|
| my $var = START { $was_in_start++; 23 };
|
| $var //= 42;
|
| $var;
|
| };
|
|
|
| ok $was_in_start.notdef, 'START {} has not run yet';
|
| is $sub(), 23, 'START {} block set our variable (2)';
|
| is $sub(), 23, 'the returned value of START {} still there';
|
| is $was_in_start, 1, 'our START {} block was invoked exactly once';
|
| }
|
|
|
| # Test that START {} blocks are executed only once even if they return undefined
|
| # (the first implementation ran them twice instead).
|
| {
|
| my $was_in_start;
|
| my $sub = { START { $was_in_start++; Mu } };
|
|
|
| ok $sub().notdef, 'START {} returned undefined';
|
| $sub();
|
| $sub();
|
| is $was_in_start, 1,
|
| 'our START { ...; Mu } block was invoked exactly once';
|
| }
|
|
|
| # vim: ft=perl6
|
From t/spec/S04-phasers/init.t lines 57–64: (skip)
-
| # L<S04/"Phasers"/INIT "runs before" "mainline code">
|
|
|
| my $str ~= 'o'; # Note that this is different from "my $str = 'o';".
|
| {
|
| INIT { $str ~= 'i' }
|
| }
|
| is $str, 'io', 'INIT {} always runs before the mainline code runs';
|
|
|
All of these phaser blocks can see any previously declared lexical variables, even if those variables have not been elaborated yet when the closure is invoked (in which case the variables evaluate to an undefined value.)
Note: Apocalypse 4 confused the notions of PRE/POST with ENTER/LEAVE. These are now separate notions. ENTER and LEAVE are used only for their side effects. PRE and POST must return boolean values that are evaluated according to the usual Design by Contract (DBC) rules. (Plus, if you use ENTER/LEAVE in a class block, they only execute when the class block is executed, but you may declare PRE/POST submethods in a class block that will be evaluated around every method in the class.) KEEP and UNDO are just variants of LEAVE, and for execution order are treated as part of the queue of LEAVE phasers.
FIRST, NEXT, and LAST are meaningful only within the lexical scope of a loop, and may occur only at the top level of such a loop block. A NEXT executes only if the end of the loop block is reached normally, or an explicit next is executed. In distinction to LEAVE phasers, a NEXT phaser is not executed if the loop block is exited via any exception other than the control exception thrown by next. In particular, a last bypasses evaluation of NEXT phasers.
From t/spec/S04-phasers/next.t lines 7–50: (skip)
-
| # L<S04/Phasers/NEXT executes "only if"
|
| # "end of the loop block" or "explicit next">
|
| {
|
| my $str = '';
|
| for 1..5 {
|
| NEXT { $str ~= ':' }
|
| next if $_ % 2 == 1;
|
| $str ~= $_;
|
| }
|
| is $str, ':2::4::', 'NEXT called by both next and normal falling out';
|
| }
|
|
|
| # NEXT is positioned at the bottom:
|
| {
|
| my $str = '';
|
| for 1..5 {
|
| next if $_ % 2 == 1;
|
| $str ~= $_;
|
| NEXT { $str ~= ':' }
|
| }
|
| is $str, ':2::4::', 'NEXT called by both next and normal falling out';
|
| }
|
|
|
| # NEXT is positioned in the middle:
|
| {
|
| my $str = '';
|
| for 1..5 {
|
| next if $_ % 2 == 1;
|
| NEXT { $str ~= ':' }
|
| $str ~= $_;
|
| }
|
| is $str, ':2::4::', 'NEXT called by both next and normal falling out';
|
| }
|
|
|
| # NEXT is evaluated even at the last iteration
|
| {
|
| my $str = '';
|
| for 1..2 {
|
| NEXT { $str ~= 'n'; }
|
| LAST { $str ~= 'l'; }
|
| }
|
| is $str, 'nnl', 'NEXT are LAST blocks may not be exclusive';
|
| }
|
|
|
From t/spec/S04-phasers/next.t lines 51–87: (skip)
-
| # L<S04/Phasers/NEXT "not executed" if exited
|
| # "via any exception other than" next>
|
|
|
| {
|
| my $str = '';
|
| try {
|
| for 1..5 {
|
| NEXT { $str ~= $_ }
|
| die if $_ > 3;
|
| }
|
| }
|
| is $str, '123', "die didn't trigger NEXT \{}";
|
| }
|
|
|
| {
|
| my $str = '';
|
| try {
|
| for 1..5 {
|
| NEXT { $str ~= $_ }
|
| leave if $_ > 3;
|
| }
|
| }
|
| is $str, '123', "leave didn't trigger NEXT \{}";
|
| }
|
|
|
| {
|
| my $str = '';
|
| my sub foo {
|
| for 1..5 {
|
| NEXT { $str ~= $_ }
|
| return if $_ > 3;
|
| }
|
| }
|
| foo();
|
| is $str, '123', "return didn't trigger NEXT \{}";
|
| }
|
|
|
From t/spec/S04-phasers/next.t lines 88–97: (skip)
-
| # L<S04/Phasers/last bypasses evaluation of NEXT phasers>
|
| {
|
| my $str = '';
|
| for 1..5 {
|
| NEXT { $str ~= $_; }
|
| last if $_ > 3;
|
| }
|
| is $str, '123', "last bypass NEXT \{}";
|
| }
|
|
|
[Note: the name FIRST used to be associated with state declarations. Now it is associated only with loops. See the START above for state semantics.]
Except for CATCH and CONTROL phasers, which run while an exception is looking for a place to handle it, all block-leaving phasers wait until the call stack is actually unwound to run. Unwinding happens only after some exception handler decides to handle the exception that way. That is, just because an exception is thrown past a stack frame does not mean we have officially left the block yet, since the exception might be resumable. In any case, exception handlers are specified to run within the dynamic scope of the failing code, whether or not the exception is resumable. The stack is unwound and the phasers are called only if an exception is not resumed.
So LEAVE phasers for a given block are necessarily evaluated after any CATCH and CONTROL phasers. This includes the LEAVE variants, KEEP and UNDO. POST phasers are evaluated after everything else, to guarantee that even LEAVE phasers can't violate DBC. Likewise PRE phasers fire off before any ENTER or FIRST (though not before BEGIN, CHECK, or INIT, since those are done at compile or process initialization time). Much like BUILD and DESTROY are implicitly called in the correct order by BUILDALL and DESTROYALL, the PRE/POST calls are via an implicit CALL-VIA-DBC method that runs outside the actual call to the method in question. Class-level PRE/POST submethods are notionally outside of the method-level PRE/POST blocks. In the normal course of things, CALL-VIA-DBC follows these steps:
From t/spec/S04-phasers/keep-undo.t lines 28–45: (skip)
-
| # L<S04/Phasers/This includes the LEAVE variants, KEEP and UNDO.>
|
| {
|
| my $str;
|
| my sub is_pos($n) {
|
| return (($n > 0) ?? 1 !! Mu);
|
| LEAVE { $str ~= ")" }
|
| KEEP { $str ~= "$n > 0" }
|
| UNDO { $str ~= "$n <= 0" }
|
| LEAVE { $str ~= "(" }
|
| }
|
|
|
| is_pos(1);
|
| is $str, '(1 > 0)', 'KEEP triggered as part of LEAVE blocks';
|
|
|
| is_pos(-5);
|
| is $str, '(1 > 0)(-5 <= 0)', 'UNDO triggered as part of LEAVE blocks';
|
| }
|
|
|
1. create an empty stack for scheduling postcalls.
2. call all the appropriate per-class C<PRE> submethods,
pushing any corresponding C<POST> onto the postcall stack.
3. call all the appropriate per-method C<PRE> phasers,
pushing any corresponding C<POST> onto the postcall stack.
4. enforce DBC logic of C<PRE> calls
5. call the method call itself, capturing return/unwind status.
6. pop and call every C<POST> on the postcall stack.
7. enforce DBC logic of C<POST> calls
8. continue with the return or unwind.
Note that in steps 2 and 3, the POST block can be defined in one of two ways. Either the corresponding POST is defined as a separate declaration (submethod for 2, phaser for 3), in which case PRE and POST share no lexical scope. Alternately, any PRE (either submethod or phaser) may define its corresponding POST as an embedded phaser block that closes over the lexical scope of the PRE. In either case, the code is pushed onto the postphaser stack to be run at the appropriate moment.
If exit phasers are running as a result of a stack unwind initiated by an exception, $! contains the exception that caused it, though it will be marked as handled by then. In any case, the information as to whether the block is being exited successfully or unsuccessfully needs to be available to decide whether to run KEEP or UNDO blocks. If there is no stack-unwinding exception when these phasers are run, $! will be Nil. The last exception caught in the outer block is available as OUTER::<$!>, as usual.
An exception thrown from an ENTER phaser will abort the ENTER queue, but one thrown from a LEAVE phaser will not. The exceptions thrown by failing PRE and POST phasers cannot be caught by a CATCH in the same block, which implies that POST phaser are not run if a PRE phaser fails. If a POST fails while an exception is in flight the POST failure doesn't replace $! but goes straight into $!.pending.
For phasers such as KEEP and POST that are run when exiting a scope normally, the return value (if any) from that scope is available as the current topic within the phaser. (It is presented as a argument, that is, either as parcel or an object that can stand alone in a list. In other words, it's exactly what return is sending to the outside world in raw form, so that the phaser doesn't accidentally impose context prematurely.)
The topic of the block outside a phaser is still available as OUTER::<$_>. Whether the return value is modifiable may be a policy of the phaser in question. In particular, the return value should not be modified within a POST phaser, but a LEAVE phaser could be more liberal.
Class-level PRE and POST submethods are not in the lexical scope of a method (and are not run in the dynamic scope of the method), so they cannot see the method's $_ at all. As methods, they do have access to the current self, of course. And the POST submethod gets the return value as the topic, just as exit phasers do.
Any phaser defined in the lexical scope of a method is a closure that closes over self as well as normal lexicals. (Or equivalently, an implementation may simply turn all such phasers into submethods whose curried invocant is the current object.)
In this statement:
given EXPR {
when EXPR { ... }
when EXPR { ... }
...
}
parentheses aren't necessary around EXPR because the whitespace between EXPR and the block forces the block to be considered a block rather than a subscript, provided the block occurs where an infix operator would be expected. This works for all control structures, not just the new ones in Perl 6. A top-level bare block is always considered a statement block if there's a term and a space before it:
if $foo { ... }
elsif $bar { ... }
else { ... }
while $more { ... }
for 1..10 { ... }
You can still parenthesize the expression argument for old times' sake, as long as there's a space between the closing paren and the opening brace. (Otherwise it will be parsed as a hash subscript.)
Note that the parser cannot intuit how many arguments a list operator is taking, so if you mean 0 arguments, you must parenthesize the argument list to force the block to appear after a term:
if caller {...} # WRONG, parsed as caller({...})
if caller() {...} # okay
if (caller) {...} # okay
Note that common idioms work as expected though:
for map { $^a + 1 }, @list { .say }
Unless you are parsing a statement that expects a block argument, it is illegal to use a bare closure where an operator is expected because it will be considered to be two terms in row. (Remove the whitespace if you wish it to be a postcircumfix.)
Anywhere a term is expected, a block is taken to be a closure definition (an anonymous subroutine). If a closure has arguments, it is always taken as a normal closure. (In addition to standard formal parameters, placeholder arguments also count, as do the underscore variables. Implicit use of $_ with .method also counts as an argument.)
However, if an argumentless closure is empty, or appears to contain nothing but a comma-separated list starting with a pair or a hash (counting a single pair or hash as a list of one element), the closure will be immediately executed as a hash composer, as if called with .().
$hash = { };
$hash = { %stuff };
$hash = { "a" => 1 };
$hash = { "a" => 1, $b, $c, %stuff, @nonsense };
$code = { %_ }; # use of %_
$code = { "a" => $_ }; # use of $_
$code = { "a" => 1, $b, $c, %stuff, @_ }; # use of @_
$code = { ; };
$code = { @stuff };
$code = { "a", 1 };
$code = { "a" => 1, $b, $c ==> print };
If you wish to be less ambiguous, the hash list operator will explicitly evaluate a list and compose a hash of the returned value, while sub or -> introduces an anonymous subroutine:
From t/spec/S04-statement-parsing/hash.t lines 4–19: (skip)
-
| # L<S04/Statement parsing/the hash list operator>
|
|
|
| plan 7;
|
|
|
| #?rakudo todo 'Hash type (WTF?)'
|
| isa_ok hash('a', 1), Hash, 'hash() returns a Hash';
|
| is hash('a', 1).keys, 'a', 'hash() with keys/values (key)';
|
| is hash('a', 1).values, 1, 'hash() with keys/values (values)';
|
|
|
| is hash('a' => 1).keys, 'a', 'hash() with pair (key)';
|
| is hash('a' => 1).values, 1, 'hash() with pair (values)';
|
|
|
| is hash(a => 1).keys, 'a', 'hash() with autoquoted pair (key)';
|
| is hash(a => 1).values, 1, 'hash() with autoquoted pair (values)';
|
|
|
| # vim: ft=perl6
|
$code = -> { "a" => 1 };
$code = sub { "a" => 1 };
$hash = hash("a" => 1);
$hash = hash("a", 1);
Note that the closure in a map will never be interpreted as a hash, since such a closure always takes arguments, and use of placeholders (including underscore variables) is taken as evidence of arguments.
If a closure is the right argument of the dot operator, the closure is interpreted as a hash subscript.
$code = {$x}; # closure because term expected
if $term{$x} # subscript because postfix expected
if $term {$x} # expression followed by statement block
if $term.{$x} # valid subscript with dot
if $term\ {$x} # valid subscript with "unspace"
Similar rules apply to array subscripts:
$array = [$x]; # array composer because term expected
if $term[$x] # subscript because postfix expected
if $term [$x] # syntax error (two terms in a row)
if $term.[$x] # valid subscript with dot
if $term\ [$x] # valid subscript with "unspace"
And to the parentheses delimiting function arguments:
$scalar = ($x); # grouping parens because term expected
if $term($x) # function call because operator expected
if $term ($x) # syntax error (two terms in a row)
if $term.($x) # valid function call with explicit dot deref
if $term\ .($x) # valid function call with "unspace" and dot
Outside of any kind of expression brackets, a final closing curly on a line (not counting whitespace or comments) always reverts to the precedence of semicolon whether or not you put a semicolon after it. (In the absence of an explicit semicolon, the current statement may continue on a subsequent line, but only with valid statement continuators such as else that cannot be confused with the beginning of a new statement. Anything else, such as a statement modifier (on, say, a loop statement) must continue on the same line, unless the newline be escaped using the "unspace" construct--see S02.)
From t/spec/S04-statements/do.t lines 136–147: (skip)
-
| # L<S04/Statement parsing/"final closing curly on a line"
|
| # reverts to semicolon>
|
| {
|
| my $a = do {
|
| 1 + 2;
|
| } # no trailing `;'
|
| is $a, 3, "final `}' on a line reverted to `;'";
|
| }
|
|
|
| lives_ok { my $a = do given 5 {} }, 'empty do block lives (RT 61034)';
|
|
|
| # vim: ft=perl6
|
Final blocks on statement-level constructs always imply semicolon precedence afterwards regardless of the position of the closing curly. Statement-level constructs are distinguished in the grammar by being declared in the statement_control category:
macro statement_control:<if> ($expr, &ifblock) {...}
macro statement_control:<while> ($expr, &whileblock) {...}
macro statement_control:<BEGIN> (&beginblock) {...}
Statement-level constructs may start only where the parser is expecting the start of a statement. To embed a statement in an expression you must use something like do {...} or try {...}.
From t/spec/S04-statements/try.t lines 5–108: (skip)
-
| # L<S04/"Statement parsing"/"or try {...}">
|
|
|
| plan 17;
|
|
|
| {
|
| # simple try
|
| my $lived = Mu;
|
| try { die "foo" };
|
| ok($! ~~ /foo/, "error var was set");
|
| };
|
|
|
| # try should work when returning an array or hash
|
| {
|
| my @array = try { 42 };
|
| is +@array, 1, '@array = try {...} worked (1)';
|
| is ~@array, "42", '@array = try {...} worked (2)';
|
| }
|
|
|
| {
|
| my @array = try { (42,) };
|
| is +@array, 1, '@array = try {...} worked (3)';
|
| is ~@array, "42", '@array = try {...} worked (4)';
|
| }
|
|
|
| {
|
| my %hash = try { 'a', 1 };
|
| is +%hash, 1, '%hash = try {...} worked (1)';
|
| is ~%hash.keys, "a", '%hash = try {...} worked (2)';
|
| }
|
|
|
| {
|
| my %hash = try { hash("a", 1) };
|
| is +%hash, 1, '%hash = try {...} worked (5)';
|
| is ~%hash.keys, "a", '%hash = try {...} worked (6)';
|
| }
|
|
|
| #?pugs todo 'bug'
|
| {
|
| my %hash;
|
| # Extra try necessary because current Pugs dies without it.
|
| try { %hash = try { a => 3 } };
|
| is +%hash, 1, '%hash = try {...} worked (7)';
|
| is ~%hash.keys, "a", '%hash = try {...} worked (8)';
|
| is ~%hash<a>, 3, '%hash = try {...} worked (9)';
|
| }
|
|
|
| # return inside try{}-blocks
|
| # PIL2JS *seems* to work, but it does not, actually:
|
| # The "return 42" works without problems, and the caller actually sees the
|
| # return value 42. But when the end of the test is reached, &try will
|
| # **resume after the return**, effectively running the tests twice.
|
| # (Therefore I moved the tests to the end, so not all tests are rerun).
|
|
|
| #?rakudo todo "try catches return exception"
|
| {
|
| my $was_in_foo = 0;
|
| sub foo {
|
| $was_in_foo++;
|
| try { return 42 };
|
| $was_in_foo++;
|
| return 23;
|
| }
|
| is foo(), 42, 'return() inside try{}-blocks works (1)';
|
| is $was_in_foo, 1, 'return() inside try{}-blocks works (2)';
|
| }
|
|
|
| {
|
| sub test1 {
|
| try { return 42 };
|
| return 23;
|
| }
|
|
|
| sub test2 {
|
| test1();
|
| die 42;
|
| }
|
|
|
| dies_ok { test2() },
|
| 'return() inside a try{}-block should cause following exceptions to really die';
|
| }
|
|
|
| {
|
| sub argcount { return +@_ }
|
| is argcount( try { 17 }, 23, 99 ), 3, 'try gets a block, nothing more';
|
| }
|
|
|
|
|
| #?rakudo todo "CATCH doesn't catch its own exceptions"
|
| {
|
| my $catches = 0;
|
| try {
|
| try {
|
| die 'catch!';
|
| CATCH {
|
| die 'caught' if ! $catches++;
|
| }
|
| }
|
| }
|
| is $catches, 1, 'CATCH does not catch exceptions thrown within it';
|
| }
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
$x = do { given $foo { when 1 {2} when 3 {4} } } + $bar;
$x = try { given $foo { when 1 {2} when 3 {4} } } + $bar;
The existence of a statement_control:<BEGIN> does not preclude us from also defining a prefix:<BEGIN> that can be used within an expression:
macro prefix:<BEGIN> (&beginblock) { beginblock().repr }
Then you can say things like:
$recompile_by = BEGIN { time } + $expiration_time;
But statement_control:<BEGIN> hides prefix:<BEGIN> at the start of a statement. You could also conceivably define a prefix:<if>, but then you may not get what you want when you say:
die if $foo;
since prefix:<if> would hide statement_modifier:<if>.
From t/spec/S06-operator-overloading/sub.t lines 188–412: (skip)
-
| # L<S04/"Statement parsing" /"since prefix:<if> would hide statement_modifier:<if>">
|
| #?rakudo skip 'prefix:<if>'
|
| {
|
| my proto prefix:<if> ($a) { $a*2 }
|
| is (if+5), 10;
|
| }
|
|
|
| # [NOTE]
|
| # pmichaud ruled that infix<if> is incorrect:
|
| # http://colabti.de/irclogger/irclogger_log/perl6?date=2006-07-29,Sat&sel=183#l292
|
| # so we won't test it here either.
|
|
|
| # great. Now, what about those silent auto-conversion operators a la:
|
| # multi sub prefix:<+> (Str $x) returns Num { ... }
|
| # ?
|
|
|
| # I mean, + is all well and good for number classes. But what about
|
| # defining other conversions that may happen?
|
|
|
| # here is one that co-erces a MyClass into a Str and a Num.
|
| #?rakudo skip 'prefix:<~> method'
|
| {
|
| class OtherClass {
|
| has $.x is rw;
|
| }
|
|
|
| class MyClass {
|
| method prefix:<~> is export { "hi" }
|
| method prefix:<+> is export { 42 }
|
| method infix:<as>($self, OtherClass $to) is export {
|
| my $obj = $to.new;
|
| $obj.x = 23;
|
| return $obj;
|
| }
|
| }
|
|
|
| my $obj;
|
| lives_ok { $obj = MyClass.new }, "instantiation of a prefix:<...> and infix:<as> overloading class worked";
|
| my $try = lives_ok { ~$obj }, "our object was stringified correctly";
|
| if ($try) {
|
| is ~$obj, "hi", "our object was stringified correctly", :todo<feature>;
|
| } else {
|
| skip 1, "Stringification failed";
|
| };
|
| #?pugs todo 'feature'
|
| is eval('($obj as OtherClass).x'), 23, "our object was coerced correctly";
|
| }
|
|
|
| #?rakudo skip 'lexical operators'
|
| {
|
| my sub infix:<Z> ($a, $b) {
|
| $a ** $b;
|
| }
|
| is (2 Z 1 Z 2), 4, "default Left-associative works.";
|
| }
|
|
|
| #?rakudo skip 'lexical operators'
|
| {
|
| my sub infix:<Z> is assoc('left') ($a, $b) {
|
| $a ** $b;
|
| }
|
|
|
| is (2 Z 1 Z 2), 4, "Left-associative works.";
|
| }
|
|
|
| #?rakudo skip 'lexical operators'
|
| {
|
| my sub infix:<Z> is assoc('right') ($a, $b) {
|
| $a ** $b;
|
| }
|
|
|
| is (2 Z 1 Z 2), 2, "Right-associative works.";
|
| }
|
|
|
| #?rakudo skip 'lexical operators'
|
| {
|
| my sub infix:<Z> is assoc('chain') ($a, $b) {
|
| $a eq $b;
|
| }
|
|
|
|
|
| is (1 Z 1 Z 1), Bool::True, "Chain-associative works.";
|
| is (1 Z 1 Z 2), Bool::False, "Chain-associative works.";
|
| }
|
|
|
| #?rakudo skip 'assoc("non")'
|
| {
|
| sub infix:<our_non_assoc_infix> is assoc('non') ($a, $b) {
|
| $a ** $b;
|
| }
|
| is (2 our_non_assoc_infix 3), (2 ** 3), "Non-associative works for just tow operands.";
|
| is ((2 our_non_assoc_infix 2) our_non_assoc_infix 3), (2 ** 2) ** 3, "Non-associative works when used with parens.";
|
| eval_dies_ok '2 our_non_assoc_infix 3 our_non_assoc_infix 4', "Non-associative should not parsed when used chainly.";
|
| }
|
|
|
| {
|
| role A { has $.v }
|
| multi sub infix:<==>(A $a, A $b) { $a.v == $b.v }
|
| lives_ok { 3 == 3 or die() }, 'old == still works on integers (+)';
|
| lives_ok { 3 == 4 and die() }, 'old == still works on integers (-)';
|
| ok (A.new(v => 3) == A.new(v => 3)), 'infix:<==> on A objects works (+)';
|
| ok !(A.new(v => 2) == A.new(v => 3)), 'infix:<==> on A objects works (-)';
|
| }
|
|
|
| {
|
| sub circumfix:<<` `>>(*@args) { @args.join('-') }
|
| is `3, 4, "f"`, '3-4-f', 'slurpy circumfix:<<...>> works'
|
|
|
| }
|
|
|
| {
|
| multi sub infix:<+=> (Int $a is rw, Int $b) { $a -= $b }
|
| my $frew = 10;
|
| $frew += 5;
|
| is $frew, 5, 'infix redefinition of += works';
|
| }
|
|
|
| {
|
| class MMDTestType {
|
| has $.a is rw;
|
| method add(MMDTestType $b) { $.a ~ $b.a }
|
| }
|
|
|
| multi sub infix:<+>(MMDTestType $a, MMDTestType $b) { $a.add($b) };
|
|
|
| my MMDTestType $a .= new(a=>'foo');
|
| my MMDTestType $b .= new(a=>'bar');
|
|
|
| is $a + $b, 'foobar', 'can overload exiting operators (here: infix:<+>)';
|
| }
|
|
|
| # test that multis with other arity don't interfere with existing ones
|
| # used to be RT #65640
|
| {
|
| multi sub infix:<+>() { 42 };
|
| ok 5 + 5 == 10, "New multis don't disturb old ones";
|
| }
|
|
|
| # taken from S06-operator-overloading/method.t
|
| #?rakudo skip 'unknown errors'
|
| {
|
| class Bar {
|
| has $.bar is rw;
|
| }
|
|
|
| multi sub prefix:<~> (Bar $self) { return $self.bar }
|
| multi sub infix:<+> (Bar $a, Bar $b) { return "$a $b" }
|
|
|
| {
|
| my $val;
|
| lives_ok {
|
| my $foo = Bar.new();
|
| $foo.bar = 'software';
|
| $val = "$foo"
|
| }, '... class methods work for class';
|
| is($val, 'software', '... basic prefix operator overloading worked');
|
|
|
| lives_ok {
|
| my $foo = Bar.new();
|
| $foo.bar = 'software';
|
| $val = $foo + $foo;
|
| }, '... class methods work for class';
|
| is($val, 'software software', '... basic infix operator overloading worked');
|
| }
|
|
|
| # Test that the object is correctly stringified when it is in an array.
|
| # And test that »...« automagically work, too.
|
| {
|
| my $obj;
|
| lives_ok {
|
| $obj = Bar.new;
|
| $obj.bar = "pugs";
|
| }, "instantiating a class which defines operators worked";
|
|
|
| my @foo = ($obj, $obj, $obj);
|
| my $res;
|
| lives_ok { $res = ~@foo }, "stringification didn't die";
|
| is $res, "pugs pugs pugs", "stringification overloading worked in array stringification";
|
|
|
| lives_ok { $res = ~[@foo »~« "!"] }, "stringification with hyperization didn't die";
|
| is $res, "pugs! pugs! pugs!", "stringification overloading was hyperized correctly";
|
| }
|
|
|
|
|
| }
|
|
|
| # RT #65638
|
| {
|
| is eval('sub infix:<,>($a, $b) { 42 }; 5, 5'), 42, 'infix:<,>($a, $b)';
|
| is eval('sub infix:<,>(Int $x where 1, Int $y where 1) { 42 }; 1, 1'), 42,
|
| 'very specific infix:<,>';
|
| #?rakudo todo 'RT 65638'
|
| is eval('sub infix:<#>($a, $b) { 42 }; 5 # 5'), 42, 'infix:<comment char>($a, $b)';
|
| #?rakudo todo 'mixed overloaded operators of different arities'
|
| is eval('sub infix:<+>() { 42 }; 5 + 5'), 10, 'infix:<+>()';
|
| is eval('sub infix:<+>($a, $b) { 42 }; 5 + 5'), 42, 'infix:<+>($a, $b)';
|
| }
|
|
|
| #?rakudo skip 'not yet implemented'
|
| {
|
| multi sub infix:<foo>($a, $b) {$a + $b};
|
|
|
| my $x foo=6;
|
| is $x, 6, 'foo= works for custom operators';
|
| }
|
|
|
| #?rakudo skip 'not yet implemented'
|
| {
|
| our multi sub infix:<bar>($a, $b) {$a + $b};
|
|
|
| my $x bar=6;
|
| is $x, 6, 'bar= works for custom operators';
|
|
|
| }
|
|
|
| # RT #74104
|
| {
|
| class RT74104 {}
|
| multi sub infix:<+>(RT74104 $, RT74104 $) { -1 }
|
| is 2+2, 4, 'overloading an operator does not hide other candidates';
|
| }
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
Built-in statement-level keywords require whitespace between the keyword and the first argument, as well as before any terminating loop. In particular, a syntax error will be reported for C-isms such as these:
From t/spec/S04-statements/for.t lines 358–493: (skip)
-
| # L<S04/Statement parsing/keywords require whitespace>
|
| #?rakudo todo "for(0..5) should die because there is no space after the for"
|
| eval_dies_ok('for(0..5) { }','keyword needs at least one whitespace after it');
|
|
|
| # looping with more than one loop variables
|
| {
|
| my @a = <1 2 3 4>;
|
| my $str = '';
|
| for @a -> $x, $y {
|
| $str ~= $x+$y;
|
| }
|
| is $str, "37", "for loop with two variables";
|
| }
|
|
|
| {
|
| #my $str = '';
|
| eval_dies_ok('for 1..5 -> $x, $y { $str ~= "$x$y" }', 'Should throw exception StopIteration');
|
| #is $str, "1234", "loop ran before throwing exception";
|
| #diag ">$str<";
|
| }
|
|
|
| #?rakudo skip 'optional variable in for loop (RT #63994)'
|
| {
|
| my $str = '';
|
| for 1..5 -> $x, $y? {
|
| $str ~= " " ~ $x*$y;
|
| }
|
| is $str, " 2 12 0";
|
| }
|
|
|
| {
|
| my $str = '';
|
| for 1..5 -> $x, $y = 7 {
|
| $str ~= " " ~ $x*$y;
|
| }
|
| is $str, " 2 12 35", 'default values in for-loops';
|
| }
|
|
|
|
|
| {
|
| my @a = <1 2 3>;
|
| my @b = <4 5 6>;
|
| my $res = '';
|
| for @a Z @b -> $x, $y {
|
| $res ~= " " ~ $x * $y;
|
| }
|
| is $res, " 4 10 18", "Z -ed for loop";
|
| }
|
|
|
| #?rakudo skip "Z only works with 2 arrays at the moment"
|
| {
|
| my @a = <1 2 3>;
|
| my $str = '';
|
|
|
| for @a Z @a Z @a Z @a Z @a -> $q, $w, $e, $r, $t {
|
| $str ~= " " ~ $q*$w*$e*$r*$t;
|
| }
|
| is $str, " 1 {2**5} {3**5}", "Z-ed for loop with 5 arrays";
|
| }
|
|
|
| {
|
| eval_dies_ok 'for 1.. { };', "Please use ..* for indefinite range";
|
| eval_dies_ok 'for 1... { };', "1... does not exist";
|
| }
|
|
|
| {
|
| my $c;
|
| for 1..8 {
|
| $c = $_;
|
| last if $_ == 6;
|
| }
|
| is $c, 6, 'for loop ends in time using last';
|
| }
|
|
|
| {
|
| my $c;
|
| for 1..* {
|
| $c = $_;
|
| last if $_ == 6;
|
| }
|
| is $c, 6, 'infinte for loop ends in time using last';
|
| }
|
|
|
| {
|
| my $c;
|
| for 1..Inf {
|
| $c = $_;
|
| last if $_ == 6;
|
| }
|
| is $c, 6, 'infinte for loop ends in time using last';
|
| }
|
|
|
| # RT #62478
|
| {
|
| eval('for (my $ii = 1; $ii <= 3; $ii++) { say $ii; }');
|
| ok "$!" ~~ /C\-style/, 'mentions C-style';
|
| ok "$!" ~~ /for/, 'mentions for';
|
| ok "$!" ~~ /loop/, 'mentions loop';
|
| }
|
|
|
| # RT #65212
|
| {
|
| my $parsed = 0;
|
| eval '$parsed = 1; for (1..3)->$n { last }';
|
| ok ! $parsed, 'for (1..3)->$n fails to parse';
|
| }
|
|
|
| # RT #71268
|
| {
|
| sub rt71268 { for ^1 {} }
|
| lives_ok { ~(rt71268) }, 'can stringify "for ^1 {}" without death';
|
| ok rt71268() ~~ (), 'result of "for ^1 {}" is ()';
|
| }
|
|
|
| # RT 62478
|
| {
|
| eval_dies_ok 'for (my $i; $i <=3; $i++) { $i; }', 'Unsupported use of C-style "for (;;)" loop; in Perl 6 please use "loop (;;)"';
|
| }
|
| {
|
| eval 'for (my $x; $x <=3; $x++) { $i; }'; diag($!);
|
| ok $! ~~ / 'C-style' /, 'Sensible error message';
|
| }
|
|
|
| # RT #64886
|
| {
|
| my $a = 0;
|
| for (1..10000000000) {
|
| $a++;
|
| last;
|
| }
|
| is $a, 1, 'for on Range with huge max value is lazy and enters block';
|
| }
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
From t/spec/S04-statements/if.t lines 155–166: (skip)
-
| # L<S04/Statement parsing/keywords require whitespace>
|
| eval_dies_ok('if($x > 1) {}','keyword needs at least one whitespace after it');
|
|
|
| # RT #76174
|
| # scoping of $_ in 'if' shouldn't break aliasing
|
| {
|
| my @a = 0, 1, 2;
|
| for @a { if $_ { $_++ } };
|
| is ~@a, '0 2 3', '"if" does not break lexical aliasing of $_'
|
| }
|
|
|
| # vim: ft=perl6
|
From t/spec/S04-statements/while.t lines 71–78: (skip)
-
| # L<S04/Statement parsing/keywords require whitespace>
|
| {
|
| my $i = 0;
|
| eval_dies_ok('while($i < 5) { $i++; }',
|
| 'keyword needs at least one whitespace after it');
|
| }
|
|
|
| # vim: ft=perl6
|
if(...) {...}
while(...) {...}
for(...) {...}
From t/spec/S04-blocks-and-statements/let.t lines 8–64: (skip)
-
| # L<S04/Definition of Success>
|
| # let() should not restore the variable if the block exited successfully
|
| # (returned a true value).
|
| {
|
| my $a = 42;
|
| {
|
| is(eval('let $a = 23; $a'), 23, "let() changed the variable (1)");
|
| 1;
|
| }
|
| is $a, 23, "let() should not restore the variable, as our block exited succesfully (1)";
|
| }
|
|
|
| # let() should restore the variable if the block failed (returned a false
|
| # value).
|
| {
|
| my $a = 42;
|
| {
|
| is(eval('let $a = 23; $a'), 23, "let() changed the variable (1)");
|
| Mu;
|
| }
|
| is $a, 42, "let() should restore the variable, as our block failed";
|
| }
|
|
|
| # Test that let() restores the variable at scope exit, not at subroutine
|
| # entry. (This might be a possibly bug.)
|
| {
|
| my $a = 42;
|
| my $get_a = { $a };
|
| {
|
| is(eval('let $a = 23; $a'), 23, "let() changed the variable (2-1)");
|
| is $get_a(), 23, "let() changed the variable (2-2)";
|
| 1;
|
| }
|
| is $a, 23, "let() should not restore the variable, as our block exited succesfully (2)";
|
| }
|
|
|
| # Test that let() restores variable even when not exited regularly (using a
|
| # (possibly implicit) call to return()), but when left because of an exception.
|
| {
|
| my $a = 42;
|
| try {
|
| is(eval('let $a = 23; $a'), 23, "let() changed the variable in a try block");
|
| die 57;
|
| };
|
| is $a, 42, "let() restored the variable, the block was exited using an exception";
|
| }
|
|
|
| {
|
| my @array = (0, 1, 2);
|
| {
|
| is(eval('let @array[1] = 42; @array[1]'), 42, "let() changed our array element");
|
| Mu;
|
| }
|
| is @array[1], 1, "let() restored our array element";
|
| }
|
|
|
| # vim: ft=perl6
|
Hypothetical variables are somewhat transactional--they keep their new values only on successful exit of the current block, and otherwise are rolled back to their original values.
It is, of course, a failure to leave the block by propagating an error exception, though returning a defined value after catching an exception is okay.
In the absence of error exception propagation, a successful exit is one that returns a defined value or parcel. (A defined parcel may contain undefined values.) So any Perl 6 function can say
fail "message";
and not care about whether the function is being called in item or list context. To return an explicit scalar undef, you can always say
return Mu; # like "return undef" in Perl 5
Then in list context, you're returning a list of length 1, which is defined (much like in Perl 5). But generally you should be using fail in such a case to return an exception object. In any case, returning an unthrown exception is considered failure from the standpoint of let. Backtracking over a closure in a regex is also considered failure of the closure, which is how hypothetical variables are managed by regexes. (And on the flip side, use of fail within a regex closure initiates backtracking of the regex.)
Everything is conceptually a closure in Perl 6, but the optimizer is free to turn unreferenced closures into mere blocks of code. It is also free to turn referenced closures into mere anonymous subroutines if the block does not refer to any external lexicals that should themselves be cloned. (When we say "clone", we mean the way the system takes a snapshot of the routine's lexical scope and binds it to the current instance of the routine so that if you ever use the current reference to the routine, it gets the current snapshot of its world in terms of the lexical symbols that are visible to it.)
All remaining blocks are conceptually cloned into closures as soon as the lexical scope containing them is entered. (This may be done lazily as long as consistent semantics are preserved, so a block that is never executed and never has a reference taken can avoid cloning altogether. Execution or reference taking forces cloning in this case--references are not allowed to be lazily cloned, since no guarantee can be made that the scope needed for cloning will remain in existence over the life of the reference.)
In particular, package subroutines are a special problem when embedded in a changing lexical scope (when they make reference to it). The binding of such a definition to a name within a symbol table counts as taking a reference, so at compile time there is an initial binding to the symbol table entry in question. For "global" bindings to symbol tables visible at compile time, this binds to the compile-time view of the lexical scopes. (At run-time, the initial run-time view of these scopes is copied from the compiler's view of them, so that initializations carry over, for instance.) At run time, when such a subroutine is cloned, an additional binding is done at clone time to the same symbol table entry that the original was bound to. (The binding is not restored on exit from the current lexical scope; this binding records the last cloning, not the currently in-use cloning, so any use of the global reference must take into consideration that it is functioning only as a cache of the most recent cloning, not as a surrogate for the current lexical scope.)
Matters are more complicated if the package in question is lexically defined. In such cases, the package must be cloned as if it were a sub on entry to the corresponding lexical scope. All runtime instances of a single package declaration share the same set of compile-time declared functions, however, the runtime instances can have different lexical environments as described in the preceding paragraph. If multiple conflicting definitons of a sub exist for the same compile-time package, an error condition exists and behavior is not specified for Perl 6.0.
Methods in classes behave functionally like package subroutines, and have the same binding behavior if the classes are cloned. Note that a class declaration, even an augment, is fundamentally a compile-time operation; composition only happens once and the results are recorded in the prototype class. Runtime typological manipulations are limited to reseating OUTER:: scopes of methods.
Lexical names do not share this problem, since the symbol goes out of scope synchronously with its usage. Unlike global subs, they do not need a compile-time binding, but like global subs, they perform a binding to the lexical symbol at clone time (again, conceptually at the entry to the outer lexical scope, but possibly deferred.)
sub foo {
# conceptual cloning happens to both blocks below
my $x = 1;
my sub bar { print $x } # already conceptualy cloned, but can be lazily deferred
my &baz := { bar(); print $x }; # block is cloned immediately, forcing cloning of bar
my $code = &bar; # this would also force bar to be cloned
return &baz;
}
In particular, blocks of inline control flow need not be cloned until called. [Note: this is currently a potential problem for user-defined constructs, since you have to take references to blocks to pass them to whatever is managing the control flow. Perhaps the laziness can be deferred through Captures to binding time, so a slurpy of block refs doesn't clone them all prematurely. On the other hand, this either means the Capture must be smart enough to keep track of the lexical scope it came from so that it can pass the info to the cloner, or it means that we need some special fat not-cloned-yet references that can carry the info lazily. Neither approach is pretty.]
Some closures produce Block objects at compile time that cannot be cloned, because they're not attached to any runtime code that can actually clone them. BEGIN, CHECK, INIT, and END blocks fall into this category. Therefore you can't reliably refer to run-time variables from these closures even if they appear to be in the scope. (The compile-time closure may, in fact, see some kind of permanent copy of the variable for some storage classes, but the variable is likely to be undefined when the closure is run in any case.) It's only safe to refer to package variables and file-scoped lexicals from such a routine.
On the other hand, it is required that CATCH and LEAVE blocks be able to see transient variables in their current lexical scope, so their cloning status depends at least on the cloning status of the block they're in.
[ Top ]
[ Index of Synopses ]