Another solution seems possible due to the fact that this is a programming-language interpreter after all.
It should be possible to re-implement the code that uses fgetc
to instead use the ps primitive: -file-
read
int bool
. This postscript operator itself uses the stdio call, but it can be called by the other file-reading functions with a continuation-passing style by pushing on the exec stack and returning. This would naturally interleave more calls to the event handler since it returns to the main loop more often.
I may still need to use a non-blocking read. But this will be easier to manage if it is called in only one place. Continuation-passing has the advantage of breaking-up larger functions into separate stages and has been successfully used to implement the window devices themselves by overriding methods in base class (the base class is implemented as a postscript dictionary). But it's still fairly new to me, so it's not my go-to duct-tape just yet. :)
I'll re-work this prototype to illustrate the approach after work. :)
Edit: Took a few days. But here's the new idea. For windows, it will need to do the nonblocking call differently, but the call can be isolated to this one place. And with the continuation-passing, the file-reading function does not need access to (or knowledge of) the event-handler, so better encapsulation.
This program behaves the same as the one in the question, it prints .
repeatedly until a keypress, then it prints the ascii code of the keypress. The .
simulates repeatedly calling the event handler while waiting for a keystroke. I had to mock-up a bit of the interpreter guts: an object type, some stacks, and an eval()
function. So this illustrates the REPL better as well.
#include <errno.h>
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
int set_raw_term (FILE *stream) {
struct termios tbuf;
if (tcgetattr(fileno(stream), &tbuf) == -1)
return -1;
tbuf.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
| INLCR | IGNCR | ICRNL | IXON);
tbuf.c_oflag &= ~OPOST;
tbuf.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
tbuf.c_cflag &= ~(CSIZE | PARENB);
tbuf.c_cflag |= CS8;
if (tcsetattr(fileno(stream), TCSANOW, &tbuf) == -1)
return -1;
return 0;
}
int set_nonblocking (FILE *stream) {
int flags;
if (set_raw_term(stream) != 0)
return -1;
if (!((flags = fcntl(fileno(stream), F_GETFL)) & O_NONBLOCK)) {
flags |= O_NONBLOCK;
fcntl(fileno(stream), F_SETFL, flags);
}
return 0;
}
int event_handler() {
putchar('.');
}
enum { null, integer, file, operator };
typedef union {
short tag;
struct {
short tag;
int val;
} int_;
struct {
short tag;
FILE *f;
} file_;
struct {
short tag;
int (*fp)();
} oper_;
} object; /* object union allows multiple types on the stacks */
object os[100]; /* operand stack */
object *tos = os; /* top of operand stack */
object es[100]; /* execution stack */
object *tes = es; /* top of execution stack */
int eval () {
if (tes == es) /* execution stack is empty */
return -1; /* return "finished" */
event_handler();
switch(tes[-1].tag) { /* type of object on top of execution stack */
case integer:
case file:
*tos++ = *--tes; /* push file or integer to operand stack */
break;
case operator:
(--tes)->oper_.fp(); /* call operator function */
break;
}
return 0; /* return "not finished" */
}
int file_read_byte () {
int ret;
object arg;
arg = *--tos; /* pop argument from operand stack */
ret = fgetc(arg.file_.f);
if (ret == EOF && (errno == EAGAIN || errno == EINTR)) { /* if no data */
*tos++ = arg; /* restore argument to operand stack */
*tes++ = (object){ .oper_.tag = operator, .oper_.fp = file_read_byte }; /* push continuation to execution stack */
return 0;
} else {
*tos++ = (object){ .int_.tag = integer, .int_.val = ret }; /* push result to operand stack */
return 0;
}
}
int main(int argc, char **argv) {
int ret;
if (set_nonblocking(stdin) != 0) {
perror(argv[0]);
return 0;
}
//printf("'%d'\n", file_read_byte(stdin));
*tos++ = (object){ .file_.tag = file, .file_.f = stdin }; /* push file argument to operand stack */
*tes++ = (object){ .oper_.tag = operator, .oper_.fp = file_read_byte }; /* push operator object to execution stack */
ret = 0;
while (ret == 0) { /* call eval until execution is "finished" */
ret = eval();
}
printf("'%d'\n", (--tos)->int_.val); /* pop returned value */
system("stty sane");
return 0;
}