/* $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); }