Skip to content

Commit 677d51b

Browse files
DspilFelix A. Wolf
andauthored
Add break and continue support (#432)
* added break and continue without labels * changes * continue with goto * removed print * pretty printing to continue * violation instead of not implemented * fix nomessages returned at stmttyping * unchecked * changed enclosinglabeledloop * some comments * pattern matching instead of isinstanceof * enclosedIn*Loop -> enclosingLoop*Node * fixed bug of not typechecking continue with label correctly, tests * added some unit tests * more unit tests and indentation fixed * continue redone * continue updates * temp * redone continue, more test cases * added header * prettyprinting * some comments * more comments * more comments * simplifications and test updates * test changes * consistency * loop label in continue node for prettyprinting and debugging * removed obsolete comment * label test * small refactoring * unneeded spaces removed * fixed duplicate labels test case annotation * implemented joao's and felix's feedback * removed obsolete label from while * moved label declaration * goodbye stack * replaced - with _ in label names, some documentation Co-authored-by: Felix A. Wolf <felix.wolf@inf.ethz.ch>
1 parent da663c7 commit 677d51b

39 files changed

+780
-9
lines changed

src/main/scala/viper/gobra/ast/internal/PrettyPrinter.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,9 @@ class DefaultPrettyPrinter extends PrettyPrinter with kiama.output.PrettyPrinter
304304
showVar(resTarget) <> "," <+> showVar(successTarget) <+> "=" <+> showExpr(mapLookup)
305305
case PredExprFold(base, args, p) => "fold" <+> "acc" <> parens(showExpr(base) <> parens(showExprList(args)) <> "," <+> showExpr(p))
306306
case PredExprUnfold(base, args, p) => "unfold" <+> "acc" <> parens(showExpr(base) <> parens(showExprList(args)) <> "," <+> showExpr(p))
307+
308+
case Continue(l, _) => "continue" <+> opt(l)(text)
309+
case Break(l, _) => "break" <+> opt(l)(text)
307310
})
308311

309312
def showProxy(x: Proxy): Doc = updatePositionStore(x) <> (x match {
@@ -705,5 +708,7 @@ class ShortPrettyPrinter extends DefaultPrettyPrinter {
705708
showVar(resTarget) <> "," <+> showVar(successTarget) <+> "=" <+> "<-" <+> showExpr(channel)
706709
case PredExprFold(base, args, p) => "fold" <+> "acc" <> parens(showExpr(base) <> parens(showExprList(args)) <> "," <+> showExpr(p))
707710
case PredExprUnfold(base, args, p) => "unfold" <+> "acc" <> parens(showExpr(base) <> parens(showExprList(args)) <> "," <+> showExpr(p))
711+
case Continue(l, _) => "continue" <+> opt(l)(text)
712+
case Break(l, _) => "break" <+> opt(l)(text)
708713
}
709714
}

src/main/scala/viper/gobra/ast/internal/Program.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,21 @@ case class Seqn(stmts: Vector[Stmt])(val info: Source.Parser.Info) extends Stmt
267267

268268
case class Label(id: LabelProxy)(val info: Source.Parser.Info) extends Stmt
269269

270+
/**
271+
* 'label' corresponds to the loop label we want to continue. In case it is a normal
272+
* continue node, it is None.
273+
* The continue node will be replaced by a goto statement to 'escLabel' which has
274+
* been placed properly while desugaring for loops.
275+
*/
276+
case class Continue(label: Option[String], escLabel: String)(val info: Source.Parser.Info) extends Stmt
277+
278+
/**
279+
* 'label' corresponds to the loop label we want to break out of. In case it is a normal
280+
* break node, it is 'None'.
281+
* The break node will be replaced by a goto statement to 'escLabel'.
282+
*/
283+
case class Break(label: Option[String], escLabel: String)(val info: Source.Parser.Info) extends Stmt
284+
270285
case class If(cond: Expr, thn: Stmt, els: Stmt)(val info: Source.Parser.Info) extends Stmt
271286

272287
case class While(cond: Expr, invs: Vector[Assertion], terminationMeasure: Option[TerminationMeasure], body: Stmt)(val info: Source.Parser.Info) extends Stmt

src/main/scala/viper/gobra/ast/internal/transform/OverflowChecksTransform.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ object OverflowChecksTransform extends InternalTransform {
8686
val ifStmt = If(cond, stmtTransform(thn), stmtTransform(els))(i.info)
8787
Seqn(Vector(assertCond, ifStmt))(i.info)
8888

89-
case w@While(cond, invs, terminationMeasure,body) =>
89+
case w@While(cond, invs, terminationMeasure, body) =>
9090
val condInfo = createAnnotatedInfo(cond.info)
9191
val assertCond = Assert(assertionExprInBounds(cond, cond.typ)(condInfo))(condInfo)
9292
val whileStmt = While(cond, invs, terminationMeasure,stmtTransform(body))(w.info)

src/main/scala/viper/gobra/ast/internal/utility/GobraStrategy.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ object GobraStrategy {
4141
case (_: Label, Seq(label: LabelProxy)) => Label(label)(meta)
4242
case (_: If, Seq(cond: Expr, thn: Stmt, els: Stmt)) => If(cond, thn, els)(meta)
4343
case (_: While, Seq(cond: Expr, invs: Vector[Assertion@unchecked], terminationMeasure: Option[TerminationMeasure@unchecked], body: Stmt)) => While(cond, invs, terminationMeasure, body)(meta)
44+
case (_: Break, Seq(label: Option[String@unchecked], escLabel: String)) => Break(label, escLabel)(meta)
45+
case (_: Continue, Seq(label: Option[String@unchecked], escLabel: String)) => Continue(label, escLabel)(meta)
4446
case (_: New, Seq(target: LocalVar, expr: Expr)) => New(target, expr)(meta)
4547
case (_: MakeSlice, Seq(target: LocalVar, typeParam: SliceT, lenArg: Expr, capArg: Option[Expr@unchecked])) => MakeSlice(target, typeParam, lenArg, capArg)(meta)
4648
case (_: MakeChannel, Seq(target: LocalVar, typeParam: ChannelT, bufferSizeArg: Option[Expr@unchecked], isChannel: MPredicateProxy, bufferSize: MethodProxy)) => MakeChannel(target, typeParam, bufferSizeArg, isChannel, bufferSize)(meta)

src/main/scala/viper/gobra/ast/internal/utility/Nodes.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ object Nodes {
3939
case DomainFunc(_, args, results) => args ++ Seq(results)
4040
case DomainAxiom(expr) => Seq(expr)
4141
case s: Stmt => s match {
42+
case Break(_, _) => Seq.empty
43+
case Continue(_, _) => Seq.empty
4244
case Block(decls, stmts) => decls ++ stmts
4345
case Seqn(stmts) => stmts
4446
case Label(label) => Seq(label)

src/main/scala/viper/gobra/frontend/Desugar.scala

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -813,13 +813,14 @@ object Desugar {
813813

814814
case b: PBlock => unit(blockD(ctx)(b))
815815

816-
case l: PLabeledStmt =>
816+
case l: PLabeledStmt => {
817817
val proxy = labelProxy(l.label)
818818
for {
819819
_ <- declare(proxy)
820820
_ <- write(in.Label(proxy)(src))
821821
s <- goS(l.stmt)
822822
} yield s
823+
}
823824

824825
case PIfStmt(ifs, els) =>
825826
val elsStmt = maybeStmtD(ctx)(els)(src)
@@ -835,22 +836,31 @@ object Desugar {
835836
}
836837
))
837838

838-
case PForStmt(pre, cond, post, spec, body) =>
839+
case n@PForStmt(pre, cond, post, spec, body) =>
839840
unit(block( // is a block because 'pre' might define new variables
840841
for {
841842
dPre <- maybeStmtD(ctx)(pre)(src)
842843
(dCondPre, dCond) <- prelude(exprD(ctx)(cond))
843844
(dInvPre, dInv) <- prelude(sequence(spec.invariants map assertionD(ctx)))
844845
(dTerPre, dTer) <- prelude(option(spec.terminationMeasure map terminationMeasureD(ctx)))
845846

847+
continueLabelName = nm.continueLabel(n, info)
848+
continueLoopLabelProxy = in.LabelProxy(continueLabelName)(src)
849+
continueLoopLabel = in.Label(continueLoopLabelProxy)(src)
850+
851+
breakLabelName = nm.breakLabel(n, info)
852+
breakLoopLabelProxy = in.LabelProxy(breakLabelName)(src)
853+
_ <- declare(breakLoopLabelProxy)
854+
breakLoopLabel = in.Label(breakLoopLabelProxy)(src)
855+
846856
dBody = blockD(ctx)(body)
847857
dPost <- maybeStmtD(ctx)(post)(src)
848858

849859
wh = in.Seqn(
850860
Vector(dPre) ++ dCondPre ++ dInvPre ++ dTerPre ++ Vector(
851-
in.While(dCond, dInv, dTer, in.Seqn(
852-
Vector(dBody, dPost) ++ dCondPre ++ dInvPre ++ dTerPre
853-
)(src))(src)
861+
in.While(dCond, dInv, dTer, in.Block(Vector(continueLoopLabelProxy),
862+
Vector(dBody, continueLoopLabel, dPost) ++ dCondPre ++ dInvPre ++ dTerPre
863+
)(src))(src), breakLoopLabel
854864
)
855865
)(src)
856866
} yield wh
@@ -1044,6 +1054,10 @@ object Desugar {
10441054
}
10451055
} yield in.Seqn(Vector(dPre, exprAss, clauseBody))(src)
10461056

1057+
case n@PContinue(label) => unit(in.Continue(label.map(x => x.name), nm.fetchContinueLabel(n, info))(src))
1058+
1059+
case n@PBreak(label) => unit(in.Break(label.map(x => x.name), nm.fetchBreakLabel(n, info))(src))
1060+
10471061
case _ => ???
10481062
}
10491063
}
@@ -3115,6 +3129,8 @@ object Desugar {
31153129
private val LABEL_PREFIX = "L"
31163130
private val GLOBAL_PREFIX = "G"
31173131
private val BUILTIN_PREFIX = "B"
3132+
private val CONTINUE_LABEL_SUFFIX = "$Continue"
3133+
private val BREAK_LABEL_SUFFIX = "$Break"
31183134

31193135
/** the counter to generate fresh names depending on the current code root for which a fresh name should be generated */
31203136
private var nonceCounter: Map[PCodeRoot, Int] = Map.empty
@@ -3188,6 +3204,61 @@ object Desugar {
31883204
f
31893205
}
31903206

3207+
/**
3208+
* Returns a unique id for a for loop node with respect to its code root.
3209+
* The id is of the form 'L_<a>_<b>' where a is the difference of the lines
3210+
* of the for loop with the code root and b is the difference of the columns
3211+
* of the for loop with the code root.
3212+
* If a differce is negative, the '-' character is replaced with '_'.
3213+
*
3214+
* @param loop the for loop we are interested in
3215+
* @param info type info to get position information
3216+
* @return string
3217+
*/
3218+
private def loopId(loop: PForStmt, info: TypeInfo) : String = {
3219+
val pom = info.getTypeInfo.tree.originalRoot.positions
3220+
val lpos = pom.positions.getStart(loop).get
3221+
val rpos = pom.positions.getStart(info.codeRoot(loop)).get
3222+
return ("L_" + (lpos.line - rpos.line) + "_" + (lpos.column - rpos.column)).replace("-", "_")
3223+
}
3224+
3225+
/** returns the loopId with the CONTINUE_LABEL_SUFFIX appended */
3226+
def continueLabel(loop: PForStmt, info: TypeInfo) : String = loopId(loop, info) + CONTINUE_LABEL_SUFFIX
3227+
3228+
/** returns the loopId with the BREAK_LABEL_SUFFIX appended */
3229+
def breakLabel(loop: PForStmt, info: TypeInfo) : String = loopId(loop, info) + BREAK_LABEL_SUFFIX
3230+
3231+
/**
3232+
* Finds the enclosing loop which the continue statement refers to and fetches its
3233+
* continue label.
3234+
*/
3235+
def fetchContinueLabel(n: PContinue, info: TypeInfo) : String = {
3236+
n.label match {
3237+
case None =>
3238+
val Some(loop) = info.enclosingLoopNode(n)
3239+
continueLabel(loop, info)
3240+
case Some(label) =>
3241+
val Some(loop) = info.enclosingLabeledLoopNode(label, n)
3242+
continueLabel(loop, info)
3243+
}
3244+
}
3245+
3246+
/**
3247+
* Finds the enclosing loop which the break statement refers to and fetches its
3248+
* break label.
3249+
*/
3250+
def fetchBreakLabel(n: PBreak, info: TypeInfo) : String = {
3251+
n.label match {
3252+
case None =>
3253+
val Some(loop) = info.enclosingLoopNode(n)
3254+
breakLabel(loop, info)
3255+
case Some(label) =>
3256+
val Some(loop) = info.enclosingLabeledLoopNode(label, n)
3257+
breakLabel(loop, info)
3258+
}
3259+
}
3260+
3261+
31913262
def inParam(idx: Int, s: PScope, context: ExternalTypeInfo): String = name(IN_PARAMETER_PREFIX)("P" + idx, s, context)
31923263
def outParam(idx: Int, s: PScope, context: ExternalTypeInfo): String = name(OUT_PARAMETER_PREFIX)("P" + idx, s, context)
31933264
def receiver(s: PScope, context: ExternalTypeInfo): String = name(RECEIVER_PREFIX)("R", s, context)

src/main/scala/viper/gobra/frontend/info/ExternalTypeInfo.scala

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@
66

77
package viper.gobra.frontend.info
88

9-
import viper.gobra.ast.frontend.{PCodeRoot, PEmbeddedDecl, PExpression, PFieldDecl, PIdnNode, PIdnUse, PKeyedElement, PMPredicateDecl, PMPredicateSig, PMember, PMethodDecl, PMethodSig, PMisc, PNode, PParameter, PPkgDef, PScope, PType}
9+
import viper.gobra.ast.frontend.{PCodeRoot, PEmbeddedDecl, PExpression, PFieldDecl, PIdnNode, PIdnUse, PKeyedElement, PMPredicateDecl, PMPredicateSig, PMember, PMethodDecl, PMethodSig, PMisc, PNode, PParameter, PPkgDef, PScope, PType, PLabelUse}
1010
import viper.gobra.frontend.PackageInfo
1111
import viper.gobra.frontend.info.base.BuiltInMemberTag.BuiltInMemberTag
1212
import viper.gobra.frontend.info.base.Type.{AbstractType, InterfaceT, StructT, Type}
1313
import viper.gobra.frontend.info.base.SymbolTable.{Embbed, Field, MPredicateImpl, MPredicateSpec, MethodImpl, MethodSpec, Regular, TypeMember}
1414
import viper.gobra.frontend.info.implementation.resolution.{AdvancedMemberSet, MemberPath}
1515
import viper.gobra.frontend.info.implementation.typing.ghost.separation.GhostType
16+
import viper.gobra.ast.frontend.PForStmt
1617

1718
trait ExternalTypeInfo {
1819

@@ -92,4 +93,10 @@ trait ExternalTypeInfo {
9293

9394
/** returns the code root for a given node; can only be called on nodes that are enclosed in a code root */
9495
def codeRoot(n: PNode): PCodeRoot with PScope
96+
97+
/** if it exists, it returns the for loop node that contains 'n' with label 'label' */
98+
def enclosingLabeledLoopNode(label: PLabelUse, n: PNode) : Option[PForStmt]
99+
100+
/** if it exists, it returns the for loop node that contains 'n' */
101+
def enclosingLoopNode(n: PNode) : Option[PForStmt]
95102
}

src/main/scala/viper/gobra/frontend/info/implementation/Errors.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ trait Errors { this: TypeInfoImpl =>
2929
// case n: PIdnUnk if isDef(n) => wellDefID(n).out
3030
case n: PMisc => wellDefMisc(n).out
3131
case n: PSpecification => wellDefSpec(n).out
32+
case n: PLabelNode => wellDefLabel(n).out
3233
case _ => noMessages
3334
}
3435

src/main/scala/viper/gobra/frontend/info/implementation/TypeInfoImpl.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ class TypeInfoImpl(final val tree: Info.GoTree, final val context: Info.Context,
8383

8484
override def codeRoot(n: PNode): PCodeRoot with PScope = enclosingCodeRoot(n)
8585

86+
override def enclosingLabeledLoopNode(label: PLabelUse, n: PNode) : Option[PForStmt] = enclosingLabeledLoop(label, n)
87+
88+
override def enclosingLoopNode(n: PNode) : Option[PForStmt] = enclosingLoop(n)
89+
8690
override def regular(n: PIdnNode): SymbolTable.Regular = entity(n) match {
8791
case r: Regular => r
8892
case _ => violation("found non-regular entity")

src/main/scala/viper/gobra/frontend/info/implementation/resolution/Enclosing.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,23 @@ trait Enclosing { this: TypeInfoImpl =>
3030
case _ => id
3131
})
3232

33+
lazy val enclosingLoop: PNode => Option[PForStmt] = {
34+
down[Option[PForStmt]](None){ case x: PForStmt => Some(x) }
35+
}
36+
37+
// Returns the enclosing loop that has a specific label
38+
// It also returns the invariants of that loop
39+
def enclosingLabeledLoop(label: PLabelUse, node: PNode) : Option[PForStmt] = {
40+
enclosingLoop(node) match {
41+
case None => None
42+
case Some(encLoop) => encLoop match {
43+
case tree.parent(l: PLabeledStmt) if l.label.name == label.name => Some(encLoop)
44+
case tree.parent(p) => enclosingLabeledLoop(label, p)
45+
case _ => violation("No parent found for a loop statement.")
46+
}
47+
}
48+
}
49+
3350
lazy val tryEnclosingUnorderedScope: PNode => Option[PUnorderedScope] =
3451
down[Option[PUnorderedScope]](None) { case x: PUnorderedScope => Some(x) }
3552

0 commit comments

Comments
 (0)