OSDN Git Service

Upgrade to mksh 51.
[android-x86/external-mksh.git] / src / tree.c
index aa861db..c057559 100644 (file)
@@ -1,7 +1,8 @@
-/*     $OpenBSD: tree.c,v 1.19 2008/08/11 21:50:35 jaredy Exp $        */
+/*     $OpenBSD: tree.c,v 1.20 2012/06/27 07:17:19 otto Exp $  */
 
 /*-
- * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+ *              2011, 2012, 2013, 2015
  *     Thorsten Glaser <tg@mirbsd.org>
  *
  * Provided that these terms and disclaimer and all copyright notices
 
 #include "sh.h"
 
-__RCSID("$MirOS: src/bin/mksh/tree.c,v 1.30 2010/02/25 20:18:19 tg Exp $");
+__RCSID("$MirOS: src/bin/mksh/tree.c,v 1.73 2015/04/11 22:03:32 tg Exp $");
 
-#define INDENT 4
+#define INDENT 8
 
-#define tputc(c, shf) shf_putchar(c, shf);
 static void ptree(struct op *, int, struct shf *);
-static void pioact(struct shf *, int, struct ioword *);
-static void tputC(int, struct shf *);
-static void tputS(char *, struct shf *);
+static void pioact(struct shf *, 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;
+
+static const char Telif_pT[] = "elif %T";
+
 /*
  * print a command tree
  */
@@ -44,21 +48,43 @@ 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)
-                       for (w = (const char **)t->vars; *w != NULL; )
+               prevent_semicolon = false;
+               /*
+                * special-case 'var=<<EOF' (rough; see
+                * exec.c:execute() for full code)
+                */
+               if (
+                   /* we have zero arguments, i.e. no programme to run */
+                   t->args[0] == NULL &&
+                   /* we have exactly one variable assignment */
+                   t->vars[0] != NULL && t->vars[1] == NULL &&
+                   /* we have exactly one I/O redirection */
+                   t->ioact != NULL && t->ioact[0] != NULL &&
+                   t->ioact[1] == NULL &&
+                   /* of type "here document" (or "here string") */
+                   (t->ioact[0]->ioflag & IOTYPE) == IOHERE) {
+                       fptreef(shf, indent, "%S", t->vars[0]);
+                       break;
+               }
+
+               if (t->vars) {
+                       w = (const char **)t->vars;
+                       while (*w)
                                fptreef(shf, indent, "%S ", *w++);
-               else
+               else
                        shf_puts("#no-vars# ", shf);
-               if (t->args)
-                       for (w = t->args; *w != NULL; )
+               if (t->args) {
+                       w = t->args;
+                       while (*w)
                                fptreef(shf, indent, "%S ", *w++);
-               else
+               else
                        shf_puts("#no-args# ", shf);
                break;
        case TEXEC:
@@ -78,30 +104,28 @@ ptree(struct op *t, int indent, struct shf *shf)
        case TOR:
        case TAND:
                fptreef(shf, indent, "%T%s %T",
-                   t->left, (t->type==TOR) ? "||" : "&&", t->right);
+                   t->left, (t->type == TOR) ? "||" : "&&", t->right);
                break;
        case TBANG:
                shf_puts("! ", shf);
+               prevent_semicolon = false;
                t = t->right;
                goto Chain;
-       case TDBRACKET: {
-               int i;
-
+       case TDBRACKET:
+               w = t->args;
                shf_puts("[[", shf);
-               for (i = 0; t->args[i]; i++)
-                       fptreef(shf, indent, " %S", t->args[i]);
+               while (*w)
+                       fptreef(shf, indent, " %S", *w++);
                shf_puts(" ]] ", shf);
                break;
-       }
        case TSELECT:
-               fptreef(shf, indent, "select %s ", t->str);
-               /* FALLTHROUGH */
        case TFOR:
-               if (t->type == TFOR)
-                       fptreef(shf, indent, "for %s ", t->str);
+               fptreef(shf, indent, "%s %s ",
+                   (t->type == TFOR) ? "for" : Tselect, t->str);
                if (t->vars != NULL) {
                        shf_puts("in ", shf);
-                       for (w = (const char **)t->vars; *w; )
+                       w = (const char **)t->vars;
+                       while (*w)
                                fptreef(shf, indent, "%S ", *w++);
                        fptreef(shf, indent, "%;");
                }
@@ -112,231 +136,279 @@ ptree(struct op *t, int indent, struct shf *shf)
                fptreef(shf, indent, "case %S in", t->str);
                for (t1 = t->left; t1 != NULL; t1 = t1->right) {
                        fptreef(shf, indent, "%N(");
-                       for (w = (const char **)t1->vars; *w != NULL; w++)
+                       w = (const char **)t1->vars;
+                       while (*w) {
                                fptreef(shf, indent, "%S%c", *w,
                                    (w[1] != NULL) ? '|' : ')');
-                       fptreef(shf, indent + INDENT, "%;%T%N;;", t1->left);
+                               ++w;
+                       }
+                       fptreef(shf, indent + INDENT, "%N%T%N;%c", t1->left,
+                           t1->u.charflag);
                }
                fptreef(shf, indent, "%Nesac ");
                break;
-       case TIF:
+#ifdef DEBUG
        case TELIF:
-               /* 3 == strlen("if ") */
-               fptreef(shf, indent + 3, "if %T", t->left);
-               for (;;) {
-                       t = t->right;
-                       if (t->left != NULL) {
-                               fptreef(shf, indent, "%;");
-                               fptreef(shf, indent + INDENT, "then%N%T",
-                                   t->left);
-                       }
-                       if (t->right == NULL || t->right->type != TELIF)
-                               break;
-                       t = t->right;
+               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, "elif %T", t->left);
-               }
-               if (t->right != NULL) {
+                       fptreef(shf, indent + 5 - i, Telif_pT + 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, "else%;%T", t->right);
+                       fptreef(shf, indent + INDENT, "%s%N%T",
+                           "else", t1->right);
                }
                fptreef(shf, indent, "%;fi ");
                break;
        case TWHILE:
        case TUNTIL:
-               /* 6 == strlen("while"/"until") */
+               /* 6 == strlen("while "/"until ") */
                fptreef(shf, indent + 6, "%s %T",
-                   (t->type==TWHILE) ? "while" : "until",
+                   (t->type == TWHILE) ? "while" : "until",
                    t->left);
-               fptreef(shf, indent, "%;do");
-               fptreef(shf, indent + INDENT, "%;%T", t->right);
+               fptreef(shf, indent, "%;");
+               fptreef(shf, indent + INDENT, "do%N%T", t->right);
                fptreef(shf, indent, "%;done ");
                break;
        case TBRACE:
-               fptreef(shf, indent + INDENT, "{%;%T", t->left);
+               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:
-               fptreef(shf, indent,
-                   t->u.ksh_func ? "function %s %T" : "%s() %T",
-                   t->str, t->left);
+               fpFUNCTf(shf, indent, tobool(t->u.ksh_func), t->str, t->left);
                break;
        case TTIME:
-               fptreef(shf, indent, "time %T", t->left);
+               fptreef(shf, indent, "%s %T", "time", t->left);
                break;
        default:
                shf_puts("<botch>", shf);
+               prevent_semicolon = false;
                break;
        }
        if ((ioact = t->ioact) != NULL) {
-               int     need_nl = 0;
+               bool need_nl = false;
 
                while (*ioact != NULL)
-                       pioact(shf, indent, *ioact++);
+                       pioact(shf, *ioact++);
                /* Print here documents after everything else... */
-               for (ioact = t->ioact; *ioact != NULL; ) {
+               ioact = t->ioact;
+               while (*ioact != NULL) {
                        struct ioword *iop = *ioact++;
 
-                       /* heredoc is 0 when tracing (set -x) */
-                       if ((iop->flag & IOTYPE) == IOHERE && iop->heredoc &&
-                           /* iop->delim[1] == '<' means here string */
-                           (!iop->delim || iop->delim[1] != '<')) {
-                               tputc('\n', shf);
+                       /* heredoc is NULL when tracing (set -x) */
+                       if ((iop->ioflag & (IOTYPE | IOHERESTR)) == IOHERE &&
+                           iop->heredoc) {
+                               shf_putc('\n', shf);
                                shf_puts(iop->heredoc, shf);
                                fptreef(shf, indent, "%s",
+                                   iop->ioflag & IONDELIM ? "<<" :
                                    evalstr(iop->delim, 0));
-                               need_nl = 1;
+                               need_nl = true;
                        }
                }
-               /* Last delimiter must be followed by a newline (this often
-                * leads to an extra blank line, but its not worth worrying
-                * about)
+               /*
+                * 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)
-                       tputc('\n', shf);
+               if (need_nl) {
+                       shf_putc('\n', shf);
+                       prevent_semicolon = true;
+               }
        }
 }
 
 static void
-pioact(struct shf *shf, int indent, struct ioword *iop)
+pioact(struct shf *shf, struct ioword *iop)
 {
-       int flag = iop->flag;
-       int type = flag & IOTYPE;
-       int expected;
+       unsigned short flag = iop->ioflag;
+       unsigned short type = flag & IOTYPE;
+       short 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);
+               shf_fprintf(shf, "%d", (int)iop->unit);
 
        switch (type) {
        case IOREAD:
-               shf_puts("< ", shf);
+               shf_putc('<', shf);
                break;
        case IOHERE:
-               shf_puts(flag & IOSKIP ? "<<-" : "<<", shf);
+               shf_puts("<<", shf);
+               if (flag & IOSKIP)
+                       shf_putc('-', shf);
                break;
        case IOCAT:
-               shf_puts(">> ", shf);
+               shf_puts(">>", shf);
                break;
        case IOWRITE:
-               shf_puts(flag & IOCLOB ? ">| " : "> ", shf);
+               shf_putc('>', shf);
+               if (flag & IOCLOB)
+                       shf_putc('|', shf);
                break;
        case IORDWR:
-               shf_puts("<> ", shf);
+               shf_puts("<>", shf);
                break;
        case IODUP:
                shf_puts(flag & IORDUP ? "<&" : ">&", shf);
                break;
        }
-       /* name/delim are 0 when printing syntax errors */
+       /* name/delim are NULL when printing syntax errors */
        if (type == IOHERE) {
                if (iop->delim)
-                       fptreef(shf, indent, "%s%S ",
-                           /* here string */ iop->delim[1] == '<' ? "" : " ",
-                           iop->delim);
+                       wdvarput(shf, iop->delim, 0, WDS_TPUTS);
+               if (flag & IOHERESTR)
+                       shf_putc(' ', shf);
+       } else if (iop->name) {
+               if (flag & IONAMEXP)
+                       print_value_quoted(shf, iop->name);
                else
-                       tputc(' ', shf);
-       } else if (iop->name)
-               fptreef(shf, indent, (iop->flag & IONAMEXP) ? "%s " : "%S ",
-                   iop->name);
-}
-
-
-/*
- * variants of fputc, fputs for ptreef and snptreef
- */
-static void
-tputC(int c, struct shf *shf)
-{
-       if ((c&0x60) == 0) {            /* C0|C1 */
-               tputc((c&0x80) ? '$' : '^', shf);
-               tputc(((c&0x7F)|0x40), shf);
-       } else if ((c&0x7F) == 0x7F) {  /* DEL */
-               tputc((c&0x80) ? '$' : '^', shf);
-               tputc('?', shf);
-       } else
-               tputc(c, shf);
+                       wdvarput(shf, iop->name, 0, WDS_TPUTS);
+               shf_putc(' ', shf);
+       }
+       prevent_semicolon = false;
 }
 
-static void
-tputS(char *wp, struct shf *shf)
+/* variant of fputs for ptreef and wdstrip */
+static const char *
+wdvarput(struct shf *shf, const char *wp, int quotelevel, int opmode)
 {
-       int c, quotelevel = 0;
+       int c;
+       const char *cs;
 
-       /* problems:
+       /*-
+        * 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 (1)
+       while (/* CONSTCOND */ 1)
                switch (*wp++) {
                case EOS:
-                       return;
+                       return (--wp);
                case ADELIM:
                case CHAR:
-                       tputC(*wp++, shf);
+                       c = *wp++;
+                       if ((opmode & WDS_MAGIC) &&
+                           (ISMAGIC(c) || c == '[' || c == '!' ||
+                           c == '-' || c == ']' || c == '*' || c == '?'))
+                               shf_putc(MAGIC, shf);
+                       shf_putc(c, shf);
                        break;
-               case QCHAR:
+               case QCHAR: {
+                       bool doq;
+
                        c = *wp++;
-                       if (!quotelevel || (c == '"' || c == '`' || c == '$'))
-                               tputc('\\', shf);
-                       tputC(c, shf);
+                       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 (*wp != 0)
-                               tputC(*wp++, shf);
-                       tputc(')', shf);
-                       wp++;
+                       cs = ")";
+ pSUB:
+                       while ((c = *wp++) != 0)
+                               shf_putc(c, shf);
+                       shf_puts(cs, shf);
                        break;
+               case FUNSUB:
+                       c = ' ';
+                       if (0)
+                               /* FALLTHROUGH */
+               case VALSUB:
+                         c = '|';
+                       shf_putc('$', shf);
+                       shf_putc('{', shf);
+                       shf_putc(c, shf);
+                       cs = ";}";
+                       goto pSUB;
                case EXPRSUB:
                        shf_puts("$((", shf);
-                       while (*wp != 0)
-                               tputC(*wp++, shf);
-                       shf_puts("))", shf);
-                       wp++;
-                       break;
+                       cs = "))";
+                       goto pSUB;
                case OQUOTE:
-                       quotelevel++;
-                       tputc('"', shf);
+                       if (opmode & WDS_TPUTS) {
+                               quotelevel++;
+                               shf_putc('"', shf);
+                       }
                        break;
                case CQUOTE:
-                       if (quotelevel)
-                               quotelevel--;
-                       tputc('"', shf);
+                       if (opmode & WDS_TPUTS) {
+                               if (quotelevel)
+                                       quotelevel--;
+                               shf_putc('"', shf);
+                       }
                        break;
                case OSUBST:
-                       tputc('$', shf);
+                       shf_putc('$', shf);
                        if (*wp++ == '{')
-                               tputc('{', shf);
+                               shf_putc('{', shf);
                        while ((c = *wp++) != 0)
-                               tputC(c, shf);
+                               shf_putc(c, shf);
+                       wp = wdvarput(shf, wp, 0, opmode);
                        break;
                case CSUBST:
                        if (*wp++ == '}')
-                               tputc('}', shf);
-                       break;
+                               shf_putc('}', shf);
+                       return (wp);
                case OPAT:
-                       tputc(*wp++, shf);
-                       tputc('(', shf);
+                       if (opmode & WDS_MAGIC) {
+                               shf_putc(MAGIC, shf);
+                               shf_putchar(*wp++ | 0x80, shf);
+                       } else {
+                               shf_putchar(*wp++, shf);
+                               shf_putc('(', shf);
+                       }
                        break;
                case SPAT:
-                       tputc('|', shf);
-                       break;
+                       c = '|';
+                       if (0)
                case CPAT:
-                       tputc(')', shf);
+                               c = /*(*/ ')';
+                       if (opmode & WDS_MAGIC)
+                               shf_putc(MAGIC, shf);
+                       shf_putc(c, shf);
                        break;
                }
 }
@@ -346,21 +418,19 @@ tputS(char *wp, struct shf *shf)
  * variable args with an ANSI compiler
  */
 /* VARARGS */
-int
+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);
-       return (0);
 }
 
 /* VARARGS */
 char *
-snptreef(char *s, int n, const char *fmt, ...)
+snptreef(char *s, ssize_t n, const char *fmt, ...)
 {
        va_list va;
        struct shf shf;
@@ -371,7 +441,8 @@ snptreef(char *s, int n, const char *fmt, ...)
        vfptreef(&shf, 0, fmt, va);
        va_end(va);
 
-       return (shf_sclose(&shf)); /* null terminates */
+       /* shf_sclose NUL terminates */
+       return (shf_sclose(&shf));
 }
 
 static void
@@ -383,48 +454,63 @@ vfptreef(struct shf *shf, int indent, const char *fmt, va_list va)
                if (c == '%') {
                        switch ((c = *fmt++)) {
                        case 'c':
-                               tputc(va_arg(va, int), shf);
+                               /* 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 */
-                               tputS(va_arg(va, char *), shf);
+                       case 'S':
+                               /* word */
+                               wdvarput(shf, va_arg(va, char *), 0, WDS_TPUTS);
                                break;
-                       case 'd':       /* decimal */
+                       case 'd':
+                               /* signed decimal */
                                shf_fprintf(shf, "%d", va_arg(va, int));
                                break;
-                       case 'u':       /* decimal */
+                       case 'u':
+                               /* unsigned decimal */
                                shf_fprintf(shf, "%u", va_arg(va, unsigned int));
                                break;
-                       case 'T':       /* format tree */
+                       case 'T':
+                               /* format tree */
                                ptree(va_arg(va, struct op *), indent, shf);
-                               break;
-                       case ';':       /* newline or ; */
-                       case 'N':       /* newline or space */
+                               goto dont_trash_prevent_semicolon;
+                       case ';':
+                               /* newline or ; */
+                       case 'N':
+                               /* newline or space */
                                if (shf->flags & SHF_STRING) {
-                                       if (c == ';')
-                                               tputc(';', shf);
-                                       tputc(' ', shf);
+                                       if (c == ';' && !prevent_semicolon)
+                                               shf_putc(';', shf);
+                                       shf_putc(' ', shf);
                                } else {
                                        int i;
 
-                                       tputc('\n', shf);
-                                       for (i = indent; i >= 8; i -= 8)
-                                               tputc('\t', shf);
-                                       for (; i > 0; --i)
-                                               tputc(' ', shf);
+                                       shf_putc('\n', shf);
+                                       i = indent;
+                                       while (i >= 8) {
+                                               shf_putc('\t', shf);
+                                               i -= 8;
+                                       }
+                                       while (i--)
+                                               shf_putc(' ', shf);
                                }
                                break;
                        case 'R':
-                               pioact(shf, indent, va_arg(va, struct ioword *));
+                               /* I/O redirection */
+                               pioact(shf, va_arg(va, struct ioword *));
                                break;
                        default:
-                               tputc(c, shf);
+                               shf_putc(c, shf);
                                break;
                        }
                } else
-                       tputc(c, shf);
+                       shf_putc(c, shf);
+               prevent_semicolon = false;
+ dont_trash_prevent_semicolon:
+               ;
        }
 }
 
@@ -454,11 +540,13 @@ tcopy(struct op *t, Area *ap)
        if (t->vars == NULL)
                r->vars = NULL;
        else {
-               for (tw = (const char **)t->vars; *tw++ != NULL; )
-                       ;
-               rw = r->vars = alloc((tw - (const char **)t->vars + 1) *
+               tw = (const char **)t->vars;
+               while (*tw)
+                       ++tw;
+               rw = r->vars = alloc2(tw - (const char **)t->vars + 1,
                    sizeof(*tw), ap);
-               for (tw = (const char **)t->vars; *tw != NULL; )
+               tw = (const char **)t->vars;
+               while (*tw)
                        *rw++ = wdcopy(*tw++, ap);
                *rw = NULL;
        }
@@ -466,11 +554,13 @@ tcopy(struct op *t, Area *ap)
        if (t->args == NULL)
                r->args = NULL;
        else {
-               for (tw = t->args; *tw++ != NULL; )
-                       ;
-               r->args = (const char **)(rw = alloc((tw - t->args + 1) *
+               tw = t->args;
+               while (*tw)
+                       ++tw;
+               r->args = (const char **)(rw = alloc2(tw - t->args + 1,
                    sizeof(*tw), ap));
-               for (tw = t->args; *tw != NULL; )
+               tw = t->args;
+               while (*tw)
                        *rw++ = wdcopy(*tw++, ap);
                *rw = NULL;
        }
@@ -487,7 +577,9 @@ tcopy(struct op *t, Area *ap)
 char *
 wdcopy(const char *wp, Area *ap)
 {
-       size_t len = wdscan(wp, EOS) - wp;
+       size_t len;
+
+       len = wdscan(wp, EOS) - wp;
        return (memcpy(alloc(len, ap), wp, len));
 }
 
@@ -497,7 +589,7 @@ wdscan(const char *wp, int c)
 {
        int nest = 0;
 
-       while (1)
+       while (/* CONSTCOND */ 1)
                switch (*wp++) {
                case EOS:
                        return (wp);
@@ -510,6 +602,8 @@ wdscan(const char *wp, int c)
                        wp++;
                        break;
                case COMSUB:
+               case FUNSUB:
+               case VALSUB:
                case EXPRSUB:
                        while (*wp++ != 0)
                                ;
@@ -546,88 +640,19 @@ wdscan(const char *wp, int c)
                }
 }
 
-/* return a copy of wp without any of the mark up characters and
- * with quote characters (" ' \) stripped.
- * (string is allocated from ATEMP)
+/*
+ * 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, bool keepq, bool make_magic)
+wdstrip(const char *wp, int opmode)
 {
        struct shf shf;
-       int c;
 
        shf_sopen(NULL, 32, SHF_WR | SHF_DYNAMIC, &shf);
-
-       /* problems:
-        *      `...` -> $(...)
-        *      x${foo:-"hi"} -> x${foo:-hi}
-        *      x${foo:-'hi'} -> x${foo:-hi} unless keepq
-        */
-       while (1)
-               switch (*wp++) {
-               case EOS:
-                       return (shf_sclose(&shf)); /* null terminates */
-               case ADELIM:
-               case CHAR:
-                       c = *wp++;
-                       if (make_magic && (ISMAGIC(c) || c == '[' || c == NOT ||
-                           c == '-' || c == ']' || c == '*' || c == '?'))
-                               shf_putchar(MAGIC, &shf);
-                       shf_putchar(c, &shf);
-                       break;
-               case QCHAR:
-                       c = *wp++;
-                       if (keepq && (c == '"' || c == '`' || c == '$' || c == '\\'))
-                               shf_putchar('\\', &shf);
-                       shf_putchar(c, &shf);
-                       break;
-               case COMSUB:
-                       shf_puts("$(", &shf);
-                       while (*wp != 0)
-                               shf_putchar(*wp++, &shf);
-                       shf_putchar(')', &shf);
-                       break;
-               case EXPRSUB:
-                       shf_puts("$((", &shf);
-                       while (*wp != 0)
-                               shf_putchar(*wp++, &shf);
-                       shf_puts("))", &shf);
-                       break;
-               case OQUOTE:
-                       break;
-               case CQUOTE:
-                       break;
-               case OSUBST:
-                       shf_putchar('$', &shf);
-                       if (*wp++ == '{')
-                           shf_putchar('{', &shf);
-                       while ((c = *wp++) != 0)
-                               shf_putchar(c, &shf);
-                       break;
-               case CSUBST:
-                       if (*wp++ == '}')
-                               shf_putchar('}', &shf);
-                       break;
-               case OPAT:
-                       if (make_magic) {
-                               shf_putchar(MAGIC, &shf);
-                               shf_putchar(*wp++ | 0x80, &shf);
-                       } else {
-                               shf_putchar(*wp++, &shf);
-                               shf_putchar('(', &shf);
-                       }
-                       break;
-               case SPAT:
-                       if (make_magic)
-                               shf_putchar(MAGIC, &shf);
-                       shf_putchar('|', &shf);
-                       break;
-               case CPAT:
-                       if (make_magic)
-                               shf_putchar(MAGIC, &shf);
-                       shf_putchar(')', &shf);
-                       break;
-               }
+       wdvarput(&shf, wp, 0, opmode);
+       /* shf_sclose NUL terminates */
+       return (shf_sclose(&shf));
 }
 
 static struct ioword **
@@ -636,9 +661,10 @@ iocopy(struct ioword **iow, Area *ap)
        struct ioword **ior;
        int i;
 
-       for (ior = iow; *ior++ != NULL; )
-               ;
-       ior = alloc((ior - iow + 1) * sizeof(struct ioword *), ap);
+       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;
@@ -680,8 +706,9 @@ tfree(struct op *t, Area *ap)
        }
 
        if (t->args != NULL) {
+               /*XXX we assume the caller is right */
                union mksh_ccphack cw;
-               /* XXX we assume the caller is right */
+
                cw.ro = t->args;
                for (w = cw.rw; *w != NULL; w++)
                        afree(*w, ap);
@@ -703,7 +730,8 @@ iofree(struct ioword **iow, Area *ap)
        struct ioword **iop;
        struct ioword *p;
 
-       for (iop = iow; (p = *iop++) != NULL; ) {
+       iop = iow;
+       while ((p = *iop++) != NULL) {
                if (p->name != NULL)
                        afree(p->name, ap);
                if (p->delim != NULL)
@@ -714,3 +742,403 @@ iofree(struct ioword **iow, Area *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)
+{
+       unsigned int c;
+       char *cp, *buf;
+       size_t n;
+
+       buf = alloc(sz + 16, ATEMP);
+       snptreef(buf, sz + 16, "%T", t);
+       cp = buf;
+ vist_loop:
+       if (UTFMODE && (n = utf_mbtowc(&c, cp)) != (size_t)-1) {
+               if (c == 0 || n >= sz)
+                       /* NUL or not enough free space */
+                       goto vist_out;
+               /* copy multibyte char */
+               sz -= n;
+               while (n--)
+                       *dst++ = *cp++;
+               goto vist_loop;
+       }
+       if (--sz == 0 || (c = (unsigned char)(*cp++)) == 0)
+               /* NUL or not enough free space */
+               goto vist_out;
+       if (ISCTRL(c & 0x7F)) {
+               /* C0 or C1 control character or DEL */
+               if (--sz == 0)
+                       /* not enough free space for two chars */
+                       goto vist_out;
+               *dst++ = (c & 0x80) ? '$' : '^';
+               c = UNCTRL(c & 0x7F);
+       } else if (UTFMODE && c > 0x7F) {
+               /* better not try to display broken multibyte chars */
+               /* also go easy on the Unicode: no U+FFFD here */
+               c = '?';
+       }
+       *dst++ = c;
+       goto vist_loop;
+
+ vist_out:
+       *dst = '\0';
+       afree(buf, ATEMP);
+}
+
+#ifdef DEBUG
+void
+dumpchar(struct shf *shf, int c)
+{
+       if (ISCTRL(c & 0x7F)) {
+               /* C0 or C1 control character or DEL */
+               shf_putc((c & 0x80) ? '$' : '^', shf);
+               c = UNCTRL(c & 0x7F);
+       }
+       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 FUNSUB:
+                       shf_puts("FUNSUB<", shf);
+                       goto dumpsub;
+               case VALSUB:
+                       shf_puts("VALSUB<", shf);
+                       goto dumpsub;
+               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) {
+               unsigned short type = iop->ioflag & IOTYPE;
+#define DT(x) case x: shf_puts(#x, shf); break;
+#define DB(x) if (iop->ioflag & 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", (int)iop->unit);
+               if (iop->delim) {
+                       shf_puts(",delim<", shf);
+                       dumpwdvar(shf, iop->delim);
+                       shf_putc('>', shf);
+               }
+               if (iop->name) {
+                       if (iop->ioflag & 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, j;
+       const char **w, *name;
+       struct op *t1;
+       static int nesting;
+
+       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 (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 (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 (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 (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 (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