Skip to content

Language Concepts: Statements

Luiz Fernando edited this page Apr 5, 2015 · 11 revisions

Statements

ZScript features a very handy and familiar set of control flow statements:

and miscellaneous statements:

Blocks of statements can be grouped with curly brackets ('{' and '}'), and can be used in place statements are accepted.

The syntax for the control flow statements available in ZScript are as follow:

if/else if/else

Syntax: if( <expression> ) <statement>, with else: if( <expression> ) <statement1> else <statement2>, with chaining of if's: if( <expression> ) <statement1> else if( <expression> ) <statement2> else if ... else < statement >

if executes a statement if a condition evaluates to true:

var a:int = 10;
var b:int = 20;

if(a < b) // a (10) is less than b (20), so this evaluates to true
{
  // Statements contained here get executed
}

An if statement can be immediately followed by an else statement, which executes in case the if fails:

var a:int = 10;
var b:int = 20;

if(a > b)
{
  print("a is greater than b");
}
else
{
  print("a is not greater than b");
}

If/else statements can be chained, ensuring no more than one of the blocks of the chain is executed:

var a:int = 10;
var b:int = 20;

if(a > b)
{
  print("a is greater than b");
}
else if(a == b)
{
  print("a is equals to b");
}
else
{
  print("a is less than b");
}

Note that the else is optional after 'else if' blocks, too:

var a:int = 10;
var b:int = 20;

if(a > b)
{
  print("a is greater than b");
}
else if(a == b)
{
  print("a is equals to b");
}
// None of the above blocks execute

The curly brackets around the statements being executed is completely optional:

var a:int = 10;
var b:int = 20;

if(a > b)
  print("a is greater than b");
else if(a == b)
  print("a is equals to b");

Though multiple sequential statements not in a block will fail, because only the first statement is considered part of the if statement:

var a:int = 10;
var b:int = 20;

if(a > b)
  print("a is greater than b");
  print("always executes");     // Here, this print always executes because it is interpreted as being outside the `if` statement

while

Syntax: while( <condition> ) < statement >

While is a type of loop control flow that executes a statement until a certain condition evaluates to false.

Sample:

var count:int = 0;

while(count < 10)
{
    print(count);
    
    // Increase count by one
    count++;
}

Prints:

0
1
2
3
4
5
6
7
8
9

for

Syntax: for( <init> ; <condition> ; <increment> ) <statement>

Performs a 'for' style looping by executing the initial <init> statement, and performing the sequence <condition> -> <statement> -> <increment> over and over while <condition> does not evaluate to false.

The <init> may either be an expression, an assignment expression or a variable declare statement, and the <condition> needs to return a bool type.

Sample 1:

for(var i = 0; i < 10; i++)
{
    print(i);
}

Prints:

0
1
2
3
4
5
6
7
8
9

The <init>, <condition> and <increment> expressions can be omitted in any combination:

Sample 2:

var i = 0;
for(; i < 10;)
{
    print(i);
    i++;
}

Is an equivalent of the first sample, and prints:

0
1
2
3
4
5
6
7
8
9

If the <condition> is omitted, the loop runs indefinitely.

for each

A common use case for for loops is the iteration through items of a list, and for those cases, a special syntax is available for the for loop, called for each:

for(var item in list)
{
    ...
}

In for each loops, the runtime automatically iterates through and assigns the values contained in list in the item variable for each iteration of the loop.

var list = [0, 1, 2, 3];
for(var item in list)
{
    print(list);
}

Prints:

0
1
2
3

You can also use a let declaration to create a constant iteration item, which is useful when you want to avoid accidentally reassigning the iterator item in the body of the loop:

var list = [0, 1, 2, 3];
for(let item in list)
{
    item = 1; // Error: Cannot reassign constant 'item'
    print(list);
}

Note: Values provided to iterate in a for each loop must be a list type.

switch

Syntax: switch( <expression> ) { case <value1>: <statements>, case <value2>: <statements>, ..., case <valueN>: <statements>, [default: <statements>] }
Author's note: I'm currently evaluating an alternative syntax for the case labels to allow different types of comparisions to be performed with the values and to allow multiple value matches to be made in the same 'case' label (as an alternative to sequential 'case' labels)

Evaluates the value of an expression, jumping to a specific internal case label of matching value, and if it fails to match with any case label, jumps to a default label, if present.

A switch statement works as a more terse alternative to if/else blocks, in case of chained comparisons of the same value:

Sample 1:

var peopleCount = 3;

switch(peopleCount)
{
  case 1:
    print("One person in the house");
    break;
  
  case 2:
    print("Two people in the house");
    break;
  
  case 3:
    print("Three people in the house - the house is full!");
    break;
}

Prints:

Three people in the house - the house is full!

Cases in a switch statement can be grouped by putting one label next to the other. In such cases, the statements after the grouped labels will execute if any of the cases matches the expression:

Sample 2:

var peopleCount = 1;

switch(peopleCount)
{
  case 0:
  case 1:
    print("Less than two people in the house");
    break;
  
  case 2:
    print("Two people in the house");
    break;
  
  case 3:
    print("Three people in the house - the house is full!");
    break;
}

Prints:

Less than two people in the house

The break statements inside the case labels is used to stop the code from flowing through the case labels:

Sample 3:

var saladOrders = 2;

switch(saladOrders)
{
  case 1:
    print("One salad order for the table");
    break;
  
  case 2:
    print("Two salad orders for the table");
  
  case 3:
    print("Three salad orders for the table - damn, these 'sum healthy people!");
    break;
}

Prints:

Two salad orders for the table
Three salad orders for the table - damn, these 'sum healthy people!

because the case 2 label didn't contain a break statement: the code 'flowed' through the next case label instead.
Author's note: I'm currently evaluating a non-gotcha alternative to implicit case fall-through in the form of a special syntax that makes the fall-through explicit

Switch statement variable

It is possible to use a value holder definition as a switch expression, and that lets you access the value that was switched upon. This is useful in situations where the switch and case labels are composed of complex expressions, the value of which you may want to reference inside a case label:

for(var i:int = 2; i < 5; i++)
{
  for(var j:int = 1; j < 5; j++)
  {
    switch(let value = i % j)
    {
      case 1:
      case 2:
      case 3:
      case 4:
        print(i, '%', j, '=', value);
    }
  }
}

Prints:

2 % 3 = 2 
2 % 4 = 2 
3 % 2 = 1 
3 % 4 = 3 
4 % 3 = 1 
Things to consider with switch statement
Types in case/switch expressions must match

The types on each of the case label expressions must match the type of the compared expression provided to the switch statement, raising errors if the types do not match:

Sample 4:

var dogId = 1;

switch(dogId)
{
  case 1:
    print("Spike");
    break;
  
  // Invalid: string type is not compatible with int type
  case "Ruffus":
    print("Ruffus");
  
  case 3:
    print("Fluffy");
    break;
}

break

Break statements function as context-sensitive flow-breaks. Break statements are only allowed within loop-type statements (for and while) and switch statements.

Switch statements require breaks in case labels, in cases where it is not desired to execute more than one label sequentially. If missing, the code 'falls through' the next case label in the sequence:

Sample 1:

var saladOrders = 2;

switch(saladOrders)
{
  case 1:
    print("One salad order for the table");
    break;
  
  case 2:
    print("Two salad orders for the table");
  
  case 3:
    print("Three salad orders for the table - damn, these 'sum healthy people!");
    break;
}

Prints:

Two salad orders for the table
Three salad orders for the table - damn, these 'sum healthy people!

because the case 2 label didn't contain a break statement: the code 'flowed' through the next case label instead.

Loop-type statements can be stopped at any time by calling a break statement within them:

Sample 1:

var count:int = 0;

while(count < 10)
{
    print(count);
    
    // Increase count by one
    count++;
    
    if(count > 4)
    {
        print("break!");
        break;
    }
}

Prints:

0
1
2
3
4
break!

Sample 2:

var count:int = 0;

for(;;)
{
    print(count);
    
    // Increase count by one
    count++;
    
    if(count > 4)
    {
        print("break!");
        break;
    }
}

Is an equivalent of the first sample, and prints:

0
1
2
3
4
break!

continue

Continue statements function as context-sensitive flow-breaks inside loop-type statements (for and while).

When called inside a loop, the flow is immediately jumped to the next iteration of the loop, just before its condition, effectively skipping the rest of the statements:

var count:int = 0;

while(count < 10)
{
    // Increase count by one
    count++;
    
    // When this continue hits, it skips the 'print' statement bellow and jumps right back to the `while`'s condition
    if(count > 4)
        continue;
    
    print(count);
}

Prints:

1
2
3
4

On for loops, continue statements jump to the next iteration of the loop:

for(var i = 0; i < 10; i++)
{
    // When this continue hits, it skips the 'print' statement bellow and jumps right back to the loop's top
    if(i > 4)
        continue;
    
    print(i);
}

Prints:

0
1
2
3
4

return

Return works as a function-wise stop that breaks out of the currently executed function immediately and returns control to the caller, optionally specifying a value to be returned alongside:

func printOne()
{
  print("1");
  
  // Return statement stops the function immediately, preventing the 'print("2")' statement from executing
  return;
  
  print("2");
}

On functions that require a return value (more on that on the next section), return statements are required to specify a value to return to the caller:

func sum(n1:int, n2:int) : int
{
  // Values are specified by adding a value between the 'return' the keyword and the semicolon
  return n1 + n2;
}

More about return statements is explained in the next section about Functions