shithub: spread

ref: a8558cb1e89245ecb38501b0fe4b64462ba69889
dir: /engine.c/

View raw version
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <draw.h>
#include "spread.h"

char Einitengine[] = "initengine: %r";

int p[3];
int pid[3];
char *preamble;

Width *firstwidth = nil;
Width *lastwidth = nil;

typedef struct Strchan Strchan;
struct Strchan {
	/* data */
	QLock l;
	Rendez full;
	Rendez empty;
	char *s;
	Response r;
	
	/* want */
	QLock w;
	int want;
};

Strchan*
mkchan(void)
{
	Strchan *c;
	c = mallocz(sizeof(Strchan), 1);
	c->full.l = &c->l;
	c->empty.l = &c->l;
	return c;
}

void
request(Strchan *c, int want)
{
	qlock(&c->w);
	c->want = want;
	qunlock(&c->w);
}

void
send(Strchan *c, char *s, int error)
{
	qlock(&c->w);
	if (!c->want) {
		qunlock(&c->w);
		return;
	}
	c->want = 0;
	qunlock(&c->w);
	qlock(&c->l);
	while (c->r.msg) {
		rsleep(&c->full);
	}
	c->r.msg = strdup(s);
	c->r.error = error;
	rwakeup(&c->empty);
	qunlock(&c->l);
}

Response
recv(Strchan *c)
{
	Response r;
	
	qlock(&c->l);
	while (!c->r.msg) {
		rsleep(&c->empty);
	}
	r = c->r;
	c->r.msg = nil;
	c->r.error = 0;
	rwakeup(&c->full);
	qunlock(&c->l);
	return r;
}

void
freeresponse(Response *r)
{
	if (r->msg)
		free(r->msg);
	r->msg = nil;
	r->error = 0;
}

Strchan *outchan;

enum {
	L_INVALID = 0,
	L_FUNC = FUNCTION,
	L_STRING = STRING,
};

int
spawnerrrdr(void)
{
	Biobuf *bin;
	char *s;
	int apid;
	
	switch (apid = rfork(RFPROC|RFMEM|RFFDG)) {
	case -1:
		sysfatal(Einitengine);
	case 0: /* child */
		break;
	default: /* parent */
		pid[2] = apid;
		return 1;
	}
	
	bin = Bfdopen(p[2], OREAD);
	if (!bin)
		sysfatal(Einitengine);
	
	while (s = Brdstr(bin, '\n', 1)) {
		if (debug)
			fprint(2, "←hoc: × %s\n", s);
		send(outchan, s, 1);
		free(s);
	}
	Bterm(bin);
	exits(nil);
}

int
inithoc(void)
{
	int in[2];
	int out[2];
	int err[2];
	int apid;
	Biobuf *bin;
	char *s;
	
	if (pipe(in) < 0)
		sysfatal(Einitengine);
	if (pipe(out) < 0)
		sysfatal(Einitengine);
	if (pipe(err) < 0)
		sysfatal(Einitengine);
	
	switch (apid = fork()) {
	case -1: /* error */
		sysfatal(Einitengine);
		break;
	case 0: /* child hoc */
		dup(out[1], 1);
		dup(in[0], 0);
		dup(err[1], 2);
		close(out[1]);
		close(out[0]);
		close(in[0]);
		close(in[1]);
		close(err[1]);
		close(err[0]);
		execl("/bin/hoc", "hoc", nil);
		sysfatal(Einitengine);
		break;
	default: /* parent */
		close(out[1]);
		close(in[0]);
		close(out[1]);
		p[0] = in[1];
		p[1] = out[0];
		p[2] = err[0];
		pid[0] = apid;
		break;
	}
	
	outchan = mkchan();
	
	switch (apid = rfork(RFPROC|RFMEM|RFFDG)) {
	case -1: /* error */
		sysfatal(Einitengine);
	case 0: /* child reader */
		break;
	default: /* parent */
		pid[1] = apid;
		return spawnerrrdr();
	}
	
	bin = Bfdopen(p[1], OREAD);
	if (!bin)
		sysfatal(Einitengine);
	
	while (s = Brdstr(bin, '\n', 1)) {
		if (debug)
			fprint(2, "←hoc: ← %s\n", s);
		send(outchan, s, 0);
		free(s);
	}
	Bterm(bin);
	exits(nil);
}

static void
hocwrite(char *s, Response *o)
{
	request(outchan, !!o);
	fprint(p[0], "%s", s);
	
	if (debug)
		fprint(2, "→hoc: %s", s);
	
	if (o) {
		*o = recv(outchan);
	}
}

void
teardown(void)
{
	if (postnote(PNPROC, pid[0], "hangup") < 0)
		sysfatal("hoc could not be exited: %r");
	if (postnote(PNPROC, pid[1], "hangup") < 0)
		sysfatal("reader could not be exited: %r");
	if (postnote(PNPROC, pid[2], "hangup") < 0)
		sysfatal("err reader could not be exited: %r");
}

static int
linetype(char *s)
{
	char *c;
	int xalpha, xnum, falpha, fnum, found;
	
	xalpha = 1;
	xnum = 0;
	falpha = 0;
	fnum = 0;
	found = L_INVALID;
	while ((c = s++) && *c) {
		if (*c != 0 && found)
			return found;
		switch (*c) {
		case 0: /* end of line */
			return L_INVALID;
		case ';': /* string line */
			if (!falpha || !fnum)
				return L_INVALID;
			found = L_STRING;
			continue;
		case '=': /* hoc function line */
			if (!falpha || !fnum)
				return L_INVALID;
			found = L_FUNC;
			continue;
		}
		if (*c >= '0' && *c <= '9') {
			if (!xnum || !falpha)
				return L_INVALID;
			fnum = 1;
			xalpha = L_INVALID;
		}
		if ((*c >= 'a' && *c <= 'z')
		 || (*c >= 'A' && *c <= 'Z') ) {
			if (!xalpha)
				return L_INVALID;
			falpha = 1;
			xnum = 1;
		}
	}
	return found;
}

void
interactivehoc()
{
	Response o;
	char *s;
	Biobuf *bin;
	
	bin = Bfdopen(0, OREAD);
	
	while (s = Brdstr(bin, '\n', 0)) {
		if (s[0] == '\n')
			continue;
		hocwrite(s, &o);
		free(s);
		if (o.msg)
			print("%s %s\n", o.error ? "×" : "→", o.msg);
	}
}

static void
calcwidth(Cell *c)
{
	Width *w;
	int tw;
	char *s;
	
	if (!font)
		return;
	
	if (!(c->buffered || c->value))
		return;
	
	s = c->buffered ? c->buffered : c->value;
	
	if (!firstwidth) {
		firstwidth = mallocz(sizeof(Width), 1);
		lastwidth = firstwidth;
		firstwidth->column = c->p.x;
		firstwidth->width = stringwidth(font, s);
		return;
	}
	for (w = firstwidth; w; w = w->next) {
		if (w->column != c->p.x)
			continue;
		w->column = c->p.x;
		tw = stringwidth(font, s);
		if (tw > w->width)
			w->width = tw;
		return;
	}
	lastwidth->next = mallocz(sizeof(Width), 1);
	lastwidth = lastwidth->next;
	lastwidth->column = c->p.x;
	tw = stringwidth(font, s);
	if (tw > lastwidth->width)
		lastwidth->width = tw;
}

static void
resetwidths(void)
{
	Width *w, *ow;
	
	for (w = firstwidth; w;) {
		ow = w->next;
		free(w);
		w = ow;
	}
	firstwidth = lastwidth = nil;
}

int
getwidth(int c)
{
	Width *w;
	
	for (w = firstwidth; w; w = w->next)
		if (w->column == c)
			return w->width;
	return -1;
}

char Hfunc[] = "func %s() { return %s }\n";
char Hstring[] = "func %s() { print \"%s\" }\n";

static void
sendctohoc(Cell *c, void*)
{
	char *h;
	char *buf;
	Response r;
	
	switch (c->type) {
	case FUNCTION:
		h = Hfunc;
		break;
	case STRING:
		calcwidth(c);
		return;
	default:
		sysfatal("code error");
	}
	
	buf = smprint(h, ptoa(c->p), c->procvalue);
	hocwrite(buf, nil);
	free(buf);
	
	r = getvalue(c->p);
	if (r.msg) {
		c->buffered = strdup(r.msg);
	} else {
		if (c->buffered)
			free(c->buffered);
		c->buffered = nil;
	}
	if (!r.error)
		calcwidth(c);
	freeresponse(&r);
}

static void
eaddcell(char *s, int type)
{
	P p;
	char *a;
	int align;
	
	switch (type) {
	case L_FUNC:
		a = strchr(s, '=');
		break;
	case L_STRING:
		a = strchr(s, ';');
		break;
	default:
		sysfatal("bad type: %d not in [1,2]", type);
	}
	
	*a = 0;
	a++;
	
	align = Aleft;
	switch (*s) {
	case '<':
		align = Aleft;
		s++;
		break;
	case '>':
		align = Aright;
		s++;
		break;
	case '|':
		align = Acenter;
		s++;
		break;
	case '.':
		align = Adot;
		s++;
		break;
	}
	
	p = atop(s);
	addcell(p, a, type, align);
}

static void
appendpreamble(char *s)
{
	char *t;
	if (!preamble) {
		preamble = strdup(s);
		return;
	}
	t = preamble;
	preamble = smprint("%s%s", preamble, s);
	free(t);
}

int
loadfile(char *file)
{
	Biobuf *bin;
	char *s;
	int type;
	
	bin = Bopen(file, OREAD);
	if (!bin) {
		werrstr("open: %r");
		return 0;
	}
	
	while (s = Brdstr(bin, '\n', 0)) {
		if (strcmp("%%%\n", s) == 0) {
			free(s);
			break;
		}
		
		appendpreamble(s);
		hocwrite(s, nil);
		free(s);
	}
	
	while (s = Brdstr(bin, '\n', 1)) {
		type = linetype(s);
		switch(type) {
		case L_INVALID:
			if (debug) {
				fprint(2, "!hoc: %s\n", s);
			}
			break;
		case L_FUNC:
		case L_STRING:
			eaddcell(s, type);
			break;
		}
		free(s);
	}
	
	Bterm(bin);
	
	sortcells();
	
	resetwidths();
	foreachcell(sendctohoc, nil);
	
	return 1;
}

static void
unbuffer(Cell *c, void*)
{
	if (c->buffered)
		free(c->buffered);
	c->buffered = nil;
}

int
updatecells()
{
	if (!sortcells())
		return 0;
	resetwidths();
	foreachcell(unbuffer, nil);
	foreachcell(sendctohoc, nil);
	return 1;
}

static char
alignchar(int align)
{
	switch (align) {
	default:
	case Aleft:
		return '<';
	case Aright:
		return '>';
	case Acenter:
		return '|';
	case Adot:
		return '.';
	}
}

static void
writecell(Cell *c, void *aux)
{
	Biobuf *b = (Biobuf*)aux;
	
	switch (c->type) {
	case FUNCTION:
		Bprint(b, "%c%s=%s\n", alignchar(c->align), ptoa(c->p), c->value);
		break;
	case STRING:
		if (!c->value || !c->value[0])
			break;
		Bprint(b, "%c%s;%s\n", alignchar(c->align), ptoa(c->p), c->value);
		break;
	}
}

int
writefile(char *file)
{
	Biobuf *bout;
	int fd;
	
	bout = Bopen(file, OWRITE);
	
	if (!bout) {
		fd = create(file, OWRITE, 0666);
		if (fd < 0) {
			werrstr("unable to create file: %r");
			return 0;
		}
		bout = Bfdopen(fd, OWRITE);
		if (!bout) {
			werrstr("error: %r");
			return 0;
		}
	}
	
	if (preamble)
		Bprint(bout, "%s", preamble);
	Bprint(bout, "%%%%%%\n");
	
	foreachcell(writecell, bout);
	
	Bterm(bout);
	return 1;
}

Response
getvalue(P cell)
{
	Cell *c;
	char *s;
	Response o;
	
	c = getcell(cell);
	if (c && c->type == STRING) {
		o.msg = strdup(c->value);
		o.error = 0;
		return o;
	}
	
	if (c && c->buffered) {
		o.msg = strdup(c->buffered);
		o.error = 0;
		return o;
	}
	
	o.msg = nil;
	s = smprint("%s()\n", ptoa(cell));
	hocwrite(s, &o);
	free(s);
	return o;
}