Skip to content

Commit c206f5a

Browse files
committed
Support continue within foreach (on sequences)
1 parent 0b87d72 commit c206f5a

File tree

5 files changed

+52
-9
lines changed

5 files changed

+52
-9
lines changed

RELEASENOTES-1.4.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,3 +330,5 @@
330330
signatures of unused `shared` methods were not properly checked, leading to
331331
invalid generated C.
332332
- `release 6 6329`
333+
- `note 6` `continue` can now be used within `foreach` loops (use within
334+
`#foreach` loops remains unsupported)

doc/1.4/language.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3784,7 +3784,8 @@ within the body.
37843784
DML currently only supports `foreach` iteration on values of `sequence` types
37853785
— which are created through [Each-In expressions](#each-in-expressions).
37863786

3787-
The `break` statement can be used within a `foreach` loop to exit it.
3787+
The `continue` statement can be used within a `foreach` loop to continue to the
3788+
next element, and the `break` statement can be used to exit the loop.
37883789

37893790
<pre>
37903791
#foreach <em>identifier</em> in (<em>expr</em>) <em>statement</em>

py/dml/codegen.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,18 +104,26 @@ def __enter__(self):
104104
def __exit__(self, exc_type, exc_val, exc_tb):
105105
assert LoopContext.current is self
106106
LoopContext.current = self.prev
107+
@abstractmethod
108+
def break_(self, site): pass
109+
def continue_(self, site):
110+
raise ECONTU(site)
107111

108112
class CLoopContext(LoopContext):
109113
'''DML loop context corresponding to a C loop'''
110114
def break_(self, site):
111115
return [mkBreak(site)]
116+
def continue_(self, site):
117+
return [mkContinue(site)]
112118

113119
class NoLoopContext(LoopContext):
114120
'''DML loop context corresponding to an inlined method call.
115121
This is used to delimit the loop stack between the method containing
116122
the inline method call and the method called.'''
117123
def break_(self, site):
118124
raise EBREAK(site)
125+
def continue_(self, site):
126+
raise ECONT(site)
119127

120128
class GotoLoopContext(LoopContext):
121129
'''DML loop context not directly corresponding to a single C loop
@@ -131,6 +139,13 @@ def break_(self, site):
131139
self.used = True
132140
return [mkGotoBreak(site, self.label)]
133141

142+
class ForeachSequenceLoopContext(GotoLoopContext):
143+
'''DML loop context corresponding to a foreach loop on an each-in sequence
144+
Uses of `break` is codegen:d as a goto past the outer loop, and `continue`
145+
is codegen:d as a `continue` of the inner loop'''
146+
def continue_(self, site):
147+
return [mkContinue(site)]
148+
134149
class Failure(ABC):
135150
'''Handle exceptions failure handling is supposed to handle the various kind of
136151
functions that are generated, with different ways of signaling
@@ -3065,7 +3080,7 @@ def foreach_each_in(site, itername, trait, each_in,
30653080
inner_scope.add_variable(
30663081
itername, type=trait_type, site=site,
30673082
init=ForeachSequence.itervar_initializer(site, trait))
3068-
context = GotoLoopContext()
3083+
context = ForeachSequenceLoopContext()
30693084
with context:
30703085
inner_body = mkCompound(site, declarations(inner_scope)
30713086
+ codegen_statements([body_ast], location, inner_scope))
@@ -3261,13 +3276,10 @@ def stmt_switch(stmt, location, scope):
32613276

32623277
@statement_dispatcher
32633278
def stmt_continue(stmt, location, scope):
3264-
if (LoopContext.current is None
3265-
or isinstance(LoopContext.current, NoLoopContext)):
3279+
if LoopContext.current is None:
32663280
raise ECONT(stmt.site)
3267-
elif isinstance(LoopContext.current, CLoopContext):
3268-
return [mkContinue(stmt.site)]
32693281
else:
3270-
raise ECONTU(stmt.site)
3282+
return LoopContext.current.continue_(stmt.site)
32713283

32723284
@statement_dispatcher
32733285
def stmt_break(stmt, location, scope):

py/dml/messages.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -956,8 +956,8 @@ class ECONT(DMLError):
956956

957957
class ECONTU(DMLError):
958958
"""
959-
A `continue` statement cannot be used in a `foreach`
960-
or `select` statement.
959+
A `continue` statement cannot be used in a `#foreach`
960+
or `#select` statement.
961961
"""
962962
fmt = "continue is not possible here"
963963

test/1.4/expressions/T_each_in.dml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,16 @@ group test is t2 {
3131
group sub is t2;
3232
}
3333

34+
template t3 {}
35+
36+
group g1 is t3;
37+
38+
group g2[i < 2][j < 3] is t3;
39+
40+
group p[i < 2][j < 3] {
41+
group g3[k < 5] is t3;
42+
}
43+
3444
method init() {
3545
local bool found = false;
3646
foreach obj in (each tr in (a.b[0].d)) {
@@ -75,4 +85,22 @@ method init() {
7585
assert test.name == dev.name;
7686
assert (each t2 in (test)).len == 1;
7787
assert (each t2 in (dev)).len == 1;
88+
89+
local uint32 count = 0;
90+
foreach g in (each t3 in (dev)) {
91+
// Meant to smoke-test that continue only skips one particular flat_index
92+
// for one particular node
93+
if (cast(g1, t3) == g
94+
|| cast(g2[0][0], t3) == g
95+
|| cast(g2[0][2], t3) == g
96+
|| cast(g2[1][2], t3) == g
97+
|| cast(p[1][1].g3[0], t3) == g
98+
|| cast(p[1][2].g3[2], t3) == g) {
99+
continue;
100+
}
101+
++count;
102+
}
103+
104+
// total - skipped
105+
assert count == 1 + 2*3 + 2*3*5 - 6;
78106
}

0 commit comments

Comments
 (0)