diff options
Diffstat (limited to 'gnu/lib/libreadline/history.c')
-rw-r--r-- | gnu/lib/libreadline/history.c | 1516 |
1 files changed, 887 insertions, 629 deletions
diff --git a/gnu/lib/libreadline/history.c b/gnu/lib/libreadline/history.c index 45c6c78..03d52ed 100644 --- a/gnu/lib/libreadline/history.c +++ b/gnu/lib/libreadline/history.c @@ -24,12 +24,6 @@ don't have to know what data types are used, just what functions you can call. I think I have done that. */ -#if defined (STATIC_MALLOC) -static char *xmalloc (), *xrealloc (); -#else -extern char *xmalloc (), *xrealloc (); -#endif /* STATIC_MALLOC */ - #include <stdio.h> #include <sys/types.h> #include <sys/file.h> @@ -55,21 +49,19 @@ extern char *xmalloc (), *xrealloc (); extern int errno; #endif /* !errno */ -#if defined (__GNUC__) -# undef alloca -# define alloca __builtin_alloca +#include "memalloc.h" +#include "history.h" + +#if defined (STATIC_MALLOC) +static char *xmalloc (), *xrealloc (); #else -# if defined (sparc) || defined (HAVE_ALLOCA_H) -# include <alloca.h> -# else -extern char *alloca (); -# endif /* sparc || HAVE_ALLOCA_H */ -#endif /* !__GNU_C__ */ +extern char *xmalloc (), *xrealloc (); +#endif /* STATIC_MALLOC */ -#include "history.h" +#define STREQ(a, b) (((a)[0] == (b)[0]) && (strcmp ((a), (b)) == 0)) +#define STREQN(a, b, n) (((a)[0] == (b)[0]) && (strncmp ((a), (b), (n)) == 0)) #ifndef savestring -extern char *xmalloc (); # ifndef strcpy extern char *strcpy (); # endif @@ -84,6 +76,10 @@ extern char *strcpy (); #define digit(c) ((c) >= '0' && (c) <= '9') #endif +#ifndef digit_value +#define digit_value(c) ((c) - '0') +#endif + #ifndef member # ifndef strchr extern char *strchr (); @@ -91,10 +87,25 @@ extern char *strchr (); #define member(c, s) ((c) ? ((char *)strchr ((s), (c)) != (char *)NULL) : 0) #endif +/* Possible history errors passed to hist_error. */ +#define EVENT_NOT_FOUND 0 +#define BAD_WORD_SPEC 1 +#define SUBST_FAILED 2 +#define BAD_MODIFIER 3 + static char error_pointer; +static char *subst_lhs; +static char *subst_rhs; +static int subst_lhs_len = 0; +static int subst_rhs_len = 0; + static char *get_history_word_specifier (); +#if defined (SHELL) +extern char *single_quote (); +#endif + /* **************************************************************** */ /* */ /* History Functions */ @@ -209,7 +220,7 @@ add_history (string) /* If the history is stifled, and history_length is zero, and it equals max_input_history, we don't save items. */ - if (!history_length) + if (history_length == 0) return; /* If there is something in the slot, then remove it. */ @@ -219,6 +230,7 @@ add_history (string) free (the_history[0]); } + /* Copy the rest of the entries, moving down one slot. */ for (i = 0; i < history_length; i++) the_history[i] = the_history[i + 1]; @@ -229,9 +241,8 @@ add_history (string) { if (!history_size) { - the_history = (HIST_ENTRY **) - xmalloc ((history_size = DEFAULT_HISTORY_GROW_SIZE) - * sizeof (HIST_ENTRY *)); + history_size = DEFAULT_HISTORY_GROW_SIZE; + the_history = (HIST_ENTRY **)xmalloc (history_size * sizeof (HIST_ENTRY *)); history_length = 1; } @@ -239,10 +250,9 @@ add_history (string) { if (history_length == (history_size - 1)) { + history_size += DEFAULT_HISTORY_GROW_SIZE; the_history = (HIST_ENTRY **) - xrealloc (the_history, - ((history_size += DEFAULT_HISTORY_GROW_SIZE) - * sizeof (HIST_ENTRY *))); + xrealloc (the_history, history_size * sizeof (HIST_ENTRY *)); } history_length++; } @@ -306,11 +316,13 @@ history_search_internal (string, direction, anchored) char *string; int direction, anchored; { - register int i = history_offset; - register int reverse = (direction < 0); + register int i, reverse; register char *line; - register int index; - int string_len = strlen (string); + register int line_index; + int string_len; + + i = history_offset; + reverse = (direction < 0); /* Take care of trivial cases first. */ @@ -320,69 +332,71 @@ history_search_internal (string, direction, anchored) if (reverse && (i == history_length)) i--; +#define NEXT_LINE() do { if (reverse) i--; else i++; } while (0) + + string_len = strlen (string); while (1) { /* Search each line in the history list for STRING. */ /* At limit for direction? */ - if ((reverse && i < 0) || - (!reverse && i == history_length)) + if ((reverse && i < 0) || (!reverse && i == history_length)) return (-1); line = the_history[i]->line; - index = strlen (line); + line_index = strlen (line); /* If STRING is longer than line, no match. */ - if (string_len > index) - goto next_line; + if (string_len > line_index) + { + NEXT_LINE (); + continue; + } /* Handle anchored searches first. */ if (anchored == ANCHORED_SEARCH) { - if (strncmp (string, line, string_len) == 0) + if (STREQN (string, line, string_len)) { history_offset = i; return (0); } - goto next_line; + NEXT_LINE (); + continue; } /* Do substring search. */ if (reverse) { - index -= string_len; + line_index -= string_len; - while (index >= 0) + while (line_index >= 0) { - if (strncmp (string, line + index, string_len) == 0) + if (STREQN (string, line + line_index, string_len)) { history_offset = i; - return (index); + return (line_index); } - index--; + line_index--; } } else { - register int limit = index - string_len + 1; - index = 0; + register int limit = line_index - string_len + 1; + line_index = 0; - while (index < limit) + while (line_index < limit) { - if (strncmp (string, line + index, string_len) == 0) + if (STREQN (string, line + line_index, string_len)) { history_offset = i; - return (index); + return (line_index); } - index++; + line_index++; } } - next_line: - if (reverse) - i--; - else - i++; + NEXT_LINE (); } } @@ -488,15 +502,20 @@ history_filename (filename) if (!return_val) { char *home; + int home_len; home = getenv ("HOME"); if (!home) home = "."; - return_val = (char *)xmalloc (2 + strlen (home) + strlen (".history")); + home_len = strlen (home); + /* strlen(".history") == 8 */ + return_val = xmalloc (2 + home_len + 8); - sprintf (return_val, "%s/.history", home); + strcpy (return_val, home); + return_val[home_len] = '/'; + strcpy (return_val + home_len + 1, ".history"); } return (return_val); @@ -530,15 +549,14 @@ read_history_range (filename, from, to) input = history_filename (filename); file = open (input, O_RDONLY, 0666); - if ((file < 0) || - (stat (input, &finfo) == -1)) + if ((file < 0) || (fstat (file, &finfo) == -1)) goto error_and_exit; - buffer = (char *)xmalloc ((int)finfo.st_size + 1); + buffer = xmalloc ((int)finfo.st_size + 1); if (read (file, buffer, finfo.st_size) != finfo.st_size) - error_and_exit: { + error_and_exit: if (file >= 0) close (file); @@ -612,15 +630,12 @@ history_truncate_file (fname, lines) struct stat finfo; filename = history_filename (fname); - if (stat (filename, &finfo) == -1) - goto truncate_exit; - file = open (filename, O_RDONLY, 0666); - if (file == -1) + if (file == -1 || fstat (file, &finfo) == -1) goto truncate_exit; - buffer = (char *)xmalloc ((int)finfo.st_size + 1); + buffer = xmalloc ((int)finfo.st_size + 1); chars_read = read (file, buffer, finfo.st_size); close (file); @@ -635,26 +650,25 @@ history_truncate_file (fname, lines) lines--; } - /* If there are fewer lines in the file than we want to truncate to, - then we are all done. */ - if (!i) - goto truncate_exit; - - /* Otherwise, write from the start of this line until the end of the - buffer. */ - for (--i; i; i--) + /* If this is the first line, then the file contains exactly the + number of lines we want to truncate to, so we don't need to do + anything. It's the first line if we don't find a newline between + the current value of i and 0. Otherwise, write from the start of + this line until the end of the buffer. */ + for ( ; i; i--) if (buffer[i] == '\n') { i++; break; } - file = open (filename, O_WRONLY | O_TRUNC | O_CREAT, 0666); - if (file == -1) - goto truncate_exit; - - write (file, buffer + i, finfo.st_size - i); - close (file); + /* Write only if there are more lines in the file than we want to + truncate to. */ + if (i && ((file = open (filename, O_WRONLY, 0666)) != -1)) + { + write (file, buffer + i, finfo.st_size - i); + close (file); + } truncate_exit: if (buffer) @@ -679,10 +693,7 @@ history_do_write (filename, nelements, overwrite) char *output = history_filename (filename); int file, mode; - if (overwrite) - mode = O_WRONLY | O_CREAT | O_TRUNC; - else - mode = O_WRONLY | O_APPEND; + mode = overwrite ? O_WRONLY | O_CREAT | O_TRUNC : O_WRONLY | O_APPEND; if ((file = open (output, mode, 0666)) == -1) { @@ -707,7 +718,7 @@ history_do_write (filename, nelements, overwrite) buffer_size += 1 + strlen (the_history[i]->line); /* Allocate the buffer, and fill it. */ - buffer = (char *)xmalloc (buffer_size); + buffer = xmalloc (buffer_size); for (i = history_length - nelements; i < history_length; i++) { @@ -869,8 +880,11 @@ get_history_event (string, caller_index, delimiting_quote) int delimiting_quote; { register int i = *caller_index; - int which, sign = 1; + register char c; HIST_ENTRY *entry; + int which, sign = 1; + int local_index, search_mode, substring_okay = 0; + char *temp; /* The event can be specified in a number of ways. @@ -889,13 +903,16 @@ get_history_event (string, caller_index, delimiting_quote) /* Move on to the specification. */ i++; +#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; - goto get_which; + RETURN_ENTRY (entry, which); } /* Hack case of numeric line specification. */ @@ -907,98 +924,84 @@ get_history_event (string, caller_index, delimiting_quote) if (digit (string[i])) { - int start = i; - - /* Get the extent of the digits. */ - for (; digit (string[i]); i++); - - /* Get the digit value. */ - sscanf (string + start, "%d", &which); + /* Get the extent of the digits and compute the value. */ + for (which = 0; digit (string[i]); i++) + which = (which * 10) + digit_value (string[i]); *caller_index = i; if (sign < 0) which = (history_length + history_base) - which; - get_which: - if (entry = history_get (which)) - return (entry->line); - - return ((char *)NULL); + 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. */ - { - int local_index; - char *temp; - int substring_okay = 0; - - if (string[i] == '?') - { - substring_okay++; - i++; - } + if (string[i] == '?') + { + substring_okay++; + i++; + } - for (local_index = i; string[i]; i++) - if (whitespace (string[i]) || - string[i] == '\n' || - string[i] == ':' || - (substring_okay && string[i] == '?') || + /* Only a closing `?' or a newline delimit a substring search string. */ + for (local_index = i; c = string[i]; i++) + if ((!substring_okay && (whitespace (c) || c == ':' || #if defined (SHELL) - member (string[i], ";&()|<>") || + member (c, ";&()|<>") || #endif /* SHELL */ - string[i] == delimiting_quote) - break; + string[i] == delimiting_quote)) || + string[i] == '\n' || + (substring_okay && string[i] == '?')) + break; - temp = (char *)alloca (1 + (i - local_index)); - strncpy (temp, &string[local_index], (i - local_index)); - temp[i - local_index] = '\0'; + temp = xmalloc (1 + (i - local_index)); + strncpy (temp, &string[local_index], (i - local_index)); + temp[i - local_index] = '\0'; - if (string[i] == '?') - i++; + if (substring_okay && string[i] == '?') + i++; - *caller_index = i; + *caller_index = i; - search_again: +#define FAIL_SEARCH() \ + do { history_offset = history_length; free (temp) ; return (char *)NULL; } while (0) - local_index = history_search_internal - (temp, -1, substring_okay ? NON_ANCHORED_SEARCH : ANCHORED_SEARCH); + search_mode = substring_okay ? NON_ANCHORED_SEARCH : ANCHORED_SEARCH; + while (1) + { + local_index = history_search_internal (temp, -1, search_mode); - if (local_index < 0) - search_lost: - { - history_offset = history_length; - return ((char *)NULL); - } + if (local_index < 0) + FAIL_SEARCH (); - if (local_index == 0 || substring_okay) - { - entry = current_history (); - history_offset = history_length; + 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) - { - if (search_string) - free (search_string); - search_string = savestring (temp); - } - - return (entry->line); - } + /* If this was a substring search, then remember the + string that we matched for word substitution. */ + if (substring_okay) + { + if (search_string) + free (search_string); + search_string = temp; + } + else + free (temp); + return (entry->line); + } - if (history_offset) - history_offset--; - else - goto search_lost; - - goto search_again; - } + if (history_offset) + history_offset--; + else + FAIL_SEARCH (); + } +#undef FAIL_SEARCH +#undef RETURN_ENTRY } - #if defined (SHELL) /* Function for extracting single-quoted strings. Used for inhibiting history expansion within single quotes. */ @@ -1019,43 +1022,535 @@ rl_string_extract_single_quoted (string, sindex) *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 = 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; +} #endif /* SHELL */ +static char * +hist_error (ret, s, start, current, errtype) + char *ret, *s; + int start, current, errtype; +{ + char *temp, *emsg; + int ll; + + ll = 1 + (current - start); + temp = xmalloc (1 + ll); + strncpy (temp, s + start, ll); + temp[ll] = 0; + + switch (errtype) + { + case EVENT_NOT_FOUND: + emsg = "event not found"; + break; + case BAD_WORD_SPEC: + emsg = "bad word specifier"; + break; + case SUBST_FAILED: + emsg = "substitution failed"; + break; + case BAD_MODIFIER: + emsg = "unrecognized history modifier"; + break; + default: + emsg = "unknown expansion error"; + break; + } + + sprintf (ret, "%s: %s", temp, emsg); + free (temp); + return ret; +} + +/* 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 = (char *) NULL; + + i = *iptr; + + for (si = i; str[si] && str[si] != delimiter; si++) + if (str[si] == '\\' && str[si + 1] == delimiter) + si++; + + if (si > i || is_rhs) + { + s = 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 = 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 = 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 = 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, want_quotes, print_only; + char *event, *temp, *result, *tstr, *t, c, *word_spec; + int result_len; + + result = 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 (i && (string[i - 1] == '\'' || string[i - 1] == '"')) + quoted_search_delimiter = string[i - 1]; + event = get_history_event (string, &i, quoted_search_delimiter); + } + + if (!event) + { + hist_error (result, string, start, i, EVENT_NOT_FOUND); + *ret_string = 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) + { + hist_error (result, string, starting_index, i, BAD_WORD_SPEC); + *ret_string = result; + return (-1); + } + + /* If no word specifier, than the thing of interest was the event. */ + if (!word_spec) + temp = savestring (event); + else + { + temp = savestring (word_spec); + free (word_spec); + } + + /* Perhaps there are other modifiers involved. Do what they say. */ + want_quotes = substitute_globally = print_only = 0; + starting_index = i; + + while (string[i] == ':') + { + c = string[i + 1]; + + if (c == 'g') + { + substitute_globally = 1; + i++; + c = string[i + 1]; + } + + switch (c) + { + default: + hist_error (result, string, i+1, i+2, BAD_MODIFIER); + *ret_string = result; + free (temp); + return -1; + +#if defined (SHELL) + case 'q': + want_quotes = 'q'; + break; + + case 'x': + want_quotes = 'x'; + break; +#endif /* SHELL */ + + /* :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, *t; + int delimiter, failed, si, l_temp; + + if (c == 's') + { + if (i + 2 < (int)strlen (string)) + 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) + { + if (subst_lhs) + 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; + } + } + + /* If there is no lhs, the substitution can't succeed. */ + if (subst_lhs_len == 0) + { + hist_error (result, string, starting_index, i, SUBST_FAILED); + *ret_string = result; + free (temp); + return -1; + } + + if (subst_rhs) + 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; + + l_temp = strlen (temp); + /* Ignore impossible cases. */ + if (subst_lhs_len > l_temp) + { + hist_error (result, string, starting_index, i, SUBST_FAILED); + *ret_string = result; + free (temp); + return (-1); + } + + /* Find the first occurrence of THIS in TEMP. */ + si = 0; + for (failed = 1; (si + subst_lhs_len) <= l_temp; si++) + if (STREQN (temp+si, subst_lhs, subst_lhs_len)) + { + int len = subst_rhs_len - subst_lhs_len + l_temp; + new_event = 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) + { + si += subst_rhs_len; + l_temp = strlen (temp); + substitute_globally++; + 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 */ + + hist_error (result, string, starting_index, i, SUBST_FAILED); + *ret_string = 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 defined (SHELL) + if (want_quotes) + { + char *x; + + if (want_quotes == 'q') + x = single_quote (temp); + else if (want_quotes == 'x') + x = quote_breaks (temp); + else + x = savestring (temp); + + free (temp); + temp = x; + } +#endif /* SHELL */ + + n = strlen (temp); + if (n > result_len) + result = xrealloc (result, n + 1); + 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 - -1) If there was an error in expansion. + 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; \ + while (j >= result_len) \ + result = xrealloc (result, result_len += 128); \ + strcpy (result + j - sl, s); \ + } \ + while (0) + +#define ADD_CHAR(c) \ + do \ + { \ + if (j >= result_len) \ + result = xrealloc (result, result_len += 64); \ + result[j++] = c; \ + result[j] = '\0'; \ + } \ + while (0) + int -history_expand (string, output) - char *string; +history_expand (hstring, output) + char *hstring; char **output; { - register int j, l = strlen (string); - int i, word_spec_error = 0; - int cc, modified = 0; - char *word_spec, *event; - int starting_index, only_printing = 0, substitute_globally = 0; + register int j; + int i, r, l, passc, cc, modified, eindex, only_printing; + char *string; /* The output string, and its length. */ - int len = 0; - char *result = (char *)NULL; + int result_len; + char *result; - /* Used in add_string; */ - char *temp, tt[2], tbl[3]; + /* Used when adding the string. */ + char *temp; /* Prepare the buffer for printing error messages. */ - result = (char *)xmalloc (len = 256); + result = xmalloc (result_len = 256); + result[0] = '\0'; - result[0] = tt[1] = tbl[2] = '\0'; - tbl[0] = '\\'; - tbl[1] = history_expansion_char; + only_printing = modified = 0; + l = strlen (hstring); /* Grovel the string. Only backslash can quote the history escape character. We also handle arg specifiers. */ @@ -1066,377 +1561,177 @@ history_expand (string, output) /* 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 (string[0] == history_subst_char) + if (hstring[0] == history_subst_char) { - char *format_string = (char *)alloca (10 + strlen (string)); + string = xmalloc (l + 5); - sprintf (format_string, "%c%c:s%s", - history_expansion_char, history_expansion_char, - string); - string = format_string; + string[0] = string[1] = history_expansion_char; + string[2] = ':'; + string[3] = 's'; + strcpy (string + 4, hstring); l += 4; - goto grovel; } - - /* 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 = 0; string[i]; i++) + else { - if (string[i] == history_expansion_char) + 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 = 0; string[i]; i++) { - if (!string[i + 1] || member (string[i + 1], history_no_expand_chars)) - continue; + cc = string[i + 1]; + if (string[i] == history_expansion_char) + { + if (!cc || member (cc, history_no_expand_chars)) + continue; #if defined (SHELL) - /* The shell uses ! as a pattern negation character in globbing [...] - expressions, so let those pass without expansion. */ - else if (i > 0 && (string[i - 1] == '[') && member (']', string + i)) - continue; + /* The shell uses ! as a pattern negation character + in globbing [...] expressions, so let those pass + without expansion. */ + else if (i > 0 && (string[i - 1] == '[') && + member (']', string + i + 1)) + continue; #endif /* SHELL */ - else - goto grovel; - } + else + break; + } #if defined (SHELL) - else if (string[i] == '\'') - { - /* If this is bash, single quotes inhibit history expansion. */ - i++; - rl_string_extract_single_quoted (string, &i); + else if (string[i] == '\'') + { + /* If this is bash, single quotes inhibit history expansion. */ + i++; + rl_string_extract_single_quoted (string, &i); + } + else if (string[i] == '\\') + { + /* If this is bash, allow backslashes to quote single + quotes and + the history expansion character. */ + if (cc == '\'' || cc == history_expansion_char) + i++; + } +#endif /* SHELL */ } - else if (string[i] == '\\') + + if (string[i] != history_expansion_char) { - /* If this is bash, allow backslashes to quote single quotes and - the history expansion character. */ - if (string[i + 1] == '\'' || (string[i + 1] == history_expansion_char)) - i++; + free (result); + *output = savestring (string); + return (0); } -#endif /* SHELL */ } - - free (result); - *output = savestring (string); - return (0); - - grovel: - - for (i = j = 0; i < l; i++) + /* Extract and perform the substitution. */ + for (passc = i = j = 0; i < l; i++) { - int passc = 0; int tchar = string[i]; + if (passc) + { + passc = 0; + ADD_CHAR (tchar); + continue; + } + if (tchar == history_expansion_char) tchar = -3; - if (passc) - { - passc = 0; - goto add_char; - } - switch (tchar) { + default: + ADD_CHAR (string[i]); + break; + case '\\': - if (string[i + 1] == history_expansion_char) - { - i++; - temp = tbl; - goto do_add; - } - else - { - passc++; - goto add_char; - } + passc++; + ADD_CHAR (tchar); + break; #if defined (SHELL) case '\'': { /* If this is bash, single quotes inhibit history expansion. */ - int quote = i, slen; + int quote, slen; - ++i; + quote = i++; rl_string_extract_single_quoted (string, &i); slen = i - quote + 2; - temp = (char *)alloca (slen); + temp = xmalloc (slen); strncpy (temp, string + quote, slen); temp[slen - 1] = '\0'; - goto do_add; + ADD_STRING (temp); + free (temp); + break; } #endif /* SHELL */ - /* case history_expansion_char: */ - case -3: - starting_index = i + 1; + 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)) - goto add_char; + { + ADD_CHAR (string[i]); + break; + } +#ifdef 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. */ - + 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 == '#') - goto hack_pound_sign; - - /* If it is followed by something that starts a word specifier, - then !! is implied as the event specifier. */ - - if (member (cc, ":$*%^")) { - char fake_s[3]; - int fake_i = 0; + if (result) + { + temp = xmalloc (1 + strlen (result)); + strcpy (temp, result); + ADD_STRING (temp); + free (temp); + } i++; - fake_s[0] = fake_s[1] = history_expansion_char; - fake_s[2] = '\0'; - event = get_history_event (fake_s, &fake_i, 0); - } - 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 (i && (string[i - 1] == '\'' || string[i - 1] == '"')) - quoted_search_delimiter = string[i - 1]; - - event = get_history_event (string, &i, quoted_search_delimiter); + break; } - - if (!event) - event_not_found: - { - int ll = 1 + (i - starting_index); - - temp = (char *)alloca (1 + ll); - strncpy (temp, string + starting_index, ll); - temp[ll - 1] = 0; - sprintf (result, "%s: %s.", temp, - word_spec_error ? "Bad word specifier" : "Event not found"); - *output = 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); +#endif - /* 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) + r = history_expand_internal (string, i, &eindex, &temp, result); + if (r < 0) { - word_spec_error++; - goto event_not_found; + *output = temp; + free (result); + if (string != hstring) + free (string); + return -1; } - - /* If no word specifier, than the thing of interest was the event. */ - if (!word_spec) - temp = event; else { - temp = (char *)alloca (1 + strlen (word_spec)); - strcpy (temp, word_spec); - free (word_spec); - } - - /* Perhaps there are other modifiers involved. Do what they say. */ - - hack_specials: - - if (string[i] == ':') - { - char *tstr; - - switch (string[i + 1]) + if (temp) { - /* :p means make this the last executed line. So we - return an error state after adding this line to the - history. */ - case 'p': - only_printing++; - goto next_special; - - /* :t discards all but the last part of the pathname. */ - case 't': - tstr = strrchr (temp, '/'); - if (tstr) - temp = ++tstr; - goto next_special; - - /* :h discards the last part of a pathname. */ - case 'h': - tstr = strrchr (temp, '/'); - if (tstr) - *tstr = '\0'; - goto next_special; - - /* :r discards the suffix. */ - case 'r': - tstr = strrchr (temp, '.'); - if (tstr) - *tstr = '\0'; - goto next_special; - - /* :e discards everything but the suffix. */ - case 'e': - tstr = strrchr (temp, '.'); - if (tstr) - temp = tstr; - goto next_special; - - /* :s/this/that substitutes `this' for `that'. */ - /* :gs/this/that substitutes `this' for `that' globally. */ - case 'g': -#ifdef NOTYET - /* :g/this/that is equivalent to :gs/this/that */ - substitute_globally = 1; - if (string[i + 2] == 's') - i++; -#else - if (string[i + 2] == 's') - { - i++; - substitute_globally = 1; - goto substitute; - } -#endif - else - - case 's': - substitute: - { - char *this, *that, *new_event; - int delimiter = 0; - int si, l_this, l_that, l_temp = strlen (temp); - - if (i + 2 < (int)strlen (string)) - delimiter = string[i + 2]; - - if (!delimiter) - break; - - i += 3; - - /* Get THIS. */ - for (si = i; string[si] && string[si] != delimiter; si++); - l_this = (si - i); - this = (char *)alloca (1 + l_this); - strncpy (this, string + i, l_this); - this[l_this] = '\0'; - - i = si; - if (string[si]) - i++; - - /* Get THAT. */ - for (si = i; string[si] && string[si] != delimiter; si++); - l_that = (si - i); - that = (char *)alloca (1 + l_that); - strncpy (that, string + i, l_that); - that[l_that] = '\0'; - - i = si; - if (string[si]) i++; - - /* Ignore impossible cases. */ - if (l_this > l_temp) - goto cant_substitute; - - /* Find the first occurrence of THIS in TEMP. */ - si = 0; - for (; (si + l_this) <= l_temp; si++) - if (strncmp (temp + si, this, l_this) == 0) - { - new_event = - (char *)alloca (1 + (l_that - l_this) + l_temp); - strncpy (new_event, temp, si); - strncpy (new_event + si, that, l_that); - strncpy (new_event + si + l_that, - temp + si + l_this, - l_temp - (si + l_this)); - new_event[(l_that - l_this) + l_temp] = '\0'; - temp = new_event; - - if (substitute_globally) - { - si += l_that; - l_temp = strlen (temp); - substitute_globally++; - continue; - } - - goto hack_specials; - } - - cant_substitute: - - if (substitute_globally > 1) - { - substitute_globally = 0; - goto hack_specials; - } - - goto event_not_found; - } - - /* :# is the line so far. Note that we have to - alloca () it since RESULT could be realloc ()'ed - below in add_string. */ - case '#': - hack_pound_sign: - if (result) - { - temp = (char *)alloca (1 + strlen (result)); - strcpy (temp, result); - } - else - temp = ""; - - next_special: - i += 2; - goto hack_specials; + modified++; + if (*temp) + ADD_STRING (temp); + free (temp); } - + only_printing = r == 1; + i = eindex; } - /* Believe it or not, we have to back the pointer up by one. */ - --i; - goto add_string; - - /* A regular character. Just add it to the output string. */ - default: - add_char: - tt[0] = string[i]; - temp = tt; - goto do_add; - - add_string: - modified++; - - do_add: - j += strlen (temp); - while (j > (len - 1)) - result = (char *)xrealloc (result, (len += 256)); - - strcpy (result + (j - strlen (temp)), temp); + break; } } *output = result; + if (string != hstring) + free (string); if (only_printing) { add_history (result); - return (-1); + return (2); } return (modified != 0); @@ -1455,15 +1750,19 @@ get_history_word_specifier (spec, from, caller_index) register int i = *caller_index; int first, last; int expecting_word_spec = 0; - char *history_arg_extract (); + 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++; + { + i++; + expecting_word_spec++; + } /* Handle special cases first. */ @@ -1471,24 +1770,15 @@ get_history_word_specifier (spec, from, caller_index) if (spec[i] == '%') { *caller_index = i + 1; - if (search_string) - return (savestring (search_string)); - else - return (savestring ("")); + return (search_string ? savestring (search_string) : savestring ("")); } /* `*' matches all of the arguments, but not the command. */ if (spec[i] == '*') { - char *star_result; - *caller_index = i + 1; - star_result = history_arg_extract (1, '$', from); - - if (!star_result) - star_result = savestring (""); - - return (star_result); + result = history_arg_extract (1, '$', from); + return (result ? result : savestring ("")); } /* `$' is last arg. */ @@ -1499,68 +1789,55 @@ get_history_word_specifier (spec, from, caller_index) } /* Try to get FIRST and LAST figured out. */ - if (spec[i] == '-' || spec[i] == '^') - { - first = 1; - goto get_last; - } - if (digit (spec[i]) && expecting_word_spec) + if (spec[i] == '-' || spec[i] == '^') + first = 1; + else if (digit (spec[i]) && expecting_word_spec) { - sscanf (spec + i, "%d", &first); - for (; digit (spec[i]); i++); + for (first = 0; digit (spec[i]); i++) + first = (first * 10) + digit_value (spec[i]); } else - return ((char *)NULL); + return ((char *)NULL); /* no valid `first' for word specifier */ - get_last: - if (spec[i] == '^') + if (spec[i] == '^' || spec[i] == '*') { + last = (spec[i] == '^') ? 1 : '$'; /* x* abbreviates x-$ */ i++; - last = 1; - goto get_args; } - - if (spec[i] != '-') + else if (spec[i] != '-') + last = first; + else { - last = first; - goto get_args; - } - - i++; + i++; - if (digit (spec[i])) - { - sscanf (spec + i, "%d", &last); - for (; digit (spec[i]); i++); + if (digit (spec[i])) + { + for (last = 0; digit (spec[i]); i++) + last = (last * 10) + digit_value (spec[i]); + } + else if (spec[i] == '$') + { + i++; + last = '$'; + } + else if (!spec[i] || spec[i] == ':') /* could be modifier separator */ + last = -1; /* x- abbreviates x-$ omitting word `$' */ } - else - if (spec[i] == '$') - { - i++; - last = '$'; - } - get_args: - { - char *result = (char *)NULL; - - *caller_index = i; + *caller_index = i; - if (last >= first) - result = history_arg_extract (first, last, from); + if (last >= first || last == '$' || last < 0) + result = history_arg_extract (first, last, from); - if (result) - return (result); - else - return ((char *)&error_pointer); - } + 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). */ + 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; @@ -1569,13 +1846,16 @@ history_arg_extract (first, last, string) register int i, len; char *result = (char *)NULL; int size = 0, offset = 0; + char **list; - char **history_tokenize (), **list; - - if (!(list = history_tokenize (string))) + /* 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++); + for (len = 0; list[len]; len++) + ; if (last < 0) last = len + last - 1; @@ -1595,27 +1875,24 @@ history_arg_extract (first, last, string) result = ((char *)NULL); else { + for (size = 0, i = first; i < last; i++) + size += strlen (list[i]) + 1; + result = xmalloc (size + 1); + for (i = first; i < last; i++) { - int l = strlen (list[i]); - - if (!result) - result = (char *)xmalloc ((size = (2 + l))); - else - result = (char *)xrealloc (result, (size += (2 + l))); strcpy (result + offset, list[i]); - offset += l; + offset += strlen (list[i]); if (i + 1 < last) { - strcpy (result + offset, " "); - offset++; + result[offset++] = ' '; + result[offset] = 0; } } } for (i = 0; i < len; i++) free (list[i]); - free (list); return (result); @@ -1637,113 +1914,94 @@ history_tokenize (string) /* Get a token, and stuff it into RESULT. The tokens are split exactly where the shell would split them. */ - get_token: - - /* Skip leading whitespace. */ - for (; string[i] && whitespace(string[i]); i++); - - start = i; - - if (!string[i] || string[i] == history_comment_char) - return (result); - - if (member (string[i], "()\n")) + while (string[i]) { - i++; - goto got_token; - } + int delimiter = 0; - if (member (string[i], "<>;&|$")) - { - int peek = string[i + 1]; + /* Skip leading whitespace. */ + for (; string[i] && whitespace (string[i]); i++) + ; + if (!string[i] || string[i] == history_comment_char) + return (result); - if (peek == string[i] && peek != '$') + start = i; + + if (member (string[i], "()\n")) { - if (peek == '<' && string[i + 2] == '-') - i++; - i += 2; + i++; goto got_token; } - else + + if (member (string[i], "<>;&|$")) { - if ((peek == '&' && - (string[i] == '>' || string[i] == '<')) || - ((peek == '>') && (string[i] == '&')) || - ((peek == '(') && (string[i] == '$'))) + int peek = string[i + 1]; + + if (peek == string[i] && peek != '$') { + if (peek == '<' && string[i + 2] == '-') + i++; i += 2; goto got_token; } + else + { + if ((peek == '&' && (string[i] == '>' || string[i] == '<')) || + ((peek == '>') && (string[i] == '&')) || + ((peek == '(') && (string[i] == '$'))) + { + i += 2; + goto got_token; + } + } + if (string[i] != '$') + { + i++; + goto got_token; + } } - if (string[i] != '$') - { - i++; - goto got_token; - } - } - /* Get word from string + i; */ - { - int delimiter = 0; + /* Get word from string + i; */ - if (member (string[i], "\"'`")) - delimiter = string[i++]; + if (member (string[i], "\"'`")) + delimiter = string[i++]; - for (;string[i]; i++) - { - if (string[i] == '\\') - { - if (string[i + 1] == '\n') - { - i++; - continue; - } - else - { - if (delimiter != '\'') - if ((delimiter != '"') || - (member (string[i], slashify_in_quotes))) - { - i++; - continue; - } - } - } + for (; string[i]; i++) + { + if (string[i] == '\\' && string[i + 1] == '\n') + { + i++; + continue; + } - if (delimiter && string[i] == delimiter) - { - delimiter = 0; - continue; - } + if (string[i] == '\\' && delimiter != '\'' && + (delimiter != '"' || member (string[i], slashify_in_quotes))) + { + i++; + continue; + } - if (!delimiter && (member (string[i], " \t\n;&()|<>"))) - goto got_token; + if (delimiter && string[i] == delimiter) + { + delimiter = 0; + continue; + } - if (!delimiter && member (string[i], "\"'`")) - { + if (!delimiter && (member (string[i], " \t\n;&()|<>"))) + break; + + if (!delimiter && member (string[i], "\"'`")) delimiter = string[i]; - continue; - } - } + } got_token: - len = i - start; - if (result_index + 2 >= size) - { - if (!size) - result = (char **)xmalloc ((size = 10) * (sizeof (char *))); - else - result = - (char **)xrealloc (result, ((size += 10) * (sizeof (char *)))); - } - result[result_index] = (char *)xmalloc (1 + len); - strncpy (result[result_index], string + start, len); - result[result_index][len] = '\0'; - result_index++; - result[result_index] = (char *)NULL; - } - if (string[i]) - goto get_token; + len = i - start; + if (result_index + 2 >= size) + result = (char **)xrealloc (result, ((size += 10) * sizeof (char *))); + result[result_index] = xmalloc (1 + len); + strncpy (result[result_index], string + start, len); + result[result_index][len] = '\0'; + result[++result_index] = (char *)NULL; + } return (result); } |