/* -------------------------------------------------------------------
 *	GREP.C:  A generalized regular expression parser
 *
 *	Published in Dr. Dobb's Journal, October 1984, page 50
 *
 *		Copyright (c) 1984 Allen Holub
 *	Copyright (c) 1984 Software Engineering Consultants
 *			P.O. Box 5679
 *		     Berkeley, CA, 94705
 *
 *		     All rights reserved.
 *
 *	This program may be copied for personal, non-commercial use
 *	only, provided that this copyright notice is included in all
 *	copies and that this program is not modified in any way.
 *	Copying for any other use without previously obtaining the
 *	written permission of the author is prohibited.
 *
 *--------------------------------------------------------------------
 */
#include "printf.c"
#include "a:tools.h"
#include "a:tools.c"

/* 
 * GREP
 *
 * Search a file for a pattern.
 *
 * The algorithm used here is essentially the algorithm in Software Tools in
 * Pascal (pp 145f.).  Though the routines have been changed somewhat to put
 * them into good 'C'.  See tools.c for details.
 *
 * This program is a healthy subset of the UNIX program of the same name.
 * The differenced are as follows:
 *
 *	- the -s, -x, and -b options are not supported.
 *	- the meta-characters b()+? are not supported.
 *
 * Usage is:
 *	grep [-vclnhyef] [expression] files ...
 *
 * To compile with the Computer Innovations C86 compiler use:
 *
 *	cc1 grep
 *	cc2 grep
 *	cc3 grep
 *	cc4 grep
 *	cc1 tools
 *	cc2 tools
 *	cc3 tools
 *	cc4 tools
 *	link grep tools,,,c86s2s.lib
 *
 * To compile with Aztec CII use:
 *
 *	cc1 -x4000 grep.c
 *	as grep.asm
 *	cc1 -x4000 tools.c
 *	as tools.asm
 *	ln grep.o tools.o a:libc.lib
 */

#define CPM	1	/* Comment this out if you're compiling
			 * in an MSDOS system.
			 */

#define MAXLINE	128	/* Maximum size of an input line. */

#define MAX_EXPR 64	/* The maximum number of regular expressions
			 * separated by newlines or | allowed.
			 */

/* The following global flags are true if a switch was set in the
 * command line, false otherwise.
 */

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

main(argc, argv) int argc; char **argv;{

    int i, j, linenum, count, numfiles, exprc;
    int line[MAXLINE];
    FILE *stream;

    TOKEN *exprv[MAX_EXPR];

    char *fgets();

    i = 1;

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

    if(*argv[i] == '-'){    /* expand the switches on the command line */
	expand_sw(argv[i++]);
	if(i == argc) abort(pr_usage(1));
    }

    /* Get the pattern string */

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

    numfiles = argc - i;	/* Get number of files left to process
				 * on the command line.
				 */

    fprintf(stderr, "(c) Copyright 1984, Software Engineering Consultants\n");

    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)){
#ifdef CPM
	    if(!fflag || yflag) stoupper(line);
#else	    if(yflag) stoupper(line);
#endif
	    for(j = exprc; --j >= 0; ){
		if(matchs(line, exprv[j], 0)){
		    count++;
		    pr_match(linenum, line, argv[i], 1, numfiles);
		}
		else pr_match(linenum, line, argv[i], 0, numfiles);
		linenum++;
		cntrl_c();
	    }
	    if(lflag && count) break;
	}
	pr_count(numfiles, argv[i], count);
	fclose(stream);
    }while(++i < argc);

    abort();
}

/* ----------------------------------------------------------------------- */

pr_count(fcount, fname, count) int fcount, count; char *fname;{

	/* Process the -c flag by printing out a count and, if more than
	 * one file was listed on the command line, the file name too.
	 */

	if(!cflag) return;
	if(fcount > 1) printf("%-12s: ",fname);
	printf("%d\n",count);
}

/* ----------------------------------------------------------------------- */

pr_match(linenum, line, fname, match, numfiles)
	int linenum, match; char *line, *fname;{

	/* If a match is found print the correct thing
	 * as specified by the command line switches.
	 */

    char buf[80];

    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) printf("%s", line);

    }
}

/* ----------------------------------------------------------------------- */

pr_usage(num) int num;{

#ifdef DEBUG
	fprintf(stderr,"%d ",num);
#endif
	fprintf(stderr,"usage: grep [-cefhlnvy] [expression] <files ...>\n");
}

/* ----------------------------------------------------------------------- */

abort(){
	exit();
}

/* ----------------------------------------------------------------------- */

expand_sw(str) char *str;{

	/* Set global flags corresponding to specific
	 * switches if those switches are set.
	 */

	vflag = 0;
	cflag = 0;
	lflag = 0;
	nflag = 0;
	hflag = 0;
	fflag = 0;
	yflag = 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 '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, max) char *lp; TOKEN **expr; int max;{

	int	found;
	TOKEN	*pat;
	char	*op;

	found = 0;

	/* Extract regular expressions separated by 0R_SYMs from lp and
	 * put them into expr.  Extract only up to max expressions.  If
	 * yflag is true, map string to upper case first.
	 */

	if(yflag) stoupper(lp);

	while(op = in_string(OR_SYM, lp)){
		if(found <= max && (pat = makepat(lp, OR_SYM))){
			*expr++ = pat;
			found++;
		}
		lp = ++op;
		if(pat == 0) goto fatal_err;
	}

	if(found <= max && (pat = makepat(lp, OR_SYM))){
		found++;
		*expr = pat;
	}

	if(pat == 0){
fatal_err:	printf("Illegal expression\n");
		exit();
	}
	return(found);
}

/* ----------------------------------------------------------------------- */

get_expr(expr, max, defexpr) TOKEN *expr[]; int max; char **defexpr;{

	FILE	*stream;
	int	count;
	char	line[MAXLINE];

#ifdef DEBUG
	int	i;
#endif

	/* Get regular expressions separated by | or newlines either out of
	 * a file or off the command line depending on whether the -f flag
	 * is set.  The expressions are converted into pattern templates
	 * (see tools.c) and pointers to the templates are put into the array
	 * expr[] (which works similar to argv).
	 *
	 * Return the number of expressions found (which can be used in a
	 * similar fashion to argc).
	 */

	count = 0;

	if(fflag){

		/* Then *defexpr is the file name and expressions should be
		 * taken from that file.
		 */

		if((stream = fopen(*defexpr, "r")) == NULL){
			fprintf(stderr, "Can't open %s\n", *defexpr);
			abort();
		}

		while((max - count) && fgets(line, MAXLINE, stream))
			count += do_or(line, &expr[count], max - count);

		fclose(stream);
	}
	else	/* 	*defexpr is the expression itself. */
		if(count += do_or(*defexpr, &expr[count], max - count))
			*defexpr = " ";

#ifdef	DEBUG
	/* Print out all the regular expressions after they have been
	 * converted into pattern templates (see tools.c).
	 */

	for(i = count; --i >= 0;){
		pr_tok(expr[i]);
		printf("------------------------------------------------\n");
	}
#endif
	return(count);
}

/* ----------------------------------------------------------------------- */

cntrl_c(){

#ifdef	CPM

    /* If any character was hit, and that character is a ^C, then abort. */

	if(bdos(11,0) && ((bdos(1,0) & 0x7f) == 0x03)) abort();

#endif

}

#include "STDLIB.C"
ter is a ^C, then abort. */

	if(bdos(11,0) && ((bdos(1,0) & 0x7f) == 0x03)) abort();

#endi