Skip to content

Commit

Permalink
Add support for labeled continue.
Browse files Browse the repository at this point in the history
  • Loading branch information
deadalnix committed Oct 31, 2024
1 parent 95f50d9 commit ae0f5a4
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 13 deletions.
14 changes: 12 additions & 2 deletions src/d/ast/statement.d
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,13 @@ class ForeachRangeStatement : LoopStatement {
* continue statements
*/
class ContinueStatement : Statement {
this(Location location) {
import source.name;
Name label;

this(Location location, Name label) {
super(location);

this.label = label;
}
}

Expand Down Expand Up @@ -240,8 +245,13 @@ class CaseStatement : Statement {
* break statements
*/
class BreakStatement : Statement {
this(Location location) {
import source.name;
Name label;

this(Location location, Name label) {
super(location);

this.label = label;
}
}

Expand Down
17 changes: 13 additions & 4 deletions src/d/parser/statement.d
Original file line number Diff line number Diff line change
Expand Up @@ -180,22 +180,31 @@ Statement parseStatement(ref TokenRange trange) {
return new ReturnStatement(location.spanTo(trange.previous), value);

case Break:
import source.name;
Name label;

trange.popFront();
if (trange.front.type == Identifier) {
label = trange.front.name;
trange.popFront();
}

trange.match(Semicolon);
return new BreakStatement(location.spanTo(trange.previous));
return new BreakStatement(location.spanTo(trange.previous), label);

case Continue:
import source.name;
Name label;

trange.popFront();
if (trange.front.type == Identifier) {
label = trange.front.name;
trange.popFront();
}

trange.match(Semicolon);
return new ContinueStatement(location.spanTo(trange.previous));
return
new ContinueStatement(location.spanTo(trange.previous), label);

case Switch:
trange.popFront();
Expand Down Expand Up @@ -237,10 +246,10 @@ Statement parseStatement(ref TokenRange trange) {
statement);

case Goto:
trange.popFront();

import source.name;
Name label;

trange.popFront();
switch (trange.front.type) {
case Identifier:
case Default:
Expand Down
53 changes: 46 additions & 7 deletions src/d/semantic/statement.d
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ private:
Label continueLabel;

Label[Name] labels;
Label[Name] namedContinues;

// Forward goto can only be resolved when the label is reached.
struct UnresolvedGoto {
Expand Down Expand Up @@ -560,12 +561,17 @@ public:
return maybeBranchTo(location, l.block);
}

Label getBreakLabel(Location location) {
Label getBreakLabel(Location location, Name name = BuiltinName!"") {
if (name != BuiltinName!"") {
import source.exception;
throw new CompileException(
location, "Labeled break statement are not supported.");
}

if (breakLabel) {
return breakLabel;
}

Name name;
final switch (breakKind) with (BreakKind) {
case None:
import source.exception;
Expand All @@ -586,10 +592,21 @@ public:
}

void visit(BreakStatement s) {
unwindAndBranch(s.location, getBreakLabel(s.location));
unwindAndBranch(s.location, getBreakLabel(s.location, s.label));
}

Label getContinueLabel(Location location) {
Label getContinueLabel(Location location, Name name) {
if (name != BuiltinName!"") {
if (auto cPtr = name in namedContinues) {
return *cPtr;
}

import source.exception;
throw new CompileException(
location, "No corresponding label for this continue statement."
);
}

if (continueLabel) {
return continueLabel;
}
Expand All @@ -600,11 +617,11 @@ public:
}

void visit(ContinueStatement s) {
unwindAndBranch(s.location, getContinueLabel(s.location));
unwindAndBranch(s.location, getContinueLabel(s.location, s.label));
}

void visit(ReturnStatement s) {
// TODO: precompute autotype instead of managing it here.
// TODO: precompute auto type instead of managing it here.
auto rt = returnType.getType();
auto isAutoReturn = rt.kind == TypeKind.Builtin
&& rt.qualifier == TypeQualifier.Mutable
Expand Down Expand Up @@ -847,7 +864,12 @@ public:
labelStacks[name] = varStack;

fixupGoto(s.location, name, label);
visit(s.statement);

if (auto ls = cast(LoopStatement) s.statement) {
LoopVisitor(&this, name).visit(ls);
} else {
visit(s.statement);
}
}

void visit(GotoStatement s) {
Expand Down Expand Up @@ -1304,6 +1326,8 @@ struct LoopVisitor {
StatementVisitor* pass;
alias pass this;

Name label;

void visit(LoopStatement s) {
// FIXME: Remove fallback.
this.dispatch(s);
Expand Down Expand Up @@ -1363,6 +1387,21 @@ struct LoopVisitor {
continueLabel = Label(incBlock, cast(uint) unwindActions.length);
breakLabel = Label(BasicBlockRef.init, cast(uint) unwindActions.length);

/**
* We could do the same for break statement, but this is more complex.
* At this stage, we do not know if the break point is reachable or not,
* and by the time we compute that, only the inner loop is available.
*/
if (label != BuiltinName!"") {
namedContinues[label] = continueLabel;
}

scope(exit) {
if (label != BuiltinName!"") {
namedContinues.remove(label);
}
}

if (element !is null) {
currentBlock.alloca(element.location, element);
}
Expand Down
32 changes: 32 additions & 0 deletions test/unit/loop.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
unittest labeled_continue {
uint k, m;
Outer: foreach (i; 0 .. 42) {
k = i;
foreach (j; 0 .. 42) {
if (k > 3) {
continue Outer;
}

m++;
}
}

assert(k == 41);
assert(m == 168);
}

/*
unittest labeled_break {
uint k;
Outer: foreach (i; 0 .. 42) {
k = i;
foreach (j; 0 .. 42) {
if (i > 3) {
break Outer;
}
}
}
assert(k == 4);
}
// */

0 comments on commit ae0f5a4

Please sign in to comment.