Menu

Syntax-Statements

Ove Kåven

Variables

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

Assignments

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.

Expressions

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.

Compound 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")
}

If statements

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

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.

While loops

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

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

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.

Control flow statements

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

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.)

Goto statements

(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")
}

Related

Wiki: Syntax

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.