    [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' )