
// Testing C code to reproduce IMP %signal and %on %event mechanism.
// Still some minor conceptual issues but sort of works...

#define _POSIX_C_SOURCE 200809L

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <setjmp.h>
#include <unistd.h>
#include <string.h>

// Setting this up so that *all* IMP %signal calls map to linux signal SUGUSR1
// Any other Linux signals (such as generated by say divide-by-zero) will need
// to be trapped and converted into an IMP signal.

typedef struct eventfm eventfm;
typedef struct eventfm {
  // These are values used in Imp77 signal mechanism, not relevant to C:
  int event;
  int subevent;
  int extra;
  char *message;
  // these are for the C signal mechanism:
  sigjmp_buf env;
  eventfm *prev;
} eventfm;

eventfm *_imp_event = NULL;
sig_atomic_t last_signal = 0;

/* Jump to the current innermost active event frame. */

static inline void croak(const char *msg) {
  write(2, msg, (unsigned int)strlen(msg));
  _exit(1);
}

void event_handler(int sig) {
  if (_imp_event == NULL) croak("NULL _imp_event\n");
  last_signal = sig; // C signal type such as SIGUSR1
  siglongjmp(_imp_event->env, 1);
}

void install_event_handler_once(void) {
  static int installed = 0;
  if (installed) return;
  
  struct sigaction sa;
  sigemptyset(&sa.sa_mask);
  sa.sa_handler = event_handler;
  sa.sa_flags = 0;
  if (sigaction(SIGUSR1, &sa, NULL) < 0) croak("sigaction(SIGUSR1, &sa, NULL) < 0\n");
  installed = 1;
}

eventfm *on_event_push(eventfm *f) {
  install_event_handler_once();
  f->prev = _imp_event;
  _imp_event = f;
  return f;
  // "return sigsetjmp(f->env, 1);" CAUSES A SEGFAULT.  MOVING IT TO THE POINT OF CALL FIXES THAT.
}

void on_event_pop(eventfm *f) {
  _imp_event = f->prev;
}

#ifdef NEVER
/* Propagate to the immediately enclosing frame without re-raising SIGUSR1. */
void propagate_event(int event, int subevent, int extra, char *message) {
  if (_imp_event == NULL || _imp_event->prev == NULL) croak("propagate_event: _imp_event == NULL || _imp_event->prev == NULL\n");

  _imp_event = _imp_event->prev;
  _imp_event->event = event;
  _imp_event->subevent = subevent;
  _imp_event->extra = extra;
  _imp_event->message = message;
  siglongjmp(_imp_event->env, 1);
  croak("Should not get here, siglongjmp failed\n");
}
#endif

void toplevel(void) {
  
  void recursive_nested_proc(int I) {
    int Localvariable = I;

    // %on %event * %start
    if (sigsetjmp(on_event_push(({eventfm *tmp_event = malloc(sizeof(eventfm));tmp_event;}))->env, 1)) {
      fprintf(stderr, "%%SIGNAL %d,%d,%d entered from recursive_nested_proc(%d)\n\n",
              _imp_event->event, _imp_event->subevent, _imp_event->extra, I);
      fprintf(stderr, "LOCALVARIABLE = %d\n\n", Localvariable);

      // %signal event, sub, extra+1 (and hidden message field)
#ifdef NEVER
      propagate_event(_imp_event->event, _imp_event->subevent, _imp_event->extra+1, _imp_event->message);
#else
      on_event_pop(_imp_event);
      _imp_event->event = 15; _imp_event->subevent = 42; _imp_event->extra += 1; _imp_event->message = ""; raise(SIGUSR1);
#endif
    } // %finish

    if (I >= 10) {
      // %signal 15,42,17
      _imp_event->event = 15; _imp_event->subevent = 42; _imp_event->extra = 17; _imp_event->message = ""; raise(SIGUSR1);
    }
    
    recursive_nested_proc(I + 3);

    on_event_pop(_imp_event);
  } // end of recursive_nested_proc(int I)

  // set up another handler at the top level.
  if (sigsetjmp(on_event_push(({eventfm *tmp_event = malloc(sizeof(eventfm));tmp_event;}))->env, 1)) {
    fprintf(stderr, "END OF toplevel: caught propagated %%signal %d, %d, %d\n", _imp_event->event, _imp_event->subevent, _imp_event->extra);
    on_event_pop(_imp_event);
    return;
  }

  recursive_nested_proc(1);

  on_event_pop(_imp_event);
}

// An IMP program might install a signal handler at the very top level which does
// the IMP diagnostic thing, to catch any events that the user program does not trap.
int main(void) {
  toplevel();
  fprintf(stderr, "Returned from toplevel to exit main()\n");
  return 0;
}
