BSD 3 development
[unix-history] / usr / src / cmd / more.c
CommitLineData
9f232b38
ES
1#include <whoami.h>
2#include <stdio.h>
3#include <signal.h>
4#include <sgtty.h>
5#include <setjmp.h>
6#include <sys/types.h>
7#include <sys/dir.h>
8#include <sys/stat.h>
9
10#ifdef CORY
11#define MBIT RAW
12#else
13#include <ctype.h>
14#define MBIT CBREAK
15#endif
16
17#define TBUFSIZ 1024
18#define LINSIZ 256
19#define ctrl(letter) ('letter' & 077)
20#define RUBOUT '\177'
21#define ESC '\033'
22#define QUIT '\034'
23
24struct sgttyb otty;
25int fnum, no_intty, no_tty, slow_tty;
26int dum_opt, dlines, onquit(), end_it();
27int stop_opt = 1;
28int promptlen;
29int startup = 1;
30int firstf = 1;
31int notell = 1;
32int inwait, pause, errors;
33int within; /* true if we are within a file,
34 false if we are between files */
35int hard, dumb, noscroll, hardtabs;
36char **fnames;
37int nfiles;
38char *shell;
39char ch;
40jmp_buf restore;
41char obuf[BUFSIZ]; /* stdout buffer */
42char Line[LINSIZ];
43int Lpp = 24; /* lines per page */
44char *Clear; /* clear screen */
45char *eraseln; /* erase line */
46char *Senter, *Sexit;/* enter and exit standout mode */
47char *tgetstr();
48int Mcol = 80; /* number of columns */
49int Wrap = 1; /* set if automargins */
50extern char PC; /* pad character */
51extern short ospeed;
52
53
54main(argc, argv)
55int argc;
56char *argv[];
57{
58 register FILE *f;
59 register char *s;
60 register char *p;
61 register char ch;
62 register int left;
63 int prnames = 0;
64 int initopt = 0;
65 int srchopt = 0;
66 int initline;
67 char buf[TBUFSIZ];
68 char clearbuf[100];
69 char initbuf[80];
70 char *clearptr;
71 char *getenv();
72 FILE *checkf();
73
74 nfiles = argc;
75 fnames = argv;
76 /* Put terminal setup stuff in separate procedure ?? (From here...) */
77 setbuf(stdout, obuf);
78 if (!(no_tty = gtty(1, &otty))) {
79 if (tgetent(buf, getenv("TERM")) <= 0) {
80 dumb++;
81 }
82 else {
83 if (((Lpp = tgetnum("li")) < 0) || tgetflag("hc")) {
84 hard++; /* Hard copy terminal */
85 Lpp = 24;
86 }
87 if (!hard && tgetflag("ns"))
88 noscroll++;
89 if ((Mcol = tgetnum("co")) < 0)
90 Mcol = 80;
91 Wrap = tgetflag("am");
92 clearptr = clearbuf;
93 eraseln = tgetstr("ce",&clearptr);
94 Clear = tgetstr("cl", &clearptr);
95 Senter = tgetstr("so", &clearptr);
96 Sexit = tgetstr("se", &clearptr);
97 PC = *tgetstr("pc", &clearptr);
98 }
99 if ((shell = getenv("SHELL")) == NULL)
100 shell = "/bin/sh";
101 }
102 no_intty = gtty(0, &otty);
103 gtty(2, &otty);
104 ospeed = otty.sg_ospeed;
105 slow_tty = ospeed < B1200;
106 hardtabs = !(otty.sg_flags & XTABS);
107 if (!no_tty) {
108 otty.sg_flags &= ~ECHO;
109 if (MBIT == CBREAK || !slow_tty)
110 otty.sg_flags |= MBIT;
111 }
112 /* ... until here or so */
113 while (--nfiles > 0) {
114 if ((ch = (*++fnames)[0]) == '-') {
115 for (s = fnames[0] + 1, dlines = 0; *s != '\0'; s++)
116 if (isdigit(*s))
117 dlines = dlines*10 + *s - '0';
118 else if (*s == 'd')
119 dum_opt = 1;
120 else if (*s == 'l')
121 stop_opt = 0;
122 }
123 else if (ch == '+') {
124 s = *fnames;
125 if (*++s == '/') {
126 srchopt++;
127 for (++s, p = initbuf; p < initbuf + 79 && *s != '\0';)
128 *p++ = *s++;
129 *p = '\0';
130 }
131 else {
132 initopt++;
133 for (initline = 0; *s != '\0'; s++)
134 if (isdigit (*s))
135 initline = initline*10 + *s -'0';
136 --initline;
137 }
138 }
139 else break;
140 }
141 if (dlines == 0)
142 dlines = Lpp - 2;
143 left = dlines;
144 if (nfiles > 1)
145 prnames++;
146 if (!no_intty && nfiles == 0) {
147 fputs("Usage: ",stderr);
148 fputs(argv[0],stderr);
149 fputs(" [-dn] name1 name2 ...\n",stderr);
150 exit(1);
151 }
152 else
153 f = stdin;
154 if (!no_tty) {
155 signal(SIGQUIT, onquit);
156 signal(SIGINT, end_it);
157 stty (2, &otty);
158 }
159 if (no_intty) {
160 if (no_tty)
161 copy_file (stdin);
162 else {
163 if (srchopt)
164 search (initbuf, stdin, 1);
165 else if (initopt)
166 skiplns (initline, stdin);
167 screen (stdin, left);
168 }
169 end_it ();
170 }
171
172 while (fnum < nfiles) {
173 if ((f = checkf (fnames[fnum])) != NULL) {
174 if (firstf) setjmp (restore);
175 if (firstf) {
176 firstf = 0;
177 if (srchopt)
178 search (initbuf, f, 1);
179 else if (initopt)
180 skiplns (initline, f);
181 }
182 else if (fnum < nfiles && !no_tty) {
183 setjmp (restore);
184 left = command (fnames[fnum], f);
185 }
186 if (left != 0) {
187 if (prnames) {
188 pr("::::::::::::::");
189 if (promptlen > 14)
190 erase (14);
191 putchar ('\n');
192 pr(fnames[fnum]);
193 pr("\n::::::::::::::\n");
194 if (left > Lpp - 4)
195 left = Lpp - 4;
196 }
197 if (no_tty)
198 copy_file (f);
199 else {
200 within++;
201 screen(f, left);
202 within = 0;
203 }
204 }
205 setjmp (restore);
206 fflush(stdout);
207 fclose(f);
208 }
209 fnum++;
210 firstf = 0;
211 }
212 otty.sg_flags |= ECHO;
213 otty.sg_flags &= ~MBIT;
214 stty(2, &otty);
215 exit(0);
216}
217
218/*
219** Check whether the file named by fs is an ASCII file which the user may
220** access. If it is, return the opened file. Otherwise return NULL.
221*/
222
223FILE *
224checkf (fs)
225register char *fs;
226{
227#ifdef CORY
228 int space[3]; /* Why doesn't libretro have a V7 stat? */
229#endif
230 struct stat stbuf;
231 register FILE *f;
232 char c;
233
234 if (stat (fs, &stbuf) == -1) {
235 fflush(stdout);
236 perror(fs);
237 return (NULL);
238 }
239 if (stbuf.st_mode & S_IFDIR) {
240 pr("\n*** ");
241 pr(fs);
242 pr(": directory ***\n\n");
243 return (NULL);
244 }
245 if ((f=fopen(fs, "r")) == NULL) {
246 fflush(stdout);
247 perror(fs);
248 return (NULL);
249 }
250 c = getc(f);
251
252 /* Try to see whether it is an ASCII file */
253
254 switch ((c | *f->_ptr << 8) & 0177777) {
255 case 0405:
256 case 0407:
257 case 0410:
258 case 0411:
259 case 0177545:
260 pr("\n******** ");
261 pr(fs);
262 pr(": Not a text file ********\n\n");
263 fclose (f);
264 return (NULL);
265 default:
266 break;
267 }
268 if (c == '\f') {
269 c = 0;
270 doclear ();
271 }
272 ungetc (c, f);
273 return (f);
274}
275
276/*
277** A real function, for the tputs routine in termlib
278*/
279
280putch (ch)
281register char ch;
282{
283 putchar (ch);
284}
285
286/*
287** Print out the contents of the file f, one screenful at a time.
288*/
289
290#define STOP -10
291
292screen (f, num_lines)
293register FILE *f;
294register int num_lines;
295{
296 register int c;
297 int nchars;
298
299 for (;;) {
300 while (num_lines > 0 && !pause) {
301 if ((nchars = getline (f)) == EOF)
302 return;
303 if (Senter && *Senter == ' ' && promptlen > 0)
304 erase (0);
305 pr (Line);
306 if (nchars < promptlen)
307 erase (nchars); /* erase () sets promptlen to 0 */
308 else promptlen = 0;
309 if (nchars < Mcol)
310 putchar('\n');
311 if (nchars == STOP)
312 break;
313 num_lines--;
314 }
315 fflush(stdout);
316 if ((c = getc(f)) == EOF) {
317 if (noscroll)
318 doclear();
319 else
320 erase (0);
321 return;
322 }
323 ungetc (c, f);
324 setjmp (restore);
325 pause = 0; startup = 0;
326 if ((num_lines = command (NULL, f)) == 0)
327 return;
328 }
329}
330
331/*
332** Come here if a quit signal is received
333*/
334
335onquit()
336{
337 signal(SIGQUIT, SIG_IGN);
338 if (!inwait) {
339 putchar ('\n');
340 if (!startup) {
341 signal(SIGQUIT, onquit);
342 longjmp (restore, 1);
343 }
344 else
345 pause++;
346 }
347 else if (!dum_opt && notell) {
348 write (2, "[Use q or Q to quit]", 20);
349 promptlen += 20;
350 notell = 0;
351 }
352 signal(SIGQUIT, onquit);
353}
354
355/*
356** Clean up terminal state and exit. Also come here if interrupt signal received
357*/
358
359end_it ()
360{
361
362 otty.sg_flags &= ~MBIT;
363 otty.sg_flags |= ECHO;
364 stty(2, &otty);
365 if (promptlen > 0)
366 kill_line ();
367 else
368 putchar ('\n');
369 exit(0);
370}
371
372copy_file(f)
373register FILE *f;
374{
375 register int c;
376
377 while ((c = getc(f)) != EOF)
378 putchar(c);
379}
380
381
382printd (n)
383register int n;
384{
385 register int a;
386
387 if (a = n/10)
388 printd(a);
389 putchar(n % 10 + '0');
390}
391
392static char bell = ctrl(G);
393
394strlen (s)
395char *s;
396{
397 register char *p;
398
399 p = s;
400 while (*p++)
401 ;
402 return (p - s - 1);
403}
404
405prompt (filename)
406char *filename;
407{
408 if (promptlen > 0)
409 kill_line ();
410 if (!hard) {
411 promptlen = 8;
412 if (Senter && Sexit)
413 tputs (Senter, 1, putch);
414 pr("--More--");
415 if (filename != NULL) {
416 pr("(Next file: ");
417 pr(filename);
418 putchar(')');
419 promptlen += 13 + strlen(filename);
420 }
421 if (dum_opt) {
422 pr("[Hit space to continue, Rubout to abort]");
423 promptlen += 40;
424 }
425 if (Senter && Sexit)
426 tputs (Sexit, 1, putch);
427 fflush(stdout);
428 }
429 else
430 write (2, &bell, 1);
431 inwait++;
432}
433
434/*
435** Get a logical line
436*/
437
438getline(f)
439register FILE *f;
440{
441 register char c;
442 register char *p;
443 register int column;
444 register int i;
445 static int colflg;
446
447 p = Line;
448 i = column = 0;
449 c = getc (f);
450 if (colflg && c == '\n') c = getc (f);
451 for (i = 1; i < LINSIZ; i++) {
452 if (c == EOF) {
453 if (p > Line) {
454 *p = '\0';
455 return (column);
456 }
457 return (EOF);
458 }
459 if (c == '\n')
460 break;
461 *p++ = c;
462 if (c == '\t')
463 if (hardtabs && column < promptlen && !hard) {
464 if (eraseln && !dumb) {
465 tputs (eraseln, 1, putch);
466 promptlen = 0;
467 }
468 else {
469 for (--p; column & 7; column++)
470 *p++ = ' ';
471 if (column >= promptlen) promptlen = 0;
472 }
473 }
474 else
475 column = 1 + (column | 7);
476 else if (c == '\b')
477 column--;
478 else if (c == '\r')
479 column = 0;
480 else if (c == '\f' && stop_opt) {
481 p[-1] = '^';
482 *p++ = 'L';
483 break;
484 }
485 else if (c == EOF)
486 return (column);
487 else if (c >= ' ')
488 column++;
489 if (column >= Mcol) break;
490 c = getc (f);
491 }
492 if (Mcol > 0 && column >= Mcol) {
493 if (!Wrap) {
494 *p++ = '\n';
495 i++;
496 }
497 }
498 colflg = (column == Mcol) || c == '\f';
499 *p = 0;
500 if (c == '\f' && stop_opt)
501 return (STOP);
502 return (column);
503}
504
505/*
506** Erase the rest of the prompt, assuming we are starting column col.
507*/
508
509erase (col)
510register int col;
511{
512
513 if (hard || promptlen == 0)
514 return;
515 if (col == 0)
516 putchar ('\r');
517 if (!dumb && eraseln)
518 tputs (eraseln, 1, putch);
519 else
520 for (col = promptlen - col; col > 0; col--)
521 putchar (' ');
522 promptlen = 0;
523}
524
525/*
526** Erase the current line entirely
527*/
528
529kill_line ()
530{
531 erase (0);
532 if (!eraseln || dumb) putchar ('\r');
533}
534
535/*
536** Print string
537*/
538
539pr(s1)
540char *s1;
541{
542 register char *s;
543 register char c;
544
545 for (s = s1; c = *s++; )
546 putchar(c);
547}
548
549/*
550** Clear the screen
551*/
552
553doclear()
554{
555 if (Clear && Lpp > 0)
556 tputs(Clear, 1, putch);
557}
558
559
560/*
561** Read a command and do it. A command consists of an optional integer
562** argument followed by the command character. Return the number of lines
563** to display in the next screenful. If there is nothing more to display
564** in the current file, zero is returned.
565*/
566
567command (filename, f)
568char *filename;
569register FILE *f;
570{
571 register int nlines;
572 register int retval;
573 register char c;
574 int id, done;
575 char comchar, cmdbuf[80], *p;
576
577#define ret(val) retval=val;done++;break
578
579 done = 0;
580 if (!errors)
581 prompt (filename);
582 else
583 errors = 0;
584 if (MBIT == RAW && slow_tty) {
585 otty.sg_flags |= MBIT;
586 stty(2, &otty);
587 }
588 for (;;) {
589 nlines = number (&comchar);
590 switch (comchar) {
591 case ' ':
592 case 'z':
593 if (nlines == 0) nlines = dlines;
594 else if (comchar == 'z') dlines = nlines;
595 ret (nlines);
596 case 'd':
597 case ctrl(D):
598 ret (11);
599 case RUBOUT:
600 case 'q':
601 case 'Q':
602 end_it ();
603 case 's':
604 case 'f':
605 if (nlines == 0) nlines++;
606 if (comchar == 'f')
607 nlines *= dlines;
608 putchar ('\r');
609 erase (0);
610 pr("\n...skipping ");
611 printd(nlines);
612 pr(" line");
613 if (nlines > 1)
614 pr("s\n\n");
615 else
616 pr("\n\n");
617 while (nlines > 0) {
618 while ((c = getc (f)) != '\n')
619 if (c == EOF) {
620 retval = 0;
621 done++;
622 goto endsw;
623 }
624 nlines--;
625 }
626 ret (dlines);
627 break;
628 case '\n':
629 ret (1);
630 case 'n':
631 if (nlines == 0)
632 nlines++;
633 putchar ('\r');
634 erase (0);
635 skipf (nlines);
636 ret (0);
637 case 'p':
638 if (no_intty) {
639 write (2, &bell, 1);
640 break;
641 }
642 putchar ('\r');
643 erase (0);
644 if (nlines == 0)
645 nlines++;
646 skipf (-nlines);
647 ret (0);
648 case '/':
649 kill_line ();
650 pr ("/");
651 promptlen = 1;
652 fflush (stdout);
653 ttyin (cmdbuf, 78, '/');
654 if (nlines == 0) nlines++;
655 write (2, "\r", 1);
656 search (cmdbuf, f, nlines);
657 ret (dlines);
658 case '!':
659 kill_line ();
660 pr ("!");
661 promptlen = 1;
662 fflush (stdout);
663 ttyin (cmdbuf, 78, '!');
664 write (2, "\n", 1);
665 promptlen = 0;
666 otty.sg_flags |= ECHO;
667 otty.sg_flags &= ~MBIT;
668 stty(2, &otty);
669 while ((id = fork ()) < 0)
670 ;
671 if (id == 0) {
672 execl (shell, shell, "-c", cmdbuf, 0);
673 write (2, "exec failed\n", 12);
674 exit (1);
675 }
676 signal (SIGINT, SIG_IGN);
677 signal (SIGQUIT, SIG_IGN);
678 wait (0);
679 signal (SIGINT, end_it);
680 signal (SIGQUIT, onquit);
681 otty.sg_flags |= MBIT;
682 otty.sg_flags &= ~ECHO;
683 stty(2, &otty);
684 pr ("----------\n(continue)\n");
685 fflush (stdout);
686 break;
687 default:
688 write (2, &bell, 1);
689 break;
690 }
691 if (done) break;
692 }
693 putchar ('\r');
694endsw:
695 inwait = 0;
696 notell++;
697 if (MBIT == RAW && slow_tty) {
698 otty.sg_flags &= ~MBIT;
699 stty(2, &otty);
700 }
701 return (retval);
702}
703
704char ch;
705
706/*
707** Read a decimal number from the terminal. Set cmd to the non-digit which
708** terminates the number.
709*/
710
711number(cmd)
712char *cmd;
713{
714 register int i;
715
716 i = 0; ch = otty.sg_kill;
717 for (;;) {
718 read (2, &ch, 1);
719 if (ch >= '0' && ch <= '9')
720 i = i*10 + ch - '0';
721 else if (ch == otty.sg_kill)
722 i = 0;
723 else {
724 *cmd = ch;
725 break;
726 }
727 }
728 return (i);
729}
730
731/*
732** Skip n lines in the file f
733*/
734
735skiplns (n, f)
736register int n;
737register FILE *f;
738{
739 register char c;
740
741 while (n > 0) {
742 while ((c = getc (f)) != '\n')
743 if (c == EOF)
744 return;
745 n--;
746 }
747}
748
749/*
750** Skip nskip files in the file list (from the command line). Nskip may be
751** negative.
752*/
753
754skipf (nskip)
755register int nskip;
756{
757 if (nskip == 0) return;
758 if (nskip > 0) {
759 if (fnum > nfiles - 1)
760 end_it ();
761 }
762 else if (within)
763 ++fnum;
764 fnum += nskip;
765 if (fnum < 0)
766 fnum = 0;
767 else if (fnum > nfiles - 1)
768 fnum = nfiles -1;
769 pr ("\n...Skipping ");
770 pr (nskip > 0 ? "to file " : "back to file ");
771 pr (fnames[fnum]);
772 pr ("\n\n");
773 --fnum;
774}
775
776readch ()
777{
778 char ch;
779
780 read (2, &ch, 1);
781 return (ch);
782}
783
784static char BS = '\b';
785static char CARAT = '^';
786
787ttyin (buf, nmax, pchar)
788char buf[];
789register int nmax;
790char pchar;
791{
792 register char *sptr;
793 register char ch;
794 register int slash = 0;
795 int maxlen;
796 char cbuf;
797
798 sptr = buf;
799 maxlen = 0;
800 while (sptr - buf < nmax) {
801 if (promptlen > maxlen) maxlen = promptlen;
802 ch = readch ();
803 if (ch == '\\') {
804 slash++;
805 }
806 else if (ch == otty.sg_erase && !slash) {
807 if (sptr > buf) {
808 --promptlen;
809 write (2, &BS, 1);
810 --sptr;
811 if (*sptr < ' ' && *sptr != '\n') {
812 --promptlen;
813 write (2, &BS, 1);
814 }
815 continue;
816 }
817 else {
818 if (!eraseln) promptlen = maxlen;
819 longjmp (restore, 1);
820 }
821 }
822 else if (ch == otty.sg_kill && !slash) {
823 if (hard)
824 pr (" XXX\n");
825 else {
826 putchar ('\r');
827 putchar (pchar);
828 if (eraseln)
829 erase (1);
830 promptlen = 1;
831 sptr = buf;
832 }
833 fflush (stdout);
834 continue;
835 }
836 if (slash && (ch == otty.sg_kill || ch == otty.sg_erase)) {
837 write (2, &BS, 1);
838 --sptr;
839 }
840 if (ch != '\\')
841 slash = 0;
842 *sptr++ = ch;
843 if (ch < ' ' && ch != '\n' && ch != ESC) {
844 ch += 0100;
845 write (2, &CARAT, 1);
846 promptlen++;
847 }
848 cbuf = ch;
849 if (ch != '\n' && ch != ESC) {
850 write (2, &cbuf, 1);
851 promptlen++;
852 }
853 else break;
854 }
855 *--sptr = '\0';
856 if (!eraseln) promptlen = maxlen;
857 if (sptr - buf >= nmax - 1)
858 error ("Line too long");
859}
860
861/*
862** Search for nth ocurrence of regular expression contained in buf in the file
863*/
864
865search (buf, file, n)
866char buf[];
867FILE *file;
868register int n;
869{
870 long startline = ftell (file);
871 register long line1 = startline;
872 register long line2 = startline;
873 register long line3 = startline;
874 register int lncount;
875
876 lncount = 0;
877 compile (buf);
878 while (!feof (file)) {
879 line3 = line2;
880 line2 = line1;
881 line1 = ftell (file);
882 rdline (file);
883 lncount++;
884 if (execute (Line))
885 if (--n == 0) {
886 if (lncount > 3 || (lncount > 1 && no_intty))
887 pr ("\n...skipping\n");
888 if (!no_intty)
889 fseek (file, line3, 0);
890 else {
891 kill_line ();
892 pr (Line);
893 putchar ('\n');
894 }
895 break;
896 }
897 }
898 if (feof (file)) {
899 if (!no_intty) {
900#ifdef CORY
901 file->_flag &= ~_IOEOF; /* why doesn't fseek do this ??!!??! */
902#endif
903 fseek (file, startline, 0);
904 }
905 else {
906 pr ("\nPattern not found\n");
907 end_it ();
908 }
909 error ("Pattern not found");
910 }
911}
912
913/*
914 * The following are adapted from the editor
915 */
916
917/*
918 * Internal form of regular expressions.
919 */
920#define CBRA 1 /* left \( bracket */
921#define CCHR 2 /* a particular character */
922#define CDOT 4 /* any char (.) */
923#define CCL 6 /* begin class ([) */
924#define NCCL 8 /* begin not class ([^) */
925#define CDOL 10 /* end of line ($) */
926#define CEOF 11 /* end of pattern */
927#define CKET 12 /* right \) bracket */
928#define CBACK 14 /* repeat previous match (\1, etc on lhs) */
929
930#define STAR 01 /* or'ed with some symbols to indicate * suffix */
931
932#define NBRA 5 /* max # of \( \) pairs */
933
934char expbuf[BUFSIZ];
935char *braslist[NBRA];
936char *braelist[NBRA];
937int nbra;
938int circfl;
939char *loc1;
940char *loc2;
941char *locs;
942
943
944/*
945 * compile: convert typed in regular expression into internal form.
946 * eof is the char that delimits the r.e.
947 * General structure of compiled r.e. in expbuf: A sequence of codes
948 * from #defines above (CCHR, CDOT, etc). Some of these take arguments
949 * which follow in line (e.g. CCHR is followed by the particular character
950 * it is required to match.) CEOF terminates the r.e.
951 */
952compile(inbuf)
953char inbuf[];
954{
955 register char c;
956 register char *ep;
957 register char *bp = inbuf;
958 char *lastep;
959 char bracket[NBRA], *bracketp;
960 int cclcnt;
961
962/* comerr: compilation error. Don't leave half baked r.e. around. */
963#define comerr(msg) {expbuf[0] = 0; nbra = 0; error(msg); }
964 ep = expbuf;
965 bracketp = bracket;
966 if ((c = *bp++) == '\0') {
967 /* null r.e.: just re-use last r.e., which is still there */
968 if (*ep==0)
969 error("No previous regular expression");
970 return;
971 }
972 nbra = 0;
973 /* circfl: true if have ^ (anchored search). */
974 circfl = 0;
975 if (c == '^') {
976 c = *bp++;
977 circfl++;
978 }
979 lastep = 0;
980 --bp;
981 for (;;) { /* for each character in the r.e. */
982 if (ep >= &expbuf[BUFSIZ])
983 comerr("r.e. too long");
984 c = *bp++;
985 if (c == '\0') {
986 /* Hit trailing delim: clean up and quit */
987 if (bracketp != bracket)
988 comerr("unmatched \\(");
989 *ep++ = CEOF;
990 *ep++ = 0;
991 return;
992 }
993 if (c!='*')
994 lastep = ep;
995 switch (c) {
996
997 case '\\':
998 if ((c = *bp++)=='(') {
999 /* \(: start of subexpression */
1000 if (nbra >= NBRA)
1001 comerr("too many \\(\\) pairs");
1002 *bracketp++ = nbra;
1003 *ep++ = CBRA;
1004 *ep++ = nbra++;
1005 continue;
1006 }
1007 if (c == ')') {
1008 /* \): end of sub exp */
1009 if (bracketp <= bracket)
1010 comerr("unmatched \\)");
1011 *ep++ = CKET;
1012 *ep++ = *--bracketp;
1013 continue;
1014 }
1015 if (c>='1' && c<'1'+NBRA) {
1016 /* \1, \2, ...: rematch previous subexp */
1017 *ep++ = CBACK;
1018 *ep++ = c-'1';
1019 continue;
1020 }
1021 /* Otherwise just force that char, not specially */
1022 *ep++ = CCHR;
1023 if (c=='\n')
1024 /* Newlines can't possibly be in lines */
1025 comerr("multi line r.e. not allowed");
1026 *ep++ = c;
1027 continue;
1028
1029 case '.':
1030 /* .: match any character */
1031 *ep++ = CDOT;
1032 continue;
1033
1034 case '*':
1035 /* *: Repeat last char indefinitely */
1036 if (lastep==0 || *lastep==CBRA || *lastep==CKET)
1037 /* Not that smart, so treat * as nonspecial */
1038 goto defchar;
1039 *lastep |= STAR;
1040 continue;
1041
1042 case '$':
1043 /* $: match end of line */
1044 if (*bp != '\0')
1045 /* $ only special at end of r.e. */
1046 goto defchar;
1047 *ep++ = CDOL;
1048 continue;
1049
1050 case '[':
1051 /*
1052 * [...]: any of chars enclosed in brackets.
1053 * Compiled form: CCL or NCCL, # of possible chars,
1054 * then each char. -'s are expanded.
1055 */
1056 *ep++ = CCL;
1057 *ep++ = 0;
1058 cclcnt = 1;
1059 if ((c = *bp++) == '^') {
1060 /* [^...]: reverse sense of match */
1061 c = *bp++;
1062 ep[-2] = NCCL;
1063 }
1064 do { /* for each char in brackets */
1065 if (c=='\n')
1066 comerr("missing ]");
1067 if (c == '-' && ep[-1] != 0) {
1068 /* form ...a-z... but [- not special */
1069 if ((c = *bp++) == ']') {
1070 /* -] not special either */
1071 *ep++ = '-';
1072 cclcnt++;
1073 break;
1074 }
1075 while (ep[-1]<c) {
1076 /* insert all chars between */
1077 *ep = ep[-1]+1;
1078 ep++;
1079 cclcnt++;
1080 if (ep>=&expbuf[BUFSIZ])
1081 comerr("Too long");
1082 }
1083 }
1084 *ep++ = c;
1085 cclcnt++;
1086 if (ep >= &expbuf[BUFSIZ])
1087 comerr("Too long");
1088 } while ((c = *bp++) != ']');
1089 lastep[1] = cclcnt; /* backpatch count */
1090 continue;
1091
1092 defchar:
1093 default:
1094 /*
1095 * An ordinary char or one treated as ordinary.
1096 * Store CCHR followed by that char, rather than
1097 * just the char. This causes most r.e.'s to take
1098 * up about twice the space you would expect.
1099 * On the other hand, it makes r.e.'s beautifully
1100 * portable, even though the codes could be real
1101 * characters.
1102 */
1103 *ep++ = CCHR;
1104 *ep++ = c;
1105 }
1106 }
1107}
1108
1109/*
1110 * execute: look for the compiled r.e. on line addr.
1111 * gf is 0 if this is the first time on this line, otherwise nonzero.
1112 * If not first, start looking at locs, otherwise at beg of linebuf.
1113 * loc1 and loc2 are set to the ends of the pattern found, if any.
1114 * 1 is returned if successful, otherwise 0.
1115 */
1116execute(lptr)
1117char *lptr;
1118{
1119 register char *p1, *p2;
1120 register int c;
1121
1122 for (c=0; c<NBRA; c++) {
1123 braslist[c] = 0;
1124 braelist[c] = 0;
1125 }
1126 p1 = lptr;
1127 p2 = expbuf;
1128 if (circfl) {
1129 /* anchored search (^): just try one advance. */
1130 loc1 = p1;
1131 return(advance(p1, p2));
1132 }
1133 /* fast check for first character */
1134 if (*p2==CCHR) {
1135 c = p2[1];
1136 do {
1137 if (*p1!=c)
1138 continue;
1139 if (advance(p1, p2)) {
1140 loc1 = p1;
1141 return(1);
1142 }
1143 } while (*p1++);
1144 return(0);
1145 }
1146 /* regular algorithm, try advance starting at each char position. */
1147 do {
1148 if (advance(p1, p2)) {
1149 loc1 = p1;
1150 return(1);
1151 }
1152 } while (*p1++);
1153 return(0);
1154}
1155
1156/*
1157 * advance: does an anchored search for expression starting at ep,
1158 * looking in line starting at lp. Returns 1 if matches, else 0.
1159 * If found, loc2 is set to end of pattern.
1160 */
1161advance(lp, ep)
1162register char *ep, *lp;
1163{
1164 register char *curlp;
1165 int i;
1166
1167 for (;;) switch (*ep++) { /* for each code in r.e., look at it..*/
1168
1169 case CCHR:
1170 if (*ep++ == *lp++)
1171 continue;
1172 return(0);
1173
1174 case CDOT:
1175 if (*lp++)
1176 continue;
1177 return(0);
1178
1179 case CDOL:
1180 if (*lp==0)
1181 continue;
1182 return(0);
1183
1184 case CEOF:
1185 loc2 = lp;
1186 return(1);
1187
1188 case CCL:
1189 if (cclass(ep, *lp++, 1)) {
1190 ep += *ep;
1191 continue;
1192 }
1193 return(0);
1194
1195 case NCCL:
1196 if (cclass(ep, *lp++, 0)) {
1197 ep += *ep;
1198 continue;
1199 }
1200 return(0);
1201
1202 case CBRA:
1203 braslist[*ep++] = lp;
1204 continue;
1205
1206 case CKET:
1207 braelist[*ep++] = lp;
1208 continue;
1209
1210 case CBACK:
1211 if (braelist[i = *ep++]==0)
1212 error("bad back reference");
1213 if (backref(i, lp)) {
1214 lp += braelist[i] - braslist[i];
1215 continue;
1216 }
1217 return(0);
1218
1219 case CBACK|STAR:
1220 if (braelist[i = *ep++] == 0)
1221 error("bad back reference");
1222 curlp = lp;
1223 while (backref(i, lp))
1224 lp += braelist[i] - braslist[i];
1225 while (lp >= curlp) {
1226 if (advance(lp, ep))
1227 return(1);
1228 lp -= braelist[i] - braslist[i];
1229 }
1230 continue;
1231
1232 case CDOT|STAR:
1233 curlp = lp;
1234 while (*lp++)
1235 ;
1236 goto star;
1237
1238 case CCHR|STAR:
1239 curlp = lp;
1240 while (*lp++ == *ep)
1241 ;
1242 ep++;
1243 goto star;
1244
1245 case CCL|STAR:
1246 case NCCL|STAR:
1247 curlp = lp;
1248 while (cclass(ep, *lp++, ep[-1]==(CCL|STAR)))
1249 ;
1250 ep += *ep;
1251 goto star;
1252
1253 star:
1254 /*
1255 * star: special treatment. We have found as many of them
1256 * as there are to find. Maybe this was too many, as dictated
1257 * by what follows in the pattern. Try, starting from the
1258 * end, to recursively advance after each char found,
1259 * and return after first successful advance (thus finding
1260 * largest possible string that matches).
1261 */
1262 do {
1263 lp--;
1264 if (lp==locs)
1265 break;
1266 if (advance(lp, ep))
1267 return(1);
1268 } while (lp > curlp);
1269 /* star failed at all attempts, so whole pattern fails. */
1270 return(0);
1271
1272 default:
1273 longjmp (restore, 1);
1274 }
1275}
1276
1277/*
1278 * backref: checks to see that text starting at lp matches previous
1279 * sub-expression #i. Returns 1 if successful, else 0. (Used for \k
1280 * on lhs.)
1281 */
1282backref(i, lp)
1283register int i;
1284register char *lp;
1285{
1286 register char *bp;
1287
1288 bp = braslist[i];
1289 while (*bp++ == *lp++)
1290 if (bp >= braelist[i])
1291 return(1);
1292 return(0);
1293}
1294
1295/*
1296 * cclass: check to see if character c is in class starting at set.
1297 * ([...] construction on lhs of r.e.) af is sense of success/failure:
1298 * af=1 is normal (success returns 1), af=0 is reversed for [^ (success
1299 * returns 0).
1300 */
1301int
1302cclass(set, c, af)
1303register char *set, c;
1304int af;
1305{
1306 register n;
1307
1308 if (c==0)
1309 return(0);
1310 n = *set++;
1311 while (--n)
1312 if (*set++ == c)
1313 return(af);
1314 return(!af);
1315}
1316
1317error (mess)
1318char *mess;
1319{
1320 if (promptlen > 0)
1321 if (hard)
1322 putchar ('\n');
1323 else
1324 kill_line ();
1325 promptlen += strlen (mess);
1326 if (Senter && Sexit) {
1327 tputs (Senter, 1, putch);
1328 pr(mess);
1329 tputs (Sexit, 1, putch);
1330 }
1331 else
1332 pr (mess);
1333 if (hard)
1334 putchar ('\n');
1335 fflush(stdout);
1336 errors++;
1337 longjmp (restore, 1);
1338}
1339
1340rdline (f)
1341register FILE *f;
1342{
1343 register char c;
1344 register char *p;
1345
1346 p = Line;
1347 while ((c = getc (f)) != '\n' && c != EOF && p - Line < LINSIZ - 1)
1348 *p++ = c;
1349 *p = '\0';
1350}