#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <setjmp.h>
#include <stdarg.h>
// all implicitly initialised to 0 ... mock Imp library for test
static int instream, outstream;
static FILE *input[4], *output[4];
// This was my first attempt to simulate %on %event/%signal in C
// and I was thinking it was looking pretty good ... until I realised
// that this is *dynamic* scoping, not static scoping. I.e. if you
// trap an event at the head of a routine and that routine recurses,
// you end up with a *lot* of event traps in the stack.
// You know, I'm not sure in Imp whether an event handler in a recursive
// procedure would catch the signal at the most recent recursion or if it would
// unwind the stack all the way back to the first call on that procedure...
// (and what happens if the same event is signalled from within the on event
// block - is there a parent handler still available or does it look for
// one in syntactically-enclosing blocks?)
// Also I don't yet remove handlers when a procedure returns (though I
// guess code to do that could be inserted by the ImpToC translator,
// except I'm thinking now that with static scoping it's not something
// that ever needs to be done...)
typedef struct IMP_EVENT {
int event;
int subevent;
int extra;
} IMP_EVENT;
typedef struct ONEVENT_HANDLER ONEVENT_HANDLER;
typedef struct ONEVENT_HANDLER {
int eventmask;
ONEVENT_HANDLER *parent_handler;
jmp_buf env;
} ONEVENT_HANDLER;
static IMP_EVENT EVENT; // global
static ONEVENT_HANDLER *global_handler = NULL; // only declared and installed in an onevent block!
void signal_event(int event, int subevent, int extra) {
ONEVENT_HANDLER *handler = global_handler;
if (global_handler == NULL) {
fprintf(stderr, "Unclaimed event %d,%d,%d\n", event, subevent, extra);
// Monitor entered from IMP ... :-) - shenannigans with gdb???
exit(1);
}
global_handler = global_handler->parent_handler; // unwind for subsequent calls
EVENT.event = event;
EVENT.subevent = subevent;
EVENT.extra = extra;
longjmp(handler->env, 1);
}
int caught_event(int event, int eventmask) {
if ((eventmask&(1<<event)) != 0) return 1;
fprintf(stderr, "Event %d was not trapped by this handler. Passing the event back up the chain.\n", event);
signal_event(EVENT.event, EVENT.subevent, EVENT.extra); // not claimed - pass it up the chain ...
}
int eventmask(int bitpos, ...) {
va_list ap;
int i;
int mask = 0;
va_start(ap, bitpos);
for (i = bitpos; i >= 0; i = va_arg(ap, int)) mask |= 1<<i;
va_end(ap);
return (mask);
}
// Trying to look as much like Imp's %on %event n,n,n, %start as possible!
#define on_event(...) ( { \
static ONEVENT_HANDLER this_handler; \
this_handler = (ONEVENT_HANDLER){.eventmask=eventmask(__VA_ARGS__), \
.parent_handler=global_handler} ; /* chain */ \
global_handler = &this_handler; \
handler_at_this_level = 1; \
setjmp(this_handler.env) && caught_event(EVENT.event,this_handler.eventmask); \
} )
void debug_traphandler_on_exit(void) {
if (global_handler) {
fprintf(stderr, "\nOn Event blocks still active as we exit() from this level:\n\n");
while (global_handler) {
fprintf(stderr, "jmp_buf at %p\n", global_handler->env);
global_handler = global_handler->parent_handler;
}
} else {
fprintf(stderr, "\nThere are *NO* On Event blocks still active as we exit() from this level");
}
fprintf(stderr, "\n");
}
#define ENTER() int handler_in_this_level = 0; do { } while(0)
void UNWIND(void) {
if (global_handler && handler_in_this_level) {
global_handler = global_handler->parent_handler;
}
}
#define return(x) do {UNWIND(); return;} while(0)
// ===============================================================================
int main(int argc, char **argv) {
auto void test_non_empty(char *file) {
auto int open_and_read(int stream, char *filename) {
int ch;
// There are no onevent blocks in this procedure. Just calls to signal_event...
if ((input[stream] = fopen(filename, "r")) == NULL) {
fprintf(stderr, "I failed to open the file. I will %%signal 9\n");
signal_event(9,stream,__LINE__);
}
ch = fgetc(input[stream]);
if (ch == EOF) {
fprintf(stderr, "File is empty. I will %%signal 5\n");
signal_event(5,stream,__LINE__);
}
instream = stream;
fprintf(stderr, "I am in %s (%s) and have read a character successfully\n", __func__, __PRETTY_FUNCTION__);
if (ch == 'X') return 'x';
else if (ch == 'Y') return('y');
// Must submit a feature request to gcc so that PRETTY_FUNCTION returns nested hierarchy!:
// e.g. main/test_non_empty/open_and_read
// They're already doing something similar for c++, and nested procedures *are* a gcc
// extension after all, so nothing standard would break...
fprintf(stderr, "Returning from 3rd level to 2nd level\n");
return ch;
}
if (on_event(5)) {
fprintf(stderr, "Event 5 caught. Empty file %s\n", file);
fprintf(stderr, "Exiting from second-level onevent block\n");
debug_traphandler_on_exit();
exit(EXIT_FAILURE);
}
int ch = open_and_read(3, file);
if (ch == 'x') then return;
fprintf(stderr, "Input 3 opened - first character is %c\n", ch);
fprintf(stderr, "Returning from 2nd level to top level\n");
}
char *testfile = "ZZZ";
if (on_event(4, 9)) {
fprintf(stderr, "Event 4 or 9 trapped correctly - Failed to open %s\n", testfile);
fprintf(stderr, "Exiting from top-level onevent block\n");
debug_traphandler_on_exit();
exit(EXIT_FAILURE);
// If we were to drop through from this onevent block, the event would no longer be trapped.
// If you did want to repeat the code, the trick would be to place a label *above* this
// onevent block, and goto it instead of dropping through.
}
test_non_empty(testfile); // 3 tests: ZZZ does not exist, ZZZ is empty, ZZZ is non-empty
fprintf(stderr, "Clean exit at end of top level\n");
debug_traphandler_on_exit();
exit(0);
}