Skip to content

Commit

Permalink
Merge pull request #528 from masak/masak/next-last
Browse files Browse the repository at this point in the history
Implement `next` and `last`
  • Loading branch information
Claes-Magnus authored Jun 3, 2019
2 parents 11f4075 + c041c18 commit aa1f378
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 20 deletions.
3 changes: 2 additions & 1 deletion lib/_007/Parser.pm6
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ();
Expand Down
24 changes: 21 additions & 3 deletions lib/_007/Parser/Actions.pm6
Original file line number Diff line number Diff line change
Expand Up @@ -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($<EXPR>);
make Q::Statement::Return.new(:$expr);
}
Expand All @@ -176,6 +176,18 @@ class _007::Parser::Actions {
make Q::Statement::Throw.new(:$expr);
}

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();
}

method statement:if ($/) {
my %parameters = $<xblock>.ast;
%parameters<else> = ast-if-any($<else>);
Expand All @@ -184,11 +196,17 @@ class _007::Parser::Actions {
}

method statement:for ($/) {
make Q::Statement::For.new(|$<xblock>.ast);
make Q::Statement::For.new(
expr => $<EXPR>.ast,
block => $<pblock>.ast,
);
}

method statement:while ($/) {
make Q::Statement::While.new(|$<xblock>.ast);
make Q::Statement::While.new(
expr => $<EXPR>.ast,
block => $<pblock>.ast,
);
}

method statement:BEGIN ($/) {
Expand Down
23 changes: 18 additions & 5 deletions lib/_007/Parser/Syntax.pm6
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ grammar _007::Parser::Syntax {
token statement:block { <pblock> }
rule statement:func-or-macro {
[export\s+]?$<routine>=(func|macro)» [<identifier> || <.panic("identifier")>]
:my $*in_routine = True;
:my $*in-routine = True;
:my $*in-loop = False;
{
declare($<routine> eq "func"
?? Q::Statement::Func
Expand All @@ -81,6 +82,14 @@ grammar _007::Parser::Syntax {
throw» [<.ws> <EXPR>]?
}

token statement:next {
next»
}

token statement:last {
last»
}

token statement:if {
if» <.ws> <xblock>
[ <.ws> else <.ws>
Expand All @@ -92,10 +101,14 @@ grammar _007::Parser::Syntax {
}

token statement:for {
for» <.ws> <xblock>
for» <.ws> <EXPR>
:my $*in-loop = True;
<pblock>
}
token statement:while {
while» <.ws> <xblock>
while» <.ws> <EXPR>
:my $*in-loop = True;
<pblock>
}
token statement:BEGIN {
BEGIN» <.ws> <statement>
Expand Down Expand Up @@ -266,7 +279,7 @@ grammar _007::Parser::Syntax {
}
token term:func {
func» <.ws> <identifier>?
:my $*in_routine = True;
:my $*in-routine = True;
<.newpad>
{
if $<identifier> {
Expand Down Expand Up @@ -300,7 +313,7 @@ grammar _007::Parser::Syntax {
rule property:method {
<identifier>
'(' ~ ')' [
:my $*in_routine = True;
:my $*in-routine = True;
<.newpad>
<parameterlist>
]
Expand Down
42 changes: 31 additions & 11 deletions lib/_007/Q.pm6
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
}
}
}
Expand All @@ -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, []);
}
}

Expand Down Expand Up @@ -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();
}
}

Expand All @@ -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();
}
}

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;
}
Expand Down
15 changes: 15 additions & 0 deletions lib/_007/Runtime.pm6
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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;
Expand Down
76 changes: 76 additions & 0 deletions t/features/for-loop.t
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,80 @@ 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";
}

{
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;
28 changes: 28 additions & 0 deletions t/features/while-loop.t
Original file line number Diff line number Diff line change
Expand Up @@ -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;

0 comments on commit aa1f378

Please sign in to comment.