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