/*************************************************************************\
** GREP - Limited implementation of the unix grep utility.               **
** Uses CMDLIN.SYS for wildcard specification. [msm] 1/2/91              **
**                                                                       **
** Copyright (c) 1991 - Morton & Pitalo, Inc.                            **
** All Rights Reserved.                                                  **
**                                                                       **
** This program was written to try a simplistic implementation of        **
** the unix grep utility and to play with CMDLIN.SYS and                 **
** redirection on the command line. There are some obvious               **
** limitations due to the fact that it is running under AMOS and         **
** not UNIX but that's life. The search routine currently uses a         **
** variation of the Knuth, Morris, Pratt algorithm to help speed         **
** up search times. Although this speeds things up a bit there are       **
** variations that would vastly increase search speed. Among the         **
** most obvious would be to read the file to be searched into a          **
** "large" memory buffer and then do the search within the buffer.       **
** This would take advantage of the smart controllers and DMA            **
** control to load the file faster rather than grabing one block         **
** at a time. Allocate as large a buffer as is available from the        **
** heap and then do an fread() to load the buffer as needed. In          **
** addition there are faster searching algorithms. For reference         **
** of this algorithm as well as some faster ones check out               **
** Sedgewicks book "Algorithms" which is an excellent source for         **
** good algorithms to speed up life in general. As for the code it's a   **
** mess, but thats due alot to the cumbersome things you have to go      **
** through for the CMDLIN.SYS interface.                                 **
**                                                                       **
** Written By: Michael Mc Murdie, Jan. 4, 1990                           **
** Any questions send e-mail to weng/am                                  **
**                                                                       **
** Also Note: A lot of the cmdlin interface stuff was lifted from the    **
** Alpha-Micro Technical Journal: Vol. 12, No. 3, So if it looks funny   **
** it's their fault.                                                     **
**                                                                       **
**                                                                       **
\*************************************************************************/

version "1.0(100),-1,0,PH$REE!PH$REU";

#include <stdio.h>
#include <cmdlin.h>
#include <string.h>
#include <cdefs.c>
#include <ctype.h>
#include "defs.h"               /* my standard definitions */

#define NSW     2               /* number of switches */

/**************** GLOBAL VARIABLES **********************/
       char    line_buf[256];  /* input line buffer */
       char    pattern[81];    /* pattern to search for */
       int     next[81];       /* next array for searching algorithm */

main(argc,argv)
       int     argc;           /* argument count */
       char    **argv;         /* argument vector */
{
       ddb     inddb;          /* input ddb */

       /*********** switch data ************/
       short   swtyp[NSW];     /* -- types */
       int     swoff[NSW];     /* -- off values */
       int     swon[NSW];      /* -- on values */
       char    snames[50];     /* concatenated names of cmdef */
       swdef   sw_tbl;         /* switch table */

/**************************************************************\
** NOTE: in order for the switches to work, the sw_tbl MUST   **
** follow the switch types, off settings, on settings and the **
** name string.                                               **
\**************************************************************/

       char    *swtxt[NSW];    /* -- names */
       int     swval[NSW];     /* -- values */
       char    *defcmd;        /* default specification */
       long    flags;          /* flags passed to/from CMDLIN.SYS */

       /************* general variables ****************/
       char    file_name[30];  /* current file being searched */
       char    *ch_pos;        /* character pointer for ofile */
       short   new_file;       /* true if nothing output from this file */
       FILE    *fp_in;         /* input file pointer */
       int     ptrn_len;       /* pattern length */
       int     chk_pos;        /* position returned */
       int     line_cnt;       /* line count value */

       /* initialize the switch types */
       swtyp[0] = 0;           /* switch type */
       swoff[0] = 0;           /* Off values */
       swon[0] = 1;            /* Switch is simply On/Off */
       swtxt[0] = "LINE";      /* include line numbering */

       swtyp[1] = 0;           /* switch type */
       swoff[1] = 0;           /* Off values */
       swon[1] = 1;            /* Switch is simply On/Off */
       swtxt[1] = "NAME";      /* only output file name with matches */

       defcmd = "=*.*";        /* default spec. Note "=" sign in front */
                               /* of spec to show it is input */

       /* check command line */
       if (argc != 3){         /* give usage info. */
         BRIGHT;
         typecr("Usage: Grep pattern filespec/switches");
         DIM;
         typecr("\nThe pattern can only be a single word and the");
         typecr("filespec can contain any of the standard CMDLIN");
         typecr("wildcard or @file parameters. In addition, output");
         typecr("can be redirected via the '>' notation after the");
         typecr("filespec/switches. Available switches are as follows:");
         BRIGHT;
         typecr("     /LINE to include line number on matched lines.");
         typecr("     /NAME to only output the filename of those files that");
         typecr("           have matches.");
         typecr("\nExample: grep find_proc() *.c/n > proc.lst");
         DIM;
         typecr("will search all files that have the extension .C for the");
         typecr("text \"find_proc()\". All matching files will be listed in");
         typecr("the file PROC.LST");
         BRIGHT;
         exit(1);
       }

       DIM;
       type("String to search for:");
       BRIGHT;
       typecr(strcpy(pattern,argv[1]));
       DIM;
       type("     Files to search:");
       BRIGHT;
       typecr(cmdlin = argv[2]);       /* reference the filespec and print */

       ptrn_len = strlen(pattern);     /* find the pattern length */
       init_next(pattern,ptrn_len);    /* initialize the next array */

       clear(inddb);                   /* clear input ddb */
       cmloc(&inddb);                  /* locate CMDLIN.SYS */

       strpack(snames,swtxt,NSW);      /* pack names into 1 string */

       /* init switch table */
       sw_tbl.swcount = NSW;           /* 1 switch defined */
       cmdef(&sw_tbl,swtyp,swoff,swon,snames);
       flags = (_op_prt|_op_err);      /* print & abort on error */

       /* initialize CMDLIN.SYS */
       if (cmini(&sw_tbl,swval,defcmd,&flags)) exit(1);

       /* check for output file spec'd */
       if (flags & _in_ofp){
         typecr("Error: Do NOT use an output specification.");
         typecr("       Use the redirection operator '>' instead.");
         exit(1);
       }

       /* get files specified */
       CUROFF;
       while(!cmnxt(&inddb,nil,&flags) && !(flags & _nx_end)){
         if (ctrlc()) break;           /* check for ^C */

/**********************************************\
** NOTE: the cmqry() call has been commented  **
** out because it outputs a CR/LF after every **
** call, whether query was requested or not.  **
** Since this messes up the screen display it **
** is NOT used.                               **
\**********************************************/

/*        if (!cmqry()) continue;       /* check for /Q */
/* skip random files */
         if (inddb.altwrk.strwrk.lsz == 65535) continue;

/* get this file name */
         ch_pos = file_name;
         ofile(&inddb,_ot_mem,&ch_pos);
         *ch_pos = NULL;

         DIM;          /* print the file name on screen */
         type("Searching File:");
         BRIGHT;
         type(file_name);
         CLR_EOL;
         COL_1;
         new_file = TRUE;

         /* open the file for input */
         if ((fp_in = fopen(file_name,"r")) == nil){
           DIM;
           type("Unable to open file:");
           BRIGHT;
           typecr(file_name);
           continue;
         }
         for(line_cnt = 1; TRUE; line_cnt++){
           if (ctrlc()){
             jobidx()->jobsts |= _j_ccc;
             break;
           }
           fgets(line_buf,255,fp_in);
           if (feof(fp_in)) break;
           if ((chk_pos = search(line_buf,ptrn_len)) != -1){
             if (new_file){
               printf("\n%s\n",file_name);
               new_file = FALSE;
               if (swval[1]) break;
             }
             if (swval[0]) printf("%d:",line_cnt);
             printf("%s",line_buf);
           }

         }
         fclose(fp_in);        /* close file */
       }               /* end of while loop */
       crlf();
       CURON;
}                       /* end of main */

/********************************************************************\
** search the string pointed to by a and return the position of the **
** pattern within the string. If the pattern is not found return -1 **
\********************************************************************/
int search(a,len_p)
       char    *a;     /* pointers to strings to check */
       int     len_p;  /* pattern length */
{
       int     i, j, N;

       N = strlen(a);          /* length of string */
       for(i = 0, j = 0; j < len_p && i < N; i++, j++)
         while ((j >= 0) && (toupper(a[i]) != pattern[j])) j = next[j];
       if (j == len_p) return(i - len_p); else return(-1);
}

/* initialize the "next" array for pattern searching */
int init_next(p,len_p)
       char    *p;     /* pointer to pattern */
       int     len_p;  /* length of pattern */
{
       int i, j;

       next[0] = -1;
       for(i = 0, j = -1; i < len_p;
               i++, j++, next[i] = (p[i] == p[j]) ? next[j] : j)
         while((j >= 0) && (p[i] != p[j])) j = next[j];
}