xref: /third_party/mksh/tree.c (revision c84f3f3c)
1/*	$OpenBSD: tree.c,v 1.21 2015/09/01 13:12:31 tedu Exp $	*/
2
3/*-
4 * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
5 *		 2011, 2012, 2013, 2015, 2016, 2017
6 *	mirabilos <m@mirbsd.org>
7 *
8 * Provided that these terms and disclaimer and all copyright notices
9 * are retained or reproduced in an accompanying document, permission
10 * is granted to deal in this work without restriction, including un-
11 * limited rights to use, publicly perform, distribute, sell, modify,
12 * merge, give away, or sublicence.
13 *
14 * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
15 * the utmost extent permitted by applicable law, neither express nor
16 * implied; without malicious intent or gross negligence. In no event
17 * may a licensor, author or contributor be held liable for indirect,
18 * direct, other damage, loss, or other issues arising in any way out
19 * of dealing in the work, even if advised of the possibility of such
20 * damage or existence of a defect, except proven that it results out
21 * of said person's immediate fault when using the work as intended.
22 */
23
24#include "sh.h"
25
26__RCSID("$MirOS: src/bin/mksh/tree.c,v 1.100 2020/10/31 04:28:54 tg Exp $");
27
28#define INDENT	8
29
30static void ptree(struct op *, int, struct shf *);
31static void pioact(struct shf *, struct ioword *);
32static const char *wdvarput(struct shf *, const char *, int, int);
33static void vfptreef(struct shf *, int, const char *, va_list);
34static struct ioword **iocopy(struct ioword **, Area *);
35static void iofree(struct ioword **, Area *);
36
37/* "foo& ; bar" and "foo |& ; bar" are invalid */
38static bool prevent_semicolon;
39
40/* here document diversion */
41static unsigned short ptree_nest;
42static bool ptree_hashere;
43static struct shf ptree_heredoc;
44#define ptree_outhere(shf) do {					\
45	if (ptree_hashere) {					\
46		shf_puts(shf_sclose(&ptree_heredoc), (shf));	\
47		shf_putc('\n', (shf));				\
48		ptree_hashere = false;				\
49		/*prevent_semicolon = true;*/			\
50	}							\
51} while (/* CONSTCOND */ 0)
52
53static const char Telif_pT[] = "elif %T";
54
55/*
56 * print a command tree
57 */
58static void
59ptree(struct op *t, int indent, struct shf *shf)
60{
61	const char **w;
62	struct ioword **ioact;
63	struct op *t1;
64	int i;
65	const char *ccp;
66
67 Chain:
68	if (t == NULL)
69		return;
70	switch (t->type) {
71	case TCOM:
72		prevent_semicolon = false;
73		/* special-case 'var=<<EOF' (cf. exec.c:execute) */
74		if (t->args &&
75		    /* we have zero arguments, i.e. no program to run */
76		    t->args[0] == NULL &&
77		    /* we have exactly one variable assignment */
78		    t->vars[0] != NULL && t->vars[1] == NULL &&
79		    /* we have exactly one I/O redirection */
80		    t->ioact != NULL && t->ioact[0] != NULL &&
81		    t->ioact[1] == NULL &&
82		    /* of type "here document" (or "here string") */
83		    (t->ioact[0]->ioflag & IOTYPE) == IOHERE &&
84		    /* the variable assignment begins with a valid varname */
85		    (ccp = skip_wdvarname(t->vars[0], true)) != t->vars[0] &&
86		    /* and has no right-hand side (i.e. "varname=") */
87		    ccp[0] == CHAR && ((ccp[1] == '=' && ccp[2] == EOS) ||
88		    /* or "varname+=" */ (ccp[1] == '+' && ccp[2] == CHAR &&
89		    ccp[3] == '=' && ccp[4] == EOS))) {
90			fptreef(shf, indent, Tf_S, t->vars[0]);
91			break;
92		}
93
94		if (t->vars) {
95			w = (const char **)t->vars;
96			while (*w)
97				fptreef(shf, indent, Tf_S_, *w++);
98		}
99#ifndef MKSH_SMALL
100		  else
101			shf_puts("#no-vars# ", shf);
102#endif
103		if (t->args) {
104			w = t->args;
105			if (*w && **w == CHAR) {
106				char *cp = wdstrip(*w++, WDS_TPUTS);
107
108				if (valid_alias_name(cp))
109					shf_putc('\\', shf);
110				shf_puts(cp, shf);
111				shf_putc(' ', shf);
112				afree(cp, ATEMP);
113			}
114			while (*w)
115				fptreef(shf, indent, Tf_S_, *w++);
116		}
117#ifndef MKSH_SMALL
118		  else
119			shf_puts("#no-args# ", shf);
120#endif
121		break;
122	case TEXEC:
123		t = t->left;
124		goto Chain;
125	case TPAREN:
126		fptreef(shf, indent + 2, "( %T) ", t->left);
127		break;
128	case TPIPE:
129		fptreef(shf, indent, "%T| ", t->left);
130		t = t->right;
131		goto Chain;
132	case TLIST:
133		fptreef(shf, indent, "%T%;", t->left);
134		t = t->right;
135		goto Chain;
136	case TOR:
137	case TAND:
138		fptreef(shf, indent, "%T%s %T",
139		    t->left, (t->type == TOR) ? "||" : "&&", t->right);
140		break;
141	case TBANG:
142		shf_puts("! ", shf);
143		prevent_semicolon = false;
144		t = t->right;
145		goto Chain;
146	case TDBRACKET:
147		w = t->args;
148		shf_puts("[[", shf);
149		while (*w)
150			fptreef(shf, indent, Tf__S, *w++);
151		shf_puts(" ]] ", shf);
152		break;
153	case TSELECT:
154	case TFOR:
155		fptreef(shf, indent, "%s %s ",
156		    (t->type == TFOR) ? "for" : Tselect, t->str);
157		if (t->vars != NULL) {
158			shf_puts("in ", shf);
159			w = (const char **)t->vars;
160			while (*w)
161				fptreef(shf, indent, Tf_S_, *w++);
162			fptreef(shf, indent, Tft_end);
163		}
164		fptreef(shf, indent + INDENT, "do%N%T", t->left);
165		fptreef(shf, indent, "%;done ");
166		break;
167	case TCASE:
168		fptreef(shf, indent, "case %S in", t->str);
169		for (t1 = t->left; t1 != NULL; t1 = t1->right) {
170			fptreef(shf, indent, "%N(");
171			w = (const char **)t1->vars;
172			while (*w) {
173				fptreef(shf, indent, "%S%c", *w,
174				    (w[1] != NULL) ? '|' : ')');
175				++w;
176			}
177			fptreef(shf, indent + INDENT, "%N%T%N;%c", t1->left,
178			    t1->u.charflag);
179		}
180		fptreef(shf, indent, "%Nesac ");
181		break;
182	case TELIF:
183		internal_errorf(TELIF_unexpected);
184		/* FALLTHROUGH */
185	case TIF:
186		i = 2;
187		t1 = t;
188		goto process_TIF;
189		do {
190			t1 = t1->right;
191			i = 0;
192			fptreef(shf, indent, Tft_end);
193 process_TIF:
194			/* 5 == strlen("elif ") */
195			fptreef(shf, indent + 5 - i, Telif_pT + i, t1->left);
196			t1 = t1->right;
197			if (t1->left != NULL) {
198				fptreef(shf, indent, Tft_end);
199				fptreef(shf, indent + INDENT, "%s%N%T",
200				    "then", t1->left);
201			}
202		} while (t1->right && t1->right->type == TELIF);
203		if (t1->right != NULL) {
204			fptreef(shf, indent, Tft_end);
205			fptreef(shf, indent + INDENT, "%s%N%T",
206			    "else", t1->right);
207		}
208		fptreef(shf, indent, "%;fi ");
209		break;
210	case TWHILE:
211	case TUNTIL:
212		/* 6 == strlen("while "/"until ") */
213		fptreef(shf, indent + 6, Tf_s_T,
214		    (t->type == TWHILE) ? "while" : "until",
215		    t->left);
216		fptreef(shf, indent, Tft_end);
217		fptreef(shf, indent + INDENT, "do%N%T", t->right);
218		fptreef(shf, indent, "%;done ");
219		break;
220	case TBRACE:
221		fptreef(shf, indent + INDENT, "{%N%T", t->left);
222		fptreef(shf, indent, "%;} ");
223		break;
224	case TCOPROC:
225		fptreef(shf, indent, "%T|& ", t->left);
226		prevent_semicolon = true;
227		break;
228	case TASYNC:
229		fptreef(shf, indent, "%T& ", t->left);
230		prevent_semicolon = true;
231		break;
232	case TFUNCT:
233		fpFUNCTf(shf, indent, tobool(t->u.ksh_func), t->str, t->left);
234		break;
235	case TTIME:
236		fptreef(shf, indent, Tf_s_T, Ttime, t->left);
237		break;
238	default:
239		shf_puts("<botch>", shf);
240		prevent_semicolon = false;
241		break;
242	}
243	if ((ioact = t->ioact) != NULL)
244		while (*ioact != NULL)
245			pioact(shf, *ioact++);
246}
247
248static void
249pioact(struct shf *shf, struct ioword *iop)
250{
251	unsigned short flag = iop->ioflag;
252	unsigned short type = flag & IOTYPE;
253	short expected;
254
255	expected = (type == IOREAD || type == IORDWR || type == IOHERE) ? 0 :
256	    (type == IOCAT || type == IOWRITE) ? 1 :
257	    (type == IODUP && (iop->unit == !(flag & IORDUP))) ? iop->unit :
258	    iop->unit + 1;
259	if (iop->unit != expected)
260		shf_fprintf(shf, Tf_d, (int)iop->unit);
261
262	switch (type) {
263	case IOREAD:
264		shf_putc('<', shf);
265		break;
266	case IOHERE:
267		if (flag & IOHERESTR) {
268			shf_puts("<<<", shf);
269			goto ioheredelim;
270		}
271		shf_puts("<<", shf);
272		if (flag & IOSKIP)
273			shf_putc('-', shf);
274		if (iop->heredoc /* nil when tracing */) {
275			/* here document diversion */
276			if (!ptree_hashere) {
277				shf_sopen(NULL, 0, SHF_WR | SHF_DYNAMIC,
278				    &ptree_heredoc);
279				ptree_hashere = true;
280			}
281			shf_putc('\n', &ptree_heredoc);
282			shf_puts(iop->heredoc, &ptree_heredoc);
283			/* iop->delim is set before iop->heredoc */
284			shf_puts(evalstr(iop->delim, 0), &ptree_heredoc);
285		}
286 ioheredelim:
287		/* delim is NULL during syntax error printing */
288		if (iop->delim && !(iop->ioflag & IONDELIM))
289			wdvarput(shf, iop->delim, 0, WDS_TPUTS);
290		break;
291	case IOCAT:
292		shf_puts(">>", shf);
293		break;
294	case IOWRITE:
295		shf_putc('>', shf);
296		if (flag & IOCLOB)
297			shf_putc('|', shf);
298		break;
299	case IORDWR:
300		shf_puts("<>", shf);
301		break;
302	case IODUP:
303		shf_puts(flag & IORDUP ? "<&" : ">&", shf);
304		break;
305	}
306	/* name is NULL for IOHERE or when printing syntax errors */
307	if (iop->ioname) {
308		if (flag & IONAMEXP)
309			print_value_quoted(shf, iop->ioname);
310		else
311			wdvarput(shf, iop->ioname, 0, WDS_TPUTS);
312	}
313	shf_putc(' ', shf);
314	prevent_semicolon = false;
315}
316
317/* variant of fputs for ptreef and wdstrip */
318static const char *
319wdvarput(struct shf *shf, const char *wp, int quotelevel, int opmode)
320{
321	int c;
322	const char *cs;
323
324	/*-
325	 * problems:
326	 *	`...` -> $(...)
327	 *	'foo' -> "foo"
328	 *	x${foo:-"hi"} -> x${foo:-hi} unless WDS_TPUTS
329	 *	x${foo:-'hi'} -> x${foo:-hi}
330	 * could change encoding to:
331	 *	OQUOTE ["'] ... CQUOTE ["']
332	 *	COMSUB [(`] ...\0	(handle $ ` \ and maybe " in `...` case)
333	 */
334	while (/* CONSTCOND */ 1)
335		switch (*wp++) {
336		case EOS:
337			return (--wp);
338		case ADELIM:
339			if (ord(*wp) == ORD(/*{*/ '}')) {
340				++wp;
341				goto wdvarput_csubst;
342			}
343			/* FALLTHROUGH */
344		case CHAR:
345			c = ord(*wp++);
346			shf_putc(c, shf);
347			break;
348		case QCHAR:
349			c = ord(*wp++);
350			if (opmode & WDS_TPUTS)
351				switch (c) {
352				default:
353					if (quotelevel == 0)
354						/* FALLTHROUGH */
355				case ORD('"'):
356				case ORD('`'):
357				case ORD('$'):
358				case ORD('\\'):
359					  shf_putc(ORD('\\'), shf);
360					break;
361				}
362			shf_putc(c, shf);
363			break;
364		case COMASUB:
365		case COMSUB:
366			shf_puts("$(", shf);
367			cs = ")";
368			if (ord(*wp) == ORD('(' /*)*/))
369				shf_putc(' ', shf);
370 pSUB:
371			while ((c = *wp++) != 0)
372				shf_putc(c, shf);
373			shf_puts(cs, shf);
374			break;
375		case FUNASUB:
376		case FUNSUB:
377			c = ORD(' ');
378			if (0)
379				/* FALLTHROUGH */
380		case VALSUB:
381			  c = ORD('|');
382			shf_putc('$', shf);
383			shf_putc('{', shf);
384			shf_putc(c, shf);
385			cs = ";}";
386			goto pSUB;
387		case EXPRSUB:
388			shf_puts("$((", shf);
389			cs = "))";
390			goto pSUB;
391		case OQUOTE:
392			if (opmode & WDS_TPUTS) {
393				quotelevel++;
394				shf_putc('"', shf);
395			}
396			break;
397		case CQUOTE:
398			if (opmode & WDS_TPUTS) {
399				if (quotelevel)
400					quotelevel--;
401				shf_putc('"', shf);
402			}
403			break;
404		case OSUBST:
405			shf_putc('$', shf);
406			if (ord(*wp++) == ORD('{'))
407				shf_putc('{', shf);
408			while ((c = *wp++) != 0)
409				shf_putc(c, shf);
410			wp = wdvarput(shf, wp, 0, opmode);
411			break;
412		case CSUBST:
413			if (ord(*wp++) == ORD('}')) {
414 wdvarput_csubst:
415				shf_putc('}', shf);
416			}
417			return (wp);
418		case OPAT:
419			shf_putchar(*wp++, shf);
420			shf_putc('(', shf);
421			break;
422		case SPAT:
423			c = ORD('|');
424			if (0)
425				/* FALLTHROUGH */
426		case CPAT:
427			  c = ORD(/*(*/ ')');
428			shf_putc(c, shf);
429			break;
430		}
431}
432
433/*
434 * this is the _only_ way to reliably handle
435 * variable args with an ANSI compiler
436 */
437/* VARARGS */
438void
439fptreef(struct shf *shf, int indent, const char *fmt, ...)
440{
441	va_list va;
442
443	va_start(va, fmt);
444	vfptreef(shf, indent, fmt, va);
445	va_end(va);
446}
447
448/* VARARGS */
449char *
450snptreef(char *s, ssize_t n, const char *fmt, ...)
451{
452	va_list va;
453	struct shf shf;
454
455	shf_sopen(s, n, SHF_WR | (s ? 0 : SHF_DYNAMIC), &shf);
456
457	va_start(va, fmt);
458	vfptreef(&shf, 0, fmt, va);
459	va_end(va);
460
461	/* shf_sclose NUL terminates */
462	return (shf_sclose(&shf));
463}
464
465static void
466vfptreef(struct shf *shf, int indent, const char *fmt, va_list va)
467{
468	int c;
469
470	if (!ptree_nest++)
471		ptree_hashere = false;
472
473	while ((c = ord(*fmt++))) {
474		if (c == '%') {
475			switch ((c = ord(*fmt++))) {
476			case ORD('c'):
477				/* character (octet, probably) */
478				shf_putchar(va_arg(va, int), shf);
479				break;
480			case ORD('s'):
481				/* string */
482				shf_puts(va_arg(va, char *), shf);
483				break;
484			case ORD('S'):
485				/* word */
486				wdvarput(shf, va_arg(va, char *), 0, WDS_TPUTS);
487				break;
488			case ORD('d'):
489				/* signed decimal */
490				shf_fprintf(shf, Tf_d, va_arg(va, int));
491				break;
492			case ORD('u'):
493				/* unsigned decimal */
494				shf_fprintf(shf, "%u", va_arg(va, unsigned int));
495				break;
496			case ORD('T'):
497				/* format tree */
498				ptree(va_arg(va, struct op *), indent, shf);
499				goto dont_trash_prevent_semicolon;
500			case ORD(';'):
501				/* newline or ; */
502			case ORD('N'):
503				/* newline or space */
504				if (shf->flags & SHF_STRING) {
505					if ((unsigned int)c == ORD(';') &&
506					    !prevent_semicolon)
507						shf_putc(';', shf);
508					shf_putc(' ', shf);
509				} else {
510					int i = indent;
511
512					ptree_outhere(shf);
513					shf_putc('\n', shf);
514					while (i >= 8) {
515						shf_putc('\t', shf);
516						i -= 8;
517					}
518					while (i--)
519						shf_putc(' ', shf);
520				}
521				break;
522			case ORD('R'):
523				/* I/O redirection */
524				pioact(shf, va_arg(va, struct ioword *));
525				break;
526			default:
527				shf_putc(c, shf);
528				break;
529			}
530		} else
531			shf_putc(c, shf);
532		prevent_semicolon = false;
533 dont_trash_prevent_semicolon:
534		;
535	}
536
537	if (!--ptree_nest)
538		ptree_outhere(shf);
539}
540
541/*
542 * copy tree (for function definition)
543 */
544struct op *
545tcopy(struct op *t, Area *ap)
546{
547	struct op *r;
548	const char **tw;
549	char **rw;
550
551	if (t == NULL)
552		return (NULL);
553
554	r = alloc(sizeof(struct op), ap);
555
556	r->type = t->type;
557	r->u.evalflags = t->u.evalflags;
558
559	if (t->type == TCASE)
560		r->str = wdcopy(t->str, ap);
561	else
562		strdupx(r->str, t->str, ap);
563
564	if (t->vars == NULL)
565		r->vars = NULL;
566	else {
567		tw = (const char **)t->vars;
568		while (*tw)
569			++tw;
570		rw = r->vars = alloc2(tw - (const char **)t->vars + 1,
571		    sizeof(*tw), ap);
572		tw = (const char **)t->vars;
573		while (*tw)
574			*rw++ = wdcopy(*tw++, ap);
575		*rw = NULL;
576	}
577
578	if (t->args == NULL)
579		r->args = NULL;
580	else {
581		tw = t->args;
582		while (*tw)
583			++tw;
584		r->args = (const char **)(rw = alloc2(tw - t->args + 1,
585		    sizeof(*tw), ap));
586		tw = t->args;
587		while (*tw)
588			*rw++ = wdcopy(*tw++, ap);
589		*rw = NULL;
590	}
591
592	r->ioact = (t->ioact == NULL) ? NULL : iocopy(t->ioact, ap);
593
594	r->left = tcopy(t->left, ap);
595	r->right = tcopy(t->right, ap);
596	r->lineno = t->lineno;
597
598	return (r);
599}
600
601char *
602wdcopy(const char *wp, Area *ap)
603{
604	size_t len;
605
606	len = wdscan(wp, EOS) - wp;
607	return (memcpy(alloc(len, ap), wp, len));
608}
609
610/* return the position of prefix c in wp plus 1 */
611const char *
612wdscan(const char *wp, int c)
613{
614	int nest = 0;
615
616	while (/* CONSTCOND */ 1)
617		switch (*wp++) {
618		case EOS:
619			return (wp);
620		case ADELIM:
621			if (c == ADELIM && nest == 0)
622				return (wp + 1);
623			if (ord(*wp) == ORD(/*{*/ '}'))
624				goto wdscan_csubst;
625			/* FALLTHROUGH */
626		case CHAR:
627		case QCHAR:
628			wp++;
629			break;
630		case COMASUB:
631		case COMSUB:
632		case FUNASUB:
633		case FUNSUB:
634		case VALSUB:
635		case EXPRSUB:
636			while (*wp++ != 0)
637				;
638			break;
639		case OQUOTE:
640		case CQUOTE:
641			break;
642		case OSUBST:
643			nest++;
644			while (*wp++ != '\0')
645				;
646			break;
647		case CSUBST:
648 wdscan_csubst:
649			wp++;
650			if (c == CSUBST && nest == 0)
651				return (wp);
652			nest--;
653			break;
654		case OPAT:
655			nest++;
656			wp++;
657			break;
658		case SPAT:
659		case CPAT:
660			if (c == wp[-1] && nest == 0)
661				return (wp);
662			if (wp[-1] == CPAT)
663				nest--;
664			break;
665		default:
666			internal_warningf(
667			    "wdscan: unknown char 0x%X (carrying on)",
668			    (unsigned char)wp[-1]);
669		}
670}
671
672/*
673 * return a copy of wp without any of the mark up characters and with
674 * quote characters (" ' \) stripped. (string is allocated from ATEMP)
675 */
676char *
677wdstrip(const char *wp, int opmode)
678{
679	struct shf shf;
680
681	shf_sopen(NULL, 32, SHF_WR | SHF_DYNAMIC, &shf);
682	wdvarput(&shf, wp, 0, opmode);
683	/* shf_sclose NUL terminates */
684	return (shf_sclose(&shf));
685}
686
687static struct ioword **
688iocopy(struct ioword **iow, Area *ap)
689{
690	struct ioword **ior;
691	int i;
692
693	ior = iow;
694	while (*ior)
695		++ior;
696	ior = alloc2(ior - iow + 1, sizeof(struct ioword *), ap);
697
698	for (i = 0; iow[i] != NULL; i++) {
699		struct ioword *p, *q;
700
701		p = iow[i];
702		q = alloc(sizeof(struct ioword), ap);
703		ior[i] = q;
704		*q = *p;
705		if (p->ioname != NULL)
706			q->ioname = wdcopy(p->ioname, ap);
707		if (p->delim != NULL)
708			q->delim = wdcopy(p->delim, ap);
709		if (p->heredoc != NULL)
710			strdupx(q->heredoc, p->heredoc, ap);
711	}
712	ior[i] = NULL;
713
714	return (ior);
715}
716
717/*
718 * free tree (for function definition)
719 */
720void
721tfree(struct op *t, Area *ap)
722{
723	char **w;
724
725	if (t == NULL)
726		return;
727
728	afree(t->str, ap);
729
730	if (t->vars != NULL) {
731		for (w = t->vars; *w != NULL; w++)
732			afree(*w, ap);
733		afree(t->vars, ap);
734	}
735
736	if (t->args != NULL) {
737		/*XXX we assume the caller is right */
738		union mksh_ccphack cw;
739
740		cw.ro = t->args;
741		for (w = cw.rw; *w != NULL; w++)
742			afree(*w, ap);
743		afree(t->args, ap);
744	}
745
746	if (t->ioact != NULL)
747		iofree(t->ioact, ap);
748
749	tfree(t->left, ap);
750	tfree(t->right, ap);
751
752	afree(t, ap);
753}
754
755static void
756iofree(struct ioword **iow, Area *ap)
757{
758	struct ioword **iop;
759	struct ioword *p;
760
761	iop = iow;
762	while ((p = *iop++) != NULL) {
763		afree(p->ioname, ap);
764		afree(p->delim, ap);
765		afree(p->heredoc, ap);
766		afree(p, ap);
767	}
768	afree(iow, ap);
769}
770
771void
772fpFUNCTf(struct shf *shf, int i, bool isksh, const char *k, struct op *v)
773{
774	if (isksh)
775		fptreef(shf, i, "%s %s %T", Tfunction, k, v);
776	else if (ktsearch(&keywords, k, hash(k)))
777		fptreef(shf, i, "%s %s() %T", Tfunction, k, v);
778	else
779		fptreef(shf, i, "%s() %T", k, v);
780}
781
782
783/* for jobs.c */
784void
785vistree(char *dst, size_t sz, struct op *t)
786{
787	unsigned int c;
788	char *cp, *buf;
789	size_t n;
790
791	buf = alloc(sz + 16, ATEMP);
792	snptreef(buf, sz + 16, Tf_T, t);
793	cp = buf;
794 vist_loop:
795	if (UTFMODE && (n = utf_mbtowc(&c, cp)) != (size_t)-1) {
796		if (c == 0 || n >= sz)
797			/* NUL or not enough free space */
798			goto vist_out;
799		/* copy multibyte char */
800		sz -= n;
801		while (n--)
802			*dst++ = *cp++;
803		goto vist_loop;
804	}
805	if (--sz == 0 || (c = ord(*cp++)) == 0)
806		/* NUL or not enough free space */
807		goto vist_out;
808	if (ksh_isctrl(c)) {
809		/* C0 or C1 control character or DEL */
810		if (--sz == 0)
811			/* not enough free space for two chars */
812			goto vist_out;
813		*dst++ = '^';
814		c = ksh_unctrl(c);
815	} else if (UTFMODE && rtt2asc(c) > 0x7F) {
816		/* better not try to display broken multibyte chars */
817		/* also go easy on the UCS: no U+FFFD here */
818		c = ORD('?');
819	}
820	*dst++ = c;
821	goto vist_loop;
822
823 vist_out:
824	*dst = '\0';
825	afree(buf, ATEMP);
826}
827
828#ifdef DEBUG
829void
830dumpchar(struct shf *shf, unsigned char c)
831{
832	if (ksh_isctrl(c)) {
833		/* C0 or C1 control character or DEL */
834		shf_putc('^', shf);
835		c = ksh_unctrl(c);
836	}
837	shf_putc(c, shf);
838}
839
840/* see: wdvarput */
841static const char *
842dumpwdvar_i(struct shf *shf, const char *wp, int quotelevel)
843{
844	int c;
845
846	while (/* CONSTCOND */ 1) {
847		switch(*wp++) {
848		case EOS:
849			shf_puts("EOS", shf);
850			return (--wp);
851		case ADELIM:
852			if (ord(*wp) == ORD(/*{*/ '}')) {
853				shf_puts(/*{*/ "]ADELIM(})", shf);
854				return (wp + 1);
855			}
856			shf_puts("ADELIM=", shf);
857			if (0)
858				/* FALLTHROUGH */
859		case CHAR:
860			  shf_puts("CHAR=", shf);
861			dumpchar(shf, *wp++);
862			break;
863		case QCHAR:
864			shf_puts("QCHAR<", shf);
865			c = ord(*wp++);
866			if (quotelevel == 0 || c == ORD('"') ||
867			    c == ORD('\\') || ctype(c, C_DOLAR | C_GRAVE))
868				shf_putc('\\', shf);
869			dumpchar(shf, c);
870			goto closeandout;
871		case COMASUB:
872			shf_puts("COMASUB<", shf);
873			goto dumpsub;
874		case COMSUB:
875			shf_puts("COMSUB<", shf);
876 dumpsub:
877			while ((c = *wp++) != 0)
878				dumpchar(shf, c);
879 closeandout:
880			shf_putc('>', shf);
881			break;
882		case FUNASUB:
883			shf_puts("FUNASUB<", shf);
884			goto dumpsub;
885		case FUNSUB:
886			shf_puts("FUNSUB<", shf);
887			goto dumpsub;
888		case VALSUB:
889			shf_puts("VALSUB<", shf);
890			goto dumpsub;
891		case EXPRSUB:
892			shf_puts("EXPRSUB<", shf);
893			goto dumpsub;
894		case OQUOTE:
895			shf_fprintf(shf, "OQUOTE{%d" /*}*/, ++quotelevel);
896			break;
897		case CQUOTE:
898			shf_fprintf(shf, /*{*/ "%d}CQUOTE", quotelevel);
899			if (quotelevel)
900				quotelevel--;
901			else
902				shf_puts("(err)", shf);
903			break;
904		case OSUBST:
905			shf_puts("OSUBST(", shf);
906			dumpchar(shf, *wp++);
907			shf_puts(")[", shf);
908			while ((c = *wp++) != 0)
909				dumpchar(shf, c);
910			shf_putc('|', shf);
911			wp = dumpwdvar_i(shf, wp, 0);
912			break;
913		case CSUBST:
914			shf_puts("]CSUBST(", shf);
915			dumpchar(shf, *wp++);
916			shf_putc(')', shf);
917			return (wp);
918		case OPAT:
919			shf_puts("OPAT=", shf);
920			dumpchar(shf, *wp++);
921			break;
922		case SPAT:
923			shf_puts("SPAT", shf);
924			break;
925		case CPAT:
926			shf_puts("CPAT", shf);
927			break;
928		default:
929			shf_fprintf(shf, "INVAL<%u>", (uint8_t)wp[-1]);
930			break;
931		}
932		shf_putc(' ', shf);
933	}
934}
935void
936dumpwdvar(struct shf *shf, const char *wp)
937{
938	dumpwdvar_i(shf, wp, 0);
939}
940
941void
942dumpioact(struct shf *shf, struct op *t)
943{
944	struct ioword **ioact, *iop;
945
946	if ((ioact = t->ioact) == NULL)
947		return;
948
949	shf_puts("{IOACT", shf);
950	while ((iop = *ioact++) != NULL) {
951		unsigned short type = iop->ioflag & IOTYPE;
952#define DT(x) case x: shf_puts(#x, shf); break;
953#define DB(x) if (iop->ioflag & x) shf_puts("|" #x, shf);
954
955		shf_putc(';', shf);
956		switch (type) {
957		DT(IOREAD)
958		DT(IOWRITE)
959		DT(IORDWR)
960		DT(IOHERE)
961		DT(IOCAT)
962		DT(IODUP)
963		default:
964			shf_fprintf(shf, "unk%d", type);
965		}
966		DB(IOEVAL)
967		DB(IOSKIP)
968		DB(IOCLOB)
969		DB(IORDUP)
970		DB(IONAMEXP)
971		DB(IOBASH)
972		DB(IOHERESTR)
973		DB(IONDELIM)
974		shf_fprintf(shf, ",unit=%d", (int)iop->unit);
975		if (iop->delim && !(iop->ioflag & IONDELIM)) {
976			shf_puts(",delim<", shf);
977			dumpwdvar(shf, iop->delim);
978			shf_putc('>', shf);
979		}
980		if (iop->ioname) {
981			if (iop->ioflag & IONAMEXP) {
982				shf_puts(",name=", shf);
983				print_value_quoted(shf, iop->ioname);
984			} else {
985				shf_puts(",name<", shf);
986				dumpwdvar(shf, iop->ioname);
987				shf_putc('>', shf);
988			}
989		}
990		if (iop->heredoc) {
991			shf_puts(",heredoc=", shf);
992			print_value_quoted(shf, iop->heredoc);
993		}
994#undef DT
995#undef DB
996	}
997	shf_putc('}', shf);
998}
999
1000void
1001dumptree(struct shf *shf, struct op *t)
1002{
1003	int i, j;
1004	const char **w, *name;
1005	struct op *t1;
1006	static int nesting;
1007
1008	for (i = 0; i < nesting; ++i)
1009		shf_putc('\t', shf);
1010	++nesting;
1011	shf_puts("{tree:" /*}*/, shf);
1012	if (t == NULL) {
1013		name = "(null)";
1014		goto out;
1015	}
1016	dumpioact(shf, t);
1017	switch (t->type) {
1018#define OPEN(x) case x: name = #x; shf_puts(" {" #x ":", shf); /*}*/
1019
1020	OPEN(TCOM)
1021		if (t->vars) {
1022			i = 0;
1023			w = (const char **)t->vars;
1024			while (*w) {
1025				shf_putc('\n', shf);
1026				for (j = 0; j < nesting; ++j)
1027					shf_putc('\t', shf);
1028				shf_fprintf(shf, " var%d<", i++);
1029				dumpwdvar(shf, *w++);
1030				shf_putc('>', shf);
1031			}
1032		} else
1033			shf_puts(" #no-vars#", shf);
1034		if (t->args) {
1035			i = 0;
1036			w = t->args;
1037			while (*w) {
1038				shf_putc('\n', shf);
1039				for (j = 0; j < nesting; ++j)
1040					shf_putc('\t', shf);
1041				shf_fprintf(shf, " arg%d<", i++);
1042				dumpwdvar(shf, *w++);
1043				shf_putc('>', shf);
1044			}
1045		} else
1046			shf_puts(" #no-args#", shf);
1047		break;
1048	OPEN(TEXEC)
1049 dumpleftandout:
1050		t = t->left;
1051 dumpandout:
1052		shf_putc('\n', shf);
1053		dumptree(shf, t);
1054		break;
1055	OPEN(TPAREN)
1056		goto dumpleftandout;
1057	OPEN(TPIPE)
1058 dumpleftmidrightandout:
1059		shf_putc('\n', shf);
1060		dumptree(shf, t->left);
1061/* middumprightandout: (unused) */
1062		shf_fprintf(shf, "/%s:", name);
1063 dumprightandout:
1064		t = t->right;
1065		goto dumpandout;
1066	OPEN(TLIST)
1067		goto dumpleftmidrightandout;
1068	OPEN(TOR)
1069		goto dumpleftmidrightandout;
1070	OPEN(TAND)
1071		goto dumpleftmidrightandout;
1072	OPEN(TBANG)
1073		goto dumprightandout;
1074	OPEN(TDBRACKET)
1075		i = 0;
1076		w = t->args;
1077		while (*w) {
1078			shf_putc('\n', shf);
1079			for (j = 0; j < nesting; ++j)
1080				shf_putc('\t', shf);
1081			shf_fprintf(shf, " arg%d<", i++);
1082			dumpwdvar(shf, *w++);
1083			shf_putc('>', shf);
1084		}
1085		break;
1086	OPEN(TFOR)
1087 dumpfor:
1088		shf_fprintf(shf, " str<%s>", t->str);
1089		if (t->vars != NULL) {
1090			i = 0;
1091			w = (const char **)t->vars;
1092			while (*w) {
1093				shf_putc('\n', shf);
1094				for (j = 0; j < nesting; ++j)
1095					shf_putc('\t', shf);
1096				shf_fprintf(shf, " var%d<", i++);
1097				dumpwdvar(shf, *w++);
1098				shf_putc('>', shf);
1099			}
1100		}
1101		goto dumpleftandout;
1102	OPEN(TSELECT)
1103		goto dumpfor;
1104	OPEN(TCASE)
1105		shf_fprintf(shf, " str<%s>", t->str);
1106		i = 0;
1107		for (t1 = t->left; t1 != NULL; t1 = t1->right) {
1108			shf_putc('\n', shf);
1109			for (j = 0; j < nesting; ++j)
1110				shf_putc('\t', shf);
1111			shf_fprintf(shf, " sub%d[(", i);
1112			w = (const char **)t1->vars;
1113			while (*w) {
1114				dumpwdvar(shf, *w);
1115				if (w[1] != NULL)
1116					shf_putc('|', shf);
1117				++w;
1118			}
1119			shf_putc(')', shf);
1120			dumpioact(shf, t);
1121			shf_putc('\n', shf);
1122			dumptree(shf, t1->left);
1123			shf_fprintf(shf, " ;%c/%d]", t1->u.charflag, i++);
1124		}
1125		break;
1126	OPEN(TWHILE)
1127		goto dumpleftmidrightandout;
1128	OPEN(TUNTIL)
1129		goto dumpleftmidrightandout;
1130	OPEN(TBRACE)
1131		goto dumpleftandout;
1132	OPEN(TCOPROC)
1133		goto dumpleftandout;
1134	OPEN(TASYNC)
1135		goto dumpleftandout;
1136	OPEN(TFUNCT)
1137		shf_fprintf(shf, " str<%s> ksh<%s>", t->str,
1138		    t->u.ksh_func ? Ttrue : Tfalse);
1139		goto dumpleftandout;
1140	OPEN(TTIME)
1141		goto dumpleftandout;
1142	OPEN(TIF)
1143 dumpif:
1144		shf_putc('\n', shf);
1145		dumptree(shf, t->left);
1146		t = t->right;
1147		dumpioact(shf, t);
1148		if (t->left != NULL) {
1149			shf_puts(" /TTHEN:\n", shf);
1150			dumptree(shf, t->left);
1151		}
1152		if (t->right && t->right->type == TELIF) {
1153			shf_puts(" /TELIF:", shf);
1154			t = t->right;
1155			dumpioact(shf, t);
1156			goto dumpif;
1157		}
1158		if (t->right != NULL) {
1159			shf_puts(" /TELSE:\n", shf);
1160			dumptree(shf, t->right);
1161		}
1162		break;
1163	OPEN(TEOF)
1164 dumpunexpected:
1165		shf_puts(Tunexpected, shf);
1166		break;
1167	OPEN(TELIF)
1168		goto dumpunexpected;
1169	OPEN(TPAT)
1170		goto dumpunexpected;
1171	default:
1172		name = "TINVALID";
1173		shf_fprintf(shf, "{T<%d>:" /*}*/, t->type);
1174		goto dumpunexpected;
1175
1176#undef OPEN
1177	}
1178 out:
1179	shf_fprintf(shf, /*{*/ " /%s}\n", name);
1180	--nesting;
1181}
1182#endif
1183