# continuations. plan(22); { # unique objects my $A := "A"; my $B := "B"; my int $run; my $res := nqp::continuationreset(nqp::null(), { $run++; 42; }); ok($run == 1, 'reset runs argument once'); ok($res == 42, 'reset passes return value'); my $ex := ''; $run := 0; # These tests are problematic. Currently the continuation bubbles up to the nearest # continuation barrier in invokeMain and dies there, with no chance to catch the # resulting exception # try { # nqp::continuationcontrol(0, nqp::null(), { $run++ }); # CATCH { $ex := $! } # } # ok( $ex ~~ /'no matching'/, 'shift dies with no reset' ); # ok( $run == 0, '...without running argument' ); # $ex := ''; # try { # nqp::continuationreset($A, { # nqp::continuationcontrol(0, $B, { $run++ }); # }); # CATCH { $ex := $! } # } # ok( $ex ~~ /'no matching'/, 'control dies with mismatched reset' ); my str $log := ''; $log := $log ~ 1; my $savecont; my $shift_returned; my $reset_returned := nqp::continuationreset(nqp::null(), { $log := $log ~ 2; $shift_returned := nqp::continuationcontrol(0, nqp::null(), -> $cont { $log := $log ~ 3; $savecont := $cont; 10; }); $log := $log ~ 4; $shift_returned * 2; }); ok( $log eq '123', 'control returns from reset' ); ok( $savecont, 'got a continuation' ); ok( $reset_returned == 10, 'shift argument return value is reset return value' ); $log := ''; $reset_returned := nqp::continuationinvoke($savecont, { $log := $log ~ '5'; 15 }); ok( $log eq '54', 'continuation invoke "returns" from control'); ok( $shift_returned == 15, 'continuation invoke argument is shift return value' ); ok( $reset_returned == 30, 'reset block return value is continuation invoke return value' ); ok( nqp::continuationinvoke($savecont, {25}) == 50, 'continuation can be used more than once' ); ok( nqp::continuationreset($A, { nqp::continuationcontrol(0, $A, -> $c { 42 }); 10; }) == 42, 'control may have a matching argument' ); ok( nqp::continuationreset($A, { nqp::continuationreset($B, { nqp::continuationcontrol(0, $A, -> $c { 42 }); 10; }); 11; }) == 42, 'control may match two levels up' ); ok( nqp::continuationreset($A, { nqp::continuationreset($B, { nqp::continuationcontrol(0, $B, -> $c { 42 }); 10; }) * 3; }) == 126, 'control may match one level up' ); ok( nqp::continuationreset($A, { nqp::continuationreset($B, { nqp::continuationcontrol(0, nqp::null(), -> $c { 42 }); 10; }) * 3; }) == 126, 'null shift matches innermost level'); my $cont := nqp::continuationreset($A, { nqp::continuationreset($B, { my $v := nqp::continuationcontrol(0, $A, -> $c { $c }); nqp::continuationcontrol(0, $B, -> $c { $v * 2 }); 14; }) * 3; }); ok(nqp::continuationinvoke($cont, {10}) == 60, 'continuations can capture reset frames'); my $*A := 10; my int $A_in_reset; my int $A_in_control; my int $A_in_invoke; my int $B_in_invoke; $cont := nqp::continuationreset($A, { $A_in_reset := $*A; { my $*A := 30; nqp::continuationcontrol(0, $A, -> $c { $A_in_control := $*A; $c }); } }); { my $*B := 20; my $*A := 40; nqp::continuationinvoke($cont, { $A_in_invoke := $*A; $B_in_invoke := $*B; }); } ok($A_in_reset == 10, 'reset sees calling scope'); ok($A_in_control == 10, 'control does not see intervening scopes'); ok($A_in_invoke == 30, 'invoke sees the continuation body'); ok($B_in_invoke == 20, 'invoke also sees the invocation context'); } { # should be 3 * 3 * 10 = 90 # will infinite loop if the clones are removed my $cont := nqp::continuationreset(nqp::null(), { 3 * nqp::continuationcontrol(0, nqp::null(), -> $k { $k }); }); my $val := nqp::continuationinvoke(nqp::continuationclone($cont), { nqp::continuationinvoke(nqp::continuationclone($cont), { 10 }) }); ok($val == 90, "restacking cont twice with clones works"); } # scheme call/cc example: amby thing, needs to use control, not control0 { # for proper R5RS semantics, run this once wrapping your main function sub run_main($f) { nqp::continuationreset(nqp::null(), $f); } sub callcc($f) { # first get the current continuation nqp::continuationcontrol(1, nqp::null(), -> $dcont { my $scheme_cont := -> $val { # when the scheme continuation is invoked, we need to *replace* # the current continuation with this one nqp::continuationcontrol(1, nqp::null(), -> $c { nqp::continuationinvoke($dcont, { $val }) }); }; nqp::continuationinvoke($dcont, { $f($scheme_cont) }); }); } my $backtrack_cont := sub ($ig) { nqp::die("Out of options!"); }; sub amb(*@options) { my $old_back := $backtrack_cont; my $here; callcc(-> $c { $here := $c }); while @options { my $next := nqp::shift(@options); $backtrack_cont := $here; return $next; } $old_back(0); } # 4-queens my $queens := run_main({ sub check($a, $b, $dist) { amb() if $a-$b == $dist || $a-$b == -$dist || $a == $b } my $q1 := amb(1,2,3,4); my $q2 := amb(1,2,3,4); check($q1,$q2,1); my $q3 := amb(1,2,3,4); check($q1,$q3,2); check($q2,$q3,1); my $q4 := amb(1,2,3,4); check($q1,$q4,3); check($q2,$q4,2); check($q3,$q4,1); "$q1$q2$q3$q4"; }); ok($queens eq '2413', 'n-queens callcc example'); } # gather/take example { my $SENTINEL := []; sub yield($value) { nqp::continuationcontrol(0, nqp::null(), -> $dcont { [$value, { nqp::continuationinvoke($dcont, {0}) }] }); } sub start_iter($body) { my $state := { $body(); yield($SENTINEL) }; -> { my $pkt := nqp::continuationreset(nqp::null(), $state); $state := $pkt[1]; $pkt[0]; } } sub mktree($l,$base) { $l ?? [ mktree($l-1,$base~0), mktree($l-1,$base~1) ] !! $base; } sub walktree($tree) { if nqp::istype($tree, NQPArray) { for $tree { walktree($_) } } else { yield($tree); } } sub uniter($block) { my $iter := start_iter($block); my $rv; my @list; until ($rv := $iter()) =:= $SENTINEL { nqp::push(@list, $rv); } nqp::join('~',@list); } ok(uniter({ walktree(mktree(3,'')) }) eq '000~001~010~011~100~101~110~111', 'gather example works'); sub exntest() { nqp::die("foo"); nqp::die("bar"); nqp::die("baz"); CATCH { yield(nqp::getmessage($!)); nqp::resume($!); } } ok(uniter(&exntest) eq 'foo~bar~baz', 'take from handler works'); }