/* header */

#include <glib.h>
#include <setjmp.h>

void g_exception_set_error (GError *err);
GError *g_exception_get_error (void);

void g_exception_set_jmp_buf (jmp_buf *jmpbuf);
jmp_buf *g_exception_get_jmp_buf (void);

G_GNUC_NORETURN void g_exception_throw (GError *err, int depth);

#define g_try_begin \
  { \
    int __g_except_depth; \
    jmp_buf __g_except_jmp_buf; \
    GError  *__g_except_error = NULL; \
    jmp_buf *__g_except_jmp_buf_save = g_exception_get_jmp_buf (); \
    g_exception_set_jmp_buf (&__g_except_jmp_buf); \
    if (!(__g_except_depth = setjmp(__g_except_jmp_buf)) || \
	(g_exception_set_jmp_buf (__g_except_jmp_buf_save), \
	 __g_except_error = g_exception_get_error (), \
	 FALSE)) \
      {

#define g_catch(Domain, Code, err) \
        __g_except_error = NULL; \
      } \
    else if ((!(Domain+0) || (Domain+0) == __g_except_error->domain) && \
	     (!(  Code+0) || (  Code+0) == __g_except_error->code  )) \
      { \
	GError *err = __g_except_error;

#define g_finally \
      } \
      {

#define g_try_end \
      } \
    g_raise; \
    g_exception_set_error (NULL); \
    g_exception_set_jmp_buf (__g_except_jmp_buf_save); \
  }

#define g_throw(err) \
  G_STMT_START { \
    GError *__g_except_throw_error = (err); \
    if (__g_except_throw_error) \
      g_exception_throw (__g_except_throw_error, 1); \
  } G_STMT_END

#define g_raise \
  G_STMT_START { \
    if (__g_except_error) \
      g_exception_throw (__g_except_error, __g_except_depth + 1); \
  } G_STMT_END



/* implementation */

static GError  *_g_except_error_current;
static jmp_buf *_g_except_jmp_buf_current;

void
g_exception_set_error (GError *err)
{
  if (_g_except_error_current)
    g_free (_g_except_error_current);
  _g_except_error_current = err;
}

GError *
g_exception_get_error (void)
{
  return _g_except_error_current;
}

void
g_exception_set_jmp_buf (jmp_buf *jmpbuf)
{
  _g_except_jmp_buf_current = jmpbuf;
}

jmp_buf *
g_exception_get_jmp_buf (void)
{
  return _g_except_jmp_buf_current;
}

void
g_exception_throw (GError *err, int depth)
{
  jmp_buf *target = g_exception_get_jmp_buf ();

  if (!target)
    g_error ("Uncaught exception (depth %d): %s", depth, err ? err->message : "(null)");

  g_exception_set_error (err);
  longjmp (*target, depth);
}



/* test case */

static void
my_read_file (const char *filename)
{
  GError *err = NULL;
  GIOChannel *stream;

  stream = g_io_channel_new_file (filename, "r", &err);
  g_throw (err);

  /* ... */

  g_io_channel_shutdown (stream, TRUE, &err);
  g_throw (err);
}

int
main (int argc, char **argv)
{
  g_try_begin {

    if (argc > 1)
      my_read_file (argv[1]);

  } g_catch (G_FILE_ERROR, G_FILE_ERROR_ACCES, e) {

    g_warning ("Oh oh: %s; ignoring", e->message);

  } g_catch (,, e) {

    g_message ("The message is: %s; chaining up", e->message);
    g_raise;

  } g_finally {
    /* right now finally doesn't work if you raise or throw in your
     * catch blocks... */

    g_message ("Finally done");

  } g_try_end

  return 0;
}
