|
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.