PageRenderTime 209ms CodeModel.GetById 12ms app.highlight 168ms RepoModel.GetById 11ms app.codeStats 1ms

/src/mswin/rdln/history.c

https://bitbucket.org/ramcdougal/neuronrxd
C | 1717 lines | 1282 code | 204 blank | 231 comment | 186 complexity | 7bd8f22f1906cd713fd073b5d02c5fdc MD5 | raw file
   1/* History.c -- standalone history library */
   2
   3/* Copyright (C) 1989, 1991 Free Software Foundation, Inc.
   4
   5   This file contains the GNU History Library (the Library), a set of
   6   routines for managing the text of previously typed lines.
   7
   8   The Library is free software; you can redistribute it and/or modify
   9   it under the terms of the GNU General Public License as published by
  10   the Free Software Foundation; either version 2 of the License, or
  11   (at your option) any later version.
  12
  13   The Library is distributed in the hope that it will be useful,
  14   but WITHOUT ANY WARRANTY; without even the implied warranty of
  15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16   GNU General Public License for more details.
  17
  18   You should have received a copy of the GNU General Public License
  19   along with this program; if not, write to the Free Software
  20   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
  21
  22/* The goal is to make the implementation transparent, so that you
  23   don't have to know what data types are used, just what functions
  24	you can call.  I think I have done that. */
  25
  26#if defined(__MSC_VER)
  27#include <../../nrnconf.h>
  28#endif
  29
  30#ifdef WIN32
  31#define NO_INDEX
  32#define rindex  strrchr
  33#define STATIC_MALLOC
  34#endif
  35
  36/* Remove these declarations when we have a complete libgnu.a. */
  37#if !defined (STATIC_MALLOC)
  38extern char *xmalloc (), *xrealloc ();
  39#else
  40static char *xmalloc (), *xrealloc ();
  41#endif
  42
  43#include "sysdep.h"
  44#include <stdio.h>
  45#include <stdlib.h>
  46#include <errno.h>
  47#ifdef WIN32
  48#define NO_SYS_FILE
  49#endif
  50
  51#ifndef	NO_SYS_FILE
  52#include <sys/file.h>
  53#endif
  54
  55#if defined(_MSC_VER)
  56#include <malloc.h>
  57#endif
  58
  59#if defined(_MSC_VER) || !defined(__MWERKS__)
  60#include <sys/stat.h>
  61#endif
  62#include <fcntl.h>
  63
  64#include "history.h"
  65
  66#ifndef savestring
  67#define savestring(x) (char *)strcpy (xmalloc (1 + strlen (x)), (x))
  68#endif
  69
  70#ifndef whitespace
  71#define whitespace(c) (((c) == ' ') || ((c) == '\t'))
  72#endif
  73
  74#ifndef digit
  75#define digit(c)  ((c) >= '0' && (c) <= '9')
  76#endif
  77
  78#ifndef member
  79#ifdef NO_INDEX
  80#define member(c, s) ((c) ? strchr ((s), (c)) : 0)
  81#else
  82#define member(c, s) ((c) ? index ((s), (c)) : 0)
  83#endif
  84#endif
  85
  86/* **************************************************************** */
  87/*								    */
  88/*			History Functions			    */
  89/*								    */
  90/* **************************************************************** */
  91
  92/* An array of HIST_ENTRY.  This is where we store the history. */
  93static HIST_ENTRY **the_history = (HIST_ENTRY **)NULL;
  94
  95/* Non-zero means that we have enforced a limit on the amount of
  96   history that we save. */
  97int history_stifled = 0;
  98
  99/* If HISTORY_STIFLED is non-zero, then this is the maximum number of
 100   entries to remember. */
 101int max_input_history;
 102
 103/* The current location of the interactive history pointer.  Just makes
 104   life easier for outside callers. */
 105static int history_offset = 0;
 106
 107/* The number of strings currently stored in the input_history list. */
 108int history_length = 0;
 109
 110/* The current number of slots allocated to the input_history. */
 111static int history_size = 0;
 112
 113/* The number of slots to increase the_history by. */
 114#define DEFAULT_HISTORY_GROW_SIZE 50
 115
 116/* The character that represents the start of a history expansion
 117   request.  This is usually `!'. */
 118char history_expansion_char = '!';
 119
 120/* The character that invokes word substitution if found at the start of
 121   a line.  This is usually `^'. */
 122char history_subst_char = '^';
 123
 124/* During tokenization, if this character is seen as the first character
 125   of a word, then it, and all subsequent characters upto a newline are
 126   ignored.  For a Bourne shell, this should be '#'.  Bash special cases
 127   the interactive comment character to not be a comment delimiter. */
 128char history_comment_char = '\0';
 129
 130/* The list of characters which inhibit the expansion of text if found
 131   immediately following history_expansion_char. */
 132char *history_no_expand_chars = " \t\n\r=";
 133
 134/* The logical `base' of the history array.  It defaults to 1. */
 135int history_base = 1;
 136
 137/* Begin a session in which the history functions might be used.  This
 138   initializes interactive variables. */
 139void
 140using_history ()
 141{
 142  history_offset = history_length;
 143}
 144
 145/* Return the number of bytes that the primary history entries are using.
 146   This just adds up the lengths of the_history->lines. */
 147int
 148history_total_bytes ()
 149{
 150  register int i, result;
 151
 152  result = 0;
 153
 154  for (i = 0; the_history && the_history[i]; i++)
 155    result += strlen (the_history[i]->line);
 156
 157  return (result);
 158}
 159
 160/* Place STRING at the end of the history list.  The data field
 161   is  set to NULL. */
 162void
 163add_history (string)
 164     char *string;
 165{
 166  HIST_ENTRY *temp;
 167
 168  if (history_stifled && (history_length == max_input_history))
 169    {
 170      register int i;
 171
 172      /* If the history is stifled, and history_length is zero,
 173	 and it equals max_input_history, we don't save items. */
 174      if (!history_length)
 175	return;
 176
 177      /* If there is something in the slot, then remove it. */
 178      if (the_history[0])
 179	{
 180	  free (the_history[0]->line);
 181	  free (the_history[0]);
 182	}
 183
 184      for (i = 0; i < history_length; i++)
 185	the_history[i] = the_history[i + 1];
 186
 187      history_base++;
 188
 189    }
 190  else
 191    {
 192      if (!history_size)
 193	{
 194	  the_history = (HIST_ENTRY **)
 195	    xmalloc ((history_size = DEFAULT_HISTORY_GROW_SIZE)
 196		     * sizeof (HIST_ENTRY *));
 197	  history_length = 1;
 198
 199	}
 200      else
 201	{
 202	  if (history_length == (history_size - 1))
 203	    {
 204	      the_history = (HIST_ENTRY **)
 205		xrealloc (the_history,
 206			  ((history_size += DEFAULT_HISTORY_GROW_SIZE)
 207			   * sizeof (HIST_ENTRY *)));
 208	  }
 209	  history_length++;
 210	}
 211    }
 212
 213  temp = (HIST_ENTRY *)xmalloc (sizeof (HIST_ENTRY));
 214  temp->line = savestring (string);
 215  temp->data = (char *)NULL;
 216
 217  the_history[history_length] = (HIST_ENTRY *)NULL;
 218  the_history[history_length - 1] = temp;
 219}
 220
 221/* Make the history entry at WHICH have LINE and DATA.  This returns
 222   the old entry so you can dispose of the data.  In the case of an
 223   invalid WHICH, a NULL pointer is returned. */
 224HIST_ENTRY *
 225replace_history_entry (which, line, data)
 226     int which;
 227     char *line;
 228     char *data;
 229{
 230  HIST_ENTRY *temp = (HIST_ENTRY *)xmalloc (sizeof (HIST_ENTRY));
 231  HIST_ENTRY *old_value;
 232
 233  if (which >= history_length)
 234    return ((HIST_ENTRY *)NULL);
 235
 236  old_value = the_history[which];
 237
 238  temp->line = savestring (line);
 239  temp->data = data;
 240  the_history[which] = temp;
 241
 242  return (old_value);
 243}
 244
 245/* Returns the magic number which says what history element we are
 246   looking at now.  In this implementation, it returns history_offset. */
 247int
 248where_history ()
 249{
 250  return (history_offset);
 251}
 252
 253/* Search the history for STRING, starting at history_offset.
 254   If DIRECTION < 0, then the search is through previous entries, else
 255   through subsequent.  If ANCHORED is non-zero, the string must
 256   appear at the beginning of a history line, otherwise, the string
 257   may appear anywhere in the line.  If the string is found, then
 258   current_history () is the history entry, and the value of this
 259   function is the offset in the line of that history entry that the
 260   string was found in.  Otherwise, nothing is changed, and a -1 is
 261   returned. */
 262
 263#define ANCHORED_SEARCH 1
 264#define NON_ANCHORED_SEARCH 0
 265
 266static int
 267history_search_internal (string, direction, anchored)
 268     char *string;
 269     int direction, anchored;
 270{
 271  register int i = history_offset;
 272  register int reverse = (direction < 0);
 273  register char *line;
 274  register int index;
 275  int string_len = strlen (string);
 276
 277  /* Take care of trivial cases first. */
 278
 279  if (!history_length || ((i == history_length) && !reverse))
 280    return (-1);
 281
 282  if (reverse && (i == history_length))
 283    i--;
 284
 285  while (1)
 286    {
 287      /* Search each line in the history list for STRING. */
 288
 289      /* At limit for direction? */
 290      if ((reverse && i < 0) ||
 291	  (!reverse && i == history_length))
 292	return (-1);
 293
 294      line = the_history[i]->line;
 295      index = strlen (line);
 296
 297      /* If STRING is longer than line, no match. */
 298      if (string_len > index)
 299	goto next_line;
 300
 301      /* Handle anchored searches first. */
 302      if (anchored == ANCHORED_SEARCH)
 303	{
 304	  if (strncmp (string, line, string_len) == 0)
 305	    {
 306	      history_offset = i;
 307	      return (0);
 308	    }
 309
 310	  goto next_line;
 311	}
 312
 313      /* Do substring search. */
 314      if (reverse)
 315	{
 316	  index -= string_len;
 317
 318	  while (index >= 0)
 319	    {
 320	      if (strncmp (string, line + index, string_len) == 0)
 321		{
 322		  history_offset = i;
 323		  return (index);
 324		}
 325	      index--;
 326	    }
 327	}
 328      else
 329	{
 330	  register int limit = index - string_len + 1;
 331	  index = 0;
 332
 333	  while (index < limit)
 334	    {
 335	      if (strncmp (string, line + index, string_len) == 0)
 336		{
 337		  history_offset = i;
 338		  return (index);
 339		}
 340	      index++;
 341	    }
 342	}
 343    next_line:
 344      if (reverse)
 345	i--;
 346      else
 347	i++;
 348    }
 349}
 350
 351/* Do a non-anchored search for STRING through the history in DIRECTION. */
 352int
 353history_search (string, direction)
 354     char *string;
 355     int direction;
 356{
 357  return (history_search_internal (string, direction, NON_ANCHORED_SEARCH));
 358}
 359
 360/* Do an anchored search for string through the history in DIRECTION. */
 361int
 362history_search_prefix (string, direction)
 363     char *string;
 364     int direction;
 365{
 366  return (history_search_internal (string, direction, ANCHORED_SEARCH));
 367}
 368
 369/* Remove history element WHICH from the history.  The removed
 370   element is returned to you so you can free the line, data,
 371   and containing structure. */
 372HIST_ENTRY *
 373remove_history (which)
 374     int which;
 375{
 376  HIST_ENTRY *return_value;
 377
 378  if (which >= history_length || !history_length)
 379    return_value = (HIST_ENTRY *)NULL;
 380  else
 381    {
 382      register int i;
 383      return_value = the_history[which];
 384
 385      for (i = which; i < history_length; i++)
 386	the_history[i] = the_history[i + 1];
 387
 388      history_length--;
 389    }
 390
 391  return (return_value);
 392}
 393
 394/* Stifle the history list, remembering only MAX number of lines. */
 395void
 396stifle_history (max)
 397     int max;
 398{
 399  if (max < 0)
 400    max = 0;
 401  if (history_length > max)
 402    {
 403      register int i, j;
 404
 405      /* This loses because we cannot free the data. */
 406      for (i = 0; i < (history_length - max); i++)
 407	{
 408	  free (the_history[i]->line);
 409	  free (the_history[i]);
 410	}
 411      history_base = i;
 412      for (j = 0, i = history_length - max; j < max; i++, j++)
 413	the_history[j] = the_history[i];
 414      the_history[j] = (HIST_ENTRY *)NULL;
 415      history_length = j;
 416    }
 417  history_stifled = 1;
 418  max_input_history = max;
 419}
 420
 421/* Stop stifling the history.  This returns the previous amount the history
 422 was stifled by.  The value is positive if the history was stifled, negative
 423 if it wasn't. */
 424int
 425unstifle_history ()
 426{
 427  int result = max_input_history;
 428  if (history_stifled)
 429    {
 430      result = - result;
 431      history_stifled = 0;
 432    }
 433  return (result);
 434}
 435
 436/* Return the string that should be used in the place of this
 437   filename.  This only matters when you don't specify the
 438   filename to read_history (), or write_history (). */
 439static char *
 440history_filename (filename)
 441     char *filename;
 442{
 443  char *return_val = filename ? savestring (filename) : (char *)NULL;
 444
 445  if (!return_val)
 446    {
 447      char *home = (char *)getenv ("HOME");
 448      if (!home) home = ".";
 449      return_val = (char *)xmalloc (2 + strlen (home) + strlen (".history"));
 450      sprintf (return_val, "%s/.history", home);
 451    }
 452  return (return_val);
 453}
 454
 455/* Add the contents of FILENAME to the history list, a line at a time.
 456   If FILENAME is NULL, then read from ~/.history.  Returns 0 if
 457   successful, or errno if not. */
 458int
 459read_history (filename)
 460     char *filename;
 461{
 462  return (read_history_range (filename, 0, -1));
 463}
 464
 465/* Read a range of lines from FILENAME, adding them to the history list.
 466   Start reading at the FROM'th line and end at the TO'th.  If FROM
 467   is zero, start at the beginning.  If TO is less than FROM, read
 468   until the end of the file.  If FILENAME is NULL, then read from
 469   ~/.history.  Returns 0 if successful, or errno if not. */
 470int
 471read_history_range (filename, from, to)
 472     char *filename;
 473     int from, to;
 474{
 475  register int line_start, line_end;
 476  char *input, *buffer = (char *)NULL;
 477  int file, current_line;
 478  struct stat finfo;
 479
 480  input = history_filename (filename);
 481  file = open (input, O_RDONLY, 0666);
 482
 483  if ((file < 0) ||
 484      (stat (input, &finfo) == -1))
 485    goto error_and_exit;
 486
 487  buffer = (char *)xmalloc (finfo.st_size + 1);
 488
 489  if (read (file, buffer, finfo.st_size) != finfo.st_size)
 490  error_and_exit:
 491    {
 492      if (file >= 0)
 493	close (file);
 494
 495      if (buffer)
 496	free (buffer);
 497
 498      return (errno);
 499    }
 500
 501  close (file);
 502
 503  /* Set TO to larger than end of file if negative. */
 504  if (to < 0)
 505    to = finfo.st_size;
 506
 507  /* Start at beginning of file, work to end. */
 508  line_start = line_end = current_line = 0;
 509
 510  /* Skip lines until we are at FROM. */
 511  while (line_start < finfo.st_size && current_line < from)
 512    {
 513      for (line_end = line_start; line_end < finfo.st_size; line_end++)
 514	if (buffer[line_end] == '\n')
 515	  {
 516	    current_line++;
 517	    line_start = line_end + 1;
 518	    if (current_line == from)
 519	      break;
 520	  }
 521    }
 522
 523  /* If there are lines left to gobble, then gobble them now. */
 524  for (line_end = line_start; line_end < finfo.st_size; line_end++)
 525    if (buffer[line_end] == '\n')
 526      {
 527	buffer[line_end] = '\0';
 528
 529	if (buffer[line_start])
 530	  add_history (buffer + line_start);
 531
 532	current_line++;
 533
 534	if (current_line >= to)
 535	  break;
 536
 537	line_start = line_end + 1;
 538      }
 539  return (0);
 540}
 541
 542/* Truncate the history file FNAME, leaving only LINES trailing lines.
 543   If FNAME is NULL, then use ~/.history. */
 544history_truncate_file (fname, lines)
 545     char *fname;
 546     register int lines;
 547{
 548  register int i;
 549  int file;
 550  char *buffer = (char *)NULL, *filename;
 551  struct stat finfo;
 552
 553  filename = history_filename (fname);
 554  if (stat (filename, &finfo) == -1)
 555    goto truncate_exit;
 556
 557  file = open (filename, O_RDONLY, 0666);
 558
 559  if (file == -1)
 560    goto truncate_exit;
 561
 562  buffer = (char *)xmalloc (finfo.st_size + 1);
 563  read (file, buffer, finfo.st_size);
 564  close (file);
 565
 566  /* Count backwards from the end of buffer until we have passed
 567     LINES lines. */
 568  for (i = finfo.st_size; lines && i; i--)
 569    {
 570      if (buffer[i] == '\n')
 571	lines--;
 572    }
 573
 574  /* If there are fewer lines in the file than we want to truncate to,
 575     then we are all done. */
 576  if (!i)
 577    goto truncate_exit;
 578
 579  /* Otherwise, write from the start of this line until the end of the
 580     buffer. */
 581  for (--i; i; i--)
 582    if (buffer[i] == '\n')
 583      {
 584	i++;
 585	break;
 586      }
 587
 588  file = open (filename, O_WRONLY | O_TRUNC | O_CREAT, 0666);
 589  if (file == -1)
 590    goto truncate_exit;
 591
 592  write (file, buffer + i, finfo.st_size - i);
 593  close (file);
 594
 595 truncate_exit:
 596  if (buffer)
 597    free (buffer);
 598
 599  free (filename);
 600}
 601
 602#define HISTORY_APPEND 0
 603#define HISTORY_OVERWRITE 1
 604
 605/* Workhorse function for writing history.  Writes NELEMENT entries
 606   from the history list to FILENAME.  OVERWRITE is non-zero if you
 607   wish to replace FILENAME with the entries. */
 608static int
 609history_do_write (filename, nelements, overwrite)
 610     char *filename;
 611     int nelements, overwrite;
 612{
 613  register int i, j;
 614  char *output = history_filename (filename);
 615  int file, mode;
 616
 617  if (overwrite)
 618    mode = O_WRONLY | O_CREAT | O_TRUNC;
 619  else
 620    mode = O_WRONLY | O_APPEND;
 621
 622  if ((file = open (output, mode, 0666)) == -1)
 623    return (errno);
 624
 625  if (nelements > history_length)
 626    nelements = history_length;
 627
 628  /* Build a buffer of all the lines to write, and write them in one syscall.
 629     Suggested by Peter Ho (peter@robosts.oxford.ac.uk). */
 630  {
 631    register int j = 0;
 632    int buffer_size = 0;
 633    char *buffer;
 634
 635    /* Calculate the total number of bytes to write. */
 636    for (i = history_length - nelements; i < history_length; i++)
 637      buffer_size += 1 + strlen (the_history[i]->line);
 638
 639    /* Allocate the buffer, and fill it. */
 640    buffer = (char *)xmalloc (buffer_size);
 641
 642    for (i = history_length - nelements; i < history_length; i++)
 643      {
 644	strcpy (buffer + j, the_history[i]->line);
 645	j += strlen (the_history[i]->line);
 646	buffer[j++] = '\n';
 647      }
 648
 649    write (file, buffer, buffer_size);
 650    free (buffer);
 651  }
 652
 653  close (file);
 654  return (0);
 655}
 656  
 657/* Append NELEMENT entries to FILENAME.  The entries appended are from
 658   the end of the list minus NELEMENTs up to the end of the list. */
 659int
 660append_history (nelements, filename)
 661     int nelements;
 662     char *filename;
 663{
 664  return (history_do_write (filename, nelements, HISTORY_APPEND));
 665}
 666
 667/* Overwrite FILENAME with the current history.  If FILENAME is NULL,
 668   then write the history list to ~/.history.  Values returned
 669   are as in read_history ().*/
 670int
 671write_history (filename)
 672     char *filename;
 673{
 674  return (history_do_write (filename, history_length, HISTORY_OVERWRITE));
 675}
 676
 677/* Return the history entry at the current position, as determined by
 678   history_offset.  If there is no entry there, return a NULL pointer. */
 679HIST_ENTRY *
 680current_history ()
 681{
 682  if ((history_offset == history_length) || !the_history)
 683    return ((HIST_ENTRY *)NULL);
 684  else
 685    return (the_history[history_offset]);
 686}
 687
 688/* Back up history_offset to the previous history entry, and return
 689   a pointer to that entry.  If there is no previous entry then return
 690   a NULL pointer. */
 691HIST_ENTRY *
 692previous_history ()
 693{
 694  if (!history_offset)
 695    return ((HIST_ENTRY *)NULL);
 696  else
 697    return (the_history[--history_offset]);
 698}
 699
 700/* Move history_offset forward to the next history entry, and return
 701   a pointer to that entry.  If there is no next entry then return a
 702   NULL pointer. */
 703HIST_ENTRY *
 704next_history ()
 705{
 706  if (history_offset == history_length)
 707    return ((HIST_ENTRY *)NULL);
 708  else
 709    return (the_history[++history_offset]);
 710}
 711
 712/* Return the current history array.  The caller has to be carefull, since this
 713   is the actual array of data, and could be bashed or made corrupt easily.
 714   The array is terminated with a NULL pointer. */
 715HIST_ENTRY **
 716history_list ()
 717{
 718  return (the_history);
 719}
 720
 721/* Return the history entry which is logically at OFFSET in the history array.
 722   OFFSET is relative to history_base. */
 723HIST_ENTRY *
 724history_get (offset)
 725     int offset;
 726{
 727  int index = offset - history_base;
 728
 729  if (index >= history_length ||
 730      index < 0 ||
 731      !the_history)
 732    return ((HIST_ENTRY *)NULL);
 733  return (the_history[index]);
 734}
 735
 736/* Search for STRING in the history list.  DIR is < 0 for searching
 737   backwards.  POS is an absolute index into the history list at
 738   which point to begin searching. */
 739int
 740history_search_pos (string, dir, pos)
 741     char *string;
 742     int dir, pos;
 743{
 744  int ret, old = where_history ();
 745  history_set_pos (pos);
 746  if (history_search (string, dir) == -1)
 747    {
 748      history_set_pos (old);
 749      return (-1);
 750    }
 751  ret = where_history ();
 752  history_set_pos (old);
 753  return ret;
 754}
 755
 756/* Make the current history item be the one at POS, an absolute index.
 757   Returns zero if POS is out of range, else non-zero. */
 758int
 759history_set_pos (pos)
 760     int pos;
 761{
 762  if (pos > history_length || pos < 0 || !the_history)
 763    return (0);
 764  history_offset = pos;
 765  return (1);
 766}
 767 
 768
 769/* **************************************************************** */
 770/*								    */
 771/*			History Expansion			    */
 772/*								    */
 773/* **************************************************************** */
 774
 775/* Hairy history expansion on text, not tokens.  This is of general
 776   use, and thus belongs in this library. */
 777
 778/* The last string searched for in a !?string? search. */
 779static char *search_string = (char *)NULL;
 780
 781/* Return the event specified at TEXT + OFFSET modifying OFFSET to
 782   point to after the event specifier.  Just a pointer to the history
 783   line is returned; NULL is returned in the event of a bad specifier.
 784   You pass STRING with *INDEX equal to the history_expansion_char that
 785   begins this specification.
 786   DELIMITING_QUOTE is a character that is allowed to end the string
 787   specification for what to search for in addition to the normal
 788   characters `:', ` ', `\t', `\n', and sometimes `?'.
 789   So you might call this function like:
 790   line = get_history_event ("!echo:p", &index, 0);  */
 791char *
 792get_history_event (string, caller_index, delimiting_quote)
 793     char *string;
 794     int *caller_index;
 795     int delimiting_quote;
 796{
 797  register int i = *caller_index;
 798  int which, sign = 1;
 799  HIST_ENTRY *entry;
 800
 801  /* The event can be specified in a number of ways.
 802
 803     !!   the previous command
 804     !n   command line N
 805     !-n  current command-line minus N
 806     !str the most recent command starting with STR
 807     !?str[?]
 808	  the most recent command containing STR
 809
 810     All values N are determined via HISTORY_BASE. */
 811
 812  if (string[i] != history_expansion_char)
 813    return ((char *)NULL);
 814
 815  /* Move on to the specification. */
 816  i++;
 817
 818  /* Handle !! case. */
 819  if (string[i] == history_expansion_char)
 820    {
 821      i++;
 822      which = history_base + (history_length - 1);
 823      *caller_index = i;
 824      goto get_which;
 825    }
 826
 827  /* Hack case of numeric line specification. */
 828 read_which:
 829  if (string[i] == '-')
 830    {
 831      sign = -1;
 832      i++;
 833    }
 834
 835  if (digit (string[i]))
 836    {
 837      int start = i;
 838
 839      /* Get the extent of the digits. */
 840      for (; digit (string[i]); i++);
 841
 842      /* Get the digit value. */
 843      sscanf (string + start, "%d", &which);
 844
 845      *caller_index = i;
 846
 847      if (sign < 0)
 848	which = (history_length + history_base) - which;
 849
 850    get_which:
 851      if (entry = history_get (which))
 852	return (entry->line);
 853
 854      return ((char *)NULL);
 855    }
 856
 857  /* This must be something to search for.  If the spec begins with
 858     a '?', then the string may be anywhere on the line.  Otherwise,
 859     the string must be found at the start of a line. */
 860  {
 861    int index;
 862    char *temp;
 863    int substring_okay = 0;
 864
 865    if (string[i] == '?')
 866      {
 867	substring_okay++;
 868	i++;
 869      }
 870
 871    for (index = i; string[i]; i++)
 872      if (whitespace (string[i]) ||
 873	  string[i] == '\n' ||
 874	  string[i] == ':' ||
 875	  (substring_okay && string[i] == '?') ||
 876	  string[i] == delimiting_quote)
 877	break;
 878
 879    temp = (char *)alloca (1 + (i - index));
 880    strncpy (temp, &string[index], (i - index));
 881    temp[i - index] = '\0';
 882
 883    if (string[i] == '?')
 884      i++;
 885
 886    *caller_index = i;
 887
 888  search_again:
 889
 890    index = history_search_internal
 891      (temp, -1, substring_okay ? NON_ANCHORED_SEARCH : ANCHORED_SEARCH);
 892
 893    if (index < 0)
 894    search_lost:
 895      {
 896	history_offset = history_length;
 897	return ((char *)NULL);
 898      }
 899
 900    if (index == 0)
 901      {
 902      search_won:
 903	entry = current_history ();
 904	history_offset = history_length;
 905	
 906	/* If this was a substring search, then remember the string that
 907	   we matched for word substitution. */
 908	if (substring_okay)
 909	  {
 910	    if (search_string)
 911	      free (search_string);
 912	    search_string = savestring (temp);
 913	  }
 914
 915	return (entry->line);
 916      }
 917
 918    if (history_offset)
 919      history_offset--;
 920    else
 921      goto search_lost;
 922    
 923    goto search_again;
 924  }
 925}
 926
 927/* Expand the string STRING, placing the result into OUTPUT, a pointer
 928   to a string.  Returns:
 929
 930   0) If no expansions took place (or, if the only change in
 931      the text was the de-slashifying of the history expansion
 932      character)
 933   1) If expansions did take place
 934  -1) If there was an error in expansion.
 935
 936  If an error ocurred in expansion, then OUTPUT contains a descriptive
 937  error message. */
 938int
 939history_expand (string, output)
 940     char *string;
 941     char **output;
 942{
 943  register int j, l = strlen (string);
 944  int i, word_spec_error = 0;
 945  int cc, modified = 0;
 946  char *word_spec, *event;
 947  int starting_index, only_printing = 0, substitute_globally = 0;
 948
 949  char *get_history_word_specifier (), *rindex ();
 950
 951  /* The output string, and its length. */
 952  int len = 0;
 953  char *result = (char *)NULL;
 954
 955  /* Used in add_string; */
 956  char *temp, tt[2], tbl[3];
 957
 958  /* Prepare the buffer for printing error messages. */
 959  result = (char *)xmalloc (len = 255);
 960
 961  result[0] = tt[1] = tbl[2] = '\0';
 962  tbl[0] = '\\';
 963  tbl[1] = history_expansion_char;
 964
 965  /* Grovel the string.  Only backslash can quote the history escape
 966     character.  We also handle arg specifiers. */
 967
 968  /* Before we grovel forever, see if the history_expansion_char appears
 969     anywhere within the text. */
 970
 971  /* The quick substitution character is a history expansion all right.  That
 972     is to say, "^this^that^" is equivalent to "!!:s^this^that^", and in fact,
 973     that is the substitution that we do. */
 974  if (string[0] == history_subst_char)
 975    {
 976      char *format_string = (char *)alloca (10 + strlen (string));
 977
 978      sprintf (format_string, "%c%c:s%s",
 979	       history_expansion_char, history_expansion_char,
 980	       string);
 981      string = format_string;
 982      l += 4;
 983      goto grovel;
 984    }
 985
 986  /* If not quick substitution, still maybe have to do expansion. */
 987
 988  /* `!' followed by one of the characters in history_no_expand_chars
 989     is NOT an expansion. */
 990  for (i = 0; string[i]; i++)
 991    if (string[i] == history_expansion_char)
 992      if (!string[i + 1] || member (string[i + 1], history_no_expand_chars))
 993	continue;
 994      else
 995	goto grovel;
 996
 997  free (result);
 998  *output = savestring (string);
 999  return (0);
1000
1001 grovel:
1002
1003  for (i = j = 0; i < l; i++)
1004    {
1005      int tchar = string[i];
1006      if (tchar == history_expansion_char)
1007	tchar = -3;
1008
1009      switch (tchar)
1010	{
1011	case '\\':
1012	  if (string[i + 1] == history_expansion_char)
1013	    {
1014	      i++;
1015	      temp = tbl;
1016	      goto do_add;
1017	    }
1018	  else
1019	    goto add_char;
1020
1021	  /* case history_expansion_char: */
1022	case -3:
1023	  starting_index = i + 1;
1024	  cc = string[i + 1];
1025
1026	  /* If the history_expansion_char is followed by one of the
1027	     characters in history_no_expand_chars, then it is not a
1028	     candidate for expansion of any kind. */
1029	  if (member (cc, history_no_expand_chars))
1030	    goto add_char;
1031
1032	  /* There is something that is listed as a `word specifier' in csh
1033	     documentation which means `the expanded text to this point'.
1034	     That is not a word specifier, it is an event specifier. */
1035
1036	  if (cc == '#')
1037	    goto hack_pound_sign;
1038
1039	  /* If it is followed by something that starts a word specifier,
1040	     then !! is implied as the event specifier. */
1041
1042	  if (member (cc, ":$*%^"))
1043	    {
1044	      char fake_s[3];
1045	      int fake_i = 0;
1046	      i++;
1047	      fake_s[0] = fake_s[1] = history_expansion_char;
1048	      fake_s[2] = '\0';
1049	      event = get_history_event (fake_s, &fake_i, 0);
1050	    }
1051	  else
1052	    {
1053	      int quoted_search_delimiter = 0;
1054
1055	      /* If the character before this `!' is a double or single
1056		 quote, then this expansion takes place inside of the
1057		 quoted string.  If we have to search for some text ("!foo"),
1058		 allow the delimiter to end the search string. */
1059	      if (i && (string[i - 1] == '\'' || string[i - 1] == '"'))
1060		quoted_search_delimiter = string[i - 1];
1061
1062	      event = get_history_event (string, &i, quoted_search_delimiter);
1063	    }
1064	  
1065	  if (!event)
1066	  event_not_found:
1067	    {
1068	    int l = 1 + (i - starting_index);
1069
1070	    temp = (char *)alloca (1 + l);
1071	    strncpy (temp, string + starting_index, l);
1072	    temp[l - 1] = 0;
1073	    sprintf (result, "%s: %s.", temp,
1074		     word_spec_error ? "Bad word specifier" : "Event not found");
1075	  error_exit:
1076	    *output = result;
1077	    return (-1);
1078	  }
1079
1080	  /* If a word specifier is found, then do what that requires. */
1081	  starting_index = i;
1082
1083	  word_spec = get_history_word_specifier (string, event, &i);
1084
1085	  /* There is no such thing as a `malformed word specifier'.  However,
1086	     it is possible for a specifier that has no match.  In that case,
1087	     we complain. */
1088	  if (word_spec == (char *)-1)
1089	  bad_word_spec:
1090	  {
1091	    word_spec_error++;
1092	    goto event_not_found;
1093	  }
1094
1095	  /* If no word specifier, than the thing of interest was the event. */
1096	  if (!word_spec)
1097	    temp = event;
1098	  else
1099	    {
1100	      temp = (char *)alloca (1 + strlen (word_spec));
1101	      strcpy (temp, word_spec);
1102	      free (word_spec);
1103	    }
1104
1105	  /* Perhaps there are other modifiers involved.  Do what they say. */
1106
1107	hack_specials:
1108
1109	  if (string[i] == ':')
1110	    {
1111	      char *tstr;
1112
1113	      switch (string[i + 1])
1114		{
1115		  /* :p means make this the last executed line.  So we
1116		     return an error state after adding this line to the
1117		     history. */
1118		case 'p':
1119		  only_printing++;
1120		  goto next_special;
1121
1122		  /* :t discards all but the last part of the pathname. */
1123		case 't':
1124		  tstr = rindex (temp, '/');
1125		  if (tstr)
1126		    temp = ++tstr;
1127		  goto next_special;
1128
1129		  /* :h discards the last part of a pathname. */
1130		case 'h':
1131		  tstr = rindex (temp, '/');
1132		  if (tstr)
1133		    *tstr = '\0';
1134		  goto next_special;
1135
1136		  /* :r discards the suffix. */
1137		case 'r':
1138		  tstr = rindex (temp, '.');
1139		  if (tstr)
1140		    *tstr = '\0';
1141		  goto next_special;
1142
1143		  /* :e discards everything but the suffix. */
1144		case 'e':
1145		  tstr = rindex (temp, '.');
1146		  if (tstr)
1147		    temp = tstr;
1148		  goto next_special;
1149
1150		  /* :s/this/that substitutes `this' for `that'. */
1151		  /* :gs/this/that substitutes `this' for `that' globally. */
1152		case 'g':
1153		  if (string[i + 2] == 's')
1154		    {
1155		      i++;
1156		      substitute_globally = 1;
1157		      goto substitute;
1158		    }
1159		  else
1160		    
1161		case 's':
1162		  substitute:
1163		  {
1164		    char *this, *that, *new_event;
1165		    int delimiter = 0;
1166		    int si, l_this, l_that, l_temp = strlen (temp);
1167
1168		    if (i + 2 < strlen (string))
1169		      delimiter = string[i + 2];
1170
1171		    if (!delimiter)
1172		      break;
1173
1174		    i += 3;
1175
1176		    /* Get THIS. */
1177		    for (si = i; string[si] && string[si] != delimiter; si++);
1178		    l_this = (si - i);
1179		    this = (char *)alloca (1 + l_this);
1180		    strncpy (this, string + i, l_this);
1181		    this[l_this] = '\0';
1182
1183		    i = si;
1184		    if (string[si])
1185		      i++;
1186
1187		    /* Get THAT. */
1188		    for (si = i; string[si] && string[si] != delimiter; si++);
1189		    l_that = (si - i);
1190		    that = (char *)alloca (1 + l_that);
1191		    strncpy (that, string + i, l_that);
1192		    that[l_that] = '\0';
1193
1194		    i = si;
1195		    if (string[si]) i++;
1196
1197		    /* Ignore impossible cases. */
1198		    if (l_this > l_temp)
1199		      goto cant_substitute;
1200
1201		    /* Find the first occurrence of THIS in TEMP. */
1202		    si = 0;
1203		    for (; (si + l_this) <= l_temp; si++)
1204		      if (strncmp (temp + si, this, l_this) == 0)
1205			{
1206			  new_event =
1207			    (char *)alloca (1 + (l_that - l_this) + l_temp);
1208			  strncpy (new_event, temp, si);
1209			  strncpy (new_event + si, that, l_that);
1210			  strncpy (new_event + si + l_that,
1211				   temp + si + l_this,
1212				   l_temp - (si + l_this));
1213			  new_event[(l_that - l_this) + l_temp] = '\0';
1214			  temp = new_event;
1215
1216			  if (substitute_globally)
1217			    {
1218			      si += l_that;
1219			      l_temp = strlen (temp);
1220			      substitute_globally++;
1221			      continue;
1222			    }
1223
1224			  goto hack_specials;
1225			}
1226
1227		  cant_substitute:
1228
1229		    if (substitute_globally > 1)
1230		      {
1231			substitute_globally = 0;
1232			goto hack_specials;
1233		      }
1234
1235		    goto event_not_found;
1236		  }
1237
1238		  /* :# is the line so far.  Note that we have to
1239		     alloca () it since RESULT could be realloc ()'ed
1240		     below in add_string. */
1241		case '#':
1242		hack_pound_sign:
1243		  if (result)
1244		    {
1245		      temp = (char *)alloca (1 + strlen (result));
1246		      strcpy (temp, result);
1247		    }
1248		  else
1249		    temp = "";
1250
1251		next_special:
1252		  i += 2;
1253		  goto hack_specials;
1254		}
1255
1256	    }
1257	  /* Believe it or not, we have to back the pointer up by one. */
1258	  --i;
1259	  goto add_string;
1260
1261	  /* A regular character.  Just add it to the output string. */
1262	default:
1263	add_char:
1264	  tt[0] = string[i];
1265	  temp = tt;
1266	  goto do_add;
1267
1268	add_string:
1269	  modified++;
1270
1271	do_add:
1272	  j += strlen (temp);
1273	  while (j > len)
1274	    result = (char *)xrealloc (result, (len += 255));
1275
1276	  strcpy (result + (j - strlen (temp)), temp);
1277	}
1278    }
1279
1280  *output = result;
1281
1282  if (only_printing)
1283    {
1284      add_history (result);
1285      return (-1);
1286    }
1287
1288  return (modified != 0);
1289}
1290
1291/* Return a consed string which is the word specified in SPEC, and found
1292   in FROM.  NULL is returned if there is no spec.  -1 is returned if
1293   the word specified cannot be found.  CALLER_INDEX is the offset in
1294   SPEC to start looking; it is updated to point to just after the last
1295   character parsed. */
1296char *
1297get_history_word_specifier (spec, from, caller_index)
1298     char *spec, *from;
1299     int *caller_index;
1300{
1301  register int i = *caller_index;
1302  int first, last;
1303  int expecting_word_spec = 0;
1304  char *history_arg_extract ();
1305
1306  /* The range of words to return doesn't exist yet. */
1307  first = last = 0;
1308
1309  /* If we found a colon, then this *must* be a word specification.  If
1310     it isn't, then it is an error. */
1311  if (spec[i] == ':')
1312    i++, expecting_word_spec++;
1313
1314  /* Handle special cases first. */
1315
1316  /* `%' is the word last searched for. */
1317  if (spec[i] == '%')
1318    {
1319      *caller_index = i + 1;
1320      if (search_string)
1321	return (savestring (search_string));
1322      else
1323	return (savestring (""));
1324    }
1325
1326  /* `*' matches all of the arguments, but not the command. */
1327  if (spec[i] == '*')
1328    {
1329      char *star_result;
1330
1331      *caller_index = i + 1;
1332      star_result = history_arg_extract (1, '$', from);
1333
1334      if (!star_result)
1335	star_result = savestring ("");
1336
1337      return (star_result);
1338    }
1339
1340  /* `$' is last arg. */
1341  if (spec[i] == '$')
1342    {
1343      *caller_index = i + 1;
1344      return (history_arg_extract ('$', '$', from));
1345    }
1346
1347  /* Try to get FIRST and LAST figured out. */
1348  if (spec[i] == '-' || spec[i] == '^')
1349    {
1350      first = 1;
1351      goto get_last;
1352    }
1353
1354 get_first:
1355  if (digit (spec[i]) && expecting_word_spec)
1356    {
1357      sscanf (spec + i, "%d", &first);
1358      for (; digit (spec[i]); i++);
1359    }
1360  else
1361    return ((char *)NULL);
1362
1363 get_last:
1364  if (spec[i] == '^')
1365    {
1366      i++;
1367      last = 1;
1368      goto get_args;
1369    }
1370
1371  if (spec[i] != '-')
1372    {
1373      last = first;
1374      goto get_args;
1375    }
1376
1377  i++;
1378
1379  if (digit (spec[i]))
1380    {
1381      sscanf (spec + i, "%d", &last);
1382      for (; digit (spec[i]); i++);
1383    }
1384  else
1385    if (spec[i] == '$')
1386      {
1387	i++;
1388	last = '$';
1389      }
1390
1391 get_args:
1392  {
1393    char *result = (char *)NULL;
1394
1395    *caller_index = i;
1396
1397    if (last >= first)
1398      result = history_arg_extract (first, last, from);
1399
1400    if (result)
1401      return (result);
1402    else
1403      return ((char *)-1);
1404  }
1405}
1406
1407/* Extract the args specified, starting at FIRST, and ending at LAST.
1408   The args are taken from STRING.  If either FIRST or LAST is < 0,
1409   then make that arg count from the right (subtract from the number of
1410   tokens, so that FIRST = -1 means the next to last token on the line). */
1411char *
1412history_arg_extract (first, last, string)
1413     int first, last;
1414     char *string;
1415{
1416  register int i, len;
1417  char *result = (char *)NULL;
1418  int size = 0, offset = 0;
1419
1420  char **history_tokenize (), **list;
1421
1422  if (!(list = history_tokenize (string)))
1423    return ((char *)NULL);
1424
1425  for (len = 0; list[len]; len++);
1426
1427  if (last < 0)
1428    last = len + last - 1;
1429
1430  if (first < 0)
1431    first = len + first - 1;
1432
1433  if (last == '$')
1434    last = len - 1;
1435
1436  if (first == '$')
1437    first = len - 1;
1438
1439  last++;
1440
1441  if (first > len || last > len || first < 0 || last < 0)
1442    result = ((char *)NULL);
1443  else
1444    {
1445      for (i = first; i < last; i++)
1446	{
1447	  int l = strlen (list[i]);
1448
1449	  if (!result)
1450	    result = (char *)xmalloc ((size = (2 + l)));
1451	  else
1452	    result = (char *)xrealloc (result, (size += (2 + l)));
1453	  strcpy (result + offset, list[i]);
1454	  offset += l;
1455	  if (i + 1 < last)
1456	    {
1457	      strcpy (result + offset, " ");
1458	      offset++;
1459	    }
1460	}
1461    }
1462
1463  for (i = 0; i < len; i++)
1464    free (list[i]);
1465
1466  free (list);
1467
1468  return (result);
1469}
1470
1471#define slashify_in_quotes "\\`\"$"
1472
1473/* Return an array of tokens, much as the shell might.  The tokens are
1474   parsed out of STRING. */
1475char **
1476history_tokenize (string)
1477     char *string;
1478{
1479  char **result = (char **)NULL;
1480  register int i, start, result_index, size;
1481  int len;
1482
1483  i = result_index = size = 0;
1484
1485  /* Get a token, and stuff it into RESULT.  The tokens are split
1486     exactly where the shell would split them. */
1487 get_token:
1488
1489  /* Skip leading whitespace. */
1490  for (; string[i] && whitespace(string[i]); i++);
1491
1492  start = i;
1493
1494  if (!string[i] || string[i] == history_comment_char)
1495    return (result);
1496
1497  if (member (string[i], "()\n")) {
1498    i++;
1499    goto got_token;
1500  }
1501
1502  if (member (string[i], "<>;&|")) {
1503    int peek = string[i + 1];
1504
1505    if (peek == string[i]) {
1506      if (peek ==  '<') {
1507	if (string[1 + 2] == '-')
1508	  i++;
1509	i += 2;
1510	goto got_token;
1511      }
1512
1513      if (member (peek, ">:&|")) {
1514	i += 2;
1515	goto got_token;
1516      }
1517    } else {
1518      if ((peek == '&' &&
1519	  (string[i] == '>' || string[i] == '<')) ||
1520	  ((peek == '>') &&
1521	  (string[i] == '&'))) {
1522	i += 2;
1523	goto got_token;
1524      }
1525    }
1526    i++;
1527    goto got_token;
1528  }
1529
1530  /* Get word from string + i; */
1531  {
1532    int delimiter = 0;
1533
1534    if (member (string[i], "\"'`"))
1535      delimiter = string[i++];
1536
1537    for (;string[i]; i++) {
1538
1539      if (string[i] == '\\') {
1540
1541	if (string[i + 1] == '\n') {
1542	  i++;
1543	  continue;
1544	} else {
1545	  if (delimiter != '\'')
1546	    if ((delimiter != '"') ||
1547		(member (string[i], slashify_in_quotes))) {
1548	      i++;
1549	      continue;
1550	    }
1551	}
1552      }
1553
1554      if (delimiter && string[i] == delimiter) {
1555	delimiter = 0;
1556	continue;
1557      }
1558
1559      if (!delimiter && (member (string[i], " \t\n;&()|<>")))
1560	goto got_token;
1561
1562      if (!delimiter && member (string[i], "\"'`")) {
1563	delimiter = string[i];
1564	continue;
1565      }
1566    }
1567    got_token:
1568
1569    len = i - start;
1570    if (result_index + 2 >= size) {
1571      if (!size)
1572	result = (char **)xmalloc ((size = 10) * (sizeof (char *)));
1573      else
1574	result =
1575	  (char **)xrealloc (result, ((size += 10) * (sizeof (char *))));
1576    }
1577    result[result_index] = (char *)xmalloc (1 + len);
1578    strncpy (result[result_index], string + start, len);
1579    result[result_index][len] = '\0';
1580    result_index++;
1581    result[result_index] = (char *)NULL;
1582  }
1583  if (string[i])
1584    goto get_token;
1585
1586  return (result);
1587}
1588
1589#if defined (STATIC_MALLOC)
1590
1591/* **************************************************************** */
1592/*								    */
1593/*			xmalloc and xrealloc ()		     	    */
1594/*								    */
1595/* **************************************************************** */
1596
1597static void memory_error_and_abort ();
1598
1599static char *
1600xmalloc (bytes)
1601     int bytes;
1602{
1603  char *temp = (char *)malloc (bytes);
1604
1605  if (!temp)
1606    memory_error_and_abort ();
1607  return (temp);
1608}
1609
1610static char *
1611xrealloc (pointer, bytes)
1612     char *pointer;
1613     int bytes;
1614{
1615  char *temp;
1616
1617  if (!pointer)
1618    temp = (char *)xmalloc (bytes);
1619  else
1620    temp = (char *)realloc (pointer, bytes);
1621
1622  if (!temp)
1623    memory_error_and_abort ();
1624
1625  return (temp);
1626}
1627
1628static void
1629memory_error_and_abort ()
1630{
1631  fprintf (stderr, "history: Out of virtual memory!\n");
1632  abort ();
1633}
1634#endif /* STATIC_MALLOC */
1635
1636
1637/* **************************************************************** */
1638/*								    */
1639/*				Test Code			    */
1640/*								    */
1641/* **************************************************************** */
1642#ifdef TEST
1643main ()
1644{
1645  char line[1024], *t;
1646  int done = 0;
1647
1648  line[0] = 0;
1649
1650  while (!done)
1651    {
1652      fprintf (stdout, "history%% ");
1653      t = gets (line);
1654
1655      if (!t)
1656	strcpy (line, "quit");
1657
1658      if (line[0])
1659	{
1660	  char *expansion;
1661	  int result;
1662
1663	  using_history ();
1664
1665	  result = history_expand (line, &expansion);
1666	  strcpy (line, expansion);
1667	  free (expansion);
1668	  if (result)
1669	    fprintf (stderr, "%s\n", line);
1670
1671	  if (result < 0)
1672	    continue;
1673
1674	  add_history (line);
1675	}
1676
1677      if (strcmp (line, "quit") == 0) done = 1;
1678      if (strcmp (line, "save") == 0) write_history (0);
1679      if (strcmp (line, "read") == 0) read_history (0);
1680      if (strcmp (line, "list") == 0)
1681	{
1682	  register HIST_ENTRY **the_list = history_list ();
1683	  register int i;
1684
1685	  if (the_list)
1686	    for (i = 0; the_list[i]; i++)
1687	      fprintf (stdout, "%d: %s\n", i + history_base, the_list[i]->line);
1688	}
1689      if (strncmp (line, "delete", strlen ("delete")) == 0)
1690	{
1691	  int which;
1692	  if ((sscanf (line + strlen ("delete"), "%d", &which)) == 1)
1693	    {
1694	      HIST_ENTRY *entry = remove_history (which);
1695	      if (!entry)
1696		fprintf (stderr, "No such entry %d\n", which);
1697	      else
1698		{
1699		  free (entry->line);
1700		  free (entry);
1701		}
1702	    }
1703	  else
1704	    {
1705	      fprintf (stderr, "non-numeric arg given to `delete'\n");
1706	    }
1707	}
1708    }
1709}
1710
1711#endif				/* TEST */
1712
1713/*
1714* Local variables:
1715* compile-command: "gcc -g -DTEST -o history history.c"
1716* end:
1717*/