Sather supports the standard constructs for conditional execution - if statements and multi-way case statements
if statements are used to conditionally execute statement lists according to the value of a boolean expression. In this form, the if keyword is followed by a boolean expression, the keyword then, a list of statements and the final keyword end. When the statement is executed, the boolean expression is evaluated and if the result is true the statements in the statement list are executed. If it is false,then control passes directly to the end of the if statement.
i:INT := -15; if i < 0 then i:=-i; end; #OUT + i; -- Prints out 15 j:INT := 15; if j < 0 then j:=-j; end; #OUT + j; -- Prints out 15 |
It often happens that one wishes to perform a sequence of tests, executing only the statements which correspond to the first test in the sequence which evaluates to true. For example, we may want to produce a integer value 'y' from an integer value 'x' which has the shape of a triangular bump. It should be zero when 'x<0', equal to 'x' when '0<=x<100', equal to '200-x' when '100 <= x<200', and equal to '0' when 'x>=200'. This can be accomplished with a nested series of if statements:
if x < 0 then y := 0; else if x < 100 then y := x; else if x < 200 then y := 200 - x; else y := 0; end; end; end; |
Because this kind of construct is so common and the deeply nested if statements can get confusing, Sather provides a special form for it. A series of elsif clauses may appear after the statements following the then keyword:
if x < 0 then y := 0; elsif x < 100 then y := x; elsif x < 200 then y := 200 - x; else y := 0; end; |
There may be an arbitrary number of such elsif clauses. Each is evaluated in turn until one returns true. The statement list following this clause is evaluated and the statement finishes. If none of the expressions is true, the statements following the final else clause are evaluated.
Multi-way branches are implemented by case statements. There may be an arbitrary number of when clauses and an optional else clause. The initial construct is evaluated first and may have a return value of any type.
i:INT := 7; switch i when 1,2,3 then j := 3; when 4,5,6 then j := 4; when 7,8,9 then j := 5; else j := 10; end; #OUT + j; -- Prints out 5 |
This type must define one or more routines named 'is_eq' with a single argument and a boolean return value.
class POINT is attr x,y:INT; create(x,y:INT):POINT is res:POINT := new; res.x := x; res.y := y; return res; end; is_eq(point2:POINT):BOOL is -- In Sather,= is short hand for a call on 'is_eq' return x = point2.x and y = point2.y; end; str:STR is return "X=" + x + " Y=" + y; end end; |
Points can then be used in a case statement as shown below
p:POINT := #POINT(3,4); zero_point:POINT := #POINT(0,0); case p when zero_point then #OUT + "Zero point\n"; when #POINT(1,1), #POINT(1,-1),#POINT(-1,-1), #POINT(-1,1) then #OUT + "Unit point: " + p.str + "\n"; else #OUT + "Some other point\n"; end; |
Note that the equal sign is really short hand for the routine is_eq. The case statement is equivalent to an if statement, each of whose branches tests a call of is_eq. Thus the above case is equvalent to
if p = zero_point then #OUT + "Zero point\n"; elsif p = #POINT(1,1) or p = #POINT(1,-1) or ... etc. then #OUT + "Unit point:" + p.str + "\n"; else #OUT + "Some other point\n"; end; |
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 for case statements but not for if statements.
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. 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.
Consider the code
p: POINT; if p.x > 3 then #OUT + p.x; end; -- Runtime error if p is void |
The above block of code will work if p is not void. If it is void, however, the test p.x >3 will result in a runtime error, since it is attempting to dot into a void reference type. We can catch this problem by using the following piece of code, and the semantics of the short-circuit and
if ~void(p) and p.x > 3 then -- The ~ symbol indicates logical negation #OUT + p.x; end; |
The above piece of code will not generate an error, even if p is void. The first part of the and expression tests for whether p is void. If it is void, then the void test returns true and the not turns this into a false. The and therefore fails before trying to evaluate the dotted expression p.x.
A similar behavior can be seen with the short-circuit or statement, where the second expression is not examine if the first expression evaluates to true
a:INT := 15; p:POINT; if a>10 or p.x < 10 then -- Since a>10 is true, the second expression is not evaluated |
Note that booleans also define an and_rout routine, which does not have the same short-circuit behavior:
if ~void(p).and_rout(p.x > 3) then -- May generate a run-time error, when 'p' is void -- The argument to the 'and_rout' routine (p.x) is evaluated -- even when the first condition, ~void(p) fails. -- Hence, if 'p' is void, p.x is still evaluated and generates a -- run-time error (attribute access of void) |