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.
Examples:
a := 5; b(7).c := 5; A::d := 5; [3] := 4; e[7,8] := 5; g:INT := 5; h ::= 5; |
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.
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: unnamedlink
Attribute assignment sugar: unnamedlink
Array element assignment: unnamedlink
Immutable class attribute assignment on unnamedlink
Example:
case i when 5,6 then ... when j then ... else ... end; |
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
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.
It is a fatal error if no branch matches and there is no else clause.
See unnamedlink for a statement description.
Example:
if a>5 then foo; elsif a>2 then bar; else error; end; |
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.
See unnamedlink for a statement description.
Example:
protect < some statements > when $STR then #ERR + exception.str; when FOO then #ERR + exception.foobar; else < some statements > end; |
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.
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.
See unnamedlink for more information.
Example:
f: INT:=4; -- Compute b factorial res: INT := 1; i :INT := 1; loop until!(i > f); res := res * i; i := i + 1; end; |
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.) .
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.
See also: unnamedlink
Examples:
foo(a: INT): INT is return a*10; end; |
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.
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.
Example:
typecase a when INT then ... when FLT then ... when $A then ... else ... end; |
For a description of the typecase statement see unnamedlink
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.
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; end; |
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. Yield causes assignment to out and inout arguments in the caller
In the example above the iterator yields odd numbers upto the specified value, "n".
See unnamedlink for more details.
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.
See unnamedlink for more details.