/* grep - search files for regular expression matches.
   Copyright (C) 1985 Dave Curry

This program is distributed in the hope that it will be useful,
but without any warranty.  No author or distributor
accepts responsibility to anyone for the consequences of using it
or for whether it serves any particular purpose or works at all,
unless he says so in writing.

   Permission is granted to anyone to distribute verbatim copies
   of this program's source code as received, in any medium, provided that
   the copyright notice, the nonwarraty notice above
   and this permission notice are preserved,
   and that the distributor grants the recipient all rights
   for further redistribution as permitted by this notice,
   and informs him of these rights.

   Permission is granted to distribute modified versions of this
   program's source code, or of portions of it, under the above
   conditions, plus the conditions that all changed files carry
   prominent notices stating who last changed them and that the
   derived material, including anything packaged together with it and
   conceptually functioning as a modification of it rather than an
   application of it, is in its entirety subject to a permission
   notice identical to this one.

   Permission is granted to distribute this program (verbatim or
   as modified) in compiled or executable form, provided verbatim
   redistribution is permitted as stated above for source code, and
    A.  it is accompanied by the corresponding machine-readable
      source code, under the above conditions, or
    B.  it is accompanied by a written offer, with no time limit,
      to distribute the corresponding machine-readable source code,
      under the above conditions, to any one, in return for reimbursement
      of the cost of distribution.   Verbatim redistribution of the
      written offer must be permitted.  Or,
    C.  it is distributed by someone who received only the
      compiled or executable form, and is accompanied by a copy of the
      written offer of source code which he received along with it.

In other words, you are welcome to use, share and improve this program.
You are forbidden to forbid anyone else to use, share and improve
what you give them.   Help stamp out software-hoarding!  */

/*
 * cgrep - context grep
 *
 * This program is very similar to grep, except that it prints lines
 * of context around matching lines.  The default is one line before
 * and one line after, but this may be changed.
 *
 * Flags understood are:
 *
 *	-A #	print # lines of context before matching lines
 *	-B #	print # lines of context after matching lines
 *	-C	turn on context grep (above flags do it too)
 *	-E	print in modified -n format for EMACS (JOVE) such that
 *		only the matching lines (and not the context lines) are
 *		parsed by the parse-some-errors command.
 *	-F	print the file name at the beginning of each matching line
 *	-L	do not separate matches with '-----' or blank lines
 *	-b	print block numbers in front of lines
 *	-c	count matching lines only
 *	-e	next argument is expression (for exprs starting with '-')
 *	-f	read expression from file
 *	-i	ignore case
 *	-l	print names of files containing matching lines only
 *	-n	number lines on output
 *	-s	silent mode - error messages only
 *	-v	all lines but those matching are printed (grep only)
 *	-w	match expression as if surrounded by \<...\>
 *	-?	print usage message and special characters
 *
 * David A. Curry, 7/9/84
 * davy@purdue-ecn
 * decvax!pur-ee!davy
 *
 * 4/20/85 - added -C flag, made -# and +# turn on cgrep.  Cleaned up
 *	      and converted to use GNU regex routines.
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <stdio.h>
#include <ctype.h>
#include "regex.h"

#define LBSIZE		256		/* starting size of buffers	*/
#define EXPSIZE		256		/* starting regexp buffer size	*/
#define NCONTEXT	1		/* default # of context lines	*/

#ifndef BYTEWIDTH
#define BYTEWIDTH	8
#endif

/*
 * We save (2 * ncontext + 1) lines at a time in
 * structures like these.
 */
struct line {
	long len;			/* length of this line		*/
	long size;			/* size of this buffer		*/
	long blkno;			/* block number of line		*/
	long lineno;			/* line number in the file	*/
	char *linebuf;			/* the line itself		*/
};

char print_context;
char print_for_emacs;
char print_line_file_names;
char dont_print_separators;
char print_block_numbers;
char print_count_only;
char ignore_case;
char only_mention_file_if_match;
char print_line_numbers;
char silent;
char negate_condition;
char match_as_word;

int nfile;				/* number of files to do	*/
int nsucc;				/* 1 if found anything		*/
long nmatch;				/* number of matching lines	*/
long nbytes;				/* number of bytes read		*/
long lineno;				/* line number in the file	*/
long nlines;				/* number of lines saved	*/
int retcode;				/* exit code			*/

int input_fd;				/* input file descriptor	*/
int blocksize;				/* block size of input file	*/
char *readbufp;				/* ptr to next char to fetch	*/
int readbufcount;			/* number of characters of 	*/
					/* buffer actually filled with 	*/
					/* data. -1 means have yet to	*/
					/* fill it; 0 = end of file.	*/
char readbuf[8192];			/* buffer for reading from file	*/

int precontext;
int aftcontext;
char *expression;			/* the compiled expression	*/
struct line **lines;			/* the array of lines we save	*/

struct re_pattern_buffer re_comp_buf = {0,};
char re_comp_fastmap[256];
char downcase_table[256];

char *malloc (), *xmalloc (), *calloc (), *xcalloc (), *realloc (), *xrealloc ();


char *progname;				/* For error messages */

main (argc, argv)
     int argc;
     char **argv;
{
  register int i;
  struct stat sbuf;
  register char *s;
  extern char _sobuf[];
  char *rindex ();
  register int argn;
  int fd;
	
  retcode = 0;
  expression = NULL;
  precontext = aftcontext = NCONTEXT;
  print_context = print_for_emacs = 0;
  print_line_file_names = dont_print_separators = 0;
  print_block_numbers = print_count_only = 0;
  ignore_case = only_mention_file_if_match = print_line_numbers = 0;
  silent = negate_condition = match_as_word = 0;

  progname = argv[0];
  /*
   * Figure out what mode we're in.
   */
  if ((s = rindex (argv[0], '/')) == NULL)
    s = argv[0];
  else
    s++;

  if (*s == 'c')
    print_context = 1;

  /*
   * Process the arguments.
   */
  for (argn = 1; argn < argc && argv[argn][0] == '-'; argn++)
    {
      for (s = argv[argn]+1; *s; s++)
	{
	  switch (*s)
	    {
	    case 'A':		/* number of post-context lines */
	      print_context = 1;

	      if (isdigit (*(s+1)))
		{
		  aftcontext = atoi (++s);

		  while (isdigit (*(s+1)))
		    s++;
		}
	      else
		{
		  if (argn + 1 >= argc)
		    {
		      error ("must give number after -A.\n", 0);
		      exit (2);
		    }

		  aftcontext = atoi (argv[++argn]);
		}

	      continue;
	    case 'B':		/* number of pre-context lines */
	      print_context = 1;

	      if (isdigit (*(s+1)))
		{
		  precontext = atoi (++s);

		  while (isdigit (*(s+1)))
		    s++;
		}
	      else
		{
		  if (argn + 1 >= argc)
		    {
		      error ("must give number after -B.\n", 0);
		      exit (2);
		    }

		  precontext = atoi (argv[++argn]);
		}

	      continue;
	    case 'C':		/* do context */
	      print_context++;
	      continue;
	    case 'E':		/* EMACS mode */
	      print_for_emacs++;
	      continue;
	    case 'F':		/* print filenames */
	      print_line_file_names++;
	      continue;
	    case 'L':		/* no separators */
	      dont_print_separators++;
	      continue;
	    case 'b':		/* block numbers */
	      print_block_numbers++;
	      continue;
	    case 'c':		/* count matching lines */
	      print_count_only++;
	      continue;
	    case 'e':		/* next arg is expr */
	      if (argn + 1 >= argc)
		{
		  error ("-e with no following expression.\n", 0);
		  exit (2);
		}
	      expression = argv[++argn];
	      continue;
	    case 'f':		/* read expression from file */
	      if (argn + 1 >= argc)
		{
		  error ("-f with no following filename.\n", 0);
		  exit (2);
		}

	      if ((fd = open (argv[++argn], 0)) < 0)
		{
		  error ("", 0);
		  perror (argv[argn]);
		  exit (2);
		}

	      fstat (fd, &sbuf);

	      expression = xmalloc (sbuf.st_size + 1);

	      read (fd, expression, sbuf.st_size);
	      expression[sbuf.st_size] = NULL;

	      close (fd);
	      continue;
	    case '?':		/* usage */
	      usage ();
	      exit (0);
	    case 'i':		/* ignore case */
	      ignore_case++;
	      continue;
	    case 'l':		/* file names only */
	      only_mention_file_if_match++;
	      continue;
	    case 'n':		/* print line numbers */
	      print_line_numbers++;
	      continue;
	    case 's':		/* silent mode */
	      silent++;
	      continue;
	    case 'v':		/* non-matching lines */
	      negate_condition++;
	      continue;
	    case 'w':		/* match as \<...\> */
	      match_as_word++;
	      continue;

	    default:
	      error ("unknown option \"%c\".\n", *s);
	      exit (2);
	    }
	}
    }

  if (!print_context)
    {
      precontext = 0;
      aftcontext = 0;
    }

  if (expression == NULL)
    {
      if (argn >= argc)
	{
	  error ("no regexp to match specified.\n", 0);
	  exit (2);
	}
      expression = argv[argn++];
    }

  /*
   * Buffer the output.  Changed this to only do it if printing context.
   * so that otherwise you can interrupt out
   * of grep as soon as you see what you want instead
   * of waiting for the buffer to flush.
   */
  if (print_context)
    setbuf (stdout, _sobuf);
	
  if (match_as_word)
    {
      s = xmalloc (strlen (expression) + 5);
      sprintf (s, "\\<%s\\>", expression);
      expression = s;
    }

  nlines = precontext + aftcontext + 1;
	
  /*
   * Get the pointers to the line buffers.
   */
  lines = (struct line **) xcalloc (nlines, sizeof (struct line *));

  /*
   * Get the line buffer structures.
   */
  for (i=0; i < nlines; i++)
    {
      lines[i] = (struct line *) xcalloc (1, sizeof (struct line));
    }
	
  /*
   * Get the line buffers.
   */
  for (i=0; i < nlines; i++)
    {
      lines[i]->linebuf = xmalloc (LBSIZE);
      lines[i]->size = LBSIZE;
    }

  /*
   * Compile the regular expression.
   */

  /*
   * We do "ignore case" by using the translate table.
   */
  if (ignore_case)
    {
      for (i=0; i <= 256; i++)
	downcase_table[i] = i;

      for (i='A'; i <= 'Z'; i++)
	downcase_table[i] = i - 'A' + 'a';

      re_comp_buf.translate = downcase_table;
    }
	
  re_comp_buf.fastmap = re_comp_fastmap;

  expression = re_compile_pattern (expression, strlen (expression),
				   &re_comp_buf);
  if (expression)
    {
      error ("regular expression error: ", expression);
      exit (2);
    }
	
  nfile = argc - argn;

  /*
   * Process the files.
   */
  if (argn == argc)
    {
      execute (NULL);
    }
  else
    {
      while (argn < argc)
	execute (argv[argn++]);
    }
	
  exit (retcode != 0 ? retcode : nsucc == 0);
}

/*
 * execute - look for expression in file
 */
execute (file)
     char *file;
{
  register int gotone;
  struct stat statbuf;
	
  /*
   * Get file as standard input.
   */
  if (file)
    {
      if ((input_fd = open (file, O_RDONLY)) < 0)
	{
	  error ("", 0);
	  perror (file );
	  retcode = 2;
	  return;
	}
    }
  else
    {
      input_fd = fileno (stdin);
    }
	
  fstat (input_fd, &statbuf);
  blocksize = statbuf.st_blksize;
  if (blocksize == 0) blocksize = 512;

  gotone = 0;
  nmatch = 0;
  nbytes = 0;
  lineno = 1;
  readbufcount = -1;
  readbufp = readbuf;

  /*
   * Load in the initial set of lines.
   */
  loadup ();

  /*
   * lines[precontext]->lineno will be -1 when
   * we've hit the end of the file.
   */
  while (lines[precontext]->lineno > 0)
    {
      /*
       * Look for a match on this line.
       */
      if ((0 <= re_search (&re_comp_buf, lines[precontext]->linebuf,
			   lines[precontext]->len, 0,
			   lines[precontext]->len, 0))
	  == !negate_condition)
	{
	  /*
	   * If we need to, print a separator.
	   */
	  if (((precontext > 0) || (aftcontext > 0))
	      && gotone && !dont_print_separators
	      && !print_count_only && !only_mention_file_if_match && !silent)
	    {
	      if (!print_line_numbers && !print_for_emacs)
		printf ("-----\n");
	      else
		printf ("\n");

	      gotone = 0;
	    }

	  output (file);
	  gotone = 1;
	}

      /*
       * Get the next line.
       */
      nextline ();
    }

  if (print_count_only)
    {
      if ((nfile > 1) || print_line_file_names)
	printf ("%s:", file);

      printf ("%ld\n", nmatch);
    }

  if (file)
    close (input_fd);
}

/*
 * loadup - loads nlines lines, starting at lines[precontext].
 */
loadup ()
{
  register int i;

  /*
   * Initialize.
   */
  for (i = 0; i < nlines; i++)
    {
      lines[i]->len = 0;
      lines[i]->blkno = 0;
      lines[i]->lineno = -1;
      lines[i]->linebuf[0] = NULL;
    }

  /*
   * Load lines.
   */
  for (i = precontext; i < nlines; i++)
    getline (lines[i]);
}

/*
 * nextline - diddle the pointers to "shift" the buffers up, and
 *	      get a new line in last buffer.
 */
nextline ()
{
  register int i;
  register struct line *tmp;
	
  /*
   * Save first line.
   */
  tmp = lines[0];

  /*
   * Copy lines.
   */
  for (i=1; i < nlines; i++)
    lines[i-1] = lines[i];
	
  /*
   * Get last line.
   */
  lines[--i] = tmp;
	
  getline (lines[i]);
}

getline (l)
     register struct line *l;
{
  register int c;
  int maxread, spaceleft;
  register char *p, *q, *endp;

  l->len = 0;
  l->blkno = nbytes / blocksize;

  /*
   * Already at end of file, nothing to read.
   */
  if (readbufcount == 0)
    {
      l->lineno = -1;
      return;
    }

  l->lineno = lineno++;

  for (;;)
    {
      maxread = readbuf + readbufcount - readbufp;

      if (maxread <= 0)
	{
	  readbufcount = read (input_fd, readbuf, sizeof (readbuf));

	  if (readbufcount == 0)
	    break;

	  readbufp = readbuf;
	  maxread = readbufcount;
	}

      spaceleft = l->size - l->len - 1;

      if (spaceleft <= 0)
	{
	  p = xrealloc (l->linebuf, l->size *= 2);

	  l->linebuf = p;
	  spaceleft = l->size - l->len - 1;
	}

      if (maxread > spaceleft)
	maxread = spaceleft;

      p = readbufp;
      q = l->linebuf + l->len;
      endp = readbufp + maxread;

      while (p != endp)
	{
	  c = *p++;
	  *q++ = c;

	  if (c == '\n')
	    break;
	}

      l->len += p - readbufp;
      nbytes += p - readbufp;
      readbufp = p;

      if (c == '\n')
	{
	  l->len--;
	  break;
	}
    }

  l->linebuf[l->len] = NULL;
}

/*
 * output - prints the window of context, and the line itself
 */
output (file)
     char *file;
{
  register int i;
	
  nsucc = 1;

  if (silent)
    return;

  if (print_count_only)
    {
      nmatch++;
      return;
    }
	
  if (only_mention_file_if_match)
    {
      printf ("%s\n", file);
      fflush (stdout);
      lseek (input_fd, 0L, 2);
      return;
    }
	
  for (i=0; i < nlines; i++)
    {
      if (lines[i]->lineno == -1)
	continue;

      if ((nfile > 1) || print_line_file_names || print_for_emacs)
	{
	  printf ("%s", file);

	  if (print_for_emacs)
	    {
	      if (i < precontext)
		putchar ('-');
	      else if (i == precontext)
		putchar (':');
	      else
		putchar ('+');
	    }
	  else
	    {
	      putchar (':');
	    }
	}

      if (print_block_numbers)
	printf ("%ld:", lines[i]->blkno);

      if (print_line_numbers || print_for_emacs)
	{
	  printf ("%ld", lines[i]->lineno);

	  if (print_for_emacs)
	    {
	      if (i < precontext)
		putchar ('-');
	      else if (i == precontext)
		putchar (':');
	      else
		putchar ('+');
	    }
	  else
	    {
	      putchar (':');
	    }
	}

      fwrite (lines[i]->linebuf, 1, lines[i]->len, stdout);
      putchar ('\n');
    }
}


error (fmt, arg)
     char *fmt, *arg;
{
  fprintf (stderr, "%s: ", progname);
  fprintf (stderr, fmt, arg);
}

usage ()
{
  printf ("Usage: grep [-#] [+#] [-A #] [-B #] [-CELbcefhilnsvw] expression file (s)\n");
  printf("Special characters: . $ + * ^ \\| [ - ] \\( \\) \\b \\B \\s \\S \\w \\W\n");
}

out_of_memory ()
{
  error ("out of memory.\n", 0);
  exit (2);
}

char *
xmalloc (size)
     int size;
{
  register char *val;
  if (val = malloc (size)) return (val);
  out_of_memory ();
}

char *
xrealloc (ptr, size)
     char *ptr;
     int size;
{
  register char *val;
  if (val = realloc (ptr, size)) return (val);
  out_of_memory ();
}

char *
xcalloc (num, size)
     int num, size;
{
  register char *val;
  if (val = calloc (num, size)) return (val);
  out_of_memory ();
}
