/*** exception.h ***/
/* C exception handling system
 * (c) Paul Field
 * v1.00 - 23/8/1995
 * v1.01 -  1/9/1995 : Added some more debugging code
 * v1.02 - 29/9/1995 : Added backtrace output on exception for debugging purposes
 * v1.03 -  9/5/1996 : Catches SIGABRT (which stops assert() exiting the program)
 *
 * based on code by Martin Ebourne and Jos Horsmeier
 * (see also David A. Spuler "C++ and C Debugging, Testing and Reliability",
 *  Prentice Hall, ISBN 0-13-308172-9)
 *
 * N.B. This module contains debugging code which will catch some of the
 * mistakes you might make with this module. To disable this code (for the
 * final version of your program), compile with the macro NDEBUG defined.
 *
 * This implementation is intended to:
 *  1) keep things simple:
 *     a) macros are necessary for the implementation but this
 *        version only uses 3; it doesn't try to do clever things in the
 *        catch block (e.g. the EXCEPTION() and ALLEXCEPT macros in
 *        Horsmeier's code)
 *     b) error reporting is left to other program modules - this gives the user
 *        more control over error handling and reporting
 *  2) keep 'normal' code fast: although in most cases the effect will be
 *     negligable, this implementation tries to make the code executed by
 *     an un-exceptional use of the 'try' block as fast as possible.
 *  3) Provide support for three types of errors:
 *       os_errors    (either thrown internally or by OSLib)
 *       signals      (SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM)
 *       user-defined (a user-defined code; useful when returning a message
 *                     is not appropriate)
 *
 * How to use this module
 * ~~~~~~~~~~~~~~~~~~~~~~
 * Before doing anything else, call exception_initialise()
 *
 * To handle exceptions you use a try...catch...catch_end construct:
 *
 *   (1) some code
 *
 *   try
 *    { (2) try block (DO NOT 'return' or 'break' out of this block
 *                     [or, less likely, 'goto', 'longjmp' or 'continue' out of it])
 *    }
 *   catch
 *    { (3) catch block
 *    }
 *   catch_end
 *
 *   (4) more code
 *
 * Normally execution would go 1,2,4. However, if an exception occurs while 2
 * is being executed (the exception can occur in functions called in 2) then
 * the flow of control will be 1,some of 2,3,4. In other words the catch
 * block contains error-handling code that will be called whenever an error
 * (in the form of an exception) occurs in the try block.
 *
 * You can have many try...catch...catch_end constructs in your program and they
 * can be nested (i.e. appear inside another try or catch block). Because of
 * this, a common thing to do in a catch block is to partly handle the error
 * (e.g. free memory, close files) and then 'throw' the exception so that
 * a 'higher-up' catch block will catch the exception and act on it.
 *
 * Generating exceptions
 * ~~~~~~~~~~~~~~~~~~~~~
 * Exceptions can be generated by:
 *  1) calling the throw() routines (see below)
 *  2) calling a non-X SWI (e.g. using OSLib)
 *  3) doing something which causes a signal
 *
 * Handling exceptions (the catch block)
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * Often a catch block doesn't care what the exception was: it simply tidies
 * up and then uses throw() to pass the exception on to a 'higher-up' catch
 * block. However, many catch blocks need to know the nature of the exception;
 * perhaps so they can generate an error message. Such a block can use
 * exception_current(), perhaps like this:
 *
 * catch
 *  { const exception *e = exception_current();
 *    os_report(&e->error);
 *  }
 * catch_end
 *
 * This example is for exposition only. A real program is likely to have
 * a general-purpose exception reporting function.
 *
 * If an exception occurs outside of a catch block then exit() will be called
 * (so any atexit()-registered functions will be called - perhaps to try to
 * emergency-save data). In this case, the error will go unreported so it's
 * usually better to use a try...catch...catch_end around all the code in main()
 *
 *
 * Things to watch out for
 * ~~~~~~~~~~~~~~~~~~~~~~~
 * Because try...catch...catch_end is not part of the language, the compiler
 * cannot check the syntax. However, if you misuse it you will generally
 * get some weird errors so, if an error doesn't make sense, check the
 * try...catch...catch_end structure.
 *
 *
 * Unless they are declared as 'volatile', automatic variables that are
 * modified within the try block will have an undefined value after
 * the try block if an exception occurs i.e.
 *
 * { int i = 1;               // automatic variable
 *   try
 *    { i = 2;                 // modified in try block
 *      generate_exception();
 *    }
 *   catch
 *    { // i is undefined here
 *    }
 *   catch_end
 *   // i is undefined here
 *
 * Debugging
 * ~~~~~~~~~
 * If this code is compiled in debugging mode (i.e. the macro NDEBUG is *not* defined)
 * then, on exception, the exception is output to stderr along with a stack dump.
 * The exception is then processed as usual.
 */



#ifndef _exception_h
#define _exception_h

#include <setjmp.h>
#include <assert.h>

#include "kernel.h"

#ifdef __cplusplus
extern "C" {
#endif

typedef enum
{
	  exception_signal
	, exception_os
	, exception_user
} exception_type;

typedef struct exception_str
{
	const char*     file;
	int             line;
	int             bt;
	exception_type  type;
	_kernel_oserror error;
} exception;

/**
 * Sets up the exception system.
 * Call before using any exception routines.
 */
void exception_initialise(void);

/**
 * Returns a description of the current exception.
 * The value returned is undefined if this is used outside of a 'catch' block.
 */
const exception* exception_current(void);

/**
 * Throws an exception of type exception_user.
 *
 * @param  id  user specific code
 */
void __throw_user(const char*, int, unsigned id);
#define throw_user(x) __throw_user(__FILE__, __LINE__, x)

/**
 * Throws an exception of type exception_os.
 *
 * @param  err  pointer to an os error.
 */
void __throw_os(const char*, int, const _kernel_oserror* err);
#define throw_os(x) __throw_os(__FILE__, __LINE__, x)

/**
 * Throws an exception of type exception_os.
 * Uses the last os error recorded by the system.
 */
void __throw_last_oserror(const char*, int);
#define throw_last_oserror() __throw_last_oserror( __FILE__, __LINE__)

/**
 * Throws an exception of type exception_os by converting a string into an so error.
 *
 * @param  pformat  a printf format string.
 * @param  ...      the format string parameters.
 */
#ifdef __CC_NORCROFT
#pragma -v1 // hint to the compiler to check f/s/printf format
#endif
void __throw_string(const char*, int, const char* pformat, ...);
#ifdef __CC_NORCROFT
#pragma -v0 // return to default
#endif
#define throw_string(...) __throw_string(__FILE__, __LINE__, __VA_ARGS__)

/**
 * Throws the current exception on level up.
 * (Only makes sense to call this within a 'catch' block)
 */
void throw_current(void);

/*********************************************
 *   ABANDON HOPE ALL YE WHO ENTER HERE :-)  *
 *                                           *
 * Beyond this point are implementation      *
 * details which you do not need to be aware *
 * of and should not rely on in any way      *
 * (because they could change in a future    *
 * release)                                  *
 *********************************************/

/* The exception system is currently implemented
 * as a linked-list of jumpbufs on the stack.
 */
typedef struct _exception_block_str
{
	struct _exception_block_str*	previous;
	jmp_buf				jmpbuf;
} _exception_block;

extern _exception_block* _exception_list;

/* A macro that detects list corruption
 * (probably caused by breaking or returning from a try block)
 */
#define escape_from_nested_try_using_return_or_break() (_exception_list != &_exception_current)

/* Basically, 'try' merely links a new _exception_block into the linked list
 * and then sets up a jmpbuf so that 'throw' can use longjmp to get at the
 * catch block.
 *
 * The instructions that link in the block could have been put in a function:
 * this would save 1 or 2 ARM instructions (depending on how the compiler optimises
 * the code) but would incur a function call overhead.
 */
#define try\
 { _exception_block _exception_current;\
   _exception_current.previous = _exception_list;\
   _exception_list = &_exception_current;\
   if (!setjmp(_exception_current.jmpbuf))\
    {

#define catch\
      assert(!escape_from_nested_try_using_return_or_break());\
      _exception_list = _exception_current.previous;\
    }\
   else\
    { /* Don't need to remove block from list - throw_xxx() will have\
       * done this already\
       */

#define catch_end\
    }\
 }

#ifdef __cplusplus
}
#endif

#endif
