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)