/*
 * File: exception.h
 * -----------------
 * This interface exports a portable exception-handling mechanism for C.
 *
 * The exception.h interface makes it possible for clients to specify a
 * handler for an exceptional conditions in a syntactically readable way. 
 * As a client, your first step is to declare an exception condition name
 * by declaring a variable of type exception, as in
 *
 *    exception MyException;
 *
 * Normal visibility rules apply, so that you should declare the exception
 * variable at the appropriate level.  For example, if an exception is
 * local to an implementation, it should be declared statically within that
 * module.  If an exception condition is shared by many modules, the
 * exception variable should be declared in an interface and exported to
 * all clients that need it.  This package defines and exports the
 * exception ErrorException, which is likely to be sufficient for many
 * clients.
 *
 * The basic functionality of exceptions is that one piece of code can
 * "throw" an exception so that it can then be "caught" by special code in
 * a dynamically enclosing section of the program.  Exceptions are
 * triggered by calling the pseudo-function throw with the exception name,
 * as in
 *
 *    throw(MyException);
 *
 * Exceptions are handled using the try statement (actually implemented
 * using macros), which has the form:
 *
 *    try {
 *       . . . statements in the body of the block . . .
 *    } catch (exception1) {
 *       . . . statements to handle exception 1 . . .
 *    } catch (exception2) {
 *       . . . statements to handle exception 2 . . .
 *    } catch (ANY) {
 *       . . . statements to handle any exception . . .
 *    } finally {
 *       . . . statements to be executed before exit in all cases . . .
 *    } endtry
 *
 * Any number of catch clauses may appear.  The ANY and finally clauses are
 * optional.
 *
 * When the program encounters the try statement, the statements in the
 * body are executed.  If no exception conditions are thrown during that
 * execution, either in this block or by a function call nested inside this
 * block, control passes to the end of the try statement when the last
 * statement in the block is executed.  If an exception is thrown during
 * the dynamic execution of the block, control immediately passes to the
 * statements in the appropriate catch clause.  Only the statements in that
 * clause are executed; no break statement is required to exit the block.
 *
 * The try statement guarantees that the statements in the finally clause,
 * if present, will always be executed, even if an exception is thrown that
 * is caught at a higher level.
 *
 * If no handler for the exception appears anywhere in the control history,
 * the program exits with an error.
 *
 * Examples of use:
 *
 * 1.  Catching errors
 *
 * The following code fragment traps calls to error, so that the program
 * does not quit but instead returns to the top-level read-and-execute
 * loop.
 *
 *    while (true) {
 *        try {
 *            printf("> ");
 *            cmd = readCommand();
 *            executeCommand(cmd);
 *        } catch (ErrorException) {
 *            printf("error: %s\n", (string) getExceptionValue());
 *            -- additional handling code, if any --
 *        } endtry
 *    }
 *
 * If either readCommand or executeCommand calls error, control will be
 * passed back to the main loop, after executing any additional handler
 * code.  The error message is passed as the exception value and can be
 * printed as shown in the example.
 *
 * 2.  Handling control-C (Unix systems)
 *
 * The following code extends the example above so that typing ^C also
 * returns to top-level:
 *
 *    #include 
 * 
 *    static exception ControlCException;
 *    static int errorCount = 0;
 *    static int controlCHandler();
 * 
 *    main() {
 *        string cmd;
 * 
 *        signal(SIGINT, controlCHandler);
 *        while (true) {
 *            try {
 *                printf("> ");
 *                cmd = readCommand();
 *                executeCommand(cmd);
 *            } catch (ControlCException) {
 *                printf("^C\n");
 *                signal(SIGINT, controlCHandler);
 *            } catch (ErrorException) {
 *                errorCount++;
 *            } endtry
 *        }
 *    }
 * 
 *    static int controlCHandler() {
 *        throw(ControlCException);
 *    }
 */

#ifndef _exception_h
#define _exception_h

#include <setjmp.h>
#include <string.h>
#include "cslib.h"

/*
 * Implementation notes
 * --------------------
 * Most of the implementation of the exception mechanism is included
 * directly within the macros defined by this file.  Clients should
 * ordinarily be able to read the general package description and ignore
 * the details of the code.
 */

/* Define error status indicators */

#define E_TOO_MANY_EXCEPT_CLAUSES 101
#define E_UNHANDLED_EXCEPTION 102

/* Codes to keep track of the state of the try handler */

#define ES_BODY       1
#define ES_CATCH      2
#define ES_CAUGHT     3
#define ES_RETRY      4
#define ES_FINALLY    5
#define ES_FINISHED   6

/*
 * The following conditional macro definition is used to debug
 * the <code>try</code> macro.  If __DebugTry__ is set, the
 * program traces the finite state machine operation used
 * to implement exceptions.
 */

#ifdef __DebugTry__
#   define debugTry(s) printf(s "\n", _es)
#else
#   define debugTry(s)
#endif

/*
 * Type: exception
 * ---------------
 * This type is used to define the general class of exceptions.  Exceptions
 * are specified by their address, so that the actual structure does not
 * matter.  Strings are used here so that exporters of exceptions can store
 * the exception name for the use of debuggers and other tools.
 */

typedef struct { string name; } exception;

/*
 * This structure is used internally to maintain a chain of
 * exception scopes on the control stack.
 */

typedef struct exceptionContextBlock {
    jmp_buf jmp;
    exception *id;
    void *value;
    string name;
    void *lock;
    struct exceptionContextBlock *link;
} ExceptionContextBlock;

/*
 * Type: ErrorException
 * --------------------
 * Predefined exception type for the error function.
 */
extern exception ErrorException;

/*
 * Constant: ANY
 * -------------
 * Predefined exception type that allows handlers to catch an arbitrary
 * exception.
 */
extern exception ANY;

/* Macros for the pseudostatement forms */

#define throw(e) throwException(&e, #e, NULL)

#define try \
      { \
          ExceptionContextBlock _ctx; \
          int _es; \
          _es = ES_BODY; \
          _ctx.lock = NULL; \
          debugTry("try"); \
          debugTry("push exception stack [es = %d]"); \
          pushExceptionStack(&_ctx); \
          if (setjmp(_ctx.jmp) != 0) { \
              _es = ES_CATCH; \
          } \
          debugTry("setjmp [es = %d]"); \
          if (_es == ES_BODY)

#define catch(e) \
          if (_es == ES_BODY || _es == ES_CAUGHT) _es = ES_FINALLY; \
          debugTry("catch (" #e ") [es = %d]"); \
          if (_es == ES_CATCH && (_ctx.id == &e || &e == &ANY)) { \
              _es = ES_CAUGHT; \
          } if (_es == ES_CAUGHT)

#define finally \
          debugTry("finally [es = %d]"); \
          debugTry("pop exception stack [es = %d]"); \
          popExceptionStack(); \
          _es = (_es == ES_CATCH) ? ES_RETRY : ES_FINISHED;

#define endtry \
          debugTry("endtry [es = %d]"); \
          if (_es != ES_FINISHED && _es != ES_RETRY) { \
              debugTry("pop exception stack [es = %d]"); \
              popExceptionStack(); \
          } \
          if (_es == ES_RETRY) _es = ES_CATCH; \
          if (_es == ES_CATCH) unwindException(&_ctx); \
      }

#define getExceptionName() _ctx.name
#define getExceptionValue() _ctx.value
#define getCurrentException() _ctx.id

/* Internal entry points -- see implementation for details */

void throwException(exception *e, string name, void *value);
void pushExceptionStack(ExceptionContextBlock *cptr);
void popExceptionStack(void);
void unwindException(ExceptionContextBlock *cptr);

#endif