xref: /inferno-os/libtk/parse.c (revision c9ccdbd573a9ae70f739fdca280e75da310a3324)
1 #include "lib9.h"
2 #include "kernel.h"
3 #include "draw.h"
4 #include "tk.h"
5 
6 #define	O(t, e)		((long)(&((t*)0)->e))
7 
8 static char* pdist(TkTop*, TkOption*, void*, char**, char*, char*);
9 static char* pstab(TkTop*, TkOption*, void*, char**, char*, char*);
10 static char* ptext(TkTop*, TkOption*, void*, char**, char*, char*);
11 static char* pwinp(TkTop*, TkOption*, void*, char**, char*, char*);
12 static char* pbmap(TkTop*, TkOption*, void*, char**, char*, char*);
13 static char* pbool(TkTop*, TkOption*, void*, char**, char*, char*);
14 static char* pfont(TkTop*, TkOption*, void*, char**, char*, char*);
15 static char* pfrac(TkTop*, TkOption*, void*, char**, char*, char*);
16 static char* pnnfrac(TkTop*, TkOption*, void*, char**, char*, char*);
17 static char* pctag(TkTop*, TkOption*, void*, char**, char*, char*);
18 static char* ptabs(TkTop*, TkOption*, void*, char**, char*, char*);
19 static char* pcolr(TkTop*, TkOption*, void*, char**, char*, char*);
20 static char* pimag(TkTop*, TkOption*, void*, char**, char*, char*);
21 static char* psize(TkTop*, TkOption*, void*, char**, char*, char*);
22 static char* pnndist(TkTop*, TkOption*, void*, char**, char*, char*);
23 static char* pact(TkTop*, TkOption*, void*, char**, char*, char*);
24 static char* pignore(TkTop*, TkOption*, void*, char**, char*, char*);
25 static char* psticky(TkTop*, TkOption*, void*, char**, char*, char*);
26 static char* plist(TkTop*, TkOption*, void*, char**, char*, char*);
27 
28 static char* (*oparse[])(TkTop*, TkOption*, void*, char**, char*, char*) =
29 {
30 	/* OPTdist */	pdist,
31 	/* OPTstab */	pstab,
32 	/* OPTtext */	ptext,
33 	/* OPTwinp */	pwinp,
34 	/* OPTflag */	pstab,
35 	/* OPTbmap */	pbmap,
36 	/* OPTbool */	pbool,
37 	/* OPTfont */	pfont,
38 	/* OPTfrac */	pfrac,
39 	/* OPTnnfrac */	pnnfrac,
40 	/* OPTctag */	pctag,
41 	/* OPTtabs */	ptabs,
42 	/* OPTcolr */	pcolr,
43 	/* OPTimag */	pimag,
44 	/* OPTsize */	psize,
45 	/* OPTnndist */	pnndist,
46 	/* OPTact */	pact,
47 	/* OPTignore */	pignore,
48 	/* OPTsticky */	psticky,
49 	/* OPTlist */ plist,
50 };
51 
52 char*
53 tkskip(char *s, char *bl)
54 {
55 	char *p;
56 
57 	while(*s) {
58 		for(p = bl; *p; p++)
59 			if(*p == *s)
60 				break;
61 		if(*p == '\0')
62 			return s;
63 		s++;
64 	}
65 	return s;
66 }
67 
68 /* XXX - Tad: error propagation? */
69 char*
70 tkword(TkTop *t, char *str, char *buf, char *ebuf, int *gotarg)
71 {
72 	int c, lev, tmp;
73 	char *val, *e, *p, *cmd;
74 	if (gotarg == nil)
75 		gotarg = &tmp;
76 
77 	/*
78 	 * ebuf is one beyond last byte in buf; leave room for nul byte in
79 	 * all cases.
80 	 */
81 	--ebuf;
82 
83 	str = tkskip(str, " \t");
84 	*gotarg = 1;
85 	lev = 1;
86 	switch(*str) {
87 	case '{':
88 		/* XXX - DBK: According to Ousterhout (p.37), while back=
89 		 * slashed braces don't count toward finding the matching
90 		 * closing braces, the backslashes should not be removed.
91 		 * Presumably this also applies to other backslashed
92 		 * characters: the backslash should not be removed.
93 		 */
94 		str++;
95 		while(*str && buf < ebuf) {
96 			c = *str++;
97 			if(c == '\\') {
98 				if(*str == '}' || *str == '{' || *str == '\\')
99 					c = *str++;
100 			} else if(c == '}') {
101 				lev--;
102 				if(lev == 0)
103 					break;
104 			} else if(c == '{')
105 				lev++;
106 			*buf++ = c;
107 		}
108 		break;
109 	case '[':
110 		/* XXX - DBK: According to Ousterhout (p. 33) command
111 		 * substitution may occur anywhere within a word, not
112 		 * only (as here) at the beginning.
113 		 */
114 		cmd = malloc(strlen(str));	/* not strlen+1 because the first character is skipped */
115 		if ( cmd == nil ) {
116 			buf[0] = '\0';	/* DBK - Why not an error message? */
117 			return str;
118 		}
119 		p = cmd;
120 		str++;
121 		while(*str) {
122 			c = *str++;
123 			if(c == '\\') {
124 				if(*str == ']' || *str == '[' || *str == '\\')
125 					c = *str++;
126 			} else if(c == ']') {
127 				lev--;
128 				if(lev == 0)
129 					break;
130 			} else if(c == '[')
131 				lev++;
132 			*p++ = c;
133 		}
134 		*p = '\0';
135 		val = nil;
136 		e = tkexec(t, cmd, &val);
137 		free(cmd);
138 		 /* XXX - Tad: is this appropriate behavior?
139 		  *	      Am I sure that the error doesn't need to be
140 		  *	      propagated back to the caller?
141 		  */
142 		if(e == nil && val != nil) {
143 			strncpy(buf, val, ebuf-buf);
144 			buf = ebuf;
145 			free(val);
146 		}
147 		break;
148 	case '\'':
149 		str++;
150 		while(*str && buf < ebuf)
151 			*buf++ = *str++;
152 		break;
153 	case '\0':
154 		*gotarg = 0;
155 		break;
156 	default:
157 		/* XXX - DBK: See comment above about command substitution.
158 		 * Also, any backslashed character should be replaced by
159 		 * itself (e.g. to put a space, tab, or [ into a word.
160 		 * We assume that the C compiler has already done the
161 		 * standard ANSI C substitutions.  (But should we?)
162 		 */
163 		while(*str && *str != ' ' && *str != '\t' && buf < ebuf)
164 			*buf++ = *str++;
165 	}
166 	*buf = '\0';
167 	return str;
168 }
169 
170 static TkOption*
171 Getopt(TkOption *o, char *buf)
172 {
173 	while(o->o != nil) {
174 		if(strcmp(buf, o->o) == 0)
175 			return o;
176 		o++;
177 	}
178 	return nil;
179 }
180 
181 TkName*
182 tkmkname(char *name)
183 {
184 	TkName *n;
185 
186 	n = malloc(sizeof(struct TkName)+strlen(name));
187 	if(n == nil)
188 		return nil;
189 	strcpy(n->name, name);
190 	n->link = nil;
191 	n->obj = nil;
192 	return n;
193 }
194 
195 char*
196 tkparse(TkTop *t, char *str, TkOptab *ot, TkName **nl)
197 {
198 	int l;
199 	TkOptab *ft;
200 	TkOption *o;
201 	TkName *f, *n;
202 	char *e, *buf, *ebuf;
203 
204 	l = strlen(str);
205 	if (l < Tkmaxitem)
206 		l = Tkmaxitem;
207 	buf = malloc(l + 1);
208 	if(buf == 0)
209 		return TkNomem;
210 	ebuf = buf + l + 1;
211 
212 	e = nil;
213 	while(e == nil) {
214 		str = tkword(t, str, buf, ebuf, nil);
215 		switch(*buf) {
216 		case '\0':
217 			goto done;
218 		case '-':
219 			if (buf[1] != '\0') {
220 				for(ft = ot; ft->ptr; ft++) {
221 					o = Getopt(ft->optab, buf+1);
222 					if(o != nil) {
223 						e = oparse[o->type](t, o, ft->ptr, &str, buf, ebuf);
224 						break;
225 					}
226 				}
227 				if(ft->ptr == nil){
228 					e = TkBadop;
229 					tkerr(t, buf);
230 				}
231 				break;
232 			}
233 			/* fall through if we've got a singleton '-' */
234 		default:
235 			if(nl == nil) {
236 				e = TkBadop;
237 				tkerr(t, buf);
238 				break;
239 			}
240 			n = tkmkname(buf);
241 			if(n == nil) {
242 				e = TkNomem;
243 				break;
244 			}
245 			if(*nl == nil)
246 				*nl = n;
247 			else {
248 				for(f = *nl; f->link; f = f->link)
249 					;
250 				f->link = n;
251 			}
252 		}
253 	}
254 
255 	if(e != nil && nl != nil)
256 		tkfreename(*nl);
257 done:
258 	free(buf);
259 	return e;
260 }
261 
262 char*
263 tkconflist(TkOptab *ot, char **val)
264 {
265 	TkOption *o;
266 	char *f, *e;
267 
268 	f = "-%s";
269 	while(ot->ptr != nil) {
270 		o = ot->optab;
271 		while(o->o != nil) {
272 			e = tkvalue(val, f, o->o);
273 			if(e != nil)
274 				return e;
275 			f = " -%s";
276 			o++;
277 		}
278 		ot++;
279 	}
280 	return nil;
281 }
282 
283 char*
284 tkgencget(TkOptab *ft, char *arg, char **val, TkTop *t)
285 {
286 	Tk *w;
287 	char *c;
288 	Point g;
289 	TkEnv *e;
290 	TkStab *s;
291 	TkOption *o;
292 	int wh, con, i, n, flag, *v;
293 	char *r, *buf, *fmt;
294 
295 	buf = mallocz(Tkmaxitem, 0);
296 	if(buf == nil)
297 		return TkNomem;
298 
299 	tkitem(buf, arg);
300 	r = buf;
301 	if(*r == '-')
302 		r++;
303 	o = nil;
304 	while(ft->ptr) {
305 		o = Getopt(ft->optab, r);
306 		if(o != nil)
307 			break;
308 		ft++;
309 	}
310 	if(o == nil) {
311 		tkerr(t, r);
312 		free(buf);
313 		return TkBadop;
314 	}
315 
316 	switch(o->type) {
317 	default:
318 		tkerr(t, r);
319 		free(buf);
320 		return TkBadop;
321 	case OPTignore:
322 		return nil;
323 	case OPTact:
324 		w = ft->ptr;
325 		g = tkposn(w);
326 		n = g.y;
327 		if(o->aux == 0)
328 			n = g.x;
329 		free(buf);
330 		return tkvalue(val, "%d", n);
331 	case OPTdist:
332 	case OPTnndist:
333 		free(buf);
334 		return tkvalue(val, "%d", OPTION(ft->ptr, int, o->offset));
335 	case OPTsize:
336 		w = ft->ptr;
337 		if(strcmp(r, "width") == 0)
338 			wh = w->req.width;
339 		else
340 			wh = w->req.height;
341 		free(buf);
342 		return tkvalue(val, "%d", wh);
343 	case OPTtext:
344 		c = OPTION(ft->ptr, char*, o->offset);
345 		if(c == nil)
346 			c = "";
347 		free(buf);
348 		return tkvalue(val, "%s", c);
349 	case OPTwinp:
350 		w = OPTION(ft->ptr, Tk*, o->offset);
351 		if(w == nil || w->name == nil)
352 			c = "";
353 		else
354 			c = w->name->name;
355 		free(buf);
356 		return tkvalue(val, "%s", c);
357 	case OPTstab:
358 		s = o->aux;
359 		c = "";
360 		con = OPTION(ft->ptr, int, o->offset);
361 		while(s->val) {
362 			if(con == s->con) {
363 				c = s->val;
364 				break;
365 			}
366 			s++;
367 		}
368 		free(buf);
369 		return tkvalue(val, "%s", c);
370 	case OPTflag:
371 		con = OPTION(ft->ptr, int, o->offset);
372 		flag = 0;
373 		for (s = o->aux; s->val != nil; s++)
374 			flag |= s->con;
375 		c = "";
376 		for (s = o->aux; s->val != nil; s++) {
377 			if ((con & flag) == s->con) {
378 				c = s->val;
379 				break;
380 			}
381 		}
382 		free(buf);
383 		return tkvalue(val, "%s", c);
384 	case OPTfont:
385 		e = OPTION(ft->ptr, TkEnv*, o->offset);
386 		free(buf);
387 		if (e->font != nil)
388 			return tkvalue(val, "%s", e->font->name);
389 		return nil;
390 	case OPTcolr:
391 		e = OPTION(ft->ptr, TkEnv*, o->offset);
392 		i = AUXI(o->aux);
393 		free(buf);
394 		return tkvalue(val, "#%.8lux", e->colors[i]);
395 	case OPTfrac:
396 	case OPTnnfrac:
397 		v = &OPTION(ft->ptr, int, o->offset);
398 		n = (int)o->aux;
399 		if(n == 0)
400 			n = 1;
401 		fmt = "%s";
402 		for(i = 0; i < n; i++) {
403 			tkfprint(buf, *v++);
404 			r = tkvalue(val, fmt, buf);
405 			if(r != nil) {
406 				free(buf);
407 				return r;
408 			}
409 			fmt = " %s";
410 		}
411 		free(buf);
412 		return nil;
413 	case OPTbmap:
414 		return tkvalue(val, "%d", OPTION(ft->ptr, Image*, o->offset) != nil);
415 	case OPTimag:
416 		return tkvalue(val, "%d", OPTION(ft->ptr, TkImg*, o->offset) != nil);
417 	}
418 }
419 
420 static char*
421 pact(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
422 {
423 	USED(buf);
424 	USED(ebuf);
425 	USED(str);
426 	USED(place);
427 	tkerr(t, o->o);
428 	return TkBadop;
429 }
430 
431 static char*
432 pignore(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
433 {
434 	char *p;
435 	USED(t);
436 	USED(o);
437 	USED(place);
438 
439 	p = tkword(t, *str, buf, ebuf, nil);
440 	if(*buf == '\0')
441 		return TkOparg;
442 	*str = p;
443 	return nil;
444 }
445 
446 static char*
447 pdist(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
448 {
449 	int d;
450 	char *e;
451 	TkEnv *env;
452 
453 	USED(buf);
454 	USED(ebuf);
455 
456 	/*
457 	 * this is a bit of a hack, as 0 is a valid option offset,
458 	 * but a nil aux is commonly used when 'w' and 'h' suffixes
459 	 * aren't appropriate.
460 	 * just make sure that no structure placed in TkOptab->ptr
461 	 * with an OPTdist element has a TkEnv as its first member.
462 	 */
463 
464 	if (o->aux == nil)
465 		env = nil;
466 	else
467 		env = OPTION(place, TkEnv*, AUXI(o->aux));
468 	e = tkfracword(t, str, &d, env);
469 	if(e != nil)
470 		return e;
471 	OPTION(place, int, o->offset) = TKF2I(d);
472 	return nil;
473 }
474 
475 static char*
476 pnndist(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
477 {
478 	char* e;
479 	int oldv;
480 
481 	oldv = OPTION(place, int, o->offset);
482 	e = pdist(t, o, place, str, buf, ebuf);
483 	if(e == nil && OPTION(place, int, o->offset) < 0) {
484 		OPTION(place, int, o->offset) = oldv;
485 		return TkBadvl;
486 	}
487 	return e;
488 }
489 
490 static char*
491 psize(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
492 {
493 	Tk *tk;
494 	char *e;
495 	int d, off;
496 
497 	USED(ebuf);
498 	e = tkfracword(t, str, &d, OPTION(place, TkEnv*, AUXI(o->aux)));
499 	if (e != nil)
500 		return e;
501 	if(d < 0)
502 		return TkBadvl;
503 
504 	tk = place;
505 	/*
506 	 * XXX there's no way of resetting Tksetwidth or Tksetheight.
507 	 * could perhaps allow it by setting width/height to {}
508 	 */
509 	if(strcmp(buf+1, "width") == 0) {
510 		tk->flag |= Tksetwidth;
511 		off = O(Tk, req.width);
512 	}
513 	else {
514 		tk->flag |= Tksetheight;
515 		off = O(Tk, req.height);
516 	}
517 	OPTION(place, int, off) = TKF2I(d);
518 	return nil;
519 }
520 
521 static char*
522 pstab(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
523 {
524 	char *p;
525 	int mask;
526 	TkStab *s, *c;
527 
528 	p = tkword(t, *str, buf, ebuf, nil);
529 	if(*buf == '\0')
530 		return TkOparg;
531 
532 	for(s = o->aux; s->val; s++)
533 		if(strcmp(s->val, buf) == 0)
534 			break;
535 	if(s->val == nil)
536 		return TkBadvl;
537 
538 	*str = p;
539 	if(o->type == OPTstab) {
540 		OPTION(place, int, o->offset) = s->con;
541 		return nil;
542 	}
543 
544 	mask = 0;
545 	for(c = o->aux; c->val; c++)
546 		mask |= c->con;
547 
548 	OPTION(place, int, o->offset) &= ~mask;
549 	OPTION(place, int, o->offset) |= s->con;
550 
551 	/*
552 	 * a hack, but otherwise we have to dirty the focus order
553 	 * every time any command is executed on a widget
554 	 */
555 	if (!strcmp(o->o, "takefocus"))
556 		tkdirtyfocusorder(t);
557 	return nil;
558 }
559 
560 enum {
561 	Stickyn = (1<<0),
562 	Stickye = (1<<1),
563 	Stickys = (1<<2),
564 	Stickyw = (1<<3)
565 };
566 
567 static int stickymap[16] =
568 {
569 	0,
570 	Tknorth,
571 	Tkeast,
572 	Tknorth|Tkeast,
573 	Tksouth,
574 	Tkfilly,
575 	Tksouth|Tkeast,
576 	Tkeast|Tkfilly,
577 	Tkwest,
578 	Tknorth|Tkwest,
579 	Tkfillx,
580 	Tknorth|Tkfillx,
581 	Tksouth|Tkwest,
582 	Tkwest|Tkfilly,
583 	Tksouth|Tkfillx,
584 	Tkfillx|Tkfilly,
585 };
586 
587 static char*
588 psticky(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
589 {
590 	char *p, *s;
591 	int flag, sflag;
592 
593 	p = tkword(t, *str, buf, ebuf, nil);
594 	*str = p;
595 
596 	flag = 0;
597 	for (s = buf; *s; s++) {
598 		switch (*s) {
599 		case 'n':
600 			flag |= Stickyn;
601 			break;
602 		case 's':
603 			flag |= Stickys;
604 			break;
605 		case 'e':
606 			flag |= Stickye;
607 			break;
608 		case 'w':
609 			flag |= Stickyw;
610 			break;
611 		case ' ':
612 		case ',':
613 			break;
614 		default:
615 			return TkBadvl;
616 		}
617 	}
618 	sflag =  OPTION(place, int, o->offset) & ~(Tkanchor|Tkfill);
619 	OPTION(place, int, o->offset) = sflag | stickymap[flag];
620 	return nil;
621 }
622 
623 static char*
624 ptext(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
625 {
626 	char **p;
627 
628 	*str = tkword(t, *str, buf, ebuf, nil);
629 
630 	p = &OPTION(place, char*, o->offset);
631 	if(*p != nil)
632 		free(*p);
633 	if(buf[0] == '\0')
634 		*p = nil;
635 	else {
636 		*p = strdup(buf);
637 		if(*p == nil)
638 			return TkNomem;
639 	}
640 	return nil;
641 }
642 
643 static char*
644 pimag(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
645 {
646 	int locked;
647 	Display *d;
648 	TkImg **p, *i;
649 
650 	i = nil;
651 	p = &OPTION(place, TkImg*, o->offset);
652 	*str = tkword(t, *str, buf, ebuf, nil);
653 	if(*buf != '\0') {
654 		i = tkname2img(t, buf);
655 		if(i == nil)
656 			return TkBadvl;
657 		i->ref++;
658 	}
659 
660 	if(*p != nil) {
661 		d = t->display;
662 		locked = lockdisplay(d);
663 		tkimgput(*p);
664 		if(locked)
665 			unlockdisplay(d);
666 	}
667 	*p = i;
668 	return nil;
669 }
670 
671 static char*
672 pbmap(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
673 {
674 	Display *d;
675 	Image *i, **p;
676 	int locked, fd;
677 	char *c;
678 
679 	p = &OPTION(place, Image*, o->offset);
680 
681 	d = t->display;
682 	*str = tkword(t, *str, buf, ebuf, nil);
683 	if(*buf == '\0' || *buf == '-') {
684 		if(*p != nil) {
685 			locked = lockdisplay(d);
686 			freeimage(*p);
687 			if(locked)
688 				unlockdisplay(d);
689 			*p = nil;
690 		}
691 		return nil;
692 	}
693 
694 	if(buf[0] == '@')
695 		i = display_open(d, buf+1);
696  else if(buf[0] == '<') {
697 		buf++;
698 		fd = strtoul(buf, &c, 0);
699 		if(c == buf) {
700 			return TkBadvl;
701 		}
702 		i = readimage(d, fd, 1);
703 	}
704 	else {
705 		char *file;
706 
707 		file = mallocz(Tkmaxitem, 0);
708 		if(file == nil)
709 			return TkNomem;
710 
711 		snprint(file, Tkmaxitem, "/icons/tk/%s", buf);
712 		i = display_open(d, file);
713 		free(file);
714 	}
715 	if(i == nil)
716 		return TkBadbm;
717 
718 	if(*p != nil) {
719 		locked = lockdisplay(d);
720 		freeimage(*p);
721 		if(locked)
722 			unlockdisplay(d);
723 	}
724 	*p = i;
725 	return nil;
726 }
727 
728 static char*
729 pfont(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
730 {
731 	TkEnv *e;
732 	Display *d;
733 	int locked;
734 	Font *font;
735 
736 	*str = tkword(t, *str, buf, ebuf, nil);
737 	if(*buf == '\0')
738 		return TkOparg;
739 
740 	d = t->display;
741 	font = font_open(d, buf);
742 	if(font == nil)
743 		return TkBadft;
744 
745 	e = tkdupenv(&OPTION(place, TkEnv*, o->offset));
746 	if(e == nil) {
747 		freefont(font);		/* XXX lockdisplay around this? */
748 		return TkNomem;
749 	}
750 	if(e->font)
751 		font_close(e->font);
752 	e->font = font;
753 
754 	locked = lockdisplay(d);
755 	e->wzero = stringwidth(font, "0");
756 	if ( e->wzero <= 0 )
757 		e->wzero = e->font->height / 2;
758 	if(locked)
759 		unlockdisplay(d);
760 
761 	return nil;
762 }
763 
764 static int
765 hex(int c)
766 {
767 	if(c >= 'a')
768 		c -= 'a'-'A';
769 	if(c >= 'A')
770 		c = 10 + (c - 'A');
771 	else
772 		c -= '0';
773 	return c;
774 }
775 
776 static ulong
777 changecol(TkEnv *e, int setcol, int col, ulong rgba)
778 {
779 	if (setcol) {
780 		e->set |= (1<<col);
781 	} else {
782 		rgba = 0;
783 		e->set &= ~(1<<col);
784 	}
785 	e->colors[col] = rgba;
786 	return rgba;
787 }
788 
789 char*
790 tkparsecolor(char *buf, ulong *rgba)
791 {
792 	char *p, *q, *e;
793 	int R, G, B, A;
794 	int i, alpha, len, alen;
795 	/*
796 	 * look for alpha modifier in *#AA or *0.5 format
797 	 */
798 	len = strlen(buf);
799 	p = strchr(buf, '*');
800 	if(p != nil) {
801 		alen = len - (p - buf);
802 		if(p[1] == '#') {
803 			if(alen != 4)
804 				return TkBadvl;
805 			alpha = (hex(p[2])<<4) | (hex(p[3]));
806 		} else {
807 			q = p+1;
808 			e = tkfrac(&q, &alpha, nil);
809 			if (e != nil)
810 				return e;
811 			alpha = TKF2I(alpha * 0xff);
812 		}
813 		*p = '\0';
814 		len -= alen;
815 	} else
816 		alpha = 0xff;
817 
818 	if (*buf == '#') {
819 		switch(len) {
820 		case 4:			/* #RGB */
821 			R = hex(buf[1]);
822 			G = hex(buf[2]);
823 			B = hex(buf[3]);
824 			*rgba = (R<<28) | (G<<20) | (B<<12) | 0xff;
825 			break;
826 		case 7:			/* #RRGGBB */
827 			R = (hex(buf[1])<<4)|(hex(buf[2]));
828 			G = (hex(buf[3])<<4)|(hex(buf[4]));
829 			B = (hex(buf[5])<<4)|(hex(buf[6]));
830 			*rgba = (R<<24) | (G<<16) | (B<<8) | 0xff;
831 			break;
832 		case 9:			/* #RRGGBBAA */
833 			R = (hex(buf[1])<<4)|(hex(buf[2]));
834 			G = (hex(buf[3])<<4)|(hex(buf[4]));
835 			B = (hex(buf[5])<<4)|(hex(buf[6]));
836 			A = (hex(buf[7])<<4)|(hex(buf[8]));
837 			*rgba = (R<<24) | (G<<16) | (B<<8) | A;
838 			break;
839 		default:
840 			return TkBadvl;
841 		}
842 	} else {
843 		for(i = 0; tkcolortab[i].val != nil; i++)
844 			if (!strcmp(tkcolortab[i].val, buf))
845 				break;
846 		if (tkcolortab[i].val == nil)
847 			return TkBadvl;
848 		*rgba = tkcolortab[i].con;
849 	}
850 	if (alpha != 0xff) {
851 		tkrgbavals(*rgba, &R, &G, &B, &A);
852 		A = (A * alpha) / 255;
853 		*rgba = tkrgba(R, G, B, A);
854 	}
855 	return nil;
856 }
857 
858 static char*
859 pcolr(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
860 {
861 	TkEnv *env;
862 	char *e;
863 	ulong rgba, dark, light;
864 	int color, setcol;
865 
866 	*str = tkword(t, *str, buf, ebuf, nil);
867 	rgba = 0;
868 	if(*buf == '\0') {
869 		setcol = 0;
870 	} else {
871 		setcol = 1;
872 		e = tkparsecolor(buf, &rgba);
873 		if(e != nil)
874 			return e;
875 	}
876 
877 	env = tkdupenv(&OPTION(place, TkEnv*, o->offset));
878 	if(env == nil)
879 		return TkNomem;
880 
881 	color = AUXI(o->aux);
882 	rgba = changecol(env, setcol, color, rgba);
883 	if(color == TkCbackgnd || color == TkCselectbgnd || color == TkCactivebgnd) {
884 		if (setcol) {
885 			light = tkrgbashade(rgba, TkLightshade);
886 			dark = tkrgbashade(rgba, TkDarkshade);
887 		} else
888 			light = dark = 0;
889 		changecol(env, setcol, color+1, light);
890 		changecol(env, setcol, color+2, dark);
891 	}
892 	return nil;
893 }
894 
895 static char*
896 pbool(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
897 {
898 	USED(buf);
899 	USED(ebuf);
900 	USED(str);
901 	USED(t);
902 	OPTION(place, int, o->offset) = 1;
903 	return nil;
904 }
905 
906 static char*
907 pwinp(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
908 {
909 	Tk *f;
910 	char *p;
911 
912 	p = tkword(t, *str, buf, ebuf, nil);
913 	if(*buf == '\0')
914 		return TkOparg;
915 	*str = p;
916 
917 	f = tklook(t, buf, 0);
918 	if(f == nil){
919 		tkerr(t, buf);
920 		return TkBadwp;
921 	}
922 
923 	OPTION(place, Tk*, o->offset) = f;
924 	return nil;
925 }
926 
927 static char*
928 pctag(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
929 {
930 	char *p;
931 	TkName *n, *l;
932 
933 	*str = tkword(t, *str, buf, ebuf, nil);
934 
935 	l = nil;
936 	p = buf;
937 	while(*p) {
938 		p = tkskip(p, " \t");
939 		buf = p;
940 		while(*p && *p != ' ' && *p != '\t')
941 			p++;
942 		if(*p != '\0')
943 			*p++ = '\0';
944 
945 		if(p == buf || buf[0] >= '0' && buf[0] <= '9') {
946 			tkfreename(l);
947 			return TkBadtg;
948 		}
949 		n = tkmkname(buf);
950 		if(n == nil) {
951 			tkfreename(l);
952 			return TkNomem;
953 		}
954 		n->link = l;
955 		l = n;
956 	}
957 	tkfreename(OPTION(place, TkName*, o->offset));
958 	OPTION(place, TkName*, o->offset) = l;
959 	return nil;
960 }
961 
962 static char*
963 pfrac(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
964 {
965 	char *p, *e;
966 	int i, n, d, *v;
967 
968 	*str = tkword(t, *str, buf, ebuf, nil);
969 
970 	v = &OPTION(place, int, o->offset);
971 	n = (int)o->aux;
972 	if(n == 0)
973 		n = 1;
974 	p = buf;
975 	for(i = 0; i < n; i++) {
976 		p = tkskip(p, " \t");
977 		if(*p == '\0')
978 			return TkOparg;
979 		e = tkfracword(t, &p, &d, nil);
980 		if (e != nil)
981 			return e;
982 		*v++ = d;
983 	}
984 	return nil;
985 }
986 
987 /*
988  * N.B. nnfrac only accepts aux==nil (can't deal with several items)
989  */
990 static char*
991 pnnfrac(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
992 {
993 	int oldv;
994 	char *e;
995 
996 	oldv = OPTION(place, int, o->offset);
997 
998 	e = pfrac(t, o, place, str, buf, ebuf);
999 	if(e == nil && OPTION(place, int, o->offset) < 0) {
1000 		OPTION(place, int, o->offset) = oldv;
1001 		return TkBadvl;
1002 	}
1003 	return e;
1004 
1005 }
1006 
1007 typedef struct Tabspec {
1008 	int	dist;
1009 	int	just;
1010 	TkEnv	*env;
1011 } Tabspec;
1012 
1013 static char*
1014 ptabs(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
1015 {
1016 	char *e, *p, *eibuf;
1017 	TkOption opd, opj;
1018 	Tabspec tspec;
1019 	TkTtabstop *tabfirst, *tab, *tabprev;
1020 	char *ibuf;
1021 
1022 	ibuf = mallocz(Tkmaxitem, 0);
1023 	if(ibuf == nil)
1024 		return TkNomem;
1025 	eibuf = ibuf + Tkmaxitem;
1026 	tspec.env = OPTION(place, TkEnv*, AUXI(o->aux));
1027 	opd.offset = O(Tabspec, dist);
1028 	opd.aux = IAUX(O(Tabspec, env));
1029 	opj.offset = O(Tabspec, dist);
1030 	opj.aux = tktabjust;
1031 	tabprev = nil;
1032 	tabfirst = nil;
1033 
1034 	p = tkword(t, *str, buf, ebuf, nil);
1035 	if(*buf == '\0') {
1036 		free(ibuf);
1037 		return TkOparg;
1038 	}
1039 	*str = p;
1040 
1041 	p = buf;
1042 	while(*p != '\0') {
1043 		e = pdist(t, &opd, &tspec, &p, ibuf, eibuf);
1044 		if(e != nil) {
1045 			free(ibuf);
1046 			return e;
1047 		}
1048 
1049 		e = pstab(t, &opj, &tspec, &p, ibuf, eibuf);
1050 		if(e != nil)
1051 			tspec.just = Tkleft;
1052 
1053 		tab = malloc(sizeof(TkTtabstop));
1054 		if(tab == nil) {
1055 			free(ibuf);
1056 			return TkNomem;
1057 		}
1058 
1059 		tab->pos = tspec.dist;
1060 		tab->justify = tspec.just;
1061 		tab->next = nil;
1062 		if(tabfirst == nil)
1063 			tabfirst = tab;
1064 		else
1065 			tabprev->next = tab;
1066 		tabprev = tab;
1067 	}
1068 	free(ibuf);
1069 
1070 	tab = OPTION(place, TkTtabstop*, o->offset);
1071 	if(tab != nil)
1072 		free(tab);
1073 	OPTION(place, TkTtabstop*, o->offset) = tabfirst;
1074 	return nil;
1075 }
1076 
1077 char*
1078 tkxyparse(Tk* tk, char **parg, Point *p)
1079 {
1080 	char *buf;
1081 
1082 	buf = mallocz(Tkmaxitem, 0);
1083 	if(buf == nil)
1084 		return TkNomem;
1085 
1086 	*parg = tkword(tk->env->top, *parg, buf, buf+Tkmaxitem, nil);
1087 	if(*buf == '\0') {
1088 		free(buf);
1089 		return TkOparg;
1090 	}
1091 	p->x = atoi(buf);
1092 
1093 	*parg = tkword(tk->env->top, *parg, buf, buf+Tkmaxitem, nil);
1094 	if(*buf == '\0') {
1095 		free(buf);
1096 		return TkOparg;
1097 	}
1098 	p->y = atoi(buf);
1099 
1100 	free(buf);
1101 	return nil;
1102 }
1103 
1104 static char*
1105 plist(TkTop *t, TkOption *o, void *place, char **str, char *buf, char *ebuf)
1106 {
1107 	char *w, ***p, *wbuf, *ewbuf, **v, **nv;
1108 	int n, m, i, found;
1109 
1110 	*str = tkword(t, *str, buf, ebuf, nil);
1111 	n = strlen(buf) + 1;
1112 	wbuf = mallocz(n, 0);
1113 	if (wbuf == nil)
1114 		return TkNomem;		/* XXX should we free old values too? */
1115 	ewbuf = &wbuf[n];
1116 
1117 	p = &OPTION(place, char**, o->offset);
1118 	if (*p != nil){
1119 		for (v = *p; *v; v++)
1120 			free(*v);
1121 		free(*p);
1122 	}
1123 	n = 0;
1124 	m = 4;
1125 	w = buf;
1126 	v = malloc(m * sizeof(char*));
1127 	if (v == nil)
1128 		goto Error;
1129 	for (;;) {
1130 		w = tkword(t, w, wbuf, ewbuf, &found);
1131 		if (!found)
1132 			break;
1133 		if (n == m - 1) {
1134 			m += m/2;
1135 			nv = realloc(v, m * sizeof(char*));
1136 			if (nv == nil)
1137 				goto Error;
1138 			v = nv;
1139 		}
1140 		v[n] = strdup(wbuf);
1141 		if (v[n] == nil)
1142 			goto Error;
1143 		n++;
1144 	}
1145 	v[n++] = nil;
1146 	*p = realloc(v, n * sizeof(char*));
1147 	free(wbuf);
1148 	return nil;
1149 Error:
1150 	free(buf);
1151 	for (i = 0; i < n; i++)
1152 		free(v[i]);
1153 	free(v);
1154 	*p = nil;
1155 	return TkNomem;
1156 }
1157