/*****************************************************************************
*           Change Log
*  Date     | Change
*-----------+-----------------------------------------------------------------
* 11-Jan-86 | Created change log
* 11-Jan-86 | Set up defaults to make sense, i.e., be useful
* 11-Jan-86 | Added boolean variable 'fold', make all case sensitivity
*           | run-time determined
* 11-Jan-86 | Use C-TOOLS directory functions to allow wildcards
* 11-Jan-86 | Handle nonlocal directories
*           | ': ' doesn't work because the command scanner breaks it at
*           | the space; made it :.
*           | :0 or :9 for digits
* 11-Jan-86 | Added -x switch to allow case sensitivity
* 11-Jan-86 | Added -s switch to force subdirectory scan
*****************************************************************************/
 /*
 *
 *
 * The	information  in  this  document  is  subject  to  change
 * without  notice  and  should not be construed as a commitment
 * by Digital Equipment Corporation or by DECUS.
 *
 * Neither Digital Equipment Corporation, DECUS, nor the authors
 * assume any responsibility for the use or reliability of  this
 * document or the described software.
 *
 *	Copyright (C) 1980, DECUS
 *
 *
 * General permission to copy or modify, but not for profit,  is
 * hereby  granted,  provided that the above copyright notice is
 * included and reference made to  the	fact  that  reproduction
 * privileges were granted by DECUS.
 *
 */
#include <stdio.h>
#ifndef DESMET
#include <ctype.h>
#endif
#include <direct.h>
 /*
 * grep.
 *
 * Runs on the Decus compiler or on vms.
 * Converted for BDS compiler (under CP/M-80), 20-Jan-83, by Chris Kern.
 *
 * Converted to IBM PC with CI-C86 C Compiler June 1983 by David N. Smith
 *
 * Modifications by Larry Afrin, November 1985:
 *    1. Definition of DESMET preprocessor variable for use with the DeSmet
 *       C compiler for the IBM PC.
 *    2. Exclusion of inclusion of the <ctype.h> file under DeSmet
 *       compilation.
 *    3. Implementation of upper/lower case differentiation:
 *       a. Adjustment of help message;
 *       b. Commented out calls to tolower() where appropriate;
 *       c. Inserted additional code where appropriate (documented at
 *          insertion points.
 *
 * On vms, define as:
 *
 *	grep :== "$disk:[account]grep"     (native)
 *	grep :== "$disk:[account]grep grep"     (Decus)
 *
 * See below for more information.
 *
 */
char	*documentation[] = {
"grep searches a file for a given pattern.  Execute by",
"   grep [flags] regular_expression file_list",
"",
"Flags are single characters preceeded by '-':",
"   -c      Only a count of matching lines is printed",
"   -f      Do not print file name for matching lines switch, see below",
"   -n      Do not precede each line by its line number",
"   -s      Scan all files in any subdirectory encountered",
"   -v      Only print non-matching lines",
"   -x      Consider upper and lower case as distinct",
"",
"The file_list is a list of files (wildcards are acceptable on RSX modes).",
"",
"The file name is normally printed if there is a file given.",
"The -f flag reverses this action (print name no file, not if more).",
"",
0 };
 /* Following message changed from "upper and lower case always ignored" to
   "upper and lower case never ignored" by Larry Afrin (see header notes). */
char	*patdoc[] = {
"The regular_expression defines the pattern to search for.  Upper- and",
"lower-case are ignored unless requested otherwise (-x).  Blank lines",
"never match.",
/* "The expression should be quoted to prevent file-name translation.", */
"x      An ordinary character (not mentioned below) matches that character.",
"'\\'    The backslash quotes any character.  \"\\$\" matches a dollar-sign.",
"'^'    A circumflex at the beginning of an expression matches the",
"       beginning of a line.",
"'$'    A dollar-sign at the end of an expression matches the end of a line.",
"'.'    A period matches any character except \"new-line\".",
"':a'   A colon matches a class of characters described by the following",
"':d'     character.  \":a\" matches any alphabetic, \":d\" or \":0\" matches",
"':n'     digits, \":n\" matches alphanumerics, \":.\" matches spaces, tabs,",
"':.'     and other control characters, such as new-line.",
"'*'    An expression followed by an asterisk matches zero or more",
"       occurrances of that expression: \"fo*\" matches \"f\", \"fo\"",
"       \"foo\", etc.",
"'+'    An expression followed by a plus sign matches one or more",
"       occurrances of that expression: \"fo+\" matches \"fo\", etc.",
"'-'    An expression followed by a minus sign optionally matches",
"       the expression.",
"'[]'   A string enclosed in square brackets matches any character in",
"       that string, but no others.  If the first character in the",
"       string is a circumflex, the expression matches any character",
"       except \"new-line\" and the characters in the string.  For",
"       example, \"[xyz]\" matches \"xx\" and \"zyx\", while \"[^xyz]\"",
"       matches \"abc\" but not \"axb\".  A range of characters may be",
"       specified by two characters separated by \"-\".  Note that,",
"       [a-z] matches alphabetics, while [z-a] never matches.",
"The concatenation of regular expressions is a regular expression.",
0};
/*$ifdef  vms					 */
/*$define VMS		    VMS native compiler  */
/*$define error(s)   _error(s)			 */
/*$endif					 */
#define LMAX	512
#define PMAX	256
#define CHAR	1
#define BOL	2
#define EOL	3
#define ANY	4
#define CLASS	5
#define NCLASS	6
#define STAR	7
#define PLUS	8
#define MINUS	9
#define ALPHA	10
#define DIGIT	11
#define NALPHA	12
#define PUNCT	13
#define RANGE	14
#define ENDPAT	15
int	cflag;
int	fflag = 1;
int	nflag = 1;
int	vflag;
int	nfile;
int	fold = 1;
int	debug	=	0;	   /* Set for debug code      */
int	dscan = 0;
char	*pp;
#ifndef vms
char	file_name[81];
#endif
char	lbuf[LMAX];
char	pbuf[PMAX];

/****************************************************************************
*                                   savedr
* Inputs:
*       char * ldr: Local drbuffer
* Effect: 
*       Copies the contents of the global state 'drbuffer' (part of the
*	CTOOLS directory scan package; see 'drsnext' documentation)
*	to the area ldr
****************************************************************************/
extern char drbuffer[48];

savedr(ldr)
   char * ldr;
    {
     int i;
     for(i=0;i<48;i++) ldr[i] = drbuffer[i];
    }

/****************************************************************************
*                                  restoredr
* Inputs:
*       char * ldr: Local dr buffer
* Effect: 
*       Restores the contents saved in ldr into the drbuffer
****************************************************************************/

restoredr(ldr)
    char * ldr;
    {
     int i;
     for(i=0;i<48;i++) drbuffer[i] = ldr[i];
    }

/****************************************************************************
*                                  scanfiles
* Inputs:
*       char * p: Directory preface
* Effect: 
*       greps the file.  If it is a directory, calls this procedure
*	recursively to walk the directory
****************************************************************************/

scanfiles(p)
    char * p;
    {
     FSPEC fdesc;
     int result;
     int first;
     char dir[80];
     int j;
     FILE * f;
     char dr[48];

     first = 1;

     if(debug)
	printf("scanfiles(%s)\n",p);

     /* Isolate the device and directory portions of the path */

     strcpy(dir,p);

     /* Scan backward to first instance of a '\\' or ':' */

     for(j = strlen(dir) - 1; j>=0; j--)
	{ /* scan back */
	 if(dir[j] == '\\' || dir[j] == ':')
	    break;
	 dir[j] = '\0';
	} /* scan back */

     /* Scan all files in pattern */

     while(1)
	{ /* scan files */
	 char name[80];

	 if(debug)
	    	if(first) printf("First: \"%s\"\n",p);
		   else   printf("Next: from \"%s\"\n",fdesc.fname);

	 if(first)
	    result = drsfirst(p,16+1,&fdesc);  /* general (0) readonly (1)
	    					  and subdirectory (16) files
					       */
	 else
	    result = drsnext(&fdesc);

	 switch(result)
	    { /* decode */
	     case 0: break;	/* success */
	     case 2: /* file not found */
		    cant(p);
		    goto next; /* C brain damage overloaded 'break' */
	     case 18: /* no files match pattern */
		    if(first) nofiles(p);
		    goto next; /* C brain damage overloaded 'break' */
	    } /* decode */

	 /* See if it is a directory */

	 first = 0;

	 if( (fdesc.fattr & 036) == 16)
	    { /* subdirectory */
	     if(dscan)
	        { /* scan subdirectory */
		 /* ignore . and .. */
		 if(strcmp(fdesc.fname,".") == 0 ||
		    strcmp(fdesc.fname,"..") == 0) continue;

		 strcpy(name,dir);
		 strcat(name,fdesc.fname);
		 strcat(name,"\\*.*");
		 savedr(dr);
		 scanfiles(name);
		 restoredr(dr);
		 if(debug) printf("Returned from subdirectory scan\n");
		 continue;
		} /* scan subdirectory */

	     continue;		/* skip directory */
	    } /* subdirectory */


	 /* Mask off the following bits:
		32 040 - archive
		 1 001 - readonly
	    leaving only
		16 020 - subdirectory
		 4 004 - system
		 2 002 - hidden
	 */


	 strcpy(name,dir);
	 strcat(name,fdesc.fname);

	 if(debug)
	     printf("%s{%02o}%s",fdesc.fname,fdesc.fattr,name); 

	 if( (fdesc.fattr & 036) != 0) continue;	

	 if(debug)
	    printf("\n");  

	 if ((f=fopen(name, "r")) == NULL)
	    cant(name);
	 else 
	    {
	     grep(f, name);
	     fclose(f);
	    }
	} /* scan files */
     next:
    }

/****************************************************************************
*                                    main
* Inputs:
*       int argc: Argument count
*	char * argv[]: List of arguments
****************************************************************************/

main(argc, argv)
char *argv[];
{
   register char   *p;
   register int    c, i;
   int		   gotpattern;
   int		   gotcha;
   FILE 	   *f;
   if (argc <= 1)
      usage("No arguments");
   if (argc == 2 && argv[1][0] == '?' && argv[1][1] == 0) {
      help(documentation);
      help(patdoc);
      return;
      }
   nfile = argc-1;
   gotpattern = 0;
   for (i=1; i < argc; ++i) {
      p = argv[i];
      if (*p == '-') {
	 ++p;
	 while (c = *p++) {
	    switch(tolower(c)) {
	    case '?':
	       help(documentation);
	       break;
	    case 'C':
	    case 'c':
	       ++cflag;
	       break;
	    case 'D':
	    case 'd':
	       ++debug;
	       break;
	    case 'F':
	    case 'f':
	       fflag = 0;
	       break;
	    case 'n':
	    case 'N':
	       nflag = 0;
	       break;
	    case 's':
	    case 'S':
	    	dscan = 1;
		break;
	    case 'v':
	    case 'V':
	       ++vflag;
	       break;
	    case 'x':
	    case 'X':
	    	fold = 0;
		break;
	    default:
	       usage("Unknown flag");
	    }
	 }
	 argv[i] = 0;
	 --nfile;
      } else if (!gotpattern) {
	 compile(p);
	 argv[i] = 0;
	 ++gotpattern;
	 --nfile;
      }
   }
   if (!gotpattern)
      usage("No pattern");
   if (nfile == 0)
      grep(stdin, 0);
   else 
      { /* file list */
       int first;
       
      for (i=1; i < argc; ++i) 
         {
	 if (p = argv[i]) 
	    { /* scan */
	     scanfiles(p);
	    } /* scan */
	 }
      } /* file list */
}

/****************************************************************************
*                                    file
* Inputs:
*       char * s: Filename to print
* Effect: 
*       Prints out the filename to stdout
****************************************************************************/

file(s)
char *s;
{
/* printf("file(0x%x (%s))\n",s,s); */
   printf("%s", s);
}

/****************************************************************************
*                                    cant
* Inputs:
*       char * s: File whose open failed
* Effect: 
*       Issues error msg to stderr
****************************************************************************/

cant(s)
char *s;
{
   fprintf(stderr, "%s: cannot open\n", s);
}

/****************************************************************************
*                                    nofiles
* Inputs:
*       char * s: File whose scan failed
* Effect: 
*       Issues error msg to stderr
****************************************************************************/

nofiles(s)
char *s;
{
   fprintf(stderr, "%s: no files matched\n", s);
}

/****************************************************************************
*                                    help
* Inputs:
*       char * hp[]: Table of help text
* Effect: 
*       Gives help
****************************************************************************/

help(hp)
char *hp[];
{
   register char   **dp;
   for (dp = hp; *dp; dp++)
      printf("%s\n", *dp);
}

/****************************************************************************
*                                    usage
* Inputs:
*       char * s: Error message to issue
* Effect: 
*       Issues proper usage message
****************************************************************************/

usage(s)
char	*s;
{
   fprintf(stderr, "?GREP-E-%s\n", s);
   fprintf(stderr,
      "Usage: grep [-cfnv] pattern [file ...].  grep ? for help\n");
   exit(1);
}

/****************************************************************************
*                                   compile
* Inputs:
*       char * source: Pattern to compile
* Effect: 
*       Compiles the pattern into the global variable pbuf
****************************************************************************/

compile(source)
char	   *source;   /* Pattern to compile	    */
{
   register char  *s;	      /* Source string pointer	   */
   register char  *lp;	      /* Last pattern pointer	   */
   register int   c;	      /* Current character	   */
   int		  o;	      /* Temp			   */
   char 	  *spp;       /* Save beginning of pattern */
   char 	  *cclass();  /* Compile class routine	   */
   s = source;
   if (debug)
      printf("Pattern = \"%s\"\n", s);
   pp = pbuf;
   while (c = *s++) {
      /*
       * STAR, PLUS and MINUS are special.
       */
      if (c == '*' || c == '+' || c == '-') {
	 if (pp == pbuf ||
	      (o=pp[-1]) == BOL ||
	      o == EOL ||
	      o == STAR ||
	      o == PLUS ||
	      o == MINUS)
	    badpat("Illegal occurrance op.", source, s);
	 store(ENDPAT);
	 store(ENDPAT);
	 spp = pp;		 /* Save pattern end	 */
	 while (--pp > lp)	 /* Move pattern down	 */
	    *pp = pp[-1];	 /* one byte		 */
	 *pp =	 (c == '*') ? STAR :
	    (c == '-') ? MINUS : PLUS;
	 pp = spp;		 /* Restore pattern end  */
	 continue;
      }
      /*
       * All the rest.
       */
      lp = pp;	       /* Remember start       */
      switch(c) {
      case '^':
	 store(BOL);
	 break;
      case '$':
	 store(EOL);
	 break;
      case '.':
	 store(ANY);
	 break;
      case '[':
	 s = cclass(source, s);
	 break;
      case ':':
	 if (*s) {
	    c = *s++;
	    switch(tolower(c)) {
	    case 'a':
	    case 'A':
	       store(ALPHA);
	       break;
	    case 'd':
	    case 'D':
	    case '0':
	    case '9':
	       store(DIGIT);
	       break;
	    case 'n':
	    case 'N':
	       store(NALPHA);
	       break;
	    case ' ':
	    case '.':
	       store(PUNCT);
	       break;
	    default:
	       badpat("Unknown : type", source, s);
	    }
	    break;
	 }
	 else	 badpat("No : type", source, s);
      case '\\':
	 if (*s)
	    c = *s++;
      default:
	 store(CHAR);
	 store( (fold ? tolower(c) : c) );
      }
   }
   store(ENDPAT);
   store(0);		    /* Terminate string     */
   if (debug) {
      for (lp = pbuf; lp < pp;) {
	 if ((c = (*lp++ & 0377)) < ' ')
	    printf("\\%o ", c);
	 else	 printf("%c ", c);
	}
	printf("\n");
   }
}

/****************************************************************************
*                                   cclass
* Inputs:
*       char * source: Pattern start...for error message
*	char * src: Class start
* Result: char *
* Effect: 
*	Compile a class (within [])
****************************************************************************/

char * cclass(source, src)
    char   *source;   /* Pattern start -- for error msg.      */
    char   *src;      /* Class start	       */
{
   register char   *s;	      /* Source pointer    */
   register char   *cp;       /* Pattern start	   */
   register int    c;	      /* Current character */
   int		   o;	      /* Temp		   */
   s = src;
   o = CLASS;
   if (*s == '^') {
      ++s;
      o = NCLASS;
   }
   store(o);
   cp = pp;
   store(0);			      /* Byte count	 */
   while ((c = *s++) && c!=']') {
      if (c == '\\') {                /* Store quoted char    */
	 if ((c = *s++) == '\0')      /* Gotta get something  */
	    badpat("Class terminates badly", source, s);
	 else	 store( (fold ? tolower(c) : c) );
      }
      else if (c == '-' &&
	    (pp - cp) > 1 && *s != ']' && *s != '\0') {
	 c = pp[-1];		 /* Range start     */
	 pp[-1] = RANGE;	 /* Range signal    */
	 store(c);		 /* Re-store start  */
	 c = *s++;		 /* Get end char and*/
	 store( (fold ? tolower(c) : c) );	 /* Store it	    */
      }
      else {
	 store( (fold ? tolower(c) : c) );	 /* Store normal char */
      }
   }
   if (c != ']')
      badpat("Unterminated class", source, s);
   if ((c = (pp - cp)) >= 256)
      badpat("Class too large", source, s);
   if (c == 0)
      badpat("Empty class", source, s);
   *cp = c;
   return(s);
}

/****************************************************************************
*                                    store
* Inputs:
*       char op: Pattern operation
* Effect: 
*       Stores the operator in the pattern; if the pointer exceeds the
*	buffer size, issues an error message and aborts
****************************************************************************/

store(op)
{
   if (pp >= &pbuf[PMAX])
      error("Pattern too complex\n");
   *pp++ = op;
}

/****************************************************************************
*                                   badpat
* Inputs:
*       char * message: Message to issue
*	char * source: Start of pattern
*	char * stop: End of pattern
* Effect: 
*       Issues an error message
****************************************************************************/

badpat(message, source, stop)
    char  *message;       /* Error message */
    char  *source;	      /* Pattern start */
    char  *stop;	      /* Pattern end   */
    {
     register int    c;
     fprintf(stderr, "-GREP-E-%s, pattern is\"%s\"\n", message, source);
     fprintf(stderr, "-GREP-E-Stopped at byte %d, '%c'\n",
	 stop-source, stop[-1]);
     error("?GREP-E-Bad pattern\n");
    }

/****************************************************************************
*                                    grep
* Inputs:
*       FILE * fp: File to process
*	char * fn: File name (for -fn option)
* Effect: 
*       Scan the file for the pattern in pbuf[]
****************************************************************************/

grep(fp, fn)
    FILE   *fp;
    char   *fn;
    {
     register int lno, count, m;
     lno = 0;
     count = 0;
/*
     printf("grep(0x%x,0x%x",fp,fn);
     if(fn) printf(" (%s)",fn);
     printf(")\n");
*/
     while (fgets(lbuf, LMAX, fp)) {
      ++lno;
      m = match();
      if ((m && !vflag) || (!m && vflag)) 
        {
	 ++count;
	 if (!cflag) 
	    { /* print line */
	     
	     if (fflag) 
		{
		 file(fn);
		}
	     if (nflag)
		printf("[%d] ", lno);
	     printf("%s", lbuf);
	    } /* print line */
	}
				   }
   if (cflag) {
      if (fflag && fn)
	 file(fn);
      printf("%d\n", count);
   }
}

/****************************************************************************
*                                    match
* Result: boolean
*       true if current line in lbuf matches pattern in pbuf
*	false if it does not
****************************************************************************/

match()
/*
 * Match the current line (in lbuf[]), return 1 if it does.
 */
{
   register char   *l;	      /* Line pointer	    */
   char *pmatch();
   for (l = lbuf; *l; l++) {
      if (pmatch(l, pbuf))
	 return(1);
   }
   return(0);
}

/****************************************************************************
*                                   pmatch
* Inputs:
*       char * line: (partial) line to match
*	char * pattern: (partial) pattern to match
* Result: char *
*       2
****************************************************************************/

char * pmatch(line, pattern)
char		   *line;     /* (partial) line to match      */
char		   *pattern;  /* (partial) pattern to match   */
{
   register char   *l;	      /* Current line pointer	      */
   register char   *p;	      /* Current pattern pointer      */
   register char   c;	      /* Current character	      */
   char 	   *e;	      /* End for STAR and PLUS match  */
   int		   op;	      /* Pattern operation	      */
   int		   n;	      /* Class counter		      */
   char 	   *are;      /* Start of STAR match	      */
   l = line;
   if (debug > 1)
      printf("pmatch(\"%s\")\n", line);
   p = pattern;
   while ((op = *p++) != ENDPAT) {
      if (debug > 1)
	 printf("byte[%d] = 0%o, '%c', op = 0%o\n",
	       l-line, *l, *l, op);
      switch(op) {
      case CHAR:
	 if ( (fold ? tolower(*l) : *l) != *p++)
	    return(0);
	 l++;
	 break;
      case BOL:
	 if (l != lbuf)
	    return(0);
	 break;
      case EOL:
	 if (*l != '\0')
	    return(0);
	 break;
      case ANY:
	 if (*l++ == '\0')
	    return(0);
	 break;
      case DIGIT:
	 if ((c = *l++) < '0' || (c > '9'))
	    return(0);
	 break;
      case ALPHA:
	 c = (fold ? tolower( *l ) : *l) ;
	 l++;
 /* these chars added by lbafrin for case differentiation:

            |                                 |
            |         and         +-----------+----------+
            |                     |                      |
            V                     V                      V
                                                                       */
	 if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z'))
	    return(0);
	 break;
      case NALPHA:
	 c = (fold ? tolower( *l ) : *l) ;
	 l++;
 /* these chars added by lbafrin for case differentiation:

            |                                   |
            |          and          +-----------+------------+
            |                       |                        |
            V                       V                        V
                                                                       */
	 if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
	    break;
	 else if (c < '0' || c > '9')
	    return(0);
	 break;
      case PUNCT:
	 c = *l++;
	 if (c == 0 || c > ' ')
	    return(0);
	 break;
      case CLASS:
      case NCLASS:
	 c = (fold ? tolower( *l ) : *l) ;
	 l++;
	 n = *p++ & 0377;
	 do {
	    if (*p == RANGE) {
	       p += 3;
	       n -= 2;
	       if (c >= p[-2] && c <= p[-1])
		  break;
	    }
	    else if (c == *p++)
	       break;
	 } while (--n > 1);
	 if ((op == CLASS) == (n <= 1))
	    return(0);
	 if (op == CLASS)
	    p += n - 2;
	 break;
      case MINUS:
	 e = pmatch(l, p);	 /* Look for a match	*/
	 while (*p++ != ENDPAT); /* Skip over pattern	*/
	 if (e) 		 /* Got a match?	*/
	    l = e;		 /* Yes, update string	*/
	 break; 		 /* Always succeeds	*/
      case PLUS:		 /* One or more ...	*/
	 if ((l = pmatch(l, p)) == 0)
	    return(0);		 /* Gotta have a match	*/
      case STAR:		 /* Zero or more ...	*/
	 are = l;		 /* Remember line start */
	 while (*l && (e = pmatch(l, p)))
	    l = e;		 /* Get longest match	*/
	 while (*p++ != ENDPAT); /* Skip over pattern	*/
	 while (l >= are) {	 /* Try to match rest	*/
	    if (e = pmatch(l, p))
	       return(e);
	    --l;		 /* Nope, try earlier	*/
	 }
	 return(0);		 /* Nothing else worked */
      default:
	 printf("Bad op code %d\n", op);
	 error("Cannot happen -- match\n");
      }
   }
   return(l);
}

/****************************************************************************
*                                    error
* Inputs:
*       char * s: Error message to issue
* Effect: 
*       Issues error message to stderr, then exits program.  Never returns.
****************************************************************************/

error(s)
char *s;
{
   fprintf(stderr, "%s", s);
   exit(1);
}
