From 6f7d9c1b2ba32f88113911b9b07ee7293869a3db Mon Sep 17 00:00:00 2001 From: Carl Masak Date: Sun, 2 Jun 2019 21:59:29 +0200 Subject: [PATCH 1/2] Implement next and last --- lib/_007/Parser/Actions.pm6 | 8 +++++++ lib/_007/Parser/Syntax.pm6 | 8 +++++++ lib/_007/Q.pm6 | 42 +++++++++++++++++++++++++++---------- lib/_007/Runtime.pm6 | 15 +++++++++++++ t/features/for-loop.t | 26 +++++++++++++++++++++++ t/features/while-loop.t | 28 +++++++++++++++++++++++++ 6 files changed, 116 insertions(+), 11 deletions(-) diff --git a/lib/_007/Parser/Actions.pm6 b/lib/_007/Parser/Actions.pm6 index a0e73381..ffe30434 100644 --- a/lib/_007/Parser/Actions.pm6 +++ b/lib/_007/Parser/Actions.pm6 @@ -176,6 +176,14 @@ class _007::Parser::Actions { make Q::Statement::Throw.new(:$expr); } + method statement:next ($/) { + make Q::Statement::Next.new(); + } + + method statement:last ($/) { + make Q::Statement::Last.new(); + } + method statement:if ($/) { my %parameters = $.ast; %parameters = ast-if-any($); diff --git a/lib/_007/Parser/Syntax.pm6 b/lib/_007/Parser/Syntax.pm6 index 0d42874d..c42ba911 100644 --- a/lib/_007/Parser/Syntax.pm6 +++ b/lib/_007/Parser/Syntax.pm6 @@ -81,6 +81,14 @@ grammar _007::Parser::Syntax { throw» [<.ws> ]? } + token statement:next { + next» + } + + token statement:last { + last» + } + token statement:if { if» <.ws> [ <.ws> else <.ws> diff --git a/lib/_007/Q.pm6 b/lib/_007/Q.pm6 index 5db1beb9..7d401804 100644 --- a/lib/_007/Q.pm6 +++ b/lib/_007/Q.pm6 @@ -880,12 +880,7 @@ class Q::Statement::If does Q::Statement { die X::ParameterMismatch.new( :type("Else block"), :$paramcount, :argcount("0 or 1")) if $paramcount > 1; - $runtime.enter($runtime.current-frame, $.else.static-lexpad, $.else.statementlist); - for @($.else.parameterlist.parameters.elements) Z $expr -> ($param, $arg) { - $runtime.declare-var($param.identifier, $arg); - } - $.else.statementlist.run($runtime); - $runtime.leave; + $runtime.run-block($.else, [$expr]); } } } @@ -900,9 +895,7 @@ class Q::Statement::Block does Q::Statement { has $.block; method run($runtime) { - $runtime.enter($runtime.current-frame, $.block.static-lexpad, $.block.statementlist); - $.block.statementlist.run($runtime); - $runtime.leave; + $runtime.run-block($.block, []); } } @@ -935,8 +928,11 @@ class Q::Statement::For does Q::Statement { unless $array ~~ Val::Array; for $array.elements -> $arg { - $runtime.run-block($.block, $count ?? [$arg] !! []); + $runtime.run-block($.block, [$arg]); + last if $runtime.last-triggered; + $runtime.reset-triggers(); } + $runtime.reset-triggers(); } } @@ -956,8 +952,11 @@ class Q::Statement::While does Q::Statement { die X::ParameterMismatch.new( :type("While loop"), :$paramcount, :argcount("0 or 1")) if $paramcount > 1; - $runtime.run-block($.block, $paramcount ?? [$expr] !! []); + $runtime.run-block($.block, [$expr]); + last if $runtime.last-triggered; + $runtime.reset-triggers(); } + $runtime.reset-triggers(); } } @@ -993,6 +992,26 @@ class Q::Statement::Throw does Q::Statement { } } +### ### Q::Statement::Next +### +### A `next` statement. +### +class Q::Statement::Next does Q::Statement { + method run($runtime) { + $runtime.trigger-next(); + } +} + +### ### Q::Statement::Last +### +### A `last` statement. +### +class Q::Statement::Last does Q::Statement { + method run($runtime) { + $runtime.trigger-last(); + } +} + ### ### Q::Statement::Func ### ### A subroutine declaration statement. @@ -1060,6 +1079,7 @@ class Q::StatementList does Q { method run($runtime) { for $.statements.elements -> $statement { my $value = $statement.run($runtime); + last if $runtime.next-triggered || $runtime.last-triggered; LAST if $statement ~~ Q::Statement::Expr { return $value; } diff --git a/lib/_007/Runtime.pm6 b/lib/_007/Runtime.pm6 index d5604ec3..5af2629f 100644 --- a/lib/_007/Runtime.pm6 +++ b/lib/_007/Runtime.pm6 @@ -32,6 +32,8 @@ class _007::Runtime { has $!prompt-builtin; has $!exit-builtin; has $.exit-code; + has $.next-triggered; + has $.last-triggered; submethod BUILD(:$!input, :$!output, :@!arguments) { $!builtin-opscope = opscope(); @@ -262,6 +264,19 @@ class _007::Runtime { $value || NONE } + method trigger-next() { + $!next-triggered = True; + } + + method trigger-last() { + $!last-triggered = True; + } + + method reset-triggers() { + $!next-triggered = False; + $!last-triggered = False; + } + method property($obj, Str $propname) { sub builtin(&fn) { my $name = &fn.name; diff --git a/t/features/for-loop.t b/t/features/for-loop.t index ea436659..75c6e9bd 100644 --- a/t/features/for-loop.t +++ b/t/features/for-loop.t @@ -55,4 +55,30 @@ use _007::Test; outputs $program, ".\n.\n.\n", "can loop over variable, not just literal array"; } +{ + my $program = q:to/./; + for [1, 2, 3, 4] -> n { + if n %% 2 { + next; + } + say(n); + } + . + + outputs $program, "1\n3\n", "`next` can skip to the next iteration of a `for` loop"; +} + +{ + my $program = q:to/./; + for [1, 2, 3, 4] -> n { + say(n); + if n == 3 { + last; + } + } + . + + outputs $program, "1\n2\n3\n", "`last` can abort a `for` loop early"; +} + done-testing; diff --git a/t/features/while-loop.t b/t/features/while-loop.t index 1f69d283..0000ba15 100644 --- a/t/features/while-loop.t +++ b/t/features/while-loop.t @@ -63,4 +63,32 @@ use _007::Test; "while loops don't accept more than one parameter"; } +{ + my $program = q:to/./; + my n = 4; + while (n = n - 1) > 0 { + if n %% 2 { + next; + } + say(n); + } + . + + outputs $program, "3\n1\n", "`next` can skip to the next iteration of a `while` loop"; +} + +{ + my $program = q:to/./; + my n = 7; + while (n = n - 1) > 0 { + say(n); + if n == 3 { + last; + } + } + . + + outputs $program, "6\n5\n4\n3\n", "`last` can abort a `while` loop early"; +} + done-testing; From c041c180276c4ba2ac7ef200de7a3e9732b5d6d0 Mon Sep 17 00:00:00 2001 From: Carl Masak Date: Sun, 2 Jun 2019 22:20:47 +0200 Subject: [PATCH 2/2] Prevent using `next`/`last` outside a loop --- lib/_007/Parser.pm6 | 3 ++- lib/_007/Parser/Actions.pm6 | 16 +++++++++--- lib/_007/Parser/Syntax.pm6 | 15 +++++++---- t/features/for-loop.t | 50 +++++++++++++++++++++++++++++++++++++ 4 files changed, 75 insertions(+), 9 deletions(-) diff --git a/lib/_007/Parser.pm6 b/lib/_007/Parser.pm6 index 712e036e..a01fcac1 100644 --- a/lib/_007/Parser.pm6 +++ b/lib/_007/Parser.pm6 @@ -15,7 +15,8 @@ class _007::Parser { method parse($program, Bool :$*unexpanded) { my %*assigned; my @*declstack; - my $*in_routine = False; + my $*in-routine = False; + my $*in-loop = False; my $*parser = self; my $*runtime = $!runtime; @!checks = (); diff --git a/lib/_007/Parser/Actions.pm6 b/lib/_007/Parser/Actions.pm6 index ffe30434..0402d3df 100644 --- a/lib/_007/Parser/Actions.pm6 +++ b/lib/_007/Parser/Actions.pm6 @@ -166,7 +166,7 @@ class _007::Parser::Actions { method statement:return ($/) { die X::ControlFlow::Return.new - unless $*in_routine; + unless $*in-routine; my $expr = ast-if-any($); make Q::Statement::Return.new(:$expr); } @@ -177,10 +177,14 @@ class _007::Parser::Actions { } method statement:next ($/) { + die X::ControlFlow.new + unless $*in-loop; make Q::Statement::Next.new(); } method statement:last ($/) { + die X::ControlFlow.new + unless $*in-loop; make Q::Statement::Last.new(); } @@ -192,11 +196,17 @@ class _007::Parser::Actions { } method statement:for ($/) { - make Q::Statement::For.new(|$.ast); + make Q::Statement::For.new( + expr => $.ast, + block => $.ast, + ); } method statement:while ($/) { - make Q::Statement::While.new(|$.ast); + make Q::Statement::While.new( + expr => $.ast, + block => $.ast, + ); } method statement:BEGIN ($/) { diff --git a/lib/_007/Parser/Syntax.pm6 b/lib/_007/Parser/Syntax.pm6 index c42ba911..0aa76b0a 100644 --- a/lib/_007/Parser/Syntax.pm6 +++ b/lib/_007/Parser/Syntax.pm6 @@ -57,7 +57,8 @@ grammar _007::Parser::Syntax { token statement:block { } rule statement:func-or-macro { [export\s+]?$=(func|macro)» [ || <.panic("identifier")>] - :my $*in_routine = True; + :my $*in-routine = True; + :my $*in-loop = False; { declare($ eq "func" ?? Q::Statement::Func @@ -100,10 +101,14 @@ grammar _007::Parser::Syntax { } token statement:for { - for» <.ws> + for» <.ws> + :my $*in-loop = True; + } token statement:while { - while» <.ws> + while» <.ws> + :my $*in-loop = True; + } token statement:BEGIN { BEGIN» <.ws> @@ -274,7 +279,7 @@ grammar _007::Parser::Syntax { } token term:func { func» <.ws> ? - :my $*in_routine = True; + :my $*in-routine = True; <.newpad> { if $ { @@ -308,7 +313,7 @@ grammar _007::Parser::Syntax { rule property:method { '(' ~ ')' [ - :my $*in_routine = True; + :my $*in-routine = True; <.newpad> ] diff --git a/t/features/for-loop.t b/t/features/for-loop.t index 75c6e9bd..749ad791 100644 --- a/t/features/for-loop.t +++ b/t/features/for-loop.t @@ -81,4 +81,54 @@ use _007::Test; outputs $program, "1\n2\n3\n", "`last` can abort a `for` loop early"; } +{ + my $program = q:to/./; + next; + . + + parse-error + $program, + X::ControlFlow, + "cannot `next` outside of loop"; +} + +{ + my $program = q:to/./; + last; + . + + parse-error + $program, + X::ControlFlow, + "cannot `last` outside of loop"; +} + +{ + my $program = q:to/./; + if 42 { + next; + } + . + + parse-error + $program, + X::ControlFlow, + "cannot `next` outside of loop -- `if` statements don't count"; +} + +{ + my $program = q:to/./; + for [1, 2, 3] { + func foo() { + next; + } + } + . + + parse-error + $program, + X::ControlFlow, + "cannot `next` outside of loop -- functions are opaque to loops"; +} + done-testing;