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