Go to the Next or Previous section, the Detailed Contents, or the Amiga E Encyclopedia.


13.2 Raising an Exception

When an error occurs (and you want to handle it), you raise an exception using either the Raise or Throw function. You call Raise 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. Throw is very similar to Raise, and the following description of Raise also applies to Throw. The difference is that Throw 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 Raise 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 New returns NIL. The parameter to Raise is stored in the special variable exception in the exception handler part of the code, so if Raise is called with a number other than ERR_MEM a message saying "Unknown exception" will be printed.

Try running this program with a really large BIG_AMOUNT constant, so that the New can't allocate the memory. Notice that the "Got enough memory" is not printed if Raise is called. That's because the execution of the normal procedure code stops when Raise 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 main procedure.

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

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 ENUM) is a good way of getting different constants for various exceptions. It's always a good idea to use constants for the parameter to Raise and in the exception handler, because it makes everything a lot more readable: Raise(ERR_MEM) is much clearer than Raise(1). 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 EXCEPT which automatically raises a zero exception when the code in the procedure successfully terminates. This is EXCEPT DO, with the DO suggesting to the reader that the exception handler is called even if no error occurs. Also, the argument to the Raise function defaults to zero if it is omitted (see 7.3 Default Arguments).

So, what happens if you call Raise 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 recent 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 fred calls barney, then when barney is being executed fred is a recent procedure. Because the main procedure is where the program starts it is a recent procedure for every other procedure in the program. This means, in practice:

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 fred procedure is terminated by the Raise(FRED) call, and the whole program is terminated by the Raise(BARNEY) call (since barney and main 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 fred procedure calls barney, so main and fred are recent procedures when Raise(BARNEY) is executed, and therefore the fred exception handler is called. When this handler finishes the call to fred in main is finished, so the main procedure is completed and we see the `Goodbye' message. In the previous program the Raise(BARNEY) call did not get handled and the whole program terminated at that point.


Go to the Next or Previous section, the Detailed Contents, or the Amiga E Encyclopedia.