diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c3c3221..b3730db0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ * Added return dispatch statements. +* Added `break` and `continue` statements. + * Added octal and quaternary literals. * Fixed several optimization bugs. diff --git a/src/main/scala/millfork/OptimizationPresets.scala b/src/main/scala/millfork/OptimizationPresets.scala index 252dd52a..ec328eab 100644 --- a/src/main/scala/millfork/OptimizationPresets.scala +++ b/src/main/scala/millfork/OptimizationPresets.scala @@ -14,6 +14,7 @@ object OptimizationPresets { UnusedGlobalVariables, ) val AssOpt: List[AssemblyOptimization] = List[AssemblyOptimization]( + UnusedLabelRemoval, AlwaysGoodOptimizations.NonetAddition, AlwaysGoodOptimizations.PointlessSignCheck, AlwaysGoodOptimizations.PoinlessLoadBeforeAnotherLoad, @@ -125,6 +126,7 @@ object OptimizationPresets { ) val Good: List[AssemblyOptimization] = List[AssemblyOptimization]( + UnusedLabelRemoval, AlwaysGoodOptimizations.Adc0Optimization, AlwaysGoodOptimizations.BitPackingUnpacking, AlwaysGoodOptimizations.BranchInPlaceRemoval, @@ -185,6 +187,7 @@ object OptimizationPresets { ) val QuickPreset: List[AssemblyOptimization] = List[AssemblyOptimization]( + UnusedLabelRemoval, AlwaysGoodOptimizations.Adc0Optimization, AlwaysGoodOptimizations.BranchInPlaceRemoval, AlwaysGoodOptimizations.CommonBranchBodyOptimization, diff --git a/src/main/scala/millfork/compiler/CompilationContext.scala b/src/main/scala/millfork/compiler/CompilationContext.scala index 7c72d3aa..8b106ca3 100644 --- a/src/main/scala/millfork/compiler/CompilationContext.scala +++ b/src/main/scala/millfork/compiler/CompilationContext.scala @@ -1,18 +1,32 @@ package millfork.compiler import millfork.{CompilationFlag, CompilationOptions} -import millfork.env.{Environment, MangledFunction, NormalFunction} +import millfork.env.{Environment, Label, NormalFunction} /** * @author Karol Stasiak */ -case class CompilationContext(env: Environment, function: NormalFunction, extraStackOffset: Int, options: CompilationOptions){ +case class CompilationContext(env: Environment, + function: NormalFunction, + extraStackOffset: Int, + options: CompilationOptions, + breakLabels: Map[String, Label] = Map(), + continueLabels: Map[String, Label] = Map()){ def withInlinedEnv(environment: Environment): CompilationContext = { val newEnv = new Environment(Some(env), MfCompiler.nextLabel("en")) newEnv.things ++= environment.things copy(env = newEnv) } + def addLabels(names: Set[String], breakLabel: Label, continueLabel: Label): CompilationContext = { + var b = breakLabels + var c = continueLabels + names.foreach{name => + b += (name -> breakLabel) + c += (name -> continueLabel) + } + this.copy(breakLabels = b, continueLabels = c) + } def addStack(i: Int): CompilationContext = this.copy(extraStackOffset = extraStackOffset + i) diff --git a/src/main/scala/millfork/compiler/MacroExpander.scala b/src/main/scala/millfork/compiler/MacroExpander.scala index cafcf994..cd5623d4 100644 --- a/src/main/scala/millfork/compiler/MacroExpander.scala +++ b/src/main/scala/millfork/compiler/MacroExpander.scala @@ -29,13 +29,15 @@ object MacroExpander { case ReturnDispatchStatement(i,ps, bs) => ReturnDispatchStatement(i.replaceVariable(paramName, target), ps.map(fx), bs.map{ case ReturnDispatchBranch(l, fu, pps) => ReturnDispatchBranch(l, f(fu), pps.map(f)) }) - case WhileStatement(c, b) => WhileStatement(f(c), b.map(gx)) - case DoWhileStatement(b, c) => DoWhileStatement(b.map(gx), f(c)) + case WhileStatement(c, b, i, n) => WhileStatement(f(c), b.map(gx), i.map(gx), n) + case DoWhileStatement(b, i, c, n) => DoWhileStatement(b.map(gx), i.map(gx), f(c), n) case ForStatement(v, start, end, dir, body) => ForStatement(h(v), f(start), f(end), dir, body.map(gx)) case IfStatement(c, t, e) => IfStatement(f(c), t.map(gx), e.map(gx)) case s:AssemblyStatement => s.copy(expression = f(s.expression)) case Assignment(d,s) => Assignment(fx(d), f(s)) case BlockStatement(s) => BlockStatement(s.map(gx)) + case BreakStatement(s) => if (s == paramName) BreakStatement(target.toString) else stmt + case ContinueStatement(s) => if (s == paramName) ContinueStatement(target.toString) else stmt case _ => println(stmt) ??? diff --git a/src/main/scala/millfork/compiler/StatementCompiler.scala b/src/main/scala/millfork/compiler/StatementCompiler.scala index 8d3b152c..d284503b 100644 --- a/src/main/scala/millfork/compiler/StatementCompiler.scala +++ b/src/main/scala/millfork/compiler/StatementCompiler.scala @@ -111,6 +111,11 @@ object StatementCompiler { ExpressionCompiler.compile(ctx, e, None, NoBranching) } case ExpressionStatement(e) => + e match { + case VariableExpression(_) | LiteralExpression(_, _) => + ErrorReporting.warn("Pointless expression statement", ctx.options, statement.position) + case _ => + } ExpressionCompiler.compile(ctx, e, None, NoBranching) case BlockStatement(s) => s.flatMap(compile(ctx, _)) @@ -227,74 +232,67 @@ object StatementCompiler { ErrorReporting.error(s"Illegal type for a condition: `$condType`", condition.position) Nil } - case WhileStatement(condition, bodyPart) => + case WhileStatement(condition, bodyPart, incrementPart, labels) => + val start = MfCompiler.nextLabel("wh") + val middle = MfCompiler.nextLabel("he") + val inc = MfCompiler.nextLabel("fp") + val end = MfCompiler.nextLabel("ew") val condType = ExpressionCompiler.getExpressionType(ctx, condition) - val bodyBlock = compile(ctx, bodyPart) + val bodyBlock = compile(ctx.addLabels(labels, Label(end), Label(inc)), bodyPart) + val incrementBlock = compile(ctx.addLabels(labels, Label(end), Label(inc)), incrementPart) val largeBodyBlock = bodyBlock.map(_.sizeInBytes).sum > 100 condType match { case ConstantBooleanType(_, true) => - val conditionBlock = ExpressionCompiler.compile(ctx, condition, someRegisterA, NoBranching) - val start = MfCompiler.nextLabel("wh") - List(labelChunk(start), bodyBlock, jmpChunk(start)).flatten + List(labelChunk(start), bodyBlock, labelChunk(inc), incrementBlock, jmpChunk(start), labelChunk(end)).flatten case ConstantBooleanType(_, false) => Nil case FlagBooleanType(_, jumpIfTrue, jumpIfFalse) => if (largeBodyBlock) { - val start = MfCompiler.nextLabel("wh") - val middle = MfCompiler.nextLabel("he") - val end = MfCompiler.nextLabel("ew") val conditionBlock = ExpressionCompiler.compile(ctx, condition, someRegisterA, NoBranching) - List(labelChunk(start), conditionBlock, branchChunk(jumpIfTrue, middle), jmpChunk(end), bodyBlock, jmpChunk(start), labelChunk(end)).flatten + List(labelChunk(start), conditionBlock, branchChunk(jumpIfTrue, middle), jmpChunk(end), labelChunk(middle), bodyBlock, labelChunk(inc), incrementBlock, jmpChunk(start), labelChunk(end)).flatten } else { - val start = MfCompiler.nextLabel("wh") - val end = MfCompiler.nextLabel("ew") val conditionBlock = ExpressionCompiler.compile(ctx, condition, someRegisterA, NoBranching) - List(labelChunk(start), conditionBlock, branchChunk(jumpIfFalse, end), bodyBlock, jmpChunk(start), labelChunk(end)).flatten + List(labelChunk(start), conditionBlock, branchChunk(jumpIfFalse, end), bodyBlock, labelChunk(inc), incrementBlock, jmpChunk(start), labelChunk(end)).flatten } case BuiltInBooleanType => if (largeBodyBlock) { - val start = MfCompiler.nextLabel("wh") - val middle = MfCompiler.nextLabel("he") - val end = MfCompiler.nextLabel("ew") val conditionBlock = ExpressionCompiler.compile(ctx, condition, someRegisterA, BranchIfTrue(middle)) - List(labelChunk(start), conditionBlock, jmpChunk(end), labelChunk(middle), bodyBlock, jmpChunk(start), labelChunk(end)).flatten + List(labelChunk(start), conditionBlock, jmpChunk(end), labelChunk(middle), bodyBlock, labelChunk(inc), incrementBlock, jmpChunk(start), labelChunk(end)).flatten } else { - val start = MfCompiler.nextLabel("wh") - val end = MfCompiler.nextLabel("ew") val conditionBlock = ExpressionCompiler.compile(ctx, condition, someRegisterA, BranchIfFalse(end)) - List(labelChunk(start), conditionBlock, bodyBlock, jmpChunk(start), labelChunk(end)).flatten + List(labelChunk(start), conditionBlock, bodyBlock, labelChunk(inc), incrementBlock, jmpChunk(start), labelChunk(end)).flatten } case _ => ErrorReporting.error(s"Illegal type for a condition: `$condType`", condition.position) Nil } - case DoWhileStatement(bodyPart, condition) => + case DoWhileStatement(bodyPart, incrementPart, condition, labels) => + val start = MfCompiler.nextLabel("do") + val inc = MfCompiler.nextLabel("fp") + val end = MfCompiler.nextLabel("od") val condType = ExpressionCompiler.getExpressionType(ctx, condition) - val bodyBlock = compile(ctx, bodyPart) + val bodyBlock = compile(ctx.addLabels(labels, Label(end), Label(inc)), bodyPart) + val incrementBlock = compile(ctx.addLabels(labels, Label(end), Label(inc)), incrementPart) val largeBodyBlock = bodyBlock.map(_.sizeInBytes).sum > 100 condType match { case ConstantBooleanType(_, true) => - val start = MfCompiler.nextLabel("do") val conditionBlock = ExpressionCompiler.compile(ctx, condition, someRegisterA, NoBranching) - List(labelChunk(start), bodyBlock, jmpChunk(start)).flatten - case ConstantBooleanType(_, false) => bodyBlock + List(labelChunk(start),bodyBlock, labelChunk(inc), incrementBlock, jmpChunk(start), labelChunk(end)).flatten + case ConstantBooleanType(_, false) => + List(bodyBlock, labelChunk(inc), incrementBlock, labelChunk(end)).flatten case FlagBooleanType(_, jumpIfTrue, jumpIfFalse) => - val start = MfCompiler.nextLabel("do") val conditionBlock = ExpressionCompiler.compile(ctx, condition, someRegisterA, NoBranching) if (largeBodyBlock) { - val end = MfCompiler.nextLabel("od") - List(labelChunk(start), bodyBlock, conditionBlock, branchChunk(jumpIfFalse, end), jmpChunk(start), labelChunk(end)).flatten + List(labelChunk(start), bodyBlock, labelChunk(inc), incrementBlock, conditionBlock, branchChunk(jumpIfFalse, end), jmpChunk(start), labelChunk(end)).flatten } else { - List(labelChunk(start), bodyBlock, conditionBlock, branchChunk(jumpIfTrue, start)).flatten + List(labelChunk(start), bodyBlock, labelChunk(inc), incrementBlock, conditionBlock, branchChunk(jumpIfTrue, start), labelChunk(end)).flatten } case BuiltInBooleanType => - val start = MfCompiler.nextLabel("do") if (largeBodyBlock) { - val end = MfCompiler.nextLabel("od") val conditionBlock = ExpressionCompiler.compile(ctx, condition, someRegisterA, BranchIfFalse(end)) - List(labelChunk(start), bodyBlock, conditionBlock, jmpChunk(start), labelChunk(end)).flatten + List(labelChunk(start), bodyBlock, labelChunk(inc), incrementBlock, conditionBlock, jmpChunk(start), labelChunk(end)).flatten } else { val conditionBlock = ExpressionCompiler.compile(ctx, condition, someRegisterA, BranchIfTrue(start)) - List(labelChunk(start), bodyBlock, conditionBlock).flatten + List(labelChunk(start), bodyBlock, labelChunk(inc), incrementBlock, conditionBlock, labelChunk(end)).flatten } case _ => ErrorReporting.error(s"Illegal type for a condition: `$condType`", condition.position) @@ -307,32 +305,36 @@ object StatementCompiler { val one = LiteralExpression(1, 1) val increment = ExpressionStatement(FunctionCallExpression("+=", List(vex, one))) val decrement = ExpressionStatement(FunctionCallExpression("-=", List(vex, one))) + val names = Set("", "for", variable) (direction, env.eval(start), env.eval(end)) match { case (ForDirection.Until | ForDirection.ParallelUntil, Some(NumericConstant(s, ssize)), Some(NumericConstant(e, _))) if s == e - 1 => - compile(ctx, Assignment(vex, f.start) :: f.body) + val end = MfCompiler.nextLabel("of") + compile(ctx.addLabels(names, Label(end), Label(end)), Assignment(vex, f.start) :: f.body) ++ labelChunk(end) case (ForDirection.Until | ForDirection.ParallelUntil, Some(NumericConstant(s, ssize)), Some(NumericConstant(e, _))) if s >= e => Nil case (ForDirection.To | ForDirection.ParallelTo, Some(NumericConstant(s, ssize)), Some(NumericConstant(e, _))) if s == e => - compile(ctx, Assignment(vex, f.start) :: f.body) + val end = MfCompiler.nextLabel("of") + compile(ctx.addLabels(names, Label(end), Label(end)), Assignment(vex, f.start) :: f.body) ++ labelChunk(end) case (ForDirection.To | ForDirection.ParallelTo, Some(NumericConstant(s, ssize)), Some(NumericConstant(e, _))) if s > e => Nil case (ForDirection.ParallelUntil, Some(NumericConstant(0, ssize)), Some(NumericConstant(e, _))) if e > 0 => compile(ctx, List( Assignment(vex, f.end), - DoWhileStatement(decrement :: f.body, FunctionCallExpression("!=", List(vex, f.start))) + DoWhileStatement(Nil, decrement :: f.body, FunctionCallExpression("!=", List(vex, f.start)), names) )) case (ForDirection.DownTo, Some(NumericConstant(s, ssize)), Some(NumericConstant(e, esize))) if s == e => - compile(ctx, Assignment(vex, LiteralExpression(s, ssize)) :: f.body) + val end = MfCompiler.nextLabel("of") + compile(ctx.addLabels(names, Label(end), Label(end)), Assignment(vex, LiteralExpression(s, ssize)) :: f.body) ++ labelChunk(end) case (ForDirection.DownTo, Some(NumericConstant(s, ssize)), Some(NumericConstant(e, esize))) if s < e => Nil case (ForDirection.DownTo, Some(NumericConstant(s, ssize)), Some(NumericConstant(0, esize))) if s > 0 => compile(ctx, List( Assignment(vex, f.start), - DoWhileStatement(f.body :+ decrement, FunctionCallExpression("!=", List(vex, f.end))) + DoWhileStatement(f.body, List(decrement), FunctionCallExpression("!=", List(vex, f.end)), names) )) @@ -341,7 +343,7 @@ object StatementCompiler { Assignment(vex, f.start), WhileStatement( FunctionCallExpression("<", List(vex, f.end)), - f.body :+ increment), + f.body, List(increment), names), )) // case (ForDirection.To | ForDirection.ParallelTo, _, Some(NumericConstant(n, _))) if n > 0 && n < 255 => // compile(ctx, List( @@ -351,16 +353,17 @@ object StatementCompiler { // f.body :+ increment), // )) case (ForDirection.To | ForDirection.ParallelTo, _, _) => - val label = MfCompiler.nextLabel("to") compile(ctx, List( Assignment(vex, f.start), WhileStatement( VariableExpression("true"), - f.body :+ IfStatement( + f.body, + List(IfStatement( FunctionCallExpression("==", List(vex, f.end)), - List(AssemblyStatement(JMP, AddrMode.Absolute, VariableExpression(label), elidable = true)), - List(increment))), - AssemblyStatement(LABEL, AddrMode.DoesNotExist, VariableExpression(label), elidable=true) + List(BreakStatement(variable)), + List(increment) + )), + names), )) case (ForDirection.DownTo, _, _) => compile(ctx, List( @@ -368,12 +371,34 @@ object StatementCompiler { IfStatement( FunctionCallExpression(">=", List(vex, f.end)), List(DoWhileStatement( - f.body :+ decrement, - FunctionCallExpression("!=", List(vex, f.end)) + f.body, + List(decrement), + FunctionCallExpression("!=", List(vex, f.end)), + names )), Nil) )) } + + case BreakStatement(l) => + ctx.breakLabels.get(l) match { + case None => + if (l == "") ErrorReporting.error("`break` outside a loop", statement.position) + else ErrorReporting.error("Invalid label: " + l, statement.position) + Nil + case Some(label) => + List(AssemblyLine.absolute(JMP, label)) + } + + case ContinueStatement(l) => + ctx.continueLabels.get(l) match { + case None => + if (l == "") ErrorReporting.error("`continue` outside a loop", statement.position) + else ErrorReporting.error("Invalid label: " + l, statement.position) + Nil + case Some(label) => + List(AssemblyLine.absolute(JMP, label)) + } // TODO } } diff --git a/src/main/scala/millfork/node/Node.scala b/src/main/scala/millfork/node/Node.scala index ebaae039..43933ef3 100644 --- a/src/main/scala/millfork/node/Node.scala +++ b/src/main/scala/millfork/node/Node.scala @@ -176,7 +176,7 @@ case class IfStatement(condition: Expression, thenBranch: List[ExecutableStateme override def getAllExpressions: List[Expression] = condition :: (thenBranch ++ elseBranch).flatMap(_.getAllExpressions) } -case class WhileStatement(condition: Expression, body: List[ExecutableStatement]) extends ExecutableStatement { +case class WhileStatement(condition: Expression, body: List[ExecutableStatement], increment: List[ExecutableStatement], labels: Set[String] = Set("", "while")) extends ExecutableStatement { override def getAllExpressions: List[Expression] = condition :: body.flatMap(_.getAllExpressions) } @@ -188,7 +188,7 @@ case class ForStatement(variable: String, start: Expression, end: Expression, di override def getAllExpressions: List[Expression] = VariableExpression(variable) :: start :: end :: body.flatMap(_.getAllExpressions) } -case class DoWhileStatement(body: List[ExecutableStatement], condition: Expression) extends ExecutableStatement { +case class DoWhileStatement(body: List[ExecutableStatement], increment: List[ExecutableStatement], condition: Expression, labels: Set[String] = Set("", "do")) extends ExecutableStatement { override def getAllExpressions: List[Expression] = condition :: body.flatMap(_.getAllExpressions) } @@ -196,6 +196,14 @@ case class BlockStatement(body: List[ExecutableStatement]) extends ExecutableSta override def getAllExpressions: List[Expression] = body.flatMap(_.getAllExpressions) } +case class BreakStatement(label: String) extends ExecutableStatement { + override def getAllExpressions: List[Expression] = Nil +} + +case class ContinueStatement(label: String) extends ExecutableStatement { + override def getAllExpressions: List[Expression] = Nil +} + object AssemblyStatement { def implied(opcode: Opcode.Value, elidable: Boolean) = AssemblyStatement(opcode, AddrMode.Implied, LiteralExpression(0, 1), elidable) diff --git a/src/main/scala/millfork/parser/MfParser.scala b/src/main/scala/millfork/parser/MfParser.scala index 719fbefc..dc20d296 100644 --- a/src/main/scala/millfork/parser/MfParser.scala +++ b/src/main/scala/millfork/parser/MfParser.scala @@ -312,7 +312,16 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o case (p, l, r) => Assignment(l, r).pos(p) } - def keywordStatement: P[ExecutableStatement] = P(returnOrDispatchStatement | ifStatement | whileStatement | forStatement | doWhileStatement | inlineAssembly | assignmentStatement) + def keywordStatement: P[ExecutableStatement] = P( + returnOrDispatchStatement | + ifStatement | + whileStatement | + forStatement | + doWhileStatement | + breakStatement | + continueStatement | + inlineAssembly | + assignmentStatement) def executableStatement: P[ExecutableStatement] = (position() ~ P(keywordStatement | expressionStatement)).map { case (p, s) => s.pos(p) } @@ -404,6 +413,10 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o def returnOrDispatchStatement: P[ExecutableStatement] = "return" ~ !letterOrDigit ~/ HWS ~ (dispatchStatementBody | mlExpression(nonStatementLevel).?.map(ReturnStatement)) + def breakStatement: P[ExecutableStatement] = ("break" ~ !letterOrDigit ~/ HWS ~ identifier.?).map(l => BreakStatement(l.getOrElse(""))) + + def continueStatement: P[ExecutableStatement] = ("continue" ~ !letterOrDigit ~/ HWS ~ identifier.?).map(l => ContinueStatement(l.getOrElse(""))) + def ifStatement: P[ExecutableStatement] = for { condition <- "if" ~ !letterOrDigit ~/ HWS ~/ mlExpression(nonStatementLevel) thenBranch <- AWS ~/ executableStatements @@ -413,7 +426,7 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o def whileStatement: P[ExecutableStatement] = for { condition <- "while" ~ !letterOrDigit ~/ HWS ~/ mlExpression(nonStatementLevel) body <- AWS ~ executableStatements - } yield WhileStatement(condition, body.toList) + } yield WhileStatement(condition, body.toList, Nil) def forDirection: P[ForDirection.Value] = ("parallel" ~ HWS ~ "to").!.map(_ => ForDirection.ParallelTo) | @@ -439,7 +452,7 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o def doWhileStatement: P[ExecutableStatement] = for { body <- "do" ~ !letterOrDigit ~/ AWS ~ executableStatements ~/ AWS condition <- "while" ~ !letterOrDigit ~/ HWS ~/ mlExpression(nonStatementLevel) - } yield DoWhileStatement(body.toList, condition) + } yield DoWhileStatement(body.toList, Nil, condition) def functionDefinition: P[DeclarationStatement] = for { p <- position() diff --git a/src/test/scala/millfork/test/BreakContinueSuite.scala b/src/test/scala/millfork/test/BreakContinueSuite.scala new file mode 100644 index 00000000..2fc1118c --- /dev/null +++ b/src/test/scala/millfork/test/BreakContinueSuite.scala @@ -0,0 +1,108 @@ +package millfork.test + +import millfork.test.emu.{EmuBenchmarkRun, EmuUnoptimizedRun} +import org.scalatest.{FunSuite, Matchers} + +/** + * @author Karol Stasiak + */ +class BreakContinueSuite extends FunSuite with Matchers { + + test("Break from one-iteration loop 1") { + EmuBenchmarkRun( + """ + | byte output @$c000 + | void main () { + | output = 0 + | do { + | break + | output += 1 + | } while false + | } + """.stripMargin)(_.readByte(0xc000) should equal(0)) + } + + test("Break from one-iteration loop 2") { + EmuBenchmarkRun( + """ + | byte output @$c000 + | void main () { + | output = 0 + | byte i + | for i,0,to,0 { + | break + | output += 1 + | } + | } + """.stripMargin)(_.readByte(0xc000) should equal(0)) + } + + test("Break from infinite loop 1") { + EmuBenchmarkRun( + """ + | byte output @$c000 + | void main () { + | output = 0 + | while true { + | output += 1 + | break + | output += 1 + | } + | } + """.stripMargin)(_.readByte(0xc000) should equal(1)) + } + + test("Break and continue from infinite loop 1") { + EmuBenchmarkRun( + """ + | byte output @$c000 + | void main () { + | output = 0 + | while true { + | if output != 0 { break } + | output += 1 + | continue + | output += 1 + | } + | } + """.stripMargin)(_.readByte(0xc000) should equal(1)) + } + + test("Nested break") { + EmuBenchmarkRun( + """ + | byte output @$c000 + | void main () { + | output = 0 + | do { + | output += 1 + | while true { + | break while + | } + | output += 1 + | } while false + | } + """.stripMargin)(_.readByte(0xc000) should equal(2)) + } + + test("Continue in for loop 1") { + EmuBenchmarkRun( + """ + | byte output @$c000 + | byte counter @$c001 + | void main () { + | output = 0 + | byte i + | for i,0,paralleluntil,50 { + | counter += 1 + | if i != 30 { continue } + | output = i + | break + | } + | } + """.stripMargin){m => + m.readByte(0xc000) should equal(30) + m.readByte(0xc001) should be > 10 + } + } +}