/*
 * GREP
 * Original version published in Dr. Dobb's Journal, October 1984
 */
#include <stdio.h>
#include <ctype.h>

#ifdef QDOS
/* make program do its own wildcard expansion of arguments */
/* as a good cli is not (yet) available for QDOS */
extern void cmdexpand();
void (*_cmdwildcard)()=cmdexpand;
/* hack, because C68 cannot get argv[0] from the commandline */
/* EJC can do this, by the way */
char _prog_name[]="grep";
extern void consetup_title();
void (*_consetup)() = consetup_title;
long _stackmargin = 1024L;
#endif


#define  BOL      '^'
#define  EOL      '$'
#define  ANY      '.'
#define  LITCHAR  'L'
#define  ESCAPE   '\\'
#define  CCL      '['
#define  CCLEND   ']'
#define  NEGATE   '^'
#define  NCCL     '!'
#define  CLOSURE  '*'
#define  OR_SYM   '|'
#define  CLS_SIZE 512

typedef  struct token
{
   char  tok;
   char  lchar;
   char  *string;
   struct token *next;
}
TOKEN;

#define  TOKSIZE  sizeof(TOKEN)

#define  MAXSTR   512

char  *amatch(lin,pat,boln)
char  *lin,*boln;
TOKEN *pat;
{
    char  *bocl,*rval,*strstart;

   if( pat==0 )
      return(0);

   strstart=lin;

   while( pat )
   {
      if( pat->tok == CLOSURE && pat->next )
      {
         pat = pat->next;
         bocl = lin;
         while( *lin && omatch(&lin,pat) )
            ;
         if( pat = pat->next )
         {
            while( bocl <= lin )
               if( rval = amatch(lin,pat,boln) )
                  return(rval);
               else
                  --lin;
            return(0);
         }
      }
      else if( omatch(&lin,pat,boln) )
         pat = pat->next;
      else
         return(0);
   }
   --lin;

   if( lin>strstart )
      return( lin );
   else
      return( strstart );
}

int   dodash(delim,src,dest,maxccl)
int   delim,maxccl;
char  **src,*dest;
{
    char *dstart;
    int  k,at_begin;
    char *sptr;

   dstart = dest;
   sptr = *src;
   at_begin = 1;

   while( *sptr && (*sptr != delim) && (dstart-dest < maxccl) )
   {
      if( *sptr == ESCAPE )
      {
         *dest++ = esc(&sptr);
         sptr++;
      }
      else if( *sptr != '-' )
         *dest++ = *sptr++;
      else if( at_begin || *(sptr+1) == delim )
         *dest++ = '-';
      else if( *(sptr-1) <= *(sptr+1) )
      {
         sptr++;
         for( k=*(sptr-2); ++k <= *sptr; )
            *dest++ = k;
         sptr++;
      }
      else
         return(0);
      at_begin=0;
   }
   *dest++='\000';
   *src=sptr;

   return(dest-dstart);
}

int   esc(s)
char  **s;
{
   int   rval;

   if( **s != ESCAPE )
      rval = **s;
   else
   {
      (*s)++;
      switch( toupper(**s) )
      {
         case '\000':   rval = ESCAPE; break;
         case  'S':     rval = ' ';    break;
         case  'N':     rval = '\n';   break;
         case  'T':     rval = '\t';   break;
         case  'B':     rval = '\b';   break;
         case  'R':     rval = '\r';   break;
         default:       rval = **s;    break;
      }
   }
   return(rval);
}

char  *in_string(delim,str)
 int   delim;
 char  *str;
{
   while( *str && *str != delim )
      str++;
   return( *str ? str : 0 );
}

TOKEN *makepat(arg,delim)
char *arg;
int delim;
{
   TOKEN *head,*tail,*ntok;
   char  buf[CLS_SIZE];
   int error;

   if( *arg=='\0' || *arg==delim || *arg=='\n' || *arg==CLOSURE)
      return(0);

   error=0;
   head=0;
   tail=0;

   while(*arg && *arg != delim && *arg != '\n' && !error )
   {
      ntok=(TOKEN *)malloc(TOKSIZE);
      ntok->string=&(ntok->lchar);
      ntok->lchar='\000';
      ntok->next=0;

      switch(*arg)
      {
         case ANY:   ntok->tok=ANY; break;

         case BOL:   if(head==0)
                        ntok->tok=BOL;
                     else
                        error=1;
                     break;

         case EOL:   if( *(arg+1) == delim || *(arg+1) == '\000'
                                           || *(arg+1) == '\n'  )
                        ntok->tok=EOL;
                     else
                        error=1;
                     break;

         case CCL:   if(*(arg+1)==NEGATE)
                     {
                        ntok->tok=NCCL;
                        arg+=2;
                     }
                     else
                     {
                        ntok->tok=CCL;
                        arg++;
                     }
                     error=dodash(CCLEND,&arg,buf,CLS_SIZE);
                     if( error )
                     {
                        ntok->string=(char *)malloc(error);
                        strcpy(ntok->string,buf);
                        error=0;
                     }
                     break;

         case CLOSURE:  if(head)
                        {
                           switch(tail->tok)
                           {
                              case BOL:
                              case EOL:
                              case CLOSURE: return(0);

                              default: ntok->tok=CLOSURE;
                           }
                        }
                        break;

         default:       ntok->tok=LITCHAR;
                        ntok->lchar=esc(&arg);
      }
      if(error || ntok ==0)
      {
         unmakepat(head);
         return(0);
      }
      else if(head==0)
      {
         ntok->next=0;
         head=tail=ntok;
      }
      else if(ntok->tok!=CLOSURE)
      {
         tail->next=ntok;
         ntok->next=tail;
         tail=ntok;
      }
      else if(head != tail)
      {
         (tail->next)->next=ntok;
         ntok->next=tail;
      }
      else
      {
         ntok->next=head;
         tail->next=ntok;
         head=ntok;
      }
      arg++;
   }

   tail->next=0;
   return(head);

}

TOKEN *getpat(arg)
char  *arg;
{
   return( makepat(arg,'\000') );
}

char *matchs(line,pat,endp)
char *line;
TOKEN *pat;
char **endp;
{
   char *rval,*bptr;
   bptr=line;
   while(*line)
      if((rval=amatch(line,pat,bptr))==0)
         line++;
      else
      {
         *endp=rval;
         rval=line;
         break;
      }

   return(rval);

}


char *stoupper(str)
char *str;
{
   char *rval;

   rval=str;
   while(*str)
   {
      *str=toupper( (*str)&0xff );
      str++;
   }

   return(rval);
}

int omatch(linp,pat,boln)
char **linp,*boln;
TOKEN *pat;
{

   int   advance;

   advance = -1;

   if(**linp)
   {
      switch(pat->tok)
      {
         case LITCHAR:  if(**linp==pat->lchar)
                           advance=1; break;
         case BOL:      if( *linp == boln )
                           advance=0; break;
         case ANY:      if( **linp!='\n')
                           advance=1;break;
         case EOL:      if( **linp =='\n')
                           advance=0;break;
         case CCL:      if( in_string(**linp,pat->string))
                           advance=1;break;
         case NCCL:     if(!in_string(**linp,pat->string))
                           advance=1;break;
         default: printf("omatch: can't happen\n");
      }
   }
   if(advance>=0)
      *linp+=advance;

   return( ++ advance );

}

unmakepat(head)
TOKEN *head;
{
   TOKEN *old_head;

   while(head)
      switch(head->tok)
      {
         case CCL: case NCCL: free(head->string);
         default: old_head=head;
                  head=head->next;
                  free(old_head);
                  break;
      }
}


#define MAXLINE 512
#define MAX_EXPR 64

int vflag,yflag,cflag,lflag,nflag,hflag,fflag,pflag;


main(argc,argv)
int argc;
char **argv;
{
   int   i,j,linenum,count;
   char  line[MAXLINE+1];
   int   numfiles;
   FILE  *stream;
   int   exprc;
   TOKEN *exprv[MAX_EXPR];
   void  pr_count(),pr_match();
   char  *begp,*endp;

   i=1;

   if( argc < 2 )
      abort( pr_usage(1) );

   if( *argv[i] == '-' )
   {
      expand_sw( argv[i++] );
      if( i == argc )
         abort( pr_usage(1) );
   }

   if( (exprc = get_expr(exprv,MAX_EXPR,&argv[i++])) == 0 )
      abort( pr_usage(2) );

   numfiles = argc - i;

   do
   {
      if(numfiles)
      {
         stream=fopen(argv[i],"r");
         if(stream==NULL)
         {
            fprintf(stderr,"Can't open %s\n",argv[i]);
            continue;
         }
      }
      else
         stream=stdin;

      count=0;
      linenum=1;

      while( fgets(line,MAXLINE,stream) )
      {
         if( yflag )
            stoupper(line);

         for( j=exprc;--j>=0; )
         {
            if( (begp=matchs(line,exprv[j],&endp)) )
            {
               count++;
               pr_match(linenum,line,argv[i],1,numfiles,begp,endp);
            }
            else
            {
               pr_match(linenum,line,argv[i],0,numfiles,line,line+(strlen(line)-1));
            }
         }
         linenum++;
         if( lflag && count )
            break;
      }
      pr_count(numfiles,argv[i],count);
      fclose(stream);
   }
   while( ++i < argc );

   abort();
}

void pr_count(fcount,fname,count)
int fcount,count;
char *fname;
{
   if(!cflag)
      return;
   if(fcount>1)
      printf("%s: ",fname);
   printf("%d\n",count);
}

void pr_match(linenum,line,fname,match,numfiles,begp,endp)
int   linenum,match,numfiles;
char  *line,*fname;
char  *begp,*endp;
{
   char  *cp;

   if(cflag)
      return;
   if( (vflag&&!match) || (!vflag&&match) )
   {
      if(!hflag&&((numfiles>1)||lflag))
         printf("%s%s",fname,lflag ? "\n" : ":");
      if(nflag)
         printf("%03d:",linenum);
      if(!lflag)
      {
         if( pflag )
            for( cp=begp; cp<=endp; ++cp )
               putchar(*cp);
         else
            printf("%s",line);
      }
   }
}

pr_usage(num)
int num;
{
   fprintf(stderr,"usage: grep [-cefhlnvyp] [expression] <files ...>\n");
}

abort()
{
   exit(0);
}

expand_sw(str)
char *str;
{
   vflag=0;
   cflag=0;
   lflag=0;
   nflag=0;
   hflag=0;
   fflag=0;
   yflag=0;
   pflag=0;

   while( *str)
   {
      switch( toupper(*str))
      {
         case  '-':
         case  'E':  break;

         case 'C' :  cflag = 1; break;
         case 'F' :  fflag = 1; break;
         case 'H' :  hflag = 1; break;
         case 'P' :  pflag = 1; break;
         case 'L' :  lflag = 1; break;
         case 'N' :  nflag = 1; break;
         case 'V' :  vflag = 1; break;
         case 'Y' :  yflag = 1; break;
         default  :  pr_usage(3);
                     abort();
                     break;
      }
      str++;
   }
}

int do_or(lp,expr,maxi)
char *lp;
TOKEN **expr;
int maxi;
{
   int   found;
   TOKEN *pat;
   char  *op;

   found=0;

   if(yflag)
      stoupper(lp);

   while( op=in_string(OR_SYM,lp) )
   {
      if( found <= maxi && (pat=makepat(lp,OR_SYM)))
      {
         *expr++=pat;
         found++;
      }
      lp=++op;
      if(pat==0)
         goto fatal_err;
   }
   if(found<=maxi&&(pat=makepat(lp,OR_SYM)))
   {
      found++;
      *expr=pat;
   }
   if(pat==0)
   {
fatal_err:
      printf("Illegal expression\n");
      exit(0);
   }

   return(found);

}

get_expr(expr,maxi,defexpr)
TOKEN **expr;
int maxi;
char **defexpr;
{
   FILE *stream;
   int count;
   char line[MAXLINE+1];

   count=0;

   if(fflag)
   {
      if((stream=fopen(*defexpr,"r"))==NULL)
      {
         fprintf(stderr,"Can't open %s\n",*defexpr);
         abort();
      }
      while((maxi-count)&&fgets(line,MAXLINE,stream))
         count+=do_or(line,&expr[count],maxi-count);
      fclose(stream);
   }
   else
      if(count+=do_or(*defexpr,&expr[count],maxi-count))
         *defexpr=(char *)" ";

   return( count );
}
