1.6 Exceptions and Program Termination

There are two kinds of exceptions in the BETA programming language: Static exceptions and Dynamic exceptions. Static exceptions are used when you have an exception pattern and know on compile time where it is when you need it. Dynamic exceptions are exception patterns that can handle a specific type of error. They are located by traversing the call stack when an error occurs. This means, that the exception handler does not need to be in scope to be called.

1.6.1 Static Exceptions

The default action of a static exception is to stop the program execution and print an informative error message on the stream screen. In addition the file <programname>.dump contains a dump of the call stack. Exceptions use the pattern Stop for termination. Specific error messages can be defined by specializing the exception pattern. The attribute msg of exception is a text object that is used to accumulate error messages in the classification hierarchy of exceptions. If the programmer wishes to prevent the program execution from being stopped in order to handle the exception himself, the boolean attribute continue of exception must be set to true.

The static exceptions are often defined as virtual procedure patterns of other patterns (such as the file pattern, discussed below). At the appropriate levels in the pattern hierarchy, the virtual patterns are bound so that the error messages are tailored to the specific context. The user can augment these error messages by means of the msg text object or choose to ignore the exception and continue execution.

In order to differentiate between potential fatal static exceptions and to be backward compatible with earlier versions of exceptions in the BETA programming language, the notification pattern is defined as:

notification: exception(# do true->continue; INNER #);

1.6.1.1 Examples Using Static Exceptions

In order to illustrate the use of static exceptions, let us look at a file exception example. Without using the exception handling facilities an attempt to open a non-existing file will produce the following error messages:

**** Exception processing 
  Error in file 'in.bet' No such file

Now let us see what can be done by using exceptions.

The binding of noSpaceError shows that a message can be added to msg. Msg could also have been overwritten, by first clearing msg (msg.clear). The binding of noSuchFileError shows how to prevent the system from stopping the execution when the program attempts to open a non-existing file. Instead the user is prompted for another file name. In fact there exists a procedure pattern (exists) that tests for the existence of a file, but this has not been used in this example.

(#
   inFile: @file 
     (# noSuchFileError:: (# do true->continue; false->OK #)#);
   OK: @ boolean; 
   
do 'in.bet' -> inFile.name;
   true -> OK;
   openFile: 
     (# 
     do inFile.openRead;
        (if not OK then
            'File does not exist!' ->  screen.putline;
            'Type input file name: ' ->  screen.puttext;
            getLine->inFile.name;
            true -> OK;
            restart openFile
        if)
     #);

   inFile.close;
#)

An attempt to open a non-existing file will produce the following error messages:

File does not exist!
Type input file name:

It gives the programmer the possibility to proceed with another file name.

1.6.2 Dynamic Exceptions

The exception pattern can be used for dynamic exceptions as well as for static exceptions. The way to raise a dynamic exception is to define an exception:

myExcetion: exception (# ... #)

and then throw it:

&myException[]->throw

This will initiate a search through the call stack to find a handler for the exception.

To catch a dynamic exception, the known try-catch-finally triplet from fx java can be used. In BETA you use a try pattern with a handler inside it:

(#
do try 
   (#
      handler:: 
        (#
        do when 
           (#
              type:: myException;
              predicate:: myPredicate
           do ...
           #);
        #);
      finally::
        (#
        do ...
        #);
   do ...
   #);
#)

Of course there can be more than one when clause in a handler.

In the when part of a handler, there are four ways to return to the calling code: propagate, abort, retry and continue.

Propagate is the default action for a handler. It tells throw that it does not handle the exception and throw sends the exception to the next handler on the call stack. If the programmer explicitly calls propagate, the same thing happens, except the code in the handler is executed too.

Abort aborts the current try blocks and executes all the finally part. If the exception was propagated through other try blocks, their finally parts are executed too.

Retry executes the finally part of the try block and starts it over.

Continue returns the control to the statement in the try do part which caused the exception.

You can use the predicate of the when part to further specify which exceptions are caugt in this when part.

1.6.2.1 Examples Using Dynamic Exceptios

Suppose we wish to cath a reference is none error, if assigning something to a NONE object:

(#
myPattern: object
  (#
     a: @integer
  enter a
  exit a
  #);

myObj: ^myPattern
do 10->myObj
#)

This program will give the following error message when run:

# Beta execution aborted: Reference is none.
# Look at 'testrefnone.dump'
# Generating dump file.  This may take a little while - please be patient

But if we use the framework presented above, we can catch the exception:

ORIGIN '~beta/basiclib/betaenv';
INCLUDE '~beta/basiclib/private/systemExceptionHandler';
INCLUDE '~beta/basiclib/systemExceptions';

-- program: descriptor --

(#
   myPattern: object
     (#
        a: @integer
     enter a
     do 'entering '->puttext; a->putint; putline;
     exit a
     #);
   
   myObj: ^myPattern
     
do runtimeExceptionHandler##->InstallSystemExceptionHandler;
   try
   (#
      name:: (# do 'mytry'->n[] #);
      handler:: 
        (# 
        do when
           (# type:: refNoneException
           do &myPattern[]->myObj[];
              retry;
           #)
        #);
   do 10->myObj
   #)
#)

This will yield the following result:

***Throwing refNoneException#***
                        ***Retrying mytry***
entering 10

We can see, that the refNoneException is caught in the when part, myObj is initialized and the try block is retried and this time, there is no refNoneException.


Basic Libraries - Reference Manual
© 1990-2004 Mjølner Informatics
[Modified: Wednesday June 6th 2001 at 22:13]