Statements
Frost code is comprised of a sequence of statements. Statements are executed one at a time, in order, except where control flow statements specifically repeat or skip statements. The following statement types are supported:
Assignment
<l-value> <assignmentOperator> <value>
An l-value is a value which can be assigned to. L-values include variables, fields, and index
operator expressions such as array[0]. The assignmentOperator specifies the kind of assignment
to be performed, either simple assignment (:=) or one of the compound assignment operators (+=,
+&=, -=, -&=, =, &=, /=, //=, //&=, %=, <<=, <<&=, >>=, >>>=, &=,
|=, ~=, &&=, ||=, or~~=). Simple assignment makes <l-value> equal to <r-value>,
whereas the compound assignment operators make <l-value> equal to
<l-value> <operator> <r-value>. Unlike in C and related languages, assignment is not an expression
and the assignment operators do not produce a value.
When the right side of the assignment is a tuple, you may assign to multiple values at once by 'destructuring' it in a multiple assignment. For instance, if we have a function that returns a tuple:
function origin():(Int, Int) { return 0, 0 }
we can pull the tuple apart during assignment by assigning it to a corresponding tuple of destinations:
def (x, y) := origin()
This gives both x and y the value 0. See destructuring tuples for
more about multiple assignment.
Method Calls
[<object>.]<methodName>(<parameters>)
A method call statement invokes a method. If the context <object> is omitted,
the method will be resolved against self or the current class. The
return values of methods called as statements are ignored.
Functions may not be called as statements: the only reason to call a method as a statement is for the side effects it may have, and functions do not have side effects.
A method call <object> may be the keyword super. This is equivalent to self, except that the
superclass' implementation of the method is used. For example,
@override
function convert():String {
    return "<Derived:\{super.convert()}>"
}
if
if <condition> {
    <statements>
}
if <condition> {
    <statements>
}
else {
    <statements>
}
The if statement makes a decision based on the value of a Bit expression. If the Bit is
true, the statements under the if are executed. If the Bit is false, then the statements
under the else (if there is an else) are executed. For instance,
def x := getValue()
if x > 5 {
    Console.printLine("x is greater than 5")
}
else if x < 5< 5 {
    Console.printLine("x was less than 5")
}
else {
    Console.printLine("x was equal to 5")
}
for
for <target> in <collection> {
    <statements>
}
<label>: for <target> in <collection> {
    <statements>
}
The for loop iterates over a collection one entry at a time, with <target> taking on the value
of the next element in the collection with each successive pass through the loop. <collection> may
be an Iterator, Iterable, Range, or SteppedRange. As a simple example, the loop:
for i in 1 ... 5 {
    Console.printLine(i)
}
will display the numbers 1 through 5, while:
for greeting in ["Hello", "Bonjour", "Konnichi wa"] {
    Console.printLine(greeting)
}
will display greetings in several different languages.
The <target> may optionally have a type specifier, as in for i:UInt8 in 1 ... 5.
The optional <label> before the loop allows break and continue statements to refer to it by
name. It is otherwise ignored.
If <collection> is a collection of tuples, <target> may be a parenthesized list
of variable names to split the tuple into separate variables:
def list := [("Five", 5), ("Twelve", 12), ("Forty-two", 42)]
for (name, value) in list {
    numbers[name] := value
}
while
while <test> {
    <statements>
}
<label>: while <test> {
    <statements>
}
The while statement repeatedly executes a block of statements so long as its Bit-valued test is
true.
The optional <label> before the loop allows break and continue statements to refer to it by
name. It is otherwise ignored.
do
do {
    <statements>
}
while <test>
label: do {
    <statements>
}
while <test>
The do loop is very similar the while loop, but it checks its condition at the end of the loop
rather than the beginning. The loop body will therefore always execute at least once.
The optional <label> before the loop allows break and continue statements to refer to it by
name. It is otherwise ignored.
loop
loop {
    <statements>
}
label: loop {
    <statements>
}
The loop statement runs its body over and over indefinitely. It is an infinite loop unless a
break or return statement escapes from it.
The optional <label> before the loop allows break and continue statements to refer to it by
name. It is otherwise ignored.
break
break
break <label>
The break statement immediately terminates a for, while, do, or loop, causing execution to
continue from the statement immediately after the end of the loop. Normally, break terminates the
innermost loop it is contained within, as in:
loop {
    var value := getValue()
    if value = null {
        break
    }
    sendValue(value)
}
break with a label can be used to break out of multiple nested loops:
outer: for x in 0 .. width {
    for y in 0 .. height {
        processCell(x, y)
        if !isValid()
            break outer
    }
}
continue
continue
continue <label>
The continue statement immediately skips ahead to the next iteration of a for, while, do, or
loop. As with break, continue normally affects the innermost loop it is contained within, but
the optional <label> allows it to refer to loops other than the innermost one.
return
return
return <value>
The return statement immediately exits the current method and (for methods which return a value)
causes it to return the specified value. Return statements in methods which return values must
always provide a value, and return statements in methods which do not return values may never
provide a value.
assert
assert <condition>
assert <condition>, <message>
An assert statement tells the compiler that <condition> should always be true at this point.
With safety checks on, the <condition> is double-checked at run time and, if it is found to in
fact be false, the program terminates with an error message. The optional <message> is a string
providing a specific message to display upon failure.
With safety checks off, the assertion is assumed to be true and is not double-checked at runtime.
This allows the compiler to optimize the code under the assumption that the condition is in fact
true. Any code that would cause an assertion failure becomes undefined behavior when safety checks
are disabled.
Assertions which always evaluate to false are a compile-time error. In other languages, you may be
used to using something equivalent to assert false to signify "it should not be possible for this
line of code to be reached", but in Frost assert false will not compile. You need to use
unreachable, described below, to express this.
IMPLEMENTATION NOTE: "assert false" isn't actually a compiler error yet, but it will be soon.
unreachable
unreachable
unreachable, <message>
The unreachable statement tells the compiler that this line of code will never actually be
reached. For instance, in the code:
def widget:Widget
if widgetReady() {
    widget := nextWidget()
}
else if canManufactureWidget() {
    widget := makeWidget()
}
processWidget(widget) -- error!
we will receive a compilation error because widget is not definitely assigned at the point where
it is used. What if there is no ready widget and we cannot manufacture a new one? But suppose we
know for sure that one of those conditions will always be true. We can then modify the code to read:
def widget:Widget
if widgetReady()
    widget := nextWidget()
else if canManufactureWidget()
    widget := makeWidget()
else {
    -- can't happen!
    unreachable
}
processWidget(widget)
and the code will then compile. We are asserting to the compiler that there is always either a
widget ready or we will be able to manufacture one, and that the final else clause cannot actually
be reached. The widget variable is therefore definitely assigned, because the only possible path
by which widget would remain unassigned is known to be unreachable.
Actually reaching an unreachable statement is, of course, bad. By default, this will result in a
safety violation which causes the program to terminate. If <message> is provided, the message will
be displayed in the event that the unreachable is encountered.
If safety checks are disabled, the compiler is free to optimize under the assumption that no
unreachable code is actually reached, and reaching unreachable therefore causes undefined
behavior.
match
match <expression> {
    when <value1> {
        <statements>
    }
    when <value2>, <value3> {
        <statements>
    }
    otherwise {
        <statements>
    }
}
The match statement runs one of a number of blocks based on the value of its <expression>.
match is a generalization of if: if runs one of two blocks based on the value of its Bit
expression, whereas match runs one of any number of blocks based on the value of its expression.
Each when may have any number of values, and the when will be run if the <expression> matches 
any of them. If none of the values in any when match, the optional otherwise block is run if
present. If none of the when values match and there is no otherwise block, execution simply
continues after the end of the match.
Unlike the similar switch statement in many other languages, Frost's match statement does
not have "fall-through": when the statements associated with a when finish, execution continues
after the end of the match statement.
When matching a choice, the when conditions may destructure the choice and
extract the values it contains. For example, given the choice:
choice Node {
    TAG(String, ImmutableArray<Node>)
    TEXT(String)
}
we can use match to both determine what kind of Node we are dealing with and to extract the data
it contains:
function text(node:Node):String {
    match node {
        when Node.TAG(name, children) {
            return "<\{name}>\{children.map(c => text(c)).join(", ")}</{name}>"
        }
        when Node.TEXT(text) {
            return text
        }
    }
}
try
try {
    ...
}
fail(<error>) {
    ...
}
Frost's try statement takes any errors occurring within the try block and forwards them to the
fail block. While this superficially resembles exception handling, it is quite different; see
error handling for more information.