[comp.lang.forth] CATCH and THROW

UNBCIC@BRFAPESP.BITNET (08/02/90)

        Can anyone post the source of CATCH and THROW for F-PC?

                               (8-DCS)

wmb@MITCH.ENG.SUN.COM (Mitch Bradley) (08/04/90)

> Can anyone post the source of CATCH and THROW for F-PC?

The "canonical" version should work just fine in F-PC, since it
implements SP@, SP!, RP@, and RP! in the same was as F83.

Here it is:

\ CATCH/THROW Error Handling Wordset
\ by Mitch Bradley
\

\ This implementation uses the non-standard words SP@ , SP! , RP@ , and
\ RP! .  These words, or their equivalents, are present in most systems.
\ Another implementation which does not use those non-standard words
\ follows this implementation.

\ Thanks to Don Colburn and Dean Sanderson for implementation suggestions.

VARIABLE HANDLER  \ Most recent error handler (should be a USER variable)

: CATCH  ( cfa -- error# | 0 )
   			( cfa )  \ Return address is already on the stack
   SP@ >R		( cfa )  \ Save data stack pointer
   EXCEPTION @ >R	( cfa )  \ Previous handler
   RP@ HANDLER !	( cfa )  \ 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# -- ??? error# ) \ Returns in saved context
   ?DUP  IF
      HANDLER @ RP!	( err# )      \ Return to saved return stack context
      R> HANDLER !	( err# )      \ Restore previous handler

      \ Remember error# on return stack before changing data stack pointer

      R> SWAP >R	( saved-sp )  \ err# is on return stack
      SP! R>		( err# )      \ Change stack pointer

      \ 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 .
   THEN
;

\ This is a portable implementation which does not use any non-standard
\ words.  This implementation has a problem: if the return stack happens
\ to contain a number which is the same as MAGIC# , then the wrong error
\ frame would be found.  This problem can be minimized by choosing a
\ magic number which is unlikely to appear on the return stack, or by
\ placing 2 different magic numbers on the return stack instead of just 1.

6775 CONSTANT MAGIC#

: CATCH  ( cfa -- error# | 0 )
                      ( cfa )     \ Return address is already on the stack
   DEPTH >R           ( cfa )     \ Save data stack size
   MAGIC# >R          ( cfa )     \ "magic" number to mark return stack
   EXECUTE            ( )         \ Execute the word passed in on the stack
   R> R> 2DROP  0     ( 0 )       \ Drop handler and signify normal completion
;

: THROW  ( ??? error# -- ??? error# )  \ Returns in saved context
   ?DUP  IF
      BEGIN  R>  MAGIC# =  UNTIL      ( err# )    \ Unwind return stack frame

      \ Remember err# on return stack before changing data stack depth

      R> SWAP >R >R                   ( return-stack: err# depth )

      \ The following code sets the stack depth to a known depth
      \ without using any nonstandard words (such as perhapse "sp!")
      \ The desired depth is kept on the return stack during the process.

      BEGIN  DEPTH R@  >  WHILE  NIP  REPEAT    \ Remove any extra items
                                                \ Depth is now <= correct depth
      BEGIN  DEPTH R@  <  WHILE  0    REPEAT    \ Add items if necessary
      R> DROP  R>                     ( err# )  \ Discard old depth

      \ 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 .
   THEN
;

wmb@MITCH.ENG.SUN.COM (09/17/90)

> 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.  ...
> when CATCH1 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 CATCH1 via NEXT,
> CATCH1 simply removes the catch frame and falls into NEXT, too, returning
> from the containing routine.

(Note: The author used the name "CATCH".  I changed it to "CATCH1" for the
purposes of discussion to avoid confusion with the ANS CATCH.)

This mechanism differs from the current ANS CATCH/THROW in the way that it
delimits the section of code that is "guarded" by the catch frame.

In the current ANS approach, the "guarded" code is an entire colon
definition, i.e.:

        ['] FOO CATCH           \ "FOO" is executed under the protection
                                \ of a CATCH frame

In the approach described above, the "guarded" code is the remainder of
the definition which contains the CATCH1 , i.e.:

        : xxx
            ...
            CATCH1
            <guarded code>
        ;

This adds yet another form of "closure" (a fragment of code that constitutes
the body of a control structure) to Forth, as the stuff between CATCH1 and ;
is a closure.

In my opinion, Forth has too many different kinds of closures already.

Also items placed on the return stack before the execution of CATCH1 are
inaccessible to the code following CATCH1 .

Finally, it is unclear whether or not CATCH1 works in interpret state,
due to its unbalanced return stack effect.

Mitch Bradley, wmb@Eng.Sun.COM

mip@IDA.LiU.SE (Mikael Patel) (09/18/90)

In article <9009172348.AA19506@ucbvax.Berkeley.EDU> wmb%MITCH.ENG.SUN.COM@SCFVM.GSFC.NASA.GOV writes:
>In the approach described above, the "guarded" code is the remainder of
>the definition which contains the CATCH1 , i.e.:
>
>        : xxx
>            ...
>            CATCH1
>            <guarded code>
>        ;
>

As object-orientation, exception handling comes in many flavors and
abstraction levels. Depending on your opinion about what a standard
definition of a language should achieve the current words for exception
might be too low level and give "away" too much of implementation.

Details as where the exception frame is stored etc gives problems with
for instance Forth chips as these often have problems picking things
on the stacks.

Just to present yet an alternative form of exception handling, this
this the form in TILE forth:

	: xxx ( a1..an -- ...)
		<guarded code>
	exception> ( a1..an e -- ..)
		<exception code>
	;

The principle words for defining exceptions (user level) and activating
them are:

	exception <name> ( -- e)
		Creates an exception name which when used will 
		push its reference onto the parameter stack.

	raise ( e -- )
		Activates the latest exceptions block with the
		given exception. Stacks are restore accordingly.

Typically a set of exceptions are defined to catch low level signals
such as arithmetic and addressing errors. An example of the usage 
would be a source module for handling stacks (the typical academia
example :-)

	exception stack-full ( -- e)
	exception stack-empty ( -- e)

	: push ( x s -- )
		<check for full stack> 
		if stack-full raise then
		<push element onto stack>
	;

	: pop ( s -- x)
		<check for empty stack> 
		if stack-empty raise then
		<pop element of stack>
	;

And usage of the stack:

	10 STACK aStack ( -- s)

	: add ( -- )
		aStack pop aStack pop + aStack push
	  exception> ( e -- )
		case
		  stack-full of <exception handling> endof
		  stack-empty of <exception handling endof
		  raise
		endcase
	;

The 'raise' in the 'case' block will cause the exception to be sent
to the next exception handling frame.

The form above is very close to that in Ada and Eiffel and (now to
the important part) it does not say anything about implementation!

One of the most important aspects of exceptions is that it allows
us to separate the 'normal' action (algorithm) from the 'error'
case. Calls to operating systems often return error code which
forces us to mix the normal and error case of an algorithm.

>In my opinion, Forth has too many different kinds of closures already.
>

Closures are not that bad. They give use a method of abstraction.
If Forth had a code block definition (like for instance PostScript)
closures would not be needed. But...forth is forth!

>Finally, it is unclear whether or not CATCH1 works in interpret state,
>due to its unbalanced return stack effect.

Oops! implementation detail again. But why use exception in interpret
state after all. Do you have an example of this, Mitch?

Mikael Patel, mip@ida.liu.se

wmb@MITCH.ENG.SUN.COM (Mitch Bradley) (09/19/90)

> As object-orientation, exception handling comes in many flavors and
> abstraction levels. Depending on your opinion about what a standard
> definition of a language should achieve the current words for exception
> might be too low level and give "away" too much of implementation.

In the case of exception handling, the obvious place to put the exception
frames is on the return stack, and one must assume that many implementors
would choose to put them there, if allowed.  Since the Forth return stack
is accessible to the programmer, one must include in the definition of the
exception handling method some statement about its effects on the return
stack.  This could take the form of either a positive statement:

        Items placed on the return stack before a CATCH1 are accessible
        afterwards (probably implying that the implementor is not
        allowed to use the return stack for the exception frame).

or a negative statement:

        Items placed on the return stack before a CATCH1 are NOT accessible
        afterwards (implying that the implementor may use the return
        stack for the exception frame at his discretion).

One nice feature of the ANS CATCH / THROW is that it completely avoids
this issue.  The "guarded" code is an entire colon definition, separate
from the definition that installs the exception handler.  Thus, there
is no question about return stack usage (because the return stack is
only ever accessible to the programmer within the context of a single
colon definition).


> Details as where the exception frame is stored etc gives problems with
> for instance Forth chips as these often have problems picking things
> on the stacks.

The ANS CATCH / THROW mechanism works just fine on Forth chips, because
it doesn't require "picking" the return stack.  THROW pops the return
stack until the exception frame is exposes, then pops the frame.


>> Finally, it is unclear whether or not CATCH1 works in interpret state,
>> due to its unbalanced return stack effect.

> Oops! implementation detail again.

Like I said, the fact that CATCH1 *might* (and probably will, on most
implementations) have a return stack effect means that its specification
must account for the possibility.


> But why use exception in interpret state after all. Do you have an
> example of this, Mitch?

The classic example is debugging.  When debugging, it is a pain to
have to remember that there are certain things that you can't just type
in at the keyboard and have them work the same way that the work inside
a colon definition.

It is even more of a pain to *explain* to novices why certain things
don't work.  It is nicer if everything just works.  As Forth "catches
on" here at Sun, I find that teaching and support is becoming more and
more critical to success.  Right or wrong, people expect things to work
the same when interpreted and when compiled, and they are confused and
unhappy when the expection is violated.  (The worst offender is ' vs. ['] ).


Mitch Bradley, wmb@Eng.Sun.COM

wmb@MITCH.ENG.SUN.COM (09/22/90)

It is my belief that CATCH & THROW were the only possible way of getting
exception handling into ANS Forth.  The reason:

Control structures are a subject of much controversy.  They are difficult
to precisely specify (proof: the discussions about how to properly describe
them spanned many many meetings, and caused much heated debate).

In fact, there is pretty much a one-to-one correspondence between
non-postfix constructs and hard-to-specify constructs.

Any exception handling solution that involves a new form of syntactic
control structure was certain to fail, because it would have required
the rethinking and the rewriting of so much of the other control structure
text.  For instance, the interactions between CATCH1 and all other control
structures would have to be specified (i.e. you can't use CATCH1 inside
a loop), as well as its return stack restrictions, and also there would
have to be a decision about whether it is immediate or not and whether or
not it is defined for interpret state and what happens when you POSTPONE
it.

This is the can of worms that one opens when one considers the addition
of a new non-postfix control structure to a Forth standard.

CATCH and THROW, being "pure postfix", avoids all these problems.

Mitch Bradley, wmb@Eng.Sun.COM