
// 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>
#include <backtrace.h>

void *__bt_state = NULL;
static int bt_started = 0;

int bt_callback(void *v, uintptr_t u, const char *filename, int lineno, const char *function) {
  /// demangle function name if C++
  const char *func_name = function;
  int status;
  //char *demangled = abi::__cxa_demangle(function, NULL, NULL, &status);
  //if (status == 0) {
  //  func_name = demangled;
  //}

  if ((filename || lineno || func_name) && (strcmp(func_name, "print_back_trace"))) {
    if (!bt_started) {
      fprintf(stderr, "\nMONITOR ENTERED FROM IMP\n\n"); bt_started = 1;
    }
    fprintf(stderr, "Entered from %s at %s:%d\n\n", func_name, filename, lineno);
  }
  return 0;
}

void bt_error_callback(void *v, const char *msg, int errnum) {
  if (strcmp(msg, "no debug info in ELF executable")) {
    fprintf(stderr, "Error %d occurred when getting the stacktrace: %s", errnum, msg);
  }
}

void bt_error_callback_create(void *v, const char *msg, int errnum) {
  fprintf(stderr, "Error %d occurred when initializing the stacktrace: %s", errnum, msg);
}

void init_back_trace(const char *filename) {
  __bt_state = backtrace_create_state(filename, 0, bt_error_callback_create, NULL);
}

void print_back_trace(void) {
  if (!__bt_state) { /// make sure init_back_trace() is called
    fprintf(stderr, "Make sure init_back_trace() is called before calling print_stack_trace()\n");
    abort();
  }
  bt_started = 0;
  backtrace_full((struct backtrace_state *) __bt_state, 0, bt_callback, bt_error_callback, NULL);
  bt_started = 0;
}

// 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 _imp_eventfm _imp_eventfm;
typedef struct _imp_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;
  _imp_eventfm *prev;
} _imp_eventfm;

_imp_eventfm *_imp_event = NULL;
sig_atomic_t _imp_last_signal = 0;

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

// croak() is an attempt to enable writing error messages from within
// signal service routines.  Hence why it is very basic.
static inline void croak(const char *msg) {
  write(2, msg, (unsigned int)strlen(msg));
  _exit(1);
}

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

_imp_eventfm *_imp_on_event_enter(_imp_eventfm *f) {
  static int initialized = 0;
  if (!initialized) {  
    struct sigaction sa;
    sigemptyset(&sa.sa_mask);
    sa.sa_handler = _imp_signal_handler;
    sa.sa_flags = 0;
    if (sigaction(SIGUSR1, &sa, NULL) < 0) croak("sigaction(SIGUSR1, &sa, NULL) < 0\n");
    initialized = 1;
  }
  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 _imp_on_event_leave(_imp_eventfm *f) {
  _imp_eventfm *lost = _imp_event;
  _imp_event = f->prev;
  free(lost);
}


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

    // %on %event * %start
    if (sigsetjmp(_imp_on_event_enter(((_imp_eventfm *)malloc(sizeof(_imp_eventfm))))->env, 1)) {
      fprintf(stderr, "%%SIGNAL %d,%d,%d trapped by recursive_nested_proc(%d)\n\n",
              _imp_event->event, _imp_event->subevent, _imp_event->extra, I);
      fprintf(stderr, "LOCALVARIABLE = %d\n\n", Localvariable);
      
      // This has to be inserted before any signal call *in an on-event block*:
      _imp_on_event_leave(_imp_event);  // unfortunately putting it at the top of the block stops the user accessing EVENT_EVENT etc.
                                        // Although I suppose _imp_on_event_leave could copy all those fields to the replacement.
      
      // %signal event, sub, extra+1 (and hidden message field)
      _imp_event->event = 15; _imp_event->subevent = 42; _imp_event->extra += 1; _imp_event->message = ""; raise(SIGUSR1);
      // WARNING: only outstanding problem is if we drop through here without resignalling,
      //          any following signals will go wrong.  And probably cause an infinite loop.
    } // %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);

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

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

    print_back_trace(); // simulate %monitor
      
    return;
  }

  recursive_nested_proc(1);

  _imp_on_event_leave(_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) {
  init_back_trace("/tmp/bt.dat");
  toplevel();
  fprintf(stderr, "Returned from toplevel to exit main()\n");
  return 0;
}
