/* $OpenBSD: tree.c,v 1.19 2008/08/11 21:50:35 jaredy Exp $ */ /*- * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 * Thorsten Glaser * * Provided that these terms and disclaimer and all copyright notices * are retained or reproduced in an accompanying document, permission * is granted to deal in this work without restriction, including un- * limited rights to use, publicly perform, distribute, sell, modify, * merge, give away, or sublicence. * * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to * the utmost extent permitted by applicable law, neither express nor * implied; without malicious intent or gross negligence. In no event * may a licensor, author or contributor be held liable for indirect, * direct, other damage, loss, or other issues arising in any way out * of dealing in the work, even if advised of the possibility of such * damage or existence of a defect, except proven that it results out * of said person's immediate fault when using the work as intended. */ #include "sh.h" __RCSID("$MirOS: src/bin/mksh/tree.c,v 1.54 2011/12/29 23:36:25 tg Exp $"); #define INDENT 8 static void ptree(struct op *, int, struct shf *); static void pioact(struct shf *, int, struct ioword *); static const char *wdvarput(struct shf *, const char *, int, int); static void vfptreef(struct shf *, int, const char *, va_list); static struct ioword **iocopy(struct ioword **, Area *); static void iofree(struct ioword **, Area *); /* "foo& ; bar" and "foo |& ; bar" are invalid */ static bool prevent_semicolon; /* * print a command tree */ static void ptree(struct op *t, int indent, struct shf *shf) { const char **w; struct ioword **ioact; struct op *t1; int i; Chain: if (t == NULL) return; switch (t->type) { case TCOM: if (t->vars) { w = (const char **)t->vars; while (*w) fptreef(shf, indent, "%S ", *w++); } else shf_puts("#no-vars# ", shf); if (t->args) { w = t->args; while (*w) fptreef(shf, indent, "%S ", *w++); } else shf_puts("#no-args# ", shf); prevent_semicolon = false; break; case TEXEC: t = t->left; goto Chain; case TPAREN: fptreef(shf, indent + 2, "( %T) ", t->left); break; case TPIPE: fptreef(shf, indent, "%T| ", t->left); t = t->right; goto Chain; case TLIST: fptreef(shf, indent, "%T%;", t->left); t = t->right; goto Chain; case TOR: case TAND: fptreef(shf, indent, "%T%s %T", t->left, (t->type == TOR) ? "||" : "&&", t->right); break; case TBANG: shf_puts("! ", shf); prevent_semicolon = false; t = t->right; goto Chain; case TDBRACKET: w = t->args; shf_puts("[[", shf); while (*w) fptreef(shf, indent, " %S", *w++); shf_puts(" ]] ", shf); break; case TSELECT: case TFOR: fptreef(shf, indent, "%s %s ", (t->type == TFOR) ? "for" : Tselect, t->str); if (t->vars != NULL) { shf_puts("in ", shf); w = (const char **)t->vars; while (*w) fptreef(shf, indent, "%S ", *w++); fptreef(shf, indent, "%;"); } fptreef(shf, indent + INDENT, "do%N%T", t->left); fptreef(shf, indent, "%;done "); break; case TCASE: fptreef(shf, indent, "case %S in", t->str); for (t1 = t->left; t1 != NULL; t1 = t1->right) { fptreef(shf, indent, "%N("); w = (const char **)t1->vars; while (*w) { fptreef(shf, indent, "%S%c", *w, (w[1] != NULL) ? '|' : ')'); ++w; } fptreef(shf, indent + INDENT, "%N%T%N;%c", t1->left, t1->u.charflag); } fptreef(shf, indent, "%Nesac "); break; #ifndef MKSH_NO_DEPRECATED_WARNING case TELIF: internal_errorf("TELIF in tree.c:ptree() unexpected"); /* FALLTHROUGH */ #endif case TIF: i = 2; t1 = t; goto process_TIF; do { t1 = t1->right; i = 0; fptreef(shf, indent, "%;"); process_TIF: /* 5 == strlen("elif ") */ fptreef(shf, indent + 5 - i, "elif %T" + i, t1->left); t1 = t1->right; if (t1->left != NULL) { fptreef(shf, indent, "%;"); fptreef(shf, indent + INDENT, "%s%N%T", "then", t1->left); } } while (t1->right && t1->right->type == TELIF); if (t1->right != NULL) { fptreef(shf, indent, "%;"); fptreef(shf, indent + INDENT, "%s%N%T", "else", t1->right); } fptreef(shf, indent, "%;fi "); break; case TWHILE: case TUNTIL: /* 6 == strlen("while"/"until") */ fptreef(shf, indent + 6, "%s %T", (t->type == TWHILE) ? "while" : "until", t->left); fptreef(shf, indent, "%;"); fptreef(shf, indent + INDENT, "do%N%T", t->right); fptreef(shf, indent, "%;done "); break; case TBRACE: fptreef(shf, indent + INDENT, "{%N%T", t->left); fptreef(shf, indent, "%;} "); break; case TCOPROC: fptreef(shf, indent, "%T|& ", t->left); prevent_semicolon = true; break; case TASYNC: fptreef(shf, indent, "%T& ", t->left); prevent_semicolon = true; break; case TFUNCT: fpFUNCTf(shf, indent, tobool(t->u.ksh_func), t->str, t->left); break; case TTIME: fptreef(shf, indent, "%s %T", "time", t->left); break; default: shf_puts("", shf); prevent_semicolon = false; break; } if ((ioact = t->ioact) != NULL) { bool need_nl = false; while (*ioact != NULL) pioact(shf, indent, *ioact++); /* Print here documents after everything else... */ ioact = t->ioact; while (*ioact != NULL) { struct ioword *iop = *ioact++; /* heredoc is NULL when tracing (set -x) */ if ((iop->flag & (IOTYPE | IOHERESTR)) == IOHERE && iop->heredoc) { shf_putc('\n', shf); shf_puts(iop->heredoc, shf); fptreef(shf, indent, "%s", iop->flag & IONDELIM ? "<<" : evalstr(iop->delim, 0)); need_nl = true; } } /* * Last delimiter must be followed by a newline (this * often leads to an extra blank line, but it's not * worth worrying about) */ if (need_nl) shf_putc('\n', shf); } } static void pioact(struct shf *shf, int indent, struct ioword *iop) { int flag = iop->flag; int type = flag & IOTYPE; int expected; expected = (type == IOREAD || type == IORDWR || type == IOHERE) ? 0 : (type == IOCAT || type == IOWRITE) ? 1 : (type == IODUP && (iop->unit == !(flag & IORDUP))) ? iop->unit : iop->unit + 1; if (iop->unit != expected) shf_fprintf(shf, "%d", iop->unit); switch (type) { case IOREAD: shf_puts("<", shf); break; case IOHERE: shf_puts(flag & IOSKIP ? "<<-" : "<<", shf); break; case IOCAT: shf_puts(">>", shf); break; case IOWRITE: shf_puts(flag & IOCLOB ? ">|" : ">", shf); break; case IORDWR: shf_puts("<>", shf); break; case IODUP: shf_puts(flag & IORDUP ? "<&" : ">&", shf); break; } /* name/delim are NULL when printing syntax errors */ if (type == IOHERE) { if (iop->delim) fptreef(shf, indent, "%S ", iop->delim); else shf_putc(' ', shf); } else if (iop->name) fptreef(shf, indent, (iop->flag & IONAMEXP) ? "%s " : "%S ", iop->name); prevent_semicolon = false; } /* variant of fputs for ptreef and wdstrip */ static const char * wdvarput(struct shf *shf, const char *wp, int quotelevel, int opmode) { int c; /*- * problems: * `...` -> $(...) * 'foo' -> "foo" * x${foo:-"hi"} -> x${foo:-hi} unless WDS_TPUTS * x${foo:-'hi'} -> x${foo:-hi} unless WDS_KEEPQ * could change encoding to: * OQUOTE ["'] ... CQUOTE ["'] * COMSUB [(`] ...\0 (handle $ ` \ and maybe " in `...` case) */ while (/* CONSTCOND */ 1) switch (*wp++) { case EOS: return (--wp); case ADELIM: case CHAR: c = *wp++; if ((opmode & WDS_MAGIC) && (ISMAGIC(c) || c == '[' || c == NOT || c == '-' || c == ']' || c == '*' || c == '?')) shf_putc(MAGIC, shf); shf_putc(c, shf); break; case QCHAR: { bool doq; c = *wp++; doq = (c == '"' || c == '`' || c == '$' || c == '\\'); if (opmode & WDS_TPUTS) { if (quotelevel == 0) doq = true; } else { if (!(opmode & WDS_KEEPQ)) doq = false; } if (doq) shf_putc('\\', shf); shf_putc(c, shf); break; } case COMSUB: shf_puts("$(", shf); while ((c = *wp++) != 0) shf_putc(c, shf); shf_putc(')', shf); break; case EXPRSUB: shf_puts("$((", shf); while ((c = *wp++) != 0) shf_putc(c, shf); shf_puts("))", shf); break; case OQUOTE: if (opmode & WDS_TPUTS) { quotelevel++; shf_putc('"', shf); } break; case CQUOTE: if (opmode & WDS_TPUTS) { if (quotelevel) quotelevel--; shf_putc('"', shf); } break; case OSUBST: shf_putc('$', shf); if (*wp++ == '{') shf_putc('{', shf); while ((c = *wp++) != 0) shf_putc(c, shf); wp = wdvarput(shf, wp, 0, opmode); break; case CSUBST: if (*wp++ == '}') shf_putc('}', shf); return (wp); case OPAT: if (opmode & WDS_MAGIC) { shf_putc(MAGIC, shf); shf_putchar(*wp++ | 0x80, shf); } else { shf_putchar(*wp++, shf); shf_putc('(', shf); } break; case SPAT: c = '|'; if (0) case CPAT: c = /*(*/ ')'; if (opmode & WDS_MAGIC) shf_putc(MAGIC, shf); shf_putc(c, shf); break; } } /* * this is the _only_ way to reliably handle * variable args with an ANSI compiler */ /* VARARGS */ void fptreef(struct shf *shf, int indent, const char *fmt, ...) { va_list va; va_start(va, fmt); vfptreef(shf, indent, fmt, va); va_end(va); } /* VARARGS */ char * snptreef(char *s, ssize_t n, const char *fmt, ...) { va_list va; struct shf shf; shf_sopen(s, n, SHF_WR | (s ? 0 : SHF_DYNAMIC), &shf); va_start(va, fmt); vfptreef(&shf, 0, fmt, va); va_end(va); /* shf_sclose NUL terminates */ return (shf_sclose(&shf)); } static void vfptreef(struct shf *shf, int indent, const char *fmt, va_list va) { int c; while ((c = *fmt++)) { if (c == '%') { switch ((c = *fmt++)) { case 'c': /* character (octet, probably) */ shf_putchar(va_arg(va, int), shf); break; case 's': /* string */ shf_puts(va_arg(va, char *), shf); break; case 'S': /* word */ wdvarput(shf, va_arg(va, char *), 0, WDS_TPUTS); break; case 'd': /* signed decimal */ shf_fprintf(shf, "%d", va_arg(va, int)); break; case 'u': /* unsigned decimal */ shf_fprintf(shf, "%u", va_arg(va, unsigned int)); break; case 'T': /* format tree */ ptree(va_arg(va, struct op *), indent, shf); goto dont_trash_prevent_semicolon; case ';': /* newline or ; */ case 'N': /* newline or space */ if (shf->flags & SHF_STRING) { if (c == ';' && !prevent_semicolon) shf_putc(';', shf); shf_putc(' ', shf); } else { int i; shf_putc('\n', shf); i = indent; while (i >= 8) { shf_putc('\t', shf); i -= 8; } while (i--) shf_putc(' ', shf); } break; case 'R': /* I/O redirection */ pioact(shf, indent, va_arg(va, struct ioword *)); break; default: shf_putc(c, shf); break; } } else shf_putc(c, shf); prevent_semicolon = false; dont_trash_prevent_semicolon: ; } } /* * copy tree (for function definition) */ struct op * tcopy(struct op *t, Area *ap) { struct op *r; const char **tw; char **rw; if (t == NULL) return (NULL); r = alloc(sizeof(struct op), ap); r->type = t->type; r->u.evalflags = t->u.evalflags; if (t->type == TCASE) r->str = wdcopy(t->str, ap); else strdupx(r->str, t->str, ap); if (t->vars == NULL) r->vars = NULL; else { tw = (const char **)t->vars; while (*tw) ++tw; rw = r->vars = alloc2(tw - (const char **)t->vars + 1, sizeof(*tw), ap); tw = (const char **)t->vars; while (*tw) *rw++ = wdcopy(*tw++, ap); *rw = NULL; } if (t->args == NULL) r->args = NULL; else { tw = t->args; while (*tw) ++tw; r->args = (const char **)(rw = alloc2(tw - t->args + 1, sizeof(*tw), ap)); tw = t->args; while (*tw) *rw++ = wdcopy(*tw++, ap); *rw = NULL; } r->ioact = (t->ioact == NULL) ? NULL : iocopy(t->ioact, ap); r->left = tcopy(t->left, ap); r->right = tcopy(t->right, ap); r->lineno = t->lineno; return (r); } char * wdcopy(const char *wp, Area *ap) { size_t len; len = wdscan(wp, EOS) - wp; return (memcpy(alloc(len, ap), wp, len)); } /* return the position of prefix c in wp plus 1 */ const char * wdscan(const char *wp, int c) { int nest = 0; while (/* CONSTCOND */ 1) switch (*wp++) { case EOS: return (wp); case ADELIM: if (c == ADELIM) return (wp + 1); /* FALLTHROUGH */ case CHAR: case QCHAR: wp++; break; case COMSUB: case EXPRSUB: while (*wp++ != 0) ; break; case OQUOTE: case CQUOTE: break; case OSUBST: nest++; while (*wp++ != '\0') ; break; case CSUBST: wp++; if (c == CSUBST && nest == 0) return (wp); nest--; break; case OPAT: nest++; wp++; break; case SPAT: case CPAT: if (c == wp[-1] && nest == 0) return (wp); if (wp[-1] == CPAT) nest--; break; default: internal_warningf( "wdscan: unknown char 0x%x (carrying on)", wp[-1]); } } /* * return a copy of wp without any of the mark up characters and with * quote characters (" ' \) stripped. (string is allocated from ATEMP) */ char * wdstrip(const char *wp, int opmode) { struct shf shf; shf_sopen(NULL, 32, SHF_WR | SHF_DYNAMIC, &shf); wdvarput(&shf, wp, 0, opmode); /* shf_sclose NUL terminates */ return (shf_sclose(&shf)); } static struct ioword ** iocopy(struct ioword **iow, Area *ap) { struct ioword **ior; int i; ior = iow; while (*ior) ++ior; ior = alloc2(ior - iow + 1, sizeof(struct ioword *), ap); for (i = 0; iow[i] != NULL; i++) { struct ioword *p, *q; p = iow[i]; q = alloc(sizeof(struct ioword), ap); ior[i] = q; *q = *p; if (p->name != NULL) q->name = wdcopy(p->name, ap); if (p->delim != NULL) q->delim = wdcopy(p->delim, ap); if (p->heredoc != NULL) strdupx(q->heredoc, p->heredoc, ap); } ior[i] = NULL; return (ior); } /* * free tree (for function definition) */ void tfree(struct op *t, Area *ap) { char **w; if (t == NULL) return; if (t->str != NULL) afree(t->str, ap); if (t->vars != NULL) { for (w = t->vars; *w != NULL; w++) afree(*w, ap); afree(t->vars, ap); } if (t->args != NULL) { /*XXX we assume the caller is right */ union mksh_ccphack cw; cw.ro = t->args; for (w = cw.rw; *w != NULL; w++) afree(*w, ap); afree(t->args, ap); } if (t->ioact != NULL) iofree(t->ioact, ap); tfree(t->left, ap); tfree(t->right, ap); afree(t, ap); } static void iofree(struct ioword **iow, Area *ap) { struct ioword **iop; struct ioword *p; iop = iow; while ((p = *iop++) != NULL) { if (p->name != NULL) afree(p->name, ap); if (p->delim != NULL) afree(p->delim, ap); if (p->heredoc != NULL) afree(p->heredoc, ap); afree(p, ap); } afree(iow, ap); } void fpFUNCTf(struct shf *shf, int i, bool isksh, const char *k, struct op *v) { if (isksh) fptreef(shf, i, "%s %s %T", Tfunction, k, v); else fptreef(shf, i, "%s() %T", k, v); } /* for jobs.c */ void vistree(char *dst, size_t sz, struct op *t) { int c; char *cp, *buf; buf = alloc(sz, ATEMP); snptreef(buf, sz, "%T", t); cp = buf; while ((c = *cp++)) { if (((c & 0x60) == 0) || ((c & 0x7F) == 0x7F)) { /* C0 or C1 control character or DEL */ if (!--sz) break; *dst++ = (c & 0x80) ? '$' : '^'; c = (c & 0x7F) ^ 0x40; } if (!--sz) break; *dst++ = c; } *dst = '\0'; afree(buf, ATEMP); } #ifdef DEBUG void dumpchar(struct shf *shf, int c) { if (((c & 0x60) == 0) || ((c & 0x7F) == 0x7F)) { /* C0 or C1 control character or DEL */ shf_putc((c & 0x80) ? '$' : '^', shf); c = (c & 0x7F) ^ 0x40; } shf_putc(c, shf); } /* see: wdvarput */ static const char * dumpwdvar_i(struct shf *shf, const char *wp, int quotelevel) { int c; while (/* CONSTCOND */ 1) { switch(*wp++) { case EOS: shf_puts("EOS", shf); return (--wp); case ADELIM: shf_puts("ADELIM=", shf); if (0) case CHAR: shf_puts("CHAR=", shf); dumpchar(shf, *wp++); break; case QCHAR: shf_puts("QCHAR<", shf); c = *wp++; if (quotelevel == 0 || (c == '"' || c == '`' || c == '$' || c == '\\')) shf_putc('\\', shf); dumpchar(shf, c); goto closeandout; case COMSUB: shf_puts("COMSUB<", shf); dumpsub: while ((c = *wp++) != 0) dumpchar(shf, c); closeandout: shf_putc('>', shf); break; case EXPRSUB: shf_puts("EXPRSUB<", shf); goto dumpsub; case OQUOTE: shf_fprintf(shf, "OQUOTE{%d", ++quotelevel); break; case CQUOTE: shf_fprintf(shf, "%d}CQUOTE", quotelevel); if (quotelevel) quotelevel--; else shf_puts("(err)", shf); break; case OSUBST: shf_puts("OSUBST(", shf); dumpchar(shf, *wp++); shf_puts(")[", shf); while ((c = *wp++) != 0) dumpchar(shf, c); shf_putc('|', shf); wp = dumpwdvar_i(shf, wp, 0); break; case CSUBST: shf_puts("]CSUBST(", shf); dumpchar(shf, *wp++); shf_putc(')', shf); return (wp); case OPAT: shf_puts("OPAT=", shf); dumpchar(shf, *wp++); break; case SPAT: shf_puts("SPAT", shf); break; case CPAT: shf_puts("CPAT", shf); break; default: shf_fprintf(shf, "INVAL<%u>", (uint8_t)wp[-1]); break; } shf_putc(' ', shf); } } void dumpwdvar(struct shf *shf, const char *wp) { dumpwdvar_i(shf, wp, 0); } void dumpioact(struct shf *shf, struct op *t) { struct ioword **ioact, *iop; if ((ioact = t->ioact) == NULL) return; shf_puts("{IOACT", shf); while ((iop = *ioact++) != NULL) { int type = iop->flag & IOTYPE; #define DT(x) case x: shf_puts(#x, shf); break; #define DB(x) if (iop->flag & x) shf_puts("|" #x, shf); shf_putc(';', shf); switch (type) { DT(IOREAD) DT(IOWRITE) DT(IORDWR) DT(IOHERE) DT(IOCAT) DT(IODUP) default: shf_fprintf(shf, "unk%d", type); } DB(IOEVAL) DB(IOSKIP) DB(IOCLOB) DB(IORDUP) DB(IONAMEXP) DB(IOBASH) DB(IOHERESTR) DB(IONDELIM) shf_fprintf(shf, ",unit=%d", iop->unit); if (iop->delim) { shf_puts(",delim<", shf); dumpwdvar(shf, iop->delim); shf_putc('>', shf); } if (iop->name) { if (iop->flag & IONAMEXP) { shf_puts(",name=", shf); print_value_quoted(shf, iop->name); } else { shf_puts(",name<", shf); dumpwdvar(shf, iop->name); shf_putc('>', shf); } } if (iop->heredoc) { shf_puts(",heredoc=", shf); print_value_quoted(shf, iop->heredoc); } #undef DT #undef DB } shf_putc('}', shf); } void dumptree(struct shf *shf, struct op *t) { int i; const char **w, *name; struct op *t1; static int nesting = 0; for (i = 0; i < nesting; ++i) shf_putc('\t', shf); ++nesting; shf_puts("{tree:" /*}*/, shf); if (t == NULL) { name = "(null)"; goto out; } dumpioact(shf, t); switch (t->type) { #define OPEN(x) case x: name = #x; shf_puts(" {" #x ":", shf); /*}*/ OPEN(TCOM) if (t->vars) { i = 0; w = (const char **)t->vars; while (*w) { shf_putc('\n', shf); for (int j = 0; j < nesting; ++j) shf_putc('\t', shf); shf_fprintf(shf, " var%d<", i++); dumpwdvar(shf, *w++); shf_putc('>', shf); } } else shf_puts(" #no-vars#", shf); if (t->args) { i = 0; w = t->args; while (*w) { shf_putc('\n', shf); for (int j = 0; j < nesting; ++j) shf_putc('\t', shf); shf_fprintf(shf, " arg%d<", i++); dumpwdvar(shf, *w++); shf_putc('>', shf); } } else shf_puts(" #no-args#", shf); break; OPEN(TEXEC) dumpleftandout: t = t->left; dumpandout: shf_putc('\n', shf); dumptree(shf, t); break; OPEN(TPAREN) goto dumpleftandout; OPEN(TPIPE) dumpleftmidrightandout: shf_putc('\n', shf); dumptree(shf, t->left); /* middumprightandout: (unused) */ shf_fprintf(shf, "/%s:", name); dumprightandout: t = t->right; goto dumpandout; OPEN(TLIST) goto dumpleftmidrightandout; OPEN(TOR) goto dumpleftmidrightandout; OPEN(TAND) goto dumpleftmidrightandout; OPEN(TBANG) goto dumprightandout; OPEN(TDBRACKET) i = 0; w = t->args; while (*w) { shf_putc('\n', shf); for (int j = 0; j < nesting; ++j) shf_putc('\t', shf); shf_fprintf(shf, " arg%d<", i++); dumpwdvar(shf, *w++); shf_putc('>', shf); } break; OPEN(TFOR) dumpfor: shf_fprintf(shf, " str<%s>", t->str); if (t->vars != NULL) { i = 0; w = (const char **)t->vars; while (*w) { shf_putc('\n', shf); for (int j = 0; j < nesting; ++j) shf_putc('\t', shf); shf_fprintf(shf, " var%d<", i++); dumpwdvar(shf, *w++); shf_putc('>', shf); } } goto dumpleftandout; OPEN(TSELECT) goto dumpfor; OPEN(TCASE) shf_fprintf(shf, " str<%s>", t->str); i = 0; for (t1 = t->left; t1 != NULL; t1 = t1->right) { shf_putc('\n', shf); for (int j = 0; j < nesting; ++j) shf_putc('\t', shf); shf_fprintf(shf, " sub%d[(", i); w = (const char **)t1->vars; while (*w) { dumpwdvar(shf, *w); if (w[1] != NULL) shf_putc('|', shf); ++w; } shf_putc(')', shf); dumpioact(shf, t); shf_putc('\n', shf); dumptree(shf, t1->left); shf_fprintf(shf, " ;%c/%d]", t1->u.charflag, i++); } break; OPEN(TWHILE) goto dumpleftmidrightandout; OPEN(TUNTIL) goto dumpleftmidrightandout; OPEN(TBRACE) goto dumpleftandout; OPEN(TCOPROC) goto dumpleftandout; OPEN(TASYNC) goto dumpleftandout; OPEN(TFUNCT) shf_fprintf(shf, " str<%s> ksh<%s>", t->str, t->u.ksh_func ? "yes" : "no"); goto dumpleftandout; OPEN(TTIME) goto dumpleftandout; OPEN(TIF) dumpif: shf_putc('\n', shf); dumptree(shf, t->left); t = t->right; dumpioact(shf, t); if (t->left != NULL) { shf_puts(" /TTHEN:\n", shf); dumptree(shf, t->left); } if (t->right && t->right->type == TELIF) { shf_puts(" /TELIF:", shf); t = t->right; dumpioact(shf, t); goto dumpif; } if (t->right != NULL) { shf_puts(" /TELSE:\n", shf); dumptree(shf, t->right); } break; OPEN(TEOF) dumpunexpected: shf_puts("unexpected", shf); break; OPEN(TELIF) goto dumpunexpected; OPEN(TPAT) goto dumpunexpected; default: name = "TINVALID"; shf_fprintf(shf, "{T<%d>:" /*}*/, t->type); goto dumpunexpected; #undef OPEN } out: shf_fprintf(shf, /*{*/ " /%s}\n", name); --nesting; } #endif