[NAME]
ALL.dao.tutorial.exception

[TITLE]
Exception Handling

[DESCRIPTION]

The handling of exceptions in Dao is very much like the handling of panics in the Go 
programming language. The basic idea is to implement exception handling codes in a
function as deferred code block (see the Deferred Block subsection in dao.tutorial.controls
for more information about this), which will be automatically executed when the function 
exists.

In the deferred block, the built-in function recover() can be used to recover a list of 
exceptions, and handle them properly. Then the execution of the caller of function which
defines the deferred blok will be resumed normally.
     
   1  routine Test()
   2  {
   3      defer { io.writeln( 'recovering:', recover() ) }
   4      io.writeln( 'Test(): before panic;' )
   5      panic( 123 )
   6      io.writeln( 'Test(): after panic;' )
   7  }
   8  
   9  io.writeln( 'Before Test();' )
  10  Test()
  11  io.writeln( 'After Test();' )
     
In this example, the built-in function panic() is used to raise an exception. This 
function can take any value as a parameter. If the parameter is an exception object,this
exception will be raised; otherwise, an generic exception object will be created to hold 
the parameter value, and then the created exception object will be raised.

Each function can define multiple deferred blocks, but only the call to recover() in the
lastly executed deferred block will return the exceptions.

An exception type can be passed to recover() to request a specific type of exception. 
     
   1  # Example to handle user defined exception type:
   2  class MyError : Exception::Error
   3  {
   4      routine serialize(){ return ('MyError', self) }
   5  }
   6  
   7  routine Test()
   8  {
   9      defer {
  10          io.writeln( 'recovering from', recover( MyError ) )
  11      }
  12  
  13      io.writeln( 'Test(): before panic;' )
  14      panic( MyError() );
  15      io.writeln( 'Test(): after panic;' )
  16  }
  17  Test()
     


As mentioned above, the normal execution will be resumed by the caller of the function
that handles the exceptions in its deferred blocks. In order to resume the normal
execution right after handling of exceptions, a special type of code blocks can be used
to "host" the deferred blocks,
     
   1  frame { block }
   2  frame ( value ) { block }
     
These kinds of blocks are executed in new stack frames, and when these blocks are
finished and the frames are removed from the stack, deferred blocks encounted in these
blocks will be automatically executed in the same way as the deferred blocks defined in
normal functions. If the exceptions are recovered in these deferred blocks, the normal
execution will be resumed right after these frame{} blocks, 
     
   1  fout = io::stdio
   2  
   3  frame {
   4      defer { recover() }
   5      fout = io.open( "NonExistentFile.txt", 'r+' )
   6  }
   7  
   8  if( fout != io::stdio ) defer{ fout.close() }
   9  
  10  fout.writeln( 'hello' )
     

Such frame{} blocks are actually expressions that may return values. The only difference
between frame{block} and frame(value){block} is that, in the second case, if the block 
raises an exception, the default value is returned instead, and the exception is 
suppressed. This can make codes such as shown in the above example simpler,
     
   1  fout = frame( io::stdio ){ io.open( "NonExistentFile.txt", 'r+' ) }
   2  if( fout != io::stdio ) defer{ fout.close() }
   3  fout.writeln( 'hello' )