diff options
| author | Igor Pashev <pashev.igor@gmail.com> | 2012-06-24 22:28:35 +0000 |
|---|---|---|
| committer | Igor Pashev <pashev.igor@gmail.com> | 2012-06-24 22:28:35 +0000 |
| commit | 3950ffe2a485479f6561c27364d3d7df5a21d124 (patch) | |
| tree | 468c6e14449d1b1e279222ec32f676b0311917d2 /src/cmd/ksh93/edit/hexpand.c | |
| download | ksh-upstream.tar.gz | |
Imported Upstream version 93u+upstream
Diffstat (limited to 'src/cmd/ksh93/edit/hexpand.c')
| -rw-r--r-- | src/cmd/ksh93/edit/hexpand.c | 734 |
1 files changed, 734 insertions, 0 deletions
diff --git a/src/cmd/ksh93/edit/hexpand.c b/src/cmd/ksh93/edit/hexpand.c new file mode 100644 index 0000000..3e2d396 --- /dev/null +++ b/src/cmd/ksh93/edit/hexpand.c @@ -0,0 +1,734 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 1982-2011 AT&T Intellectual Property * +* and is licensed under the * +* Eclipse Public License, Version 1.0 * +* by AT&T Intellectual Property * +* * +* A copy of the License is available at * +* http://www.eclipse.org/org/documents/epl-v10.html * +* (with md5 checksum b35adb5213ca9657e911e9befb180842) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* David Korn <dgk@research.att.com> * +* * +***********************************************************************/ +#pragma prototyped +/* + * bash style history expansion + * + * Author: + * Karsten Fleischer + * Omnium Software Engineering + * An der Luisenburg 7 + * D-51379 Leverkusen + * Germany + * + * <K.Fleischer@omnium.de> + */ + + +#include "defs.h" +#include "edit.h" + +#if ! SHOPT_HISTEXPAND + +NoN(hexpand) + +#else + +static char *modifiers = "htrepqxs&"; +static int mod_flags[] = { 0, 0, 0, 0, HIST_PRINT, HIST_QUOTE, HIST_QUOTE|HIST_QUOTE_BR, 0, 0 }; + +#define DONE() {flag |= HIST_ERROR; cp = 0; stakseek(0); goto done;} + +struct subst +{ + char *str[2]; /* [0] is "old", [1] is "new" string */ +}; + + +/* + * parse an /old/new/ string, delimiter expected as first char. + * if "old" not specified, keep sb->str[0] + * if "new" not specified, set sb->str[1] to empty string + * read up to third delimeter char, \n or \0, whichever comes first. + * return adress is one past the last valid char in s: + * - the address containing \n or \0 or + * - one char beyond the third delimiter + */ + +static char *parse_subst(const char *s, struct subst *sb) +{ + char *cp,del; + int off,n = 0; + + /* build the strings on the stack, mainly for '&' substition in "new" */ + off = staktell(); + + /* init "new" with empty string */ + if(sb->str[1]) + free(sb->str[1]); + sb->str[1] = strdup(""); + + /* get delimiter */ + del = *s; + + cp = (char*) s + 1; + + while(n < 2) + { + if(*cp == del || *cp == '\n' || *cp == '\0') + { + /* delimiter or EOL */ + if(staktell() != off) + { + /* dupe string on stack and rewind stack */ + stakputc('\0'); + if(sb->str[n]) + free(sb->str[n]); + sb->str[n] = strdup(stakptr(off)); + stakseek(off); + } + n++; + + /* if not delimiter, we've reached EOL. Get outta here. */ + if(*cp != del) + break; + } + else if(*cp == '\\') + { + if(*(cp+1) == del) /* quote delimiter */ + { + stakputc(del); + cp++; + } + else if(*(cp+1) == '&' && n == 1) + { /* quote '&' only in "new" */ + stakputc('&'); + cp++; + } + else + stakputc('\\'); + } + else if(*cp == '&' && n == 1 && sb->str[0]) + /* substitute '&' with "old" in "new" */ + stakputs(sb->str[0]); + else + stakputc(*cp); + cp++; + } + + /* rewind stack */ + stakseek(off); + + return cp; +} + +/* + * history expansion main routine + */ + +int hist_expand(const char *ln, char **xp) +{ + int off, /* stack offset */ + q, /* quotation flags */ + p, /* flag */ + c, /* current char */ + flag=0; /* HIST_* flags */ + Sfoff_t n, /* history line number, counter, etc. */ + i, /* counter */ + w[2]; /* word range */ + char *sp, /* stack pointer */ + *cp, /* current char in ln */ + *str, /* search string */ + *evp, /* event/word designator string, for error msgs */ + *cc=0, /* copy of current line up to cp; temp ptr */ + hc[3], /* default histchars */ + *qc="\'\"`"; /* quote characters */ + Sfio_t *ref=0, /* line referenced by event designator */ + *tmp=0, /* temporary line buffer */ + *tmp2=0;/* temporary line buffer */ + Histloc_t hl; /* history location */ + static Namval_t *np = 0; /* histchars variable */ + static struct subst sb = {0,0}; /* substition strings */ + static Sfio_t *wm=0; /* word match from !?string? event designator */ + + if(!wm) + wm = sfopen(NULL, NULL, "swr"); + + hc[0] = '!'; + hc[1] = '^'; + hc[2] = 0; + if((np = nv_open("histchars",sh.var_tree,0)) && (cp = nv_getval(np))) + { + if(cp[0]) + { + hc[0] = cp[0]; + if(cp[1]) + { + hc[1] = cp[1]; + if(cp[2]) + hc[2] = cp[2]; + } + } + } + + /* save shell stack */ + if(off = staktell()) + sp = stakfreeze(0); + + cp = (char*)ln; + + while(cp && *cp) + { + /* read until event/quick substitution/comment designator */ + if((*cp != hc[0] && *cp != hc[1] && *cp != hc[2]) + || (*cp == hc[1] && cp != ln)) + { + if(*cp == '\\') /* skip escaped designators */ + stakputc(*cp++); + else if(*cp == '\'') /* skip quoted designators */ + { + do + stakputc(*cp); + while(*++cp && *cp != '\''); + } + stakputc(*cp++); + continue; + } + + if(hc[2] && *cp == hc[2]) /* history comment designator, skip rest of line */ + { + stakputc(*cp++); + stakputs(cp); + DONE(); + } + + n = -1; + str = 0; + flag &= HIST_EVENT; /* save event flag for returning later */ + evp = cp; + ref = 0; + + if(*cp == hc[1]) /* shortcut substitution */ + { + flag |= HIST_QUICKSUBST; + goto getline; + } + + if(*cp == hc[0] && *(cp+1) == hc[0]) /* refer to line -1 */ + { + cp += 2; + goto getline; + } + + switch(c = *++cp) { + case ' ': + case '\t': + case '\n': + case '\0': + case '=': + case '(': + stakputc(hc[0]); + continue; + case '#': /* the line up to current position */ + flag |= HIST_HASH; + cp++; + n = staktell(); /* terminate string and dup */ + stakputc('\0'); + cc = strdup(stakptr(0)); + stakseek(n); /* remove null byte again */ + ref = sfopen(ref, cc, "s"); /* open as file */ + n = 0; /* skip history file referencing */ + break; + case '-': /* back reference by number */ + if(!isdigit(*(cp+1))) + goto string_event; + cp++; + case '0': /* reference by number */ + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + n = 0; + while(isdigit(*cp)) + n = n * 10 + (*cp++) - '0'; + if(c == '-') + n = -n; + break; + case '$': + n = -1; + case ':': + break; + case '?': + cp++; + flag |= HIST_QUESTION; + string_event: + default: + /* read until end of string or word designator/modifier */ + str = cp; + while(*cp) + { + cp++; + if((!(flag&HIST_QUESTION) && + (*cp == ':' || isspace(*cp) + || *cp == '^' || *cp == '$' + || *cp == '*' || *cp == '-' + || *cp == '%') + ) + || ((flag&HIST_QUESTION) && (*cp == '?' || *cp == '\n'))) + { + c = *cp; + *cp = '\0'; + } + } + break; + } + +getline: + flag |= HIST_EVENT; + if(str) /* !string or !?string? event designator */ + { + + /* search history for string */ + hl = hist_find(shgd->hist_ptr, str, + shgd->hist_ptr->histind, + flag&HIST_QUESTION, -1); + if((n = hl.hist_command) == -1) + n = 0; /* not found */ + } + if(n) + { + if(n < 0) /* determine index for backref */ + n = shgd->hist_ptr->histind + n; + /* search and use history file if found */ + if(n > 0 && hist_seek(shgd->hist_ptr, n) != -1) + ref = shgd->hist_ptr->histfp; + + } + if(!ref) + { + /* string not found or command # out of range */ + c = *cp; + *cp = '\0'; + errormsg(SH_DICT, ERROR_ERROR, "%s: event not found", evp); + *cp = c; + DONE(); + } + + if(str) /* string search: restore orig. line */ + { + if(flag&HIST_QUESTION) + *cp++ = c; /* skip second question mark */ + else + *cp = c; + } + + /* colon introduces either word designators or modifiers */ + if(*(evp = cp) == ':') + cp++; + + w[0] = 0; /* -1 means last word, -2 means match from !?string? */ + w[1] = -1; /* -1 means last word, -2 means suppress last word */ + + if(flag & HIST_QUICKSUBST) /* shortcut substitution */ + goto getsel; + + n = 0; + while(n < 2) + { + switch(c = *cp++) { + case '^': /* first word */ + if(n == 0) + { + w[0] = w[1] = 1; + goto skip; + } + else + goto skip2; + case '$': /* last word */ + w[n] = -1; + goto skip; + case '%': /* match from !?string? event designator */ + if(n == 0) + { + if(!str) + { + w[0] = 0; + w[1] = -1; + ref = wm; + } + else + { + w[0] = -2; + w[1] = sftell(ref) + hl.hist_char; + } + sfseek(wm, 0, SEEK_SET); + goto skip; + } + default: + skip2: + cp--; + n = 2; + break; + case '*': /* until last word */ + if(n == 0) + w[0] = 1; + w[1] = -1; + skip: + flag |= HIST_WORDDSGN; + n = 2; + break; + case '-': /* until last word or specified index */ + w[1] = -2; + flag |= HIST_WORDDSGN; + n = 1; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': /* specify index */ + if((*evp == ':') || w[1] == -2) + { + w[n] = c - '0'; + while(isdigit(c=*cp++)) + w[n] = w[n] * 10 + c - '0'; + flag |= HIST_WORDDSGN; + if(n == 0) + w[1] = w[0]; + n++; + } + else + n = 2; + cp--; + break; + } + } + + if(w[0] != -2 && w[1] > 0 && w[0] > w[1]) + { + c = *cp; + *cp = '\0'; + errormsg(SH_DICT, ERROR_ERROR, "%s: bad word specifier", evp); + *cp = c; + DONE(); + } + + /* no valid word designator after colon, rewind */ + if(!(flag & HIST_WORDDSGN) && (*evp == ':')) + cp = evp; + +getsel: + /* open temp buffer, let sfio do the (re)allocation */ + tmp = sfopen(NULL, NULL, "swr"); + + /* push selected words into buffer, squash + whitespace into single blank or a newline */ + n = i = q = 0; + + while((c = sfgetc(ref)) > 0) + { + if(isspace(c)) + { + flag |= (c == '\n' ? HIST_NEWLINE : 0); + continue; + } + + if(n >= w[0] && ((w[0] != -2) ? (w[1] < 0 || n <= w[1]) : 1)) + { + if(w[0] < 0) + sfseek(tmp, 0, SEEK_SET); + else + i = sftell(tmp); + + if(i > 0) + sfputc(tmp, flag & HIST_NEWLINE ? '\n' : ' '); + + flag &= ~HIST_NEWLINE; + p = 1; + } + else + p = 0; + + do + { + cc = strchr(qc, c); + q ^= cc ? 1<<(int)(cc - qc) : 0; + if(p) + sfputc(tmp, c); + } + while((c = sfgetc(ref)) > 0 && (!isspace(c) || q)); + + if(w[0] == -2 && sftell(ref) > w[1]) + break; + + flag |= (c == '\n' ? HIST_NEWLINE : 0); + n++; + } + if(w[0] != -2 && w[1] >= 0 && w[1] >= n) + { + c = *cp; + *cp = '\0'; + errormsg(SH_DICT, ERROR_ERROR, "%s: bad word specifier", evp); + *cp = c; + DONE(); + } + else if(w[1] == -2) /* skip last word */ + sfseek(tmp, i, SEEK_SET); + + /* remove trailing newline */ + if(sftell(tmp)) + { + sfseek(tmp, -1, SEEK_CUR); + if(sfgetc(tmp) == '\n') + sfungetc(tmp, '\n'); + } + + sfputc(tmp, '\0'); + + if(str) + { + if(wm) + sfclose(wm); + wm = tmp; + } + + if(cc && (flag&HIST_HASH)) + { + /* close !# temp file */ + sfclose(ref); + flag &= ~HIST_HASH; + free(cc); + cc = 0; + } + + evp = cp; + + /* selected line/words are now in buffer, now go for the modifiers */ + while(*cp == ':' || (flag & HIST_QUICKSUBST)) + { + if(flag & HIST_QUICKSUBST) + { + flag &= ~HIST_QUICKSUBST; + c = 's'; + cp--; + } + else + c = *++cp; + + sfseek(tmp, 0, SEEK_SET); + tmp2 = sfopen(tmp2, NULL, "swr"); + + if(c == 'g') /* global substitution */ + { + flag |= HIST_GLOBALSUBST; + c = *++cp; + } + + if(cc = strchr(modifiers, c)) + flag |= mod_flags[cc - modifiers]; + else + { + errormsg(SH_DICT, ERROR_ERROR, "%c: unrecognized history modifier", c); + DONE(); + } + + if(c == 'h' || c == 'r') /* head or base */ + { + n = -1; + while((c = sfgetc(tmp)) > 0) + { /* remember position of / or . */ + if((c == '/' && *cp == 'h') || (c == '.' && *cp == 'r')) + n = sftell(tmp2); + sfputc(tmp2, c); + } + if(n > 0) + { /* rewind to last / or . */ + sfseek(tmp2, n, SEEK_SET); + /* end string there */ + sfputc(tmp2, '\0'); + } + } + else if(c == 't' || c == 'e') /* tail or suffix */ + { + n = 0; + while((c = sfgetc(tmp)) > 0) + { /* remember position of / or . */ + if((c == '/' && *cp == 't') || (c == '.' && *cp == 'e')) + n = sftell(tmp); + } + /* rewind to last / or . */ + sfseek(tmp, n, SEEK_SET); + /* copy from there on */ + while((c = sfgetc(tmp)) > 0) + sfputc(tmp2, c); + } + else if(c == 's' || c == '&') + { + cp++; + + if(c == 's') + { + /* preset old with match from !?string? */ + if(!sb.str[0] && wm) + sb.str[0] = strdup(sfsetbuf(wm, (Void_t*)1, 0)); + cp = parse_subst(cp, &sb); + } + + if(!sb.str[0] || !sb.str[1]) + { + c = *cp; + *cp = '\0'; + errormsg(SH_DICT, ERROR_ERROR, + "%s%s: no previous substitution", + (flag & HIST_QUICKSUBST) ? ":s" : "", + evp); + *cp = c; + DONE(); + } + + /* need pointer for strstr() */ + str = sfsetbuf(tmp, (Void_t*)1, 0); + + flag |= HIST_SUBSTITUTE; + while(flag & HIST_SUBSTITUTE) + { + /* find string */ + if(cc = strstr(str, sb.str[0])) + { /* replace it */ + c = *cc; + *cc = '\0'; + sfputr(tmp2, str, -1); + sfputr(tmp2, sb.str[1], -1); + *cc = c; + str = cc + strlen(sb.str[0]); + } + else if(!sftell(tmp2)) + { /* not successfull */ + c = *cp; + *cp = '\0'; + errormsg(SH_DICT, ERROR_ERROR, + "%s%s: substitution failed", + (flag & HIST_QUICKSUBST) ? ":s" : "", + evp); + *cp = c; + DONE(); + } + /* loop if g modifier specified */ + if(!cc || !(flag & HIST_GLOBALSUBST)) + flag &= ~HIST_SUBSTITUTE; + } + /* output rest of line */ + sfputr(tmp2, str, -1); + if(*cp) + cp--; + } + + if(sftell(tmp2)) + { /* if any substitions done, swap buffers */ + if(wm != tmp) + sfclose(tmp); + tmp = tmp2; + tmp2 = 0; + } + cc = 0; + if(*cp) + cp++; + } + + /* flush temporary buffer to stack */ + if(tmp) + { + sfseek(tmp, 0, SEEK_SET); + + if(flag & HIST_QUOTE) + stakputc('\''); + + while((c = sfgetc(tmp)) > 0) + { + if(isspace(c)) + { + flag = flag & ~HIST_NEWLINE; + + /* squash white space to either a + blank or a newline */ + do + flag |= (c == '\n' ? HIST_NEWLINE : 0); + while((c = sfgetc(tmp)) > 0 && isspace(c)); + + sfungetc(tmp, c); + + c = (flag & HIST_NEWLINE) ? '\n' : ' '; + + if(flag & HIST_QUOTE_BR) + { + stakputc('\''); + stakputc(c); + stakputc('\''); + } + else + stakputc(c); + } + else if((c == '\'') && (flag & HIST_QUOTE)) + { + stakputc('\''); + stakputc('\\'); + stakputc(c); + stakputc('\''); + } + else + stakputc(c); + } + if(flag & HIST_QUOTE) + stakputc('\''); + } + } + + stakputc('\0'); + +done: + if(cc && (flag&HIST_HASH)) + { + /* close !# temp file */ + sfclose(ref); + free(cc); + cc = 0; + } + + /* error? */ + if(staktell() && !(flag & HIST_ERROR)) + *xp = strdup(stakfreeze(1)); + + /* restore shell stack */ + if(off) + stakset(sp,off); + else + stakseek(0); + + /* drop temporary files */ + + if(tmp && tmp != wm) + sfclose(tmp); + if(tmp2) + sfclose(tmp2); + + return (flag & HIST_ERROR ? HIST_ERROR : flag & HIST_FLAG_RETURN_MASK); +} + +#endif |
