[back] [Abstract] [Copyright Notice] [Contents] [next]

Sather - A Language Manual - Chapter 10
Exceptions


Exceptions are used to escape from method calls under unusual circumstances. For example, a robust numerical application may wish to provide an alternate means of solving a problem under unusual circumstances such as ill conditioning. Exceptions bypass the ordinary way of returning from methods and may be used to skip over multiple callers until a suitable handler is found.


10.1 Throwing and Catching Exceptions

There are two aspects to indicating errors using exceptions - how the error is indicated at the point where it occurs. This is usually referred to as throwing the exception. The other aspect of exceptions is how the error message is handled, which is referred to as catching the exception.


10.1.1 Throwing Exceptions with raise

Exceptions are explicitly raised by raise statements. The raise statement specifies an expression, which is evaluated to obtain the exception object.

add_if_positive(i:INT) is
   if i < 0 then
      raise "Negative value:" + i + "\n";
   end;
end;

In the example above, the object happens to be a string that indicates the problem. In general, the exception object must provide enough information for the error handling mechanism. Since the error handling mechanism can discriminate between different objects of different types, it is standard practice to use the type of the exception object to indicate the type of the error that occurred.


10.1.2 Catching Exceptions with protect

Exceptions are passed to higher contexts until a handler is found and the exception is caught. Exceptions are caught using protect statements. The protect statement surrounds a piece of code, and provides an appropriate method of handling any exceptions that might occur when executing that piece of code.

protect
   foo;
when $STR then
   #ERR + "An error in foo!:" + exception.str;
when INT then 
   #ERR + "INT error=" + exception; -- 'exception' of type INT
else
   -- Some other error handling
end;

When there is an uncaught exception in a protect statement, the system finds the first type specifier listed in the 'when' lists which is a supertype of the exception object type. The statement list following this specifier is executed and then control passes to the statement following the protect statement.

In the protect clause, the exception raised may be referred to by the built in expression 'exception'[15], which refers to the exception object. The type of the exception object can be used to categorize the exception and to discriminate between exceptions when they are actually caught. In fact, the when clauses may be viewed as a typecase (see The typecase statement, section 5.6) on the exception object.

Points to note


10.1.3 Usage to avoid

Exceptions can be significantly slower than ordinary routine calls, so they should be avoided except for truly exceptional (unexpected) cases. Using exceptions to implement normal control flow may be tempting, but should be avoided. For instance, in the STR_CURSOR class, we can make use of exceptions for parsing. It might be tempting to write code like the following

test_bool:BOOL is
   protect
      current_state ::= save_state;
      b ::= get_bool;
      restore_state(current_state);
   when STR_CURSOR_EX then
      return(false);
   end;
   return(true);
end;

The above code determines whether a boolean is present in the string by trying to read one and treating an error state as evidence that there is no boolean. While it is perfectly correct code, this is an example of what you should not do. The implementation of a function should not rely on exceptions for its normal functioning. Doing so is extremely inefficient and can result in an unnecessarily complicated flow of control.


10.1.4 Alternatives to Exceptions

The alternative to using exceptions is to use a sticky error flag in the class, as is done by IEEE exceptions and the current FILE classes. This has problems such as the fact that the outermost error is logged, not the most immediate one, and it is very easy to forget to test for the error. However, this method has a much lower overhead and is suitable in certain cases.


10.2 A more elaborate example

Consider the following routine, which tries to read a boolean value from a string:

get_bool(file_name:STR):BOOL is
   f:FILE := FILE::open_for_read(file_name);
   if f.error then
      raise #FILE_OPEN_EXC(file_name);
   end;
   s:STR := f.str;  -- Read the file into a string
   f.close;         -- Close the file
   res:BOOL;
   bool ::= "";
   i:INT := 0;
   loop
      until!(~(s[i].is_alpha) or (s[i].is_space) or i >= s.size);
      bool := bool + s[i];
      i := i + 1;
   end;
   case bool
   when "true","t","True","T","TRUE" then
      return true;
   when "false","f","False","F","FALSE" then
      return false;
   else
      raise #PARSE_BAD_BOOL_EXC(s);
   end;
end;

In the above routine there are two possible errors - either the file could not be opened or it does not contain a valid boolean. The two cases can be distinguised at the point when the exception is caught

protect
   file_name:STR; ... set to a value
   b:BOOL := get_bool(s);
when FILE_OPEN_EXC then
   #ERR + "Could not open:" + exception.file_name + "\n";
when PARSE_BAD_BOOL_EXC then
   #ERR + "Error in reading boolean:" + exception.str + "\n";
end;

The classes that implement these exceptions can be fairly simple

class FILE_OPEN_EXC is
   readonly attr str:STR:
   create(file_name:STR):SAME is
      res ::= new;
      res.str := file_name;
      return res;
   end;
end;

The other exception class is very similar.


[back] [Abstract] [Copyright Notice] [Contents] [next]
Sather - A Language Manual
4 November 1999
B. Gomes, D. Stoutamire, B. Vaysman and H. Klawitter
Norbert Nemec nobbi@gnu.org