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.