In this section...
14.3.1 Matrix Inversion 14.3.2 Division 14.3.3 A File Example 14.3.4 A Package Example 14.3.5 Example of Last Wishes |
procedure MAIN is procedure TREAT_MATRICES(N : INTEGER) is SINGULAR : exception; ... procedure INVERT(M : in out MATRIX) is begin -- compute inverse of determinant -- note : this may implicitly raise NUMERIC_ERROR -- complete inversion of the matrix. exception when NUMERIC_ERROR => raise SINGULAR; end INVERT; procedure TREAT_ONE is M : MATRIX; begin READ(M); INVERT(M); PRINT(M); exception when SINGULAR => PRINT("Matrix is singular"); end TREAT_ONE; begin -- TREAT_MATRICES for COUNT in 1 .. N loop PRINT("ITERATION"); PRINT(COUNT); TREAT_ONE; end loop; end TREAT_MATRICES; begin TREAT_MATRICES(20); end MAIN; |
As this example illustrates, the possible occurrence of NUMERIC_ERROR within INVERT is envisaged and consequently an appropriate handler has been provided. On the other hand, an occurrence of this exception within READ or PRINT would cause termination of MAIN, since no handler has been provided within MAIN.
In order to illustrate the dynamic behavior of this program, let us consider the stack situation (stacking downward) during a call to INVERT:
(1) MAIN calling TREAT_MATRICES TREAT_MATRICES calling TREAT_ONE TREAT_ONE calling INVERT INVERT executing normal statements of INVERT |
(2) MAIN calling TREAT_MATRICES TREAT_MATRICES calling TREAT_ONE TREAT_ONE calling INVERT INVERT executing the handler for NUMERIC_ERROR |
(3) MAIN calling TREAT_MATRICES TREAT_MATRICES calling TREAT_ONE TREAT_ONE executing the handler for SINGULAR |
(4) MAIN calling TREAT_MATRICES TREAT_MATRICES executing the loop statement |
function DIVISION(A, B : REAL) return REAL is begin return A/B; exception when NUMERIC_ERROR => return REAL'LAST; end; |
Should NUMERIC_ERROR occur during the computation of A/B, the execution of the handler will complete the execution of the function DIVISION. Any statement that is valid within the sequence of statements of DIVISION is also valid in the handler. In particular the handler may provide the return statement
return REAL'LAST;
on behalf of the function.
This example illustrates the nature of handlers. They must be viewed as substitutes, ready to take charge of the operations in case of error.
with TEXT_IO; use TEXT_IO; procedure TRANSFER is INPUT : FILE_TYPE; OUTPUT : FILE_TYPE; C : CHARACTER; begin OPEN(INPUT, MODE => IN_FILE, NAME => "SOURCE"); OPEN(OUTPUT, MODE => OUT_FILE, NAME => "DESTINATION"); loop GET(INPUT, C); PUT(OUTPUT, C); end loop; exception when END_ERROR => CLOSE(INPUT); CLOSE(OUTPUT); end; |
The procedure TRANSFER transfers the characters from the file SOURCE into the file DESTINATION. At each iteration GET is called, and eventually an END_ERROR exception will occur. Then the corresponding handler will be activated and its execution will complete the execution of TRANSFER.
This example shows that although many exceptions will represent error conditions, some of them may just be normal conditions for termination.
package TABLE_MANAGER is type ITEM is ... ... procedure INSERT (NEW_ITEM : in ITEM); procedure RETRIEVE(FIRST_ITEM : out ITEM); TABLE_FULL : exception; -- raised by INSERT when table full end; package body TABLE_MANAGER is ... procedure INSERT(NEW_ITEM : in ITEM) is begin if FREE_LIST_EMPTY then raise TABLE_FULL; end if; -- remaining code for INSERT end; ... end TABLE_MANAGER; |
The interface of the table manager defines the operations INSERT and RETRIEVE, and the exception TABLE_FULL. Any procedure that uses the package may provide a local handler for this exception; for example:
procedure APPLICATION is use TABLE_MANAGER; ... procedure SAFE_INSERT(ELEMENT : in ITEM) is NEXT : ITEM; begin INSERT(ELEMENT); exception when TABLE_FULL => RETRIEVE(NEXT); -- perform usual treatment of NEXT INSERT(ELEMENT); end SAFE_INSERT; begin -- includes calls of SAFE_INSERT instead of INSERT end APPLICATION; |
Within procedure APPLICATION, a procedure SAFE_INSERT with a local handler for TABLE_FULL is provided. Should this exception be raised by the body of INSERT, the local handler for TABLE_FULL gains control and calls RETRIEVE before reiterating the call of INSERT. Should an exception occur again in this second call, the execution of SAFE_INSERT will be abandoned and the exception will be propagated to the caller of SAFE_INSERT.
It is worth mentioning that the body of INSERT is assumed to be programmed in a robust manner: it does not modify any global variable if it cannot accomplish the insertion normally. It is this property that permits SAFE_INSERT to reiterate the call of INSERT when the first call fails.
A calls B, B calls C, C calls D
Then if the exception is raised while executing D, the execution of D is abandoned; then that of C and of B, in that order. (The propagation occurs in the reverse of the order of calls.)
One may want to let these procedures express their last wishes before being abandoned - for instance, to perform some cleanup actions. This can be achieved by providing a handler for others in each of these procedures. Each such handler will then issue the statement
raise;
which raises the same exception to the attention of the calling procedure. (We are thus able to achieve an effect similar to that of the unwind clause of Mesa without the need for a special language construct.) We illustrate last wishes with the example of a procedure that performs operations on a file:
procedure OPERATE(NAME : STRING) is FILE : FILE_TYPE; begin -- initial actions OPEN(FILE, INOUT_FILE, NAME); -- perform work on the file CLOSE(FILE); -- final actions end; |
Should an exception occur during operations on the file, the file would be left in an open state when the body of OPERATE is left. This is avoided by expressing the appropriate corrective action in a handler:
procedure SAFE_OPERATE(NAME : STRING) is FILE : FILE_TYPE; begin -- initial actions OPEN(FILE, INOUT_FILE, NAME); begin -- perform work on the file exception when others => CLOSE(FILE); raise; end; CLOSE(FILE); -- final actions end; |
Now if any exception occurs, either during the initial or the final actions, it will be propagated to the caller of SAFE_OPERATE, and the file will at that time be closed. If however the exception occurs within the block, while performing work on the file, the inner handler will first close the file before propagating the exception.
Similar techniques can be used in parallel processing examples: a given task should not be left waiting forever to receive the stop signal from a task whose execution was abandoned after it sent a start signal.