Local variables can be used to store values or references temporarily. They are destroyed once the program execution leaves the scope they're defined in. They can be created in several ways:
num: int
This will define num
as an integer, and initialize it with its default value. For integers, the default value is zero. For reference types, the default value is null
. (Although it may sound inefficient to always initialize a variable when it's created, it isn't really. If the initialization turns out to be redundant, the compiler can always optimize it away later. Thus, defining predictable behaviour, to simplify coding (and avoiding bugs), is considered more important.)
num: int = 5
This will define num
as an integer, and initialize it with the value 5. Any expression can be used as an initializer (as long as the types are compatible).
obj: File("/dev/null", "r")
This will define obj
as a file object, and initialize it by calling its allocator/constructor, passing it the two given strings. In this case, the allocator will open the file /dev/null
for reading. You can then use obj
to read from it.
num ::= 5
This will define num
as an integer because the initializer, 5
, is an integer. Only the type of the initializer matters, the type of any later assignments to the variable are not taken into account.
num = 5
(UNDER CONSIDERATION) If num
is not already defined, then this will create a new variable using the type of the initializer (integer in this case). Again, only the type of the first assignment matters, the type of later assignments are not taken into account. (This technique is not recommended for production code, because if someone later defines a global variable or class field called num
, then your code will suddenly use it instead of creating a local variable. However, for casual programming it is offered as a convenience.)
It's possible to declare a variable as static
. In this case, the variable, and its value, is not destroyed before the program itself terminates. Example:
static num: int
Assigning to a variable copies the value or reference to it. Any expression can be assigned to the variable; the value of the expression is computed, and then copied to the variable.
num = 10
Note that an assignment is a statement, not an operator. You cannot use the equals sign like this inside expressions like you can in C. This allows the symbol to be used for other useful things (such as naming parameters in calls), and also stops inexperienced programmers from doing assignments when they mean to use the equality operator ==
. (If you really want to do assignments inside expressions, you can use :=
, the inline assignment operator.)
Since the value of the expression is always computed before the assignment is done, it is possible to use the old value of a variable to compute a new value for the same variable.
num = num + 1
This would increment the value of num
by 1. This type of assignment also has a shorthand form:
num += 1
Conditions can be added to assignment statements.
num = 10 if num < 10
This will only assign 10
to num
if it was smaller than 10
before the assignment.
A bare expression can be a statement. This is mainly useful if the expression is a call.
printf("Hello World\n")
Just as with assignments, conditions can be added to expression statements.
Any place where a single statement is accepted, it's possible to use multiple statements if they are enclosed in curly brackets. This also creates a new scope, which can hold its own local variables.
{ num: int = 10 printf("Hello\n") }
The if
statement allows statements of any kind to be executed conditionally. There are several ways to write if
statements. The standard way is to use compound statements.
if num < 10 { num = 10 }
An if
statement may have an else
clause, which is executed if the condition was false.
if check_status() { printf("All OK\n") } else { printf("Failed\n") }
if
statements can be chained (though, in many cases, it may be better to use a switch
statement instead).
if num == 5 { printf("Found a 5\n") } else if num == 6 { printf("Found a 6\n") } else { printf("Found neither\n") }
Compound statements are not mandatory. Most statements that start with a keyword are recognized.
if num < 10 return num
Otherwise, you can use the then
keyword.
if num < 10 then num = 10
In such cases, however, you can usually add conditions to statements instead.
num = 10 if num < 10
if
statements can be used as expressions. In this case, the last statement of both branches must be an expression to return.
result: bool = if check_status() { printf("All OK\n") true } else { printf("Failed\n") false }
To embed an if
into a larger expression, enclose it in parentheses.
data = (if num < 10 then 10 else num) - 10
switch
statements allow statements to be executed depending on the value of a variable or expression. While an if
statement can only check one value at a time, a switch
statement can check many. Thus, though switch statements could be emulated using if
chains, switch
statements are usually both more efficient and more readable.
switch num { case 5: printf("Found a 5\n") case 6: printf("Found a 6\n") case 10, 20: printf("Found a 10 or 20\n") default: printf("Found neither\n") }
Unlike in C, there is no implicit fallthrough, so break
statements are not needed (though they are allowed). This is because the majority of cases do not need fallthrough. (If you do need such functionality, you may use goto case
statements.)
Each case
is its own scope. There's no need for extra curly brackets if you need to define local variables inside a particular case
.
The while
loop allows statements to be executed repeatedly as long as a condition is true. The condition is tested before every loop iteration (but not during them). The standard way to write while
loops is to use compound statements.
while num < 10 { num = num + 1 }
As with the if
statement, compound statements are not mandatory. Some statements that start with a keyword are recognized (but not as many as for the if
statement). Otherwise, you can use the do
keyword.
while num < 10 do num = num + 1
do while
loops are like while loops, except that the condition is first tested after the first loop iteration, instead of before. This means that the statements are always executed at least once.
do { num = num + 1 } while num < 10
Or, without compound statements:
do num = num + 1 while num < 10
for
loops come in a couple of variants. The simple one iterates over all elements in a container or range.
for x in the_list { do_something(x) }
In this case, the_list
is a variable referring to a container object holding a list of objects. The statements are executed once for each such object, with the local variable x
referring to that object. The scope of the variable is only the for loop itself.
The other variant is the C-style for loop.
for (num=0; num<10; num+=1) { do_something(num) }
Here, the parentheses are required. They contain three statement parts separated by semicolons. Each part is optional, but the semicolons are mandatory. The first part is a comma-separated initializer list, which can contain definitions of new variables and/or assignments to already existing variables. If a new variable is defined here, its scope is only the for loop itself. The second part is a condition. As in a while loop, it is tested before the first iteration. The third part is a comma-separated list of assignments to perform after every iteration, just before testing the condition again.
The break
statement breaks out of a loop or switch
statement. Example:
do { if need_to_abort() { break } num = num + 1 } while num < 10
The continue
statement breaks out of the current iteration of a loop, but not the loop itself. The next iteration is prepared as normal (including any for
-loop assignments, and testing of any loop condition). Example:
do { if need_to_try_again() { continue } num = num + 1 } while num < 10
The return
statement ends execution of a function and returns the provided value, if any. Example:
return num
As with assignments, conditions can be added to control flow statements.
Try statements are used to recover from exceptions. Exceptions are usually thrown when some error occurs. When exceptions are thrown, normal execution aborts. If the exception was thrown in scope of a try statement which has an exception handler that matches the exception, then execution jumps to that handler.
try { risky_operation() finish_operation() } catch err: IOError { handle_io_error(err) }
The above will catch any IOError
exceptions thrown by risky_operation
or finish_operation
, and recover from them by calling handle_io_error
. (If the exception was thrown by risky_operation
, then finish_operation
is not executed.) Other types of exceptions are not caught.
try { risky_operation() finish_operation() } catch { handle_any_error() }
The above will catch any exception, no matter what type. However, there's no way to determine the type of exception, so this should only be used as a last-resort handler. For example:
try { risky_operation() finish_operation() } catch err: IOError { handle_io_error(err) } catch { handle_any_error() }
The above handles IOError
by calling handle_io_error
, and also handles all other types of exceptions by calling handle_any_error
.
try { risky_operation() } finally { finish_operation() }
The above forces finish_operation
to be called even if an exception is thrown by risky_operation
. However, it does not actually catch the exception. If this try statement is executing within another try statement, then the exception is passed to the outer try statement after calling finish_operation
.
try { risky_operation() } finally { finish_operation() } catch err: IOError { handle_io_error(err) } catch { handle_any_error() }
The above combines all of the described handlers. If an exception is thrown from risky_operation
, then finish_operation
is called anyway, after the appropriate exception handler. (However, exceptions thrown by finish_operation
will not be handled by this try statement.)
(NOT IMPLEMENTED YET)
goto
statements can be used to cause execution to jump directly to some given point inside a function. They are subject to several restrictions. For example, you are, in general, not allowed to jump directly into a scope you're not already in. Most of the time, goto
statements should be avoided in favour of other language features (like control flow statements, or exceptions and try statements), but sometimes they are useful.
Goto statements come in a couple of variants. The regular one requires that you explicitly declare jump targets using label
:
label my_label num = num + 1 goto my_label
There's also a variant that can be used inside switch
statements:
switch num { case 5: printf("Handle 5\n") goto case 6 case 6: printf("Handle 5 or 6\n") }