/*
 * text.c - textual representations of syntax trees
 *
 * This file is part of zsh, the Z shell.
 *
 * Copyright (c) 1992-1997 Paul Falstad
 * All rights reserved.
 *
 * Permission is hereby granted, without written agreement and without
 * license or royalty fees, to use, copy, modify, and distribute this
 * software and to distribute modified versions of this software for any
 * purpose, provided that the above copyright notice and the following
 * two paragraphs appear in all copies of this software.
 *
 * In no event shall Paul Falstad or the Zsh Development Group be liable
 * to any party for direct, indirect, special, incidental, or consequential
 * damages arising out of the use of this software and its documentation,
 * even if Paul Falstad and the Zsh Development Group have been advised of
 * the possibility of such damage.
 *
 * Paul Falstad and the Zsh Development Group specifically disclaim any
 * warranties, including, but not limited to, the implied warranties of
 * merchantability and fitness for a particular purpose.  The software
 * provided hereunder is on an "as is" basis, and Paul Falstad and the
 * Zsh Development Group have no obligation to provide maintenance,
 * support, updates, enhancements, or modifications.
 *
 */

#include "zsh.mdh"
#include "text.pro"

static char *tptr, *tbuf, *tlim, *tpending;
static int tsiz, tindent, tnewlins, tjob;

static void
dec_tindent(void)
{
    DPUTS(tindent == 0, "attempting to decrement tindent below zero");
    if (tindent > 0)
	tindent--;
}

/*
 * Add a pair of pending strings and a newline.
 * This is used for here documents.  It will be output when
 * we have a lexically significant newline.
 *
 * This isn't that common and a multiple use on the same line is *very*
 * uncommon; we don't try to optimise it.
 *
 * This is not used for job text; there we bear the inaccuracy
 * of turning this into a here-string.
 */
static void
taddpending(char *str1, char *str2)
{
    int len = strlen(str1) + strlen(str2) + 1;

    /*
     * We don't strip newlines from here-documents converted
     * to here-strings, so no munging is required except to
     * add a newline after the here-document terminator.
     * However, because the job text doesn't automatically
     * have a newline right at the end, we handle that
     * specially.
     */
    if (tpending) {
	int oldlen = strlen(tpending);
	tpending = realloc(tpending, len + oldlen);
	sprintf(tpending + oldlen, "%s%s", str1, str2);
    } else {
	tpending = (char *)zalloc(len);
	sprintf(tpending, "%s%s", str1, str2);
    }
}

/* Output the pending string where appropriate */

static void
tdopending(void)
{
    if (tpending) {
	taddchr('\n');
	taddstr(tpending);
	zsfree(tpending);
	tpending = NULL;
    }
}

/* add a character to the text buffer */

/**/
static void
taddchr(int c)
{
    *tptr++ = c;
    if (tptr == tlim) {
	if (!tbuf) {
	    tptr--;
	    return;
	}
	tbuf = realloc(tbuf, tsiz *= 2);
	tlim = tbuf + tsiz;
	tptr = tbuf + tsiz / 2;
    }
}

/* add a string to the text buffer */

/**/
static void
taddstr(char *s)
{
    int sl = strlen(s);
    char c;

    while (tptr + sl >= tlim) {
	int x = tptr - tbuf;

	if (!tbuf)
	    return;
	tbuf = realloc(tbuf, tsiz *= 2);
	tlim = tbuf + tsiz;
	tptr = tbuf + x;
    }
    if (tnewlins) {
	memcpy(tptr, s, sl);
	tptr += sl;
    } else
	while ((c = *s++))
	    *tptr++ = (c == '\n' ? ' ' : c);
}

/**/
static void
taddlist(Estate state, int num)
{
    if (num) {
	while (num--) {
	    taddstr(ecgetstr(state, EC_NODUP, NULL));
	    taddchr(' ');
	}
	tptr--;
    }
}

/* add a newline, or something equivalent, to the text buffer */

/**/
static void
taddnl(int no_semicolon)
{
    int t0;

    if (tnewlins) {
	tdopending();
	taddchr('\n');
	for (t0 = 0; t0 != tindent; t0++)
	    taddchr('\t');
    } else if (no_semicolon) {
	taddstr(" ");
    } else {
	taddstr("; ");
    }
}

/* get a permanent textual representation of n */

/**/
mod_export char *
getpermtext(Eprog prog, Wordcode c, int start_indent)
{
    struct estate s;

    if (!c)
	c = prog->prog;

    useeprog(prog);		/* mark as used */

    s.prog = prog;
    s.pc = c;
    s.strs = prog->strs;

    tindent = start_indent;
    tnewlins = 1;
    tbuf = (char *)zalloc(tsiz = 32);
    tptr = tbuf;
    tlim = tbuf + tsiz;
    tjob = 0;
    if (prog->len)
	gettext2(&s);
    *tptr = '\0';
    freeeprog(prog);		/* mark as unused */
    untokenize(tbuf);
    return tbuf;
}

/* get a representation of n in a job text buffer */

/**/
char *
getjobtext(Eprog prog, Wordcode c)
{
    static char jbuf[JOBTEXTSIZE];

    struct estate s;

    if (!c)
	c = prog->prog;

    useeprog(prog);		/* mark as used */
    s.prog = prog;
    s.pc = c;
    s.strs = prog->strs;

    tindent = 0;
    tnewlins = 0;
    tbuf = NULL;
    tptr = jbuf;
    tlim = tptr + JOBTEXTSIZE - 1;
    tjob = 1;
    gettext2(&s);
    *tptr = '\0';
    freeeprog(prog);		/* mark as unused */
    untokenize(jbuf);
    return jbuf;
}

/*
 * gettext2() shows one way to walk through the word code without
 * recursion. We start by reading a word code and executing the
 * action for it. Some codes have sub-structures (like, e.g. WC_FOR)
 * and require something to be done after the sub-structure has been
 * handled. For these codes a tstack structure which describes what
 * has to be done is pushed onto a stack. Codes without sub-structures
 * arrange for the next structure being taken from the stack so that
 * the action for it is executed instead of the one for the next
 * word code. If the stack is empty at this point, we have handled
 * the whole structure we were called for.
 */

typedef struct tstack *Tstack;

struct tstack {
    Tstack prev;
    wordcode code;
    int pop;
    union {
	struct {
	    LinkList list;
	} _redir;
	struct {
	    char *strs;
	    Wordcode end;
	    int nargs;
	} _funcdef;
	struct {
	    Wordcode end;
	} _case;
	struct {
	    int cond;
	    Wordcode end;
	} _if;
	struct {
	    int par;
	} _cond;
	struct {
	    Wordcode end;
	} _subsh;
    } u;
};

static Tstack tstack, tfree;

static Tstack
tpush(wordcode code, int pop)
{
    Tstack s;

    if ((s = tfree))
	tfree = s->prev;
    else
	s = (Tstack) zalloc(sizeof(*s));

    s->prev = tstack;
    tstack = s;
    s->code = code;
    s->pop = pop;

    return s;
}

/**/
static void
gettext2(Estate state)
{
    Tstack s, n;
    int stack = 0;
    wordcode code;

    while (1) {
	if (stack) {
	    if (!(s = tstack))
		break;
	    if (s->pop) {
		tstack = s->prev;
		s->prev = tfree;
		tfree = s;
	    }
	    code = s->code;
	    stack = 0;
	} else {
	    s = NULL;
	    code = *state->pc++;
	}
	switch (wc_code(code)) {
	case WC_LIST:
	    if (!s) {
		s = tpush(code, (WC_LIST_TYPE(code) & Z_END));
		stack = 0;
	    } else {
		if (WC_LIST_TYPE(code) & Z_ASYNC) {
		    taddstr(" &");
		    if (WC_LIST_TYPE(code) & Z_DISOWN)
			taddstr("|");
		}
		if (!(stack = (WC_LIST_TYPE(code) & Z_END))) {
		    if (tnewlins)
			taddnl(0);
		    else
			taddstr((WC_LIST_TYPE(code) & Z_ASYNC) ? " " : "; ");
		    s->code = *state->pc++;
		    s->pop = (WC_LIST_TYPE(s->code) & Z_END);
		}
	    }
	    if (!stack && (WC_LIST_TYPE(s->code) & Z_SIMPLE))
		state->pc++;
	    break;
	case WC_SUBLIST:
	    if (!s) {
                if (!(WC_SUBLIST_FLAGS(code) & WC_SUBLIST_SIMPLE) &&
                    wc_code(*state->pc) != WC_PIPE)
                    stack = -1;
		if (WC_SUBLIST_FLAGS(code) & WC_SUBLIST_NOT)
		    taddstr(stack ? "!" : "! ");
		if (WC_SUBLIST_FLAGS(code) & WC_SUBLIST_COPROC)
		    taddstr(stack ? "coproc" : "coproc ");
		s = tpush(code, (WC_SUBLIST_TYPE(code) == WC_SUBLIST_END));
	    } else {
		if (!(stack = (WC_SUBLIST_TYPE(code) == WC_SUBLIST_END))) {
		    taddstr((WC_SUBLIST_TYPE(code) == WC_SUBLIST_OR) ?
			    " || " : " && ");
		    s->code = *state->pc++;
		    s->pop = (WC_SUBLIST_TYPE(s->code) == WC_SUBLIST_END);
		    if (WC_SUBLIST_FLAGS(s->code) & WC_SUBLIST_NOT)
			taddstr("! ");
		    if (WC_SUBLIST_FLAGS(s->code) & WC_SUBLIST_COPROC)
			taddstr("coproc ");
		}
	    }
	    if (stack < 1 && (WC_SUBLIST_FLAGS(s->code) & WC_SUBLIST_SIMPLE))
		state->pc++;
	    break;
	case WC_PIPE:
	    if (!s) {
		tpush(code, (WC_PIPE_TYPE(code) == WC_PIPE_END));
		if (WC_PIPE_TYPE(code) == WC_PIPE_MID)
		    state->pc++;
	    } else {
		if (!(stack = (WC_PIPE_TYPE(code) == WC_PIPE_END))) {
		    taddstr(" | ");
		    s->code = *state->pc++;
		    if (!(s->pop = (WC_PIPE_TYPE(s->code) == WC_PIPE_END)))
			state->pc++;
		}
	    }
	    break;
	case WC_REDIR:
	    if (!s) {
		state->pc--;
		n = tpush(code, 1);
		n->u._redir.list = ecgetredirs(state);
	    } else {
		getredirs(s->u._redir.list);
		stack = 1;
	    }
	    break;
	case WC_ASSIGN:
	    taddstr(ecgetstr(state, EC_NODUP, NULL));
	    if (WC_ASSIGN_TYPE2(code) == WC_ASSIGN_INC) taddchr('+');
	    taddchr('=');
	    if (WC_ASSIGN_TYPE(code) == WC_ASSIGN_ARRAY) {
		taddchr('(');
		taddlist(state, WC_ASSIGN_NUM(code));
		taddstr(") ");
	    } else {
		taddstr(ecgetstr(state, EC_NODUP, NULL));
		taddchr(' ');
	    }
	    break;
	case WC_SIMPLE:
	    taddlist(state, WC_SIMPLE_ARGC(code));
	    stack = 1;
	    break;
	case WC_SUBSH:
	    if (!s) {
		taddstr("(");
		tindent++;
		taddnl(1);
		n = tpush(code, 1);
		n->u._subsh.end = state->pc + WC_SUBSH_SKIP(code);
		/* skip word only use for try/always */
		state->pc++;
	    } else {
		state->pc = s->u._subsh.end;
		dec_tindent();
		/* semicolon is optional here but more standard */
		taddnl(0);
		taddstr(")");
		stack = 1;
	    }
	    break;
	case WC_CURSH:
	    if (!s) {
		taddstr("{");
		tindent++;
		taddnl(1);
		n = tpush(code, 1);
		n->u._subsh.end = state->pc + WC_CURSH_SKIP(code);
		/* skip word only use for try/always */
		state->pc++;
	    } else {
		state->pc = s->u._subsh.end;
		dec_tindent();
		/* semicolon is optional here but more standard */
		taddnl(0);
		taddstr("}");
		stack = 1;
	    }
	    break;
	case WC_TIMED:
	    if (!s) {
		taddstr("time");
		if (WC_TIMED_TYPE(code) == WC_TIMED_PIPE) {
		    taddchr(' ');
		    tindent++;
		    tpush(code, 1);
		} else
		    stack = 1;
	    } else {
		dec_tindent();
		stack = 1;
	    }
	    break;
	case WC_FUNCDEF:
	    if (!s) {
		Wordcode p = state->pc;
		Wordcode end = p + WC_FUNCDEF_SKIP(code);
		int nargs = *state->pc++;

		taddlist(state, nargs);
		if (nargs)
		    taddstr(" ");
		if (tjob) {
		    taddstr("() { ... }");
		    state->pc = end;
		    if (!nargs) {
			/*
			 * Unnamed fucntion.
			 * We're not going to pull any arguments off
			 * later, so skip them now...
			 */
			state->pc += *end;
		    }
		    stack = 1;
		} else {
		    taddstr("() {");
		    tindent++;
		    taddnl(1);
		    n = tpush(code, 1);
		    n->u._funcdef.strs = state->strs;
		    n->u._funcdef.end = end;
		    n->u._funcdef.nargs = nargs;
		    state->strs += *state->pc;
		    state->pc += 3;
		}
	    } else {
		state->strs = s->u._funcdef.strs;
		state->pc = s->u._funcdef.end;
		dec_tindent();
		taddnl(0);
		taddstr("}");
		if (s->u._funcdef.nargs == 0) {
		    /* Unnamed function with post-arguments */
		    int nargs;
		    s->u._funcdef.end += *state->pc++;
		    nargs = *state->pc++;
		    if (nargs) {
			taddstr(" ");
			taddlist(state, nargs);
		    }
		    state->pc = s->u._funcdef.end;
		}
		stack = 1;
	    }
	    break;
	case WC_FOR:
	    if (!s) {
		taddstr("for ");
		if (WC_FOR_TYPE(code) == WC_FOR_COND) {
		    taddstr("((");
		    taddstr(ecgetstr(state, EC_NODUP, NULL));
		    taddstr("; ");
		    taddstr(ecgetstr(state, EC_NODUP, NULL));
		    taddstr("; ");
		    taddstr(ecgetstr(state, EC_NODUP, NULL));
		    taddstr(")) do");
		} else {
		    taddlist(state, *state->pc++);
		    if (WC_FOR_TYPE(code) == WC_FOR_LIST) {
			taddstr(" in ");
			taddlist(state, *state->pc++);
		    }
		    taddnl(0);
		    taddstr("do");
		}
		tindent++;
		taddnl(0);
		tpush(code, 1);
	    } else {
		dec_tindent();
		taddnl(0);
		taddstr("done");
		stack = 1;
	    }
	    break;
	case WC_SELECT:
	    if (!s) {
		taddstr("select ");
		taddstr(ecgetstr(state, EC_NODUP, NULL));
		if (WC_SELECT_TYPE(code) == WC_SELECT_LIST) {
		    taddstr(" in ");
		    taddlist(state, *state->pc++);
		}
		tindent++;
		taddnl(0);
		tpush(code, 1);
	    } else {
		dec_tindent();
		taddnl(0);
		taddstr("done");
		stack = 1;
	    }
	    break;
	case WC_WHILE:
	    if (!s) {
		taddstr(WC_WHILE_TYPE(code) == WC_WHILE_UNTIL ?
			"until " : "while ");
		tindent++;
		tpush(code, 0);
	    } else if (!s->pop) {
		dec_tindent();
		taddnl(0);
		taddstr("do");
		tindent++;
		taddnl(0);
		s->pop = 1;
	    } else {
		dec_tindent();
		taddnl(0);
		taddstr("done");
		stack = 1;
	    }
	    break;
	case WC_REPEAT:
	    if (!s) {
		taddstr("repeat ");
		taddstr(ecgetstr(state, EC_NODUP, NULL));
		taddnl(0);
		taddstr("do");
		tindent++;
		taddnl(0);
		tpush(code, 1);
	    } else {
		dec_tindent();
		taddnl(0);
		taddstr("done");
		stack = 1;
	    }
	    break;
	case WC_CASE:
	    if (!s) {
		Wordcode end = state->pc + WC_CASE_SKIP(code);

		taddstr("case ");
		taddstr(ecgetstr(state, EC_NODUP, NULL));
		taddstr(" in");

		if (state->pc >= end) {
		    if (tnewlins)
			taddnl(0);
		    else
			taddchr(' ');
		    taddstr("esac");
		    stack = 1;
		} else {
		    tindent++;
		    if (tnewlins)
			taddnl(0);
		    else
			taddchr(' ');
		    taddstr("(");
		    code = *state->pc++;
		    taddstr(ecgetstr(state, EC_NODUP, NULL));
		    state->pc++;
		    taddstr(") ");
		    tindent++;
		    n = tpush(code, 0);
		    n->u._case.end = end;
		    n->pop = (state->pc - 2 + WC_CASE_SKIP(code) >= end);
		}
	    } else if (state->pc < s->u._case.end) {
		dec_tindent();
		switch (WC_CASE_TYPE(code)) {
		case WC_CASE_OR:
		    taddstr(" ;;");
		    break;

		case WC_CASE_AND:
		    taddstr(";&");
		    break;

		default:
		    taddstr(";|");
		    break;
		}
		if (tnewlins)
		    taddnl(0);
		else
		    taddchr(' ');
		taddstr("(");
		code = *state->pc++;
		taddstr(ecgetstr(state, EC_NODUP, NULL));
		state->pc++;
		taddstr(") ");
		tindent++;
		s->code = code;
		s->pop = ((state->pc - 2 + WC_CASE_SKIP(code)) >=
			  s->u._case.end);
	    } else {
		dec_tindent();
		switch (WC_CASE_TYPE(code)) {
		case WC_CASE_OR:
		    taddstr(" ;;");
		    break;

		case WC_CASE_AND:
		    taddstr(";&");
		    break;

		default:
		    taddstr(";|");
		    break;
		}
		dec_tindent();
		if (tnewlins)
		    taddnl(0);
		else
		    taddchr(' ');
		taddstr("esac");
		stack = 1;
	    }
	    break;
	case WC_IF:
	    if (!s) {
		Wordcode end = state->pc + WC_IF_SKIP(code);

		taddstr("if ");
		tindent++;
		state->pc++;

		n = tpush(code, 0);
		n->u._if.end = end;
		n->u._if.cond = 1;
	    } else if (s->pop) {
		stack = 1;
	    } else if (s->u._if.cond) {
		dec_tindent();
		taddnl(0);
		taddstr("then");
		tindent++;
		taddnl(0);
		s->u._if.cond = 0;
	    } else if (state->pc < s->u._if.end) {
		dec_tindent();
		taddnl(0);
		code = *state->pc++;
		if (WC_IF_TYPE(code) == WC_IF_ELIF) {
		    taddstr("elif ");
		    tindent++;
		    s->u._if.cond = 1;
		} else {
		    taddstr("else");
		    tindent++;
		    taddnl(0);
		}
	    } else {
		s->pop = 1;
		dec_tindent();
		taddnl(0);
		taddstr("fi");
		stack = 1;
	    }
	    break;
	case WC_COND:
	    {
		static char *c1[] = {
		    "=", "!=", "<", ">", "-nt", "-ot", "-ef", "-eq",
		    "-ne", "-lt", "-gt", "-le", "-ge", "=~"
		};

		int ctype;

		if (!s) {
		    taddstr("[[ ");
		    n = tpush(code, 1);
		    n->u._cond.par = 2;
		} else if (s->u._cond.par == 2) {
		    taddstr(" ]]");
		    stack = 1;
		    break;
		} else if (s->u._cond.par == 1) {
		    taddstr(" )");
		    stack = 1;
		    break;
		} else if (WC_COND_TYPE(s->code) == COND_AND) {
		    taddstr(" && ");
		    code = *state->pc++;
		    if (WC_COND_TYPE(code) == COND_OR) {
			taddstr("( ");
			n = tpush(code, 1);
			n->u._cond.par = 1;
		    }
		} else if (WC_COND_TYPE(s->code) == COND_OR) {
		    taddstr(" || ");
		    code = *state->pc++;
		    if (WC_COND_TYPE(code) == COND_AND) {
			taddstr("( ");
			n = tpush(code, 1);
			n->u._cond.par = 1;
		    }
		}
		while (!stack) {
		    switch ((ctype = WC_COND_TYPE(code))) {
		    case COND_NOT:
			taddstr("! ");
			code = *state->pc++;
			if (WC_COND_TYPE(code) <= COND_OR) {
			    taddstr("( ");
			    n = tpush(code, 1);
			    n->u._cond.par = 1;
			}
			break;
		    case COND_AND:
			n = tpush(code, 1);
			n->u._cond.par = 0;
			code = *state->pc++;
			if (WC_COND_TYPE(code) == COND_OR) {
			    taddstr("( ");
			    n = tpush(code, 1);
			    n->u._cond.par = 1;
			}
			break;
		    case COND_OR:
			n = tpush(code, 1);
			n->u._cond.par = 0;
			code = *state->pc++;
			if (WC_COND_TYPE(code) == COND_AND) {
			    taddstr("( ");
			    n = tpush(code, 1);
			    n->u._cond.par = 1;
			}
			break;
		    case COND_MOD:
			taddstr(ecgetstr(state, EC_NODUP, NULL));
			taddchr(' ');
			taddlist(state, WC_COND_SKIP(code));
			stack = 1;
			break;
		    case COND_MODI:
			{
			    char *name = ecgetstr(state, EC_NODUP, NULL);

			    taddstr(ecgetstr(state, EC_NODUP, NULL));
			    taddchr(' ');
			    taddstr(name);
			    taddchr(' ');
			    taddstr(ecgetstr(state, EC_NODUP, NULL));
			    stack = 1;
			}
			break;
		    default:
			if (ctype < COND_MOD) {
			    /* Binary test: `a = b' etc. */
			    taddstr(ecgetstr(state, EC_NODUP, NULL));
			    taddstr(" ");
			    taddstr(c1[ctype - COND_STREQ]);
			    taddstr(" ");
			    taddstr(ecgetstr(state, EC_NODUP, NULL));
			    if (ctype == COND_STREQ ||
				ctype == COND_STRNEQ)
				state->pc++;
			} else {
			    /* Unary test: `-f foo' etc. */ 
			    char c2[4];

			    c2[0] = '-';
			    c2[1] = ctype;
			    c2[2] = ' ';
			    c2[3] = '\0';
			    taddstr(c2);
			    taddstr(ecgetstr(state, EC_NODUP, NULL));
			}
			stack = 1;
			break;
		    }
		}
	    }
	    break;
	case WC_ARITH:
	    taddstr("((");
	    taddstr(ecgetstr(state, EC_NODUP, NULL));
	    taddstr("))");
	    stack = 1;
	    break;
	case WC_TRY:
	    if (!s) {
		taddstr("{");
		tindent++;
		taddnl(0);
		n = tpush(code, 0);
		state->pc++;
		/* this is the end of the try block alone */
		n->u._subsh.end = state->pc + WC_CURSH_SKIP(state->pc[-1]);
	    } else if (!s->pop) {
		state->pc = s->u._subsh.end;
		dec_tindent();
		taddnl(0);
		taddstr("} always {");
		tindent++;
		taddnl(0);
		s->pop = 1;
	    } else {
		dec_tindent();
		taddnl(0);
		taddstr("}");
		stack = 1;
	    }
	    break;
	case WC_END:
	    stack = 1;
	    break;
	default:
	    DPUTS(1, "unknown word code in gettext2()");
	    return;
	}
    }
    tdopending();
}

/**/
void
getredirs(LinkList redirs)
{
    LinkNode n;
    static char *fstr[] =
    {
	">", ">|", ">>", ">>|", "&>", "&>|", "&>>", "&>>|", "<>", "<",
	"<<", "<<-", "<<<", "<&", ">&", NULL /* >&- */, "<", ">"
    };
    taddchr(' ');
    for (n = firstnode(redirs); n; incnode(n)) {
	Redir f = (Redir) getdata(n);

	switch (f->type) {
	case REDIR_WRITE:
	case REDIR_WRITENOW:
	case REDIR_APP:
	case REDIR_APPNOW:
	case REDIR_ERRWRITE:
	case REDIR_ERRWRITENOW:
	case REDIR_ERRAPP:
	case REDIR_ERRAPPNOW:
	case REDIR_READ:
	case REDIR_READWRITE:
	case REDIR_HERESTR:
	case REDIR_MERGEIN:
	case REDIR_MERGEOUT:
	case REDIR_INPIPE:
	case REDIR_OUTPIPE:
	    if (f->varid) {
		taddchr('{');
		taddstr(f->varid);
		taddchr('}');
	    } else if (f->fd1 != (IS_READFD(f->type) ? 0 : 1))
		taddchr('0' + f->fd1);
	    if (f->type == REDIR_HERESTR &&
		(f->flags & REDIRF_FROM_HEREDOC)) {
		if (tnewlins) {
		    /*
		     * Strings that came from here-documents are converted
		     * to here strings without quotation, so convert them
		     * back.
		     */
		    taddstr(fstr[REDIR_HEREDOC]);
		    taddstr(f->here_terminator);
		    taddpending(f->name, f->munged_here_terminator);
		} else {
		    int fnamelen, sav;
		    taddstr(fstr[REDIR_HERESTR]);
		    /*
		     * Just a quick and dirty representation.
		     * Remove a terminating newline, if any.
		     */
		    fnamelen = strlen(f->name);
		    if (fnamelen > 0 && f->name[fnamelen-1] == '\n') {
			sav = 1;
			f->name[fnamelen-1] = '\0';
		    } else
			sav = 0;
		    /*
		     * Strings that came from here-documents are converted
		     * to here strings without quotation, so add that
		     * now.  If tokens are present we need to do double quoting.
		     */
		    if (!has_token(f->name)) {
			taddchr('\'');
			taddstr(quotestring(f->name, NULL, QT_SINGLE));
			taddchr('\'');
		    } else {
			taddchr('"');
			taddstr(quotestring(f->name, NULL, QT_DOUBLE));
			taddchr('"');
		    }
		    if (sav)
			f->name[fnamelen-1] = '\n';
		}
	    } else {
		taddstr(fstr[f->type]);
		if (f->type != REDIR_MERGEIN && f->type != REDIR_MERGEOUT)
		    taddchr(' ');
		taddstr(f->name);
	    }
	    taddchr(' ');
	    break;
#ifdef DEBUG
	case REDIR_CLOSE:
	    DPUTS(1, "BUG: CLOSE in getredirs()");
	    taddchr(f->fd1 + '0');
	    taddstr(">&- ");
	    break;
	default:
	    DPUTS(1, "BUG: unknown redirection in getredirs()");
#endif
	}
    }
    tptr--;
}
