Control flow statements are statements that alter the flow of code. They either branch, i.e., execute one of several possible alternatives, or they loop, i.e., repeat part of the code one or more times.
There are several types of loops:
The while loop gets repeated as long as the condition remains true:
while @unit == null do
ubind(@poly);
end;
Similar to while loops, except the condition is placed at the end of the loop. Do-while loops therefore always execute at least once:
do
ubind(@poly);
while @unit == null;
Note
In version 3.0.0, the loop keyword became optional. The keyword will be deprecated and then removed in a future release.
Loop over a range of values, in an inclusive or exclusive fashion. The .. range operator indicates an inclusive range:
for var n in 14 .. 18 do
println(n);
end;
printflush(message1);
// prints 14, 15, 16, 17 and 18 on separate lines
The ... range operator indicates an exclusive range:
var sum = 0;
for var addr in 0 ... 64 do
sum += cell1[addr];
end;
var avg = sum / 64;
This loop will calculate the average of all 64 cells (0-based index) of cell1.
It is also possible to use expressions to specify the ranges:
var sum = 0;
for var n in firstIndex + 1 .. lastIndex - 1 do
sum += cell1[n];
end;
Note
The range is evaluated before the loop begins. If the value of the upper bound changes while the loop executes, it isn't reflected while the loop executes. To have the condition fully evaluated on each iteration, use a C-style loop or a while loop.
However, if a volatile built-in is used as a bound (such as @links), no defensive copy will be created and the variable will be used in the condition directly. This doesn't apply to expressions involving volatile built-ins: for n in 0 .. @links - 1 causes @links - 1 to be evaluated and stored in a temporary variable.
The suggested way to loop over all linked blocks is
for n = 0 ... @links do
block = getlink(n);
/* do something with block */
end;
It is possible to iterate the range in descending order by specifying descending:
for var n in 14 ... 18 descending do
println(n);
end;
printflush(message1);
// prints 17, 16, 15 and 14 on separate lines
The descending keyword just reverses the order of loop iterations. The range still needs to specify the lower bound first and the upper bound second. If the range is exclusive (as in the example above), the iteration starts at the upper bound value decreased by one.
Important
Currently, range iteration loops can only increment/decrement the value by 1. If the start value is greater than the end value, the loop body won't get executed at all, both in ascending and descending iteration order.
Loop over a fixed collection of values or expressions:
for u in @mono, @poly, @mega do
ubind(u);
if @unit != null then
break;
end;
end;
print(u);
printflush(message1);
Tries to bind a mono, poly, or mega, in this order, ending the loop when successfully binding one.
The list of values is fixed—it cannot be stored in a variable, for example, as Mindustry Logic itself doesn't support dynamic arrays or collections. It is possible to specify an expression in the list of values, though, and each expression is evaluated at the beginning of the iteration that uses it. This loop
var n = 0;
for var a in foo(), foo(), foo() do
print(a, "\n");
end;
def foo()
++n;
end;
printflush(message1);
prints values 1, 2, 3, as the foo() function call is evaluated at the beginning of each iteration.
The list iterator loop can use more loop variables to process several items from the list at once:
for var unit, count in
@mono, 5,
@poly, 4,
@mega, 2
do
print($"$unit: $count\n");
end;
printflush(message1);
This code will print out mono: 5, poly: 4 and mega: 2 on separate lines.
The values in the list aren't organized into tuples. You can put them on separate lines, as shown in the example, to keep them organized. The list length must be divisible by the number of loop control variables.
If you use expressions based on the values of the loop control variables in the list, the results are generally undefined. Example:
var a = 1;
var b = 2;
for a, b in b, a do
print(a, b);
end;
This code prints out 22 and not 21, as might be expected.
When an array or subarray is used in the value list (external or internal), the array elements are concatenated with the rest of the list:
var a[] = (1, 2, 3, 4, 5);
external(cell1) b[] = (6, 7, 8, 9);
// Prints 1234506789
for var i in a, 0, b do
print(i);
end;
println();
// Prints 12342345
for var i in a[0 .. 3], a[1 .. 4] do
print(i);
end;
println();
// Prints 3 7 5 13 17
for var i, j in a, 0, b do
println(i + j);
end;
Tip
It is generally more efficient to use list iteration loop rather than other forms of loops (e.g., range iteration loop combined with index access) for internal arrays. For external arrays, index access is about as effective as list iteration loop, but produces smaller code.
When loop unrolling optimization is applied, the resulting code is identical regardless of the type of loop used.
If the elements of the list being iterated over are variables or arrays, it is possible to change their values by declaring the loop control variable with the out modifier:
var a = 1, b = 2, c = 3, d = 4;
for var out i in a, b, c, d do
print(i);
i = i * 2;
end;
println();
print(a, b, c, d);
This code will print "1234" on one line, followed by "2468" on the second line.
It is possible to declare more than one variable in the loop as output:
var a = 1, b = 2, c = 3, d = 4;
for var out i, out j in a, b, c, d do
tmp = i;
i = j;
j = tmp;
end;
print(a, b, c, d);
This code swaps values of a and c with b and d, producing "2143" on output.
It is also possible to use a list iteration loop to initialize variables:
var index = 0;
for var out i in a, b, c, d do
i = ++index;
end;
print(a, b, c, d);
This code initializes values a, b, c and d to 1, 2, 3 and 4 respectively. No warning about these variables not being initialized is made because their initial values aren't used inside the loop body.
If some elements in the list cannot be modified, it is an error if it is assigned to an out loop control variable:
// Error - 'c + 1' is not a variable
for var out i in a, b, c + 1, d do
i = rand(10);
end;
It is possible to specify multiple iterators and their values in the loop. In each iteration, all iterators are assigned values from their respective lists. Iterators/values groups are separated using a semicolon. Iterators in each group may be declared out, and each group can have different number of iterators. The only requirement is that all iterator groups must be provided with data for the same number of iterations.
var a[20], b[10];
// Ten iterations in total: a has 20 elements, but feeds 2 iterators
for var i, j in a; var out k in b do
k = i + j;
end;
By combining subarrays and parallel iteration, arrays can be processed by list iteration loops in many ways. For example, it is possible to iterate over all adjacent pairs of elements in the array:
var a[10];
var index = 0;
for var out i in a do
i = index++;
end;
for var e1 in a[0 ... 9]; e2 in a[1 ... 10] do
print(e1, e2, " ");
end;
printflush(message1);
This code outputs 01 12 23 34 45 56 67 78 89 .
This can be used in, for example, a bubble sort algorithm:
const SIZE = 10;
var a[SIZE];
for var out i in a do
i = floor(rand(1000));
end;
for var i in a do
println(i);
end;
// Bubblesort!
do
var swapped = false;
for var out i in a[0 ... 9]; var out j in a[1 ... 10] do
if i > j then
var x = i; i = j; j = x;
swapped = true;
end;
end;
while swapped;
println();
for var i in a do
println(i);
end;
It is possible to execute the loop in descending order by specifying descending keyword.
// This code prints "4321"
for var i in 1, 2, 3, 4 descending do
print(i);
end;
In the case of parallel iterations, each group of iterators can be processed in ascending or descending order separately:
var a[10], b[10];
for var i in a descending; var out k in b do
k = 2 * i;
end;
In case of multiple iterators, the descending keyword reverses the order of iterations. Individual iterators get assigned the same values, just in descending order:
// Prints "12345678"
for var i, j in 1, 2, 3, 4, 5, 6, 7, 8 do
print(i, j);
end;
printflush(message1);
// Prints "78563412"
for var i, j in 1, 2, 3, 4, 5, 6, 7, 8 descending do
print(i, j);
end;
printflush(message1);
Descending iteration order is especially useful with varargs, where it provides the only means to access arrays in reverse order:
const SIZE = 10;
var array[SIZE];
begin
for var i in 0 ... SIZE do
array[i] = i;
end;
reverse(out array);
print(array);
printflush(message1);
end;
inline void reverse(array...)
// We need to stop in the middle, otherwise the elements would get swapped twice
var count = length(array) \ 2;
for var out i in array; var out j in array descending do
if --count >= 0 then
var t = i; i = j; j = t;
end;
end;
end;
The syntax is similar to C's, except for the absence of parenthesis and the do keyword:
// Visit every block in a given region, one at a time, starting from the bottom
// left and ending at the upper right
var dx = 1;
for var x = SW_X, y = SW_Y; x < NE_X && j < NE_Y ; x += dx do
// do something with this block
if x == NE_X then
dx *= -1;
y += dy;
end;
end;
It is possible to easily create infinite loops using the loop keyword. The main advantage is that the infinite loop can be used in the global scope even with strict syntax. This supports easy creation of programs that have an initialization part and a main part which loops indefinitely, which is often the case in mlog programs:
#set syntax = strict;
#set symbolic-labels = true;
#set remarks = comments;
/// Initialization code (link guard)
guarded linked switch1;
/// Another initialization
var counter = 0;
/// Initialization code again
begin
switch1.enabled = false;
end;
/// The main code: an infinite loop
loop
print(counter++);
printflush(message1);
if switch1.enabled then
switch1.enabled = false;
counter = 0;
end;
end;
compiles to:
# Mlog code compiled with support for symbolic labels
# You can safely add/remove instructions, in most parts of the program
# Pay closer attention to sections of the program manipulating @counter
# Initialization code (link guard)
label_0:
jump label_0 equal switch1 null
# Another initialization
set .counter 0
# Initialization code again
control enabled switch1 false 0 0 0
# The main code: an infinite loop
label_3:
set *tmp2 .counter
op add .counter .counter 1
print *tmp2
printflush message1
sensor *tmp4 switch1 @enabled
jump label_3 equal *tmp4 false
control enabled switch1 false 0 0 0
set .counter 0
jump label_3 always 0 0
Mindcode offers three types of conditionals: if/else expressions, the ternary operator, and case/when expressions. Ternary operator was described in the previous chapter.
In Mindcode, if is an expression, meaning it returns a value. The returned value is the last value of the branch. For example:
var result = if n == 0 then
"ready";
else
"pending";
end;
Depending on the value of n, result will contain the one of ready or pending.
To handle more than two alternatives, you can use elsif as an alternative to nested if statements:
var text = if n > 0 then
"positive";
elsif n < 0 then
"negative";
else
"zero";
end;
is equivalent to
var text = if n > 0 then
"positive";
else
if n < 0 then
"negative";
else
"zero";
end;
end;
Case expression is another way of writing conditionals. Use case expression when you need to test a value against multiple different alternatives:
var status = case num_enemies
when 0 then
"chill";
when 1, 2 then
"alert";
when 3, 4, 5 then
"vigilant";
else
"nuke-the-place";
end;
Multiple comma-separated expressions can be listed after each when keyword. It is also possible to use range expressions and even mix them with normal expression like this:
var text = case number
when 0, 1, 2**3 .. 2**5, 42, -115 then
"A number I like";
when 10**5 .. 10**9 then
"A very big number";
else
"An ugly number";
end;
When a null literal is used as a value in the when clause, Mindcode generates a strict comparison to that literal. If both a zero literal and a null literal are present in your when values, both 0 and null are matched using strict comparison:
var text = case number
when 0, 1, 2**3 .. 2**5, 42, -115 then
"A number I like";
when 10**5 .. 10**9 then
"A very big number";
when null then
"A null";
else
"An ugly number";
end;
If the when null clause is not used, or the when clause contains an expression that evaluates to null or zero (as opposed to a null or zero literal), null and zero are not distinguished by the case statement.
While the Case Switching optimization can alter case expressions heavily, the original behavior described here is preserved.
- Some expressions after the
whenkeyword might or might not get evaluated, depending on the value of the case expression. Do not use expressions with side effects (such as a function call that would modify some global variable). - Avoid having several
whenbranches matching the same value -- currently the first matching branch gets executed, but the behavior might change in the future.
Code blocks are used to group a set of statements together:
begin
var enabled = floor(rand(2));
print("Current value is ", enabled);
switch1.enabled = enabled;
end;
Variables declared in a code block are local to the code block.
In the strict syntax mode, executable code needs to be enclosed in a code block, a function, or an infinite loop.
You can use a break or continue statement inside a loop in the usual sense (break exits the loop, continue skips the rest of the current iteration):
while not within(x, y, 6) do
approach(x, y, 4);
if @unit.@dead == 1 then
break;
end;
// ...
end;
An unlabeled break statement exits the innermost loop, however a labeled break can exit from an outer statement. It is necessary to mark the outer statement with a label, and then use the break <label> syntax, as shown here:
MainLoop:
for var i in 1 .. 10 do
for var j in 5 .. 20 do
if i > j then
break MainLoop;
end;
print(j);
end;
end;
Similarly, continue MainLoop; skips the rest of the current iteration in both the inner loop and the main loop. Every loop in Mindcode can be marked with a label, and the break or continue statements can use those labels to specify which of the currently active loops they operate on.
When an explicit label is not specified for a loop statement, it is possible to use an implicit label loop:
loop
println("Outer 1");
while true do
println("Inner 1");
break loop;
println("Inner 2");
end;
println("Outer 2");
end;
Both the print("Inner 2"); and print("Outer 2"); statements are skipped by the break loop; statement.
It is possible to use the break statement to exit a code block, be it a regular, debug or atomic block. A label needs to be used with the break statement meant to exit a code block. Labels are specified in the same way as in the case of loops:
MainBlock: begin
print("Before");
break MainBlock;
print("After"); // Unreachable code
end;
Note
The break statement used without a label can break only out of loops, never out of a code block.
The keyword that opens a loop (that is, do, for, loop and while) or a code block (atomic, begin and debug) can be used as a label in break and continue statements, assuming that an explicit label hasn't been assigned to the loop or code block. This implicit label can be used only when its use is unambiguous, meaning that only one instance of a matching loop or code block is active at that point in the program. For example:
for i in 0 ... 10 do
j = ceil(rand(100));
while j > 0 do
if j * i < 10 then
break for; // Breaks the outer loop
end;
j--;
end;
println(i);
end;
printflush(message1);
The implicit labels become ambiguous when the same types of loops or blocks are nested:
for i in 0 ... 10 do
for j in 0 ... 10 do
println(i, ", ", j);
if i * j > 75 then
break for; // Error: the label 'for' is ambiguous here
end;
end;
end;
The end() function maps to the end instruction, and as such has a special meaning – it resets the execution of the program and starts it from the beginning again. In this sense, the end() function is one of control flow statements. The function may be called from anywhere, even from a recursive function. The following rules apply when the function is invoked:
- the processor starts executing the program from the beginning,
- values of existing variables are preserved (the last value written to any uninitialized1 global or main variable before
end()is called is preserved), - the call stack is reset – calling recursive functions starts from the topmost level again.
« Previous: Expressions | Up: Contents | Next: Functions »
Footnotes
-
Only uninitialized variables are handled this way. Any value assigned to an initialized variable before calling
end()would get overwritten with whatever value the variable is initialized to when the program execution is restarted. ↩