summaryrefslogtreecommitdiff
path: root/bin/pdksh/lex.c
diff options
context:
space:
mode:
authorJason Downs <downsj@cvs.openbsd.org>1996-08-14 06:19:13 +0000
committerJason Downs <downsj@cvs.openbsd.org>1996-08-14 06:19:13 +0000
commit3039b959c7de1e35fe4ec27ae02f1c8fe8d4d4ac (patch)
tree09c6b5a4f13282e07afe153445fcfaa0d10651f4 /bin/pdksh/lex.c
parent596c2317eee31ab9606c6b88085644407d4d2f0f (diff)
Import pdksh 5.2.7.
Diffstat (limited to 'bin/pdksh/lex.c')
-rw-r--r--bin/pdksh/lex.c1204
1 files changed, 1204 insertions, 0 deletions
diff --git a/bin/pdksh/lex.c b/bin/pdksh/lex.c
new file mode 100644
index 00000000000..06e912fae09
--- /dev/null
+++ b/bin/pdksh/lex.c
@@ -0,0 +1,1204 @@
+/* $OpenBSD: lex.c,v 1.1 1996/08/14 06:19:11 downsj Exp $ */
+
+/*
+ * lexical analysis and source input
+ */
+
+#include "sh.h"
+#include <ctype.h>
+
+static void readhere ARGS((struct ioword *iop));
+static int getsc_ ARGS((void));
+static void getsc_line ARGS((Source *s));
+static char *get_brace_var ARGS((XString *wsp, char *wp));
+static int arraysub ARGS((char **strp));
+static const char *ungetsc_ ARGS((int c));
+static int getsc_bn_ ARGS((void));
+
+static void gethere ARGS((void));
+
+/* optimized getsc_() */
+#define getsc() ((*source->str != '\0') ? *source->str++ : getsc_())
+#define getsc_bn() (*source->str != '\0' && *source->str != '\\' \
+ ? *source->str++ : getsc_bn_())
+#define ungetsc(c) (source->str > source->start ? source->str-- : ungetsc_(c))
+
+
+/*
+ * Lexical analyzer
+ *
+ * tokens are not regular expressions, they are LL(1).
+ * for example, "${var:-${PWD}}", and "$(size $(whence ksh))".
+ * hence the state stack.
+ */
+
+int
+yylex(cf)
+ int cf;
+{
+ register int c, state;
+ char states [64], *statep = states; /* XXX overflow check */
+ XString ws; /* expandable output word */
+ register char *wp; /* output word pointer */
+ register char *sp, *dp;
+ char UNINITIALIZED(*ddparen_start);
+ int istate;
+ int UNINITIALIZED(c2);
+ int UNINITIALIZED(nparen), UNINITIALIZED(csstate);
+ int UNINITIALIZED(ndparen);
+ int UNINITIALIZED(indquotes);
+
+
+ Again:
+ Xinit(ws, wp, 64, ATEMP);
+
+ if (cf&ONEWORD)
+ istate = SWORD;
+ else if (cf&LETEXPR) {
+ *wp++ = OQUOTE; /* enclose arguments in (double) quotes */
+ istate = SDPAREN;
+ ndparen = 0;
+ } else { /* normal lexing */
+ istate = (cf & HEREDELIM) ? SHEREDELIM : SBASE;
+ while ((c = getsc()) == ' ' || c == '\t')
+ ;
+ if (c == '#')
+ while ((c = getsc()) != '\0' && c != '\n')
+ ;
+ ungetsc(c);
+ }
+ if (source->flags & SF_ALIAS) { /* trailing ' ' in alias definition */
+ source->flags &= ~SF_ALIAS;
+ /* In POSIX mode, a trailing space only counts if we are
+ * parsing a simple command
+ */
+ if (!Flag(FPOSIX) || (cf & CMDWORD))
+ cf |= ALIAS;
+ }
+
+ /* collect non-special or quoted characters to form word */
+ for (*statep = state = istate;
+ !((c = getsc()) == 0 || ((state == SBASE || state == SHEREDELIM)
+ && ctype(c, C_LEX1))); )
+ {
+ Xcheck(ws, wp);
+ switch (state) {
+ 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;
+ }
+ /* fall through.. */
+ Sbase1: /* includes *(...|...) pattern (*+?@!) */
+#ifdef KSH
+ if (c == '*' || c == '@' || c == '+' || c == '?'
+ || c == '!')
+ {
+ c2 = getsc();
+ if (c2 == '(' /*)*/ ) {
+ *wp++ = OPAT;
+ *wp++ = c;
+ *++statep = state = SPATTERN;
+ break;
+ }
+ ungetsc(c2);
+ }
+#endif /* KSH */
+ /* fall through.. */
+ Sbase2: /* doesn't include *(...|...) pattern (*+?@!) */
+ switch (c) {
+ case '\\':
+ c = getsc();
+ if (c != '\n') {
+#ifdef OS2
+ if (isalnum(c)) {
+ *wp++ = CHAR, *wp++ = '\\';
+ *wp++ = CHAR, *wp++ = c;
+ } else
+#endif
+ *wp++ = QCHAR, *wp++ = c;
+ } else
+ if (wp == Xstring(ws, wp)) {
+ Xfree(ws, wp); /* free word */
+ goto Again;
+ }
+ break;
+ case '\'':
+ *++statep = state = SSQUOTE;
+ *wp++ = OQUOTE;
+ break;
+ case '"':
+ *++statep = state = SDQUOTE;
+ *wp++ = OQUOTE;
+ break;
+ default:
+ goto Subst;
+ }
+ break;
+
+ Subst:
+ switch (c) {
+ case '\\':
+ c = getsc();
+ switch (c) {
+ case '\n':
+ break;
+ case '"': case '\\':
+ case '$': case '`':
+ *wp++ = QCHAR, *wp++ = c;
+ break;
+ default:
+ Xcheck(ws, wp);
+ *wp++ = CHAR, *wp++ = '\\';
+ *wp++ = CHAR, *wp++ = c;
+ break;
+ }
+ break;
+ case '$':
+ c = getsc();
+ if (c == '(') /*)*/ {
+ c = getsc();
+ if (c == '(') /*)*/ {
+ *++statep = state = SDDPAREN;
+ nparen = 2;
+ ddparen_start = wp;
+ *wp++ = EXPRSUB;
+ } else {
+ ungetsc(c);
+ *++statep = state = SPAREN;
+ nparen = 1;
+ csstate = 0;
+ *wp++ = COMSUB;
+ }
+ } else if (c == '{') /*}*/ {
+ *wp++ = OSUBST;
+ wp = get_brace_var(&ws, wp);
+ /* If this is a trim operation,
+ * wrap @(...) around the pattern
+ * (allows easy handling of ${a#b|c})
+ */
+ c = getsc_bn();
+ if (c == '#' || c == '%') {
+ *wp++ = CHAR, *wp++ = c;
+ if ((c2 = getsc_bn()) == c)
+ *wp++ = CHAR, *wp++ = c;
+ else
+ ungetsc(c2);
+ *wp++ = OPAT, *wp++ = '@';
+ *++statep = state = STBRACE;
+ } else {
+ ungetsc(c);
+ *++statep = state = SBRACE;
+ }
+ } else if (ctype(c, C_ALPHA)) {
+ *wp++ = OSUBST;
+ do {
+ Xcheck(ws, wp);
+ *wp++ = c;
+ c = getsc();
+ } while (ctype(c, C_ALPHA|C_DIGIT));
+ *wp++ = '\0';
+ *wp++ = CSUBST;
+ ungetsc(c);
+ } else if (ctype(c, C_DIGIT|C_VAR1)) {
+ Xcheck(ws, wp);
+ *wp++ = OSUBST;
+ *wp++ = c;
+ *wp++ = '\0';
+ *wp++ = CSUBST;
+ } else {
+ *wp++ = CHAR, *wp++ = '$';
+ ungetsc(c);
+ }
+ break;
+ case '`':
+ *++statep = state = SBQUOTE;
+ *wp++ = COMSUB;
+ /* Need to know if we are inside double quotes
+ * since sh/at&t-ksh translate the \" to " in
+ * "`..\"..`".
+ */
+ indquotes = 0;
+ if (!Flag(FPOSIX))
+ for (sp = statep; sp > states; --sp)
+ if (*sp == SDQUOTE)
+ indquotes = 1;
+ break;
+ default:
+ *wp++ = CHAR, *wp++ = c;
+ }
+ break;
+
+ case SSQUOTE:
+ if (c == '\'') {
+ state = *--statep;
+ *wp++ = CQUOTE;
+ } else
+ *wp++ = QCHAR, *wp++ = c;
+ break;
+
+ case SDQUOTE:
+ if (c == '"') {
+ state = *--statep;
+ *wp++ = CQUOTE;
+ } else
+ goto Subst;
+ break;
+
+ case SPAREN: /* $( .. ) */
+ /* 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 (csstate) {
+ case 0: /* normal */
+ switch (c) {
+ case '(':
+ nparen++;
+ break;
+ case ')':
+ nparen--;
+ break;
+ case '\\':
+ csstate = 1;
+ break;
+ case '"':
+ csstate = 2;
+ break;
+ case '\'':
+ csstate = 4;
+ break;
+ }
+ break;
+
+ case 1: /* backslash in normal mode */
+ case 3: /* backslash in double quotes */
+ --csstate;
+ break;
+
+ case 2: /* double quotes */
+ if (c == '"')
+ csstate = 0;
+ else if (c == '\\')
+ csstate = 3;
+ break;
+
+ case 4: /* single quotes */
+ if (c == '\'')
+ csstate = 0;
+ break;
+ }
+ if (nparen == 0) {
+ state = *--statep;
+ *wp++ = 0; /* end of COMSUB */
+ } else
+ *wp++ = c;
+ break;
+
+ case SDDPAREN: /* $(( .. )) */
+ /* todo: deal with $((...); (...)) properly */
+ /* XXX should nest using existing state machine
+ * (embed "..", $(...), etc.) */
+ if (c == '(')
+ nparen++;
+ else if (c == ')') {
+ nparen--;
+ if (nparen == 1) {
+ /*(*/
+ if ((c2 = getsc()) == ')') {
+ state = *--statep;
+ *wp++ = 0; /* end of EXPRSUB */
+ break;
+ } else {
+ ungetsc(c2);
+ /* mismatched parenthesis -
+ * assume we were really
+ * parsing a $(..) expression
+ */
+ memmove(ddparen_start + 1,
+ ddparen_start,
+ wp - ddparen_start);
+ *ddparen_start++ = COMSUB;
+ *ddparen_start = '('; /*)*/
+ wp++;
+ csstate = 0;
+ *statep = state = SPAREN;
+ }
+ }
+ }
+ *wp++ = c;
+ break;
+
+ case SBRACE:
+ /*{*/
+ if (c == '}') {
+ state = *--statep;
+ *wp++ = CSUBST;
+ } else
+ goto Sbase1;
+ break;
+
+ case STBRACE:
+ /* same as SBRACE, except | is saved as SPAT and
+ * CPAT is added at the end.
+ */
+ /*{*/
+ if (c == '}') {
+ state = *--statep;
+ *wp++ = CPAT;
+ *wp++ = CSUBST;
+ } else if (c == '|') {
+ *wp++ = SPAT;
+ } else
+ goto Sbase1;
+ break;
+
+ case SBQUOTE:
+ if (c == '`') {
+ *wp++ = 0;
+ state = *--statep;
+ } else if (c == '\\') {
+ switch (c = getsc()) {
+ case '\n':
+ break;
+ case '\\':
+ case '$': case '`':
+ *wp++ = c;
+ break;
+ case '"':
+ if (indquotes) {
+ *wp++ = c;
+ break;
+ }
+ /* fall through.. */
+ default:
+ *wp++ = '\\';
+ *wp++ = c;
+ break;
+ }
+ } else
+ *wp++ = c;
+ break;
+
+ case SWORD: /* ONEWORD */
+ goto Subst;
+
+ case SDPAREN: /* LETEXPR: (( ... )) */
+ /*(*/
+ if (c == ')') {
+ if (ndparen > 0)
+ --ndparen;
+ /*(*/
+ else if ((c2 = getsc()) == ')') {
+ c = 0;
+ *wp++ = CQUOTE;
+ goto Done;
+ } else
+ ungetsc(c2);
+ } else if (c == '(')
+ /* parenthesis inside quotes and backslashes
+ * are lost, but at&t ksh doesn't count them
+ * either
+ */
+ ++ndparen;
+ goto Sbase2;
+
+ 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 != '\n') {
+ *wp++ = QCHAR;
+ *wp++ = c;
+ }
+ } else if (c == '\'') {
+ *++statep = state = SSQUOTE;
+ *wp++ = OQUOTE;
+ } else if (c == '"') {
+ state = SHEREDQUOTE;
+ *wp++ = OQUOTE;
+ } else {
+ *wp++ = CHAR;
+ *wp++ = c;
+ }
+ break;
+
+ case SHEREDQUOTE: /* " in <<,<<- delimiter */
+ if (c == '"') {
+ *wp++ = CQUOTE;
+ state = SHEREDELIM;
+ } else {
+ if (c == '\\' && (c = getsc()) == '\n')
+ break;
+ *wp++ = CHAR;
+ *wp++ = c;
+ }
+ break;
+
+ case SPATTERN: /* in *(...|...) pattern (*+?@!) */
+ if ( /*(*/ c == ')') {
+ *wp++ = CPAT;
+ state = *--statep;
+ } else if (c == '|')
+ *wp++ = SPAT;
+ else
+ goto Sbase1;
+ break;
+ }
+ }
+Done:
+ Xcheck(ws, wp);
+ if (state != istate)
+ yyerror("no closing quote\n");
+
+ /* This done to avoid tests for SHEREDELIM wherever SBASE tested */
+ if (state == SHEREDELIM)
+ state = SBASE;
+
+ if ((c == '<' || c == '>') && state == SBASE) {
+ char *cp = Xstring(ws, wp);
+ if (Xlength(ws, wp) == 2 && cp[0] == CHAR && digit(cp[1])) {
+ wp = cp; /* throw away word */
+ c2/*unit*/ = cp[1] - '0';
+ } else
+ c2/*unit*/ = c == '>'; /* 0 for <, 1 for > */
+ }
+
+ if (wp == Xstring(ws, wp) && state == SBASE) {
+ Xfree(ws, wp); /* free word */
+ /* no word, process LEX1 character */
+ switch (c) {
+ default:
+ return c;
+
+ case '|':
+ case '&':
+ case ';':
+ if ((c2 = getsc()) == c)
+ c = (c == ';') ? BREAK :
+ (c == '|') ? LOGOR :
+ (c == '&') ? LOGAND :
+ YYERRCODE;
+#ifdef KSH
+ else if (c == '|' && c2 == '&')
+ c = COPROC;
+#endif /* KSH */
+ else
+ ungetsc(c2);
+ return c;
+
+ case '>':
+ case '<': {
+ register struct ioword *iop;
+
+ iop = (struct ioword *) alloc(sizeof(*iop), ATEMP);
+ iop->unit = c2/*unit*/;
+
+ c2 = getsc();
+ /* <<, >>, <> are ok, >< is not */
+ if (c == c2 || (c == '<' && c2 == '>')) {
+ iop->flag = c == c2 ?
+ (c == '>' ? IOCAT : IOHERE) : IORDWR;
+ if (iop->flag == IOHERE)
+ if (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 = (char *) 0;
+ iop->delim = (char *) 0;
+ yylval.iop = iop;
+ return REDIR;
+ }
+ case '\n':
+ gethere();
+ if (cf & CONTIN)
+ goto Again;
+ return c;
+
+ case '(': /*)*/
+ if ((c2 = getsc()) == '(') /*)*/
+ c = MDPAREN;
+ else
+ ungetsc(c2);
+ return c;
+ /*(*/
+ case ')':
+ return c;
+ }
+ }
+
+ *wp++ = EOS; /* terminate word */
+ yylval.cp = Xclose(ws, wp);
+ if (state == SWORD || state == SDPAREN) /* ONEWORD? */
+ return LWORD;
+ ungetsc(c); /* unget terminator */
+
+ /* copy word to unprefixed string ident */
+ for (sp = yylval.cp, dp = ident; dp < ident+IDENT && (c = *sp++) == CHAR; )
+ *dp++ = *sp++;
+ /* Make sure the ident array stays '\0' paded */
+ 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;
+ int h = hash(ident);
+
+ /* { */
+ if ((cf & KEYWORD) && (p = tsearch(&keywords, ident, h))
+ && (!(cf & ESACONLY) || p->val.i == ESAC || p->val.i == '}'))
+ {
+ afree(yylval.cp, ATEMP);
+ return p->val.i;
+ }
+ if ((cf & ALIAS) && (p = tsearch(&aliases, ident, h))
+ && (p->flag & ISSET))
+ {
+ register Source *s;
+
+ for (s = source; s->type == SALIAS; s = s->next)
+ if (s->u.tblp == p)
+ return LWORD;
+ /* push alias expansion */
+ s = pushs(SALIAS, source->areap);
+ s->start = s->str = p->val.s;
+ s->u.tblp = p;
+ s->next = source;
+ source = s;
+ afree(yylval.cp, ATEMP);
+ goto Again;
+ }
+ }
+
+ return LWORD;
+}
+
+static void
+gethere()
+{
+ register struct ioword **p;
+
+ for (p = heres; p < herep; p++)
+ readhere(*p);
+ herep = heres;
+}
+
+/*
+ * read "<<word" text into temp file
+ */
+
+static void
+readhere(iop)
+ register struct ioword *iop;
+{
+ struct shf *volatile shf;
+ struct temp *h;
+ register int c;
+ char *volatile eof;
+ char *eofp;
+ int skiptabs, bn;
+ int i;
+
+ eof = evalstr(iop->delim, 0);
+
+ if (e->flags & EF_FUNC_PARSE) {
+ h = maketemp(APERM);
+ h->next = func_heredocs;
+ func_heredocs = h;
+ } else {
+ h = maketemp(ATEMP);
+ h->next = e->temps;
+ e->temps = h;
+ }
+ iop->name = h->name;
+ if (!(shf = h->shf))
+ yyerror("cannot create temporary file %s - %s\n",
+ h->name, strerror(errno));
+
+ newenv(E_ERRH);
+ i = ksh_sigsetjmp(e->jbuf, 0);
+ if (i) {
+ quitenv();
+ shf_close(shf);
+ unwind(i);
+ }
+
+ bn = iop->flag & IOEVAL;
+ for (;;) {
+ eofp = eof;
+ skiptabs = iop->flag & IOSKIP;
+ while ((c = (bn ? getsc_bn() : getsc())) != 0) {
+ if (skiptabs) {
+ if (c == '\t')
+ continue;
+ skiptabs = 0;
+ }
+ if (c != *eofp)
+ break;
+ eofp++;
+ }
+ /* Allow EOF here so commands with out trailing newlines
+ * will work (eg, ksh -c '...', $(...), etc).
+ */
+ if (*eofp == '\0' && (c == 0 || c == '\n'))
+ break;
+ ungetsc(c);
+ shf_write(eof, eofp - eof, shf);
+ while ((c = (bn ? getsc_bn() : getsc())) != '\n') {
+ if (c == 0)
+ yyerror("here document `%s' unclosed\n", eof);
+ shf_putc(c, shf);
+ }
+ shf_putc(c, shf);
+ }
+ shf_flush(shf);
+ if (shf_error(shf))
+ yyerror("error saving here document `%s': %s\n",
+ eof, strerror(shf_errno(shf)));
+ /*XXX add similar checks for write errors everywhere */
+ quitenv();
+ shf_close(shf);
+}
+
+void
+#ifdef HAVE_PROTOTYPES
+yyerror(const char *fmt, ...)
+#else
+yyerror(fmt, va_alist)
+ const char *fmt;
+ va_dcl
+#endif
+{
+ va_list va;
+
+ yynerrs++;
+ /* pop aliases and re-reads */
+ while (source->type == SALIAS || source->type == SREREAD)
+ source = source->next;
+ source->str = null; /* zap pending input */
+
+ error_prefix(TRUE);
+ SH_VA_START(va, fmt);
+ shf_vfprintf(shl_out, fmt, va);
+ va_end(va);
+ errorf(null);
+}
+
+/*
+ * input for yylex with alias expansion
+ */
+
+Source *
+pushs(type, areap)
+ int type;
+ Area *areap;
+{
+ register Source *s;
+
+ s = (Source *) alloc(sizeof(Source), areap);
+ s->type = type;
+ s->str = null;
+ s->start = NULL;
+ s->line = 0;
+ s->errline = 0;
+ s->file = NULL;
+ s->flags = 0;
+ s->next = NULL;
+ s->areap = areap;
+ if (type == SFILE || type == SSTDIN) {
+ char *dummy;
+ Xinit(s->xs, dummy, 256, s->areap);
+ } else
+ memset(&s->xs, 0, sizeof(s->xs));
+ return s;
+}
+
+static int
+getsc_()
+{
+ register Source *s = source;
+ register int c;
+
+ 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 = newline;
+ s->type = SEOF;
+ } else {
+ s->start = s->str = space;
+ 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
+ && isspace(strchr(s->u.tblp->val.s, 0)[-1]))
+ {
+ source = s = s->next; /* pop source stack */
+ s->flags |= SF_ALIAS;
+ } else {
+ /* put a fake space at the end of the alias.
+ * This keeps the current alias in the source
+ * list so recursive aliases can be detected.
+ * The addition of a space after an alias
+ * never affects anything (I think).
+ */
+ s->flags |= SF_ALIASEND;
+ s->start = s->str = space;
+ }
+ continue;
+
+ case SREREAD:
+ if (s->start != s->u.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);
+ }
+ }
+ return c;
+}
+
+static void
+getsc_line(s)
+ Source *s;
+{
+ char *xp = Xstring(s->xs, xp);
+ int 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;
+
+#ifdef KSH
+ if (have_tty && ksh_tmout) {
+ ksh_tmout_state = TMOUT_READING;
+ alarm(ksh_tmout);
+ }
+#endif /* KSH */
+#ifdef EDIT
+ if (have_tty && (0
+# ifdef VI
+ || Flag(FVI)
+# endif /* VI */
+# ifdef EMACS
+ || Flag(FEMACS) || Flag(FGMACS)
+# endif /* EMACS */
+ ))
+ {
+ int nread;
+
+ nread = x_read(xp, LINE);
+ if (nread < 0) /* read error */
+ nread = 0;
+ xp[nread] = '\0';
+ xp += nread;
+ }
+ else
+#endif /* EDIT */
+ {
+ if (interactive) {
+ pprompt(prompt, 0);
+#ifdef OS2
+ setmode (0, O_TEXT);
+#endif /* OS2 */
+ } 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 null so doubling works... */
+ XcheckN(s->xs, xp, Xlength(s->xs, xp));
+ xp--; /* ...and move back again */
+ }
+#ifdef OS2
+ setmode(0, O_BINARY);
+#endif /* OS2 */
+ /* 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;
+#ifdef KSH
+ if (have_tty && ksh_tmout)
+ {
+ ksh_tmout_state = TMOUT_EXECUTING;
+ alarm(0);
+ }
+#endif /* KSH */
+ s->start = s->str = Xstring(s->xs, xp);
+ 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) {
+#ifdef HISTORY
+ char *p = Xstring(s->xs, xp);
+ if (cur_prompt == PS1)
+ while (*p && ctype(*p, C_IFS) && ctype(*p, C_IFSWS))
+ p++;
+ if (*p) {
+# ifdef EASY_HISTORY
+ if (cur_prompt == PS2)
+ histappend(Xstring(s->xs, xp), 1);
+ else
+# endif /* EASY_HISTORY */
+ {
+ s->line++;
+ histsave(s->line, s->str, 1);
+ }
+ }
+#endif /* HISTORY */
+ }
+ if (interactive)
+ set_prompt(PS2, (Source *) 0);
+}
+
+void
+set_prompt(to, s)
+ 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 *ps1;
+ Area *saved_atemp;
+
+ ps1 = str_val(global("PS1"));
+ shf = shf_sopen((char *) 0, strlen(ps1) * 2,
+ SHF_WR | SHF_DYNAMIC, (struct shf *) 0);
+ 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 (ksh_sigsetjmp(e->jbuf, 0)) {
+ prompt = safe_prompt;
+ warningf(TRUE, "error during expansion of PS1");
+ } else
+ prompt = str_save(substitute(ps1, 0),
+ saved_atemp);
+ quitenv();
+ }
+ break;
+
+ case PS2: /* command continuation */
+ prompt = str_val(global("PS2"));
+ break;
+ }
+}
+
+/* See also related routine, promptlen() in edit.c */
+void
+pprompt(cp, ntruncate)
+ const char *cp;
+ int ntruncate;
+{
+#if 0
+ char nbuf[32];
+ int c;
+
+ while (*cp != 0) {
+ if (*cp != '!')
+ c = *cp++;
+ else if (*++cp == '!')
+ c = *cp++;
+ else {
+ int len;
+ char *p;
+
+ shf_snprintf(p = nbuf, sizeof(nbuf), "%d",
+ source->line + 1);
+ len = strlen(nbuf);
+ if (ntruncate) {
+ if (ntruncate >= len) {
+ ntruncate -= len;
+ continue;
+ }
+ p += ntruncate;
+ len -= ntruncate;
+ ntruncate = 0;
+ }
+ shf_write(p, len, shl_out);
+ continue;
+ }
+ if (ntruncate)
+ --ntruncate;
+ else
+ shf_putc(c, shl_out);
+ }
+#endif /* 0 */
+ if (ntruncate)
+ shellf("%.*s", ntruncate, cp);
+ else {
+ shf_puts(cp, shl_out);
+ shf_flush(shl_out);
+ }
+}
+
+/* Read the variable part of a ${...} expression (ie, up to but not including
+ * the :[-+?=#%] or close-brace.
+ */
+static char *
+get_brace_var(wsp, wp)
+ XString *wsp;
+ char *wp;
+{
+ enum parse_state {
+ PS_INITIAL, PS_SAW_HASH, PS_IDENT,
+ PS_NUMBER, PS_VAR1, PS_END
+ }
+ state;
+ char c;
+
+ state = PS_INITIAL;
+ while (1) {
+ c = getsc_bn();
+ /* State machine to figure out where the variable part ends. */
+ switch (state) {
+ case PS_INITIAL:
+ if (c == '#') {
+ state = PS_SAW_HASH;
+ break;
+ }
+ /* fall through.. */
+ case PS_SAW_HASH:
+ if (letter(c))
+ state = PS_IDENT;
+ else if (digit(c))
+ state = PS_NUMBER;
+ else if (ctype(c, C_VAR1))
+ state = PS_VAR1;
+ else
+ state = PS_END;
+ break;
+ case PS_IDENT:
+ if (!letnum(c)) {
+ state = PS_END;
+ 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();
+ }
+ }
+ break;
+ case PS_NUMBER:
+ if (!digit(c))
+ state = PS_END;
+ break;
+ case PS_VAR1:
+ state = PS_END;
+ break;
+ case PS_END: /* keep gcc happy */
+ break;
+ }
+ if (state == PS_END) {
+ *wp++ = '\0'; /* end of variable part */
+ ungetsc(c);
+ break;
+ }
+ Xcheck(*wsp, wp);
+ *wp++ = 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(strp)
+ 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_bn();
+ 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_(c)
+ int c;
+{
+ /* 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->u.ugbuf[0] = c; s->u.ugbuf[1] = '\0';
+ s->start = s->str = s->u.ugbuf;
+ s->next = source;
+ source = s;
+ }
+ return source->str;
+}
+
+/* Called to get a char that isn't a \newline sequence. */
+static int
+getsc_bn_ ARGS((void))
+{
+ int c;
+
+ while (1) {
+ c = getsc_();
+ if (c != '\\')
+ return c;
+ c = getsc();
+ if (c != '\n') {
+ ungetsc(c);
+ return '\\';
+ }
+ /* ignore the \newline; get the next char... */
+ }
+}