/* frame_stack.c         -*-objc-*-
 *
 * A frame stack keeps track of the information needed for
 * catch, throw and unwind-protect.
 *
 * Copyright Niels Möller <nisse@lysator.liu.se> 1995
 *
 * Freely distributable under the terms and conditions of the
 * GNU General Public License.
 */

/* This file supports both ANSI-C and Objective-C.
 * If compile it with a C-compiler, only C is supported.
 * Compile it with a n Objective-C compiler, defining __OBJC__
 * to get Objective-C support as well.
 */

/* All this code is written so that it is possible to have several
 * frame_stacks. This is probably overkill, but if someone ever tries
 * to make this thread-safe, it might be useful.
 */

#include <frame_stack.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define obstack_chunk_alloc frame_stack_alloc
#define obstack_chunk_free free


/* frame_stack_alloc needs to know which frame_stack it is handling */
static struct frame_stack *me;

static void *frame_stack_alloc(size_t size);


static int cmp_eq(frame_id f, void *d)
{
  return (f == d);
}


void
frstack_init(struct frame_stack *stack)
{
  memset(stack, 0, sizeof(*stack));
  obstack_init(&(stack->ob));
}


frame_id
fr_catch_setup(struct frame_stack *stack)
{
  frame_id frame;

  me = stack;
  frame = obstack_alloc(&(stack->ob),
			sizeof(struct frstack_catch_frame));
  me = NULL;
  
  frame->type = frstack_catch;

  frame->up = stack->last;
  stack->last = frame;
  return frame;
}

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

  me = stack;
  frame = obstack_alloc(&(stack->ob),
			sizeof(struct frstack_catch_object_frame));
  me = NULL;
  
  frame->type = frstack_catch_object;

  frame->up = stack->last;
  stack->last = frame;

  ( (struct frstack_catch_object_frame *) frame)->object = obj;
  
  return frame;
}
#endif __OBJC__


void
fr_throw(struct frame_stack *stack, frame_id tag, int value)
{
  /* Perhaps there's no real need to do this search.
   * p should always equal tag.
   */
  frame_id p;

  p = frstack_find_frame(stack, cmp_eq, tag);
  frstack_unwind(stack, p);
  
  if (p)
    {
      longjmp(( (struct frstack_catch_frame *) p)->where, value);
      /* Never returns here */
    }
  /* No matching catch found */
  frstack_free(stack, NULL);

  if (stack->no_catch)
    (*(stack->no_catch))(value);

  /* No default handler either */
  fprintf(stderr, "frame_stack: throw without catch\n");
  exit(EXIT_FAILURE);
}

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

  /* For error recovery in case obstack_alloc fails when allocating
   * another stack frame */
  me = stack;
  stack->tmp_type = frstack_protect_fn0;
  stack->tmp_fn = function;
  
  frame = obstack_alloc(&(stack->ob),
			sizeof(struct frstack_protect_fn0_frame));
  me = NULL;
  stack->tmp_type = frstack_none;
  
  frame->type = frstack_protect_fn0;

  frame->up = stack->last;
  stack->last = frame;
  ( (struct frstack_protect_fn0_frame *) frame)->fn = function;
  return frame;
}

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

  /* For error recovery in case obstack_alloc fails when allocating
   * another stack frame */
  me = stack;
  stack->tmp_type = frstack_protect_fn1;
  stack->tmp_fn = function;
  stack->tmp_arg = arg;
  
  frame = obstack_alloc(&(stack->ob),
			sizeof(struct frstack_protect_fn1_frame));
  me = NULL;
  stack->tmp_type = frstack_none;
  
  frame->type = frstack_protect_fn1;

  frame->up = stack->last;
  stack->last = frame;
  ( (struct frstack_protect_fn1_frame *) frame)->fn = function;
    ( (struct frstack_protect_fn1_frame *) frame)->arg = arg;
  return frame;
}

#ifdef __OBJC__

frame_id
fr_protect_object(struct frame_stack *stack, id object, SEL message)
{
  frame_id frame;

  /* For error recovery in case obstack_alloc fails when allocating
   * another stack frame */
  me = stack;
  stack->tmp_type = frstack_protect_object;
  stack->tmp_rec = object;
  stack->tmp_sel = message;
  
  frame = obstack_alloc(&(stack->ob),
			sizeof(struct frstack_protect_object_frame));
  me = NULL;
  stack->tmp_type = frstack_none;
  
  frame->type = frstack_protect_object;

  frame->up = stack->last;
  stack->last = frame;
  ( (struct frstack_protect_object_frame *) frame)->rec = object;
  ( (struct frstack_protect_object_frame *) frame)->sel = message;
  return frame;
}

#endif __OBJC__


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

  for (p = stack->last; p && !cmp(p, data); p = p->up)
    ;
  return p;
}


void
frstack_unwind(struct frame_stack *stack, frame_id frame)
{
  frame_id p;
  int stop;
  
  for (p = stack->last, stop = 0;
       p && !stop;
       p = p->up)
    {
      switch (p->type)
	{
	case frstack_none:
	case frstack_catch:
#ifdef __OBJC__
	case frstack_catch_object:
#endif __OBJC__
	  ; /* Skip these frames */
	break;

	case frstack_protect_fn0:
	  (( (struct frstack_protect_fn0_frame *) p)->fn)();
	  break;
	case frstack_protect_fn1:
	  (( (struct frstack_protect_fn1_frame *) p)->fn)
	    (( (struct frstack_protect_fn1_frame *) p)->arg);
	  break;
	  
#ifdef __OBJC__
	case frstack_protect_object:
	  [( (struct frstack_protect_object_frame *) p)
	      ->rec perform:
	      ( (struct frstack_protect_object_frame *) p) -> sel];
	  break;
#endif __OBJC__

	default:
	  /* This is fatal. */
	  fprintf(stderr, "frame_stack: Frame stack corrupt!"
		  "Unknown frame type %d\n", p->type);
	  abort();
	}
      if (p == frame)
	stop = 1;
    }
      
}


void
frstack_free(struct frame_stack *stack, frame_id frame)
{
  if (frame)
    {
      stack->last = frame->up;
      obstack_free(&(stack->ob), frame);
    }
  else
    {
      stack->last = NULL;
      obstack_free(&(stack->ob), NULL);
      obstack_init(&(stack->ob));
    }
}


static void *frame_stack_alloc(size_t size)
{
  void *p = malloc(size);
  if (p)
    return p;

  {
    /* Handle out of memory */
    struct frame_stack *me2 = me;
    me = NULL;    /* Restore */
    /* First handle the case that memory allocation fails while a
     * unwind-protect function is being installed */

    switch (me2->tmp_type)
      {
      case frstack_protect_fn0:
	(me2->tmp_fn)();
	break;
      case frstack_protect_fn1:
	(me2->tmp_fn)(me2->tmp_arg);
	break;
	
#ifdef __OBJC__
      case frstack_protect_object:
	[me2->tmp_rec perform: me2->tmp_sel];
	break;
#endif __OBJC__
      default:
	/* Ignore */
      }
  if (me2->error_tag)
    fr_throw(me2, me2->error_tag, me2->error_value);
  else
    if (me2->on_error)
      (*(me->on_error))();
  }
  /* Out of memory, and no handler for it! */
  fprintf(stderr,"frame_stack: Out of memory\n");
  exit(EXIT_FAILURE);
}
