home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 October / usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso / unix / volume9 / fastgrep / part01 < prev    next >
Text File  |  1987-06-15  |  57KB  |  1,746 lines

  1. Path: seismo!uunet!rs
  2. From: rs@uunet.UU.NET (Rich Salz)
  3. Newsgroups: comp.sources.unix
  4. Subject: v09i061:  Fastest grep around, Part01/02
  5. Message-ID: <365@uunet.UU.NET>
  6. Date: 16 Jun 87 23:35:08 GMT
  7. Organization: UUNET Communications Services, Arlington, VA
  8. Lines: 1735
  9. Approved: rs@uunet.uu.net
  10.  
  11. Submitted by: James A. Woods <ames!aurora!jaw>
  12. Mod.sources: Volume 9, Issue 61
  13. Archive-name: fastgrep/Part01
  14.  
  15. [  This is the latest posting of the Woods/Spencer super-fast grep.
  16.    It was announced in net.sources a few weeks ago.  You will probably
  17.    want to replace all your other greps with this one.  (Greps?)
  18.    When you unpak this, take careful not of the last few lines of the
  19.    script -- they make some "8-bit kanji" files.  --r$  ]
  20.  
  21. # To unbundle, sh this file
  22. echo Makefile 1>&2
  23. cat > Makefile <<'End of Makefile'
  24. # optional items for ENV:
  25. # -I.            use regexp.h in current directory, not /usr/include.
  26. # -DEGREPOLD=path    location of std egrep (normally /usr/bin/egrep).
  27. # -DGREPOLD=path    location of std grep (normally /bin/grep).
  28. # -DFGREPOLD=path    location of std fgrep (normally /usr/bin/fgrep).
  29. # -Dstrrchr=rindex, -Dstrchr=index    for troglodytes.
  30. # -DSLOWSYS        invoke xread() for system time quirk on PDP, others? 
  31. # -DNOKANJI        default is for Japanese Unix.  undef only for raw
  32. #             parity-marked search capability, not standard w/grep.
  33. # -DCHINESE        for systems using EUC Chinese2 codes
  34.  
  35. ENV= -I.
  36.  
  37. # warning:  do not overwrite existing [ef]?grep family with $BIN path choice
  38. BIN= /usr/local
  39.  
  40. # optional items for OBJ:
  41. # misc.o        for V7 or BSD 4.2 systems w/no getopt(3) or string(3)
  42. #              also contains xread() per above.
  43. # regexp.o        if Henry Spencer's regexp(3) is not installed
  44. #            V8 people -- your regexp.h won't do
  45.  
  46. OBJ= regexp.o
  47.  
  48. CFLAGS= -O $(ENV) 
  49. #CFLAGS= -O -i $(ENV)    uncomment this line for PDP-11
  50.  
  51. regexp:    ; make -f Makefile.regexp r; make egrep
  52.  
  53. egrep:    egrep.o regerror.o
  54.     cc $(CFLAGS) egrep.o regerror.o -o egrep $(OBJ)
  55.     rm -f grep fgrep
  56.     ln egrep grep
  57.     ln egrep fgrep
  58.  
  59. install: 
  60.     rm -f $(BIN)/*grep
  61.     strip egrep
  62.     mv egrep $(BIN)
  63.     ln $(BIN)/egrep $(BIN)/grep
  64.     ln $(BIN)/egrep $(BIN)/fgrep
  65.  
  66. clean:
  67.     rm *.o ./egrep ./grep ./fgrep ./try
  68. End of Makefile
  69. echo README.FIRST 1>&2
  70. cat > README.FIRST <<'End of README.FIRST'
  71. here is the fast 'grep/egrep' package sent to comp.sources and u. c. berkeley.
  72. included are the prerequisite routines developed by henry spencer of
  73. univ. of toronto -- these are also part of the comp.sources archive.
  74.  
  75. i've already updated spencer's care package to incorporate three fixes
  76. which have appeared in the same forum.
  77.  
  78. the makefiles are configured for bsd 4.3 and sys5 unix.
  79. they assume that the spencer regexp() is not already in a system library --
  80. read the makefile comments if this is not the case.
  81.  
  82. for stock 4.3 sites, apply the diff 'diff.egrep.y.bsd' to the existing
  83. source in /usr/src/usr.bin/egrep.y and re-make.  this adds full support
  84. for the -i option.  the procedure is then:
  85.  
  86.     make
  87.     sh eg.shell    # amusement
  88.     make install
  89.     
  90. ames!jaw
  91. End of README.FIRST
  92. echo README.kanji.mods 1>&2
  93. cat > README.kanji.mods <<'End of README.kanji.mods'
  94.      Three areas must be addressed to provide full Kanji compatibility.
  95. Only #1 (for the non-regular expression case) has been implemented
  96. directly in our grep/egrep-compatible Boyer-Moore-based code.
  97.  
  98.     (1) false middle match
  99.  
  100.        (a) meta-free Kanji
  101.        (b) Kanji regexprs
  102.  
  103. Kanji 16-bit "EUC" data codes (see Jung/Kalash, "Yunikkusu wa Nihongo o
  104. Hanasemasu", p. 209, Atlanta Usenix, 1986) have the upper bit on in both
  105. bytes, so as to allow intermixing of ASCII while preserving end-of-string
  106. detection.  'grep' must beware of matching two Kanji byte pairs in
  107. the interior of two unrelated Kanji characters.  e.g.
  108.  
  109.     text:         a (k1 k2) b (k3 k4) (k5 k6)
  110.     pattern:                   (k4   k5)    
  111.  
  112. is a bad match, given ascii bytes 'a' and 'b', and Kanji characters
  113. (k1 k2), (k3 k4), and (k5 k6).  The solution for Kanji grep using
  114. the traditional algorithm might be to anchor the pattern only at
  115. Kanji pair boundaries while scanning forward.
  116.  
  117. Boyer-Moore methods cannot afford this.  So we allow false matches, then
  118. scan backwards for legality (the first ascii byte in the text occurring
  119. before the candidate match disambiguates).  Another appealing method,
  120. for "layered" processing via regexp(3), is to convert the meta-free
  121. Kanji to '(^|[^\000-\177])k1k2', assuming Henry Spencer's code is
  122. "8-bit clean".  Case (b) (e.g. regexprs like 'k1k2.*k3k4') is similar,
  123. though syntax translation may be more difficult.
  124.  
  125.     (2) closures
  126.  
  127.      Eight-bit egrep '(k1k2)*' [where the '*' may be '+' or '?'], would
  128. wrongly apply the closure to the previous byte instead of the byte pair.
  129. One solution (without touching the existing 'regexp(3)' or 'e?grep' source)
  130. is to simply parenthesize reg exprs 'k1k2*' -> '(k1k2)*'.
  131. [only works with egrep syntax, so should occur after the grep->egrep
  132. expr xlation].
  133.  
  134.     (3) character classes
  135.  
  136.        (a) easy case:  [k1k2k3k4k5k6]
  137.  
  138.                -- just map to (k1k2|k3k4|k5k6).
  139.  
  140.        (b) hard:  ranges [k1k2-k3k4]
  141.  
  142. fail for byte-oriented char class code.
  143. Kanji interpretation (how do ideograms collate?) is also problematic.
  144. Translation to egrep '.*((k1k2)|(k1k2++)...|(k3k4)).*', where '++'
  145. denotes "16-bit successor" is conceivable, but farfetched.
  146.  
  147.      Now, translations (1) and (2) may be done [messily] w/o touching
  148. Spencer's code, while (3) could be farmed out to standard Kanji egrep via the
  149. process exec mechanism already established (see pep4grep.doc[123]).
  150. But if (3) were done this way (invoking exec()), then the other cases might
  151. also be done without recourse to the above xlations [just match "regmust"
  152. first, then pass false drops to the Japan Unix std.]  However, r.e.'s handled
  153. in such a manner would make hybrid Boyer-Moore slow for small files, except for
  154. systems running MACH.  We could have ad hoc file size vs. exec() tradeoff
  155. detectors control things for Kanji (it's already done for Anglo exprs), but
  156. previous success has hinged upon having the regexp(3) layer compatible with the
  157. r.e. style of the coarser egrep utility.
  158.  
  159.      Thus we take the easy way out and make fast grep only apply to simple
  160. non-r.e. Kanji.  The very best approach remains modification of proprietary
  161. Kanji egrep to incorporate Boyer-Moore directly, by doing Boyer-Moore on the
  162. buffers first before rescanning with the Kanji r.e. machine.  Someday.
  163.  
  164. -- James A. Woods (ames!jaw)
  165.  
  166. Postscript:  The several articles in the special issue of UNIX Review
  167. (March 1987) have delineated the bewildering variety of codesets
  168. (shifted JIS, HP 15/16, many EUC flavors, etc.).  A late addition to
  169. [ef]?grep Kanji support is capability for intermixed Katakana (SS2).
  170. Full testing on real Kanji files has not been done.  Comments are welcome.
  171. End of README.kanji.mods
  172. echo diff.egrep.y.bsd 1>&2
  173. cat > diff.egrep.y.bsd <<'End of diff.egrep.y.bsd'
  174. 19a20
  175. > #include <ctype.h>
  176. 27a29
  177. > char cmap[256];
  178. 51a54
  179. > int     iflag;
  180. 425a429
  181. >     register int i;
  182. 426a431,433
  183. >     for ( i = 0; i < 256; i++ )
  184. >         cmap[i] = (char) i;
  185. 454a462,467
  186. >         case 'i':
  187. >             iflag++;
  188. >             for ( i = 'A'; i <= 'Z'; i++ )
  189. >                 cmap[i] = (char) tolower ( i );
  190. >             continue;
  191. 483a497,502
  192. >     if ( iflag ) {
  193. >         register char *s;
  194. >         for ( s = input; *s != '\0'; s++ )
  195. >             if ( isupper ( (int)(*s) ) )
  196. >                 *s = (char) tolower ( (int)(*s) );
  197. >     }
  198. 508a528
  199. >     register char *cmapr = cmap;
  200. 544c564
  201. <         cstat = gotofn[cstat][*p&0377]; /* all input chars made positive */
  202. ---
  203. >         cstat = gotofn[cstat][cmapr[*(unsigned char *)p]]; 
  204. End of diff.egrep.y.bsd
  205. echo eg.shell 1>&2
  206. cat > eg.shell <<'End of eg.shell'
  207. DICT=/usr/dict/words    
  208.  
  209. echo "   testing MARK OF ZORRO: time ./egrep astrian $DICT"
  210. time ./egrep astrian $DICT
  211. echo ""
  212. echo "   testing ... AND VICTIM: time /usr/bin/egrep astrian $DICT" 
  213. time /usr/bin/egrep astrian $DICT
  214. echo ""
  215. echo "   testing A CAPITAL IDEA: time egrep -i zurich $DICT"
  216. time ./egrep -i zurich $DICT
  217. echo ""
  218. echo "   testing HOAGY CARMICHAEL: time egrep 'hoe.*g' $DICT"
  219. time ./egrep 'hoe.*g' $DICT
  220. echo ""
  221. echo "   testing NE PLUS ULTRA: grep '+=' egrep.c"
  222. ./grep '+=' ./egrep.c
  223. echo ""
  224. echo "   testing THE JAMES FILES: grep -l 'James'"
  225. ./grep -l James *
  226. echo ""
  227. echo "   testing CLEVER HANS EFFECT: egrep -c count < $DICT"
  228. ./egrep -c count < $DICT
  229. echo ""
  230. echo "   testing NUMBER OF THE BEAST: egrep -n '^[sS]atan$' $DICT"
  231. time ./egrep -n '^[sS]atan$' $DICT
  232. echo ""
  233. echo "   testing STATUS BACK BABY: grep -s 'my.*baby' $DICT"
  234. if ./grep -s 'my.*baby' $DICT
  235. then echo SOMETHING IS WRONG
  236. else echo status OK 
  237. fi
  238. echo ""
  239. echo "   testing PARALLEL FIFTHS: time egrep 'Ae|Ze|Oe|Qe|Xe' $DICT"
  240. time ./egrep 'Ae|Qe|Oe|Xe|Ze' $DICT
  241. echo ""
  242. echo "   testing TEE FOR TWO:  tee < eg.shell | ./egrep TWO"
  243. echo "   (or, short blocks go home)"
  244. tee < eg.shell | ./egrep TWO 
  245. echo ""
  246. echo "   testing HARD-TO-RHYME COLORS:"
  247. echo "        (echo orange; echo silver; echo purple) > colors"
  248. echo "        time ./fgrep -f colors $DICT > /dev/null"
  249. (echo orange; echo silver; echo purple) > colors
  250. time ./fgrep -f colors $DICT > /dev/null
  251. rm colors
  252. echo ""
  253. echo "   testing FAKE KANJI: ./egrep -f kanjipat.fake kanji.fake.test" 
  254. ./egrep -f kanjipat.fake kanji.fake.test | tr -d '\216'
  255. echo ""
  256. echo "   testing NOTHING: ./egrep '' egrep.c" 
  257. ./egrep '' $DICT
  258. echo ""
  259. echo "   testing SPEAK OF THE DEVIL (torture test courtesy Scott Anderson):" 
  260. echo "   or, WIN ALL 32 WITHOUT LAZY EVALUATION" 
  261. echo './egrep "' 'M[ou]'"'"'?am+[ae]r .*([AEae]l[- ])?[GKQ]h?[aeu]+([dtz][dhz]?)+af[iy]"' egad
  262. cat > egad << 'Egad'
  263. 1)  Muammar Qaddafi
  264. 2)  Mo'ammar Gadhafi
  265. 3)  Muammar Kaddafi
  266. 4)  Muammar Qadhafi
  267. 5)  Moammar El Kadhafi
  268. 6)  Muammar Gadafi
  269. 7)  Mu'ammar al-Qadafi
  270. 8)  Moamer El Kazzafi
  271. 9)  Moamar al-Gaddafi
  272. 10) Mu'ammar Al Qathafi
  273. 11) Muammar Al Qathafi
  274. 12) Mo'ammar el-Gadhafi
  275. 13) Moamar El Kadhafi
  276. 14) Muammar al-Qadhafi
  277. 15) Mu'ammar al-Qadhdhafi
  278. 16) Mu'ammar Qadafi
  279. 17) Moamar Gaddafi
  280. 18) Mu'ammar Qadhdhafi
  281. 19) Muammar Khaddafi
  282. 20) Muammar al-Khaddafi
  283. 21) Mu'amar al-Kadafi
  284. 22) Muammar Ghaddafy
  285. 23) Muammar Ghadafi
  286. 24) Muammar Ghaddafi
  287. 25) Muamar Kaddafi
  288. 26) Muammar Quathafi
  289. 27) Muammar Gheddafi
  290. 28) Muamar Al-Kaddafi
  291. 29) Moammar Khadafy
  292. 30) Moammar Qudhafi
  293. 31) Mu'ammar al-Qaddafi
  294. 32) Mulazim Awwal Mu'ammar Muhammad Abu Minyar al-Qadhafi
  295. Egad
  296. # there are subtle reasons why this odd command is not directly applied
  297. # to a "here document"
  298. time ./egrep "M[ou]'?am+[ae]r .*([AEae]l[- ])?[GKQ]h?[aeu]+([dtz][dhz]?)+af[iy]" egad
  299. rm egad
  300. End of eg.shell
  301. echo egrep.c 1>&2
  302. cat > egrep.c <<'End of egrep.c'
  303.  
  304. /*
  305.      Hybrid Boyer/Moore/Gosper-assisted 'grep/egrep/fgrep' search, with delta0
  306.      table as in original paper (CACM, October, 1977).  No delta1 or delta2.
  307.      According to experiment (Horspool, Soft. Prac. Exp., 1982), delta2 is of
  308.      minimal practical value.  However, to improve for worst case input,
  309.      integrating the improved Galil strategies (Apostolico/Giancarlo, SIAM. J.
  310.      Comput., Feb. 1986) deserves consideration.
  311.  
  312.      Method:     extract longest metacharacter-free string from expression.
  313.         this is done using a side-effect from henry spencer's regcomp().
  314.         use boyer-moore to match such, then pass submatching lines
  315.         to either regexp() or standard 'egrep', depending on certain
  316.         criteria within execstrategy() below.  [this tradeoff is due
  317.         to the general slowness of the regexp() nondeterministic
  318.         machine on complex expressions, as well as the startup time
  319.         of standard 'egrep' on short files.]  alternatively, one may
  320.         change the vendor-supplied 'egrep' automaton to include
  321.         boyer-moore directly.  see accompanying writeup for discussion
  322.         of kanji expression treatment.
  323.  
  324.         late addition:  apply trickbag for fast match of simple
  325.         alternations (sublinear, in common low-cardinality cases).
  326.         trap fgrep into this lair.
  327.  
  328.         gnu additions:  -f, newline as |, \< and \> [in regexec()], more
  329.                 comments.  inspire better dfa exec() strategy.
  330.                 serious testing and help with special cases.
  331.  
  332.      Algorithm amalgam summary:
  333.  
  334.         dfa e?grep         (aho/thompson)
  335.         ndfa regexp()         (spencer/aho)
  336.         bmg            (boyer/moore/gosper)
  337.         "superimposed" bmg       (jaw)
  338.         fgrep            (aho/corrasick)
  339.  
  340.         sorry, but the knuth/morris/pratt machine, horspool's
  341.         "frequentist" code, and the rabin/karp matcher, however cute,
  342.         just don't cut it for this production.
  343.  
  344.      James A. Woods                Copyright (c) 1986
  345.      NASA Ames Research Center
  346. */
  347. #include <stdio.h>
  348. #include <ctype.h>
  349. #include <sys/types.h>
  350. #include <sys/stat.h>
  351. #include <regexp.h>        /* must be henry spencer's version */
  352.  
  353. #define    MIN(A, B)    ((A) > (B) ? (B) : (A))
  354.  
  355. #ifdef    SLOWSYS
  356. #define read    xread
  357. #endif
  358. /*
  359.  * define existing [ef]?grep program locations below for use by execvp().
  360.  * [execlp() would be used were it not for the possibility of
  361.  * installation-dependent recursion.] 
  362.  */
  363. #ifndef EGREPSTD
  364. #define    EGREPSTD    "/usr/bin/egrep"
  365. #endif
  366. #ifndef GREPSTD
  367. #define    GREPSTD        "/bin/grep"
  368. #endif
  369. #ifndef FGREPSTD
  370. #define    FGREPSTD    "/usr/bin/fgrep"
  371. #endif
  372.  
  373. #define BUFSIZE    8192        /* make higher for cray */
  374. #define PATSIZE 6000
  375. #define LARGE     BUFSIZE + PATSIZE
  376.  
  377. #define ALTSIZE    100        /* generous? */
  378. #define NALT    7        /* tied to scanf() size in alternate() */
  379. #define NMUSH    6        /* loosely relates to expected alt length */
  380.  
  381. #define    FIRSTFEW    10    /* Always do FIRSTFEW matches with regexec() */
  382. #define    PUNTPERCENT    5    /* After FIRSTFEW, if PUNTPERCENT of the input
  383.                  * was processed by regexp(), exec std egrep. */
  384. #define NL    '\n'
  385. #define    EOS    '\0'
  386. #define    NONASCII    0200    /* Bit mask for Kanji non-ascii chars */
  387. #define META    "\n^$.[]()?+*|\\"    /* egrep meta-characters */
  388. #define SS2    '\216'        /* EUC Katakana (or Chinese2) prefix */
  389. #define SS3    '\217'        /* EUC Kanji2 (or Chinese3) prefix */
  390.  
  391. extern char *optarg;
  392. extern int optind;
  393. char *progname;
  394.  
  395. int cflag, iflag, eflag, fflag, lflag, nflag;    /* SVID flags */
  396. int sflag, hflag;        /* v7, v8, bsd */
  397.  
  398. int firstflag;            /* Stop at first match */
  399. int grepflag;            /* Called as "grep" */
  400. int fgrepflag;            /* Called as "fgrep" */
  401. int altflag;            /* Simple alternation in pattern */
  402. int boyonly;            /* No regexp needed -- all simple */
  403. int flushflag;
  404. int grepold, egrepold, fgrepold;
  405.  
  406. int nalt;            /* Number of alternatives */
  407. int nsuccess;            /* 1 for match, 2 for error */
  408. int altmin;            /* Minimum length of all the alternate
  409.                  * strings */
  410. int firstfile;            /* argv index of first file argument */
  411. long nmatch;            /* Number of matches in this file */
  412. long incount, counted;        /* Amount of input consumed */
  413. long rxcount;            /* Bytes of input processed by regexec() */
  414. int boyfound;            /* accumulated partial matches (tripped by
  415.                  * FIRSTFEW) */
  416. int prevmatch;            /* next three lines aid fast -n */
  417. long nline, prevnline;
  418. char *prevloc;
  419.  
  420. regexp *rspencer;
  421. char *pattern;
  422. char *patboy;            /* Pattern for simple Boyer-Moore */
  423. char *patfile;            /* Filename containing pattern(s) */
  424.  
  425. int delta0[256];        /* Boyer-Moore algorithm core */
  426. char cmap[256];            /* Usually 0-255, but if -i, maps upper to
  427.                  * lower case */
  428. char str[BUFSIZE + 2];
  429. int nleftover;
  430. char linetemp[BUFSIZE];
  431. char altpat[NALT][ALTSIZE];    /* alternation component storage */
  432. int altlen[NALT];
  433. short altset[NMUSH + 1][256];
  434. char preamble[200];        /* match prefix (filename, line no.) */
  435.  
  436. int fd;
  437. char *
  438. strchr(), *strrchr(), *strcpy(), *strncpy(), *strpbrk(), *sprintf(), *malloc();
  439. char *
  440. grepxlat(), *fold(), *pfile(), *alternate(), *isolate();
  441. char **args;
  442.  
  443. main(argc, argv)
  444.     int argc;
  445.     char *argv[];
  446. {
  447.     int c;
  448.     int errflag = 0;
  449.  
  450.     args = argv;
  451.  
  452.     if ((progname = strrchr(argv[0], '/')) != 0)
  453.         progname++;
  454.     else
  455.         progname = argv[0];
  456.     if (strcmp(progname, "grep") == 0)
  457.         grepflag++;
  458.     if (strcmp(progname, "fgrep") == 0)
  459.         fgrepflag++;
  460.  
  461.     while ((c = getopt(argc, argv, "bchie:f:lnsvwxy1")) != EOF) {
  462.         switch (c) {
  463.  
  464.         case 'f':
  465.             fflag++;
  466.             patfile = optarg;
  467.             continue;
  468.         case 'b':
  469.         case 'v':
  470.             egrepold++;    /* boyer-moore of little help here */
  471.             continue;
  472.         case 'c':
  473.             cflag++;
  474.             continue;
  475.         case 'e':
  476.             eflag++;
  477.             pattern = optarg;
  478.             continue;
  479.         case 'h':
  480.             hflag++;
  481.             continue;
  482.         case '1':    /* Stop at very first match */
  483.             firstflag++;    /* spead freaks only */
  484.             continue;
  485.         case 'i':
  486.             iflag++;
  487.             continue;
  488.         case 'l':
  489.             lflag++;
  490.             continue;
  491.         case 'n':
  492.             nflag++;
  493.             continue;
  494.         case 's':
  495.             sflag++;
  496.             continue;
  497.         case 'w':
  498.         case 'y':
  499.             if (!grepflag)
  500.                 errflag++;
  501.             grepold++;
  502.             continue;
  503.         case 'x':    /* needs more work, like -b above */
  504.             if (!fgrepflag)
  505.                 errflag++;
  506.             fgrepold++;
  507.             continue;
  508.         case '?':
  509.             errflag++;
  510.         }
  511.     }
  512.     if (errflag || ((argc <= optind) && !fflag && !eflag)) {
  513.         if (grepflag)
  514. oops("usage: grep [-bcihlnsvwy] [-e] pattern [file ...]");
  515.         else if (fgrepflag)
  516. oops("usage: fgrep [-bcilnv] {-f patfile | [-e] strings} [file ...]");
  517.         else        /* encourage SVID options, though we provide
  518.                  * others */
  519. oops("usage: egrep [-bcilnv] {-f patfile | [-e] pattern} [file ...]");
  520.     }
  521.     if (fflag)
  522.         pattern = pfile(patfile);
  523.     else if (!eflag)
  524.         pattern = argv[optind++];
  525.  
  526.     if ((argc - optind) <= 1)    /* Filename invisible given < 2 files */
  527.         hflag++;
  528.     if (pattern[0] == EOS)
  529.         oops("empty expression");
  530.     /*
  531.      * 'grep/egrep' merger -- "old" grep is called to handle: tagged
  532.      * exprs \( \), word matches \< and \>, -w and -y options, char
  533.      * classes with '-' at end (egrep bug?), and patterns beginning with
  534.      * an asterisk (don't ask why). otherwise, characters meaningful to
  535.      * 'egrep' but not to 'grep' are escaped; the entire expr is then
  536.      * passed to 'egrep'. 
  537.      */
  538.     if (grepflag && !grepold) {
  539.         if (strindex(pattern, "\\(") >= 0 ||
  540.             strindex(pattern, "\\<") >= 0 ||
  541.             strindex(pattern, "\\>") >= 0 ||
  542.             strindex(pattern, "-]") >= 0 ||
  543.             pattern[0] == '*')    /* grep bug */
  544.             grepold++;
  545.         else
  546.             pattern = grepxlat(pattern);
  547.     }
  548.     if (grepold || egrepold || fgrepold)
  549.         kernighan(argv);
  550.  
  551.     if (iflag)
  552.         strcpy(pattern, fold(pattern));
  553.     /*
  554.      * If the pattern is a plain string, just run boyer-moore. If it
  555.      * consists of meta-free alternatives, run "superimposed" bmg.
  556.      * Otherwise, find best string, and compile pattern for regexec(). 
  557.      */
  558.     if (strpbrk(pattern, META) == NULL) {    /* do boyer-moore only */
  559.         boyonly++;
  560.         patboy = pattern;
  561.     } else {
  562.         if ((patboy = alternate(pattern)) != NULL)
  563.             boyonly++;
  564.         else {
  565.             if ((patboy = isolate(pattern)) == NULL)
  566.                 kernighan(argv);    /* expr too involved */
  567. #ifndef NOKANJI
  568.             for (c = 0; pattern[c] != EOS; c++)
  569.                 if (pattern[c] & NONASCII)    /* kanji + meta */
  570.                     kernighan(argv);
  571. #endif
  572.             if ((rspencer = regcomp(pattern)) == NULL)
  573.                 oops("regcomp failure");
  574.         }
  575.     }
  576.     gosper(patboy);        /* "pre-conditioning is wonderful"
  577.                  * -- v. strassen */
  578.  
  579.     if ((firstfile = optind) >= argc) {
  580.         /* Grep standard input */
  581.         if (lflag)    /* We don't know its name! */
  582.             exit(1);
  583.         egsecute((char *) NULL);
  584.     } else {
  585.         while (optind < argc) {
  586.             egsecute(argv[optind]);
  587.             optind++;
  588.             if (firstflag && (nsuccess == 1))
  589.                 break;
  590.         }
  591.     }
  592.     exit((nsuccess == 2) ? 2 : (nsuccess == 0));
  593. }
  594.  
  595. char *
  596. pfile(pfname)            /* absorb expression from file */
  597.     char *pfname;
  598. {
  599.     FILE *pf;
  600.     struct stat patstat;
  601.     static char *pat;
  602.  
  603.     if ((pf = fopen(pfname, "r")) == NULL)
  604.         oops("can't read pattern file");
  605.     if (fstat(fileno(pf), &patstat) != 0)
  606.         oops("can't stat pattern file");
  607.     if (patstat.st_size > PATSIZE) {
  608.         if (fgrepflag) {    /* defer to unix version */
  609.             fgrepold++;
  610.             return "dummy";
  611.         } else
  612.             oops("pattern file too big");
  613.     }
  614.     if ((pat = malloc((unsigned) patstat.st_size + 1)) == NULL)
  615.         oops("out of memory to read pattern file");
  616.     if (patstat.st_size !=
  617.         fread(pat, sizeof(char), patstat.st_size + 1, pf))
  618.         oops("error reading pattern file");
  619.     (void) fclose(pf);
  620.  
  621.     pat[patstat.st_size] = EOS;
  622.     if (pat[patstat.st_size - 1] == NL)    /* NOP for egrep; helps grep */
  623.         pat[patstat.st_size - 1] = EOS;
  624.  
  625.     if (nlcount(pat, &pat[patstat.st_size]) > NALT) {
  626.         if (fgrepflag)
  627.             fgrepold++;    /* "what's it all about, alfie?" */
  628.         else
  629.             egrepold++;
  630.     }
  631.     return (pat);
  632. }
  633.  
  634. egsecute(file)
  635.     char *file;
  636. {
  637.     if (file == NULL)
  638.         fd = 0;
  639.     else if ((fd = open(file, 0)) <= 0) {
  640.         fprintf(stderr, "%s: can't open %s\n", progname, file);
  641.         nsuccess = 2;
  642.         return;
  643.     }
  644.     chimaera(file, patboy);
  645.  
  646.     if (!boyonly && !flushflag && file != NULL)
  647.         flushmatches();
  648.     if (file != NULL)
  649.         close(fd);
  650. }
  651.  
  652. chimaera(file, pat)        /* "reach out and boyer-moore search someone" */
  653.     char *file, *pat;    /* -- soon-to-be-popular bumper sticker */
  654. {
  655.     register char *k, *strend, *s;
  656.     register int j, count;
  657.     register int *deltazero = delta0;
  658.     int patlen = altmin;
  659.     char *t;
  660.     char *gotamatch(), *kanji(), *linesave(), *submatch();
  661.  
  662.     nleftover = boyfound = flushflag = 0;
  663.     nline = 1L;
  664.     prevmatch = 0;
  665.     nmatch = counted = rxcount = 0L;
  666.  
  667.     while ((count = read(fd, str + nleftover, BUFSIZE - nleftover)) > 0) {
  668.  
  669.         counted += count;
  670.         strend = linesave(str, count);
  671.  
  672.         for (k = str + patlen - 1; k < strend;) {
  673.             /*
  674.              * for a large class of patterns, upwards of 80% of
  675.              * match time is spent on the next line.  we beat
  676.              * existing microcode (vax 'matchc') this way. 
  677.              */
  678.             while ((k += deltazero[*(unsigned char *) k]) < strend);
  679.             if (k < (str + LARGE))
  680.                 break;
  681.             k -= LARGE;
  682.  
  683.             if (altflag) {
  684.                 /*
  685.                  * Parallel Boyer-Moore.  Check whether each
  686.                  * of the previous <altmin> chars COULD be
  687.                  * from one of the alternative strings. 
  688.                  */
  689.                 s = k - 1;
  690.                 j = altmin;
  691.                 while (altset[--j][(unsigned char)
  692.                          cmap[*(unsigned char *) s--]]);
  693.                 /*
  694.                  * quick test fails. in this life, compare
  695.                  * 'em all.  but, a "reverse trie" would
  696.                  * attenuate worst case (linear w/delta2?). 
  697.                  */
  698.                 if (--j < 0) {
  699.                     count = nalt - 1;
  700.                     do {
  701.                         s = k;
  702.                         j = altlen[count];
  703.                         t = altpat[count];
  704.  
  705.                         while
  706.                             (cmap[*(unsigned char *) s--]
  707.                              == t[--j]);
  708.                         if (j < 0)
  709.                             break;
  710.                     }
  711.                     while (count--);
  712.                 }
  713.             } else {
  714.                 /* One string -- check it */
  715.                 j = patlen - 1;
  716.                 s = k - 1;
  717.                 while (cmap[*(unsigned char *) s--] == pat[--j]);
  718.             }
  719.             /*
  720.              * delta-less shortcut for literati. short shrift for
  721.              * genetic engineers? 
  722.              */
  723.             if (j >= 0) {
  724.                 k++;    /* no match; restart next char */
  725.                 continue;
  726.             }
  727.             k = submatch(file, pat, str, strend, k, count);
  728.             if (k == NULL)
  729.                 return;
  730.         }
  731.         if (nflag) {
  732.             if (prevmatch)
  733.                 nline = prevnline + nlcount(prevloc, k);
  734.             else
  735.                 nline = nline + nlcount(str, k);
  736.             prevmatch = 0;
  737.         }
  738.         strncpy(str, linetemp, nleftover);
  739.     }
  740.     if (cflag) {
  741.         /* Bug from old grep: -c overrides -h.  We fix the bug. */
  742.         if (!hflag)
  743.             printf("%s:", file);
  744.         printf("%ld\n", nmatch);
  745.     }
  746. }
  747.  
  748. char *
  749. linesave(str, count)        /* accumulate partial line at end of buffer */
  750.     char str[];
  751.     register int count;
  752. {
  753.     register int j;
  754.  
  755.     count += nleftover;
  756.     if (count != BUFSIZE && fd != 0)
  757.         str[count++] = NL;    /* insurance for broken last line */
  758.     str[count] = EOS;
  759.     for (j = count - 1; str[j] != NL && j >= 0;)
  760.         j--;
  761.     /*
  762.      * break up these lines: long line (> BUFSIZE), last line of file, or
  763.      * short return from read(), as from tee(1) input 
  764.      */
  765.     if (j < 0 && (count == (BUFSIZE - nleftover))) {
  766.         str[count++] = NL;
  767.         str[count] = EOS;
  768.         linetemp[0] = EOS;
  769.         nleftover = 0;
  770.         return (str + count);
  771.     } else {
  772.         nleftover = count - j - 1;
  773.         strncpy(linetemp, str + j + 1, nleftover);
  774.         return (str + j);
  775.     }
  776. }
  777.  
  778. /*
  779.  * Process partial match. First check for mis-aligned Kanji, then match line
  780.  * against full compiled r.e. if statistics do not warrant handing off to
  781.  * standard egrep. 
  782.  */
  783. char *
  784. submatch(file, pat, str, strend, k, altindex)
  785.     char file[], pat[], str[];
  786.     register char *strend, *k;
  787.     int altindex;
  788. {
  789.     register char *s;
  790.     char *t, c;
  791.  
  792.     t = k;
  793.     s = ((altflag) ? k - altlen[altindex] + 1 : k - altmin + 1);
  794. #ifndef NOKANJI
  795.     c = ((altflag) ? altpat[altindex][0] : pat[0]);
  796.     if (c & NONASCII)
  797.         if ((s = kanji(str, s, k)) == NULL)
  798.             return (++k);    /* reject false kanji */
  799. #endif
  800.     do;
  801.     while (*s != NL && --s >= str);
  802.     k = s + 1;        /* now at line start */
  803.  
  804.     if (boyonly)
  805.         return (gotamatch(file, k));
  806.  
  807.     incount = counted - (strend - k);
  808.     if (boyfound++ == FIRSTFEW)
  809.         execstrategy(file);
  810.  
  811.     s = t;
  812.     do
  813.         rxcount++;
  814.     while (*s++ != NL);
  815.     *--s = EOS;
  816.     /*
  817.      * "quick henry -- the flit" (after theodor geisel) 
  818.      */
  819.     if (regexec(rspencer, ((iflag) ? fold(k) : k)) == 1) {
  820.         *s = NL;
  821.         if (gotamatch(file, k) == NULL)
  822.             return (NULL);
  823.     }
  824.     *s = NL;
  825.     return (s + 1);
  826. }
  827.  
  828. /*
  829.  * EUC code disambiguation -- scan backwards to first 7-bit code, while
  830.  * counting intervening 8-bit codes.  If odd, reject unaligned Kanji pattern. 
  831.  * SS2/3 checks are for intermixed Japanase Katakana or Kanji2. 
  832.  */
  833. char *
  834. kanji(str, s, k)
  835.     register char *str, *s, *k;
  836. {
  837.     register int j = 0;
  838.  
  839.     for (s--; s >= str; s--) {
  840.         if (*s == SS2 || *s == SS3 || (*s & NONASCII) == 0)
  841.             break;
  842.         j++;
  843.     }
  844. #ifndef CHINESE
  845.     if (*s == SS2)
  846.         j -= 1;
  847. #endif  CHINESE
  848.     return ((j & 01) ? NULL : k);
  849. }
  850.  
  851. /*
  852.  * Compute "Boyer-Moore" delta table -- put skip distance in delta0[c] 
  853.  */
  854. gosper(pattern)
  855.     char *pattern;        /* ... HAKMEM lives ... */
  856. {
  857.     register int i, j;
  858.     unsigned char c;
  859.  
  860.     /* Make one-string case look like simple alternatives case */
  861.     if (!altflag) {
  862.         nalt = 1;
  863.         altmin = altlen[0] = strlen(pattern);
  864.         strcpy(altpat[0], pattern);
  865.     }
  866.     /* For chars that aren't in any string, skip by string length. */
  867.     for (j = 0; j < 256; j++) {
  868.         delta0[j] = altmin;
  869.         cmap[j] = j;    /* Sneak in initialization of cmap */
  870.     }
  871.  
  872.     /* For chars in a string, skip distance from char to end of string. */
  873.     /* (If char appears more than once, skip minimum distance.) */
  874.     for (i = 0; i < nalt; i++)
  875.         for (j = 0; j < altlen[i] - 1; j++) {
  876.             c = altpat[i][j];
  877.             delta0[c] = MIN(delta0[c], altlen[i] - j - 1);
  878.             if (iflag && islower((int) c))
  879.                 delta0[toupper((int) c)] = delta0[c];
  880.         }
  881.  
  882.     /* For last char of each string, fall out of search loop. */
  883.     for (i = 0; i < nalt; i++) {
  884.         c = altpat[i][altlen[i] - 1];
  885.         delta0[c] = LARGE;
  886.         if (iflag && islower((int) c))
  887.             delta0[toupper((int) c)] = LARGE;
  888.     }
  889.     if (iflag)
  890.         for (j = 'A'; j <= 'Z'; j++)
  891.             cmap[j] = tolower((int) j);
  892. }
  893.  
  894. /*
  895.  * Print, count, or stop on full match. Result is either the location for
  896.  * continued search, or NULL to stop. 
  897.  */
  898. char *
  899. gotamatch(file, s)
  900.     register char *file, *s;
  901. {
  902.     char *savematch();
  903.     int squirrel = 0;    /* nonzero to squirrel away FIRSTFEW matches */
  904.  
  905.     nmatch++;
  906.     nsuccess = 1;
  907.     if (!boyonly && boyfound <= FIRSTFEW && file != NULL)
  908.         squirrel = 1;
  909.  
  910.     if (sflag)
  911.         return (NULL);    /* -s usurps all flags (unlike some versions) */
  912.     if (cflag) {        /* -c overrides -l, we guess */
  913.         do;
  914.         while (*s++ != NL);
  915.     } else if (lflag) {
  916.         puts(file);
  917.         return (NULL);
  918.     } else {
  919.         if (!hflag)
  920.             if (!squirrel)
  921.                 printf("%s:", file);
  922.             else
  923.                 sprintf(preamble, "%s:", file);
  924.         if (nflag) {
  925.             if (prevmatch)
  926.                 prevnline = prevnline + nlcount(prevloc, s);
  927.             else
  928.                 prevnline = nline + nlcount(str, s);
  929.             prevmatch = 1;
  930.  
  931.             if (!squirrel)
  932.                 printf("%ld:", prevnline);
  933.             else
  934.                 sprintf(preamble + strlen(preamble),
  935.                     "%ld:", prevnline);
  936.         }
  937.         if (!squirrel) {
  938.             do
  939.                 putchar(*s);
  940.             while (*s++ != NL);
  941.         } else
  942.             s = savematch(s);
  943.  
  944.         if (nflag)
  945.             prevloc = s - 1;
  946.     }
  947.     return ((firstflag && !cflag) ? NULL : s);
  948. }
  949.  
  950. char *
  951. fold(line)
  952.     char *line;
  953. {
  954.     static char fline[BUFSIZE];
  955.     register char *s, *t = fline;
  956.  
  957.     for (s = line; *s != EOS; s++)
  958.         *t++ = (isupper((int) *s) ? (char) tolower((int) *s) : *s);
  959.     *t = EOS;
  960.     return (fline);
  961. }
  962.  
  963. strindex(s, t)            /* the easy way, as in K&P, p. 192 */
  964.     char *s, *t;
  965. {
  966.     int i, n;
  967.  
  968.     n = strlen(t);
  969.     for (i = 0; s[i] != '\0'; i++)
  970.         if (strncmp(s + i, t, n) == 0)
  971.             return (i);
  972.     return (-1);
  973. }
  974.  
  975. char *
  976. grepxlat(pattern)        /* grep pattern meta conversion */
  977.     char *pattern;
  978. {
  979.     register char *p, *s;
  980.     static char newpat[BUFSIZE];
  981.  
  982.     for (s = newpat, p = pattern; *p != EOS;) {
  983.         if (*p == '\\') {    /* skip escapes ... */
  984.             *s++ = *p++;
  985.             if (*p)
  986.                 *s++ = *p++;
  987.         } else if (*p == '[') {    /* ... and char classes */
  988.             while (*p != EOS && *p != ']')
  989.                 *s++ = *p++;
  990.         } else if (strchr("+?|()", *p) != NULL) {
  991.             *s++ = '\\';    /* insert protection */
  992.             *s++ = *p++;
  993.         } else
  994.             *s++ = *p++;
  995.     }
  996.     *s = EOS;
  997.     return (newpat);
  998. }
  999.  
  1000. /*
  1001.  * Test for simple alternation.  Result is NULL if it's not so simple, or is
  1002.  * a pointer to the first string if it is. Warning:  sscanf size is a
  1003.  * fixpoint, beyond which the speedup linearity starts to break down.  In the
  1004.  * wake of the elegant aho/corrasick "trie"-based fgrep, generalizing
  1005.  * altpat[] to arbitrary size is not useful. 
  1006.  */
  1007. char *
  1008. alternate(regexpr)
  1009.     char *regexpr;
  1010. {
  1011.     register i, j, min;
  1012.     unsigned char c;
  1013.     char oflow[100];
  1014.  
  1015.     if (fgrepflag && strchr(regexpr, '|'))
  1016.         return (NULL);
  1017.  
  1018.     i = strlen(regexpr);
  1019.     for (j = 0; j < i; j++)
  1020.         if (regexpr[j] == NL)
  1021.             regexpr[j] = '|';
  1022.  
  1023.     if (!fgrepflag) {
  1024.         if (strchr(regexpr, '|') == NULL || regexpr[0] == '|')
  1025.             return (NULL);
  1026.         if (strpbrk(regexpr, "^$.[]()?+*\\") != NULL
  1027.             || strindex(regexpr, "||") >= 0)
  1028.             return (NULL);
  1029.     }
  1030.     oflow[0] = EOS;
  1031.     nalt = sscanf(regexpr, "%[^|]|%[^|]|%[^|]|%[^|]|%[^|]|%[^|]|%[^|]|%[^|]",
  1032.               altpat[0], altpat[1], altpat[2], altpat[3], altpat[4], altpat[5], altpat[6], oflow);
  1033.  
  1034.     if (nalt < 1 || oflow[0] != EOS)
  1035.         return (NULL);
  1036.  
  1037.     altmin = NMUSH;
  1038.     for (j = 0; j < nalt; j++) {
  1039.         min = altlen[j] = strlen(altpat[j]);
  1040.         if (min > ALTSIZE)
  1041.             return (NULL);
  1042.         altmin = MIN(altmin, min);
  1043.     }
  1044.     if (nalt > 1) {        /* build superimposed "pre-match" sets per
  1045.                  * char */
  1046.         altflag++;
  1047.         for (j = 0; j < nalt; j++)
  1048.             for (i = 0; i < altmin; i++) {
  1049.                 c = altpat[j][altlen[j] - altmin + i];
  1050.                 altset[i + 1][c] = 1;    /* offset for sentinel */
  1051.             }
  1052.     }
  1053.     return (altpat[0]);
  1054. }
  1055.  
  1056. /*
  1057.  * Grapple with the dfa (std egrep) vs. ndfa (regexp) tradeoff. Criteria to
  1058.  * determine whether to use dfa-based egrep:  We do FIRSTFEW matches with
  1059.  * regexec().  Otherwise, if Boyer-Moore up to now matched more than
  1060.  * PUNTPERCENT of the input, and there is sufficient bulk remaining to
  1061.  * justify justify a process exec, do old *grep, presuming that its greater
  1062.  * speed at regular expressions will pay us back over this volume.  At
  1063.  * FIRSTFEW, dump the saved matches collected by savematch(). They are saved
  1064.  * so that a "PUNT" can "rewind" to ignore them.  Stdin is problematic,
  1065.  * since it's hard to rewind. 
  1066.  */
  1067.  
  1068. #define CTHRESH    50000
  1069.  
  1070. execstrategy(file)
  1071.     char *file;
  1072. {
  1073.     struct stat stbuf;
  1074.     int pctmatch;
  1075.     long cremain;
  1076.  
  1077.     pctmatch = (100 * rxcount) / incount;
  1078.     if (!grepflag && pctmatch > PUNTPERCENT && file != NULL) {
  1079.         fstat(fd, &stbuf);
  1080.         cremain = stbuf.st_size - incount;
  1081.         if (cremain > CTHRESH)
  1082.             kernighan(args);
  1083.     }
  1084.     if (file != NULL)
  1085.         flushmatches();
  1086. }
  1087.  
  1088. nlcount(bstart, bstop)        /* flail interval to totalize newlines. */
  1089.     char *bstart, *bstop;
  1090. {
  1091.     register char *s = bstart;
  1092.     register char *t = bstop;
  1093.     register int count = 0;
  1094.  
  1095.     do {            /* loop unroll for older architectures */
  1096.         if (*t == NL)    /* ... ask ames!jaw for sample code */
  1097.             count++;
  1098.     } while (t-- > s);
  1099.  
  1100.     return (count);
  1101. }
  1102.  
  1103. char *
  1104. isolate(regexpr)        /* isolate longest metacharacter-free string */
  1105.     char *regexpr;
  1106. {
  1107.     char *dummyexpr;
  1108.  
  1109.     /*
  1110.      * We add (.)* because Henry's regcomp only figures regmust if it
  1111.      * sees a leading * pattern.  Foo! 
  1112.      */
  1113.     dummyexpr = malloc((unsigned) strlen(regexpr) + 5);
  1114.     sprintf(dummyexpr, "(.)*%s", regexpr);
  1115.     if ((rspencer = regcomp(dummyexpr)) == NULL)
  1116.         kernighan(args);
  1117.     return (rspencer->regmust);
  1118. }
  1119.  
  1120. char *matches[FIRSTFEW];
  1121. static int mcount = 0;
  1122.  
  1123. char *
  1124. savematch(s)            /* horde matches during statistics gathering */
  1125.     register char *s;
  1126. {
  1127.     char *p;
  1128.     char *start = s;
  1129.     int msize = 0;
  1130.     int psize = strlen(preamble);
  1131.  
  1132.     while (*s++ != NL)
  1133.         msize++;
  1134.     *--s = EOS;
  1135.  
  1136.     p = malloc((unsigned) msize + 1 + psize);
  1137.     strcpy(p, preamble);
  1138.     strcpy(p + psize, start);
  1139.     matches[mcount++] = p;
  1140.  
  1141.     preamble[0] = 0;
  1142.     *s = NL;
  1143.     return (s);
  1144. }
  1145.  
  1146. flushmatches()
  1147. {
  1148.     int n;
  1149.  
  1150.     flushflag = 1;
  1151.     for (n = 0; n < mcount; n++)
  1152.         printf("%s\n", matches[n]);
  1153.     mcount = 0;
  1154. }
  1155.  
  1156. oops(message)
  1157.     char *message;
  1158. {
  1159.     fprintf(stderr, "%s: %s\n", progname, message);
  1160.     exit(2);
  1161. }
  1162.  
  1163. kernighan(args)            /* "let others do the hard part ..." */
  1164.     char *args[];
  1165. {
  1166.     /*
  1167.      * We may have already run grep on some of the files; remove them
  1168.      * from the arg list we pass on.  Note that we can't delete them
  1169.      * totally because the number of file names affects the output
  1170.      * (automatic -h). 
  1171.      */
  1172.     /* better would be fork/exec per punted file -- jaw */
  1173.  
  1174.     while (firstfile && optind > firstfile)
  1175.         args[firstfile++] = "/dev/null";
  1176.  
  1177.     fflush(stdout);
  1178.  
  1179.     if (grepflag)
  1180.         execvp(GREPSTD, args), oops("can't exec old 'grep'");
  1181.     else if (fgrepflag)
  1182.         execvp(FGREPSTD, args), oops("can't exec old 'fgrep'");
  1183.     else
  1184.         execvp(EGREPSTD, args), oops("can't exec old 'egrep'");
  1185. }
  1186. End of egrep.c
  1187. echo k.pat 1>&2
  1188. cat > k.pat <<'End of k.pat'
  1189. buffy|q|XY
  1190. End of k.pat
  1191. echo k.test 1>&2
  1192. cat > k.test <<'End of k.test'
  1193. WXY--this line should *not* print--
  1194. abXY--this line should print, but *not* the previous line--
  1195. XWXY--this line should print--
  1196. XYcd--this line also--
  1197. q--this line too--
  1198. ZWWXYembedded Katakana, unaligned Kanji
  1199. ZWXYembedded Katakana
  1200. End of k.test
  1201. echo pep4grep.doc1 1>&2
  1202. cat > pep4grep.doc1 <<'End of pep4grep.doc1'
  1203. >From postnews Tue Mar 18 18:04:08 1986
  1204. Subject: More Pep for Boyer-Moore Grep (part 1 of 2)
  1205. Newsgroups: net.unix
  1206.  
  1207. #  The chief defect of Henry King
  1208.    Was chewing little bits of string.
  1209.  
  1210.     -- Hilaire Belloc, Cautionary Tales [1907]
  1211.  
  1212. #  Attempt the end, and never stand to doubt
  1213.    Nothing's so hard but search will find it out.
  1214.  
  1215.     -- Robert Herrick, Hesperides [1648]
  1216.  
  1217.      The world does not need another 'grep' variant.  And so, what is this
  1218. we offer?  On the surface, the exact same 'egrep' actually, but underneath,
  1219. a swift Boyer-Moore hybrid, in C, which can beat assembler versions utilizing
  1220. microcoded string search instructions.  The offering, designed in the
  1221. Kernighanian sense to utilize the existing 'egrep' when it must, also
  1222. makes use of Mr. Henry Spencer's regexp(3) functions in an unusual way.
  1223. For the edification of those without on-line access to system source code,
  1224. the vendor-supplied 'egrep' is left in a pristine state.
  1225.  
  1226.      With code now wending its way to mod.sources, we obtain the following
  1227. results.  Times (in seconds) are all measured on a VAX 11/750 system running
  1228. BSD 4.2 on Fujitsu Eagles, although our 'egrep' has been run on the Sun 2,
  1229. V7 Unix/PDP 11, Vaxen configured with System V, and, for added effect, the
  1230. NASA Ames Cray 2.
  1231.  
  1232.             200K bytes       user   sys    notes
  1233.  
  1234.   (new) egrep  astrian /usr/dict/words     0.4    0.5    implementation by "jaw"
  1235.     match      "           "         0.5    0.5    VAX-only (Waterloo)
  1236.     bm      "           "         1.1    0.6    Peter Bain's version 2
  1237.   (old) egrep     "           "      5.6    1.7    standard    
  1238.  
  1239. [note:  the output here is the single word "Zoroastrian".]
  1240.  
  1241.      Aha, you quip -- this is all very fine for the 99 and 44/100's percent
  1242. metacharacter-free world, but what about timing for shorter strings, character
  1243. folding, as well as for the more interesting universe of extended regular 
  1244. expressions?  Samples forthwith.  (Egrep below refers to the new one, times for
  1245. the /usr/bin code being about the same as above on most any pattern.)
  1246.  
  1247.     egrep      zurich        0.4  0.5    0 words output
  1248.     egrep -i zuRich      0.4  0.5    1 
  1249.     egrep -i zeus          0.6  0.6    1
  1250.     egrep -i zen          0.7  0.6    11
  1251.     bm      zen          2.2  0.6    10
  1252.     egrep      ZZ          0.8  0.6    0
  1253.     bm      ZZ          3.0  0.7    0
  1254.     egrep -c Z          1.5  0.6    19
  1255.     bm -c      Z          5.9  0.7    19
  1256.  
  1257. Admittedly, most people (or programs) don't search for single characters,
  1258. where Boyer-Moore is a bit slow, but it's important for the layered regular
  1259. expression approach described herein.  We might point out from the above that
  1260. the popular "fold" option crippled by 'bm' costs little; it's only a slight
  1261. adjustment of the precomputed "delta" table as well as a single character
  1262. array reference in a secondary loop.  Why has Bain claimed complexity for this?
  1263. Also, the times show that the inner loop chosen for our code (modeled after
  1264. the original speedup done by Boyer-Moore for the PDP 10) consistently betters
  1265. the "blindingly fast" version by a factor of two to three.  The tipoff was
  1266. from previous paper studies (esp. Horspool, see header notes in code) noting
  1267. that the algorithm should, when implemented efficiently, best typical microcode.
  1268. Now it does. 
  1269.  
  1270.     while ( (k += delta0 ( *k )) < strend )
  1271.         ;        /* over 80% of time spent here */
  1272.  
  1273. is the key (modulo precomputation tricks), and takes but three or four
  1274. instructions on most machines.
  1275.  
  1276.      Basic method for regular expressions:
  1277.  
  1278.     (1) isolate the longest metacharacter-free pattern string via the
  1279.         "regmust" field provided by H. Spencer's regcomp() routine.
  1280.  
  1281.         (Non-kosher, but worth not re-inventing the wheel.
  1282.         v8 folks just might have to reverse-engineer Spencer's
  1283.         reverse-engineering to provide equivalent functionality.
  1284.         You see, there are many more sites running his code than v8.
  1285.         Besides, we enjoy using regexpr technology on itself.
  1286.  
  1287.     (2) for "short" input, submatching lines are passed to regexec().
  1288.  
  1289.     (3) for "long" input, start up a standard 'egrep' process via
  1290.         popen() or equivalent.  Why not just use regexec()?  Unfortunately
  1291.         for our application, Spencer's otherwise admirable finite-state
  1292.         automaton exhibits poor performance for complex expressions.
  1293.         Setting a threshold on input length, though not perfect, helps.
  1294.         If pipes on Unix were free, we'd use this way exclusively.
  1295.         Until then, we buy happiness for those who might
  1296.  
  1297.             egrep stuff /usr/spool/news/net/unix/*
  1298.  
  1299.         or on other directories full of short files.
  1300.  
  1301. [note: the details of (3) have changed in the re-release -- see pep4grep.doc3]
  1302.  
  1303. So,
  1304.     [new] egrep -i 'hoe.*g' words        1.2  1.1
  1305.                          {shoestring,Shoenberg}
  1306.     [new] egrep '(a|b).*zz.*[od]$'    words     1.5  1.1
  1307.                          {blizzard,buzzword,palazzo}
  1308.     [old] egrep                 6.3  1.4
  1309. but,
  1310.     {new,old} egrep -c 'first|second'    similar times (no isolate)
  1311.  
  1312. Again, we stress that given the different nature of the simulations of the two
  1313. nondeterministic reg. expr. state-machines (one functionless), cases can be
  1314. "cooked" to show things in a bad light, so a hybrid is warranted.
  1315. We can generally do better incorporating the Boyer-Moore algorithm directly
  1316. into the AT&T code.  For the last example, the abstraction
  1317.  
  1318.     (egrep first words &; egrep second words) | sort -u | wc
  1319.  
  1320. ideally would work better on a parallel machine, but if you're expecting
  1321. something as amazing in this draft as, say, Morwen B. Thistlethwaite's 52-move
  1322. Rubik's Cube solution, you're in the wrong place.
  1323.  
  1324. [note: but see pep4grep.doc3 -- now [ef]?grep handles some parallelism fast]
  1325.  
  1326.      About options -- the SVID ones are supported (-c, -l, bonus -i for BSD),
  1327. and -s and -h works as for BSD and v8.  Note: the 'egrep' here just hands off
  1328. patterns to the old code for things like -n, -b, -v, and multiple patterns.
  1329. As a bone to throw to the enemies of the cat-v school, there is a -1 flag
  1330. (halt after printing first match), but we don't talk about it much.
  1331. Multiple patterns can done ala 'bm' but laziness in the presence of lack of
  1332. knowledge of where 'fgrep' wins has prevailed for version 1.
  1333.  
  1334.      Personally I feel that adapting ("internationalizing") the 'egrep' effort
  1335. for two-byte Kanji is FAR more important than tweeking options or tradeoffs,
  1336. so for you large-alphabet Boyer-Moore algorithm specialists, send ideas
  1337. this way.
  1338.      
  1339.      Further historical/philosophical comments follow in the sequel.
  1340.  
  1341.      James A. Woods (ames!jaw)
  1342.      NASA Ames Research Center
  1343.  
  1344. End of pep4grep.doc1
  1345. echo pep4grep.doc2 1>&2
  1346. cat > pep4grep.doc2 <<'End of pep4grep.doc2'
  1347. >From postnews Tue Mar 18 18:05:22 1986
  1348. Subject: More Pep for Boyer-Moore Grep (part 2 of 2)
  1349. Newsgroups: net.unix
  1350.  
  1351. #  "Gratiano speaks an infinite deal of nothing, more than any man in all
  1352.    of Venice.  His reasons are as two grains of wheat hid in two bushels of
  1353.    chaff:  you shall seek all day ere you find them, they are not worth
  1354.    the search."  -- Shakespeare, Merchant of Venice
  1355.  
  1356. ... or, part 2, "Reach out and Boyer-Moore Egrep Someone"
  1357.  
  1358.      Maybe you never use 'grep'.  Then ignore this.  But if you do, why not
  1359. use the best algorithm?  Serious addicts know that for unstructured yet
  1360. stable text, B-trees are used for speed, or something like Lesk's nifty
  1361. (and unavailable) 'grab' suite for inverted files are ways to go.  Barring file
  1362. inversion daemons for netnews and other ephemera, we are limited to the
  1363. present improvements.
  1364.  
  1365.      Proper skeptics should question why a nearly I/O-bound program (but
  1366. not for any CPU with less than the power of several VAX MIPS, alas) should
  1367. be made more so.  The question was posed in B & M's classic 1978 CACM
  1368. paper -- the answer then was to free up more CPU cycles for timesharing.
  1369. Now, our motivations are more mundane (we won't have desktop 5 MIP machines
  1370. for another year), but not only that, we've discovered that the Cray 2's
  1371. standard 'egrep' is also very anemic, performing 8-12 times as worse as ours
  1372. on simple patterns.  For shame, especially since hearing of the rumor that
  1373. certain group theorists have a search application ready for testing.
  1374. Boyer-Moore could fill in until a Cray vectorizing C compiler shows up.
  1375. Sheer speed for machines whose filesystems are cached in memory is nice too.
  1376.  
  1377.      A quick-and-dirty rundown of the debts to which the new hybrid pays
  1378. now follows.
  1379.  
  1380.     Thompson, K. T. (CACM, November 1968):
  1381.         Regular Expression Search Algorithm.  As usual, obvious
  1382.         once you understand it.  The current 'egrep'.  Still
  1383.         useful as a base.  Abstracted by Aho/Ullman as Algorithm
  1384.         9.1 in Design and Analysis of Computer Algorithms.
  1385.  
  1386.     Boyer/Moore:
  1387.         Not quite pre-Unix.  Oh well.  Modern designers should
  1388.         know better now, if they want their stuff to get out there.
  1389.         By the way, I haven't used delta2 (or 1) since the O(mn) case
  1390.         case doesn't come up too often.  Sure Knuth stood on his head
  1391.         to better the linearity, but his proof had a bug in it until
  1392.         the 1980 SIAM J. Comput. retraction.  Would you want to code
  1393.         something that even Knuth trips up on?
  1394.  
  1395.          Now to assuage nagging feelings that geneticists might want
  1396.         to search entire libraries of 9000-unit nucleotide protein
  1397.         sequences for ((AGCA|TTGCA).*TGC)|AGCT)?T?A+ or some nonsense
  1398.         which MIGHT be nonlinear, you would want delta2.  So convince
  1399.         someone to do the Galil/Apostolico/Giancarlo 2n comparison
  1400.         worst case stuff.  See egrep.c for reference.
  1401.         
  1402.     Gosper, W. (HAKMEM 1972):
  1403.         Gosper didn't get around to the Thompson-like machine until
  1404.         1972 with HAKMEM.  His PDP 10 code is nevertheless valiant.
  1405.         He is also (barely) credited with conceiving the backwards
  1406.         match idea independently.  Where is he now?
  1407.         
  1408.     Morris/Pratt:
  1409.         Nice guys, but for this purpose, has-beens.
  1410.         Neat to see a hacker's triumph bury some theory.
  1411.  
  1412.     Horspool (Software Practice & Experience, 1980):
  1413.         Now here's a Canadian after the heart of things
  1414.         (perfect hashing, text compression, NP-complete
  1415.         code generation probs., etc.)  Did some Amdahl
  1416.         timings to show that delta2 is not so hot.
  1417.         Knows about Search For Least Frequent Character First,
  1418.         which is useful for short patterns. 
  1419.  
  1420.     {,e,f}grep man page:
  1421.         The laughable bugnote "but we do not know a single algorithm
  1422.         that spans a wide enough range of space-time tradeoffs"
  1423.         certainly presumes that there is no such thing as switching
  1424.         logic.  How the 'grep' family got into a multiple-version
  1425.         mess is probably a Guy Harris story; 'egrep' looks like the
  1426.         winner, as its functionality is pretty much a superset of
  1427.         the other two.  The K & P teaser (p. 105) offers hope for
  1428.         unification, but we see no difference with extant V8 code.
  1429.  
  1430.      "Not cited in the text" -- the sexy randomized Karp/Rabin string searcher
  1431. (Sedgewick, Algorithms, or Karp's Turing Award Lecture), and the ribald classic
  1432. Time Warps, String Edits, and Macromolecules -- The Theory and Practice
  1433. of Sequence Comparison (Kruskal & Sankoff).  Inquire within.
  1434. Thanks for your patience,
  1435.  
  1436.      James A. Woods (ames!jaw)
  1437.      NASA Ames Research Center
  1438.  
  1439. P.S.
  1440.      Current applications for Boyer-Moore code include modification of 
  1441. 'fastfind' for true speed, as well as substring search for 'grab', both
  1442. benefiting from BM-style search thru incrementally-compressed files/indices.
  1443.  
  1444. End of pep4grep.doc2
  1445. echo pep4grep.doc3 1>&2
  1446. cat > pep4grep.doc3 <<'End of pep4grep.doc3'
  1447. Subject: Get Hep to Kanji-Ready Five-Algorithm [ef]?grep
  1448.  
  1449. #  "I need very little,
  1450.       and of that little,
  1451.          I need very little."  -- St. Francis of Assisi
  1452.  
  1453.      Hybrid blitz 'egrep', whose urquell is a veritable chimaera of at least
  1454. five string search techniques, is now further tuned.
  1455.  
  1456.      Posted to USENET (and the mod.sources archive) some months ago, our
  1457. implementation of "plug-compatible" egrep.c has been extended to support:
  1458.  
  1459.     transparent 'grep' expression translation, to bring the speed of
  1460.     hyper-'egrep' to bear upon searches specified via the less capable
  1461.     'grep' syntax.
  1462.  
  1463.     interception of 'fgrep' for alternations of low (<= 7) cardinality,
  1464.     using a novel method of Boyer-Moore table superimposition and
  1465.     pre-computation magic.  the resulting speedup applies also to simple
  1466.     metacharacter-free 'egrep'-style alternations.
  1467.  
  1468.     (the above two improvements are made invisible by linking the
  1469.     grep/egrep/fgrep triumvirate.)
  1470.  
  1471.     a revised strategy of fallback to standard 'egrep' for hard
  1472.     cases, which eliminates costly popen() plumbing in favor of a
  1473.     statistically-based re-exec() dynamic.
  1474.  
  1475.     more complete application of fast match to standard options,
  1476.     including -n line numbering.
  1477.  
  1478.     preparation for Kanji pattern input, based upon parity-marked EUC
  1479.     codes.  new egrep.c is "eight-bit clean".  the fast algorithms
  1480.     unfortunately currently apply only to meta-free patterns and
  1481.     simple alternations; full Kanji regular expression treatment
  1482.     remains problematic.  however, the new code will pass difficult
  1483.     input through to [ef]?grep utilities in the UNIX Japan standard
  1484.     substrate.
  1485.  
  1486.      Kanji capability is perhaps the most important addition, as the
  1487. number of UNIX systems in the Orient proliferate, providing a "new market"
  1488. for Boyer-Moore-style search.  However, actual search efficacy remains
  1489. unknown until the Gaijin author gains feedback from JUNET or points beyond.
  1490. For all we know, Nippon text search utilities may already incorporate
  1491. the methods.  Tests conducted so far have been with artificial Kanji files.
  1492.  
  1493.      In case you are w(o|a)ndering, be reminded that no vendor source
  1494. changes are required to use the code.  It is configured as a turbo-charged
  1495. "front-end" to the existing section one commands, though it is (barely)
  1496. conceivable to adapt things, at a loss in worst-case efficiency, for
  1497. (heaven forfend!) non-Unix systems running C.  And, since we do not offer
  1498. a minimalist grep, do not expect it to run well on minimalist UNIX clones.
  1499.  
  1500.      Below appears a brief timing run on Webster's 2nd wordlist.  Notes
  1501. on implementation changes since original release follow in the next message.
  1502. If you want to skip the rest, fine.  The easy instructions -- download
  1503. from comp.sources [or 'anonymous' ftp of egrep.shar.Z from ames-aurora.arpa
  1504. for the (im)patient], and run:
  1505.  
  1506.     make
  1507.     sh eg.shell    # regression test amusement
  1508.     make install
  1509.  
  1510. after perusing README.FIRST.  Though the bundle in ~ftp/pub at NASA Ames
  1511. Research Center contains prerequisite support from Univ. of Toronto's
  1512. Henry Spencer, we are not re-posting regcomp()/regexec() to comp.sources.
  1513. John Gilmore of the GNU project has a modified regexec(), but it is not
  1514. mandatory for running the new egrep.
  1515.  
  1516.      Contrary to popular belief, old egrep is not I/O bound for large
  1517. large files on most machines.  The new version is.  One sample:
  1518.  
  1519.     time egrep 'u.*nix' /usr/dict/web2    (2.5 MB)
  1520.       (yielding {Coturnix, Pseudophoenix, [Tt]urnix}), shows
  1521.  
  1522.                 user     sys      real     (load ave. < 1)
  1523.  
  1524.     VAX 11/750, 4.3 BSD, Fujitsu Eagles
  1525.        (new)    6.8      3.8       11
  1526.        (old)       70.0      5.5       87
  1527.  
  1528.     Sun 3/160, 3.2 OS, Eagle on SMD
  1529.        (new)    1.7      2.2        5
  1530.        (old)       14.7      1.5       16
  1531.  
  1532.     Cray 2, Sys 5, no disk striping
  1533.        (new)        .93      .11        1
  1534.        (old)       8.07      .21        8
  1535.  
  1536. notes:  New egrep was actually run with -i option, but not old egrep.
  1537. Also, fumbling for three-character residue is not [ef]?grep's forte,
  1538. so the example is conservative.
  1539.  
  1540. Sun 3 has higher sys time for some unknown reason (a guess:  VAX 4.3 kernel
  1541. handles read() calls with oddsize buffers differently?).  Cray 2 reportedly
  1542. does disk I/O at 5-10 megabytes per second, but the architecture/compiler
  1543. is bad at byte addressing -- no cache, no vectors here.  Unfair comparison:
  1544. new egrep on Sun beats old egrep on Cray 2, even with fast Cray I/O!
  1545.  
  1546. Speculation:  the code might be useful on the Macintosh II, even if the Unix
  1547. filesystem (Sys 5?) were to waste 3/4 of the 1 MB/sec SCSI disk bandwidth.
  1548. Mac 2 testers please forward info to ames!jaw.
  1549.  
  1550.      Another metric is inner loop efficiency:
  1551.  
  1552.                 # instructions
  1553.     VAX Berkeley cc            5
  1554.     Sun 68020 3.2 cc        6
  1555.     Stallman's GNU 68020 cc        4
  1556.     MIPS R2000            6
  1557.     Cray 2                   25
  1558.  
  1559. Thanks goes to mips!dce (David Elliott) for his testing time, as well as
  1560. to RMS for two-upping Sun's C compiler.
  1561.  
  1562.      Of course, if you have a Connection Machine dedicated to running their
  1563. admirable full-text keyworder on "in-core" text, you won't need [ef]?grep at
  1564. all.  And, for unindexed text on fine-grained parallel machines, reg. expr.
  1565. search algorithms can be made to run with a lower time bound (see the Hillis
  1566. book).  But if your files are on disk, even a specialized search chip won't help
  1567. once things become I/O or bus limited.  For this reason, vectorization on a
  1568. Cray(ette) would be a bust, though Cray buffs may write the author for other
  1569. scalar speedup ideas...
  1570.  
  1571. [continued]
  1572. End of pep4grep.doc3
  1573. echo pep4grep.doc4 1>&2
  1574. cat > pep4grep.doc4 <<'End of pep4grep.doc4'
  1575. #  How long a time lies in one little word! -- Shakespeare, Richard II, I, iii
  1576.  
  1577. #  Fine words butter no parsnips. -- Southern proverb
  1578.  
  1579.         [ef]?grep Implementation Changes
  1580.  
  1581. 'grep' r.e. translation:
  1582.  
  1583.      To buy speed for the novice 'grep' user who deigns not to learn the
  1584. extended 'egrep' syntax, we translate 'grep' r.e.'s to the 'egrep' superset.
  1585. It is straightforward enough to surround search patterns meaningful to
  1586. 'egrep' but not to 'grep'.  Odd cases include the -w option, not implemented
  1587. in standard 'egrep', the defunct -y option, and "tagged expressions", which
  1588. are done via an exec() of /bin/grep.  Tagged exprs, like
  1589.  
  1590.     grep '\(....\).*\1' /usr/dict/words
  1591.  
  1592. which outputs chaff like "beriberi", "couscous", "hodgepodge", and
  1593. "lightweight", are weird.  The irregularity these exprs lend coupled with
  1594. a low complexity/utility ratio kept them from being part of 'egrep'.
  1595. But for this feature, old 'grep' code could be thrown away.
  1596.  
  1597. 'fgrep' improvement / (partial) unification:
  1598.  
  1599.      In the new release, we trap low-complexity disjunctions such as
  1600.  
  1601.         egrep 'boyer|moore' file
  1602. or
  1603.         fgrep 'boyer\n
  1604.         moore' file
  1605.  
  1606. (or with "-f patfile" in place of the pattern) with a method to superimpose
  1607. the non-terminals within the Boyer/Moore table.  When scanning text backwards,
  1608. other programming tricks short-circuit some tests against the pattern.
  1609. Sparing further details, which might make for a more formal writeup, it
  1610. suffices to say that although worst-case complexity here is O(Rn) with string
  1611. length 'n', and R == r.e. size, average-case for text is still sublinear.  E.g.
  1612.  
  1613.     egrep 'silver|orange|purple'      # hard-to-rhyme color test in eg.shell
  1614.  
  1615. looks at ~55000 chars in /usr/dict/words, whereas (three) separate invocations
  1616. of egrep on the individual color words make the code look at ~40000 bytes per
  1617. word.  Aho/Corrasick's 'fgrep', in contrast, must look at all 200KB in the
  1618. dictionary.  The elegant "trie" construction within "fgrep" excels, however,
  1619. for medium/large R.  An equally ambitious "reverse trie", supplementing our
  1620. extant "alternation mush", would attenuate worst-case behavior while preserving
  1621. low R speedup.  We save the addition for another day.
  1622.  
  1623.      Since the syntax for [ef]grep is similar, we thought of making egrep
  1624. hand off to fgrep for sufficiently large metacharacter-free R, as there is no
  1625. strong reason to make the user conscious of the separate algorithms.  Certain
  1626. technicalities prevent this.  For one, we are not willing to invent an 'egrep'
  1627. option to inform the code to interpret a file of (say a hundred) word
  1628. alternatives containing some innocent metacharacter, that it is literal
  1629. 'fgrep' input, rather than a closure-containing 'egrep' pattern which would
  1630. otherwise make egrep explode.  More work could be done here.
  1631.  
  1632.      Our motivation?  Is this not all overblown?  Perhaps, but now you can
  1633. build a simple fast "NSA filter", or search for the seven dwarfs at leisure.
  1634. Besides, the final nail needed to be driven into 'bm/match', which tried
  1635. to do parallel match, but actually shuffled things out of order during its
  1636. simplistic block-based scheme.  These programs, part of source archive also,
  1637. are now historical curiosities.
  1638.  
  1639. Kanji egrep:
  1640.  
  1641.      Copious notes are in README.kanji.mods.  The March 1987 Unix Review
  1642. was indispensable for pointing out the embedded "SS2" Katakana pitfalls.
  1643. The modularity of our code as a semi-dependent filter was necessary for this
  1644. exploration, as we have no access to AT&T/Unisoft Kanji code.  Again, JUNET
  1645. or Sigma project people -- please respond with grep war stories or usage notes.
  1646.  
  1647. Worst-case r.e. handling:
  1648.  
  1649.      The first code release elaborately called upon a function mcilroy()
  1650. to pipe partial match output to old egrep for tough expressions, whose
  1651. backtracking might swamp regexp().  Some details of the DFA/NDFA tradeoff
  1652. were discussed in pep4grep.doc[12].  Due largely to feedback from John Gilmore
  1653. of the GNU project, the strategy was revised.  egrep.c function kernighan()
  1654. ("let others do the hard part") now usurps calls to costly popen() by invoking
  1655. exec() on old egrep when necessary.  Rough partial match statistics gathered
  1656. on the fly determine the handoff.  You may revise the time reported previously
  1657. for
  1658.     egrep 'hoe.*g' /usr/dict/words
  1659.  
  1660. from 1.2 user, 1.1 sys seconds (VAX 11/750, 4.3BSD, Fuji disks) to 0.8u, 0.4s.
  1661. For those public-spirited souls who really want to build a PD egrep out of
  1662. what we offer, sans fallback from regexp() to an AT&T /usr/bin/egrep, the
  1663. slippery test "egrep 'g+h+o' /usr/dict/words" will prove enlightening.
  1664.  
  1665. Faster -n option:
  1666.  
  1667.      By popular demand.  Though Boyer/Moore techniques subvert line numbering,
  1668. we've made it faster with brute force (loop unrolling helps VAXEN, but not
  1669. CRISPS).  Timing tests for this and other options appear in the eg.shell script.
  1670.  
  1671. Not so fast:
  1672.  
  1673.     -v, -b, -w, various r.e.'s with no rexexp() "residue"
  1674.  
  1675. (you'll still have to use the layered "grep host /etc/hosts | grep -w host"
  1676. for speed.)
  1677.  
  1678. Other contra-indications for new [ef]?grep:
  1679.  
  1680.     Monster patterns
  1681.  
  1682.      The amazing expressions output by /usr/lib/calendar still beg for
  1683. the lazy evaluation technique rolled into edition 8 egrep by Prof. Aho of
  1684. Princeton.  Hinted at on p. 105 in Kernighan & Pike, lazy evaluation reduces
  1685. standard egrep r.e. compile time.  Here the possible O(R**2) machine
  1686. construction cost is eliminated to amortize complexity at run-time and 
  1687. shifted to such only if a bad match actually happens.  Whew!  Fortunately,
  1688. this is not so important for simple r.e. fare, where H. Spencer's regexp()
  1689. works well, but it certainly helps calendar(1).
  1690.  
  1691.      The catch with lazy eval. is that it slows down simple matching (15-20%
  1692. for /usr/dict/words on VAX), so it hasn't been adopted by System V egrep.
  1693. Note that our egrep, deferring to the underlying one in /usr/bin, doesn't
  1694. care much about these hideous beasts -- it just doesn't do better on them.
  1695. However, [ef]?grep does well by the Kadhafi matcher (eg.shell, again).
  1696.  
  1697.     Long lines, small alphabets
  1698.  
  1699.      Finally, a comment on one rapidly burgeoning application area
  1700. where new egrep should not be blindly proscribed -- genome sequencing.
  1701. Though line limits have been raised (to 8192 byte buffers), much of
  1702. GENBANK has no newlines.  The code would need modification for scanning
  1703. freestyle.  Also, locating ACGT sequences with the current "superimposed BMG"
  1704. over a 4-letter alphabet might actually be worse, but the global homology
  1705. crowd probably uses a >20 letter protein alphabet (for other reasons).
  1706. At any rate, genetic string search generally relies on more sophisticated
  1707. methods such as dynamic programming ala Sankoff/Kruskal.
  1708.  
  1709.      On the other hand, large alphabets such as Kanji probably help
  1710. performance.  As do parallel transfer disks, MACH file mapping, ...
  1711. Your suggestions welcome.
  1712.  
  1713.      James A. Woods (ames!jaw)
  1714.      NASA Ames Research Center
  1715.  
  1716. P.S.  Preserving author credit, [ef]?grep may be redistributed as you wish.
  1717. End of pep4grep.doc4
  1718. echo regerror.c 1>&2
  1719. cat > regerror.c <<'End of regerror.c'
  1720. #include <stdio.h>
  1721.  
  1722. void
  1723. regerror(s)
  1724. char *s;
  1725. {
  1726. #ifdef ERRAVAIL
  1727.     error("regexp: %s", s);
  1728. #else
  1729. /*
  1730.     fprintf(stderr, "regexp(3): %s\n", s);
  1731.     exit(1);
  1732. */
  1733.     return;      /* let std. egrep handle errors */
  1734. #endif
  1735.     /* NOTREACHED */
  1736. }
  1737. End of regerror.c
  1738. cat k.test | tr W '\200' | tr X '\201' | tr Y '\202' | tr Z '\216' > kanji.fake.test
  1739. cat k.pat | tr X '\201' | tr Y '\202' > kanjipat.fake
  1740. rm k.test k.pat
  1741. -- 
  1742. Rich $alz            rsalz@pineapple.bbn.com
  1743. Cronus Project, BBN Labs    "Anger is an energy"
  1744.