Add mksh from CVS 2010/08/24 as system/core/mksh module

Both shells (ash from system/core/sh, and mksh) are built by
default but only the one where $(TARGET_SHELL) is set to is
actually installed (the shell and the mkshrc configuration
file are tagged shell_mksh for this to work).

Signed-off-by: Thorsten Glaser <tg@mirbsd.org>
diff --git a/mksh/src/lex.c b/mksh/src/lex.c
new file mode 100644
index 0000000..d0219e7
--- /dev/null
+++ b/mksh/src/lex.c
@@ -0,0 +1,1782 @@
+/*	$OpenBSD: lex.c,v 1.44 2008/07/03 17:52:08 otto Exp $	*/
+
+/*-
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ *	Thorsten Glaser <tg@mirbsd.org>
+ *
+ * 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/lex.c,v 1.118 2010/07/25 11:35:41 tg Exp $");
+
+/*
+ * states while lexing word
+ */
+#define SBASE		0	/* outside any lexical constructs */
+#define SWORD		1	/* implicit quoting for substitute() */
+#define SLETPAREN	2	/* inside (( )), implicit quoting */
+#define SSQUOTE		3	/* inside '' */
+#define SDQUOTE		4	/* inside "" */
+#define SEQUOTE		5	/* inside $'' */
+#define SBRACE		6	/* inside ${} */
+#define SQBRACE		7	/* inside "${}" */
+#define SCSPAREN	8	/* inside $() */
+#define SBQUOTE		9	/* inside `` */
+#define SASPAREN	10	/* inside $(( )) */
+#define SHEREDELIM	11	/* parsing <<,<<- delimiter */
+#define SHEREDQUOTE	12	/* parsing " in <<,<<- delimiter */
+#define SPATTERN	13	/* parsing *(...|...) pattern (*+?@!) */
+#define STBRACE		14	/* parsing ${...[#%]...} */
+#define SLETARRAY	15	/* inside =( ), just copy */
+#define SADELIM		16	/* like SBASE, looking for delimiter */
+#define SHERESTRING	17	/* parsing <<< string */
+
+/* Structure to keep track of the lexing state and the various pieces of info
+ * needed for each particular state. */
+typedef struct lex_state Lex_state;
+struct lex_state {
+	int ls_state;
+	union {
+		/* $(...) */
+		struct scsparen_info {
+			int nparen;	/* count open parenthesis */
+			int csstate;	/* XXX remove */
+#define ls_scsparen ls_info.u_scsparen
+		} u_scsparen;
+
+		/* $((...)) */
+		struct sasparen_info {
+			int nparen;	/* count open parenthesis */
+			int start;	/* marks start of $(( in output str */
+#define ls_sasparen ls_info.u_sasparen
+		} u_sasparen;
+
+		/* ((...)) */
+		struct sletparen_info {
+			int nparen;	/* count open parenthesis */
+#define ls_sletparen ls_info.u_sletparen
+		} u_sletparen;
+
+		/* `...` */
+		struct sbquote_info {
+			int indquotes;	/* true if in double quotes: "`...`" */
+#define ls_sbquote ls_info.u_sbquote
+		} u_sbquote;
+
+#ifndef MKSH_SMALL
+		/* =(...) */
+		struct sletarray_info {
+			int nparen;	/* count open parentheses */
+#define ls_sletarray ls_info.u_sletarray
+		} u_sletarray;
+#endif
+
+		/* ADELIM */
+		struct sadelim_info {
+			unsigned char nparen;	/* count open parentheses */
+#define SADELIM_BASH	0
+#define SADELIM_MAKE	1
+			unsigned char style;
+			unsigned char delimiter;
+			unsigned char num;
+			unsigned char flags;	/* ofs. into sadelim_flags[] */
+#define ls_sadelim ls_info.u_sadelim
+		} u_sadelim;
+
+		/* $'...' */
+		struct sequote_info {
+			bool got_NUL;	/* ignore rest of string */
+#define ls_sequote ls_info.u_sequote
+		} u_sequote;
+
+		Lex_state *base;	/* used to point to next state block */
+	} ls_info;
+};
+
+typedef struct {
+	Lex_state *base;
+	Lex_state *end;
+} State_info;
+
+static void readhere(struct ioword *);
+static int getsc__(void);
+static void getsc_line(Source *);
+static int getsc_bn(void);
+static int s_get(void);
+static void s_put(int);
+static char *get_brace_var(XString *, char *);
+static int arraysub(char **);
+static const char *ungetsc(int);
+static void gethere(bool);
+static Lex_state *push_state_(State_info *, Lex_state *);
+static Lex_state *pop_state_(State_info *, Lex_state *);
+
+static int dopprompt(const char *, int, bool);
+
+static int backslash_skip;
+static int ignore_backslash_newline;
+
+/* optimised getsc_bn() */
+#define _getsc()	(*source->str != '\0' && *source->str != '\\' \
+			 && !backslash_skip && !(source->flags & SF_FIRST) \
+			 ? *source->str++ : getsc_bn())
+/* optimised getsc__() */
+#define	_getsc_()	((*source->str != '\0') && !(source->flags & SF_FIRST) \
+			 ? *source->str++ : getsc__())
+
+#ifdef MKSH_SMALL
+static int getsc(void);
+static int getsc_(void);
+
+static int
+getsc(void)
+{
+	return (_getsc());
+}
+
+static int
+getsc_(void)
+{
+	return (_getsc_());
+}
+#else
+/* !MKSH_SMALL: use them inline */
+#define getsc()		_getsc()
+#define getsc_()	_getsc_()
+#endif
+
+#define STATE_BSIZE	32
+
+#define PUSH_STATE(s)	do {					\
+	if (++statep == state_info.end)				\
+		statep = push_state_(&state_info, statep);	\
+	state = statep->ls_state = (s);				\
+} while (0)
+
+#define POP_STATE()	do {					\
+	if (--statep == state_info.base)			\
+		statep = pop_state_(&state_info, statep);	\
+	state = statep->ls_state;				\
+} while (0)
+
+/**
+ * Lexical analyser
+ *
+ * tokens are not regular expressions, they are LL(1).
+ * for example, "${var:-${PWD}}", and "$(size $(whence ksh))".
+ * hence the state stack.
+ */
+
+int
+yylex(int cf)
+{
+	Lex_state states[STATE_BSIZE], *statep, *s2, *base;
+	State_info state_info;
+	int c, c2, state;
+	XString ws;		/* expandable output word */
+	char *wp;		/* output word pointer */
+	char *sp, *dp;
+
+ Again:
+	states[0].ls_state = -1;
+	states[0].ls_info.base = NULL;
+	statep = &states[1];
+	state_info.base = states;
+	state_info.end = &state_info.base[STATE_BSIZE];
+
+	Xinit(ws, wp, 64, ATEMP);
+
+	backslash_skip = 0;
+	ignore_backslash_newline = 0;
+
+	if (cf&ONEWORD)
+		state = SWORD;
+	else if (cf&LETEXPR) {
+		/* enclose arguments in (double) quotes */
+		*wp++ = OQUOTE;
+		state = SLETPAREN;
+		statep->ls_sletparen.nparen = 0;
+#ifndef MKSH_SMALL
+	} else if (cf&LETARRAY) {
+		state = SLETARRAY;
+		statep->ls_sletarray.nparen = 0;
+#endif
+	} else {		/* normal lexing */
+		state = (cf & HEREDELIM) ? SHEREDELIM : SBASE;
+		while ((c = getsc()) == ' ' || c == '\t')
+			;
+		if (c == '#') {
+			ignore_backslash_newline++;
+			while ((c = getsc()) != '\0' && c != '\n')
+				;
+			ignore_backslash_newline--;
+		}
+		ungetsc(c);
+	}
+	if (source->flags & SF_ALIAS) {	/* trailing ' ' in alias definition */
+		source->flags &= ~SF_ALIAS;
+		cf |= ALIAS;
+	}
+
+	/* Initial state: one of SBASE SHEREDELIM SWORD SASPAREN */
+	statep->ls_state = state;
+
+	/* check for here string */
+	if (state == SHEREDELIM) {
+		c = getsc();
+		if (c == '<') {
+			state = SHERESTRING;
+			while ((c = getsc()) == ' ' || c == '\t')
+				;
+			ungetsc(c);
+			c = '<';
+			goto accept_nonword;
+		}
+		ungetsc(c);
+	}
+
+	/* collect non-special or quoted characters to form word */
+	while (!((c = getsc()) == 0 ||
+	    ((state == SBASE || state == SHEREDELIM || state == SHERESTRING) &&
+	    ctype(c, C_LEX1)))) {
+ accept_nonword:
+		Xcheck(ws, wp);
+		switch (state) {
+		case SADELIM:
+			if (c == '(')
+				statep->ls_sadelim.nparen++;
+			else if (c == ')')
+				statep->ls_sadelim.nparen--;
+			else if (statep->ls_sadelim.nparen == 0 &&
+			    (c == /*{*/ '}' || c == statep->ls_sadelim.delimiter)) {
+				*wp++ = ADELIM;
+				*wp++ = c;
+				if (c == /*{*/ '}' || --statep->ls_sadelim.num == 0)
+					POP_STATE();
+				if (c == /*{*/ '}')
+					POP_STATE();
+				break;
+			}
+			/* FALLTHROUGH */
+		case SBASE:
+			if (c == '[' && (cf & (VARASN|ARRAYVAR))) {
+				*wp = EOS;	/* temporary */
+				if (is_wdvarname(Xstring(ws, wp), false)) {
+					char *p, *tmp;
+
+					if (arraysub(&tmp)) {
+						*wp++ = CHAR;
+						*wp++ = c;
+						for (p = tmp; *p; ) {
+							Xcheck(ws, wp);
+							*wp++ = CHAR;
+							*wp++ = *p++;
+						}
+						afree(tmp, ATEMP);
+						break;
+					} else {
+						Source *s;
+
+						s = pushs(SREREAD,
+						    source->areap);
+						s->start = s->str =
+						    s->u.freeme = tmp;
+						s->next = source;
+						source = s;
+					}
+				}
+				*wp++ = CHAR;
+				*wp++ = c;
+				break;
+			}
+			/* FALLTHROUGH */
+ Sbase1:		/* includes *(...|...) pattern (*+?@!) */
+			if (c == '*' || c == '@' || c == '+' || c == '?' ||
+			    c == '!') {
+				c2 = getsc();
+				if (c2 == '(' /*)*/ ) {
+					*wp++ = OPAT;
+					*wp++ = c;
+					PUSH_STATE(SPATTERN);
+					break;
+				}
+				ungetsc(c2);
+			}
+			/* FALLTHROUGH */
+ Sbase2:		/* doesn't include *(...|...) pattern (*+?@!) */
+			switch (c) {
+			case '\\':
+ getsc_qchar:
+				if ((c = getsc())) {
+					/* trailing \ is lost */
+					*wp++ = QCHAR;
+					*wp++ = c;
+				}
+				break;
+			case '\'':
+ open_ssquote:
+				*wp++ = OQUOTE;
+				ignore_backslash_newline++;
+				PUSH_STATE(SSQUOTE);
+				break;
+			case '"':
+ open_sdquote:
+				*wp++ = OQUOTE;
+				PUSH_STATE(SDQUOTE);
+				break;
+			default:
+				goto Subst;
+			}
+			break;
+
+ Subst:
+			switch (c) {
+			case '\\':
+				c = getsc();
+				switch (c) {
+				case '"':
+					if ((cf & HEREDOC))
+						goto heredocquote;
+					/* FALLTHROUGH */
+				case '\\':
+				case '$': case '`':
+ store_qchar:
+					*wp++ = QCHAR;
+					*wp++ = c;
+					break;
+				default:
+ heredocquote:
+					Xcheck(ws, wp);
+					if (c) {
+						/* trailing \ is lost */
+						*wp++ = CHAR;
+						*wp++ = '\\';
+						*wp++ = CHAR;
+						*wp++ = c;
+					}
+					break;
+				}
+				break;
+			case '$':
+ subst_dollar:
+				c = getsc();
+				if (c == '(') /*)*/ {
+					c = getsc();
+					if (c == '(') /*)*/ {
+						PUSH_STATE(SASPAREN);
+						statep->ls_sasparen.nparen = 2;
+						statep->ls_sasparen.start =
+						    Xsavepos(ws, wp);
+						*wp++ = EXPRSUB;
+					} else {
+						ungetsc(c);
+						PUSH_STATE(SCSPAREN);
+						statep->ls_scsparen.nparen = 1;
+						statep->ls_scsparen.csstate = 0;
+						*wp++ = COMSUB;
+					}
+				} else if (c == '{') /*}*/ {
+					*wp++ = OSUBST;
+					*wp++ = '{'; /*}*/
+					wp = get_brace_var(&ws, wp);
+					c = getsc();
+					/* allow :# and :% (ksh88 compat) */
+					if (c == ':') {
+						*wp++ = CHAR;
+						*wp++ = c;
+						c = getsc();
+						if (c == ':') {
+							*wp++ = CHAR;
+							*wp++ = '0';
+							*wp++ = ADELIM;
+							*wp++ = ':';
+							PUSH_STATE(SBRACE);
+							PUSH_STATE(SADELIM);
+							statep->ls_sadelim.style = SADELIM_BASH;
+							statep->ls_sadelim.delimiter = ':';
+							statep->ls_sadelim.num = 1;
+							statep->ls_sadelim.nparen = 0;
+							break;
+						} else if (ksh_isdigit(c) ||
+						    c == '('/*)*/ || c == ' ' ||
+						    c == '$' /* XXX what else? */) {
+							/* substring subst. */
+							if (c != ' ') {
+								*wp++ = CHAR;
+								*wp++ = ' ';
+							}
+							ungetsc(c);
+							PUSH_STATE(SBRACE);
+							PUSH_STATE(SADELIM);
+							statep->ls_sadelim.style = SADELIM_BASH;
+							statep->ls_sadelim.delimiter = ':';
+							statep->ls_sadelim.num = 2;
+							statep->ls_sadelim.nparen = 0;
+							break;
+						}
+					} else if (c == '/') {
+						*wp++ = CHAR;
+						*wp++ = c;
+						if ((c = getsc()) == '/') {
+							*wp++ = ADELIM;
+							*wp++ = c;
+						} else
+							ungetsc(c);
+						PUSH_STATE(SBRACE);
+						PUSH_STATE(SADELIM);
+						statep->ls_sadelim.style = SADELIM_BASH;
+						statep->ls_sadelim.delimiter = '/';
+						statep->ls_sadelim.num = 1;
+						statep->ls_sadelim.nparen = 0;
+						break;
+					}
+					/* If this is a trim operation,
+					 * treat (,|,) specially in STBRACE.
+					 */
+					if (ctype(c, C_SUBOP2)) {
+						ungetsc(c);
+						PUSH_STATE(STBRACE);
+					} else {
+						ungetsc(c);
+						if (state == SDQUOTE)
+							PUSH_STATE(SQBRACE);
+						else
+							PUSH_STATE(SBRACE);
+					}
+				} else if (ksh_isalphx(c)) {
+					*wp++ = OSUBST;
+					*wp++ = 'X';
+					do {
+						Xcheck(ws, wp);
+						*wp++ = c;
+						c = getsc();
+					} while (ksh_isalnux(c));
+					*wp++ = '\0';
+					*wp++ = CSUBST;
+					*wp++ = 'X';
+					ungetsc(c);
+				} else if (ctype(c, C_VAR1 | C_DIGIT)) {
+					Xcheck(ws, wp);
+					*wp++ = OSUBST;
+					*wp++ = 'X';
+					*wp++ = c;
+					*wp++ = '\0';
+					*wp++ = CSUBST;
+					*wp++ = 'X';
+				} else if (c == '\'' && (state == SBASE)) {
+					/* XXX which other states are valid? */
+					*wp++ = OQUOTE;
+					ignore_backslash_newline++;
+					PUSH_STATE(SEQUOTE);
+					statep->ls_sequote.got_NUL = false;
+					break;
+				} else {
+					*wp++ = CHAR;
+					*wp++ = '$';
+					ungetsc(c);
+				}
+				break;
+			case '`':
+ subst_gravis:
+				PUSH_STATE(SBQUOTE);
+				*wp++ = COMSUB;
+				/* Need to know if we are inside double quotes
+				 * since sh/AT&T-ksh translate the \" to " in
+				 * "`...\"...`".
+				 * This is not done in POSIX mode (section
+				 * 3.2.3, Double Quotes: "The backquote shall
+				 * retain its special meaning introducing the
+				 * other form of command substitution (see
+				 * 3.6.3). The portion of the quoted string
+				 * from the initial backquote and the
+				 * characters up to the next backquote that
+				 * is not preceded by a backslash (having
+				 * escape characters removed) defines that
+				 * command whose output replaces `...` when
+				 * the word is expanded."
+				 * Section 3.6.3, Command Substitution:
+				 * "Within the backquoted style of command
+				 * substitution, backslash shall retain its
+				 * literal meaning, except when followed by
+				 * $ ` \.").
+				 */
+				statep->ls_sbquote.indquotes = 0;
+				s2 = statep;
+				base = state_info.base;
+				while (1) {
+					for (; s2 != base; s2--) {
+						if (s2->ls_state == SDQUOTE) {
+							statep->ls_sbquote.indquotes = 1;
+							break;
+						}
+					}
+					if (s2 != base)
+						break;
+					if (!(s2 = s2->ls_info.base))
+						break;
+					base = s2-- - STATE_BSIZE;
+				}
+				break;
+			case QCHAR:
+				if (cf & LQCHAR) {
+					*wp++ = QCHAR;
+					*wp++ = getsc();
+					break;
+				}
+				/* FALLTHROUGH */
+			default:
+ store_char:
+				*wp++ = CHAR;
+				*wp++ = c;
+			}
+			break;
+
+		case SEQUOTE:
+			if (c == '\'') {
+				POP_STATE();
+				*wp++ = CQUOTE;
+				ignore_backslash_newline--;
+			} else if (c == '\\') {
+				if ((c2 = unbksl(true, s_get, s_put)) == -1)
+					c2 = s_get();
+				if (c2 == 0)
+					statep->ls_sequote.got_NUL = true;
+				if (!statep->ls_sequote.got_NUL) {
+					char ts[4];
+
+					if ((unsigned int)c2 < 0x100) {
+						*wp++ = QCHAR;
+						*wp++ = c2;
+					} else {
+						c = utf_wctomb(ts, c2 - 0x100);
+						ts[c] = 0;
+						for (c = 0; ts[c]; ++c) {
+							*wp++ = QCHAR;
+							*wp++ = ts[c];
+						}
+					}
+				}
+			} else if (!statep->ls_sequote.got_NUL) {
+				*wp++ = QCHAR;
+				*wp++ = c;
+			}
+			break;
+
+		case SSQUOTE:
+			if (c == '\'') {
+				POP_STATE();
+				*wp++ = CQUOTE;
+				ignore_backslash_newline--;
+			} else {
+				*wp++ = QCHAR;
+				*wp++ = c;
+			}
+			break;
+
+		case SDQUOTE:
+			if (c == '"') {
+				POP_STATE();
+				*wp++ = CQUOTE;
+			} else
+				goto Subst;
+			break;
+
+		case SCSPAREN:	/* $( ... ) */
+			/* todo: deal with $(...) quoting properly
+			 * kludge to partly fake quoting inside $(...): doesn't
+			 * really work because nested $(...) or ${...} inside
+			 * double quotes aren't dealt with.
+			 */
+			switch (statep->ls_scsparen.csstate) {
+			case 0:	/* normal */
+				switch (c) {
+				case '(':
+					statep->ls_scsparen.nparen++;
+					break;
+				case ')':
+					statep->ls_scsparen.nparen--;
+					break;
+				case '\\':
+					statep->ls_scsparen.csstate = 1;
+					break;
+				case '"':
+					statep->ls_scsparen.csstate = 2;
+					break;
+				case '\'':
+					statep->ls_scsparen.csstate = 4;
+					ignore_backslash_newline++;
+					break;
+				}
+				break;
+
+			case 1:	/* backslash in normal mode */
+			case 3:	/* backslash in double quotes */
+				--statep->ls_scsparen.csstate;
+				break;
+
+			case 2:	/* double quotes */
+				if (c == '"')
+					statep->ls_scsparen.csstate = 0;
+				else if (c == '\\')
+					statep->ls_scsparen.csstate = 3;
+				break;
+
+			case 4:	/* single quotes */
+				if (c == '\'') {
+					statep->ls_scsparen.csstate = 0;
+					ignore_backslash_newline--;
+				}
+				break;
+			}
+			if (statep->ls_scsparen.nparen == 0) {
+				POP_STATE();
+				*wp++ = 0;	/* end of COMSUB */
+			} else
+				*wp++ = c;
+			break;
+
+		case SASPAREN:	/* $(( ... )) */
+			/* XXX should nest using existing state machine
+			 * (embed "...", $(...), etc.) */
+			if (c == '(')
+				statep->ls_sasparen.nparen++;
+			else if (c == ')') {
+				statep->ls_sasparen.nparen--;
+				if (statep->ls_sasparen.nparen == 1) {
+					/*(*/
+					if ((c2 = getsc()) == ')') {
+						POP_STATE();
+						/* end of EXPRSUB */
+						*wp++ = 0;
+						break;
+					} else {
+						char *s;
+
+						ungetsc(c2);
+						/* mismatched parenthesis -
+						 * assume we were really
+						 * parsing a $(...) expression
+						 */
+						s = Xrestpos(ws, wp,
+						    statep->ls_sasparen.start);
+						memmove(s + 1, s, wp - s);
+						*s++ = COMSUB;
+						*s = '('; /*)*/
+						wp++;
+						statep->ls_scsparen.nparen = 1;
+						statep->ls_scsparen.csstate = 0;
+						state = statep->ls_state =
+						    SCSPAREN;
+					}
+				}
+			}
+			*wp++ = c;
+			break;
+
+		case SQBRACE:
+			if (c == '\\') {
+				/*
+				 * perform POSIX "quote removal" if the back-
+				 * slash is "special", i.e. same cases as the
+				 * {case '\\':} in Subst: plus closing brace;
+				 * in mksh code "quote removal" on '\c' means
+				 * write QCHAR+c, otherwise CHAR+\+CHAR+c are
+				 * emitted (in heredocquote:)
+				 */
+				if ((c = getsc()) == '"' || c == '\\' ||
+				    c == '$' || c == '`' || c == /*{*/'}')
+					goto store_qchar;
+				goto heredocquote;
+			}
+			goto common_SQBRACE;
+
+		case SBRACE:
+			if (c == '\'')
+				goto open_ssquote;
+			else if (c == '\\')
+				goto getsc_qchar;
+ common_SQBRACE:
+			if (c == '"')
+				goto open_sdquote;
+			else if (c == '$')
+				goto subst_dollar;
+			else if (c == '`')
+				goto subst_gravis;
+			else if (c != /*{*/ '}')
+				goto store_char;
+			POP_STATE();
+			*wp++ = CSUBST;
+			*wp++ = /*{*/ '}';
+			break;
+
+		case STBRACE:
+			/* Same as SBASE, except (,|,) treated specially */
+			if (c == /*{*/ '}') {
+				POP_STATE();
+				*wp++ = CSUBST;
+				*wp++ = /*{*/ '}';
+			} else if (c == '|') {
+				*wp++ = SPAT;
+			} else if (c == '(') {
+				*wp++ = OPAT;
+				*wp++ = ' ';	/* simile for @ */
+				PUSH_STATE(SPATTERN);
+			} else
+				goto Sbase1;
+			break;
+
+		case SBQUOTE:
+			if (c == '`') {
+				*wp++ = 0;
+				POP_STATE();
+			} else if (c == '\\') {
+				switch (c = getsc()) {
+				case '\\':
+				case '$': case '`':
+					*wp++ = c;
+					break;
+				case '"':
+					if (statep->ls_sbquote.indquotes) {
+						*wp++ = c;
+						break;
+					}
+					/* FALLTHROUGH */
+				default:
+					if (c) {
+						/* trailing \ is lost */
+						*wp++ = '\\';
+						*wp++ = c;
+					}
+					break;
+				}
+			} else
+				*wp++ = c;
+			break;
+
+		case SWORD:	/* ONEWORD */
+			goto Subst;
+
+		case SLETPAREN:	/* LETEXPR: (( ... )) */
+			/*(*/
+			if (c == ')') {
+				if (statep->ls_sletparen.nparen > 0)
+					--statep->ls_sletparen.nparen;
+				else if ((c2 = getsc()) == /*(*/ ')') {
+					c = 0;
+					*wp++ = CQUOTE;
+					goto Done;
+				} else {
+					Source *s;
+
+					ungetsc(c2);
+					/* mismatched parenthesis -
+					 * assume we were really
+					 * parsing a $(...) expression
+					 */
+					*wp = EOS;
+					sp = Xstring(ws, wp);
+					dp = wdstrip(sp, true, false);
+					s = pushs(SREREAD, source->areap);
+					s->start = s->str = s->u.freeme = dp;
+					s->next = source;
+					source = s;
+					return ('('/*)*/);
+				}
+			} else if (c == '(')
+				/* parenthesis inside quotes and backslashes
+				 * are lost, but AT&T ksh doesn't count them
+				 * either
+				 */
+				++statep->ls_sletparen.nparen;
+			goto Sbase2;
+
+#ifndef MKSH_SMALL
+		case SLETARRAY:	/* LETARRAY: =( ... ) */
+			if (c == '('/*)*/)
+				++statep->ls_sletarray.nparen;
+			else if (c == /*(*/')')
+				if (statep->ls_sletarray.nparen-- == 0) {
+					c = 0;
+					goto Done;
+				}
+			*wp++ = CHAR;
+			*wp++ = c;
+			break;
+#endif
+
+		case SHERESTRING:	/* <<< delimiter */
+			if (c == '\\') {
+				c = getsc();
+				if (c) {
+					/* trailing \ is lost */
+					*wp++ = QCHAR;
+					*wp++ = c;
+				}
+				/* invoke quoting mode */
+				Xstring(ws, wp)[0] = QCHAR;
+			} else if (c == '$') {
+				if ((c2 = getsc()) == '\'') {
+					PUSH_STATE(SEQUOTE);
+					statep->ls_sequote.got_NUL = false;
+					goto sherestring_quoted;
+				}
+				ungetsc(c2);
+				goto sherestring_regular;
+			} else if (c == '\'') {
+				PUSH_STATE(SSQUOTE);
+ sherestring_quoted:
+				*wp++ = OQUOTE;
+				ignore_backslash_newline++;
+				/* invoke quoting mode */
+				Xstring(ws, wp)[0] = QCHAR;
+			} else if (c == '"') {
+				state = statep->ls_state = SHEREDQUOTE;
+				*wp++ = OQUOTE;
+				/* just don't IFS split; no quoting mode */
+			} else {
+ sherestring_regular:
+				*wp++ = CHAR;
+				*wp++ = c;
+			}
+			break;
+
+		case SHEREDELIM:	/* <<,<<- delimiter */
+			/* XXX chuck this state (and the next) - use
+			 * the existing states ($ and \`...` should be
+			 * stripped of their specialness after the
+			 * fact).
+			 */
+			/* here delimiters need a special case since
+			 * $ and `...` are not to be treated specially
+			 */
+			if (c == '\\') {
+				c = getsc();
+				if (c) {
+					/* trailing \ is lost */
+					*wp++ = QCHAR;
+					*wp++ = c;
+				}
+			} else if (c == '$') {
+				if ((c2 = getsc()) == '\'') {
+					PUSH_STATE(SEQUOTE);
+					statep->ls_sequote.got_NUL = false;
+					goto sheredelim_quoted;
+				}
+				ungetsc(c2);
+				goto sheredelim_regular;
+			} else if (c == '\'') {
+				PUSH_STATE(SSQUOTE);
+ sheredelim_quoted:
+				*wp++ = OQUOTE;
+				ignore_backslash_newline++;
+			} else if (c == '"') {
+				state = statep->ls_state = SHEREDQUOTE;
+				*wp++ = OQUOTE;
+			} else {
+ sheredelim_regular:
+				*wp++ = CHAR;
+				*wp++ = c;
+			}
+			break;
+
+		case SHEREDQUOTE:	/* " in <<,<<- delimiter */
+			if (c == '"') {
+				*wp++ = CQUOTE;
+				state = statep->ls_state =
+				    /* dp[1] == '<' means here string */
+				    Xstring(ws, wp)[1] == '<' ?
+				    SHERESTRING : SHEREDELIM;
+			} else {
+				if (c == '\\') {
+					switch (c = getsc()) {
+					case '\\': case '"':
+					case '$': case '`':
+						break;
+					default:
+						if (c) {
+							/* trailing \ lost */
+							*wp++ = CHAR;
+							*wp++ = '\\';
+						}
+						break;
+					}
+				}
+				*wp++ = CHAR;
+				*wp++ = c;
+			}
+			break;
+
+		case SPATTERN:	/* in *(...|...) pattern (*+?@!) */
+			if ( /*(*/ c == ')') {
+				*wp++ = CPAT;
+				POP_STATE();
+			} else if (c == '|') {
+				*wp++ = SPAT;
+			} else if (c == '(') {
+				*wp++ = OPAT;
+				*wp++ = ' ';	/* simile for @ */
+				PUSH_STATE(SPATTERN);
+			} else
+				goto Sbase1;
+			break;
+		}
+	}
+ Done:
+	Xcheck(ws, wp);
+	if (statep != &states[1])
+		/* XXX figure out what is missing */
+		yyerror("no closing quote\n");
+
+#ifndef MKSH_SMALL
+	if (state == SLETARRAY && statep->ls_sletarray.nparen != -1)
+		yyerror("%s: ')' missing\n", T_synerr);
+#endif
+
+	/* This done to avoid tests for SHEREDELIM wherever SBASE tested */
+	if (state == SHEREDELIM || state == SHERESTRING)
+		state = SBASE;
+
+	dp = Xstring(ws, wp);
+	if ((c == '<' || c == '>' || c == '&') && state == SBASE) {
+		struct ioword *iop = alloc(sizeof(struct ioword), ATEMP);
+
+		if (Xlength(ws, wp) == 0)
+			iop->unit = c == '<' ? 0 : 1;
+		else for (iop->unit = 0, c2 = 0; c2 < Xlength(ws, wp); c2 += 2) {
+			if (dp[c2] != CHAR)
+				goto no_iop;
+			if (!ksh_isdigit(dp[c2 + 1]))
+				goto no_iop;
+			iop->unit = (iop->unit * 10) + dp[c2 + 1] - '0';
+		}
+
+		if (iop->unit >= FDBASE)
+			goto no_iop;
+
+		if (c == '&') {
+			if ((c2 = getsc()) != '>') {
+				ungetsc(c2);
+				goto no_iop;
+			}
+			c = c2;
+			iop->flag = IOBASH;
+		} else
+			iop->flag = 0;
+
+		c2 = getsc();
+		/* <<, >>, <> are ok, >< is not */
+		if (c == c2 || (c == '<' && c2 == '>')) {
+			iop->flag |= c == c2 ?
+			    (c == '>' ? IOCAT : IOHERE) : IORDWR;
+			if (iop->flag == IOHERE) {
+				if ((c2 = getsc()) == '-')
+					iop->flag |= IOSKIP;
+				else
+					ungetsc(c2);
+			}
+		} else if (c2 == '&')
+			iop->flag |= IODUP | (c == '<' ? IORDUP : 0);
+		else {
+			iop->flag |= c == '>' ? IOWRITE : IOREAD;
+			if (c == '>' && c2 == '|')
+				iop->flag |= IOCLOB;
+			else
+				ungetsc(c2);
+		}
+
+		iop->name = NULL;
+		iop->delim = NULL;
+		iop->heredoc = NULL;
+		Xfree(ws, wp);	/* free word */
+		yylval.iop = iop;
+		return (REDIR);
+ no_iop:
+		;
+	}
+
+	if (wp == dp && state == SBASE) {
+		Xfree(ws, wp);	/* free word */
+		/* no word, process LEX1 character */
+		if ((c == '|') || (c == '&') || (c == ';') || (c == '('/*)*/)) {
+			if ((c2 = getsc()) == c)
+				c = (c == ';') ? BREAK :
+				    (c == '|') ? LOGOR :
+				    (c == '&') ? LOGAND :
+				    /* c == '(' ) */ MDPAREN;
+			else if (c == '|' && c2 == '&')
+				c = COPROC;
+			else
+				ungetsc(c2);
+		} else if (c == '\n') {
+			gethere(false);
+			if (cf & CONTIN)
+				goto Again;
+		} else if (c == '\0')
+			/* need here strings at EOF */
+			gethere(true);
+		return (c);
+	}
+
+	*wp++ = EOS;		/* terminate word */
+	yylval.cp = Xclose(ws, wp);
+	if (state == SWORD || state == SLETPAREN
+	    /* XXX ONEWORD? */
+#ifndef MKSH_SMALL
+	    || state == SLETARRAY
+#endif
+	    )
+		return (LWORD);
+
+	/* unget terminator */
+	ungetsc(c);
+
+	/*
+	 * note: the alias-vs-function code below depends on several
+	 * interna: starting from here, source->str is not modified;
+	 * the way getsc() and ungetsc() operate; etc.
+	 */
+
+	/* copy word to unprefixed string ident */
+	sp = yylval.cp;
+	dp = ident;
+	if ((cf & HEREDELIM) && (sp[1] == '<'))
+		while (dp < ident+IDENT) {
+			if ((c = *sp++) == CHAR)
+				*dp++ = *sp++;
+			else if ((c != OQUOTE) && (c != CQUOTE))
+				break;
+		}
+	else
+		while (dp < ident+IDENT && (c = *sp++) == CHAR)
+			*dp++ = *sp++;
+	/* Make sure the ident array stays '\0' padded */
+	memset(dp, 0, (ident+IDENT) - dp + 1);
+	if (c != EOS)
+		*ident = '\0';	/* word is not unquoted */
+
+	if (*ident != '\0' && (cf&(KEYWORD|ALIAS))) {
+		struct tbl *p;
+		uint32_t h = hash(ident);
+
+		/* { */
+		if ((cf & KEYWORD) && (p = ktsearch(&keywords, ident, h)) &&
+		    (!(cf & ESACONLY) || p->val.i == ESAC || p->val.i == '}')) {
+			afree(yylval.cp, ATEMP);
+			return (p->val.i);
+		}
+		if ((cf & ALIAS) && (p = ktsearch(&aliases, ident, h)) &&
+		    (p->flag & ISSET)) {
+			/*
+			 * this still points to the same character as the
+			 * ungetsc'd terminator from above
+			 */
+			const char *cp = source->str;
+
+			/* prefer POSIX but not Korn functions over aliases */
+			while (*cp == ' ' || *cp == '\t')
+				/*
+				 * this is like getsc() without skipping
+				 * over Source boundaries (including not
+				 * parsing ungetsc'd characters that got
+				 * pushed into an SREREAD) which is what
+				 * we want here anyway: find out whether
+				 * the alias name is followed by a POSIX
+				 * function definition (only the opening
+				 * parenthesis is checked though)
+				 */
+				++cp;
+			/* prefer functions over aliases */
+			if (*cp == '(' /*)*/)
+				/*
+				 * delete alias upon encountering function
+				 * definition
+				 */
+				ktdelete(p);
+			else {
+				Source *s = source;
+
+				while (s && (s->flags & SF_HASALIAS))
+					if (s->u.tblp == p)
+						return (LWORD);
+					else
+						s = s->next;
+				/* push alias expansion */
+				s = pushs(SALIAS, source->areap);
+				s->start = s->str = p->val.s;
+				s->u.tblp = p;
+				s->flags |= SF_HASALIAS;
+				s->next = source;
+				if (source->type == SEOF) {
+					/* prevent infinite recursion at EOS */
+					source->u.tblp = p;
+					source->flags |= SF_HASALIAS;
+				}
+				source = s;
+				afree(yylval.cp, ATEMP);
+				goto Again;
+			}
+		}
+	}
+
+	return (LWORD);
+}
+
+static void
+gethere(bool iseof)
+{
+	struct ioword **p;
+
+	for (p = heres; p < herep; p++)
+		if (iseof && (*p)->delim[1] != '<')
+			/* only here strings at EOF */
+			return;
+		else
+			readhere(*p);
+	herep = heres;
+}
+
+/*
+ * read "<<word" text into temp file
+ */
+
+static void
+readhere(struct ioword *iop)
+{
+	int c;
+	char *volatile eof;
+	char *eofp;
+	int skiptabs;
+	XString xs;
+	char *xp;
+	int xpos;
+
+	if (iop->delim[1] == '<') {
+		/* process the here string */
+		xp = iop->heredoc = evalstr(iop->delim, DOBLANK);
+		c = strlen(xp) - 1;
+		memmove(xp, xp + 1, c);
+		xp[c] = '\n';
+		return;
+	}
+
+	eof = evalstr(iop->delim, 0);
+
+	if (!(iop->flag & IOEVAL))
+		ignore_backslash_newline++;
+
+	Xinit(xs, xp, 256, ATEMP);
+
+	for (;;) {
+		eofp = eof;
+		skiptabs = iop->flag & IOSKIP;
+		xpos = Xsavepos(xs, xp);
+		while ((c = getsc()) != 0) {
+			if (skiptabs) {
+				if (c == '\t')
+					continue;
+				skiptabs = 0;
+			}
+			if (c != *eofp)
+				break;
+			Xcheck(xs, xp);
+			Xput(xs, xp, c);
+			eofp++;
+		}
+		/* Allow EOF here so commands with out trailing newlines
+		 * will work (eg, ksh -c '...', $(...), etc).
+		 */
+		if (*eofp == '\0' && (c == 0 || c == '\n')) {
+			xp = Xrestpos(xs, xp, xpos);
+			break;
+		}
+		ungetsc(c);
+		while ((c = getsc()) != '\n') {
+			if (c == 0)
+				yyerror("here document '%s' unclosed\n", eof);
+			Xcheck(xs, xp);
+			Xput(xs, xp, c);
+		}
+		Xcheck(xs, xp);
+		Xput(xs, xp, c);
+	}
+	Xput(xs, xp, '\0');
+	iop->heredoc = Xclose(xs, xp);
+
+	if (!(iop->flag & IOEVAL))
+		ignore_backslash_newline--;
+}
+
+void
+yyerror(const char *fmt, ...)
+{
+	va_list va;
+
+	/* pop aliases and re-reads */
+	while (source->type == SALIAS || source->type == SREREAD)
+		source = source->next;
+	source->str = null;	/* zap pending input */
+
+	error_prefix(true);
+	va_start(va, fmt);
+	shf_vfprintf(shl_out, fmt, va);
+	va_end(va);
+	errorfz();
+}
+
+/*
+ * input for yylex with alias expansion
+ */
+
+Source *
+pushs(int type, Area *areap)
+{
+	Source *s;
+
+	s = alloc(sizeof(Source), areap);
+	memset(s, 0, sizeof(Source));
+	s->type = type;
+	s->str = null;
+	s->areap = areap;
+	if (type == SFILE || type == SSTDIN)
+		XinitN(s->xs, 256, s->areap);
+	return (s);
+}
+
+static int
+getsc__(void)
+{
+	Source *s = source;
+	int c;
+
+ getsc_again:
+	while ((c = *s->str++) == 0) {
+		s->str = NULL;		/* return 0 for EOF by default */
+		switch (s->type) {
+		case SEOF:
+			s->str = null;
+			return (0);
+
+		case SSTDIN:
+		case SFILE:
+			getsc_line(s);
+			break;
+
+		case SWSTR:
+			break;
+
+		case SSTRING:
+			break;
+
+		case SWORDS:
+			s->start = s->str = *s->u.strv++;
+			s->type = SWORDSEP;
+			break;
+
+		case SWORDSEP:
+			if (*s->u.strv == NULL) {
+				s->start = s->str = "\n";
+				s->type = SEOF;
+			} else {
+				s->start = s->str = " ";
+				s->type = SWORDS;
+			}
+			break;
+
+		case SALIAS:
+			if (s->flags & SF_ALIASEND) {
+				/* pass on an unused SF_ALIAS flag */
+				source = s->next;
+				source->flags |= s->flags & SF_ALIAS;
+				s = source;
+			} else if (*s->u.tblp->val.s &&
+			    (c = strnul(s->u.tblp->val.s)[-1], ksh_isspace(c))) {
+				source = s = s->next;	/* pop source stack */
+				/* Note that this alias ended with a space,
+				 * enabling alias expansion on the following
+				 * word.
+				 */
+				s->flags |= SF_ALIAS;
+			} else {
+				/* At this point, we need to keep the current
+				 * alias in the source list so recursive
+				 * aliases can be detected and we also need
+				 * to return the next character. Do this
+				 * by temporarily popping the alias to get
+				 * the next character and then put it back
+				 * in the source list with the SF_ALIASEND
+				 * flag set.
+				 */
+				source = s->next;	/* pop source stack */
+				source->flags |= s->flags & SF_ALIAS;
+				c = getsc__();
+				if (c) {
+					s->flags |= SF_ALIASEND;
+					s->ugbuf[0] = c; s->ugbuf[1] = '\0';
+					s->start = s->str = s->ugbuf;
+					s->next = source;
+					source = s;
+				} else {
+					s = source;
+					/* avoid reading eof twice */
+					s->str = NULL;
+					break;
+				}
+			}
+			continue;
+
+		case SREREAD:
+			if (s->start != s->ugbuf)	/* yuck */
+				afree(s->u.freeme, ATEMP);
+			source = s = s->next;
+			continue;
+		}
+		if (s->str == NULL) {
+			s->type = SEOF;
+			s->start = s->str = null;
+			return ('\0');
+		}
+		if (s->flags & SF_ECHO) {
+			shf_puts(s->str, shl_out);
+			shf_flush(shl_out);
+		}
+	}
+	/* check for UTF-8 byte order mark */
+	if (s->flags & SF_FIRST) {
+		s->flags &= ~SF_FIRST;
+		if (((unsigned char)c == 0xEF) &&
+		    (((const unsigned char *)(s->str))[0] == 0xBB) &&
+		    (((const unsigned char *)(s->str))[1] == 0xBF)) {
+			s->str += 2;
+			UTFMODE = 1;
+			goto getsc_again;
+		}
+	}
+	return (c);
+}
+
+static void
+getsc_line(Source *s)
+{
+	char *xp = Xstring(s->xs, xp), *cp;
+	bool interactive = Flag(FTALKING) && s->type == SSTDIN;
+	int have_tty = interactive && (s->flags & SF_TTY);
+
+	/* Done here to ensure nothing odd happens when a timeout occurs */
+	XcheckN(s->xs, xp, LINE);
+	*xp = '\0';
+	s->start = s->str = xp;
+
+	if (have_tty && ksh_tmout) {
+		ksh_tmout_state = TMOUT_READING;
+		alarm(ksh_tmout);
+	}
+	if (interactive)
+		change_winsz();
+	if (have_tty && (
+#if !MKSH_S_NOVI
+	    Flag(FVI) ||
+#endif
+	    Flag(FEMACS) || Flag(FGMACS))) {
+		int nread;
+
+		nread = x_read(xp, LINE);
+		if (nread < 0)	/* read error */
+			nread = 0;
+		xp[nread] = '\0';
+		xp += nread;
+	} else {
+		if (interactive)
+			pprompt(prompt, 0);
+		else
+			s->line++;
+
+		while (1) {
+			char *p = shf_getse(xp, Xnleft(s->xs, xp), s->u.shf);
+
+			if (!p && shf_error(s->u.shf) &&
+			    shf_errno(s->u.shf) == EINTR) {
+				shf_clearerr(s->u.shf);
+				if (trap)
+					runtraps(0);
+				continue;
+			}
+			if (!p || (xp = p, xp[-1] == '\n'))
+				break;
+			/* double buffer size */
+			xp++;	/* move past NUL so doubling works... */
+			XcheckN(s->xs, xp, Xlength(s->xs, xp));
+			xp--;	/* ...and move back again */
+		}
+		/* flush any unwanted input so other programs/builtins
+		 * can read it. Not very optimal, but less error prone
+		 * than flushing else where, dealing with redirections,
+		 * etc.
+		 * todo: reduce size of shf buffer (~128?) if SSTDIN
+		 */
+		if (s->type == SSTDIN)
+			shf_flush(s->u.shf);
+	}
+	/* XXX: temporary kludge to restore source after a
+	 * trap may have been executed.
+	 */
+	source = s;
+	if (have_tty && ksh_tmout) {
+		ksh_tmout_state = TMOUT_EXECUTING;
+		alarm(0);
+	}
+	cp = Xstring(s->xs, xp);
+#ifndef MKSH_SMALL
+	if (interactive && *cp == '!' && cur_prompt == PS1) {
+		int linelen;
+
+		linelen = Xlength(s->xs, xp);
+		XcheckN(s->xs, xp, fc_e_n + /* NUL */ 1);
+		/* reload after potential realloc */
+		cp = Xstring(s->xs, xp);
+		/* change initial '!' into space */
+		*cp = ' ';
+		/* NUL terminate the current string */
+		*xp = '\0';
+		/* move the actual string forward */
+		memmove(cp + fc_e_n, cp, linelen + /* NUL */ 1);
+		xp += fc_e_n;
+		/* prepend it with "fc -e -" */
+		memcpy(cp, fc_e_, fc_e_n);
+	}
+#endif
+	s->start = s->str = cp;
+	strip_nuls(Xstring(s->xs, xp), Xlength(s->xs, xp));
+	/* Note: if input is all nulls, this is not eof */
+	if (Xlength(s->xs, xp) == 0) {
+		/* EOF */
+		if (s->type == SFILE)
+			shf_fdclose(s->u.shf);
+		s->str = NULL;
+	} else if (interactive && *s->str &&
+	    (cur_prompt != PS1 || !ctype(*s->str, C_IFS | C_IFSWS))) {
+		histsave(&s->line, s->str, true, true);
+#if !defined(MKSH_SMALL) && HAVE_PERSISTENT_HISTORY
+	} else if (interactive && cur_prompt == PS1) {
+		cp = Xstring(s->xs, xp);
+		while (*cp && ctype(*cp, C_IFSWS))
+			++cp;
+		if (!*cp)
+			histsync();
+#endif
+	}
+	if (interactive)
+		set_prompt(PS2, NULL);
+}
+
+void
+set_prompt(int to, Source *s)
+{
+	cur_prompt = to;
+
+	switch (to) {
+	case PS1:	/* command */
+		/* Substitute ! and !! here, before substitutions are done
+		 * so ! in expanded variables are not expanded.
+		 * NOTE: this is not what AT&T ksh does (it does it after
+		 * substitutions, POSIX doesn't say which is to be done.
+		 */
+		{
+			struct shf *shf;
+			char * volatile ps1;
+			Area *saved_atemp;
+
+			ps1 = str_val(global("PS1"));
+			shf = shf_sopen(NULL, strlen(ps1) * 2,
+			    SHF_WR | SHF_DYNAMIC, NULL);
+			while (*ps1)
+				if (*ps1 != '!' || *++ps1 == '!')
+					shf_putchar(*ps1++, shf);
+				else
+					shf_fprintf(shf, "%d",
+						s ? s->line + 1 : 0);
+			ps1 = shf_sclose(shf);
+			saved_atemp = ATEMP;
+			newenv(E_ERRH);
+			if (sigsetjmp(e->jbuf, 0)) {
+				prompt = safe_prompt;
+				/* Don't print an error - assume it has already
+				 * been printed. Reason is we may have forked
+				 * to run a command and the child may be
+				 * unwinding its stack through this code as it
+				 * exits.
+				 */
+			} else {
+				char *cp = substitute(ps1, 0);
+				strdupx(prompt, cp, saved_atemp);
+			}
+			quitenv(NULL);
+		}
+		break;
+	case PS2:	/* command continuation */
+		prompt = str_val(global("PS2"));
+		break;
+	}
+}
+
+static int
+dopprompt(const char *cp, int ntruncate, bool doprint)
+{
+	int columns = 0, lines = 0, indelimit = 0;
+	char delimiter = 0;
+
+	/* Undocumented AT&T ksh feature:
+	 * If the second char in the prompt string is \r then the first char
+	 * is taken to be a non-printing delimiter and any chars between two
+	 * instances of the delimiter are not considered to be part of the
+	 * prompt length
+	 */
+	if (*cp && cp[1] == '\r') {
+		delimiter = *cp;
+		cp += 2;
+	}
+	for (; *cp; cp++) {
+		if (indelimit && *cp != delimiter)
+			;
+		else if (*cp == '\n' || *cp == '\r') {
+			lines += columns / x_cols + ((*cp == '\n') ? 1 : 0);
+			columns = 0;
+		} else if (*cp == '\t') {
+			columns = (columns | 7) + 1;
+		} else if (*cp == '\b') {
+			if (columns > 0)
+				columns--;
+		} else if (*cp == delimiter)
+			indelimit = !indelimit;
+		else if (UTFMODE && ((unsigned char)*cp > 0x7F)) {
+			const char *cp2;
+			columns += utf_widthadj(cp, &cp2);
+			if (doprint && (indelimit ||
+			    (ntruncate < (x_cols * lines + columns))))
+				shf_write(cp, cp2 - cp, shl_out);
+			cp = cp2 - /* loop increment */ 1;
+			continue;
+		} else
+			columns++;
+		if (doprint && (*cp != delimiter) &&
+		    (indelimit || (ntruncate < (x_cols * lines + columns))))
+			shf_putc(*cp, shl_out);
+	}
+	if (doprint)
+		shf_flush(shl_out);
+	return (x_cols * lines + columns);
+}
+
+
+void
+pprompt(const char *cp, int ntruncate)
+{
+	dopprompt(cp, ntruncate, true);
+}
+
+int
+promptlen(const char *cp)
+{
+	return (dopprompt(cp, 0, false));
+}
+
+/* Read the variable part of a ${...} expression (ie, up to but not including
+ * the :[-+?=#%] or close-brace.
+ */
+static char *
+get_brace_var(XString *wsp, char *wp)
+{
+	enum parse_state {
+		PS_INITIAL, PS_SAW_HASH, PS_IDENT,
+		PS_NUMBER, PS_VAR1
+	} state;
+	char c;
+
+	state = PS_INITIAL;
+	while (1) {
+		c = getsc();
+		/* State machine to figure out where the variable part ends. */
+		switch (state) {
+		case PS_INITIAL:
+			if (c == '#' || c == '!' || c == '%') {
+				state = PS_SAW_HASH;
+				break;
+			}
+			/* FALLTHROUGH */
+		case PS_SAW_HASH:
+			if (ksh_isalphx(c))
+				state = PS_IDENT;
+			else if (ksh_isdigit(c))
+				state = PS_NUMBER;
+			else if (ctype(c, C_VAR1))
+				state = PS_VAR1;
+			else
+				goto out;
+			break;
+		case PS_IDENT:
+			if (!ksh_isalnux(c)) {
+				if (c == '[') {
+					char *tmp, *p;
+
+					if (!arraysub(&tmp))
+						yyerror("missing ]\n");
+					*wp++ = c;
+					for (p = tmp; *p; ) {
+						Xcheck(*wsp, wp);
+						*wp++ = *p++;
+					}
+					afree(tmp, ATEMP);
+					c = getsc();	/* the ] */
+				}
+				goto out;
+			}
+			break;
+		case PS_NUMBER:
+			if (!ksh_isdigit(c))
+				goto out;
+			break;
+		case PS_VAR1:
+			goto out;
+		}
+		Xcheck(*wsp, wp);
+		*wp++ = c;
+	}
+ out:
+	*wp++ = '\0';	/* end of variable part */
+	ungetsc(c);
+	return (wp);
+}
+
+/*
+ * Save an array subscript - returns true if matching bracket found, false
+ * if eof or newline was found.
+ * (Returned string double null terminated)
+ */
+static int
+arraysub(char **strp)
+{
+	XString ws;
+	char	*wp;
+	char	c;
+	int	depth = 1;	/* we are just past the initial [ */
+
+	Xinit(ws, wp, 32, ATEMP);
+
+	do {
+		c = getsc();
+		Xcheck(ws, wp);
+		*wp++ = c;
+		if (c == '[')
+			depth++;
+		else if (c == ']')
+			depth--;
+	} while (depth > 0 && c && c != '\n');
+
+	*wp++ = '\0';
+	*strp = Xclose(ws, wp);
+
+	return (depth == 0 ? 1 : 0);
+}
+
+/* Unget a char: handles case when we are already at the start of the buffer */
+static const char *
+ungetsc(int c)
+{
+	if (backslash_skip)
+		backslash_skip--;
+	/* Don't unget eof... */
+	if (source->str == null && c == '\0')
+		return (source->str);
+	if (source->str > source->start)
+		source->str--;
+	else {
+		Source *s;
+
+		s = pushs(SREREAD, source->areap);
+		s->ugbuf[0] = c; s->ugbuf[1] = '\0';
+		s->start = s->str = s->ugbuf;
+		s->next = source;
+		source = s;
+	}
+	return (source->str);
+}
+
+
+/* Called to get a char that isn't a \newline sequence. */
+static int
+getsc_bn(void)
+{
+	int c, c2;
+
+	if (ignore_backslash_newline)
+		return (getsc_());
+
+	if (backslash_skip == 1) {
+		backslash_skip = 2;
+		return (getsc_());
+	}
+
+	backslash_skip = 0;
+
+	while (1) {
+		c = getsc_();
+		if (c == '\\') {
+			if ((c2 = getsc_()) == '\n')
+				/* ignore the \newline; get the next char... */
+				continue;
+			ungetsc(c2);
+			backslash_skip = 1;
+		}
+		return (c);
+	}
+}
+
+static Lex_state *
+push_state_(State_info *si, Lex_state *old_end)
+{
+	Lex_state *news = alloc(STATE_BSIZE * sizeof(Lex_state), ATEMP);
+
+	news[0].ls_info.base = old_end;
+	si->base = &news[0];
+	si->end = &news[STATE_BSIZE];
+	return (&news[1]);
+}
+
+static Lex_state *
+pop_state_(State_info *si, Lex_state *old_end)
+{
+	Lex_state *old_base = si->base;
+
+	si->base = old_end->ls_info.base - STATE_BSIZE;
+	si->end = old_end->ls_info.base;
+
+	afree(old_base, ATEMP);
+
+	return (si->base + STATE_BSIZE - 1);
+}
+
+static int
+s_get(void)
+{
+	return (getsc());
+}
+
+static void
+s_put(int c)
+{
+	ungetsc(c);
+}