Statement and Expression Catalogue
This chapter presents a catalogue of statements and expressions in Sather and descriptions of them that originated in the specification. In some cases, these definitions are duplicated elsewhwere in the text. However, they have been included here, sometimes with more elaborate examples, as a convenient reference.
15.1.1 Assignment statements
Assignment statements are used to assign objects to variables or attributes. The expression on the right hand side must have a return type which is a subtype of the declared type of the destination specified by the left hand side. When a reference object is assigned to a location, only a reference to the object is assigned. This means that later changes to the state of the object will be observable from the assigned location. Since immutable and closure objects cannot be modified once constructed, this issue is not relevant to them.
| ||b(7).c := 5;|
| ||A::d := 5;|
| || := 4;|
| ||e[7,8] := 5;|
| ||g:INT := 5;|
| ||h ::= 5;|
An assignment can also declare new local variables using the ::= syntax.
The operation of assignment statements on attributes is described in the section on Attribute Accessor Routines. They are often syntactic sugar for function calls with one argument, which is the right hand side.
- Type inference in assignment statements on page 53.
- Attribute assignment sugar on page 51.
- Array element assignment on page 118.
- Immutable class attribute assignment on page 121.
15.1.2 case statements
Multi-way branches are implemented by case statements. There may be an arbitrary number of when clauses and an optional else clause. The initial expression construct is evaluated first and may have a return value of any type. This type must define one or more routines named 'is_eq' with a single argument and a boolean return value.The expressions tested in the branches of the if statement are the expressions of successive when lists. The first one of these calls to returns true causes the corresponding statement list to be executed and control passed to the statement following the case statement. If none of the when expressions matches and an else clause is present, then the statement list following the else clause is executed
|Example:||case i |
| ||when 5,6 then ...|
| ||when j then|
| ||else ...|
There is one difference between the case statement and the equivalent if statement. If none of the branches of an if statement match and no else clause is present, then execution just continues onto the next statement after the if statement. However, if none of the branches of the case statement matches and there is no else clause, then a fatal run-time error will result.
Points to note
- It is a fatal error if no branch matches and there is no else clause.
15.1.3 if statements
if statements are used to conditionally execute statement lists according to the value of a boolean expression. Each expression that is tested must return a boolean value. The first expression is evaluated and if it is true, the following statement list is executed. If it is false, then the expressions of successive elsif clauses are evaluated in order. The statement list following the first of these to return true is executed. If none of the expressions return true and there is an else clause, then its statement list is executed. Note that the else clause is not compulsory.
|Example:||if a>5 then foo|
| ||elsif a>2 then bar|
| ||else error|
15.1.4 protect statements
Exceptions may be explicitly raised by a program or generated by the system. Each exception is represented by an exception object whose type is used to select a handler from a protect statement. Execution of a protect statement begins with the statement list following the 'protect' keyword. These statements are executed to completion unless an exception is raised which is not caught by some nested protect.
|Example:||protect < some statements >|
| ||when $STR then |
| || #ERR+exception.str;|
| ||when FOO then|
| || #ERR+exception.foobar;|
When there is an uncaught exception in a protect statement, the system finds the first type specifier listed in the 'when' lists which is a supertype of the exception object type. The statement list following this specifier is executed and then control passes to the statement following the protect statement. An exception expression may be used to access the exception object in these handler statements. If none of the specified types are supertypes, then the statements in an 'else' clause are executed if it is present. If it is not present, the same exception object is raised to the next most recently entered protect statement which is still in progress. It is a fatal error to raise an exception which is not handled by some protect statement. protect statements may only contain iterator calls if they also contain the surrounding loop statement. protect statements without an else clause must have at least one when.
- Statement description on page 132 and the chapter on exceptions in general.
15.1.5 loop statements
Iteration is done with loop statements, used in conjunction with iterator calls. An execution state is maintained for each textual iterator call. When a loop is entered, the execution state of all enclosed iterator calls is initialized. When an iterator is first called in a loop, the expressions for self and for each once argument are evaluated left to right. Then the expressions for arguments which are not once (in or inout before the call, out or inout after the call; are evaluated left to right. On subsequent calls, only the expressions for arguments which are not once are re-evaluated. self and any once arguments retain their earlier values. The expressions for self and for once arguments may not themselves contain iterator calls (such iters would only execute their first iteration.) .
|Example:||f: INT:=4; --Compute b factorial|
| ||res: INT := 1;|
| ||i :INT := 1;|
| ||loop until!(i > f);|
| || res:= res * i;|
| || i := i + 1;|
When an iterator is called, it executes the statements in its body in order. If it executes a yield statement, control is returned to the caller. Subsequent calls on the iterator resume execution with the statement following the yield statement. If an iterator executes quit or reaches the end of its body, control passes immediately to the end of the innermost enclosing loop statement in the caller and no value is returned.
- Statement description on page 59 and the chapter on iterators in general.
15.1.6 return statements
return statements are used to return from routine calls. No other statements may follow a return statement in a statement list because they could never be executed. If a routine doesn't have a return value then it may return either by executing a return statement without an expression portion or by executing the last statement in the routine body.
|Examples:||foo(a: INT): INT is|
| || return a*10; end;|
If a routine has a return value, then its return statements must specify expressions whose types are subtypes of the routine's declared return type (see the chapter on Abstract Classes and Subtyping). Execution of the return statement causes the expression to be evaluated and its value to be returned. It is a fatal error if the final statement executed in a routine with a return type is not a return or raise statement.
15.1.7 typecase statements
The typecase statement is described in the chapter on Abstract Classes and Subtyping on page 79.
| ||when INT then ...|
| ||when FLT then ...|
| ||when $A then ...|
| ||else ....|
An operation that depends on the runtime type of an object held by a variable of abstract type may be performed inside a typecase statement. The variable in the typecase ('a' in the above example) must name a local variable or an argument of a method. If the typecase appears in an iterator, then the mode of the argument must be once; otherwise, the type of object that such an argument holds could change.
On execution, each successive type specifier is tested for being a supertype of the type of the object held by the variable. The statement list following the first matching type specifier is executed and control passes to the statement following the typecase. Within each statement list, the type of the typecase variable is taken to be the type specified by the matching type specifier unless the variable's declared type is a subtype of it, in which case it retains its declared type. It is not legal to assign to the typecase variable within the statement lists. If the object's type is not a subtype of any of the type specifiers and an else clause is present, then the statement list following it is executed.
It is a fatal error for no branch to match in the absence of an else clause. The declared type of the variable is not changed within the else statement list. If the value of the variable is void when the typecase is executed, then its type is taken to be the declared type of the variable.
15.1.8 yield statements
yield statements are used to return control to a loop and may appear only in iterator definitions. The yield statement must be followed by a value if the iterator has a return value and must be absent if it does not. The value yielded must be a subtype of the return type of the iterator. Execution of a yield statement causes the expression to be evaluated and its value to be returned to the caller of the iterator in which it appears. Yield is not permitted within a protect statement (see page 187). Yield causes assignment to out and inout arguments in the caller
|Examples:||odd_upto!(n: INT): INT is|
| || i: INT := 0;|
| || loop until!(i = n);|
| || if i.is_odd then yield i end;|
| || i := i + 1;|
| || end;|
In the example above the iterator yields odd numbers upto the specified value, "n".
- Statement description on page 62 and the chapter on iterators in general.
15.1.9 quit statements
quit statements are used to terminate loops and may only appear in iterator definitions. No value is returned from an iterator when it quits, and no assignment takes place to out or inout arguments in the caller. No statements may follow a quit statement in a statement list.
- Statement description on page 62 and the chapter on iterators in general.
We describe below a few special expressions used in Sather - void, void() and the short circuit boolean operations or and and.
15.2.1 void expressions
A void expression returns a value whose type is determined from context. void is the value that a variable of the type receives when it is declared but not explicitly initialized. The value of void for objects (except for immutable objects) is a special value that indicates the absence of an object - it is essentially the NULL pointer. Immutable objects are described in their own chapter, but for the sake of reference:
|Class||Initial Value||Class||Initial Value|
|BOOL||false|| || |
For other immutable types the void value is determined by recursively setting each attribute and array element to void. For numerical types, this results in the appropriate version of 'zero'.
void expressions may appear
- as the initializer for a constant or shared attribute. In fact, for most built-in classes, the only legal constant value is the void value e.g.
const a: POINT := void;
void expressions may not appear:
- as the right hand side of an assignment statement
- as the return value in a return or yield statement
- as the value of one of the expressions in a case statement
- as the exception object in a raise statement (see the chapter on Exceptions)
- as an argument value in a method call
- in a creation expression. In this last case, the argument is ignored in resolving overloading.
It is a fatal error to access object attributes of a void variable of reference type or to make any calls on a void variable of abstract type. Calls on a void variable of an immutable type are, however, quite legal (otherwise you would not be able to dot into a false boolean or a zero valued integer!)
- as the left argument of the dot '.' operator.
|a: POINT := #POINT(3,3);|
-- ILLEGAL (and silly) a.void
15.2.2 void test expressions
Void test expressions evaluate their argument and return a boolean value which is true if the value is void .
#OUT + void(p); -- Prints out true
p := #POINT(3,5);
#OUT + void(p); -- Prints out false
p := void;
#OUT + void(p); -- Prints out true;
#OUT + void(b); -- Prints out true
b := false;
#OUT + void(b); -- Prints out true!
-- Even though b has been assigned, it has the void value
15.2.3 Short circuit boolean expressions: and and or
and expressions compute the conjunction of two boolean expressions and return boolean values. The first expression is evaluated and if false, false is immediately returned as the result. Otherwise, the second expression is evaluated and its value returned.
|Example||if (3>a and b>6) or (c="Goo") then|
| || #OUT+"Success!"|
or expressions compute the disjunction of two boolean expressions and return boolean values. The first expression is evaluated and if true, true is immediately returned as the result. Otherwise, the second expression is evaluated and its value returned.
15.2.4 exception expressions
exception expressions may only appear within the statements of the when and else clauses in protect statements. They return the exception object that caused the when branch to be taken in the most tightly enclosing protect statement. The return type is the type specified in the corresponding when clause (page 187). In an else clause the return type is '$OB'.
| || .... some code|
| ||when STR then #OUT+exception.str end;|
| ||when ...|
| ||else ...|
- The description of the protect statement on page 132.
 The other built-in basic types are defined as arrays of BOOL and all have their values set to void by this rule.