/* frame_stack.h
 *
 * Copyright Niels Möller <nisse@lysator.liu.se> 1995
 *
 * Freely distributable under the terms and conditions of the
 * GNU General Public License.
 */


/* A frame stack keeps track of the information needed
 * for catch, throw and unwind-protect
 */

#ifndef FRAME_STACK_H_INCLUDED
#define FRAME_STACK_H_INCLUDED

#include <stddef.h>
#include <setjmp.h>
#include <obstack.h>

#ifdef __OBJC__
#include <objc/Object.h>
#endif __OBJC__


/* I use two different types of catch frames to get separate
 * spaces for C tags and Objective-C tag objects.
 *
 * frstack_catch uses a pointer into the stack as tag.
 * frstack_catch_object uses a pointer to an object. When throw()ing,
 *                  the object is asked if it matches.
 */

enum frstack_types
{
  frstack_none = 0,
  frstack_catch,
  frstack_catch_object,
  frstack_protect_fn0,
  frstack_protect_fn1,
  frstack_protect_object,
};

struct frstack_frame
{
  enum frstack_types type;
  struct frstack_frame *up;
};

/* Specific stack frames */
struct frstack_catch_frame
{
  struct frstack_frame link;
  jmp_buf where;
};

struct frstack_protect_fn0_frame
{
  struct frstack_frame link;
  void (*fn)(void);
};

struct frstack_protect_fn1_frame
{
  struct frstack_frame link;
  void (*fn)(void *arg);
  void *arg;
};

#ifdef __OBJC__
struct frstack_protect_object_frame
{
  struct frstack_frame link;
  id rec;
  SEL sel;
};


struct frstack_catch_object_frame
{
  struct frstack_frame link;
  id object;
};

#endif __OBJC__

typedef struct frstack_frame *frame_id;


struct frame_stack
{
  struct obstack ob;
  /* Pointer to the latest allocated frame. */
  frame_id last;

  /* This function is called if throw finds no matching catch */
  void (*no_catch)(int value);
    
  /* If memeory allocation fails, throw to the tag TAG if
   * it is non-NULL, otherwise, call the function ON_ERROR.
   */
  frame_id error_tag;
  int error_value;
  void (*on_error)();

  /* This is a little tricky. A throw can happen because of a
   * out of memory condition, which happens while we try to add
   * a function to be called when stack unwinding happens.
   *
   * In this case, the function being added should be called. But
   * as it is not yet stored on the stack, we must store it here instead.
   */
  enum frstack_types tmp_type;
  void (*tmp_fn)();
  void *tmp_arg;
#ifdef __OBJC__
  id tmp_rec;
  SEL tmp_sel;
#endif __OBJC__
};

void
frstack_init(struct frame_stack *stack);


/* fr_catch_setup pushes a new stack frame, and allocates
 * a jmp_buf array for setjmp. This frame should be freed
 * with fr_free() at the end of the catch construct.
 */

frame_id
fr_catch_setup(struct frame_stack *stack);

#ifdef __OBJC__
frame_id
fr_catch_object_setup(struct frame_stack *stack, id obj);
#endif __OBJC__

/* Throw never returns. If there's no matching catch, or something
 * else goes wrong, throw calls the function pointed stack->no_catch .
 */

void
fr_throw(struct frame_stack *stack, frame_id tag, int value);


/* Register a function to be called if a throw unwinds
 * the stack. */

frame_id
fr_protect_fn0(struct frame_stack *stack, void (*function)(void));


/* Register a function with argument to be called if a throw unwinds
 * the stack. */

frame_id
fr_protect_fn1(struct frame_stack *stack, void (*function)(void *arg), void *arg);


#ifdef __OBJC__
/* Register an object and a message to it be sent if the stack is
 * unwinded. */

frame_id
fr_protect_object(struct frame_stack *stack, id object, SEL message);
#endif __OBJC__


/* Return the most recent frame f such that cmp(f, data) is true.
 * No cleanup or deallocation is done. NULL is returned if no
 * matching frame is found.
 */

frame_id
frstack_find_frame(struct frame_stack *stack,
		   int (*cmp)(frame_id f, void *d),
		   void *data);


/* Unwinds stack, calling any registered functions, until
 * FRAME is found. If FRAME is NULL, all stack frames are
 * unwinded. Does not deallocate any of the frames.
 */

void
frstack_unwind(struct frame_stack *stack, frame_id frame);


/* To be called when a catch or protect is no longer active.
 * Only deallocates the frames, no other cleanup is done.
 * A frame_id of NULL deallocates all frames.
 */

void
frstack_free(struct frame_stack *stack, frame_id frame);

#endif FRAME_STACK_H_INCLUDED
