For reliability, the compiler must be given the opportunity to check for the accidental omission of some alternatives. For that reason, Ada requires that all possible values of the type of the discriminating expression be provided for in the choices. This rule is weakened if the discriminating expression is a name whose subtype is static: the choices that must be provided are then all the values of this subtype. Finally, a qualified expression can be used to restrict the possible choices, and a final choice others may be used to represent all values not previously specified.
As an example consider the declarations
type DAY is (MON, TUE, WED, THU, FRI, SAT, SUN); subtype WORKDAY is DAY range MON .. FRI; subtype RESTDAY is DAY range SAT .. SUN; TODAY : DAY; START : WORKDAY; |
With the above declarations all values of the type DAY (the type of TODAY) must appear in one selection, as in
case TODAY is when MON | TUE | WED | THU | FRI => WORK; when SAT | SUN => REST; end case; |
This could have been written in the equivalent form
case TODAY is when MON | TUE | WED | THU | FRI => WORK; when others => REST; end case; |
If in a given context it is known that the case discriminant belongs to a given subtype, a case statement with a qualified expression may be used. Only the values of the corresponding subtype can appear in the selections.
case WORKDAY'(TODAY) is when MON | WED | FRI => LATE; when TUE | THU => EARLY; end case; |
Should the value of TODAY not belong to the subtype WORKDAY (for example if TODAY = SAT), then the exception CONSTRAINT_ERROR would be raised by the evaluation of the qualified expression. This cannot arise in the following example, which uses the fact that the subtype of START is static:
case START is when MON | WED | FRI => LATE; when TUE | THU => EARLY; end case; |
The other main criterion in the design of case statements is generality: the syntax of selections should accommodate all situations that are likely to arise, given that the case discriminant has a discrete type. Hence it should permit ranges as well as lists of values.
Thus the first example above is more likely to be written using ranges:
case TODAY is when MON .. FRI => WORK; when SAT .. SUN => REST; end case; |
or (better) using the subtype names:
case TODAY is when WORKDAY => WORK; when RESTDAY => REST; end case; |
Such ranges and subtype names are very useful for case choices. They avoid long lists that can be tedious to read and therefore error- prone.
In many ways a case statement is similar to an array of statements and this is somewhat reflected in the syntax. For example we may compute the opposite of a given direction by means of a case statement:
type DIRECTION is (NORTH, WEST, SOUTH, EAST); COURSE : DIRECTION; BACK : DIRECTION; ... -- a value is given to COURSE case COURSE is when NORTH => BACK := SOUTH; when WEST => BACK := EAST; when SOUTH => BACK := NORTH; when EAST => BACK := WEST; end case; -- now BACK is the direction opposite to COURSE |
Another formulation of this computation uses an array of directions declared as
INVERSE : constant array(DIRECTION) of DIRECTION := (NORTH => SOUTH, WEST => EAST, SOUTH => NORTH, EAST => WEST); |
and the assignment statement
BACK := INVERSE(COURSE);
As can be seen from the above example, the conceptual similarity is actually reflected in the similarity of the syntaxes for case statements and for array aggregates.
A very important diagnostic facility that the compiler should provide is the listing of all values of the discriminating type that do not appear in the listed choices. For an incomplete (and therefore incorrect) case statement, the compiler has the information and should provide it to the programmer. In the absence of this kind of diagnostic, it might be quite difficult for the programmer to discover missing values for an enumeration type with a large number of values.
Case statements are conventionally implemented with an implicit transfer table. This table will generally contain one place for each possible value of the discriminating type. Quite often however, if some of the alternatives include null statements, the compiler may optimize the code generated, by using a shorter table and an explicit range check. As an example
case TODAY is when SAT => SHOP; when SUN => SLEEP; when others => null; end case; |
may be compiled to produce code equivalent to
if TODAY in RESTDAY then case RESTDAY'(TODAY) is -- no check needed when SAT => SHOP; when SUN => SLEEP; end case; end if; |
thus leading to a two-place transfer table. Finally, case statements with very sparse selections or with ranges as selections can be compiled as equivalent if statements. Thus for our first example we may have:
if TODAY in WORKDAY then WORK; else REST; end if; |