EXTENSION OF THE EXCEPTION HANDLING MECHANISM M.L.Gassanenko St.Petersburg, Russia. gml@ag.pu.ru ABSTRACT The extension of the exception wordset presented here includes the mechanisms of error interception and retrying. The mechanism of retrying enables programmer to focus on the logic of the process and reduce the amount of code responsible for repeating the attempts. The syntax is compatible with the ANSI standard notation. 1. INTRODUCTION One of the most important application areas of Forth is controlling hardware attached to the main procesor. Situations where there is a controller with its own "intellect" and logic, some documentation on it, and some peculiarities of hardware become known only due to practice are of frequent occurence. Forth is a powerful tool in this area because of ability to reflect the controller's logic (really, there are at least two levels: the level of port numbers and values and the level of meaningful commands, both can be supported by Forth) and due to exception handling, improvements of which will be discussed below. In recent years human memory gets considered as one of the most important resources to be taken into account in system development. Human memory is unreliable and limited by the psychological constant 7+-2 (objects kept in mind); therefore demands on human memory may turn into expenses of time spent in looking for anything forgotten, and finding and correcting bugs made due to the human memory unreliability. In addition, many people feel discomfort when they have to keep in mind more items than they can. We can subdivide things that should be kept in memory into 3 classes: 1) things forgetting which makes further activity meaningless; normally, they do not get forgotten (e.g. the purpose of one's activity); 2) things remembering which requires more human memory than just keeping them in mind (e.g. programer can recalculate a stack state knowing the initial state and the stack operations, but doing this he may forget something else); 3) things remembering which requires no additional human memory (e.g. a stack state mentioned in comments). Unfortunately, there is not much sense in counting items which should be kept in human memory because the number should depend on programmer's personal skills. Nevertheless, these notions remain useful. They allow us to speak about simplicity not only at the intuitive level. 2. THE GOAL Our goal was to create a transparent notation for error handling and retrial, i.e. a notation with minimal demands on human memory (both in keeping and in remembering). The standard CATCH [1] was considered unsatisfactory. The notation had to allow thinking in terms of successfully executing sequences of controller commands, i.e. the notions introduced by the controller documentation. There was also a requirement that exception handling should not interfere with DO loops and allow to access the loop indices. This may not seem important, but indeed this feature was very valuable for getting started quickly. In general, the less problems other than the controller you have, the better, even if these problems are just trivial. The requirement of compatibility with the standard CATCH meant that the notation had to allow us to define the standard CATCH in its terms, and then use both notations in the same program. 3. ADDITIONAL CONSIDERATIONS The use of a special structure to organize retrials facilitates understanding, i.e. reduces demands on human memory. Thinking in terms of sequences of successful operations (that may be repeated if they turn out to be unsuccessful) is easier than thinking in terms of loops that repeat operations until they succeed. A feeling that something is wrong in a control structure consumes human memory (programmer thinks what it could or should be). The ANSI standard CATCH compels programmer to factor his code into at least two words: one for actions that may fail and another responsible for their repetitions. This weakness of syntax at the same time provides a solution for another syntax weakness: if we use the same control structures (IFs and loops) for programing both repetition of attemts and the logic of the process, these two sorts of code mix up and the program becomes less readable. If we solve the second problem by introducing a special control structure for attempts repetition, we may consider getting rid of the compulsory factoring as well. At first, it is programmer who should decide how to factor his code. If programmer is forced by the language to make a decision, this is a language weakness. Unnecessary factoring is not better than insufficient factoring. At second, CATCH conduces to defining words which cannot be executed directly, but have to be put in the context of definitions that tick ([']) them and pass to CATCH. These definitions are local labels that have no meaning in themselves (an example: if we do not put such definition in the implied context and it executes THROW, interrupts do not get enabled). Thirdly, CATCH is a control structure that uses an auxiliary contant (the auxiliary word execution token). Most of felicitous (and therefore popular) control structures use no auxiliary data (the loop bounds are not auxiliary data, they are control data). 4. THE SYNTAX The first step was to separate CATCH into CATCH( and )CATCH . This allowed us to avoid auxiliary definitions and to use the indices of an enclosing loop from within CATCH( ... )CATCH . The second step was to introduce a special mechanism of repetition of failing actions. This solved the problem of mixing up normal control flow and retrial logic. The resulting syntax looks like this: TRY CATCH( .... \ code that can fail .... .... )CATCH \ places error code onto the stack ?DUP IF 3 RETRY \ retry up to 3 more times THROW \ then give up THEN (the exception handler should not necessarily end with a THROW). The standard CATCH may be defined as following: : CATCH >R CATCH( R@ EXECUTE )CATCH R> DROP ; and may be used with TRY and RETRY : TRY ['] CATCH ?DUP IF .... 3 RETRY THROW THEN Note that we cannot write ['] TRY CATCH because the execution token of will be consumed by CATCH and will not be properly restored (this may depend on implementation, though). Probably, we should better define a word that combines the functionalities of both TRY and CATCH( : : TRAP( POSTPONE TRY POSTPONE CATCH( ; IMMEDIATE and probably: : )IFERR POSTPONE )CATCH POSTPONE ?DUP POSTPONE IF ; IMMEDIATE : )CASEERR POSTPONE )CATCH POSTPONE CASE ; IMMEDIATE 5. THE SPECIFICATIONS CATCH( "catch-paren" Interpretation: Interpretation semantics for this word are undefined. Compilation: ( C: -- catch-orig ) Place catch-orig onto the control flow stack. Append the run-time semantics given below to the current definition. The semantics are incomplete until resolved by )CATCH . Run-time: ( i*x -- i*x ) ( EX: -- catch-sys ) Push an exception frame onto exception stack. )CATCH "paren-catch" Interpretation: Interpretation semantics for this word are undefined. Compilation: ( C: catch-orig -- ) Append the run-time semantics given below to the current definition and then resolve catch-orig. Run-time: ( -- 0 ) ( EX: catch-sys -- ) Remove the exception frame from exception stack and leave zero on the stack. Note. The code that follows )CATCH may receive control also as a result of executing THROW, in which case a non-zero value is left on the stack. THROW ( k*x 0 -- k*x ) | ( k*x n -- i*x n ; control transfer ) ( EX: catch-sys -- ) If any bits of n are non-zero, pop the topmost exception frame from the exception stack. Adjust the depths of all stacks so that they are the same as the depths saved in the exception frame (i is the same number as the i in the input argument to corresponding CATCH( ), put n on top of the data stack and and transfer control to a point just after the )CATCH corresponding to the CATCH( that pushed the exception frame. TRY ( j*x -- j*x ) Fill the last attempt frame on the exception stack. At any time at least one attempt frame is present on the exception stack. Assign 0 to the counter of attempts. Note 1. An exception frame includes space for an attempt frame. Note 2. An attempt frame includes information about the depths of all stacks and the counter of attempts. Note 3. There is always at least an attempt frame on the exception stack. RETRY ( +n -- | j*x ; control transfer ) "Retry up to +nth additional attempt." If +n is greater than the value of attempts counter, then add 1 to the attempts counter, adjust the depths of all stacks so that they are the same as the depths saved in the attempt frame (j is the same number as the j in the input argument to corresponding TRY ), and transfer control to a point just after the TRY . An ambiguous condition exists if the depth of some stack is less than the corresponding depth saved in the attempt frame. An ambiguous condition exists if there is no proper attempt frame filled by a TRY on the exception stack. Note. TRY sets the counter of attempts to 0. ATTEMPT# ( -- n ) "attempt-number" n is the value in the attempts counter, equals zero after execution of TRY and increases by 1 every time that RETRY executes. 6. IMPLEMENTATION ISSUES We used a separate exception stack since: 1) this technique allows to use loop indices inside CATCH( ... )CATCH; 2) this approach is simpler to implement. The excepion stack is never empty: there's always some space reserved for an attempt frame. TRY writes the attempt frame to this memory, probably overwriting the previous attempt frame; it does not adjust the exception stack pointer. CATCH( places the exception frame onto the exception stack and reserves space for a new attempt frame. 7. DISCUSSION The exception handling wordset greatly simplifies programming of various kinds of operations that may fail. The code is compatible with ANS standard CATCH-THROW, i.e. CATCH may be defined via CATCH( and )CATCH and both may be used in the same program. With the use of CATCH( ... )CATCH the definitions naturally split into two pats: the actions and the reaction on errors. Both parts tend to bloat, though. The usual candidates for factoring out are: code that analyse the device reply and probably raise exceptions; code following )CATCH that analyses the error code; (re)intialising actions; meaningful sequences of device commands. The ability to use the indices of the enclosing loop from within CATCH(...)CATCH turned out to be very useful, especially in the beginning of the development. In an application source file of 1300 lines, 34.5K bytes, 260 empty lines, 105 comment lines, 126 colon definitions, 22 CODE definitions, 27 VALUEs, 20 constants, 6 VARIABLEs, 31 CREATE definitions, 11 DEFERs (total: 243 definitions in 935 lines), 16 DO loops (used in 14 definitions) and 18 CATCHes (used in 12 definitions) there are 2 definitions where indices of enclosing loop are used, and (to compare with) 2 definitions where DO loops are used inside CATCH(...)CATCH (one of these 2 loops is used just for a delay). TRY is used 17 times, RETRY is used 22 times, THROW is used 37 times. There are 18 BEGIN loops, 85 IF statements and 1 CASE statement. This main file does not contain the whole application source: there are also some utility files. The application programms a controller and deals with a timer. RETRY enables programmer to get rid of auxiliary retry control structures and focus on the logic of the process. On the other hand, it is also an interesting control structure in itself: INITIALIZE TRY CATCH( ... )CATCH IF 2 RETRY REINITIALIZE-ALL 6 RETRY THROW THEN makes 2 additional attempts without complete reinitialization, and then makes 4 more attempts with reinitialization. The following example shows the use of TRY ... RETRY structure for determining equipment parameters: rate1 TO the-rate TRY CATCH( )CATCH IF rate2 TO the-rate 1 RETRY rate3 TO the-rate 2 RETRY rate3 TO the-rate 3 RETRY THROW THEN Whereas THROW is a non-local return, RETRY is a non-local loop. The code responsible for retrial may be factored out, since RETRY restores the return stack pointer when it transfers control to the code that follows TRY. An important point (which applies mostly to FORTH-83 systems) is that the text interpreter should be able to intercept an exception, otherwise Forth loses its interactive nature. As a last resort, a definition like: : T CATCH( ' EXECUTE )CATCH ." errcode=" . ; can help. Although the CATCH structure is known for a long time, and has been a candidate for inclusion into the standard for a long period (at least, the BASIS17 document (19 June 1991) already specified the same syntax), other exception handling mechanisms have appeared [2,3]. The author's personal contacts showed that the ANSI CATCH inspired some programmers to create their own exception handling mechanisms. We should note that most of the Forth exception handling mechanisms we have seen use confix notation (some kind of brackets surrounding the actions to be tried) rather than pass an auxiliary execution token to an exception interceptor like CATCH. We may suppose that evolution of the exception handling mechanisms in Forth will continue. 8. CONCLUSION The extension of the exception wordset presented here includes the mechanisms of error interception and retrying. The mechanism of retrying enables programmer to focus on the logic of the process and to reduce the amount of code responsible for repeating the attempts. The code becomes more readable, since with the use of a special control structure for repeating attempts, this sort of code can be recognized at a glance. The syntax is compatible with the ANSI standard notation: the standard CATCH may be defined via CATCH( ... )CATCH and both notations may be used in the same program. REFERENCES [1] American National Standard for Information Systems - Programming Languages - Forth. American National Standard Institute, Inc., 1994. [2] Rodriguez, B.J., "A Forth Exception Handler," SIGForth, Vol. 1, Summer'89, p.11-13. [3] Wejgaard, W., "TRY: A Simple Exception Handler," proc. of the euroFORML'91 conf., 4 p.