Contents | < Browse | Browse >
13C. defining exceptions for built-in functions (RAISE/IF)
With exceptions like before, we have made a major gain over the
old way of defining our own "error()" function, but still it is
a lot of typing to have to check for NIL with every call to New().
The E exception handling system allows for definition of exceptions
for all E functions (like New(), OpenW() etc.), and for all Library
functions (OpenLibrary(), AllocMem() etc.), even for those
included by modules. Syntax:
RAISE <exceptionId> IF <func> <comp> <value> , ...
the part after RAISE may be repeated with a ",".
RAISE NOMEM IF New()=NIL,
NOLIBRARY IF OpenLibrary()=NIL
the first line says something like: "whenever a call to New() results
in NIL, automatically raise the NOMEM exception".
<comp> may be any of = <> > < >= <=
After this definition, we may write all through our programs:
without having to write:
IF mem=NIL THEN Raise(NOMEM)
Note that the only difference is that 'mem' never gets any value
if the runtime system invokes the handler: code is generated for
every call to New() to check directly after New() returns and call
Raise() when necessary.
We'll now be implementing a small example that would be complex to solve
without exception handling: we call a function recursively, and in each
we allocate a resource (in this case memory), which we allocate before,
and release after the recursive call. What happens when somewhere high
in the recursion a severe error occurs, and we have to leave the program?
right: we would (in a conventional language) be unable to free all the
resources lower in the recursion while leaving the program, because all
pointers to those memory areas are stored in unreachable local variables.
In E, we can simply raise an exception, and from the end of the handler
again raise an exception, thus recursively calling all handlers and
releasing all resources. Example:
ENUM NOMEM /* ,... */
RAISE NOMEM IF AllocMem()=NIL
PROC alloc() HANDLE
mem:=AllocMem(SIZE,0) /* see how many blocks we can get */
alloc() /* do recursion */
IF mem THEN FreeMem(mem,SIZE)
ReThrow() /* recursively call all handlers */
This is of course a simulation of a natural programming problem that
is usually far more complex, and thus the need for exception handling
becomes far more obvious. For a real-life example program whose error
handling would have become very difficult without exception
handlers, see the 'D.e' utility source.
The "DO" after an EXCEPT means that instead of jumping to ENDPROC,
the main code will simply continue execution in the handler as soon
as it gets there. it also sets exception to 0.
This is handy if you free resources local to a PROC in the handler.