Control statements - IS4Code/Sona GitHub Wiki
Control statements alter the flow of code and affect the execution of other statements. They are usually found together with normal statements in blocks, but they are also accepted in certain other contexts, such as in collection expressions, or after inline
.
do
The do
statement takes a single block and executes it. The do
keyword is also found as a part of other statements where it separates a block from the rest of the statement.
Syntax
do_statement:
'do'
statements
'end';
if
The if
statement evaluates a series of conditions and executes the first branch where the condition is true
, or the else
branch, if present.
Syntax
if_statement:
'if' expression 'then'
statements
('elseif' expression 'then'
statements)*
('else'
statements),
'end';
while
The while
statement repeatedly executes a block in a loop while a condition is true
. The condition is re-evaluated upon each entry into the loop. Inside the statement, break
and continue
may be used to skip the rest of the block and exit the statement or re-enter it from the beginning, respectively.
Syntax
while_statement:
'while' expression 'do'
statements
'end';
A special version of the statement, while true do
(or any version with true
wrapped in parentheses), can be used to loop forever, unless the loop is interrupted or an exception is thrown. This version may be regarded as terminating.
repeat…until
The repeat…until
statement is similar to while
, but evaluates the condition after the body is executed, and only exits the loop if the condition is true
. It is possible to use variables created within the body in the condition.
Syntax
repeat_statement:
'repeat'
statements
'until' expression;
for…in
The for…in
statement iterates over all elements in a given sequence, repeatedly executing the block with a given variable set to each element of the sequence.
Syntax
for_statement:
'for' variable 'in' (
expression |
expression '..' expression ('by' expression)
) 'do'
statements
'end';
The ..
version is preferred when the expression is formed by ..
, in which case the loop iterates over a range from the left operand to the right one, optionally with a given amount to skip (1 by default). This interpretation is used even when the ..
operator would not denote a range (but concatenation) of the operands, in which case wrapping the operation into parentheses is necessary.
switch
The switch
statement matches the result of an expression against a set of patterns, denoted by case
, entering the first block corresponding to a matching pattern.
Syntax
switch_statement:
'switch' expression
('case' pattern? ('when' expression)? 'do'
statements)+
('else'
statements)?
'end';
Each case
branch may also have a corresponding when
clause, containing an additional condition for successful match. The condition may use variables declared in the pattern. The pattern may be missing, in which case the when
clause is mandatory. The statement may end with an else
branch, which is entered if no other case
was successful. The else
branch must be present if other branches do not handle all cases of the input.
Within a case
or else
branch, the continue
statement together with an expression may be used to match a different value against the same patterns, re-entering the statement. Other occurrences of continue
or break
are an error.
try
The try
statement executes a given block and handles exceptions thrown within the block.
Syntax
try_statement:
'try'
statements
('case' pattern ('when' expression)? 'do'
statements)*
('else'
statements)?
('finally'
statements)?
'end';
The case
and else
branches have the same interpretation as in switch
, but match the exception raised by the main body of the statement. The finally
branch is executed in all situations upon leaving the statement, regardless of an exception being thrown. Unhandled exceptions are thrown outside of the statement.
At least one of the case
, else
, or finally
branches must be present.
Within try
and finally
, the break
and continue
statements are permitted and control of the enclosing statement that supports them. Within case
or else
, only the continue
statement with an expression is permitted, accepting an Exception
value.
Syntactic categories
A control statement can be syntactically placed in a specific category based on all execution paths going through the statement, including all the immediate statements following it (the trailing statements):
- Terminating, if all paths contain
throw
or another terminating statement, and do not go through atry
statement that may catch the exception. - Interrupting, if some paths contain
break
orcontinue
or another interrupting statement, unless the path goes through a control statement to which the interruptible statement pertains, and all other paths contain a terminating statement. - Returning, if some paths contain
return
or another returning statement, and all other paths contain a terminating or interrupting statement. - Interruptible, if some paths contain an interrupting statement and at least one other path is open, or some paths contain an interruptible statement. The other paths are open or contain a terminating statement.
- Conditional, if some paths contain a returning statement and among the other paths at least one is not terminating, or some paths contain a conditional statement.
- Free, if not in any other category.
The recursive nature of these rules is important to be able to bypass syntactic restrictions on normal statements, for example throw
and do throw end
are both considered terminating.
The final category of a statement can be also determined by first setting up all paths going through it as terminating, and gradually replacing any terminating path with one of a different kind. This is illustrated in the following image; O corresponds to adding an open path, R to a returning path, and I to an interrupting path:
/images/Control-statements-categories.svg
The overall distinction is necessary to maintain because F# does not support exiting a block natively, and so all such blocks need to be rewritten in a semantically equivalent form without any unsupported statements:
- Free statements do not affect the flow of code in any way, and do not require any special behaviour.
- The trailing statements of returning, interrupting, and terminating statements are ignored. While they are treated during compilation as any other piece of code, they do not participate in any relevant execution paths.
- Terminating statements are regarded as able to produce a value of any type (by virtue of never returning). A function ending with a terminating statement is thus also able to produce a value of any type. They also act as a sort of "wildcard", by acting as returning or interrupting when necessary.
- Returning statements always produce a single value. A function ending with a returning statement is thus able to get its value from it directly.
- Interrupting statements affect the behaviour of enclosing statements, and require similar handling as returning statements, but do not have effect outside the control statement they relate to. Importantly, mixing returning and interrupting statements makes the whole statement returning ‒ the reason is that
return
already needs to interrupt a block and so covers everythingbreak
must be able to do, while interrupting statements do not affect outside statements when contained in control statements they affect, unlikereturn
. - Interruptible and conditional statements are the open versions of interrupting and returning statements, respectively, containing at least one open path.
Note that the placement of a statement in one of these categories depends on syntactical analysis, not semantic. This means that the mere presence/absence of return
, throw
or similar within the relevant parts of the statement is the sole deciding factor, not information that could be derived from types, constants, or similar.
Examples
if c then
print(x)
end
The if
is a free statement, since there are no special statements that would affect its category.
if c then
return x
end
This if
is a conditional statement, since one path through it (when not c
) does not go through the return
.
if c then
return x
end
return y
This if
is a returning statement. Even though the if
itself did not change, the non-returning path is now returning thanks to the trailing return
.
if c then
throw x
end
return y
This if
could be considered a free statement or a returning statement, since the trailing statements are returning. Regardless, the semantics of the whole block are the same.
if c then
throw x
else
throw y
end
return z
This if
is a throwing statement. The trailing return
is ignored.