#include "lib9.h"
#include "kernel.h"
#include "draw.h"
#include "tk.h"

#define	O(t, e)		((long)(&((t*)0)->e))

static char* pdist(TkTop*, TkOption*, void*, char**, char*, char*);
static char* pstab(TkTop*, TkOption*, void*, char**, char*, char*);
static char* ptext(TkTop*, TkOption*, void*, char**, char*, char*);
static char* pwinp(TkTop*, TkOption*, void*, char**, char*, char*);
static char* pbmap(TkTop*, TkOption*, void*, char**, char*, char*);
static char* pbool(TkTop*, TkOption*, void*, char**, char*, char*);
static char* pfont(TkTop*, TkOption*, void*, char**, char*, char*);
static char* pfrac(TkTop*, TkOption*, void*, char**, char*, char*);
static char* pnnfrac(TkTop*, TkOption*, void*, char**, char*, char*);
static char* pctag(TkTop*, TkOption*, void*, char**, char*, char*);
static char* ptabs(TkTop*, TkOption*, void*, char**, char*, char*);
static char* pcolr(TkTop*, TkOption*, void*, char**, char*, char*);
static char* pimag(TkTop*, TkOption*, void*, char**, char*, char*);
static char* psize(TkTop*, TkOption*, void*, char**, char*, char*);
static char* pnndist(TkTop*, TkOption*, void*, char**, char*, char*);
static char* pact(TkTop*, TkOption*, void*, char**, char*, char*);
static char* pignore(TkTop*, TkOption*, void*, char**, char*, char*);
static char* psticky(TkTop*, TkOption*, void*, char**, char*, char*);
static char* plist(TkTop*, TkOption*, void*, char**, char*, char*);

static char* (*oparse[])(TkTop*, TkOption*, void*, char**, char*, char*) =
{
	/* OPTdist */	pdist,
	/* OPTstab */	pstab,
	/* OPTtext */	ptext,
	/* OPTwinp */	pwinp,
	/* OPTflag */	pstab,
	/* OPTbmap */	pbmap,
	/* OPTbool */	pbool,
	/* OPTfont */	pfont,
	/* OPTfrac */	pfrac,
	/* OPTnnfrac */	pnnfrac,
	/* OPTctag */	pctag,
	/* OPTtabs */	ptabs,
	/* OPTcolr */	pcolr,
	/* OPTimag */	pimag,
	/* OPTsize */	psize,
	/* OPTnndist */	pnndist,
	/* OPTact */	pact,
	/* OPTignore */	pignore,
	/* OPTsticky */	psticky,
	/* OPTlist */ plist,
};

char*
tkskip(char *s, char *bl)
{
	char *p;

	while(*s) {
		for(p = bl; *p; p++)
			if(*p == *s)
				break;
		if(*p == '\0')
			return s;	
		s++;
	}
	return s;
}

/* XXX - Tad: error propagation? */
char*
tkword(TkTop *t, char *str, char *buf, char *ebuf, int *gotarg)
{
	int c, lev, tmp;
	char *val, *e, *p, *cmd;
	if (gotarg == nil)
		gotarg = &tmp;

	/*
	 * ebuf is one beyond last byte in buf; leave room for nul byte in
	 * all cases.
	 */
	--ebuf;

	str = tkskip(str, " \t");
	*gotarg = 1;
	lev = 1;
	switch(*str) {
	case '{':
		/* XXX - DBK: According to Ousterhout (p.37), while back=
		 * slashed braces don't count toward finding the matching
		 * closing braces, the backslashes should not be removed.
		 * Presumably this also applies to other backslashed
		 * characters: the backslash should not be removed.
		 */
		str++;
		while(*str && buf < ebuf) {
			c = *str++;
			if(c == '\\') {
				if(*str == '}' || *str == '{' || *str == '\\')
					c = *str++;
			}
			else
			if(c == '}') {
				lev--;
				if(lev == 0)
					break;
			}
			else
			if(c == '{')
				lev++;
			*buf++ = c;
		}
		break;
	case '[':
		/* XXX - DBK: According to Ousterhout (p. 33) command
		 * substitution may occur anywhere within a word, not
		 * only (as here) at the beginning.
		 */
		cmd = malloc(strlen(str));	/* not strlen+1 because the first character is skipped */
		if ( cmd == nil ) {
			buf[0] = '\0';	/* DBK - Why not an error message? */
			return str;
		}
		p = cmd;
		str++;
		while(*str) {
			c = *str++;
			if(c == '\\') {
				if(*str == ']' || *str == '[' || *str == '\\')
					c = *str++;
			}
			else
			if(c == ']') {
				lev--;
				if(lev == 0)
					break;
			}
			else
			if(c == '[')
				lev++;
			*p++ = c;
		}
		*p = '\0';
		val = nil;
		e = tkexec(t, cmd, &val);
		free(cmd);
		 /* XXX - Tad: is this appropriate behavior?
		  *	      Am I sure that the error doesn't need to be
		  *	      propagated back to the caller?
		  */
		if(e == nil && val != nil) {
			strncpy(buf, val, ebuf-buf);
			buf = ebuf;
			free(val);
		}
		break;
	case '\'':
		str++;
		while(*str && buf < ebuf)
			*buf++ = *str++;
		break;
	case '\0':
		*gotarg = 0;
		break;
	default:
		/* XXX - DBK: See comment above about command substitution.
		 * Also, any backslashed character should be replaced by
		 * itself (e.g. to put a space, tab, or [ into a word.
		 * We assume that the C compiler has already done the
		 * standard ANSI C substitutions.  (But should we?)
		 */
		while(*str && *str != ' ' && *str != '\t' && buf < ebuf)
			*buf++ = *str++;
	}
	*buf = '\0';
	return str;
}

static TkOption*
Getopt(TkOption *o, char *buf)
{
	while(o->o != nil) {
		if(strcmp(buf, o->o) == 0)
			return o;
		o++;
	}
	return nil;
}

TkName*
tkmkname(char *name)
{
	TkName *n;

	n = malloc(sizeof(struct TkName)+strlen(name));
	if(n == nil)
		return nil;
	strcpy(n->name, name);
	n->link = nil;
	n->obj = nil;
	return n;
}

char*
tkparse(TkTop *t, char *str, TkOptab *ot, TkName **nl)
{
	int l;
	TkOptab *ft;
	TkOption *o;
	TkName *f, *n;
	char *e, *buf, *ebuf;

	l = strlen(str);
	if (l < Tkmaxitem)
		l = Tkmaxitem;
	buf = malloc(l + 1);
	if(buf == 0)
		return TkNomem;
	ebuf = buf + l + 1;

	e = nil;
	while(e == nil) {
		str = tkword(t, str, buf, ebuf, nil);
		switch(*buf) {
		case '\0':
			goto done;
		case '-':
			if (buf[1] != '\0') {
				for(ft = ot; ft->ptr; ft++) {
					o = Getopt(ft->optab, buf+1);
					if(o != nil) {
						e = oparse[o->type](t, o, ft->ptr, &str, buf, ebuf);
						break;
					}
				}
				if(ft->ptr == nil){
					e = TkBadop;
					tkerr(t, buf);
				}
				break;
			}
			/* fall through if we've got a singleton '-' */
		default:
			if(nl == nil) {
				e = TkBadop;
				tkerr(t, buf);
				break;
			}
			n = tkmkname(buf);
			if(n == nil) {
				e = TkNomem;
				break;
			}
			if(*nl == nil)
				*nl = n;
			else {
				for(f = *nl; f->link; f = f->link)
					;
				f->link = n;
			}
		}		
	}

	if(e != nil && nl != nil)
		tkfreename(*nl);
done:
	free(buf);
	return e;
}

char*
tkconflist(TkOptab *ot, char **val)
{
	TkOption *o;
	char *f, *e;

	f = "-%s";
	while(ot->ptr != nil) {
		o = ot->optab;
		while(o->o != nil) {
			e = tkvalue(val, f, o->o);
			if(e != nil)
				return e;
			f = " -%s";
			o++;
		}
		ot++;
	}
	return nil;
}

char*
tkgencget(TkOptab *ft, char *arg, char **val, TkTop *t)
{
	Tk *w;
	char *c;
	Point g;
	TkEnv *e;
	TkStab *s;
	TkOption *o;
	int wh, con, i, n, flag, *v;
	char *r, *buf, *fmt;

	buf = mallocz(Tkmaxitem, 0);
	if(buf == nil)
		return TkNomem;

	tkitem(buf, arg);
	r = buf;
	if(*r == '-')
		r++;
	o = nil;
	while(ft->ptr) {
		o = Getopt(ft->optab, r);
		if(o != nil)
			break;
		ft++;
	}
	if(o == nil) {
		tkerr(t, r);
		free(buf);
		return TkBadop;
	}

	switch(o->type) {
	default:
		tkerr(t, r);
		free(buf);
		return TkBadop;
	case OPTignore:
		return nil;
	case OPTact:
		w = ft->ptr;
		g = tkposn(w);
		n = g.y;
		if(o->aux == 0)
			n = g.x;
		free(buf);
		return tkvalue(val, "%d", n);
	case OPTdist:
	case OPTnndist:
		free(buf);
		return tkvalue(val, "%d", OPTION(ft->ptr, int, o->offset));
	case OPTsize:
		w = ft->ptr;
		if(strcmp(r, "width") == 0)
			wh = w->req.width;
		else
			wh = w->req.height;
		free(buf);
		return tkvalue(val, "%d", wh);
	case OPTtext:
		c = OPTION(ft->ptr, char*, o->offset);
		if(c == nil)
			c = "";
		free(buf);
		return tkvalue(val, "%s", c);
	case OPTwinp:
		w = OPTION(ft->ptr, Tk*, o->offset);
		if(w == nil || w->name == nil)
			c = "";
		else
			c = w->name->name;
		free(buf);
		return tkvalue(val, "%s", c);
	case OPTstab:
		s = o->aux;
		c = "";
		con = OPTION(ft->ptr, int, o->offset);
		while(s->val) {
			if(con == s->con) {
				c = s->val;
				break;
			}
			s++;
		}
		free(buf);
		return tkvalue(val, "%s", c);
	case OPTflag:
		con = OPTION(ft->ptr, int, o->offset);
		flag = 0;
		for (s = o->aux; s->val != nil; s++)
			flag |= s->con;
		c = "";
		for (s = o->aux; s->val != nil; s++) {
			if ((con & flag) == s->con) {
				c = s->val;
				break;
			}
		}
		free(buf);
		return tkvalue(val, "%s", c);
	case OPTfont:
		e = OPTION(ft->ptr, TkEnv*, o->offset);
		free(buf);
		if (e->font != nil)
			return tkvalue(val, "%s", e->font->name);
		return nil;
	case OPTcolr:
		e = OPTION(ft->ptr, TkEnv*, o->offset);
		i = AUXI(o->aux);
		free(buf);
		return tkvalue(val, "#%.8lux", e->colors[i]);
	case OPTfrac:
	case OPTnnfrac:
		v = &OPTION(ft->ptr, int, o->offset);
		n = (int)o->aux;
		if(n == 0)
			n = 1;
		fmt = "%s";
		for(i = 0; i < n; i++) {
			tkfprint(buf, *v++);
			r = tkvalue(val, fmt, buf);
			if(r != nil) {
				free(buf);
				return r;
			}
			fmt = " %s";
		}
		free(buf);
		return nil;
	case OPTbmap:
		return tkvalue(val, "%d", OPTION(ft->ptr, Image*, o->offset) != nil);
	case OPTimag:
		return tkvalue(val, "%d", OPTION(ft->ptr, TkImg*, o->offset) != nil);
	}
}

static char*
pact(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
{
	USED(buf);
	USED(ebuf);
	USED(str);
	USED(place);
	tkerr(t, o->o);
	return TkBadop;
}

static char*
pignore(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
{
	char *p;
	USED(t);
	USED(o);
	USED(place);

	p = tkword(t, *str, buf, ebuf, nil);
	if(*buf == '\0')
		return TkOparg;
	*str = p;
	return nil;
}

static char*
pdist(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
{
	int d;
	char *e;
	TkEnv *env;

	USED(buf);
	USED(ebuf);

	/*
	 * this is a bit of a hack, as 0 is a valid option offset,
	 * but a nil aux is commonly used when 'w' and 'h' suffixes
	 * aren't appropriate.
	 * just make sure that no structure placed in TkOptab->ptr
	 * with an OPTdist element has a TkEnv as its first member.
	 */

	if (o->aux == nil)
		env = nil;
	else
		env = OPTION(place, TkEnv*, AUXI(o->aux));
	e = tkfracword(t, str, &d, env);
	if(e != nil)
		return e;
	OPTION(place, int, o->offset) = TKF2I(d);
	return nil;
}

static char*
pnndist(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
{
	char* e;
	int oldv;

	oldv = OPTION(place, int, o->offset);
	e = pdist(t, o, place, str, buf, ebuf);
	if(e == nil && OPTION(place, int, o->offset) < 0) {
		OPTION(place, int, o->offset) = oldv;
		return TkBadvl;
	}
	return e;	
}

static char*
psize(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
{
	Tk *tk;
	char *e;
	int d, off;

	USED(ebuf);
	e = tkfracword(t, str, &d, OPTION(place, TkEnv*, AUXI(o->aux)));
	if (e != nil)
		return e;
	if(d < 0)
		return TkBadvl;

	tk = place;
	/*
	 * XXX there's no way of resetting Tksetwidth or Tksetheight.
	 * could perhaps allow it by setting width/height to {}
	 */
	if(strcmp(buf+1, "width") == 0) {
		tk->flag |= Tksetwidth;
		off = O(Tk, req.width);
	}
	else {
		tk->flag |= Tksetheight;
		off = O(Tk, req.height);
	}
	OPTION(place, int, off) = TKF2I(d);
	return nil;
}

static char*
pstab(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
{
	char *p;
	int mask;
	TkStab *s, *c;

	p = tkword(t, *str, buf, ebuf, nil);
	if(*buf == '\0')
		return TkOparg;

	for(s = o->aux; s->val; s++)
		if(strcmp(s->val, buf) == 0)
			break;
	if(s->val == nil)
		return TkBadvl;

	*str = p;
	if(o->type == OPTstab) {
		OPTION(place, int, o->offset) = s->con;
		return nil;
	}

	mask = 0;
	for(c = o->aux; c->val; c++)
		mask |= c->con;

	OPTION(place, int, o->offset) &= ~mask;
	OPTION(place, int, o->offset) |= s->con;

	/*
	 * a hack, but otherwise we have to dirty the focus order
	 * every time any command is executed on a widget
	 */
	if (!strcmp(o->o, "takefocus"))
		tkdirtyfocusorder(t);
	return nil;
}

enum {
	Stickyn = (1<<0),
	Stickye = (1<<1),
	Stickys = (1<<2),
	Stickyw = (1<<3)
};

static int stickymap[16] =
{
	0,
	Tknorth,
	Tkeast,
	Tknorth|Tkeast,
	Tksouth,
	Tkfilly,
	Tksouth|Tkeast,
	Tkeast|Tkfilly,
	Tkwest,
	Tknorth|Tkwest,
	Tkfillx,
	Tknorth|Tkfillx,
	Tksouth|Tkwest,
	Tkwest|Tkfilly,
	Tksouth|Tkfillx,
	Tkfillx|Tkfilly,
};

static char*
psticky(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
{
	char *p, *s;
	int flag, sflag;

	p = tkword(t, *str, buf, ebuf, nil);
	*str = p;

	flag = 0;
	for (s = buf; *s; s++) {
		switch (*s) {
		case 'n':
			flag |= Stickyn;
			break;
		case 's':
			flag |= Stickys;
			break;
		case 'e':
			flag |= Stickye;
			break;
		case 'w':
			flag |= Stickyw;
			break;
		case ' ':
		case ',':
			break;
		default:
			return TkBadvl;
		}
	}
	sflag =  OPTION(place, int, o->offset) & ~(Tkanchor|Tkfill);
	OPTION(place, int, o->offset) = sflag | stickymap[flag];
	return nil;
}

static char*
ptext(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
{
	char **p;

	*str = tkword(t, *str, buf, ebuf, nil);

	p = &OPTION(place, char*, o->offset);
	if(*p != nil)
		free(*p);
	if(buf[0] == '\0')
		*p = nil;
	else {
		*p = strdup(buf);
		if(*p == nil)
			return TkNomem;
	}
	return nil;
}

static char*
pimag(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
{
	int locked;
	Display *d;
	TkImg **p, *i;

	i = nil;
	p = &OPTION(place, TkImg*, o->offset);
	*str = tkword(t, *str, buf, ebuf, nil);
	if(*buf != '\0') {
		i = tkname2img(t, buf);
		if(i == nil)
			return TkBadvl;
		i->ref++;
	}

	if(*p != nil) {
		d = t->display;
		locked = lockdisplay(d);
		tkimgput(*p);
		if(locked)
			unlockdisplay(d);
	}
	*p = i;
	return nil;
}

static char*
pbmap(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
{
	Display *d;
	Image *i, **p;
	int locked, fd;
	char *c;

	p = &OPTION(place, Image*, o->offset);

	d = t->display;
	*str = tkword(t, *str, buf, ebuf, nil);
	if(*buf == '\0' || *buf == '-') {
		if(*p != nil) {
			locked = lockdisplay(d);
			freeimage(*p);
			if(locked)
				unlockdisplay(d);
			*p = nil;
		}
		return nil;
	}

	if(buf[0] == '@')
		i = display_open(d, buf+1);
	else
	if(buf[0] == '<') {
		buf++;
		fd = strtoul(buf, &c, 0);
		if(c == buf) {
			return TkBadvl;
		}
		i = readimage(d, fd, 1);
	}
	else {
		char *file;

		file = mallocz(Tkmaxitem, 0);
		if(file == nil)
			return TkNomem;

		snprint(file, Tkmaxitem, "/icons/tk/%s", buf);
		i = display_open(d, file);
		free(file);
	}
	if(i == nil)
		return TkBadbm;

	if(*p != nil) {
		locked = lockdisplay(d);
		freeimage(*p);
		if(locked)
			unlockdisplay(d);
	}
	*p = i;
	return nil;
}

static char*
pfont(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
{
	TkEnv *e;
	Display *d;
	int locked;
	Font *font;

	*str = tkword(t, *str, buf, ebuf, nil);
	if(*buf == '\0')
		return TkOparg;

	d = t->display;
	font = font_open(d, buf);
	if(font == nil)
		return TkBadft;

	e = tkdupenv(&OPTION(place, TkEnv*, o->offset));
	if(e == nil) {
		freefont(font);		/* XXX lockdisplay around this? */
		return TkNomem;
	}
	if(e->font)
		font_close(e->font);
	e->font = font;

	locked = lockdisplay(d);
	e->wzero = stringwidth(font, "0");
	if ( e->wzero <= 0 )
		e->wzero = e->font->height / 2;
	if(locked)
		unlockdisplay(d);

	return nil;
}

static int
hex(int c)
{
	if(c >= 'a')
		c -= 'a'-'A';
	if(c >= 'A')
		c = 10 + (c - 'A');
	else
		c -= '0';
	return c;
}

static ulong
changecol(TkEnv *e, int setcol, int col, ulong rgba)
{
	if (setcol) {
		e->set |= (1<<col);
	} else {
		rgba = 0;
		e->set &= ~(1<<col);
	}
	e->colors[col] = rgba;
	return rgba;
}

char*
tkparsecolor(char *buf, ulong *rgba)
{
	char *p, *q, *e;
	int R, G, B, A;
	int i, alpha, len, alen;
	/*
	 * look for alpha modifier in *#AA or *0.5 format
	 */
	len = strlen(buf);
	p = strchr(buf, '*');
	if(p != nil) {
		alen = len - (p - buf);
		if(p[1] == '#') {
			if(alen != 4)
				return TkBadvl;
			alpha = (hex(p[2])<<4) | (hex(p[3]));
		} else {
			q = p+1;
			e = tkfrac(&q, &alpha, nil);
			if (e != nil)
				return e;
			alpha = TKF2I(alpha * 0xff);
		}
		*p = '\0';
		len -= alen;
	} else
		alpha = 0xff;
	
	if (*buf == '#') {
		switch(len) {
		case 4:			/* #RGB */
			R = hex(buf[1]);
			G = hex(buf[2]);
			B = hex(buf[3]);
			*rgba = (R<<28) | (G<<20) | (B<<12) | 0xff;
			break;
		case 7:			/* #RRGGBB */
			R = (hex(buf[1])<<4)|(hex(buf[2]));
			G = (hex(buf[3])<<4)|(hex(buf[4]));
			B = (hex(buf[5])<<4)|(hex(buf[6]));
			*rgba = (R<<24) | (G<<16) | (B<<8) | 0xff;
			break;
		case 9:			/* #RRGGBBAA */
			R = (hex(buf[1])<<4)|(hex(buf[2]));
			G = (hex(buf[3])<<4)|(hex(buf[4]));
			B = (hex(buf[5])<<4)|(hex(buf[6]));
			A = (hex(buf[7])<<4)|(hex(buf[8]));
			*rgba = (R<<24) | (G<<16) | (B<<8) | A;
			break;
		default:
			return TkBadvl;
		}
	} else {
		for(i = 0; tkcolortab[i].val != nil; i++)
			if (!strcmp(tkcolortab[i].val, buf))
				break;
		if (tkcolortab[i].val == nil)
			return TkBadvl;
		*rgba = tkcolortab[i].con;
	}
	if (alpha != 0xff) {
		tkrgbavals(*rgba, &R, &G, &B, &A);
		A = (A * alpha) / 255;
		*rgba = tkrgba(R, G, B, A);
	}
	return nil;
}

static char*
pcolr(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
{
	TkEnv *env;
	char *e;
	ulong rgba, dark, light;
	int color, setcol;

	*str = tkword(t, *str, buf, ebuf, nil);
	rgba = 0;
	if(*buf == '\0') {
		setcol = 0;
	} else {
		setcol = 1;
		e = tkparsecolor(buf, &rgba);
		if(e != nil)
			return e;
	}
		
	env = tkdupenv(&OPTION(place, TkEnv*, o->offset));
	if(env == nil)
		return TkNomem;

	color = AUXI(o->aux);
	rgba = changecol(env, setcol, color, rgba);
	if(color == TkCbackgnd || color == TkCselectbgnd || color == TkCactivebgnd) {
		if (setcol) {
			light = tkrgbashade(rgba, TkLightshade);
			dark = tkrgbashade(rgba, TkDarkshade);
		} else
			light = dark = 0;
		changecol(env, setcol, color+1, light);
		changecol(env, setcol, color+2, dark);
	}
	return nil;
}

static char*
pbool(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
{
	USED(buf);
	USED(ebuf);
	USED(str);
	USED(t);
	OPTION(place, int, o->offset) = 1;
	return nil;
}

static char*
pwinp(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
{
	Tk *f;
	char *p;

	p = tkword(t, *str, buf, ebuf, nil);
	if(*buf == '\0')
		return TkOparg;
	*str = p;
	
	f = tklook(t, buf, 0);
	if(f == nil){
		tkerr(t, buf);
		return TkBadwp;
	}

	OPTION(place, Tk*, o->offset) = f;
	return nil;
}

static char*
pctag(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
{
	char *p;
	TkName *n, *l;

	*str = tkword(t, *str, buf, ebuf, nil);

	l = nil;
	p = buf;
	while(*p) {
		p = tkskip(p, " \t");
		buf = p;
		while(*p && *p != ' ' && *p != '\t')
			p++;
		if(*p != '\0')
			*p++ = '\0';

		if(p == buf || buf[0] >= '0' && buf[0] <= '9') {
			tkfreename(l);
			return TkBadtg;
		}
		n = tkmkname(buf);
		if(n == nil) {
			tkfreename(l);
			return TkNomem;
		}
		n->link = l;
		l = n;
	}
	tkfreename(OPTION(place, TkName*, o->offset));
	OPTION(place, TkName*, o->offset) = l;
	return nil;
}

static char*
pfrac(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
{
	char *p, *e;
	int i, n, d, *v;

	*str = tkword(t, *str, buf, ebuf, nil);

	v = &OPTION(place, int, o->offset);
	n = (int)o->aux;
	if(n == 0)
		n = 1;
	p = buf;
	for(i = 0; i < n; i++) {
		p = tkskip(p, " \t");
		if(*p == '\0')
			return TkOparg;
		e = tkfracword(t, &p, &d, nil);
		if (e != nil)
			return e;
		*v++ = d;
	}
	return nil;
}

/*
 * N.B. nnfrac only accepts aux==nil (can't deal with several items)
 */
static char*
pnnfrac(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
{
	int oldv;
	char *e;

	oldv = OPTION(place, int, o->offset);

	e = pfrac(t, o, place, str, buf, ebuf);
	if(e == nil && OPTION(place, int, o->offset) < 0) {
		OPTION(place, int, o->offset) = oldv;
		return TkBadvl;
	}
	return e;	

}

typedef struct Tabspec {
	int	dist;
	int	just;
	TkEnv	*env;
} Tabspec;

static char*
ptabs(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
{
	char *e, *p, *eibuf;
	TkOption opd, opj;
	Tabspec tspec;
	TkTtabstop *tabfirst, *tab, *tabprev;
	char *ibuf;

	ibuf = mallocz(Tkmaxitem, 0);
	if(ibuf == nil)
		return TkNomem;
	eibuf = ibuf + Tkmaxitem;
	tspec.env = OPTION(place, TkEnv*, AUXI(o->aux));
	opd.offset = O(Tabspec, dist);
	opd.aux = IAUX(O(Tabspec, env));
	opj.offset = O(Tabspec, dist);
	opj.aux = tktabjust;
	tabprev = nil;
	tabfirst = nil;

	p = tkword(t, *str, buf, ebuf, nil);
	if(*buf == '\0') {
		free(ibuf);
		return TkOparg;
	}
	*str = p;

	p = buf;
	while(*p != '\0') {
		e = pdist(t, &opd, &tspec, &p, ibuf, eibuf);
		if(e != nil) {
			free(ibuf);
			return e;
		}

		e = pstab(t, &opj, &tspec, &p, ibuf, eibuf);
		if(e != nil)
			tspec.just = Tkleft;

		tab = malloc(sizeof(TkTtabstop));
		if(tab == nil) {
			free(ibuf);
			return TkNomem;
		}

		tab->pos = tspec.dist;
		tab->justify = tspec.just;
		tab->next = nil;
		if(tabfirst == nil)
			tabfirst = tab;
		else
			tabprev->next = tab;
		tabprev = tab;
	}
	free(ibuf);

	tab = OPTION(place, TkTtabstop*, o->offset);
	if(tab != nil)
		free(tab);
	OPTION(place, TkTtabstop*, o->offset) = tabfirst;
	return nil;
}

char*
tkxyparse(Tk* tk, char **parg, Point *p)
{
	char *buf;

	buf = mallocz(Tkmaxitem, 0);
	if(buf == nil)
		return TkNomem;

	*parg = tkword(tk->env->top, *parg, buf, buf+Tkmaxitem, nil);
	if(*buf == '\0') {
		free(buf);
		return TkOparg;
	}
	p->x = atoi(buf);

	*parg = tkword(tk->env->top, *parg, buf, buf+Tkmaxitem, nil);
	if(*buf == '\0') {
		free(buf);
		return TkOparg;
	}
	p->y = atoi(buf);

	free(buf);
	return nil;
}

static char*
plist(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
{
	char *w, ***p, *wbuf, *ewbuf, **v, **nv;
	int n, m, i, found;

	*str = tkword(t, *str, buf, ebuf, nil);
	n = strlen(buf) + 1;
	wbuf = mallocz(n, 0);
	if (wbuf == nil)
		return TkNomem;		/* XXX should we free old values too? */
	ewbuf = &wbuf[n];

	p = &OPTION(place, char**, o->offset);
	if (*p != nil){
		for (v = *p; *v; v++)
			free(*v);
		free(*p);
	}
	n = 0;
	m = 4;
	w = buf;
	v = malloc(m * sizeof(char*));
	if (v == nil)
		goto Error;
	for (;;) {
		w = tkword(t, w, wbuf, ewbuf, &found);
		if (!found)
			break;
		if (n == m - 1) {
			m += m/2;
			nv = realloc(v, m * sizeof(char*));
			if (nv == nil)
				goto Error;
			v = nv;
		}
		v[n] = strdup(wbuf);
		if (v[n] == nil)
			goto Error;
		n++;
	}
	v[n++] = nil;
	*p = realloc(v, n * sizeof(char*));
	free(wbuf);
	return nil;
Error:
	free(buf);
	for (i = 0; i < n; i++)
		free(v[i]);
	free(v);
	*p = nil;
	return TkNomem;
}
