.sr sect_handle Section`12.2 .sr app_io Appendix`III . .chapter "Exception Handling and Exits" .para A routine is designed to perform a certain task. However, in certain cases that task may be impossible to perform. In such a case, instead of returning normally (which would imply successful performance of the intended task), the routine should notify its caller by signalling an 2exception*, consisting of a descriptive name and zero or more result objects. .para For example, the procedure string$fetch takes a string and an integer index and returns the character of the string with the given index. However, if the integer is not a legal index into the string, the exception bounds is signalled instead. The type specification of a routine contains a description of the exceptions it may signal; for example, string$fetch is of type .show proctype (string, int) returns (char) signals (bounds) .eshow .para The exception handling mechanism consists of two parts, the signalling of exceptions and the handling of exceptions. Signalling is the way a routine notifies its caller of an exceptional condition; handling is the way the caller responds to such notification. A signalled exception always goes to the immediate caller, and the exception must be handled in that caller. When a routine signals an exception, the current activation of that routine terminates and the corresponding invocation (in the caller) is said to 2raise* the exception. When an invocation raises an exception E, control immediately transfers to the closest handler for E that is attached to a statement containing the invocation. When execution of the handler completes, control passes to the statement following the one to which the handler is attached. .section "Signal Statement" .para An exception is signalled with a signal statement, which has the form: .show signal name lbkt ( expression, etc ) rbkt .eshow A signal statement may appear anywhere in the body of a routine. The execution of a signal statement begins with evaluation of the expressions (if any), from left to right, to produce a list of 2exception* 2results*. The activation of the routine is then terminated. Execution continues as described in sect_handle below. .para The exception name must be either one of the exception names listed in the routine heading, or 2failure*. (The 2failure* exception is implicitly part of all routine headings and types; it may not be declared explicitly.) If the corresponding exception specification in the heading has the form .show name (T1, etc, Tn) .eshow then there must be exactly 2n* expressions in the signal statement, and the type of the 2ith* expression must be included in Ti. Otherwise, if the name is 2failure*, then there must be exactly one expression present, whose type is string. .para The following useless procedure contains a number of examples of signal statements: .show 7 signaller = s(1)proc (i: int) returns (int)  signals (zero, negative(int)) t(1)if i < 0 then signal negative(-i) t(1) elseif i > 0 then return(i) t(1) elseif i = 0 then signal zero t(1) else signal failure("unreachable statement executed!") t(1) end t(1)end signaller .eshow .section "Except Statement" .para When a routine activation terminates by signalling an exception, the corresponding invocation (the text of the call) is said to 2raise* that exception. When an exception is raised, the caller specifies what action should be taken by the use of handlers attached to statements. .para A statement with handlers attached is called an except statement, and has the form: .show 3 statement except s(1)lcurly when_handler rcurly t(1)lbkt others_handler rbkt t(1)end .eshow where .show 4 .long_def others_handler .def1 when_handler "when name , etc lbkt ( decl , etc ) rbkt : body" .or "when name , etc ( * ) : body" .def1 others_handler "others lbkt ( idn : type_spec ) rbkt : body" .eshow At least one handler must be present. Let S be the statement to which the handlers are attached, and let X be the entire except statement. Each when_handler specifies one or more exception names and a body. The body is executed if an exception with one of those names is raised by an invocation in S. All of the names listed in the when_handlers must be distinct. The optional others_handler is used to handle all exceptions not explicitly named in the when_handlers. The statement S can be any form of statement, and can even contain other except statements. .para If, during the execution of S, some invocation in S raises an exception E, control immediately transfers to the closest handler for E that is attached to a statement containing the invocation. When execution of the handler completes (assuming no exception causes a transfer out of the handler), control passes to the statement following the one to which the handler is attached. Thus if the closest handler is attached to S, the statement following X is executed next. If execution of S terminates normally (i.e., control never transfers to a handler attached to S), then the statement following X is executed next. .para An exception raised inside a handler is treated the same as any other exception: control passes to the closest handler for that exception. Note that an exception raised in some handler attached to S cannot be handled by any handler attached to S; either the exception is handled within the handler, or it is handled by some handler attached to a statement containing X. .para We now consider the forms of handlers in more detail. The form .show when name , etc lbkt ( decl , etc ) rbkt : body .eshow is used to handle exceptions with the given names when the exception results are of interest. The optional declared variables, which are local to the handler, are assigned the exception results before the body is executed. Every exception potentially handled by this form must have the same number of results as there are declared variables, and the types of the results must match exactly the types of the variables. The form .show when name , etc ( * ) : body .eshow handles all exception with the given names, regardless of whether or not there are exception results; any actual results are discarded. Hence exceptions with differing numbers and types of results can be handled together. .para The form .show others lbkt ( idn : type_spec ) rbkt : body .eshow is optional, and must appear last in a handler list. This form handles any exception not handled by other handlers in the list. If a variable is declared, it must be of type string. The variable, which is local to the handler, is assigned a lower case string representing the actual exception name; any results are discarded. .para Note that exception results are ignored when matching exceptions to handlers; only the names of exceptions are used. Thus the following is illegal, in that int$div signals zero_divide without any results, but the closest handler has a declared variable: .show 6 begin y: int := 0 x: int := 3 / y except when zero_divide (z: int): return end end except when zero_divide: return end .eshow .para An invocation need not be surrounded by except statements that handle all potential exceptions. This policy was adopted because in many cases the programmer can prove that a particular exception will not arise. For example, the invocation int$div(x,`7) will never signal zero_divide. However, this policy does lead to the possibility that some invocation may raise an exception for which there is no handler. To avoid this situation, every routine body is contained implicitly in an except statement of the form .show 4 begin s(1)2routine body* end except s(1)when failure (s: string): s(2)signal failure(s); t(1)others (s: string):t(2)signal failure("unhandled exception: " || s); t(1)end; .eshow 2Failure* exceptions are propagated unchanged; an exception named 2name* becomes failure("unhandled exception: 2name*"). .section "An Example" .para We now present an example demonstrating the use of exception handlers. We will write a procedure, sum_stream, which reads a sequence of signed decimal integers from a character stream and returns the sum of those integers. The stream is viewed as containing a sequence of fields separated by spaces; each field must consist of a non-empty sequence of digits, optionally preceded by a single minus sign. Sum_stream has the form .show 5 sum_stream = s(1)proc (s: stream) returns (int) signals(s(2)overflow, t(2)unrepresentable_integer(string), t(2)bad_format (string)); t(1)etc t(1)end sum_stream; .eshow Sum_stream signals overflow if the sum of the numbers or an intermediate sum is outside the implemented range of integers. Unrepresentable_integer is signalled if the stream contains an individual number that is outside the implemented range of integers. Bad_format is signalled if the stream contains a field that is not an integer. .para We will use the 2getc* operation of the 2stream* data type (see app_io), whose type is .show proctype (stream) returns (char) signals (end_of_file, not_possible(string)); .eshow This operation returns the next character from the stream, unless the stream is empty, in which case end_of_file is signalled. Not_possible is signalled if the operation cannot be performed on the given stream (e.g., it is an output stream, or does not allow character operations, etc.) We will assume that we are given a stream for which 2getc* is always possible. .para The following procedure is used to convert character strings to integers: .show 5 s2i = s(1)proc (s: string) returns (int)  signals (s(2)invalid_character(char), t(2)bad_format, t(2)unrepresentable_integer); t(1)etc t(1)end s2i; .eshow 2S2i* signals invalid_character if its string argument contains a character other than a digit or a minus sign. Bad_format is signalled if the string contains a minus sign following a digit, more than one minus sign, or no digits. Unrepresentable_integer is signalled if the string represents an integer that is outside the implemented range of integers. .para An implementation of sum_stream is presented in Figure current_figure. .begin_figure "The sum_stream procedure." .show .ta 14 18 22 sum_stream = proc (s: stream) returns (int) signals (s(1)overflow, t(1)unrepresentable_integer(string), t(1)bad_format(string)); sum: int := 0; num: string := ""; while true do % skip over spaces between values; sum is valid, num is meaningless c: char := stream$getc(s); while c = ' ' do c := stream$getc(s); end; % read a value; num accumulates new number, sum becomes previous sum while c ~= ' ' do num := string$append(num, c); c := stream$getc(s); end; except when end_of_file: end; % restore sum to validity sum := sum + s2i(num); end; except when end_of_file: return(sum); when unrepresentable_integer: signal unrepresentable_integer (num); when bad_format,invalid_character(*): signal bad_format (num); when overflow: signal overflow; end; end sum_stream; .rtabs .eshow .finish_figure There are two loops within an infinite loop: one to skip spaces, and one to accumulate digits for conversion to a number. Notice the placement of the inner end_of_file handler. If end_of_file is raised in the second inner loop, then the sum is computed correctly, and the first invocation of stream$getc will again raise end_of_file. This time, however, the infinite loop is terminated and execution transfers to the other end_of_file handler, which then returns the accumulated sum. .para We have placed the remaining exception handlers outside of the infinite loop to avoid cluttering up the main part of the algorithm. Each of these exception handlers could also have been placed after the particular statement containing the invocation that signalled the corresponding exception. The (*) form is used in the handler for the bad_format and invalid_character exceptions since the exception results are not used. Note that the overflow handler catches exceptions signalled by the int$add procedure, which is invoked using the infix + notation. Note also that in this example all of the exceptions raised by sum_stream originate as exceptions signalled by lower-level modules. Sum_stream simply reflects these exceptions upwards in terms that are meaningful to its callers. Although some of the names may be unchanged, the meanings of the exceptions (and even the number of results) are different in the two levels. .para As mentioned above, we have assumed stream$getc never signals not_possible; if it does, then sum_stream will terminate, raising the exception failure("unhandled exception: not_possible"). .section "Exits and the Placement of Handlers" .para A 2local* transfer of control can be effected by using an exit statement, which has the form: .show exit name lbkt ( expression, etc ) rbkt .eshow An exit statement is similar to a signal statement except that where the signal statement 2signals* an exception to the 2calling* routine, the exit statement 2raises* the exception directly in the 2current* routine. An exception raised by an exit statement must be handled (explicitly) by a containing except statement with a handler of the form .show when name , etc lbkt ( decl , etc ) rbkt : body .eshow The types of the expressions in the exit statement must match exactly the types of the variables declared in the handler. .para The exit statement and the signal statement mesh nicely to form a uniform mechanism. The signal statement can be viewed simply as terminating a routine activation; an exit is then performed at the point of invocation in the caller. (Because this exit is implicit, it is not subject to the restrictions on exits listed above.)