//
// Copyright (C) 1991 Texas Instruments Incorporated.
//
// Permission is granted to any individual or institution to use, copy, modify,
// and distribute this software, provided that this complete copyright and
// permission notice is maintained, intact, in all copies and supporting
// documentation.
//
// Texas Instruments Incorporated provides this software "as is" without
// express or implied warranty.
//
// Created: MJF 07/05/89 -- Initial design and implementation.
// Changed: LGO 10/05/89 -- Make the destructor inline
// Changed: MJF 02/22/89 -- Added group names to Exception and Excp_Handler
// Changed: MJF 02/22/89 -- Added member functions: raise, stop, match,
//                          and default_handler

// The C++ Exception handling scheme  is a raise,  handle and proceed mechanism
// similar to the  Common Lisp    Condition Handling  system.  When a   program
// encounters  a  particular or   unusual  situation which  is  often  (but not
// necessarily) an  error, it  can (1) represent   the  situation in  an object
// called an EXCEPTION, (2) announce that the situation has occurred by RAISING
// the Exception, (3) provide ways to deal with the situation  by  defining and
// establishing  HANDLERS,  and (4) continue or  PROCEED  from the situation by
// invoking a handler function.
//
// This file contains the definitions of  the Exception class and the Exception
// Handler class, a set of pre-defined subclasses of the Exception class  and a
// set of pre-defined  Exception Handler functions.   This file also  describes
// the macros EXCEPTION, RAISE, STOP and VERIFY which allow the user to create,
// and raise an Exception.
//
// The  base Exception class  includes two  slots for  reporting  Exceptions: a
// message prefix and  a  format string.  Both  of these slots are public which
// makes access easier by the  Exception  Handler  functions and the  EXCEPTION
// macro.  This class also includes  a  protected  slot which  indicates if the
// Exception was handled in some  way after being  raised.   It also includes a
// slot which identifies the group names of the Exception.  The Exception class
// includes a method  for  reporting a message (using  the  message  prefix and
// format string) on a  specified output stream.  It   includes a method  which
// sets/clears the Exception handled status slot and a method which returns the
// value of this slot.  It  includes a  method which  searches for an Exception
// Handler which  handles this Exception and  invokes  its  handler function if
// found.  It includes a method  which  defines  the default handler.  The base
// Exception class  does nothing here,  but  derived  Exception classes   could
// define a way  to  handle the  Exception  if  no global Exception  Handler is
// found.  It also includes a method which indicates if a the Exception is of a
// specified type or if this type is a group name of the Exception.
//
//
// There are three constructors for the Exception class.  In the constructor
// which takes no arguments the slots are initialized to NULL.  The other two
// constructors initialize the group names of the Exception with one or more
// Exception types.  All slots defined in a class derived from Exception should
// not be initialized with a constructor, but instead with assignment
// statements (which is possible because the slots are all public).  This
// provides an easier interface when defining an Exception, creating an
// Exception (see the EXCEPTION macro) and also defining Exception Handler
// functions.  To create an Exception and initialize its data slots, only the
// name of the slots need to be known (eg EXCEPTION(excp_name,
// slot_name=value)).  If the data slots where initialized by a constructor
// with arguments, the order of these arguments would have to be known when
// using the EXCEPTION macro to create an Exception.  Having the slots public
// also makes it easier when defining an Exception Handler function to access
// or modify these slots.  Accessor member functions could handle this, but
// their names would have to be known by the EXCEPTION macro and the Exception
// handler functions.
//
// Classes derived from the base Exception class are used as  a means of saving
// the state of  the situation and  communicating this information to Exception
// Handlers. When an Exception can be  fixed and proceeding  from the Exception
// is possible,  information on how  the Exception  has  been proceeded  by the
// Exception  Handler is also stored in  the Exception  object by the Exception
// Handler function  which  was  invoked.   There are  six predefined   classes
// provided as  part of COOL. Exception is  the base  class. From Exception are
// derived  Warning, System_Signal,  Fatal, and Error.  From  Error are derived
// System_Error and Verify_Error.
//
// Each of these Exception based classes has a default handler member function
// defined.  If no Exception Handler is found and the Exception is of type,
// Error or Fatal, its error message is reported on standard error and the
// program is terminated.  If the Exception is of type, Warning, the warning
// message is reported on standard error and the program resumes to point after
// call of RAISE().  If the Exception is of type, System_Error, the system
// error message is reported on standard error and the program is terminated.
// If the Exception is of type, System_Signal, the signal is reported and the
// program resumes to point after call of system function signal().
//
// The derived Exception classes can be implemented to include slots for saving
// the wrong/unexpected values detected by a program.  These  values report the
// state  of the situation to  the Exception Handler and  are often  used by an
// Exception Handler when reporting the Exception/Error message to  some output
// stream.  Slots can also be included in an Exception  class as a way  for the
// signaller (the one  who raises the Exception)  to  indicate to  an Exception
// Handler ways of handling/proceeding from the Exception.  For  example, if an
// Exception occurs because a variable  has  a wrong value, an Exception object
// is  first created and  then  raised/signalled.  The exception object defined
// for this situation would have a slot with the wrong  value and a  slot for a
// new value.  An Exception Handler could be establisted to handle this type of
// situation by supplying a new  value (usually by interactively informing  the
// user about  the wrong value   and querying the   user for  a new value)  and
// storing this new value in  the Exception object  and returning the Exception
// object to the signaller.  The signaller could then assign this new  value to
// the variable in error.
//
// The Excp_Handler class has two slots that deal with exceptions: an Exception
// type and an Exception Handler function.  An Exception  Handler is defined to
// invoke  a specific  Exception   Handler function for  a  specific   type  of
// Exception.
//
// An Exception Handler will handle  a particular type  of Exception by calling
// its  Exception  Handler function.  Handling   an  Exception  means trying to
// provide a way to proceed from the Exception.  An  Exception Handler function
// could handle  the  Exception by  simply reporting the  Exception to standard
// error and terminating  the program or  dropping  a core   image for  further
// debugging by user with DBX.  Another way of  proceeding is to query the user
// for a fix, store the fix in the Exception object, and return to the point in
// a program where the Exception was raised.
//
// The Excp_Handler class also contains  slots which point  to the  top (first)
// Exception Handler  and point to  the  next Exception  Handler after  itself.
// When an Exception Handler is declared, the object is placed at the top of an
// Exception  Handler stack.  When an Exception  is raised, a  call  is made to
// handle the Exception by first  searching  for  a  handler starting with  the
// handler at the top of the Exception Handler stack.
//
// An  Excp_Handler has three constructors.  Two  of the constructors create an
// instance of an  Exception Handler initializing  with  the specified type  or
// types  of  Exception  this Exception Handler will  handle  and the specified
// pointer to an Exception Handler  function.  If more  than one Exception type
// is specified the  variable argument constructor  is  called.  A  constructor
// with no arguments simpley uses Error as the Exception  type and exit_handler
// and the  Exception Handler function.   Both constructors adds  itself to the
// top of the Exception Handler stack.
//
// When  an   Exception  is raised/signalled  (via  macros  RAISE or  STOP), an
// Exception Handler for this Exception is searched.  An  Exception Handler, if
// found, deals with the Exception by  calling  its Exception Handler function.
// The Exception Handler function can correct  the Exception, ignore  it, or do
// most anything.
// 
//
// This  Exception mechanism is merely a  way to detect and raise  an Exception
// and find  the appropriate Exception Handler for  dealing with the Exception.
// To define a user specific Exception class, derive from  either the Exception
// class or one of the pre-defined Exception types, Error, Fatal, System_Error,
// System_Signal, or Verify_Error.  All  new data slots should  be public.  The
// report method might need to be modified.
//
// To  define  a way of   handling an  Exception,  define an  Exception Handler
// function which takes  as its   first  argument, a pointer to  the  Exception
// object  and   returns void.  A  pointer   to this function is  passed   when
// constructing an Exception Handler  object.  The Exception   Handler function
// can be defined with more  than one argument, but   a  new Exception  Handler
// class must be defined  with  a  new  version of  the  virtual invoke_handler
// method.  For  example, the Jump_Handler  class  modifies  the invoke_handler
// method to call a  function with two arguments,  a pointer  to the  Exception
// object and a pointer to the Exception Handler object (ie, this).
//
// To create an instance of  an Exception, use the  macro EXCEPTION.  To create
// as well  as announce/raise an Exception type,  use the macros RAISE, STOP or
// VERIFY.  To raise an Exception object, use  functions raise or  stop.  These
// macros include  argument which specify the exception  name, the group names,
// the format string,  format  arguments and slot   (key/value) arguments.  The
// exception name passed to macros EXCEPTION, RAISE and STOP must have  a class
// derived from Exception or a  subclass of Exception.  If a  name is passed to
// the VERIFY  macro   (this is optional),   it  is the   group  name   of  the
// Verify_Error class.
//
// To establish/create an  Exception Handler with a  specific handler  function
// for a specific Exception type, declare an instance  of the Exception Handler
// and provide the exception type.

#ifndef EXCEPTIONH				// If no Exception class define
#define EXCEPTIONH				// define it

#ifndef GEN_CLASSH				// If no Generic Class
#include <cool/Gen_class.h>			// include the Generic header
#endif						

// Note: "ERR_MSG" package, the text package for error message strings
// is defined in the Generic.h file.

typedef Symbol* Excp_Type;

class Exception : public Generic {
protected:
  Boolean excp_handled;				// Set by Exception Handler
  Excp_Type* group_names;			// Array of group names

public:
  const char* msg_prefix;			// Exception message prefix
  const char* format_msg;			// Exception message string

  Exception();					// Empty constructor
  Exception(Excp_Type);				// Adds one group name
  Exception(int, Excp_Type, ...);		// Adds one or more group names
  ~Exception();					// Destructor

  void set_group_names(Excp_Type);		// Sets group name
  void set_group_names(int, Excp_Type, ...);	// Sets one or more group names

  inline Boolean is_handled() CONST;		// True if Exception handled
  inline void handled(Boolean);			// Sets exception handled flag

  const char* message_prefix() CONST;		// Returns message prefix
  virtual void report(ostream& os) CONST;	// Reports message on ostream

  virtual Exception* raise();			// Search/invoke handler
  virtual void stop();				// Search/invoke handler and
						// exit if no handler.

  virtual void default_handler();		// default handler if no others

  Boolean match(Excp_Type);			// Returns TRUE if of specified
  Boolean match(Excp_Type*);			// exception type(s).

  Boolean is_group_member(Excp_Type);		// Returns TRUE if group member
  Boolean is_group_member(Excp_Type*);		// Returns TRUE if group member

  friend ostream& operator<<(ostream& os, const Exception& excp); // Output
  friend ostream& operator<<(ostream& os, const Exception* excp); // Output
};

// is_handled -- Inidicates exception status
// Input:        None
// Output:       TRUE if exception was handled, FALSE otherwise

inline Boolean Exception::is_handled() CONST {
  return excp_handled;
}

// handled -- sets Exception handled flag 
// Input:     Value to set exception flag
// Output:    None

inline void Exception::handled(Boolean val) {
  excp_handled = val;
}


// Warning -- Exceptions of this type are supported by an Exception Handler
//            function which will report message and return.

class Warning : public Exception {
public:
  Warning();
  ~Warning();
  virtual void default_handler();
};


// Error -- Exceptions of this type are supported by an Exception Handler
//          function which will report error message and terminate with exit().

class Error : public Exception {
public:
  Error();
  ~Error();
  virtual void default_handler();
};


// Fatal -- Exceptions of this type are supported by an Exception Handler 
//          function which will report error message and terminate with abort(),
//          a core dump.

class Fatal : public Exception {
public:
  Fatal();
  ~Fatal();
  virtual void default_handler();
};


// Verify_Error -- This is the default type of Exception constructed
//                 if no exception type is specified while using
//                 the macro VERIFY().

class Verify_Error : public Error {
public:
  const char* test;
   Verify_Error();
  ~Verify_Error();
  virtual void report(ostream&) CONST; // report message uses test slot
};


//  System Error -- Exceptions of this type are supported by an Exception
//                  Handler function which will report the system error message
//                  and terminate with exit(error_code).
//                - This error type is signalled when it is detected that the
//                  external variable ERRNO has been set to a system defined
//                  error code defined in from <sys/errno.h> after calling a
//                  library routine
//                - An example of Signalling:
//                    x = sqrt(y);
//                    if (errno) ERROR(System_Error,error_code=errno);

class System_Error : public Error {
public:
  int error_code;		 // the system errno
  System_Error();
  ~System_Error();
  virtual void report(ostream&) CONST;
  virtual void default_handler();
};

// ***
// System_Signal  - Exceptions of this type are supported by Exception Handler
//                  functions which deal with detection of system signals.
//                  A user can use the system function, signal(), to ignore
//                  the signal or to interrupt to a specified function
//                  when the signal occurs.  If this specified function
//                  is setup to signal a System_Signal error, an Exception
//                  Handler could be defined to respond to the signal.
//                -  A system signal is generated by some abnormal event,
//                  initiated by a user at a terminal (quit, interrupt, stop)
//                  or a program, kill(), or when a process is stopped
//                  while running in the background.
//                - Signals are optionally generated when a process resumes
//                  after being stopped, when the status of child processes
//                  changes, or when input is ready at the terminal.
//                - Most signals cause termination of the receiving process
//                  (with a core image) if no action is taken.
//                  Some signals cause the receiving process to be stopped.
//                  Some signals are simply ignored/disgarded if the
//                  process has not requested otherwise.
//                - The list of all signals with names are in signal.h.
// ***

class System_Signal : public Exception {
public:
  int signo;				// the system signum
  System_Signal();
  ~System_Signal();
  virtual void report(ostream&) CONST; // report message using signo.
  virtual void default_handler();
};

extern char* hprintf(const char*, ...);	   // heap printf

#define MSG_MAX 256			   // used by hprintf

extern Exception* Exception_g;		   // the global Exception pointer
					   // generated by EXCEPTION cpp macro

//  EXCEPTION is a cpp macro used to construct an Exception object.
//
//  EXCEPTION has the following syntax:
//
//  EXCEPTION(excp_name [, group_names] [, format_string] [rest_args])
//
//  where   excp_name  := name
//         group_names := SYM(name) [, group_names ]
//       format_string := "string"
//           rest_args := rest_arg | [, rest_arg]
//            rest_arg := format_arg | key_value_slot_arg
//          format_arg := name
//  key_value_slot_arg := name = value
//
//  In general:
//
//  EXCEPTION(excp_name, SYM(group_name1), SYM(group_name2), format_string,
//            key1=val1, arg1, key2=val2, arg2);
//
//  would generate:
//
//  (Exception_g = new excp_name(),
//   Exception_g = set_group_names(2, SYM(group_name1), SYM(group_name2)),
//   Exception_g->format_msg = hprintf(ERR_MSG(format_string),
//                                     val1, arg1, val2, arg2),
//   Exception_g->key1 = val1, 
//   Exception_g->key2 = val2, 
//   Exception_g);
//
//  Note that the order of the format_args and key_value_slot_args
//  depends on the control characters in format_string.  

// More specific examples using EXCEPTION
//
// (1) EXCEPTION with error type, group names and format string
//
// EXCEPTION(Error, SYM(Serious_Error), SYM(Very_Serious),
//                  "Serious problem here");
//
//  would generate:
//
//   (Exception_g = new Error(),
//    Exception_g = set_group_names(2, SYM(Serious_Error), SYM(Very_Serious)),
//    Exception_g->format_msg = hprintf(ERR_MSG("Serious problem here.")),
//    Exception_g);  
//   
//
// (2) EXCEPTION with error type, format string and mixture of slot arguments
//     and format arguments.
// 
// Assume a defined error type, Bad_Argment_Error, with two slots, arg_name and
// arg_value.
//
// Class Bad_Argument_Error : public Fatal {
//  public:
//   char* arg_name;
//   int arg_value;
//   Bad_Argment_Error();
// }
//
// then...
//
// EXCEPTION(Bad_Argument_Error, "Arg %s has value %d which is out of range
//                               for vector %s "
//                               arg_name="foo", arg_value=x, vec1);
//
// would generate...
//
//   (Exception_g = new Bad_Argument_Error(),
//    Exception_g->arg_name = "foo",
//    Exception_g->arg_value = x,
//    Exception_g->format_msg =
//      hprintf(ERR_MSG("Argt %s has value %d which is out of range
//                       for vector %s."),
//              "foo", x, vec1),
//    Exception_g);  
//
// (3) EXCEPTION with error type, and slot arguments (but no format message).
//
// Assume a defined error type, Out_Of_Range, with two slots, value and vector.
// 
// Class Out_of_Range : public Fatal {
//  public:
//   int value;
//   Generic vector;
//   Out_of_Range() {
//      format_msg = "Value of %d is out of range for Vector %s."
//   }
//   void report(ostream& os) {   
//      Fatal:: report(os);
//      os << form(format_msg, value, vector);
//   }
// }
//   then...
//
//   EXCEPTION(Out_of_Range, value=n, vector=v1);
//
//   would generate...
//
//   (Exception_g = new Out_of_Range(),
//    Exception_g->value =  n,
//    Exception_g->vector = v1,
//    Exception_g);  

// *********************************************************************
// Raising Exceptions
// *********************************************************************

// *********************************************************************
// RAISE - signals/raises an exception.
//         The Exception object is constructed using macro EXCEPTION.
//         The Exception object is raised using member function raise().
//         raise() searches for an Exception Handler to handle the Exception
//         and if found invokes the Exception Handler function.  It returns
//         the Exception object if the Exception Handler function returns or
//         if no Exception Handler is found.
//
//  RAISE(REST: excp_args)
//
//  RAISE(excp_name [, group_names] [, format_string] [format_or_slot_args])
//
//  where  excp_name     := name
//         group_names   := SYM(name) [, group_names]
//         format_string := "string"
//         format_or_slot_args := format_or_slot_arg [, format_or_slot_args]
//         format_or_slot_arg  := format_arg | slot_arg = value
//
// *********************************************************************

MACRO RAISE(REST: excp_args) {
  // construct an exception object and signal it.
  (EXCEPTION(excp_args))->raise()
}

// *********************************************************************
//  STOP - signals/raises an error (regardless of exception type)
//         by terminating program with exit() if no Exception Handler
//         handles the exception.
//
//         Normally - only errors of type Error will exit,
//                    and errors of type Fatal will drop core.
// 
//         The Exception object is constructed using macro EXCEPTION.
//         The Exception object is raised using member function stop().
//         stop() searches for an Exception Handler to handle the Exception
//         and terminate program if the Exception Handler function
//         which is invoked returns or if no Exception Handler is found.
//
//  STOP(REST: excp_args)
//
//  STOP(excp_name [, group_names] [, format_string] [format_or_slot_args])
//
//  where  excp_name     := name
//         group_names   := SYM(name) [, group_names]
//         format_string := "string"
//         format_or_slot_args := format_or_slot_arg [, format_or_slot_args]
//         format_or_slot_arg  := format_arg | slot_arg = value
//
// *********************************************************************

MACRO STOP(REST: excp_args) {
  // construct an exception object and signal a fatal error.
 (EXCEPTION(excp_args))->stop()
}

// *********************************************************************
// VERIFY(test_expression [, alias_name], REST: args)
//   - verifies that test_expression is TRUE and
//     raises an Exception of type Verify_Error if TEST_EXPRESSION fails.
//     ALIAS_NAME is optional, but if specified is an alias/group name of the
//     Verify_Error exception.
//
//     
//
// VERIFY(test_expression)
// VERIFY(test_expression, REST: args)
// 
// VERIFY(test_expression, [, group_names]
//                         [, format_string] [format_or_slot_args])
//
//  where  group_names   := SYM(name) [, group_names]
//         format_string := "string"
//         format_or_slot_args := format_or_slot_arg [, format_or_slot_args]
//         format_or_slot_arg  := format_arg | slot_arg = value
//
// *********************************************************************

MACRO VERIFY(test_expression, REST: args=count) {
  if (!test_expression) 
#if count
    (EXCEPTION(Verify_Error, args, test=#test_expression))->raise()
#else
    (EXCEPTION(Verify_Error, test=#test_expression))->raise()
#endif
}


// *********************************************************************
// Exception Handler Class
// *********************************************************************

typedef void (*Excp_Handler_Function) (Exception*);

class Excp_Handler : public Generic {
protected:
  static Excp_Handler* top_handler;
  Excp_Handler* next_handler;

  Excp_Handler_Function eh_func;  // Exception Handler function
  Excp_Type* excp_types;	  // Array of exception types for this handler 

  friend Exception* Exception::raise();

public:
  Excp_Handler();
  Excp_Handler(Excp_Handler_Function, Excp_Type);
  Excp_Handler(Excp_Handler_Function, int, Excp_Type, ...);
  ~Excp_Handler();

  // invokes Exception Handler function on specified Exception
  virtual Boolean invoke_handler(Exception*);

  // finds Exception Handler for specified Exception
  // starting at optional specified Exception Handler.
  friend Excp_Handler* find_handler(Exception* exp = NULL,
				    Excp_Handler* ehp = NULL);
};

#endif				// End ifdef of EXCEPTIONH
