mirror of
				https://github.com/MariaDB/server.git
				synced 2025-11-03 20:36:16 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			1592 lines
		
	
	
	
		
			38 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1592 lines
		
	
	
	
		
			38 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/* histexpand.c -- history expansion. */
 | 
						|
 | 
						|
/* Copyright (C) 1989-2004 Free Software Foundation, Inc.
 | 
						|
 | 
						|
   This file contains the GNU History Library (the Library), a set of
 | 
						|
   routines for managing the text of previously typed lines.
 | 
						|
 | 
						|
   The Library is free software; you can redistribute it and/or modify
 | 
						|
   it under the terms of the GNU General Public License as published by
 | 
						|
   the Free Software Foundation; either version 2, or (at your option)
 | 
						|
   any later version.
 | 
						|
 | 
						|
   The Library is distributed in the hope that it will be useful, but
 | 
						|
   WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | 
						|
   General Public License for more details.
 | 
						|
 | 
						|
   The GNU General Public License is often shipped with GNU software, and
 | 
						|
   is generally kept in a file called COPYING or LICENSE.  If you do not
 | 
						|
   have a copy of the license, write to the Free Software Foundation,
 | 
						|
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1335  USA. */
 | 
						|
 | 
						|
#define READLINE_LIBRARY
 | 
						|
 | 
						|
#if defined (HAVE_CONFIG_H)
 | 
						|
#  include "config_readline.h"
 | 
						|
#endif
 | 
						|
 | 
						|
#include <stdio.h>
 | 
						|
 | 
						|
#if defined (HAVE_STDLIB_H)
 | 
						|
#  include <stdlib.h>
 | 
						|
#else
 | 
						|
#  include "ansi_stdlib.h"
 | 
						|
#endif /* HAVE_STDLIB_H */
 | 
						|
 | 
						|
#if defined (HAVE_UNISTD_H)
 | 
						|
#  ifndef _MINIX
 | 
						|
#    include <sys/types.h>
 | 
						|
#  endif
 | 
						|
#  include <unistd.h>
 | 
						|
#endif
 | 
						|
 | 
						|
#include "rlmbutil.h"
 | 
						|
 | 
						|
#include "history.h"
 | 
						|
#include "histlib.h"
 | 
						|
 | 
						|
#include "rlshell.h"
 | 
						|
#include "xmalloc.h"
 | 
						|
 | 
						|
#define HISTORY_WORD_DELIMITERS		" \t\n;&()|<>"
 | 
						|
#define HISTORY_QUOTE_CHARACTERS	"\"'`"
 | 
						|
 | 
						|
#define slashify_in_quotes "\\`\"$"
 | 
						|
 | 
						|
typedef int _hist_search_func_t PARAMS((const char *, int));
 | 
						|
 | 
						|
static char error_pointer;
 | 
						|
 | 
						|
static char *subst_lhs;
 | 
						|
static char *subst_rhs;
 | 
						|
static int subst_lhs_len;
 | 
						|
static int subst_rhs_len;
 | 
						|
 | 
						|
static char *get_history_word_specifier PARAMS((char *, char *, int *));
 | 
						|
static char *history_find_word PARAMS((char *, int));
 | 
						|
static int history_tokenize_word PARAMS((const char *, int));
 | 
						|
static char *history_substring PARAMS((const char *, int, int));
 | 
						|
 | 
						|
static char *quote_breaks PARAMS((char *));
 | 
						|
 | 
						|
/* Variables exported by this file. */
 | 
						|
/* The character that represents the start of a history expansion
 | 
						|
   request.  This is usually `!'. */
 | 
						|
char history_expansion_char = '!';
 | 
						|
 | 
						|
/* The character that invokes word substitution if found at the start of
 | 
						|
   a line.  This is usually `^'. */
 | 
						|
char history_subst_char = '^';
 | 
						|
 | 
						|
/* During tokenization, if this character is seen as the first character
 | 
						|
   of a word, then it, and all subsequent characters upto a newline are
 | 
						|
   ignored.  For a Bourne shell, this should be '#'.  Bash special cases
 | 
						|
   the interactive comment character to not be a comment delimiter. */
 | 
						|
char history_comment_char = '\0';
 | 
						|
 | 
						|
/* The list of characters which inhibit the expansion of text if found
 | 
						|
   immediately following history_expansion_char. */
 | 
						|
const char *history_no_expand_chars = " \t\n\r=";
 | 
						|
 | 
						|
/* If set to a non-zero value, single quotes inhibit history expansion.
 | 
						|
   The default is 0. */
 | 
						|
int history_quotes_inhibit_expansion = 0;
 | 
						|
 | 
						|
/* Used to split words by history_tokenize_internal. */
 | 
						|
const char *history_word_delimiters = HISTORY_WORD_DELIMITERS;
 | 
						|
 | 
						|
/* If set, this points to a function that is called to verify that a
 | 
						|
   particular history expansion should be performed. */
 | 
						|
rl_linebuf_func_t *history_inhibit_expansion_function;
 | 
						|
 | 
						|
/* **************************************************************** */
 | 
						|
/*								    */
 | 
						|
/*			History Expansion			    */
 | 
						|
/*								    */
 | 
						|
/* **************************************************************** */
 | 
						|
 | 
						|
/* Hairy history expansion on text, not tokens.  This is of general
 | 
						|
   use, and thus belongs in this library. */
 | 
						|
 | 
						|
/* The last string searched for by a !?string? search. */
 | 
						|
static char *search_string;
 | 
						|
 | 
						|
/* The last string matched by a !?string? search. */
 | 
						|
static char *search_match;
 | 
						|
 | 
						|
/* Return the event specified at TEXT + OFFSET modifying OFFSET to
 | 
						|
   point to after the event specifier.  Just a pointer to the history
 | 
						|
   line is returned; NULL is returned in the event of a bad specifier.
 | 
						|
   You pass STRING with *INDEX equal to the history_expansion_char that
 | 
						|
   begins this specification.
 | 
						|
   DELIMITING_QUOTE is a character that is allowed to end the string
 | 
						|
   specification for what to search for in addition to the normal
 | 
						|
   characters `:', ` ', `\t', `\n', and sometimes `?'.
 | 
						|
   So you might call this function like:
 | 
						|
   line = get_history_event ("!echo:p", &index, 0);  */
 | 
						|
char *
 | 
						|
get_history_event (string, caller_index, delimiting_quote)
 | 
						|
     const char *string;
 | 
						|
     int *caller_index;
 | 
						|
     int delimiting_quote;
 | 
						|
{
 | 
						|
  register int i;
 | 
						|
  register char c;
 | 
						|
  HIST_ENTRY *entry;
 | 
						|
  int which, sign, local_index, substring_okay;
 | 
						|
  _hist_search_func_t *search_func;
 | 
						|
  char *temp;
 | 
						|
 | 
						|
  /* The event can be specified in a number of ways.
 | 
						|
 | 
						|
     !!   the previous command
 | 
						|
     !n   command line N
 | 
						|
     !-n  current command-line minus N
 | 
						|
     !str the most recent command starting with STR
 | 
						|
     !?str[?]
 | 
						|
	  the most recent command containing STR
 | 
						|
 | 
						|
     All values N are determined via HISTORY_BASE. */
 | 
						|
 | 
						|
  i = *caller_index;
 | 
						|
 | 
						|
  if (string[i] != history_expansion_char)
 | 
						|
    return ((char *)NULL);
 | 
						|
 | 
						|
  /* Move on to the specification. */
 | 
						|
  i++;
 | 
						|
 | 
						|
  sign = 1;
 | 
						|
  substring_okay = 0;
 | 
						|
 | 
						|
#define RETURN_ENTRY(e, w) \
 | 
						|
	return ((e = history_get (w)) ? e->line : (char *)NULL)
 | 
						|
 | 
						|
  /* Handle !! case. */
 | 
						|
  if (string[i] == history_expansion_char)
 | 
						|
    {
 | 
						|
      i++;
 | 
						|
      which = history_base + (history_length - 1);
 | 
						|
      *caller_index = i;
 | 
						|
      RETURN_ENTRY (entry, which);
 | 
						|
    }
 | 
						|
 | 
						|
  /* Hack case of numeric line specification. */
 | 
						|
  if (string[i] == '-')
 | 
						|
    {
 | 
						|
      sign = -1;
 | 
						|
      i++;
 | 
						|
    }
 | 
						|
 | 
						|
  if (_rl_digit_p (string[i]))
 | 
						|
    {
 | 
						|
      /* Get the extent of the digits and compute the value. */
 | 
						|
      for (which = 0; _rl_digit_p (string[i]); i++)
 | 
						|
	which = (which * 10) + _rl_digit_value (string[i]);
 | 
						|
 | 
						|
      *caller_index = i;
 | 
						|
 | 
						|
      if (sign < 0)
 | 
						|
	which = (history_length + history_base) - which;
 | 
						|
 | 
						|
      RETURN_ENTRY (entry, which);
 | 
						|
    }
 | 
						|
 | 
						|
  /* This must be something to search for.  If the spec begins with
 | 
						|
     a '?', then the string may be anywhere on the line.  Otherwise,
 | 
						|
     the string must be found at the start of a line. */
 | 
						|
  if (string[i] == '?')
 | 
						|
    {
 | 
						|
      substring_okay++;
 | 
						|
      i++;
 | 
						|
    }
 | 
						|
 | 
						|
  /* Only a closing `?' or a newline delimit a substring search string. */
 | 
						|
  for (local_index = i; (c = string[i]); i++)
 | 
						|
    {
 | 
						|
#if defined (HANDLE_MULTIBYTE)
 | 
						|
      if (MB_CUR_MAX > 1 && rl_byte_oriented == 0)
 | 
						|
	{
 | 
						|
	  int v;
 | 
						|
	  mbstate_t ps;
 | 
						|
 | 
						|
	  memset (&ps, 0, sizeof (mbstate_t));
 | 
						|
	  /* These produce warnings because we're passing a const string to a
 | 
						|
	     function that takes a non-const string. */
 | 
						|
	  _rl_adjust_point ((char *)string, i, &ps);
 | 
						|
	  if ((v = _rl_get_char_len ((char *)string + i, &ps)) > 1)
 | 
						|
	    {
 | 
						|
	      i += v - 1;
 | 
						|
	      continue;
 | 
						|
	    }
 | 
						|
        }
 | 
						|
 | 
						|
#endif /* HANDLE_MULTIBYTE */
 | 
						|
      if ((!substring_okay && (whitespace (c) || c == ':' ||
 | 
						|
	  (history_search_delimiter_chars && member (c, history_search_delimiter_chars)) ||
 | 
						|
	  string[i] == delimiting_quote)) ||
 | 
						|
	  string[i] == '\n' ||
 | 
						|
	  (substring_okay && string[i] == '?'))
 | 
						|
	break;
 | 
						|
    }
 | 
						|
 | 
						|
  which = i - local_index;
 | 
						|
  temp = (char *)xmalloc (1 + which);
 | 
						|
  if (which)
 | 
						|
    strncpy (temp, string + local_index, which);
 | 
						|
  temp[which] = '\0';
 | 
						|
 | 
						|
  if (substring_okay && string[i] == '?')
 | 
						|
    i++;
 | 
						|
 | 
						|
  *caller_index = i;
 | 
						|
 | 
						|
#define FAIL_SEARCH() \
 | 
						|
  do { \
 | 
						|
    history_offset = history_length; free (temp) ; return (char *)NULL; \
 | 
						|
  } while (0)
 | 
						|
 | 
						|
  /* If there is no search string, try to use the previous search string,
 | 
						|
     if one exists.  If not, fail immediately. */
 | 
						|
  if (*temp == '\0' && substring_okay)
 | 
						|
    {
 | 
						|
      if (search_string)
 | 
						|
        {
 | 
						|
          free (temp);
 | 
						|
          temp = savestring (search_string);
 | 
						|
        }
 | 
						|
      else
 | 
						|
        FAIL_SEARCH ();
 | 
						|
    }
 | 
						|
 | 
						|
  search_func = substring_okay ? history_search : history_search_prefix;
 | 
						|
  while (1)
 | 
						|
    {
 | 
						|
      local_index = (*search_func) (temp, -1);
 | 
						|
 | 
						|
      if (local_index < 0)
 | 
						|
	FAIL_SEARCH ();
 | 
						|
 | 
						|
      if (local_index == 0 || substring_okay)
 | 
						|
	{
 | 
						|
	  entry = current_history ();
 | 
						|
	  history_offset = history_length;
 | 
						|
	
 | 
						|
	  /* If this was a substring search, then remember the
 | 
						|
	     string that we matched for word substitution. */
 | 
						|
	  if (substring_okay)
 | 
						|
	    {
 | 
						|
	      FREE (search_string);
 | 
						|
	      search_string = temp;
 | 
						|
 | 
						|
	      FREE (search_match);
 | 
						|
	      search_match = history_find_word (entry->line, local_index);
 | 
						|
	    }
 | 
						|
	  else
 | 
						|
	    free (temp);
 | 
						|
 | 
						|
	  return (entry->line);
 | 
						|
	}
 | 
						|
 | 
						|
      if (history_offset)
 | 
						|
	history_offset--;
 | 
						|
      else
 | 
						|
	FAIL_SEARCH ();
 | 
						|
    }
 | 
						|
#undef FAIL_SEARCH
 | 
						|
#undef RETURN_ENTRY
 | 
						|
}
 | 
						|
 | 
						|
/* Function for extracting single-quoted strings.  Used for inhibiting
 | 
						|
   history expansion within single quotes. */
 | 
						|
 | 
						|
/* Extract the contents of STRING as if it is enclosed in single quotes.
 | 
						|
   SINDEX, when passed in, is the offset of the character immediately
 | 
						|
   following the opening single quote; on exit, SINDEX is left pointing
 | 
						|
   to the closing single quote. */
 | 
						|
static void
 | 
						|
hist_string_extract_single_quoted (string, sindex)
 | 
						|
     char *string;
 | 
						|
     int *sindex;
 | 
						|
{
 | 
						|
  register int i;
 | 
						|
 | 
						|
  for (i = *sindex; string[i] && string[i] != '\''; i++)
 | 
						|
    ;
 | 
						|
 | 
						|
  *sindex = i;
 | 
						|
}
 | 
						|
 | 
						|
static char *
 | 
						|
quote_breaks (s)
 | 
						|
     char *s;
 | 
						|
{
 | 
						|
  register char *p, *r;
 | 
						|
  char *ret;
 | 
						|
  int len = 3;
 | 
						|
 | 
						|
  for (p = s; p && *p; p++, len++)
 | 
						|
    {
 | 
						|
      if (*p == '\'')
 | 
						|
	len += 3;
 | 
						|
      else if (whitespace (*p) || *p == '\n')
 | 
						|
	len += 2;
 | 
						|
    }
 | 
						|
 | 
						|
  r = ret = (char *)xmalloc (len);
 | 
						|
  *r++ = '\'';
 | 
						|
  for (p = s; p && *p; )
 | 
						|
    {
 | 
						|
      if (*p == '\'')
 | 
						|
	{
 | 
						|
	  *r++ = '\'';
 | 
						|
	  *r++ = '\\';
 | 
						|
	  *r++ = '\'';
 | 
						|
	  *r++ = '\'';
 | 
						|
	  p++;
 | 
						|
	}
 | 
						|
      else if (whitespace (*p) || *p == '\n')
 | 
						|
	{
 | 
						|
	  *r++ = '\'';
 | 
						|
	  *r++ = *p++;
 | 
						|
	  *r++ = '\'';
 | 
						|
	}
 | 
						|
      else
 | 
						|
	*r++ = *p++;
 | 
						|
    }
 | 
						|
  *r++ = '\'';
 | 
						|
  *r = '\0';
 | 
						|
  return ret;
 | 
						|
}
 | 
						|
 | 
						|
static char *
 | 
						|
hist_error(s, start, current, errtype)
 | 
						|
      char *s;
 | 
						|
      int start, current, errtype;
 | 
						|
{
 | 
						|
  char *temp;
 | 
						|
  const char *emsg;
 | 
						|
  int ll, elen;
 | 
						|
 | 
						|
  ll = current - start;
 | 
						|
 | 
						|
  switch (errtype)
 | 
						|
    {
 | 
						|
    case EVENT_NOT_FOUND:
 | 
						|
      emsg = "event not found";
 | 
						|
      elen = 15;
 | 
						|
      break;
 | 
						|
    case BAD_WORD_SPEC:
 | 
						|
      emsg = "bad word specifier";
 | 
						|
      elen = 18;
 | 
						|
      break;
 | 
						|
    case SUBST_FAILED:
 | 
						|
      emsg = "substitution failed";
 | 
						|
      elen = 19;
 | 
						|
      break;
 | 
						|
    case BAD_MODIFIER:
 | 
						|
      emsg = "unrecognized history modifier";
 | 
						|
      elen = 29;
 | 
						|
      break;
 | 
						|
    case NO_PREV_SUBST:
 | 
						|
      emsg = "no previous substitution";
 | 
						|
      elen = 24;
 | 
						|
      break;
 | 
						|
    default:
 | 
						|
      emsg = "unknown expansion error";
 | 
						|
      elen = 23;
 | 
						|
      break;
 | 
						|
    }
 | 
						|
 | 
						|
  temp = (char *)xmalloc (ll + elen + 3);
 | 
						|
  strncpy (temp, s + start, ll);
 | 
						|
  temp[ll] = ':';
 | 
						|
  temp[ll + 1] = ' ';
 | 
						|
  strcpy (temp + ll + 2, emsg);
 | 
						|
  return (temp);
 | 
						|
}
 | 
						|
 | 
						|
/* Get a history substitution string from STR starting at *IPTR
 | 
						|
   and return it.  The length is returned in LENPTR.
 | 
						|
 | 
						|
   A backslash can quote the delimiter.  If the string is the
 | 
						|
   empty string, the previous pattern is used.  If there is
 | 
						|
   no previous pattern for the lhs, the last history search
 | 
						|
   string is used.
 | 
						|
 | 
						|
   If IS_RHS is 1, we ignore empty strings and set the pattern
 | 
						|
   to "" anyway.  subst_lhs is not changed if the lhs is empty;
 | 
						|
   subst_rhs is allowed to be set to the empty string. */
 | 
						|
 | 
						|
static char *
 | 
						|
get_subst_pattern (str, iptr, delimiter, is_rhs, lenptr)
 | 
						|
     char *str;
 | 
						|
     int *iptr, delimiter, is_rhs, *lenptr;
 | 
						|
{
 | 
						|
  register int si, i, j, k;
 | 
						|
  char *s;
 | 
						|
#if defined (HANDLE_MULTIBYTE)
 | 
						|
  mbstate_t ps;
 | 
						|
#endif
 | 
						|
 | 
						|
  s = (char *)NULL;
 | 
						|
  i = *iptr;
 | 
						|
 | 
						|
#if defined (HANDLE_MULTIBYTE)
 | 
						|
  memset (&ps, 0, sizeof (mbstate_t));
 | 
						|
  _rl_adjust_point (str, i, &ps);
 | 
						|
#endif
 | 
						|
 | 
						|
  for (si = i; str[si] && str[si] != delimiter; si++)
 | 
						|
#if defined (HANDLE_MULTIBYTE)
 | 
						|
    if (MB_CUR_MAX > 1 && rl_byte_oriented == 0)
 | 
						|
      {
 | 
						|
	int v;
 | 
						|
	if ((v = _rl_get_char_len (str + si, &ps)) > 1)
 | 
						|
	  si += v - 1;
 | 
						|
	else if (str[si] == '\\' && str[si + 1] == delimiter)
 | 
						|
	  si++;
 | 
						|
      }
 | 
						|
    else
 | 
						|
#endif /* HANDLE_MULTIBYTE */
 | 
						|
      if (str[si] == '\\' && str[si + 1] == delimiter)
 | 
						|
	si++;
 | 
						|
 | 
						|
  if (si > i || is_rhs)
 | 
						|
    {
 | 
						|
      s = (char *)xmalloc (si - i + 1);
 | 
						|
      for (j = 0, k = i; k < si; j++, k++)
 | 
						|
	{
 | 
						|
	  /* Remove a backslash quoting the search string delimiter. */
 | 
						|
	  if (str[k] == '\\' && str[k + 1] == delimiter)
 | 
						|
	    k++;
 | 
						|
	  s[j] = str[k];
 | 
						|
	}
 | 
						|
      s[j] = '\0';
 | 
						|
      if (lenptr)
 | 
						|
	*lenptr = j;
 | 
						|
    }
 | 
						|
 | 
						|
  i = si;
 | 
						|
  if (str[i])
 | 
						|
    i++;
 | 
						|
  *iptr = i;
 | 
						|
 | 
						|
  return s;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
postproc_subst_rhs ()
 | 
						|
{
 | 
						|
  char *new;
 | 
						|
  int i, j, new_size;
 | 
						|
 | 
						|
  new = (char *)xmalloc (new_size = subst_rhs_len + subst_lhs_len);
 | 
						|
  for (i = j = 0; i < subst_rhs_len; i++)
 | 
						|
    {
 | 
						|
      if (subst_rhs[i] == '&')
 | 
						|
	{
 | 
						|
	  if (j + subst_lhs_len >= new_size)
 | 
						|
	    new = (char *)xrealloc (new, (new_size = new_size * 2 + subst_lhs_len));
 | 
						|
	  strcpy (new + j, subst_lhs);
 | 
						|
	  j += subst_lhs_len;
 | 
						|
	}
 | 
						|
      else
 | 
						|
	{
 | 
						|
	  /* a single backslash protects the `&' from lhs interpolation */
 | 
						|
	  if (subst_rhs[i] == '\\' && subst_rhs[i + 1] == '&')
 | 
						|
	    i++;
 | 
						|
	  if (j >= new_size)
 | 
						|
	    new = (char *)xrealloc (new, new_size *= 2);
 | 
						|
	  new[j++] = subst_rhs[i];
 | 
						|
	}
 | 
						|
    }
 | 
						|
  new[j] = '\0';
 | 
						|
  free (subst_rhs);
 | 
						|
  subst_rhs = new;
 | 
						|
  subst_rhs_len = j;
 | 
						|
}
 | 
						|
 | 
						|
/* Expand the bulk of a history specifier starting at STRING[START].
 | 
						|
   Returns 0 if everything is OK, -1 if an error occurred, and 1
 | 
						|
   if the `p' modifier was supplied and the caller should just print
 | 
						|
   the returned string.  Returns the new index into string in
 | 
						|
   *END_INDEX_PTR, and the expanded specifier in *RET_STRING. */
 | 
						|
static int
 | 
						|
history_expand_internal (string, start, end_index_ptr, ret_string, current_line)
 | 
						|
     char *string;
 | 
						|
     int start, *end_index_ptr;
 | 
						|
     char **ret_string;
 | 
						|
     char *current_line;	/* for !# */
 | 
						|
{
 | 
						|
  int i, n, starting_index;
 | 
						|
  int substitute_globally, subst_bywords, want_quotes, print_only;
 | 
						|
  char *event, *temp, *result, *tstr, *t, c, *word_spec;
 | 
						|
  int result_len;
 | 
						|
#if defined (HANDLE_MULTIBYTE)
 | 
						|
  mbstate_t ps;
 | 
						|
 | 
						|
  memset (&ps, 0, sizeof (mbstate_t));
 | 
						|
#endif
 | 
						|
 | 
						|
  result = (char *)xmalloc (result_len = 128);
 | 
						|
 | 
						|
  i = start;
 | 
						|
 | 
						|
  /* If it is followed by something that starts a word specifier,
 | 
						|
     then !! is implied as the event specifier. */
 | 
						|
 | 
						|
  if (member (string[i + 1], ":$*%^"))
 | 
						|
    {
 | 
						|
      char fake_s[3];
 | 
						|
      int fake_i = 0;
 | 
						|
      i++;
 | 
						|
      fake_s[0] = fake_s[1] = history_expansion_char;
 | 
						|
      fake_s[2] = '\0';
 | 
						|
      event = get_history_event (fake_s, &fake_i, 0);
 | 
						|
    }
 | 
						|
  else if (string[i + 1] == '#')
 | 
						|
    {
 | 
						|
      i += 2;
 | 
						|
      event = current_line;
 | 
						|
    }
 | 
						|
  else
 | 
						|
    {
 | 
						|
      int quoted_search_delimiter = 0;
 | 
						|
 | 
						|
      /* If the character before this `!' is a double or single
 | 
						|
	 quote, then this expansion takes place inside of the
 | 
						|
	 quoted string.  If we have to search for some text ("!foo"),
 | 
						|
	 allow the delimiter to end the search string. */
 | 
						|
#if defined (HANDLE_MULTIBYTE)
 | 
						|
      if (MB_CUR_MAX > 1 && rl_byte_oriented == 0)
 | 
						|
	{
 | 
						|
	  int ch, l;
 | 
						|
	  l = _rl_find_prev_mbchar (string, i, MB_FIND_ANY);
 | 
						|
	  ch = string[l];
 | 
						|
	  /* XXX - original patch had i - 1 ???  If i == 0 it would fail. */
 | 
						|
	  if (i && (ch == '\'' || ch == '"'))
 | 
						|
	    quoted_search_delimiter = ch;
 | 
						|
	}
 | 
						|
      else
 | 
						|
#endif /* HANDLE_MULTIBYTE */	  
 | 
						|
	if (i && (string[i - 1] == '\'' || string[i - 1] == '"'))
 | 
						|
	  quoted_search_delimiter = string[i - 1];
 | 
						|
 | 
						|
      event = get_history_event (string, &i, quoted_search_delimiter);
 | 
						|
    }
 | 
						|
	  
 | 
						|
  if (event == 0)
 | 
						|
    {
 | 
						|
      *ret_string = hist_error (string, start, i, EVENT_NOT_FOUND);
 | 
						|
      free (result);
 | 
						|
      return (-1);
 | 
						|
    }
 | 
						|
 | 
						|
  /* If a word specifier is found, then do what that requires. */
 | 
						|
  starting_index = i;
 | 
						|
  word_spec = get_history_word_specifier (string, event, &i);
 | 
						|
 | 
						|
  /* There is no such thing as a `malformed word specifier'.  However,
 | 
						|
     it is possible for a specifier that has no match.  In that case,
 | 
						|
     we complain. */
 | 
						|
  if (word_spec == (char *)&error_pointer)
 | 
						|
    {
 | 
						|
      *ret_string = hist_error (string, starting_index, i, BAD_WORD_SPEC);
 | 
						|
      free (result);
 | 
						|
      return (-1);
 | 
						|
    }
 | 
						|
 | 
						|
  /* If no word specifier, than the thing of interest was the event. */
 | 
						|
  temp = word_spec ? savestring (word_spec) : savestring (event);
 | 
						|
  FREE (word_spec);
 | 
						|
 | 
						|
  /* Perhaps there are other modifiers involved.  Do what they say. */
 | 
						|
  want_quotes = substitute_globally = subst_bywords = print_only = 0;
 | 
						|
  starting_index = i;
 | 
						|
 | 
						|
  while (string[i] == ':')
 | 
						|
    {
 | 
						|
      c = string[i + 1];
 | 
						|
 | 
						|
      if (c == 'g' || c == 'a')
 | 
						|
	{
 | 
						|
	  substitute_globally = 1;
 | 
						|
	  i++;
 | 
						|
	  c = string[i + 1];
 | 
						|
	}
 | 
						|
      else if (c == 'G')
 | 
						|
	{
 | 
						|
	  subst_bywords = 1;
 | 
						|
	  i++;
 | 
						|
	  c = string[i + 1];
 | 
						|
	}
 | 
						|
 | 
						|
      switch (c)
 | 
						|
	{
 | 
						|
	default:
 | 
						|
	  *ret_string = hist_error (string, i+1, i+2, BAD_MODIFIER);
 | 
						|
	  free (result);
 | 
						|
	  free (temp);
 | 
						|
	  return -1;
 | 
						|
 | 
						|
	case 'q':
 | 
						|
	  want_quotes = 'q';
 | 
						|
	  break;
 | 
						|
 | 
						|
	case 'x':
 | 
						|
	  want_quotes = 'x';
 | 
						|
	  break;
 | 
						|
 | 
						|
	  /* :p means make this the last executed line.  So we
 | 
						|
	     return an error state after adding this line to the
 | 
						|
	     history. */
 | 
						|
	case 'p':
 | 
						|
	  print_only++;
 | 
						|
	  break;
 | 
						|
 | 
						|
	  /* :t discards all but the last part of the pathname. */
 | 
						|
	case 't':
 | 
						|
	  tstr = strrchr (temp, '/');
 | 
						|
	  if (tstr)
 | 
						|
	    {
 | 
						|
	      tstr++;
 | 
						|
	      t = savestring (tstr);
 | 
						|
	      free (temp);
 | 
						|
	      temp = t;
 | 
						|
	    }
 | 
						|
	  break;
 | 
						|
 | 
						|
	  /* :h discards the last part of a pathname. */
 | 
						|
	case 'h':
 | 
						|
	  tstr = strrchr (temp, '/');
 | 
						|
	  if (tstr)
 | 
						|
	    *tstr = '\0';
 | 
						|
	  break;
 | 
						|
 | 
						|
	  /* :r discards the suffix. */
 | 
						|
	case 'r':
 | 
						|
	  tstr = strrchr (temp, '.');
 | 
						|
	  if (tstr)
 | 
						|
	    *tstr = '\0';
 | 
						|
	  break;
 | 
						|
 | 
						|
	  /* :e discards everything but the suffix. */
 | 
						|
	case 'e':
 | 
						|
	  tstr = strrchr (temp, '.');
 | 
						|
	  if (tstr)
 | 
						|
	    {
 | 
						|
	      t = savestring (tstr);
 | 
						|
	      free (temp);
 | 
						|
	      temp = t;
 | 
						|
	    }
 | 
						|
	  break;
 | 
						|
 | 
						|
	/* :s/this/that substitutes `that' for the first
 | 
						|
	   occurrence of `this'.  :gs/this/that substitutes `that'
 | 
						|
	   for each occurrence of `this'.  :& repeats the last
 | 
						|
	   substitution.  :g& repeats the last substitution
 | 
						|
	   globally. */
 | 
						|
 | 
						|
	case '&':
 | 
						|
	case 's':
 | 
						|
	  {
 | 
						|
	    char *new_event;
 | 
						|
	    int delimiter, failed, si, l_temp, we;
 | 
						|
 | 
						|
	    if (c == 's')
 | 
						|
	      {
 | 
						|
		if (i + 2 < (int)strlen (string))
 | 
						|
		  {
 | 
						|
#if defined (HANDLE_MULTIBYTE)
 | 
						|
		    if (MB_CUR_MAX > 1 && rl_byte_oriented == 0)
 | 
						|
		      {
 | 
						|
			_rl_adjust_point (string, i + 2, &ps);
 | 
						|
			if (_rl_get_char_len (string + i + 2, &ps) > 1)
 | 
						|
			  delimiter = 0;
 | 
						|
			else
 | 
						|
			  delimiter = string[i + 2];
 | 
						|
		      }
 | 
						|
		    else
 | 
						|
#endif /* HANDLE_MULTIBYTE */
 | 
						|
		      delimiter = string[i + 2];
 | 
						|
		  }
 | 
						|
		else
 | 
						|
		  break;	/* no search delimiter */
 | 
						|
 | 
						|
		i += 3;
 | 
						|
 | 
						|
		t = get_subst_pattern (string, &i, delimiter, 0, &subst_lhs_len);
 | 
						|
		/* An empty substitution lhs with no previous substitution
 | 
						|
		   uses the last search string as the lhs. */
 | 
						|
		if (t)
 | 
						|
		  {
 | 
						|
		    FREE (subst_lhs);
 | 
						|
		    subst_lhs = t;
 | 
						|
		  }
 | 
						|
		else if (!subst_lhs)
 | 
						|
		  {
 | 
						|
		    if (search_string && *search_string)
 | 
						|
		      {
 | 
						|
			subst_lhs = savestring (search_string);
 | 
						|
			subst_lhs_len = strlen (subst_lhs);
 | 
						|
		      }
 | 
						|
		    else
 | 
						|
		      {
 | 
						|
			subst_lhs = (char *) NULL;
 | 
						|
			subst_lhs_len = 0;
 | 
						|
		      }
 | 
						|
		  }
 | 
						|
 | 
						|
		FREE (subst_rhs);
 | 
						|
		subst_rhs = get_subst_pattern (string, &i, delimiter, 1, &subst_rhs_len);
 | 
						|
 | 
						|
		/* If `&' appears in the rhs, it's supposed to be replaced
 | 
						|
		   with the lhs. */
 | 
						|
		if (member ('&', subst_rhs))
 | 
						|
		  postproc_subst_rhs ();
 | 
						|
	      }
 | 
						|
	    else
 | 
						|
	      i += 2;
 | 
						|
 | 
						|
	    /* If there is no lhs, the substitution can't succeed. */
 | 
						|
	    if (subst_lhs_len == 0)
 | 
						|
	      {
 | 
						|
		*ret_string = hist_error (string, starting_index, i, NO_PREV_SUBST);
 | 
						|
		free (result);
 | 
						|
		free (temp);
 | 
						|
		return -1;
 | 
						|
	      }
 | 
						|
 | 
						|
	    l_temp = strlen (temp);
 | 
						|
	    /* Ignore impossible cases. */
 | 
						|
	    if (subst_lhs_len > l_temp)
 | 
						|
	      {
 | 
						|
		*ret_string = hist_error (string, starting_index, i, SUBST_FAILED);
 | 
						|
		free (result);
 | 
						|
		free (temp);
 | 
						|
		return (-1);
 | 
						|
	      }
 | 
						|
 | 
						|
	    /* Find the first occurrence of THIS in TEMP. */
 | 
						|
	    /* Substitute SUBST_RHS for SUBST_LHS in TEMP.  There are three
 | 
						|
	       cases to consider:
 | 
						|
 | 
						|
		 1.  substitute_globally == subst_bywords == 0
 | 
						|
		 2.  substitute_globally == 1 && subst_bywords == 0
 | 
						|
		 3.  substitute_globally == 0 && subst_bywords == 1
 | 
						|
 | 
						|
	       In the first case, we substitute for the first occurrence only.
 | 
						|
	       In the second case, we substitute for every occurrence.
 | 
						|
	       In the third case, we tokenize into words and substitute the
 | 
						|
	       first occurrence of each word. */
 | 
						|
 | 
						|
	    si = we = 0;
 | 
						|
	    for (failed = 1; (si + subst_lhs_len) <= l_temp; si++)
 | 
						|
	      {
 | 
						|
		/* First skip whitespace and find word boundaries if
 | 
						|
		   we're past the end of the word boundary we found
 | 
						|
		   the last time. */
 | 
						|
		if (subst_bywords && si > we)
 | 
						|
		  {
 | 
						|
		    for (; temp[si] && whitespace (temp[si]); si++)
 | 
						|
		      ;
 | 
						|
		    we = history_tokenize_word (temp, si);
 | 
						|
		  }
 | 
						|
 | 
						|
		if (STREQN (temp+si, subst_lhs, subst_lhs_len))
 | 
						|
		  {
 | 
						|
		    int len = subst_rhs_len - subst_lhs_len + l_temp;
 | 
						|
		    new_event = (char *)xmalloc (1 + len);
 | 
						|
		    strncpy (new_event, temp, si);
 | 
						|
		    strncpy (new_event + si, subst_rhs, subst_rhs_len);
 | 
						|
		    strncpy (new_event + si + subst_rhs_len,
 | 
						|
			     temp + si + subst_lhs_len,
 | 
						|
			     l_temp - (si + subst_lhs_len));
 | 
						|
		    new_event[len] = '\0';
 | 
						|
		    free (temp);
 | 
						|
		    temp = new_event;
 | 
						|
 | 
						|
		    failed = 0;
 | 
						|
 | 
						|
		    if (substitute_globally)
 | 
						|
		      {
 | 
						|
			/* Reported to fix a bug that causes it to skip every
 | 
						|
			   other match when matching a single character.  Was
 | 
						|
			   si += subst_rhs_len previously. */
 | 
						|
			si += subst_rhs_len - 1;
 | 
						|
			l_temp = strlen (temp);
 | 
						|
			substitute_globally++;
 | 
						|
			continue;
 | 
						|
		      }
 | 
						|
		    else if (subst_bywords)
 | 
						|
		      {
 | 
						|
			si = we;
 | 
						|
			l_temp = strlen (temp);
 | 
						|
			continue;
 | 
						|
		      }
 | 
						|
		    else
 | 
						|
		      break;
 | 
						|
		  }
 | 
						|
	      }
 | 
						|
 | 
						|
	    if (substitute_globally > 1)
 | 
						|
	      {
 | 
						|
		substitute_globally = 0;
 | 
						|
		continue;	/* don't want to increment i */
 | 
						|
	      }
 | 
						|
 | 
						|
	    if (failed == 0)
 | 
						|
	      continue;		/* don't want to increment i */
 | 
						|
 | 
						|
	    *ret_string = hist_error (string, starting_index, i, SUBST_FAILED);
 | 
						|
	    free (result);
 | 
						|
	    free (temp);
 | 
						|
	    return (-1);
 | 
						|
	  }
 | 
						|
	}
 | 
						|
      i += 2;
 | 
						|
    }
 | 
						|
  /* Done with modfiers. */
 | 
						|
  /* Believe it or not, we have to back the pointer up by one. */
 | 
						|
  --i;
 | 
						|
 | 
						|
  if (want_quotes)
 | 
						|
    {
 | 
						|
      char *x;
 | 
						|
 | 
						|
      if (want_quotes == 'q')
 | 
						|
	x = sh_single_quote (temp);
 | 
						|
      else if (want_quotes == 'x')
 | 
						|
	x = quote_breaks (temp);
 | 
						|
      else
 | 
						|
	x = savestring (temp);
 | 
						|
 | 
						|
      free (temp);
 | 
						|
      temp = x;
 | 
						|
    }
 | 
						|
 | 
						|
  n = strlen (temp);
 | 
						|
  if (n >= result_len)
 | 
						|
    result = (char *)xrealloc (result, n + 2);
 | 
						|
  strcpy (result, temp);
 | 
						|
  free (temp);
 | 
						|
 | 
						|
  *end_index_ptr = i;
 | 
						|
  *ret_string = result;
 | 
						|
  return (print_only);
 | 
						|
}
 | 
						|
 | 
						|
/* Expand the string STRING, placing the result into OUTPUT, a pointer
 | 
						|
   to a string.  Returns:
 | 
						|
 | 
						|
  -1) If there was an error in expansion.
 | 
						|
   0) If no expansions took place (or, if the only change in
 | 
						|
      the text was the de-slashifying of the history expansion
 | 
						|
      character)
 | 
						|
   1) If expansions did take place
 | 
						|
   2) If the `p' modifier was given and the caller should print the result
 | 
						|
 | 
						|
  If an error ocurred in expansion, then OUTPUT contains a descriptive
 | 
						|
  error message. */
 | 
						|
 | 
						|
#define ADD_STRING(s) \
 | 
						|
	do \
 | 
						|
	  { \
 | 
						|
	    int sl = strlen (s); \
 | 
						|
	    j += sl; \
 | 
						|
	    if (j >= result_len) \
 | 
						|
	      { \
 | 
						|
		while (j >= result_len) \
 | 
						|
		  result_len += 128; \
 | 
						|
		result = (char *)xrealloc (result, result_len); \
 | 
						|
	      } \
 | 
						|
	    strcpy (result + j - sl, s); \
 | 
						|
	  } \
 | 
						|
	while (0)
 | 
						|
 | 
						|
#define ADD_CHAR(c) \
 | 
						|
	do \
 | 
						|
	  { \
 | 
						|
	    if (j >= result_len - 1) \
 | 
						|
	      result = (char *)xrealloc (result, result_len += 64); \
 | 
						|
	    result[j++] = c; \
 | 
						|
	    result[j] = '\0'; \
 | 
						|
	  } \
 | 
						|
	while (0)
 | 
						|
 | 
						|
int
 | 
						|
history_expand (hstring, output)
 | 
						|
     char *hstring;
 | 
						|
     char **output;
 | 
						|
{
 | 
						|
  register int j;
 | 
						|
  int i, r, l, passc, cc, modified, eindex, only_printing, dquote;
 | 
						|
  char *string;
 | 
						|
 | 
						|
  /* The output string, and its length. */
 | 
						|
  int result_len;
 | 
						|
  char *result;
 | 
						|
 | 
						|
#if defined (HANDLE_MULTIBYTE)
 | 
						|
  char mb[MB_LEN_MAX];
 | 
						|
  mbstate_t ps;
 | 
						|
#endif
 | 
						|
 | 
						|
  /* Used when adding the string. */
 | 
						|
  char *temp;
 | 
						|
 | 
						|
  if (output == 0)
 | 
						|
    return 0;
 | 
						|
 | 
						|
  /* Setting the history expansion character to 0 inhibits all
 | 
						|
     history expansion. */
 | 
						|
  if (history_expansion_char == 0)
 | 
						|
    {
 | 
						|
      *output = savestring (hstring);
 | 
						|
      return (0);
 | 
						|
    }
 | 
						|
    
 | 
						|
  /* Prepare the buffer for printing error messages. */
 | 
						|
  result = (char *)xmalloc (result_len = 256);
 | 
						|
  result[0] = '\0';
 | 
						|
 | 
						|
  only_printing = modified = 0;
 | 
						|
  l = strlen (hstring);
 | 
						|
 | 
						|
  /* Grovel the string.  Only backslash and single quotes can quote the
 | 
						|
     history escape character.  We also handle arg specifiers. */
 | 
						|
 | 
						|
  /* Before we grovel forever, see if the history_expansion_char appears
 | 
						|
     anywhere within the text. */
 | 
						|
 | 
						|
  /* The quick substitution character is a history expansion all right.  That
 | 
						|
     is to say, "^this^that^" is equivalent to "!!:s^this^that^", and in fact,
 | 
						|
     that is the substitution that we do. */
 | 
						|
  if (hstring[0] == history_subst_char)
 | 
						|
    {
 | 
						|
      string = (char *)xmalloc (l + 5);
 | 
						|
 | 
						|
      string[0] = string[1] = history_expansion_char;
 | 
						|
      string[2] = ':';
 | 
						|
      string[3] = 's';
 | 
						|
      strcpy (string + 4, hstring);
 | 
						|
      l += 4;
 | 
						|
    }
 | 
						|
  else
 | 
						|
    {
 | 
						|
#if defined (HANDLE_MULTIBYTE)
 | 
						|
      memset (&ps, 0, sizeof (mbstate_t));
 | 
						|
#endif
 | 
						|
 | 
						|
      string = hstring;
 | 
						|
      /* If not quick substitution, still maybe have to do expansion. */
 | 
						|
 | 
						|
      /* `!' followed by one of the characters in history_no_expand_chars
 | 
						|
	 is NOT an expansion. */
 | 
						|
      for (i = dquote = 0; string[i]; i++)
 | 
						|
	{
 | 
						|
#if defined (HANDLE_MULTIBYTE)
 | 
						|
	  if (MB_CUR_MAX > 1 && rl_byte_oriented == 0)
 | 
						|
	    {
 | 
						|
	      int v;
 | 
						|
	      v = _rl_get_char_len (string + i, &ps);
 | 
						|
	      if (v > 1)
 | 
						|
		{
 | 
						|
		  i += v - 1;
 | 
						|
		  continue;
 | 
						|
		}
 | 
						|
	    }
 | 
						|
#endif /* HANDLE_MULTIBYTE */
 | 
						|
 | 
						|
	  cc = string[i + 1];
 | 
						|
	  /* The history_comment_char, if set, appearing at the beginning
 | 
						|
	     of a word signifies that the rest of the line should not have
 | 
						|
	     history expansion performed on it.
 | 
						|
	     Skip the rest of the line and break out of the loop. */
 | 
						|
	  if (history_comment_char && string[i] == history_comment_char &&
 | 
						|
	      (i == 0 || member (string[i - 1], history_word_delimiters)))
 | 
						|
	    {
 | 
						|
	      while (string[i])
 | 
						|
		i++;
 | 
						|
	      break;
 | 
						|
	    }
 | 
						|
	  else if (string[i] == history_expansion_char)
 | 
						|
	    {
 | 
						|
	      if (!cc || member (cc, history_no_expand_chars))
 | 
						|
		continue;
 | 
						|
	      /* If the calling application has set
 | 
						|
		 history_inhibit_expansion_function to a function that checks
 | 
						|
		 for special cases that should not be history expanded,
 | 
						|
		 call the function and skip the expansion if it returns a
 | 
						|
		 non-zero value. */
 | 
						|
	      else if (history_inhibit_expansion_function &&
 | 
						|
			(*history_inhibit_expansion_function) (string, i))
 | 
						|
		continue;
 | 
						|
	      else
 | 
						|
		break;
 | 
						|
	    }
 | 
						|
	  /* Shell-like quoting: allow backslashes to quote double quotes
 | 
						|
	     inside a double-quoted string. */
 | 
						|
	  else if (dquote && string[i] == '\\' && cc == '"')
 | 
						|
	    i++;
 | 
						|
	  /* More shell-like quoting:  if we're paying attention to single
 | 
						|
	     quotes and letting them quote the history expansion character,
 | 
						|
	     then we need to pay attention to double quotes, because single
 | 
						|
	     quotes are not special inside double-quoted strings. */
 | 
						|
	  else if (history_quotes_inhibit_expansion && string[i] == '"')
 | 
						|
	    {
 | 
						|
	      dquote = 1 - dquote;
 | 
						|
	    }
 | 
						|
	  else if (dquote == 0 && history_quotes_inhibit_expansion && string[i] == '\'')
 | 
						|
	    {
 | 
						|
	      /* If this is bash, single quotes inhibit history expansion. */
 | 
						|
	      i++;
 | 
						|
	      hist_string_extract_single_quoted (string, &i);
 | 
						|
	    }
 | 
						|
	  else if (history_quotes_inhibit_expansion && string[i] == '\\')
 | 
						|
	    {
 | 
						|
	      /* If this is bash, allow backslashes to quote single
 | 
						|
		 quotes and the history expansion character. */
 | 
						|
	      if (cc == '\'' || cc == history_expansion_char)
 | 
						|
		i++;
 | 
						|
	    }
 | 
						|
	  
 | 
						|
	}
 | 
						|
	  
 | 
						|
      if (string[i] != history_expansion_char)
 | 
						|
	{
 | 
						|
	  free (result);
 | 
						|
	  *output = savestring (string);
 | 
						|
	  return (0);
 | 
						|
	}
 | 
						|
    }
 | 
						|
 | 
						|
  /* Extract and perform the substitution. */
 | 
						|
  for (passc = dquote = i = j = 0; i < l; i++)
 | 
						|
    {
 | 
						|
      int tchar = string[i];
 | 
						|
 | 
						|
      if (passc)
 | 
						|
	{
 | 
						|
	  passc = 0;
 | 
						|
	  ADD_CHAR (tchar);
 | 
						|
	  continue;
 | 
						|
	}
 | 
						|
 | 
						|
#if defined (HANDLE_MULTIBYTE)
 | 
						|
      if (MB_CUR_MAX > 1 && rl_byte_oriented == 0)
 | 
						|
	{
 | 
						|
	  int k, c;
 | 
						|
 | 
						|
	  c = tchar;
 | 
						|
	  memset (mb, 0, sizeof (mb));
 | 
						|
	  for (k = 0; k < MB_LEN_MAX; k++)
 | 
						|
	    {
 | 
						|
	      mb[k] = (char)c;
 | 
						|
	      memset (&ps, 0, sizeof (mbstate_t));
 | 
						|
	      if (_rl_get_char_len (mb, &ps) == -2)
 | 
						|
		c = string[++i];
 | 
						|
	      else
 | 
						|
		break;
 | 
						|
	    }
 | 
						|
	  if (strlen (mb) > 1)
 | 
						|
	    {
 | 
						|
	      ADD_STRING (mb);
 | 
						|
	      break;
 | 
						|
	    }
 | 
						|
	}
 | 
						|
#endif /* HANDLE_MULTIBYTE */
 | 
						|
 | 
						|
      if (tchar == history_expansion_char)
 | 
						|
	tchar = -3;
 | 
						|
      else if (tchar == history_comment_char)
 | 
						|
	tchar = -2;
 | 
						|
 | 
						|
      switch (tchar)
 | 
						|
	{
 | 
						|
	default:
 | 
						|
	  ADD_CHAR (string[i]);
 | 
						|
	  break;
 | 
						|
 | 
						|
	case '\\':
 | 
						|
	  passc++;
 | 
						|
	  ADD_CHAR (tchar);
 | 
						|
	  break;
 | 
						|
 | 
						|
	case '"':
 | 
						|
	  dquote = 1 - dquote;
 | 
						|
	  ADD_CHAR (tchar);
 | 
						|
	  break;
 | 
						|
	  
 | 
						|
	case '\'':
 | 
						|
	  {
 | 
						|
	    /* If history_quotes_inhibit_expansion is set, single quotes
 | 
						|
	       inhibit history expansion. */
 | 
						|
	    if (dquote == 0 && history_quotes_inhibit_expansion)
 | 
						|
	      {
 | 
						|
		int quote, slen;
 | 
						|
 | 
						|
		quote = i++;
 | 
						|
		hist_string_extract_single_quoted (string, &i);
 | 
						|
 | 
						|
		slen = i - quote + 2;
 | 
						|
		temp = (char *)xmalloc (slen);
 | 
						|
		strncpy (temp, string + quote, slen);
 | 
						|
		temp[slen - 1] = '\0';
 | 
						|
		ADD_STRING (temp);
 | 
						|
		free (temp);
 | 
						|
	      }
 | 
						|
	    else
 | 
						|
	      ADD_CHAR (string[i]);
 | 
						|
	    break;
 | 
						|
	  }
 | 
						|
 | 
						|
	case -2:		/* history_comment_char */
 | 
						|
	  if (i == 0 || member (string[i - 1], history_word_delimiters))
 | 
						|
	    {
 | 
						|
	      temp = (char *)xmalloc (l - i + 1);
 | 
						|
	      strcpy (temp, string + i);
 | 
						|
	      ADD_STRING (temp);
 | 
						|
	      free (temp);
 | 
						|
	      i = l;
 | 
						|
	    }
 | 
						|
	  else
 | 
						|
	    ADD_CHAR (string[i]);
 | 
						|
	  break;
 | 
						|
 | 
						|
	case -3:		/* history_expansion_char */
 | 
						|
	  cc = string[i + 1];
 | 
						|
 | 
						|
	  /* If the history_expansion_char is followed by one of the
 | 
						|
	     characters in history_no_expand_chars, then it is not a
 | 
						|
	     candidate for expansion of any kind. */
 | 
						|
	  if (member (cc, history_no_expand_chars))
 | 
						|
	    {
 | 
						|
	      ADD_CHAR (string[i]);
 | 
						|
	      break;
 | 
						|
	    }
 | 
						|
 | 
						|
#if defined (NO_BANG_HASH_MODIFIERS)
 | 
						|
	  /* There is something that is listed as a `word specifier' in csh
 | 
						|
	     documentation which means `the expanded text to this point'.
 | 
						|
	     That is not a word specifier, it is an event specifier.  If we
 | 
						|
	     don't want to allow modifiers with `!#', just stick the current
 | 
						|
	     output line in again. */
 | 
						|
	  if (cc == '#')
 | 
						|
	    {
 | 
						|
	      if (result)
 | 
						|
		{
 | 
						|
		  temp = (char *)xmalloc (1 + strlen (result));
 | 
						|
		  strcpy (temp, result);
 | 
						|
		  ADD_STRING (temp);
 | 
						|
		  free (temp);
 | 
						|
		}
 | 
						|
	      i++;
 | 
						|
	      break;
 | 
						|
	    }
 | 
						|
#endif
 | 
						|
 | 
						|
	  r = history_expand_internal (string, i, &eindex, &temp, result);
 | 
						|
	  if (r < 0)
 | 
						|
	    {
 | 
						|
	      *output = temp;
 | 
						|
	      free (result);
 | 
						|
	      if (string != hstring)
 | 
						|
		free (string);
 | 
						|
	      return -1;
 | 
						|
	    }
 | 
						|
	  else
 | 
						|
	    {
 | 
						|
	      if (temp)
 | 
						|
		{
 | 
						|
		  modified++;
 | 
						|
		  if (*temp)
 | 
						|
		    ADD_STRING (temp);
 | 
						|
		  free (temp);
 | 
						|
		}
 | 
						|
	      only_printing = r == 1;
 | 
						|
	      i = eindex;
 | 
						|
	    }
 | 
						|
	  break;
 | 
						|
	}
 | 
						|
    }
 | 
						|
 | 
						|
  *output = result;
 | 
						|
  if (string != hstring)
 | 
						|
    free (string);
 | 
						|
 | 
						|
  if (only_printing)
 | 
						|
    {
 | 
						|
#if 0
 | 
						|
      add_history (result);
 | 
						|
#endif
 | 
						|
      return (2);
 | 
						|
    }
 | 
						|
 | 
						|
  return (modified != 0);
 | 
						|
}
 | 
						|
 | 
						|
/* Return a consed string which is the word specified in SPEC, and found
 | 
						|
   in FROM.  NULL is returned if there is no spec.  The address of
 | 
						|
   ERROR_POINTER is returned if the word specified cannot be found.
 | 
						|
   CALLER_INDEX is the offset in SPEC to start looking; it is updated
 | 
						|
   to point to just after the last character parsed. */
 | 
						|
static char *
 | 
						|
get_history_word_specifier (spec, from, caller_index)
 | 
						|
     char *spec, *from;
 | 
						|
     int *caller_index;
 | 
						|
{
 | 
						|
  register int i = *caller_index;
 | 
						|
  int first, last;
 | 
						|
  int expecting_word_spec = 0;
 | 
						|
  char *result;
 | 
						|
 | 
						|
  /* The range of words to return doesn't exist yet. */
 | 
						|
  first = last = 0;
 | 
						|
  result = (char *)NULL;
 | 
						|
 | 
						|
  /* If we found a colon, then this *must* be a word specification.  If
 | 
						|
     it isn't, then it is an error. */
 | 
						|
  if (spec[i] == ':')
 | 
						|
    {
 | 
						|
      i++;
 | 
						|
      expecting_word_spec++;
 | 
						|
    }
 | 
						|
 | 
						|
  /* Handle special cases first. */
 | 
						|
 | 
						|
  /* `%' is the word last searched for. */
 | 
						|
  if (spec[i] == '%')
 | 
						|
    {
 | 
						|
      *caller_index = i + 1;
 | 
						|
      return (search_match ? savestring (search_match) : savestring (""));
 | 
						|
    }
 | 
						|
 | 
						|
  /* `*' matches all of the arguments, but not the command. */
 | 
						|
  if (spec[i] == '*')
 | 
						|
    {
 | 
						|
      *caller_index = i + 1;
 | 
						|
      result = history_arg_extract (1, '$', from);
 | 
						|
      return (result ? result : savestring (""));
 | 
						|
    }
 | 
						|
 | 
						|
  /* `$' is last arg. */
 | 
						|
  if (spec[i] == '$')
 | 
						|
    {
 | 
						|
      *caller_index = i + 1;
 | 
						|
      return (history_arg_extract ('$', '$', from));
 | 
						|
    }
 | 
						|
 | 
						|
  /* Try to get FIRST and LAST figured out. */
 | 
						|
 | 
						|
  if (spec[i] == '-')
 | 
						|
    first = 0;
 | 
						|
  else if (spec[i] == '^')
 | 
						|
    {
 | 
						|
      first = 1;
 | 
						|
      i++;
 | 
						|
    }
 | 
						|
  else if (_rl_digit_p (spec[i]) && expecting_word_spec)
 | 
						|
    {
 | 
						|
      for (first = 0; _rl_digit_p (spec[i]); i++)
 | 
						|
	first = (first * 10) + _rl_digit_value (spec[i]);
 | 
						|
    }
 | 
						|
  else
 | 
						|
    return ((char *)NULL);	/* no valid `first' for word specifier */
 | 
						|
 | 
						|
  if (spec[i] == '^' || spec[i] == '*')
 | 
						|
    {
 | 
						|
      last = (spec[i] == '^') ? 1 : '$';	/* x* abbreviates x-$ */
 | 
						|
      i++;
 | 
						|
    }
 | 
						|
  else if (spec[i] != '-')
 | 
						|
    last = first;
 | 
						|
  else
 | 
						|
    {
 | 
						|
      i++;
 | 
						|
 | 
						|
      if (_rl_digit_p (spec[i]))
 | 
						|
	{
 | 
						|
	  for (last = 0; _rl_digit_p (spec[i]); i++)
 | 
						|
	    last = (last * 10) + _rl_digit_value (spec[i]);
 | 
						|
	}
 | 
						|
      else if (spec[i] == '$')
 | 
						|
	{
 | 
						|
	  i++;
 | 
						|
	  last = '$';
 | 
						|
	}
 | 
						|
#if 0
 | 
						|
      else if (!spec[i] || spec[i] == ':')
 | 
						|
	/* check against `:' because there could be a modifier separator */
 | 
						|
#else
 | 
						|
      else
 | 
						|
	/* csh seems to allow anything to terminate the word spec here,
 | 
						|
	   leaving it as an abbreviation. */
 | 
						|
#endif
 | 
						|
	last = -1;		/* x- abbreviates x-$ omitting word `$' */
 | 
						|
    }
 | 
						|
 | 
						|
  *caller_index = i;
 | 
						|
 | 
						|
  if (last >= first || last == '$' || last < 0)
 | 
						|
    result = history_arg_extract (first, last, from);
 | 
						|
 | 
						|
  return (result ? result : (char *)&error_pointer);
 | 
						|
}
 | 
						|
 | 
						|
/* Extract the args specified, starting at FIRST, and ending at LAST.
 | 
						|
   The args are taken from STRING.  If either FIRST or LAST is < 0,
 | 
						|
   then make that arg count from the right (subtract from the number of
 | 
						|
   tokens, so that FIRST = -1 means the next to last token on the line).
 | 
						|
   If LAST is `$' the last arg from STRING is used. */
 | 
						|
char *
 | 
						|
history_arg_extract (first, last, string)
 | 
						|
     int first, last;
 | 
						|
     const char *string;
 | 
						|
{
 | 
						|
  register int i, len;
 | 
						|
  char *result;
 | 
						|
  int size, offset;
 | 
						|
  char **list;
 | 
						|
 | 
						|
  /* XXX - think about making history_tokenize return a struct array,
 | 
						|
     each struct in array being a string and a length to avoid the
 | 
						|
     calls to strlen below. */
 | 
						|
  if ((list = history_tokenize (string)) == NULL)
 | 
						|
    return ((char *)NULL);
 | 
						|
 | 
						|
  for (len = 0; list[len]; len++)
 | 
						|
    ;
 | 
						|
 | 
						|
  if (last < 0)
 | 
						|
    last = len + last - 1;
 | 
						|
 | 
						|
  if (first < 0)
 | 
						|
    first = len + first - 1;
 | 
						|
 | 
						|
  if (last == '$')
 | 
						|
    last = len - 1;
 | 
						|
 | 
						|
  if (first == '$')
 | 
						|
    first = len - 1;
 | 
						|
 | 
						|
  last++;
 | 
						|
 | 
						|
  if (first >= len || last > len || first < 0 || last < 0 || first > last)
 | 
						|
    result = ((char *)NULL);
 | 
						|
  else
 | 
						|
    {
 | 
						|
      for (size = 0, i = first; i < last; i++)
 | 
						|
	size += strlen (list[i]) + 1;
 | 
						|
      result = (char *)xmalloc (size + 1);
 | 
						|
      result[0] = '\0';
 | 
						|
 | 
						|
      for (i = first, offset = 0; i < last; i++)
 | 
						|
	{
 | 
						|
	  strcpy (result + offset, list[i]);
 | 
						|
	  offset += strlen (list[i]);
 | 
						|
	  if (i + 1 < last)
 | 
						|
	    {
 | 
						|
      	      result[offset++] = ' ';
 | 
						|
	      result[offset] = 0;
 | 
						|
	    }
 | 
						|
	}
 | 
						|
    }
 | 
						|
 | 
						|
  for (i = 0; i < len; i++)
 | 
						|
    free (list[i]);
 | 
						|
  free (list);
 | 
						|
 | 
						|
  return (result);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
history_tokenize_word (string, ind)
 | 
						|
     const char *string;
 | 
						|
     int ind;
 | 
						|
{
 | 
						|
  register int i;
 | 
						|
  int delimiter;
 | 
						|
 | 
						|
  i = ind;
 | 
						|
  delimiter = 0;
 | 
						|
 | 
						|
  if (member (string[i], "()\n"))
 | 
						|
    {
 | 
						|
      i++;
 | 
						|
      return i;
 | 
						|
    }
 | 
						|
 | 
						|
  if (member (string[i], "<>;&|$"))
 | 
						|
    {
 | 
						|
      int peek = string[i + 1];
 | 
						|
 | 
						|
      if (peek == string[i] && peek != '$')
 | 
						|
	{
 | 
						|
	  if (peek == '<' && string[i + 2] == '-')
 | 
						|
	    i++;
 | 
						|
	  else if (peek == '<' && string[i + 2] == '<')
 | 
						|
	    i++;
 | 
						|
	  i += 2;
 | 
						|
	  return i;
 | 
						|
	}
 | 
						|
      else
 | 
						|
	{
 | 
						|
	  if ((peek == '&' && (string[i] == '>' || string[i] == '<')) ||
 | 
						|
	      (peek == '>' && string[i] == '&') ||
 | 
						|
	      (peek == '(' && (string[i] == '>' || string[i] == '<')) || /* ) */
 | 
						|
	      (peek == '(' && string[i] == '$')) /* ) */
 | 
						|
	    {
 | 
						|
	      i += 2;
 | 
						|
	      return i;
 | 
						|
	    }
 | 
						|
	}
 | 
						|
 | 
						|
      if (string[i] != '$')
 | 
						|
	{
 | 
						|
	  i++;
 | 
						|
	  return i;
 | 
						|
	}
 | 
						|
    }
 | 
						|
 | 
						|
  /* Get word from string + i; */
 | 
						|
 | 
						|
  if (member (string[i], HISTORY_QUOTE_CHARACTERS))
 | 
						|
    delimiter = string[i++];
 | 
						|
 | 
						|
  for (; string[i]; i++)
 | 
						|
    {
 | 
						|
      if (string[i] == '\\' && string[i + 1] == '\n')
 | 
						|
	{
 | 
						|
	  i++;
 | 
						|
	  continue;
 | 
						|
	}
 | 
						|
 | 
						|
      if (string[i] == '\\' && delimiter != '\'' &&
 | 
						|
	  (delimiter != '"' || member (string[i], slashify_in_quotes)))
 | 
						|
	{
 | 
						|
	  i++;
 | 
						|
	  continue;
 | 
						|
	}
 | 
						|
 | 
						|
      if (delimiter && string[i] == delimiter)
 | 
						|
	{
 | 
						|
	  delimiter = 0;
 | 
						|
	  continue;
 | 
						|
	}
 | 
						|
 | 
						|
      if (!delimiter && (member (string[i], history_word_delimiters)))
 | 
						|
	break;
 | 
						|
 | 
						|
      if (!delimiter && member (string[i], HISTORY_QUOTE_CHARACTERS))
 | 
						|
	delimiter = string[i];
 | 
						|
    }
 | 
						|
 | 
						|
  return i;
 | 
						|
}
 | 
						|
 | 
						|
static char *
 | 
						|
history_substring (string, start, end)
 | 
						|
     const char *string;
 | 
						|
     int start, end;
 | 
						|
{
 | 
						|
  register int len;
 | 
						|
  register char *result;
 | 
						|
 | 
						|
  len = end - start;
 | 
						|
  result = (char *)xmalloc (len + 1);
 | 
						|
  strncpy (result, string + start, len);
 | 
						|
  result[len] = '\0';
 | 
						|
  return result;
 | 
						|
}
 | 
						|
 | 
						|
/* Parse STRING into tokens and return an array of strings.  If WIND is
 | 
						|
   not -1 and INDP is not null, we also want the word surrounding index
 | 
						|
   WIND.  The position in the returned array of strings is returned in
 | 
						|
   *INDP. */
 | 
						|
static char **
 | 
						|
history_tokenize_internal (string, wind, indp)
 | 
						|
     const char *string;
 | 
						|
     int wind, *indp;
 | 
						|
{
 | 
						|
  char **result;
 | 
						|
  register int i, start, result_index, size;
 | 
						|
 | 
						|
  /* If we're searching for a string that's not part of a word (e.g., " "),
 | 
						|
     make sure we set *INDP to a reasonable value. */
 | 
						|
  if (indp && wind != -1)
 | 
						|
    *indp = -1;
 | 
						|
 | 
						|
  /* Get a token, and stuff it into RESULT.  The tokens are split
 | 
						|
     exactly where the shell would split them. */
 | 
						|
  for (i = result_index = size = 0, result = (char **)NULL; string[i]; )
 | 
						|
    {
 | 
						|
      /* Skip leading whitespace. */
 | 
						|
      for (; string[i] && whitespace (string[i]); i++)
 | 
						|
	;
 | 
						|
      if (string[i] == 0 || string[i] == history_comment_char)
 | 
						|
	return (result);
 | 
						|
 | 
						|
      start = i;
 | 
						|
 | 
						|
      i = history_tokenize_word (string, start);
 | 
						|
 | 
						|
      /* If we have a non-whitespace delimiter character (which would not be
 | 
						|
	 skipped by the loop above), use it and any adjacent delimiters to
 | 
						|
	 make a separate field.  Any adjacent white space will be skipped the
 | 
						|
	 next time through the loop. */
 | 
						|
      if (i == start && history_word_delimiters)
 | 
						|
	{
 | 
						|
	  i++;
 | 
						|
	  while (string[i] && member (string[i], history_word_delimiters))
 | 
						|
	    i++;
 | 
						|
	}
 | 
						|
 | 
						|
      /* If we are looking for the word in which the character at a
 | 
						|
	 particular index falls, remember it. */
 | 
						|
      if (indp && wind != -1 && wind >= start && wind < i)
 | 
						|
        *indp = result_index;
 | 
						|
 | 
						|
      if (result_index + 2 >= size)
 | 
						|
	result = (char **)xrealloc (result, ((size += 10) * sizeof (char *)));
 | 
						|
 | 
						|
      result[result_index++] = history_substring (string, start, i);
 | 
						|
      result[result_index] = (char *)NULL;
 | 
						|
    }
 | 
						|
 | 
						|
  return (result);
 | 
						|
}
 | 
						|
 | 
						|
/* Return an array of tokens, much as the shell might.  The tokens are
 | 
						|
   parsed out of STRING. */
 | 
						|
char **
 | 
						|
history_tokenize (string)
 | 
						|
     const char *string;
 | 
						|
{
 | 
						|
  return (history_tokenize_internal (string, -1, (int *)NULL));
 | 
						|
}
 | 
						|
 | 
						|
/* Find and return the word which contains the character at index IND
 | 
						|
   in the history line LINE.  Used to save the word matched by the
 | 
						|
   last history !?string? search. */
 | 
						|
static char *
 | 
						|
history_find_word (line, ind)
 | 
						|
     char *line;
 | 
						|
     int ind;
 | 
						|
{
 | 
						|
  char **words, *s;
 | 
						|
  int i, wind;
 | 
						|
 | 
						|
  words = history_tokenize_internal (line, ind, &wind);
 | 
						|
  if (wind == -1 || words == 0)
 | 
						|
    return ((char *)NULL);
 | 
						|
  s = words[wind];
 | 
						|
  for (i = 0; i < wind; i++)
 | 
						|
    free (words[i]);
 | 
						|
  for (i = wind + 1; words[i]; i++)
 | 
						|
    free (words[i]);
 | 
						|
  free (words);
 | 
						|
  return s;
 | 
						|
}
 |