[comp.lang.forth] CATCH / THROW

guest@willett.pgh.pa.us (Guest Account) (09/15/90)

[I am posting this with the author's permission.  I will forward any
*posted* replies back to the author.  -dwp]

Message-Id: <4660872@AppleLink.Apple.COM>
Subject: FORTH CATCH/THROW
To: WILLETT!DWP@goofy.apple.com
From: D2044@AppleLink.Apple.COM (Flavors Tech, Doug Currie,PRT)
Date: 12 Sep 90 18:31 GMT

12Sep90
 
Doug Phillips...
 
I saw your note in Best of GENIE, FORTH Dimensions XII#3, re: CATCH/THROW.
I had no idea that CATCH/THROW had caught on since I recommended the
mechanism to George Shaw last year.  Your "for free" analysis of the
requirements for CATCH/THROW implementation is not quite complete.  There
is a middle ground between your two approaches.  I have used this technique
for years in my FORTH systems, and the mechanism is free if your program
doesn't use it, and quite cheap if it does.
 
The idea is to have a pointer to the top most catch frame on the return
stack, and links from catch frame to catch frame.  So far, this is just
like your first choice.  Now when CATCH is executed, it builds the catch
frame, and _calls_ the remainder of its containing routine (instead of
returning).  When the containing routine returns to the CATCH via NEXT,
CATCH simply removes the catch frame and falls into NEXT, too, returning
from the containing routine.  NEXT itself is not changed in the least!
The return stack can have anything at all on it (in fact I use it for my
LOCAL mechanism).
 
Now THROW is implemented as a return from the outermost CATCH (which
hasn't returned yet... it called its return point) after restoring the
catch frame pointer.  I also have a routine called PUNT-CATCH which
removes the top most catch frame from the return stack.
 
Reproduced below are the CATCH, THROW, and PUNT-CATCH routines in M68000
assembly code (this is a native code FORTH, i.e., NEXT = RTS).  The
references to the frame pointer (A2) are for local variables.
 
   EXPORT  catch
catch  ; ( - n T or F )  catch
   MOVE.W   #0, -(A4)           ; rtn val
   MOVEA.L (SP), A0                ; rtn addr, leave it for throw
   MOVE.L   A2, -(SP)            ; save frame ptr
   MOVE.L   _catches_(A6), -(SP)   ; setup catch frame
   MOVE.L   SP, _catches_(A6)         ; link it in
   JSR    (A0)                 ; return first time
   ; colon word RTS comes here
   MOVE.L   (SP)+, _catches_(A6)    ; unlink
   ADDQ.L   #8, SP                 ; remove catch frame
   RTS                        ; return from colon word
 
   EXPORT throw
throw  ; ( n - n T )  throw - doesn't return to caller but to catch
   MOVE.W   #-1, -(A4)          ; rtn val T
   MOVE.L   _catches_(A6), SP      ; get catch frame
   MOVE.L   (SP)+, _catches_(A6)     ; unlink it
   MOVE.L   (SP)+, A2              ; restore frame ptr
   RTS                         ; return from catch again
 
   EXPORT punt_catch
punt_catch ; ( - ) removes catch frame
   MOVEA.L (SP)+, A0               ; rtn addr
   ADDQ.L   #4, SP            ; remove catch's rtn addr
   MOVE.L   (SP)+, _catches_(A6)     ; unlink
   ADDQ.L   #8, SP             ; remove catch frame
   JMP     (A0)                   ; return
 
As a further optimization, I have two mechanisms to set up local variables.
One allows CATCHes in the routine with locals, the other doen't and is very
cheap.  The two mechanisms may be mixed in one program.  This is just one
more way to make CATCH/THROW inexpensive when not used.
 
Regards,
 
e
 (Doug Currie, Flavors Technology, Inc., 3 Northern Blvd., Amherst NH 03031)
 
cc: FORTH Dimensions
    FORTH Interest Group
    1330 S. Bascom Ave., Suite D
    San Jose CA 95155
 
    SIGFORTH, ANS FORTH Project
    c/o George Shaw
    POBox 3471
    Hayward CA 94540-3471
 
P.S. Have you guys (FORTH Dimensions, SIGFORTH, ANS FORTH Project) got
INTERNET addresses?  I'm not on GENIE!
 

bouma@cs.purdue.EDU (William J. Bouma) (01/16/91)

> A long time ago Mitch Bradley wrote:
>
> In F83, and indeed in most Forth systems, the following code will work:
> 
> VARIABLE HANDLER   \ Most recent exception handler
> 
> : CATCH  ( execution-token -- error# | 0 )
>                         ( token )  \ Return address is already on the stack
>    SP@ >R               ( token )  \ Save data stack pointer
>    HANDLER @ >R         ( token )  \ Previous handler
>    RP@ HANDLER !        ( token )  \ Set current handler to this one
>    EXECUTE              ( )        \ Execute the word passed in on the stack
>    R> HANDLER !         ( )        \ Restore previous handler
>    R> DROP              ( )        \ Discard saved stack pointer
>    0                    ( 0 )      \ Signify normal completion
> ;
> 
> : THROW  ( ??? error#|0 -- ??? error# )  \ Returns in saved context
>    ?DUP IF
>       HANDLER @ RP!     ( err# )       \ Return to saved return stack context
>       R> HANDLER !      ( err# )       \ Restore previous handler
>                         ( err# )       \ Remember error# on return stack
>                         ( err# )       \ before changing data stack pointer
>       R> SWAP >R        ( saved-sp )   \ err# is on return stack
>       SP! DROP R>       ( err# )       \ Change stack pointer
>    THEN
>    \ This return will return to the caller of catch, because the return
>    \ stack has been restored to the state that existed when CATCH began
>    \ execution .
> ;
 

This is fine, but there is one thing that I do not like.  Each CATCH
handler has the responsibility of checking if it handles the specific
error it has been passed and, if not, THROWing it on to the next.  We
can slightly modify the scheme above to get an easier to use mechanism.

VARIABLE HANDLER   \ Most recent exception handler
 
: CATCH  ( execution-token tag -- error# | 0 )
                        ( token tag )  \ Return address is already on the stack
   SP@ >R               ( token tag )  \ Save data stack pointer
   HANDLER @ >R         ( token tag )  \ Previous handler
   >R                   ( token )  \ Save tag
   RP@ HANDLER !        ( token )  \ Set current handler to this one
   EXECUTE              ( )        \ Execute the word passed in on the stack
   R> DROP          	( )        \ Trash the tag
   R> HANDLER !         ( )        \ Restore previous handler
   R> DROP              ( )        \ Discard saved stack pointer
   0                    ( 0 )      \ Signify normal completion
;

: THROW  ( ??? error# tag -- ??? error# ) \ Returns in saved context
    HANDLER @ >R      ( err# tag )        \ Get saved return stack context
    BEGIN				  \ Find matching tag in the stack
      R> RP!          ( err# tag )        \ Return to saved stack context
      R> OVER =       ( err# tag flag )   \ Compare tags
    UNTIL
    R> HANDLER !      ( err# tag )   \ Restore previous handler
    DROP              ( err# )       \ Remember error# on return stack
                      ( err# )       \ before changing data stack pointer
    R> SWAP >R        ( saved-sp )   \ err# is on return stack
    SP! DROP R>       ( err# )       \ Change stack pointer
;

A simple example of use would be:

    VARIABLE DIVBY0

    ' SOMEARITHMETIC DIVBY0 CATCH
    IF
      ." Divide by 0 error "
    THEN

I don't envision the THROW itself doing any error checking as Mitch has
done.  I am not sure why he wants to do that?

    : SOMEARITHMETIC ... DUP 0 = IF 1 DIVBY0 THROW THEN ... ;

I suggest using variables as the tags since they always will have a unique
address value, plus the THROWing word could possibly send back extra info
to the CATCH in the tag.  This example doesn't take much advantage of the
full powers of this scheme as described below.

The reason to have both a tag and an error number is because a tag is used
to select the handler, then the handler is free to choose what to do about
any specific error.  In Mitch's scheme the error numbers have to be unique
across all handlers to keep the handler from catching the wrong error. 
Thus in each handler the selection mechanism will be a sequence of cascaded
IFs.  In my scheme the error numbers need only be unique within the handler.
Thus a value branching select mechanism can be used which is much faster.

Also, any THROW on a sequence of nested CATCHes will be faster in my scheme.
But if no THROW occurs, mine is slightly slower as 3 things are pushed on
the stack rather than two.  But if no THROWs occur, what good are they 8^).

DISCLAIMER: I have not programmed in Forth for 10 yrs.
	    However, in my spare time I like to program Forth.
-- 
Bill <bouma@cs.purdue.edu>

UNBCIC@BRFAPESP.BITNET (01/17/91)

If you want named catchs, do it yourself! It's possible to do named catchs
from the current ANS Forth Catch & Throw... and the ones that don't need it
don't have to pay the cost (speed?).

                              (8-DCS)
Daniel C. Sobral
UNBCIC@BRFAPESP.BITNET