3BSD/usr/src/cmd/more.c

Compare this file to the similar file:
Show the results in this format:

#include <whoami.h>
#include <stdio.h>
#include <signal.h>
#include <sgtty.h>
#include <setjmp.h>
#include <sys/types.h>
#include <sys/dir.h>
#include <sys/stat.h>

#ifdef CORY
#define MBIT RAW
#else
#include <ctype.h>
#define MBIT CBREAK
#endif

#define TBUFSIZ	1024
#define LINSIZ	256
#define ctrl(letter)	('letter' & 077)
#define RUBOUT	'\177'
#define ESC	'\033'
#define QUIT	'\034'

struct sgttyb 	otty;
int		fnum, no_intty, no_tty, slow_tty;
int		dum_opt, dlines, onquit(), end_it();
int		stop_opt = 1;
int		promptlen;
int		startup = 1;
int		firstf = 1;
int		notell = 1;
int		inwait, pause, errors;
int		within;	/* true if we are within a file,
			false if we are between files */
int		hard, dumb, noscroll, hardtabs;
char		**fnames;
int		nfiles;
char		*shell;
char		ch;
jmp_buf		restore;
char		obuf[BUFSIZ];	/* stdout buffer */
char		Line[LINSIZ];
int		Lpp = 24;	/* lines per page */
char		*Clear;		/* clear screen */
char		*eraseln;	/* erase line */
char		*Senter, *Sexit;/* enter and exit standout mode */
char		*tgetstr();
int		Mcol = 80;	/* number of columns */
int		Wrap = 1;	/* set if automargins */
extern char	PC;		/* pad character */
extern short	ospeed;


main(argc, argv)
int argc;
char *argv[];
{
    register FILE	*f;
    register char	*s;
    register char	*p;
    register char	ch;
    register int	left;
    int			prnames = 0; 
    int			initopt = 0;
    int			srchopt = 0;
    int			initline;
    char		buf[TBUFSIZ];
    char		clearbuf[100];
    char		initbuf[80];
    char		*clearptr;
    char		*getenv();
    FILE		*checkf();

    nfiles = argc;
    fnames = argv;
    /* Put terminal setup stuff in separate procedure ?? (From here...) */
    setbuf(stdout, obuf);
    if (!(no_tty = gtty(1, &otty))) {
	if (tgetent(buf, getenv("TERM")) <= 0) {
	    dumb++;
	}
	else {
	    if (((Lpp = tgetnum("li")) < 0) || tgetflag("hc")) {
		hard++;	/* Hard copy terminal */
		Lpp = 24;
	    }
	    if (!hard && tgetflag("ns"))
		noscroll++;
	    if ((Mcol = tgetnum("co")) < 0)
		Mcol = 80;
	    Wrap = tgetflag("am");
	    clearptr = clearbuf;
	    eraseln = tgetstr("ce",&clearptr);
	    Clear = tgetstr("cl", &clearptr);
	    Senter = tgetstr("so", &clearptr);
	    Sexit = tgetstr("se", &clearptr);
	    PC = *tgetstr("pc", &clearptr);
	}
	if ((shell = getenv("SHELL")) == NULL)
	    shell = "/bin/sh";
    }
    no_intty = gtty(0, &otty);
    gtty(2, &otty);
    ospeed = otty.sg_ospeed;
    slow_tty = ospeed < B1200;
    hardtabs =  !(otty.sg_flags & XTABS);
    if (!no_tty) {
	otty.sg_flags &= ~ECHO;
	if (MBIT == CBREAK || !slow_tty)
	    otty.sg_flags |= MBIT;
    }
    /* ... until here or so */
    while (--nfiles > 0) {
	if ((ch = (*++fnames)[0]) == '-') {
	    for (s = fnames[0] + 1, dlines = 0; *s != '\0'; s++)
		if (isdigit(*s))
		    dlines = dlines*10 + *s - '0';
		else if (*s == 'd')
		    dum_opt = 1;
		else if (*s == 'l')
		    stop_opt = 0;
	}
	else if (ch == '+') {
	    s = *fnames;
	    if (*++s == '/') {
		srchopt++;
		for (++s, p = initbuf; p < initbuf + 79 && *s != '\0';)
		    *p++ = *s++;
		*p = '\0';
	    }
	    else {
		initopt++;
		for (initline = 0; *s != '\0'; s++)
		    if (isdigit (*s))
			initline = initline*10 + *s -'0';
		--initline;
	    }
	}
	else break;
    }
    if (dlines == 0)
	dlines = Lpp - 2;
    left = dlines;
    if (nfiles > 1)
	prnames++;
    if (!no_intty && nfiles == 0) {
	fputs("Usage: ",stderr);
	fputs(argv[0],stderr);
	fputs(" [-dn] name1 name2 ...\n",stderr);
	exit(1);
    }
    else
	f = stdin;
    if (!no_tty) {
	signal(SIGQUIT, onquit);
	signal(SIGINT, end_it);
	stty (2, &otty);
    }
    if (no_intty) {
	if (no_tty)
	    copy_file (stdin);
	else {
	    if (srchopt)
		search (initbuf, stdin, 1);
	    else if (initopt)
		skiplns (initline, stdin);
	    screen (stdin, left);
	}
	end_it ();
    }

    while (fnum < nfiles) {
	if ((f = checkf (fnames[fnum])) != NULL) {
	    if (firstf) setjmp (restore);
	    if (firstf) {
		firstf = 0;
		if (srchopt)
		    search (initbuf, f, 1);
		else if (initopt)
		    skiplns (initline, f);
	    }
	    else if (fnum < nfiles && !no_tty) {
		setjmp (restore);
		left = command (fnames[fnum], f);
	    }
	    if (left != 0) {
		if (prnames) {
		    pr("::::::::::::::");
		    if (promptlen > 14)
			erase (14);
		    putchar ('\n');
		    pr(fnames[fnum]);
		    pr("\n::::::::::::::\n");
		    if (left > Lpp - 4)
			left = Lpp - 4;
		}
		if (no_tty)
		    copy_file (f);
		else {
		    within++;
		    screen(f, left);
		    within = 0;
		}
	    }
	    setjmp (restore);
	    fflush(stdout);
	    fclose(f);
	}
	fnum++;
	firstf = 0;
    }
    otty.sg_flags |= ECHO;
    otty.sg_flags &= ~MBIT;
    stty(2, &otty);
    exit(0);
}

/*
** Check whether the file named by fs is an ASCII file which the user may
** access.  If it is, return the opened file. Otherwise return NULL.
*/

FILE *
checkf (fs)
register char *fs;
{
#ifdef CORY
    int space[3];	/* Why doesn't libretro have a V7 stat? */
#endif
    struct stat stbuf;
    register FILE *f;
    char c;

    if (stat (fs, &stbuf) == -1) {
	fflush(stdout);
	perror(fs);
	return (NULL);
    }
    if (stbuf.st_mode & S_IFDIR) {
	pr("\n*** ");
	pr(fs);
	pr(": directory ***\n\n");
	return (NULL);
    }
    if ((f=fopen(fs, "r")) == NULL) {
	fflush(stdout);
	perror(fs);
	return (NULL);
    }
    c = getc(f);

    /* Try to see whether it is an ASCII file */

    switch ((c | *f->_ptr << 8) & 0177777) {
    case 0405:
    case 0407:
    case 0410:
    case 0411:
    case 0177545:
	pr("\n******** ");
	pr(fs);
	pr(": Not a text file ********\n\n");
	fclose (f);
	return (NULL);
    default:
	break;
    }
    if (c == '\f') {
	c = 0;
	doclear ();
    }
    ungetc (c, f);
    return (f);
}

/*
** A real function, for the tputs routine in termlib
*/

putch (ch)
register char ch;
{
    putchar (ch);
}

/*
** Print out the contents of the file f, one screenful at a time.
*/

#define STOP -10

screen (f, num_lines)
register FILE *f;
register int num_lines;
{
    register int c;
    int nchars;

    for (;;) {
	while (num_lines > 0 && !pause) {
	    if ((nchars = getline (f)) == EOF)
		return;
	    if (Senter && *Senter == ' ' && promptlen > 0)
		erase (0);
	    pr (Line);
	    if (nchars < promptlen)
		erase (nchars);	/* erase () sets promptlen to 0 */
	    else promptlen = 0;
	    if (nchars < Mcol)
		putchar('\n');
	    if (nchars == STOP)
		break;
	    num_lines--;
	}
	fflush(stdout);
	if ((c = getc(f)) == EOF) {
	    if (noscroll)
		doclear();
	    else
		erase (0);
	    return;
	}
	ungetc (c, f);
	setjmp (restore);
	pause = 0; startup = 0;
	if ((num_lines = command (NULL, f)) == 0)
	    return;
    }
}

/*
** Come here if a quit signal is received
*/

onquit()
{
    signal(SIGQUIT, SIG_IGN);
    if (!inwait) {
	putchar ('\n');
	if (!startup) {
	    signal(SIGQUIT, onquit);
	    longjmp (restore, 1);
	}
	else
	    pause++;
    }
    else if (!dum_opt && notell) {
	write (2, "[Use q or Q to quit]", 20);
	promptlen += 20;
	notell = 0;
    }
    signal(SIGQUIT, onquit);
}

/*
** Clean up terminal state and exit. Also come here if interrupt signal received
*/

end_it ()
{

    otty.sg_flags &= ~MBIT;
    otty.sg_flags |= ECHO;
    stty(2, &otty);
    if (promptlen > 0)
	kill_line ();
    else
	putchar ('\n');
    exit(0);
}

copy_file(f)
register FILE *f;
{
    register int c;

    while ((c = getc(f)) != EOF)
	putchar(c);
}


printd (n)
register int n;
{
    register int a;

    if (a = n/10)
	printd(a);
    putchar(n % 10 + '0');
}

static char bell = ctrl(G);

strlen (s)
char *s;
{
    register char *p;

    p = s;
    while (*p++)
	;
    return (p - s - 1);
}

prompt (filename)
char *filename;
{
    if (promptlen > 0)
	kill_line ();
    if (!hard) {
	promptlen = 8;
	if (Senter && Sexit)
	    tputs (Senter, 1, putch);
	pr("--More--");
	if (filename != NULL) {
	    pr("(Next file: ");
	    pr(filename);
	    putchar(')');
	    promptlen += 13 + strlen(filename);
	}
	if (dum_opt) {
	    pr("[Hit space to continue, Rubout to abort]");
	    promptlen += 40;
	}
	if (Senter && Sexit)
	    tputs (Sexit, 1, putch);
	fflush(stdout);
    }
    else
	write (2, &bell, 1);
    inwait++;
}

/*
** Get a logical line
*/

getline(f)
register FILE *f;
{
    register char	c;
    register char	*p;
    register int	column;
    register int	i;
    static int		colflg;

    p = Line;
    i = column = 0;
    c = getc (f);
    if (colflg && c == '\n') c = getc (f);
    for (i = 1; i < LINSIZ; i++) {
	if (c == EOF) {
	    if (p > Line) {
		*p = '\0';
		return (column);
	    }
	    return (EOF);
	}
	if (c == '\n')
	    break;
	*p++ = c;
	if (c == '\t')
	    if (hardtabs && column < promptlen && !hard) {
		if (eraseln && !dumb) {
		    tputs (eraseln, 1, putch);
		    promptlen = 0;
		}
		else {
		    for (--p; column & 7; column++)
			*p++ = ' ';
		    if (column >= promptlen) promptlen = 0;
		}
	    }
	    else
		column = 1 + (column | 7);
	else if (c == '\b')
	    column--;
	else if (c == '\r')
	    column = 0;
	else if (c == '\f' && stop_opt) {
		p[-1] = '^';
		*p++ = 'L';
		break;
	}
	else if (c == EOF)
	    return (column);
	else if (c >= ' ')
	    column++;
	if (column >= Mcol) break;
	c = getc (f);
    }
    if (Mcol > 0 && column >= Mcol) {
	if (!Wrap) {
	    *p++ = '\n';
	    i++;
	}
    }
    colflg = (column == Mcol) || c == '\f';
    *p = 0;
    if (c == '\f' && stop_opt)
	return (STOP);
    return (column);
}

/*
** Erase the rest of the prompt, assuming we are starting column col.
*/

erase (col)
register int col;
{

    if (hard || promptlen == 0)
	return;
    if (col == 0)
	putchar ('\r');
    if (!dumb && eraseln)
	tputs (eraseln, 1, putch);
    else
	for (col = promptlen - col; col > 0; col--)
	    putchar (' ');
    promptlen = 0;
}

/*
** Erase the current line entirely
*/

kill_line ()
{
    erase (0);
    if (!eraseln || dumb) putchar ('\r');
}

/*
**  Print string
*/

pr(s1)
char	*s1;
{
    register char	*s;
    register char	c;

    for (s = s1; c = *s++; )
	putchar(c);
}

/*
**  Clear the screen
*/

doclear()
{
    if (Clear && Lpp > 0)
	tputs(Clear, 1, putch);
}


/*
** Read a command and do it. A command consists of an optional integer
** argument followed by the command character.  Return the number of lines
** to display in the next screenful.  If there is nothing more to display
** in the current file, zero is returned.
*/

command (filename, f)
char *filename;
register FILE *f;
{
    register int nlines;
    register int retval;
    register char c;
    int id, done;
    char comchar, cmdbuf[80], *p;

#define ret(val) retval=val;done++;break

    done = 0;
    if (!errors)
	prompt (filename);
    else
	errors = 0;
    if (MBIT == RAW && slow_tty) {
	otty.sg_flags |= MBIT;
	stty(2, &otty);
    }
    for (;;) {
	nlines = number (&comchar);
	switch (comchar) {
	case ' ':
	case 'z':
	    if (nlines == 0) nlines = dlines;
	    else if (comchar == 'z') dlines = nlines;
	    ret (nlines);
	case 'd':
	case ctrl(D):
	    ret (11);
	case RUBOUT:
	case 'q':
	case 'Q':
	    end_it ();
	case 's':
	case 'f':
	    if (nlines == 0) nlines++;
	    if (comchar == 'f')
		nlines *= dlines;
	    putchar ('\r');
	    erase (0);
	    pr("\n...skipping ");
	    printd(nlines);
	    pr(" line");
	    if (nlines > 1)
		pr("s\n\n");
	    else
		pr("\n\n");
	    while (nlines > 0) {
		while ((c = getc (f)) != '\n')
		    if (c == EOF) {
			retval = 0;
			done++;
			goto endsw;
		    }
		    nlines--;
	    }
	    ret (dlines);
	    break;
	case '\n':
	    ret (1);
	case 'n':
	    if (nlines == 0)
		nlines++;
	    putchar ('\r');
	    erase (0);
	    skipf (nlines);
	    ret (0);
	case 'p':
	    if (no_intty) {
		write (2, &bell, 1);
		break;
	    }
	    putchar ('\r');
	    erase (0);
	    if (nlines == 0)
		nlines++;
	    skipf (-nlines);
	    ret (0);
	case '/':
	    kill_line ();
	    pr ("/");
	    promptlen = 1;
	    fflush (stdout);
	    ttyin (cmdbuf, 78, '/');
	    if (nlines == 0) nlines++;
	    write (2, "\r", 1);
	    search (cmdbuf, f, nlines);
	    ret (dlines);
	case '!':
	    kill_line ();
	    pr ("!");
	    promptlen = 1;
	    fflush (stdout);
	    ttyin (cmdbuf, 78, '!');
	    write (2, "\n", 1);
	    promptlen = 0;
	    otty.sg_flags |= ECHO;
	    otty.sg_flags &= ~MBIT;
	    stty(2, &otty);
	    while ((id = fork ()) < 0)
		;
	    if (id == 0) {
		execl (shell, shell, "-c", cmdbuf, 0);
		write (2, "exec failed\n", 12);
		exit (1);
	    }
	    signal (SIGINT, SIG_IGN);
	    signal (SIGQUIT, SIG_IGN);
	    wait (0);
	    signal (SIGINT, end_it);
	    signal (SIGQUIT, onquit);
	    otty.sg_flags |= MBIT;
	    otty.sg_flags &= ~ECHO;
	    stty(2, &otty);
	    pr ("----------\n(continue)\n");
	    fflush (stdout);
	    break;
	default:
	    write (2, &bell, 1);
	    break;
	}
	if (done) break;
    }
    putchar ('\r');
endsw:
    inwait = 0;
    notell++;
    if (MBIT == RAW && slow_tty) {
	otty.sg_flags &= ~MBIT;
	stty(2, &otty);
    }
    return (retval);
}

char ch;

/*
** Read a decimal number from the terminal. Set cmd to the non-digit which
** terminates the number.
*/

number(cmd)
char *cmd;
{
    register int i;

    i = 0; ch = otty.sg_kill;
    for (;;) {
	read (2, &ch, 1);
	if (ch >= '0' && ch <= '9')
	    i = i*10 + ch - '0';
	else if (ch == otty.sg_kill)
	    i = 0;
	else {
	    *cmd = ch;
	    break;
	}
    }
    return (i);
}

/*
** Skip n lines in the file f
*/

skiplns (n, f)
register int n;
register FILE *f;
{
    register char c;

    while (n > 0) {
	while ((c = getc (f)) != '\n')
	    if (c == EOF)
		return;
	    n--;
    }
}

/*
** Skip nskip files in the file list (from the command line). Nskip may be
** negative.
*/

skipf (nskip)
register int nskip;
{
    if (nskip == 0) return;
    if (nskip > 0) {
	if (fnum > nfiles - 1)
	    end_it ();
    }
    else if (within)
	++fnum;
    fnum += nskip;
    if (fnum < 0)
	fnum = 0;
    else if (fnum > nfiles - 1)
	fnum = nfiles -1;
    pr ("\n...Skipping ");
    pr (nskip > 0 ? "to file " : "back to file ");
    pr (fnames[fnum]);
    pr ("\n\n");
    --fnum;
}

readch ()
{
    char ch;

    read (2, &ch, 1);
    return (ch);
}

static char BS = '\b';
static char CARAT = '^';

ttyin (buf, nmax, pchar)
char buf[];
register int nmax;
char pchar;
{
    register char *sptr;
    register char ch;
    register int slash = 0;
    int	maxlen;
    char cbuf;

    sptr = buf;
    maxlen = 0;
    while (sptr - buf < nmax) {
	if (promptlen > maxlen) maxlen = promptlen;
	ch = readch ();
	if (ch == '\\') {
	    slash++;
	}
	else if (ch == otty.sg_erase && !slash) {
	    if (sptr > buf) {
		--promptlen;
		write (2, &BS, 1);
		--sptr;
		if (*sptr < ' ' && *sptr != '\n') {
		    --promptlen;
		    write (2, &BS, 1);
		}
		continue;
	    }
	    else {
		if (!eraseln) promptlen = maxlen;
		longjmp (restore, 1);
	    }
	}
	else if (ch == otty.sg_kill && !slash) {
	    if (hard)
		pr (" XXX\n");
	    else {
		putchar ('\r');
		putchar (pchar);
		if (eraseln)
		    erase (1);
		promptlen = 1;
		sptr = buf;
	    }
	    fflush (stdout);
	    continue;
	}
	if (slash && (ch == otty.sg_kill || ch == otty.sg_erase)) {
	    write (2, &BS, 1);
	    --sptr;
	}
	if (ch != '\\')
	    slash = 0;
	*sptr++ = ch;
	if (ch < ' ' && ch != '\n' && ch != ESC) {
	    ch += 0100;
	    write (2, &CARAT, 1);
	    promptlen++;
	}
	cbuf = ch;
	if (ch != '\n' && ch != ESC) {
	    write (2, &cbuf, 1);
	    promptlen++;
	}
	else break;
    }
    *--sptr = '\0';
    if (!eraseln) promptlen = maxlen;
    if (sptr - buf >= nmax - 1)
	error ("Line too long");
}

/*
** Search for nth ocurrence of regular expression contained in buf in the file
*/

search (buf, file, n)
char buf[];
FILE *file;
register int n;
{
    long startline = ftell (file);
    register long line1 = startline;
    register long line2 = startline;
    register long line3 = startline;
    register int lncount;

    lncount = 0;
    compile (buf);
    while (!feof (file)) {
	line3 = line2;
	line2 = line1;
	line1 = ftell (file);
	rdline (file);
	lncount++;
	if (execute (Line))
		if (--n == 0) {
		    if (lncount > 3 || (lncount > 1 && no_intty))
			pr ("\n...skipping\n");
		    if (!no_intty)
			fseek (file, line3, 0);
		    else {
			kill_line ();
			pr (Line);
			putchar ('\n');
		    }
		    break;
		}
    }
    if (feof (file)) {
	if (!no_intty) {
#ifdef CORY
	    file->_flag &= ~_IOEOF; /* why doesn't fseek do this ??!!??! */
#endif
	    fseek (file, startline, 0);
	}
	else {
	    pr ("\nPattern not found\n");
	    end_it ();
	}
	error ("Pattern not found");
    }
}

/*
 * The following are adapted from the editor
 */

/*
 * Internal form of regular expressions.
 */
#define	CBRA	1	/* left \( bracket */
#define	CCHR	2	/* a particular character */
#define	CDOT	4	/* any char (.) */
#define	CCL	6	/* begin class ([) */
#define	NCCL	8	/* begin not class ([^) */
#define	CDOL	10	/* end of line ($) */
#define	CEOF	11	/* end of pattern */
#define	CKET	12	/* right \) bracket */
#define	CBACK	14	/* repeat previous match (\1, etc on lhs) */

#define	STAR	01	/* or'ed with some symbols to indicate * suffix */
 
#define	NBRA	5	/* max # of \( \) pairs */

char expbuf[BUFSIZ];
char *braslist[NBRA];
char *braelist[NBRA];
int nbra;
int circfl;
char *loc1;
char *loc2;
char *locs;


/*
 * compile: convert typed in regular expression into internal form.
 * eof is the char that delimits the r.e.
 * General structure of compiled r.e. in expbuf: A sequence of codes
 * from #defines above (CCHR, CDOT, etc). Some of these take arguments
 * which follow in line (e.g. CCHR is followed by the particular character
 * it is required to match.) CEOF terminates the r.e.
 */
compile(inbuf)
char inbuf[];
{
	register char c;
	register char *ep;
	register char *bp = inbuf;
	char *lastep;
	char bracket[NBRA], *bracketp;
	int cclcnt;

/* comerr: compilation error. Don't leave half baked r.e. around. */
#define comerr(msg) {expbuf[0] = 0; nbra = 0; error(msg); }
	ep = expbuf;
	bracketp = bracket;
	if ((c = *bp++) == '\0') {
		/* null r.e.: just re-use last r.e., which is still there */
		if (*ep==0)
			error("No previous regular expression");
		return;
	}
	nbra = 0;
	/* circfl: true if have ^ (anchored search). */
	circfl = 0;
	if (c == '^') {
		c = *bp++;
		circfl++;
	}
	lastep = 0;
	--bp;
	for (;;) {	/* for each character in the r.e. */
		if (ep >= &expbuf[BUFSIZ])
			comerr("r.e. too long");
		c = *bp++;
		if (c == '\0') {
			/* Hit trailing delim: clean up and quit */
			if (bracketp != bracket)
				comerr("unmatched \\(");
			*ep++ = CEOF;
			*ep++ = 0;
			return;
		}
		if (c!='*')
			lastep = ep;
		switch (c) {

		case '\\':
			if ((c = *bp++)=='(') {
				/* \(: start of subexpression */
				if (nbra >= NBRA)
					comerr("too many \\(\\) pairs");
				*bracketp++ = nbra;
				*ep++ = CBRA;
				*ep++ = nbra++;
				continue;
			}
			if (c == ')') {
				/* \): end of sub exp */
				if (bracketp <= bracket)
					comerr("unmatched \\)");
				*ep++ = CKET;
				*ep++ = *--bracketp;
				continue;
			}
			if (c>='1' && c<'1'+NBRA) {
				/* \1, \2, ...: rematch previous subexp */
				*ep++ = CBACK;
				*ep++ = c-'1';
				continue;
			}
			/* Otherwise just force that char, not specially */
			*ep++ = CCHR;
			if (c=='\n')
				/* Newlines can't possibly be in lines */
				comerr("multi line r.e. not allowed");
			*ep++ = c;
			continue;

		case '.':
			/* .: match any character */
			*ep++ = CDOT;
			continue;

		case '*':
			/* *: Repeat last char indefinitely */
			if (lastep==0 || *lastep==CBRA || *lastep==CKET)
				/* Not that smart, so treat * as nonspecial */
				goto defchar;
			*lastep |= STAR;
			continue;

		case '$':
			/* $: match end of line */
			if (*bp != '\0')
				/* $ only special at end of r.e. */
				goto defchar;
			*ep++ = CDOL;
			continue;

		case '[':
			/*
			 * [...]: any of chars enclosed in brackets.
			 * Compiled form: CCL or NCCL, # of possible chars,
			 * then each char. -'s are expanded.
			 */
			*ep++ = CCL;
			*ep++ = 0;
			cclcnt = 1;
			if ((c = *bp++) == '^') {
				/* [^...]: reverse sense of match */
				c = *bp++;
				ep[-2] = NCCL;
			}
			do {	/* for each char in brackets */
				if (c=='\n')
					comerr("missing ]");
				if (c == '-' && ep[-1] != 0) {
					/* form ...a-z... but [- not special */
					if ((c = *bp++) == ']') {
						/* -] not special either */
						*ep++ = '-';
						cclcnt++;
						break;
					}
					while (ep[-1]<c) {
						/* insert all chars between */
						*ep = ep[-1]+1;
						ep++;
						cclcnt++;
						if (ep>=&expbuf[BUFSIZ])
							comerr("Too long");
					}
				}
				*ep++ = c;
				cclcnt++;
				if (ep >= &expbuf[BUFSIZ])
					comerr("Too long");
			} while ((c = *bp++) != ']');
			lastep[1] = cclcnt;	/* backpatch count */
			continue;

		defchar:
		default:
			/*
			 * An ordinary char or one treated as ordinary.
			 * Store CCHR followed by that char, rather than
			 * just the char. This causes most r.e.'s to take
			 * up about twice the space you would expect.
			 * On the other hand, it makes r.e.'s beautifully
			 * portable, even though the codes could be real
			 * characters.
			 */
			*ep++ = CCHR;
			*ep++ = c;
		}
	}
}

/*
 * execute: look for the compiled r.e. on line addr.
 * gf is 0 if this is the first time on this line, otherwise nonzero.
 * If not first, start looking at locs, otherwise at beg of linebuf.
 * loc1 and loc2 are set to the ends of the pattern found, if any.
 * 1 is returned if successful, otherwise 0.
 */
execute(lptr)
char *lptr;
{
	register char *p1, *p2;
	register int c;

	for (c=0; c<NBRA; c++) {
		braslist[c] = 0;
		braelist[c] = 0;
	}
	p1 = lptr;
	p2 = expbuf;
	if (circfl) {
		/* anchored search (^): just try one advance. */
		loc1 = p1;
		return(advance(p1, p2));
	}
	/* fast check for first character */
	if (*p2==CCHR) {
		c = p2[1];
		do {
			if (*p1!=c)
				continue;
			if (advance(p1, p2)) {
				loc1 = p1;
				return(1);
			}
		} while (*p1++);
		return(0);
	}
	/* regular algorithm, try advance starting at each char position. */
	do {
		if (advance(p1, p2)) {
			loc1 = p1;
			return(1);
		}
	} while (*p1++);
	return(0);
}

/*
 * advance: does an anchored search for expression starting at ep,
 * looking in line starting at lp. Returns 1 if matches, else 0.
 * If found, loc2 is set to end of pattern.
 */
advance(lp, ep)
register char *ep, *lp;
{
	register char *curlp;
	int i;

	for (;;) switch (*ep++) {	/* for each code in r.e., look at it..*/

	case CCHR:
		if (*ep++ == *lp++)
			continue;
		return(0);

	case CDOT:
		if (*lp++)
			continue;
		return(0);

	case CDOL:
		if (*lp==0)
			continue;
		return(0);

	case CEOF:
		loc2 = lp;
		return(1);

	case CCL:
		if (cclass(ep, *lp++, 1)) {
			ep += *ep;
			continue;
		}
		return(0);

	case NCCL:
		if (cclass(ep, *lp++, 0)) {
			ep += *ep;
			continue;
		}
		return(0);

	case CBRA:
		braslist[*ep++] = lp;
		continue;

	case CKET:
		braelist[*ep++] = lp;
		continue;

	case CBACK:
		if (braelist[i = *ep++]==0)
			error("bad back reference");
		if (backref(i, lp)) {
			lp += braelist[i] - braslist[i];
			continue;
		}
		return(0);

	case CBACK|STAR:
		if (braelist[i = *ep++] == 0)
			error("bad back reference");
		curlp = lp;
		while (backref(i, lp))
			lp += braelist[i] - braslist[i];
		while (lp >= curlp) {
			if (advance(lp, ep))
				return(1);
			lp -= braelist[i] - braslist[i];
		}
		continue;

	case CDOT|STAR:
		curlp = lp;
		while (*lp++)
			;
		goto star;

	case CCHR|STAR:
		curlp = lp;
		while (*lp++ == *ep)
			;
		ep++;
		goto star;

	case CCL|STAR:
	case NCCL|STAR:
		curlp = lp;
		while (cclass(ep, *lp++, ep[-1]==(CCL|STAR)))
			;
		ep += *ep;
		goto star;

	star:
		/*
		 * star: special treatment. We have found as many of them
		 * as there are to find. Maybe this was too many, as dictated
		 * by what follows in the pattern. Try, starting from the
		 * end, to recursively advance after each char found,
		 * and return after first successful advance (thus finding
		 * largest possible string that matches).
		 */
		do {
			lp--;
			if (lp==locs)
				break;
			if (advance(lp, ep))
				return(1);
		} while (lp > curlp);
		/* star failed at all attempts, so whole pattern fails. */
		return(0);

	default:
		longjmp (restore, 1);
	}
}

/*
 * backref: checks to see that text starting at lp matches previous
 * sub-expression #i. Returns 1 if successful, else 0. (Used for \k
 * on lhs.)
 */
backref(i, lp)
register int i;
register char *lp;
{
	register char *bp;

	bp = braslist[i];
	while (*bp++ == *lp++)
		if (bp >= braelist[i])
			return(1);
	return(0);
}

/*
 * cclass: check to see if character c is in class starting at set.
 * ([...] construction on lhs of r.e.) af is sense of success/failure:
 * af=1 is normal (success returns 1), af=0 is reversed for [^ (success
 * returns 0).
 */
int
cclass(set, c, af)
register char *set, c;
int af;
{
	register n;

	if (c==0)
		return(0);
	n = *set++;
	while (--n)
		if (*set++ == c)
			return(af);
	return(!af);
}

error (mess)
char *mess;
{
    if (promptlen > 0)
	if (hard)
	    putchar ('\n');
	else
	    kill_line ();
    promptlen += strlen (mess);
    if (Senter && Sexit) {
	tputs (Senter, 1, putch);
	pr(mess);
	tputs (Sexit, 1, putch);
    }
    else
	pr (mess);
    if (hard)
	putchar ('\n');
    fflush(stdout);
    errors++;
    longjmp (restore, 1);
}

rdline (f)
register FILE *f;
{
    register char c;
    register char *p;

    p = Line;
    while ((c = getc (f)) != '\n' && c != EOF && p - Line < LINSIZ - 1)
	*p++ = c;
    *p = '\0';
}