@database beginner.guide 

@Master beginner 

@Width 75 


This is the AmigaGuideŽ file beginner.guide, produced by Makeinfo-1.55 from 
the input file beginner.


@NODE "main" "Exception Handling"
@Next "Memory.guide/main" 
@Prev "Modules.guide/main" 
@Toc "Contents.guide/main" 

Exception Handling
******************

   Often your program has to check the results of functions and do
different things if errors have occurred.  For instance, if you try to
open a window (using @{b }OpenW@{ub }), you may get a @{b }NIL@{ub } pointer returned which
shows that the window could not be opened for some reason.  In this case
you normally can't continue with the program, so you must tidy up and
terminate.  Tidying up can sometimes involve closing windows, screens and
libraries, so sometimes your error cases can make your program cluttered
and messy.  This is where exceptions come in--an @{fg shine }exception@{fg text } is simply an
error case, and @{fg shine }exception handling@{fg text } is dealing with error cases.  The
exception handling in E neatly separates error specific code from the real
code of your program.


 @{" Procedures with Exception Handlers " Link "Procedures with Exception Handlers" } 
 @{" Raising an Exception " Link "Raising an Exception" } 
 @{" Automatic Exceptions " Link "Automatic Exceptions" } 
 @{" Raise within an Exception Handler " Link "Raise within an Exception Handler" } 


@ENDNODE

@NODE "Procedures with Exception Handlers" "Procedures with Exception Handlers"
@Next "Raising an Exception" 
@Toc "main" 

Procedures with Exception Handlers
==================================

   A procedure with an exception handler looks like this:

     PROC fred(params...) HANDLE
       /* Main, real code */
     EXCEPT
       /* Error handling code */
     ENDPROC

This is very similar to a normal procedure, apart from the @{b }HANDLE@{ub } and
@{b }EXCEPT@{ub } keywords.  The @{b }HANDLE@{ub } keyword means the procedure is going to have
an exception handler, and the @{b }EXCEPT@{ub } keyword marks the end of the normal
code and the start of the exception handling code.  The procedure works
just as normal, executing the code in the part before the @{b }EXCEPT@{ub }, but when
an error happens you can pass control to the exception handler (i.e., the
code after the @{b }EXCEPT@{ub } is executed).


@ENDNODE

@NODE "Raising an Exception" "Raising an Exception"
@Next "Automatic Exceptions" 
@Prev "Procedures with Exception Handlers" 
@Toc "main" 

Raising an Exception
====================

   When an error occurs (and you want to handle it), you @{fg shine }raise@{fg text } an
exception using either the @{b }Raise@{ub } or @{b }Throw@{ub } function.  You call @{b }Raise@{ub } with a
number which identifies the kind of error that occurred.  The code in the
exception handler is responsible for decoding the number and then doing
the appropriate thing.  @{b }Throw@{ub } is very similar to @{b }Raise@{ub }, and the following
description of @{b }Raise@{ub } also applies to @{b }Throw@{ub }.  The difference is that @{b }Throw@{ub }
takes a second argument which can be used to pass extra information to a
handler (usually a string).  The terms `raising' and `throwing' an
exception can be used interchangeably.

   When @{b }Raise@{ub } is called it immediately stops the execution of the current
procedure code and passes control to the exception handler of most recent
procedure which has a handler (which may be the current procedure).  This
is a bit complicated, but you can stick to raising exceptions and handling
them in the same procedure, as in the next example:

     CONST BIG_AMOUNT = 100000
     
     ENUM ERR_MEM=1
     
     PROC main() HANDLE
       DEF block
       block:=New(BIG_AMOUNT)
       IF block=NIL THEN Raise(ERR_MEM)
       WriteF('Got enough memory\\n')
     EXCEPT
       IF exception=ERR_MEM
         WriteF('Not enough memory\\n')
       ELSE
         WriteF('Unknown exception\\n')
       ENDIF
     ENDPROC

This uses an exception handler to print a message saying there wasn't
enough memory if the call to @{b }New@{ub } returns @{b }NIL@{ub }.  The parameter to @{b }Raise@{ub } is
stored in the special variable @{b }exception@{ub } in the exception handler part of
the code, so if @{b }Raise@{ub } is called with a number other than @{b }ERR_MEM@{ub } a message
saying "Unknown exception" will be printed.

   Try running this program with a really large @{b }BIG_AMOUNT@{ub } constant, so
that the @{b }New@{ub } can't allocate the memory.  Notice that the "Got enough
memory" is not printed if @{b }Raise@{ub } is called.  That's because the execution
of the normal procedure code stops when @{b }Raise@{ub } is called, and control
passes to the appropriate exception handler.  When the end of the
exception handler is reached the procedure is finished, and in this case
the program terminates because the procedure was the @{b }main@{ub } procedure.

   If @{b }Throw@{ub } is used instead of @{b }Raise@{ub } then, in the handler, the special
variable @{b }exceptioninfo@{ub } will contain the value of the second parameter.
This can be used in conjunction with @{b }exception@{ub } to provide the handler with
more information about the error.  Here's the above example re-written to
use @{b }Throw@{ub }:

     CONST BIG_AMOUNT = 100000
     
     ENUM ERR_MEM=1
     
     PROC main() HANDLE
       DEF block
       block:=New(BIG_AMOUNT)
       IF block=NIL THEN Throw(ERR_MEM, 'Not enough memory\\n')
       WriteF('Got enough memory\\n')
     EXCEPT
       IF exception=ERR_MEM
         WriteF(exceptioninfo)
       ELSE
         WriteF('Unknown exception\\n')
       ENDIF
     ENDPROC

   An enumeration (using @{b }ENUM@{ub }) is a good way of getting different
constants for various exceptions.  It's always a good idea to use
constants for the parameter to @{b }Raise@{ub } and in the exception handler, because
it makes everything a lot more readable: @{b }Raise(ERR_MEM)@{ub } is much clearer
than @{b }Raise(1)@{ub }.  The enumeration starts at one because zero is a special
exception: it usually means that no error occurred.  This is useful when
the handler does the same cleaning up that would normally be done when the
program terminates successfully.  For this reason there is a special form
of @{b }EXCEPT@{ub } which automatically raises a zero exception when the code in the
procedure successfully terminates.  This is @{b }EXCEPT DO@{ub }, with the @{b }DO@{ub }
suggesting to the reader that the exception handler is called even if no
error occurs.  Also, the argument to the @{b }Raise@{ub } function defaults to zero
if it is omitted (see @{"Default Arguments" Link "Procedures.guide/Default Arguments" }).

   So, what happens if you call @{b }Raise@{ub } in a procedure without an exception
handler?  Well, this is where the real power of the handling mechanism
comes to light.  In this case, control passes to the exception handler of
the most @{fg shine }recent@{fg text } procedure with a handler.  If none are found then the
program terminates.  `Recent' means one of the procedures involved in
calling your procedure.  So, if the procedure @{b }fred@{ub } calls @{b }barney@{ub }, then when
@{b }barney@{ub } is being executed @{b }fred@{ub } is a recent procedure.  Because the @{b }main@{ub }
procedure is where the program starts it is a recent procedure for every
other procedure in the program.  This means, in practice:

   @{b }*@{ub } If you define @{b }fred@{ub } to be a procedure with an exception handler then
     any procedures called by @{b }fred@{ub } will have their exceptions handled by
     the handler in @{b }fred@{ub } if they don't have their own handler.

   @{b }*@{ub } If you define @{b }main@{ub } to be a procedure with an exception handler then
     any exceptions that are raised will always be dealt with by some
     exception handling code (i.e., the handler of @{b }main@{ub } or some other
     procedure).

   Here's a more complicated example:

     ENUM FRED=1, BARNEY
     
     PROC main()
       WriteF('Hello from main\\n')
       fred()
       barney()
       WriteF('Goodbye from main\\n')
     ENDPROC
     
     PROC fred() HANDLE
       WriteF(' Hello from fred\\n')
       Raise(FRED)
       WriteF(' Goodbye from fred\\n')
     EXCEPT
       WriteF(' Handler fred: \\d\\n', exception)
     ENDPROC
     
     PROC barney()
       WriteF('  Hello from barney\\n')
       Raise(BARNEY)
       WriteF('  Goodbye from barney\\n')
     ENDPROC

When you run this program you get the following output:

     Hello from main
      Hello from fred
      Handler fred: 1
       Hello from barney

This is because the @{b }fred@{ub } procedure is terminated by the @{b }Raise(FRED)@{ub } call,
and the whole program is terminated by the @{b }Raise(BARNEY)@{ub } call (since
@{b }barney@{ub } and @{b }main@{ub } do not have handlers).

   Now try this:

     ENUM FRED=1, BARNEY
     
     PROC main()
       WriteF('Hello from main\\n')
       fred()
       WriteF('Goodbye from main\\n')
     ENDPROC
     
     PROC fred() HANDLE
       WriteF(' Hello from fred\\n')
       barney()
       Raise(FRED)
       WriteF(' Goodbye from fred\\n')
     EXCEPT
       WriteF(' Handler fred: \\d\\n', exception)
     ENDPROC
     
     PROC barney()
       WriteF('  Hello from barney\\n')
       Raise(BARNEY)
       WriteF('  Goodbye from barney\\n')
     ENDPROC

When you run this you get the following output:

     Hello from main
      Hello from fred
       Hello from barney
      Handler fred: 2
     Goodbye from main

Now the @{b }fred@{ub } procedure calls @{b }barney@{ub }, so @{b }main@{ub } and @{b }fred@{ub } are recent
procedures when @{b }Raise(BARNEY)@{ub } is executed, and therefore the @{b }fred@{ub }
exception handler is called.  When this handler finishes the call to @{b }fred@{ub }
in @{b }main@{ub } is finished, so the @{b }main@{ub } procedure is completed and we see the
`Goodbye' message.  In the previous program the @{b }Raise(BARNEY)@{ub } call did not
get handled and the whole program terminated at that point.


@ENDNODE

@NODE "Automatic Exceptions" "Automatic Exceptions"
@Next "Raise within an Exception Handler" 
@Prev "Raising an Exception" 
@Toc "main" 

Automatic Exceptions
====================

   In the previous section we saw an example of raising an exception when
a call to @{b }New@{ub } returned @{b }NIL@{ub }.  We can re-write this example to use
@{fg shine }automatic@{fg text } exception raising:

     CONST BIG_AMOUNT = 100000
     
     ENUM ERR_MEM=1
     
     RAISE ERR_MEM IF New()=NIL
     
     PROC main() HANDLE
       DEF block
       block:=New(BIG_AMOUNT)
       WriteF('Got enough memory\\n')
     EXCEPT
       IF exception=ERR_MEM
         WriteF('Not enough memory\\n')
       ELSE
         WriteF('Unknown exception\\n')
       ENDIF
     ENDPROC

The only difference is the removal of the @{b }IF@{ub } which checked the value of
@{b }block@{ub }, and the addition of a @{b }RAISE@{ub } part.  This @{b }RAISE@{ub } part means that
whenever the @{b }New@{ub } function is called in the program, the exception @{b }ERR_MEM@{ub }
will be raised if it returns @{b }NIL@{ub } (i.e., the exception @{b }ERR_MEM@{ub } is
automatically raised).  This unclutters the program by removing a lot of
error checking @{b }IF@{ub } statements.

   The precise form of the @{b }RAISE@{ub } part is:

     RAISE @{fg shine }exception@{fg text }  IF @{fg shine }function@{fg text }()  @{fg shine }compare@{fg text }  @{fg shine }value@{fg text } ,
           @{fg shine }exception2@{fg text } IF @{fg shine }function2@{fg text }() @{fg shine }compare2@{fg text } @{fg shine }value2@{fg text } ,
           ...
           @{fg shine }exceptionN@{fg text } IF @{fg shine }functionN@{fg text }() @{fg shine }compareN@{fg text } @{fg shine }valueN@{fg text }

The @{fg shine }exception@{fg text } is a constant (or number) which represents the exception
to be raised, @{fg shine }function@{fg text } is the E built-in or system function to be
automatically checked, @{fg shine }value@{fg text } is the return value to be checked against,
and  @{fg shine }compare@{fg text } is the method of checking (i.e., @{b }=@{ub }, @{b }<>@{ub }, @{b }<@{ub }, @{b }<=@{ub }, @{b }>@{ub } or @{b }>=@{ub }).
This mechanism only exists for built-in or library functions because they
would otherwise have no way of raising exceptions.  The procedures you
define yourself can, of course, use @{b }Raise@{ub } to raise exceptions in a much
more flexible way.


@ENDNODE

@NODE "Raise within an Exception Handler" "Raise within an Exception Handler"
@Prev "Automatic Exceptions" 
@Toc "main" 

@{b }Raise@{ub } within an Exception Handler
=================================

   If you call @{b }Raise@{ub } within an exception handler then control passes to
the next most recent handler.  In this way you can write procedures which
have handlers that perform local tidying up.  By using @{b }Raise@{ub } at the end of
the handler code you can invoke the next layer of tidying up.

   As an example we'll use the Amiga system functions @{b }AllocMem@{ub } and @{b }FreeMem@{ub }
which are like the built-in function @{b }New@{ub } and @{b }Dispose@{ub }, but the memory
allocated by @{b }AllocMem@{ub } @{i }must@{ui } be deallocated (using @{b }FreeMem@{ub }) when it's
finished with, before the end of the program.

     CONST SMALL=100, BIG=123456789
     
     ENUM ERR_MEM=1
     
     RAISE ERR_MEM IF AllocMem()=NIL
     
     PROC main()
       allocate()
     ENDPROC
     
     PROC allocate() HANDLE
       DEF mem=NIL
       mem:=AllocMem(SMALL, 0)
       morealloc()
       FreeMem(mem, SMALL)
     EXCEPT
       IF mem THEN FreeMem(mem, SMALL)
       WriteF('Handler: deallocating "allocate" local memory\\n')
     ENDPROC
     
     PROC morealloc() HANDLE
       DEF more=NIL, andmore=NIL
       more:=AllocMem(SMALL, 0)
       andmore:=AllocMem(BIG, 0)
       WriteF('Allocated all the memory!\\n')
       FreeMem(andmore, BIG)
       FreeMem(more, SMALL)
     EXCEPT
       IF andmore THEN FreeMem(andmore, BIG)
       IF more THEN FreeMem(more, SMALL)
       WriteF('Handler: deallocating "morealloc" local memory\\n')
       Raise(ERR_MEM)
     ENDPROC

The calls to @{b }AllocMem@{ub } are automatically checked, and if @{b }NIL@{ub } is returned
the exception @{b }ERR_MEM@{ub } is raised.  The handler in the @{b }allocate@{ub } procedure
checks to see if it needs to free the memory pointed to by @{b }mem@{ub }, and the
handler in the @{b }morealloc@{ub } checks @{b }andmore@{ub } and @{b }more@{ub }.  At the end of the
@{b }morealloc@{ub } handler is the call @{b }Raise(ERR_MEM)@{ub }.  This passes control to the
exception handler of the @{b }allocate@{ub } procedure, since @{b }allocate@{ub } called
@{b }morealloc@{ub }.

   There's a couple of subtle points to notice about this example.
Firstly, the memory variables are all initialised to @{b }NIL@{ub }.  This is because
the automatic exception raising on @{b }AllocMem@{ub } will result in the variables
not being assigned if the call returns @{b }NIL@{ub } (i.e., the exception is raised
before the assignment takes place), and the handler needs them to be @{b }NIL@{ub }
if @{b }AllocMem@{ub } fails.  Of course, if @{b }AllocMem@{ub } does not return @{b }NIL@{ub } the
assignments work as normal.

   Secondly, the @{b }IF@{ub } statements in the handlers check the memory pointer
variables do not contain @{b }NIL@{ub } by using their values as truth values.  Since
@{b }NIL@{ub } is actually zero, a non-@{b }NIL@{ub } pointer will be non-zero, i.e., true in
the @{b }IF@{ub } check.  This shorthand is often used, and so you should be aware of
it.

   It is quite common that an exception handler will want to raise the
same exception after it has done its processing.  The function @{b }ReThrow@{ub }
(which has no arguments) can be used for this purpose.  It will re-raise
the exception, but only if the exception is not zero (since this special
value means that no error occurred).  If the exception is zero then this
function has no effect.  In fact, the following code fragments (within a
handler) are equivalent:

       ReThrow()
     
       IF exception THEN Throw(exception, exceptioninfo)

   There are two examples, in Part Three, of how to use an exception
handler to make a program more readable: one deals with using data files
(see @{"String Handling and I-O" Link "Examples.guide/String Handling and I-O" }) and the other deals with opening screens and
windows (see @{"Screens" Link "Examples.guide/Screens" }).


@ENDNODE

