/***********************************************************************
* *
* This software is part of the ast package *
* Copyright (c) 1982-2010 AT&T Intellectual Property *
* and is licensed under the *
* Common Public License, Version 1.0 *
* by AT&T Intellectual Property *
* *
* A copy of the License is available at *
* http://www.opensource.org/licenses/cpl1.0.txt *
* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) *
* *
* 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(sh.hist_ptr, str,
sh.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 = sh.hist_ptr->histind + n;
/* search and use history file if found */
if(n > 0 && hist_seek(sh.hist_ptr, n) != -1)
ref = sh.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