home *** CD-ROM | disk | FTP | other *** search
/ Shareware Overload / ShartewareOverload.cdr / progm / cpgms.zip / GREP.C < prev    next >
C/C++ Source or Header  |  1985-11-19  |  28KB  |  1,158 lines

  1. /*
  2.  *
  3.  *
  4.  * The    information  in  this  document  is  subject  to  change
  5.  * without  notice  and  should not be construed as a commitment
  6.  * by Digital Equipment Corporation or by DECUS.
  7.  *
  8.  * Neither Digital Equipment Corporation, DECUS, nor the authors
  9.  * assume any responsibility for the use or reliability of  this
  10.  * document or the described software.
  11.  *
  12.  *    Copyright (C) 1980, DECUS
  13.  *
  14.  *
  15.  * General permission to copy or modify, but not for profit,  is
  16.  * hereby  granted,  provided that the above copyright notice is
  17.  * included and reference made to  the    fact  that  reproduction
  18.  * privileges were granted by DECUS.
  19.  *
  20.  */
  21.  
  22. #include "stdio.h"
  23. /*  #include "ctype.h"  */
  24.  
  25. /*
  26.  * grep.
  27.  *
  28.  * Runs on the Decus compiler or on vms.
  29.  * Converted for BDS compiler (under CP/M-80), 20-Jan-83, by Chris Kern.
  30.  *
  31.  * Converted to IBM PC with CI-C86 C Compiler June 1983 by David N. Smith
  32.  *
  33.  * Converted to IBM PC with DeSmet C Compiler July 1984 by Richard Threlkeld.
  34.  *    Added wild card facility for MS-DOS 2.0 and up.
  35.  *    Added flag to prefix output line with filename.
  36.  *    Added use of "#" for a blank and ":#" for control meta
  37.  *      (DeSmet C cannot handle blank in pattern well).
  38.  *    Changed the backslash to an @ (at-sign) to allow
  39.  *      unambiguous use of backslash for escaped characters
  40.  *      (e.g. \t,\b,\f,\n,\r for tab, backspace, formfeed,
  41.  *      newline, and carriage return, respectively).
  42.  *    Added braces ('{' and '}') to group pattern elements.  Plus,
  43.  *      Minus, and Star all act on the delimited group as an element.
  44.  *      This allows f{oo}+ to match only lines with an even number of
  45.  *      'o's after an 'f'.  This is preparation for a CHANGE command.
  46.  *
  47.  * On vms, define as:
  48.  *
  49.  *    grep :== "$disk:[account]grep"     (native)
  50.  *    grep :== "$disk:[account]grep grep"     (Decus)
  51.  *
  52.  * See below for more information.
  53.  *
  54.  */
  55.  
  56. #define QUOTE '@'
  57. #define BLANK '#'
  58. #define ESCAPE '\\'
  59.  
  60. char    *documentation[] = {
  61. "grep searches a file for a given pattern.  Execute by",
  62. "   grep [flags] regular_expression file_list",
  63. "",
  64. "Flags are single characters preceeded by '-':",
  65. "   -c      Only a count of matching lines is printed",
  66. "   -f      Print file name for matching lines switch, see below",
  67. "   -p      Prefix file name to output lines and turn on -f flag",
  68. "   -n      Each line is preceeded by its line number",
  69. "   -v      Only print non-matching lines",
  70. "",
  71. "The file_list is a list of files (wildcards are acceptable on RSX modes).",
  72.  
  73. "",
  74. "The file name is normally printed if there is a file given.",
  75. "The -f flag reverses this action (print name no file, not if more).",
  76. "",
  77. 0 };
  78.  
  79.  
  80. char    *patdoc[] = {
  81. "The regular_expression defines the pattern to search for.  Upper- and",
  82. "lower-case are always ignored.  Blank lines never match.  The expression",
  83. "should be quoted to prevent file-name translation.",
  84. "x      An ordinary character (not mentioned below) matches that character.",
  85. "'\\'   The backslash escapes tab, backspace, formfeed, newline, and",
  86. "'          carriage return with \\t, \\b, \\f, \\n, and \\r.",
  87. "'@'    The at-sign quotes any character.  \"@$\" matches a dollar-sign.",
  88. "'^'    A circumflex at the beginning of an expression matches the",
  89. "       beginning of a line.",
  90. "'$'    A dollar-sign at the end of an expression matches the end of a line.",
  91. "'.'    A period matches any character except \"new-line\".",
  92. "'#'    An \"at-sign\" matches one blank.",
  93. "':a'   A colon matches a class of characters described by the following",
  94. "':d'     character.  \":a\" matches any alphabetic, \":d\" matches digits,",
  95. "':n'     \":n\" matches alphanumerics, \": \" matches spaces, tabs, and",
  96. "':#'     other control characters, such as new-line.",
  97. "'*'    An expression followed by an asterisk matches zero or more",
  98. "       occurrances of that expression: \"fo*\" matches \"f\", \"fo\"",
  99. "       \"foo\", etc.",
  100. "'+'    An expression followed by a plus sign matches one or more",
  101. "       occurrances of that expression: \"fo+\" matches \"fo\", etc.",
  102. "'-'    An expression followed by a minus sign optionally matches",
  103. "       the expression.",
  104. "'[]'   A string enclosed in square brackets matches any character in",
  105. "       that string, but no others.  If the first character in the",
  106. "       string is a circumflex, the expression matches any character",
  107. "       except \"new-line\" and the characters in the string.  For",
  108. "       example, \"[xyz]\" matches \"xx\" and \"zyx\", while \"[^xyz]\"",
  109. "       matches \"abc\" but not \"axb\".  A range of characters may be",
  110. "       specified by two characters separated by \"-\".  Note that,",
  111. "       [a-z] matches alphabetics, while [z-a] never matches.",
  112. "'{}'   Group regular expressions into one pattern element: f{oo}+",
  113. "       will only match an 'f' followed by an even number of 'o's.",
  114. "       Braces may be nested.  They are not allowed in square brackets.",
  115. "The concatenation of regular expressions is a regular expression.",
  116. 0};
  117.  
  118. #define DeSmet 1
  119. #define MSDOS 1
  120.  
  121. #ifndef stdin
  122. #define stdin STDIN
  123. #define stdout STDOUT
  124. #define stderr STDERR
  125. #endif
  126.  
  127. #define LMAX    512
  128. #define PMAX    256
  129.  
  130. #define CHAR    1
  131. #define BOL    2
  132. #define EOL    3
  133. #define ANY    4
  134. #define CLASS    5
  135. #define NCLASS    6
  136. #define STAR    7
  137. #define PLUS    8
  138. #define MINUS    9
  139. #define ALPHA    10
  140. #define DIGIT    11
  141. #define NALPHA    12
  142. #define PUNCT    13
  143. #define RANGE    14
  144. #define ENDPAT    15
  145. #define BRACE 16
  146. #define ENDBRACE 17
  147.  
  148. int    cflag;
  149. int    fflag;
  150. int    nflag;
  151. int    vflag;
  152. int    pflag;
  153. int    nfile;
  154.  
  155. int    debug    =    0;       /* Set for debug code      */
  156.  
  157. char    *pp;
  158.  
  159. #ifndef vms
  160. char    file_name[81];
  161. #endif
  162.  
  163. char    lbuf[LMAX];
  164. char    pbuf[PMAX];
  165.  
  166. /*******************************************************/
  167.  
  168. main(argc, argv)
  169. char *argv[];
  170. {
  171.    register char   *p;
  172.    register int    c, i;
  173.    int           gotpattern;
  174.    int           gotcha;
  175.    char  *fexpnd();
  176.  
  177.    FILE        *f;
  178.  
  179.    if (argc <= 1)
  180.       usage("No arguments");
  181.    if (argc == 2 && argv[1][0] == '?' && argv[1][1] == 0) {
  182.       help(documentation);
  183.       help(patdoc);
  184.       return;
  185.       }
  186.    nfile = argc-1;
  187.    gotpattern = 0;
  188.    for (i=1; i < argc; ++i) {
  189.       p = argv[i];
  190.       if (*p == '-') {
  191.      ++p;
  192.      while (c = *p++) {
  193.         switch(tolower(c)) {
  194.  
  195.         case '?':
  196.            help(documentation);
  197.            break;
  198.  
  199.         case 'C':
  200.         case 'c':
  201.            ++cflag;
  202.            break;
  203.  
  204.         case 'D':
  205.         case 'd':
  206.            ++debug;
  207.            break;
  208.  
  209.         case 'F':
  210.         case 'f':
  211.            ++fflag;
  212.            break;
  213.  
  214.         case 'p':
  215.         case 'P':
  216.            ++pflag;
  217.        fflag = 1;
  218.            break;
  219.  
  220.         case 'n':
  221.         case 'N':
  222.            ++nflag;
  223.            break;
  224.  
  225.         case 'v':
  226.         case 'V':
  227.            ++vflag;
  228.            break;
  229.  
  230.         default:
  231.            usage("Unknown flag");
  232.         }
  233.      }
  234.      argv[i] = 0;
  235.      --nfile;
  236.       } else if (!gotpattern) {
  237.      compile(p);
  238.      argv[i] = 0;
  239.      ++gotpattern;
  240.      --nfile;
  241.       }
  242.    }
  243.    if (!gotpattern)
  244.       usage("No pattern");
  245.    if (nfile == 0)
  246.       grep(stdin, 0);
  247.    else {
  248.       fflag = fflag ^ (nfile > 0);
  249.       for (i=1; i < argc; ++i) {
  250. #if MSDOS
  251.      if (argv[i])
  252.      while (p = fexpnd(argv[i])) {
  253. #else
  254.      if (p = argv[i]) {
  255. #endif
  256.         if ((f=fopen(p, "r")) == NULL)
  257.            cant(p);
  258.         else {
  259.            grep(f, p);
  260.            fclose(f);
  261.         }
  262.      }
  263.       }
  264.    }
  265. }
  266.  
  267. /*******************************************************/
  268.  
  269. file(s)
  270. char *s;
  271. {
  272.    printf("File %s:\n", s);
  273. }
  274.  
  275. /*******************************************************/
  276.  
  277. cant(s)
  278. char *s;
  279. {
  280.    fprintf(stderr, "%s: cannot open\n", s);
  281. }
  282.  
  283.  
  284. /*******************************************************/
  285.  
  286. help(hp)
  287. char **hp;  /* dns added extra '*'  */
  288. /*
  289.  * Give good help
  290.  */
  291. {
  292.    register char   **dp;
  293.  
  294.    for (dp = hp; *dp; dp++)
  295.       printf("%s\n", *dp);
  296. }
  297.  
  298.  
  299. /*******************************************************/
  300.  
  301. usage(s)
  302. char    *s;
  303. {
  304.    fprintf(stderr, "?GREP-E-%s\n", s);
  305.    fprintf(stderr,
  306.       "Usage: grep [-cfnv] pattern [file ...].  grep ? for help\n");
  307.    exit(1);
  308. }
  309.  
  310.  
  311.  
  312. /*******************************************************/
  313.  
  314.  
  315. compile(source)
  316. char       *source;   /* Pattern to compile        */
  317. /*
  318.  * Compile the pattern into global pbuf[]
  319.  */
  320. {
  321.    register char  *s;          /* Source string pointer       */
  322.    register int   c;          /* Current character       */
  323.    int          o;          /* Temp               */
  324.    char       *pcompil();  /* Compile braced group       */
  325.  
  326.    pp = pbuf;
  327.    s = source;
  328.    if (debug)
  329.       printf("Pattern = \"%s\"\n", s);
  330.    s = pcompil(s);
  331.    if (*(--s) == '}')
  332.     badpat("Unmatched close brace",source,s);
  333. }
  334.  
  335. /*******************************************************/
  336.  
  337. char *
  338. pcompil(source)
  339. char       *source;   /* Pattern to compile        */
  340. /*
  341.  * Compile (recursively) the pattern into global pbuf[]
  342.  */
  343. {
  344.    register char  *s;          /* Source string pointer       */
  345.    register char  *lp;          /* Last pattern pointer       */
  346.    register int   c;          /* Current character       */
  347.    int          o;          /* Temp               */
  348.    char       *spp;       /* Save beginning of pattern */
  349.    char       *cclass();  /* Compile class routine       */
  350.    char       *sbrace();  /* Compile braced group       */
  351.    char       spchr();     /* Translate char to escaped char */
  352.  
  353.    s = source;
  354.    while (c = *s++) {
  355.       /*
  356.        * STAR, PLUS and MINUS are special.
  357.        */
  358.       if (c == '*' || c == '+' || c == '-') {
  359.      if (pp == pbuf ||
  360.           (o=pp[-1]) == BOL ||
  361.           o == EOL ||
  362.           o == STAR ||
  363.           o == PLUS ||
  364.           o == MINUS)
  365.         badpat("Illegal occurrance op.", source, s);
  366.      store(ENDPAT);
  367.      store(ENDPAT);
  368.      spp = pp;         /* Save pattern end     */
  369.      while (--pp > lp)     /* Move pattern down     */
  370.         *pp = pp[-1];     /* one byte         */
  371.      *pp =     (c == '*') ? STAR :
  372.         (c == '-') ? MINUS : PLUS;
  373.      pp = spp;         /* Restore pattern end  */
  374.      continue;
  375.       }
  376.       /*
  377.        * All the rest.
  378.        */
  379.       lp = pp;           /* Remember start       */
  380.       switch(c) {
  381.  
  382.       case BLANK:
  383.      store(CHAR);
  384.     store(' ');
  385.      break;
  386.  
  387.       case '^':
  388.      store(BOL);
  389.      break;
  390.  
  391.       case '$':
  392.      store(EOL);
  393.      break;
  394.  
  395.       case '.':
  396.      store(ANY);
  397.      break;
  398.  
  399.       case '}':         /* brace pairs group pattern strings */
  400.      return(s);
  401.  
  402.       case '{':         /* brace pairs group pattern strings */
  403.      s = sbrace(source, s);
  404.      break;
  405.  
  406.       case '[':
  407.      s = cclass(source, s);
  408.      break;
  409.  
  410.       case ':':
  411.      if (*s) {
  412.         c = *s++;
  413.         switch(tolower(c)) {
  414.  
  415.         case 'a':
  416.         case 'A':
  417.            store(ALPHA);
  418.            break;
  419.  
  420.         case 'd':
  421.         case 'D':
  422.            store(DIGIT);
  423.            break;
  424.  
  425.         case 'n':
  426.         case 'N':
  427.            store(NALPHA);
  428.            break;
  429.  
  430.         case BLANK:
  431.            store(PUNCT);
  432.            break;
  433.  
  434.         default:
  435.            badpat("Unknown : type", source, s);
  436.  
  437.         }
  438.         break;
  439.      }
  440.      else     badpat("No : type", source, s);
  441.  
  442.       case QUOTE:
  443.      if (*s) {
  444.         c = *s++;
  445.         store(CHAR);
  446.         store(tolower(c));
  447.     }
  448.     break;
  449.  
  450.       case ESCAPE:
  451.      if (*s) {
  452.         store(CHAR);
  453.         store(spchr(tolower(c=*s++)));
  454.      }
  455.      break;
  456.  
  457.       default:
  458.      store(CHAR);
  459.      store(tolower(c));
  460.       }
  461.    }
  462.    store(ENDPAT);
  463.    store(0);            /* Terminate string     */
  464.    if (debug) {
  465.       for (lp = pbuf; lp < pp;) {
  466.      if ((c = (*lp++ & 0377)) < ' ')
  467.         printf("%d ", c);
  468.      else     printf("%c ", c);
  469.     }
  470.     printf("\n");
  471.    }
  472.    return(s);
  473. }
  474.  
  475. /*******************************************************/
  476.  
  477. char spchr(inchar)
  478. char inchar;
  479. {
  480.     switch(inchar) {
  481.  
  482.         case 't':
  483.             return('\t');
  484.         case 'b':
  485.             return('\b');
  486.         case 'f':
  487.             return('\f');
  488.         case 'n':
  489.             return('\n');
  490.         case 'r':
  491.             return('\r');
  492.         default:
  493.             return(inchar);
  494.         }
  495. }
  496.  
  497. /*******************************************************/
  498.  
  499. char *
  500. cclass(source, src)
  501. char       *source;   /* Pattern start -- for error msg.      */
  502. char       *src;      /* Class start           */
  503. /*
  504.  * Compile a class (within [])
  505.  */
  506. {
  507.    register char   *s;          /* Source pointer    */
  508.    register char   *cp;       /* Pattern start       */
  509.    register int    c;          /* Current character */
  510.    int           o;          /* Temp           */
  511.    char       spchr();     /* Translate char to escaped char */
  512.  
  513.    s = src;
  514.    o = CLASS;
  515.    if (*s == '^') {
  516.       ++s;
  517.       o = NCLASS;
  518.    }
  519.    store(o);
  520.    cp = pp;
  521.    store(0);                  /* Byte count     */
  522.    while ((c = *s++) && c!=']') {
  523.       if (c == QUOTE) {            /* Store quoted char    */
  524.      if ((c = *s++) == '\0')      /* Gotta get something  */
  525.         badpat("Class terminates badly", source, s);
  526.      else     store(tolower(c));
  527.       }
  528.       else if (c == ESCAPE) {
  529.         if ((c = *s++) == '\0')
  530.             badpat("Invalid Escape sequence", source, s);
  531.         else
  532.             store(spchr(c));
  533.     }
  534.       else if (c == '-' &&
  535.         (pp - cp) > 1 && *s != ']' && *s != '\0') {
  536.      c = pp[-1];         /* Range start     */
  537.      pp[-1] = RANGE;     /* Range signal    */
  538.      store(c);         /* Re-store start  */
  539.      c = *s++;         /* Get end char and*/
  540.      store(tolower(c));     /* Store it        */
  541.       }
  542.       else {
  543.      store(tolower(c));     /* Store normal char */
  544.       }
  545.    }
  546.    if (c != ']')
  547.       badpat("Unterminated class", source, s);
  548.    if ((c = (pp - cp)) >= 256)
  549.       badpat("Class too large", source, s);
  550.    if (c == 0)
  551.       badpat("Empty class", source, s);
  552.    *cp = c;
  553.    return(s);
  554. }
  555.  
  556.  
  557. /*******************************************************/
  558.  
  559. char *
  560. sbrace(source, src)
  561. char       *source;   /* Pattern start -- for error msg.      */
  562. char       *src;      /* Bracketed area start  */
  563. /*
  564.  * Compile a pattern withing {} (a grouping)
  565.  */
  566. {
  567.    register char   *s;          /* Source pointer    */
  568.    register int    c;          /* Current character */
  569.    int           o;          /* Temp           */
  570.  
  571.    s = src;
  572.    o = BRACE;
  573.    store(o);
  574.  
  575.    s = pcompil(s);       /* call compile to finish pattern */
  576.    if (s[-1] == '}')    /* set up end of group */
  577.     store(ENDBRACE);
  578.    else
  579.     badpat("Unmatched brace",source,s);
  580.    return(s);
  581. }
  582.  
  583. /*******************************************************/
  584.  
  585. store(op)
  586. {
  587.    if (pp >= &pbuf[PMAX])
  588.       error("Pattern too complex\n");
  589.    *pp++ = op;
  590. }
  591.  
  592.  
  593. /*******************************************************/
  594.  
  595. badpat(message, source, stop)
  596. char  *message;       /* Error message */
  597. char  *source;          /* Pattern start */
  598. char  *stop;          /* Pattern end   */
  599. {
  600.    register int    c, lp;
  601.  
  602.    if (debug) {
  603.       for (lp = pbuf; lp < pp;) {
  604.      if ((c = (*lp++ & 0377)) < ' ')
  605.         printf("%d ", c);
  606.      else     printf("%c ", c);
  607.     }
  608.     printf("\n");
  609.    }
  610.    fprintf(stderr, "-GREP-E-%s, pattern is\"%s\"\n", message, source);
  611.    fprintf(stderr, "-GREP-E-Stopped at byte %d, '%c'\n",
  612.      stop-source, stop[-1]);
  613.    error("?GREP-E-Bad pattern\n");
  614. }
  615.  
  616.  
  617.  
  618. /*******************************************************/
  619.  
  620. grep(fp, fn)
  621. FILE       *fp;       /* File to process        */
  622. char       *fn;       /* File name (for -f option)  */
  623. /*
  624.  * Scan the file for the pattern in pbuf[]
  625.  */
  626. {
  627.    register int lno, count, m;
  628.  
  629.    lno = 0;
  630.    count = 0;
  631.    while (fgets(lbuf, LMAX, fp)) {
  632.       ++lno;
  633. #if DeSmet
  634.     for (m=0; lbuf[m]; m++)
  635.         if ((lbuf[m] == '\r') || (lbuf[m] == '\n'))
  636.             lbuf[m] = '\0';
  637. #endif
  638.       m = match();
  639.       if ((m && !vflag) || (!m && vflag)) {
  640.      ++count;
  641.      if (!cflag) {
  642.         if (fflag && fn) {
  643.            file(fn);
  644.            fn = 0;
  645.         }
  646.         if (pflag && fn)
  647.            printf("%s\t", fn);
  648.         if (nflag)
  649.            printf("%d\t", lno);
  650.         printf("%s\n", lbuf);
  651.      }
  652.       }
  653.    }
  654.    if (cflag) {
  655.       if (fflag && fn)
  656.      file(fn);
  657.       printf("%d\n", count);
  658.    }
  659. }
  660.  
  661.  
  662. /*******************************************************/
  663.  
  664. match()
  665. /*
  666.  * Match the current line (in lbuf[]), return 1 if it does.
  667.  */
  668. {
  669.    register char   *l;          /* Line pointer        */
  670.    char *pmatch();
  671.  
  672.    for (l = lbuf; *l; l++) {
  673.       if (pmatch(l, pbuf))
  674.      return(1);
  675.    }
  676.    return(0);
  677. }
  678.  
  679. /*******************************************************/
  680.  
  681. char *
  682. pmatch(line, pattern)
  683. char           *line;     /* (partial) line to match      */
  684. char           *pattern;  /* (partial) pattern to match   */
  685. {
  686.    register char   *l;          /* Current line pointer          */
  687.    register char   *p;          /* Current pattern pointer      */
  688.    register char   c;          /* Current character          */
  689.    char        *e;          /* End for STAR and PLUS match  */
  690.    int           op;          /* Pattern operation          */
  691.    int           n;          /* Class counter              */
  692.    char        *are;      /* Start of STAR match          */
  693.  
  694.    l = line;
  695.    if (debug > 1)
  696.       printf("pmatch(\"%s\")\n", line);
  697.    p = pattern;
  698.    while ((op = *p++) != ENDPAT) {
  699.       if (debug > 1)
  700.      printf("byte[%d] = 0%o, '%c', op = 0%o\n",
  701.            l-line, *l, *l, op);
  702.       switch(op) {
  703.  
  704.       case CHAR:
  705.      if (tolower(*l++) != *p++)
  706.         return(0);
  707.      break;
  708.  
  709.       case BOL:
  710.      if (l != lbuf)
  711.         return(0);
  712.      break;
  713.  
  714.       case EOL:
  715.      if (*l != '\0')
  716.         return(0);
  717.      break;
  718.  
  719.       case ANY:
  720.      if (*l++ == '\0')
  721.         return(0);
  722.      break;
  723.  
  724.       case DIGIT:
  725.      if ((c = *l++) < '0' || (c > '9'))
  726.         return(0);
  727.      break;
  728.  
  729.       case ALPHA:
  730.      c = tolower(*l++);
  731.      if (c < 'a' || c > 'z')
  732.         return(0);
  733.      break;
  734.  
  735.       case NALPHA:
  736.      c = tolower(*l++);
  737.      if (c >= 'a' && c <= 'z')
  738.         break;
  739.      else if (c < '0' || c > '9')
  740.         return(0);
  741.      break;
  742.  
  743.       case PUNCT:
  744.      c = *l++;
  745.      if (c == 0 || c > ' ')
  746.         return(0);
  747.      break;
  748.  
  749.       case BRACE:
  750.      e = pmatch(l, p);    /* match braced group */
  751.      while (*p++ != ENDBRACE) ;      /* skip matched group */
  752.      if (e)
  753.         l = e;    /* if match, update area */
  754.      else
  755.         return(0);    /* else, return no match */
  756.      break;
  757.  
  758.       case CLASS:
  759.       case NCLASS:
  760.      c = tolower(*l++);
  761.      n = *p++ & 0377;
  762.      do {
  763.         if (*p == RANGE) {
  764.            p += 3;
  765.            n -= 2;
  766.            if (c >= p[-2] && c <= p[-1])
  767.           break;
  768.         }
  769.         else if (c == *p++)
  770.            break;
  771.      } while (--n > 1);
  772.      if ((op == CLASS) == (n <= 1))
  773.         return(0);
  774.      if (op == CLASS)
  775.         p += n - 2;
  776.      break;
  777.  
  778.       case MINUS:
  779.      e = pmatch(l, p);     /* Look for a match    */
  780.      while (*p++ != ENDPAT); /* Skip over pattern    */
  781.      if (e)          /* Got a match?    */
  782.         l = e;         /* Yes, update string    */
  783.      break;          /* Always succeeds    */
  784.  
  785.       case PLUS:         /* One or more ...    */
  786.      if ((l = pmatch(l, p)) == 0)
  787.         return(0);         /* Gotta have a match    */
  788.       case STAR:         /* Zero or more ...    */
  789.      are = l;         /* Remember line start */
  790.      while (*l && (e = pmatch(l, p)))
  791.         l = e;         /* Get longest match    */
  792.      while (*p++ != ENDPAT); /* Skip over pattern    */
  793.      while (l >= are) {     /* Try to match rest    */
  794.         if (e = pmatch(l, p))
  795.            return(e);
  796.         --l;         /* Nope, try earlier    */
  797.      }
  798.      return(0);         /* Nothing else worked */
  799.  
  800.       default:
  801.      printf("Bad op code %d\n", op);
  802.      error("Cannot happen -- match\n");
  803.       }
  804.    }
  805.    return(l);
  806. }
  807.  
  808. /*******************************************************/
  809.  
  810. itoa(n,s)        /* convert n to characters in s */
  811. char s[];
  812. int n;
  813. {
  814.     int i,sign;
  815.  
  816.     if ((sign = n) < 0)  /* record sign */
  817.         n = -n;         /* make positive */
  818.     i = 0;
  819.     do {                /* generate digits in reverse order */
  820.         s[i++] = n % 10 + '0';          /* get next digit */
  821.     } while ((n /= 10) > 0);        /* delete it */
  822.     if (sign < 0)
  823.         s[i++] = '-';
  824.     s[i] = '\0';
  825.     reverse(s);
  826. }
  827.  
  828. reverse(s)        /* reverse string s in place */
  829. char s[];
  830. {
  831.     int c, i, j;
  832.  
  833.     for (i=0, j=strlen(s)-1; i<j; i++, j--) {
  834.         c = s[i];
  835.         s[i] = s[j];
  836.         s[j] = c;
  837.     }
  838. }
  839.  
  840. /*******************************************************/
  841.  
  842. error(s)
  843. char *s;
  844. {
  845.    fprintf(stderr, "%s", s);
  846.    exit(1);
  847. }
  848.  
  849. /*******************************************************/
  850.  
  851. #if MSDOS
  852.  
  853.  
  854. /*******************************************************/
  855.  
  856. /*
  857.     fexpnd returns a pointer to the next filename.ext which
  858.     matches the first parameter (str) or a zero is no file 
  859.     matches that parameter.  The given parameter is processed 
  860.     to generate a normalized search string and a normalized 
  861.     path prefix.  The prefix is returned in the area pointed to 
  862.     by the second parameter.  This string may be concatenated 
  863.     with the returned value to give a fully-qualified dataset 
  864.     name for open, rename, erase, etc.
  865.  
  866.     You should continue calling fexpnd with the same first 
  867.     parameter until it returns a zero, then pass it a new first 
  868.     parameter.  This can be used to expand a list of ambiguous 
  869.     filenames on a parameter list.   */
  870.  
  871.  
  872. char mydta[80] = {0};
  873. char fxs[80] = {0};
  874. char fxsrch[80] = {0};
  875. char fxpref[80] = {0};
  876.  
  877. char *fexpnd(str, path)
  878. char *str, *path;
  879. {
  880.     int getret, fixpath(), strcpy(), dirnxt(), dirfst();
  881.     int savdta(), restdta();
  882.     char *rindex();
  883.  
  884.     savdta();        /* save old dta in an unknown location */
  885.     setdta(&mydta); /* make MS-DOS use my dta */
  886.     if (strcmp(str,fxs) == 0)
  887.         getret = dirnxt();        /* MS-DOS 2.0+ get next function */
  888.     else {
  889.         strcpy(fxs,str);            /* for detection of a new name */
  890.         if (!fixpath(str, fxsrch, fxpref))
  891.             return (0);
  892.         strcpy(path, fxpref);        /* give path to caller */
  893.         getret = dirfst(fxsrch);    /* MS-DOS 2.0+ get first function */
  894.     }
  895.  
  896.     restdta();        /* restore old dta */
  897.     if (getret == 0)  {
  898.         return(&mydta[30]);        /* return filename.ext  */
  899.     }
  900.     else
  901.         return 0;
  902. }
  903.  
  904. int dtads, dtadx;
  905.  
  906. savdta()
  907. {
  908. #asm
  909.     PUSH    AX
  910.     PUSH    ES
  911.     PUSH    BX
  912.     MOV        AH,2fh
  913.     INT        21h
  914.     MOV        WORD dtads_,ES
  915.     MOV        WORD dtadx_,BX
  916.     POP        BX
  917.     POP        ES
  918.     POP        AX
  919. #
  920. }
  921.  
  922. restdta()
  923. {
  924. #asm
  925.     PUSH    DS
  926.     PUSH    DX
  927.     PUSH    AX
  928.     MOV        DX,WORD dtads_
  929.     MOV        DS,DX
  930.     MOV        DX,WORD dtadx_
  931.     MOV        AH,1AH
  932.     INT        21h
  933.     POP        AX
  934.     POP        DX
  935.     POP        DS
  936. #
  937. }
  938.  
  939. char *dtap;
  940.  
  941. setdta(newdta)
  942. char *newdta;
  943. {
  944.     dtap = newdta;
  945. #asm
  946.     PUSH    DX
  947.     PUSH    AX
  948.     MOV        DX,WORD dtap_
  949.     MOV        AH,1AH
  950.     INT        21h
  951.     POP        AX
  952.     POP        DX
  953. #
  954. }
  955.  
  956. char *dirfnp;
  957.  
  958. dirfst(s)
  959. char *s;
  960. {
  961.     dirfnp = s;
  962. #asm
  963.     PUSH    DX
  964.     PUSH    CX
  965.     MOV        DX,WORD dirfnp_
  966.     MOV        AH,4eh
  967.     INT        21h
  968.     JC    D56X1
  969.     MOV    AX,0
  970. D56X1:    NOP
  971.     POP    CX
  972.     POP        DX
  973. #
  974. }
  975.  
  976. dirnxt()
  977. {
  978. #asm
  979.     MOV        AH,4fh
  980.     INT        21h
  981.     JC    D56X2
  982.     MOV    AX,0
  983. D56X2:    NOP
  984. #
  985. }
  986.  
  987.  
  988. /* ================================================================ */
  989. /*
  990.     From Dr. Dobbs #108 October 1985.   */
  991.  
  992. /* fixpath() processes a DOS pathname for two different uses.
  993.     The input path, *ip, is presumably a command operand like the 
  994.     first operand of DIR.  One output, the search path *sp, is 
  995.     the input possibly augmented with "*.*" or "\*.*" so that it 
  996.     will work reliably with DOS functions 4E/$f.  The other output
  997.     is a lead-in path, *lip, which can be prefixed to the simple 
  998.     filename.ext returned by fuctions 4E/4F to make a complete path 
  999.     for opening or erasing a file.
  1000.  
  1001.     The function returns 1 if it is successful, but 0 if the 
  1002.     input path can't be processed and should not be used.
  1003.  
  1004.     Some input paths can be processed here yet be invalid or 
  1005.     useless.  The search path produced from such an input wwill
  1006.     cause an error return from function 4E (search first match). */
  1007. /* ================================================================ */
  1008.  
  1009. int fixpath(ip, sp, lip)
  1010. register char     *ip,    /* input path */
  1011.                 *sp,    /* the search path */
  1012.                 *lip;    /* the lead-in or prefix path */
  1013. {
  1014.     char *cp;            /* work pointer for scanning paths */
  1015.     char attr;            /* attribute for chgattr */
  1016.     int ret, strlen(), strcpy(), strcmp(), strcat(), chgattr();
  1017.  
  1018. /* ================================================================ */
  1019. /*    Pick off 4 special cases:
  1020.     (1)    the NULL string, which we take to mean "*.*"
  1021.     (2)    the simple "d:" reference to a drive, which we also take
  1022.         to mean "d:*.*"
  1023.     (3)    the root-dir reference "d:\" which is mishandled by 
  1024.         function 0x4300 of both dDOS 2.1 and 3.0.
  1025.     (4)    any reference that ends in "\"
  1026.  
  1027.     In all cases, the input path is ok as a lead-in, but it needs
  1028.     the global qualifier *.* added for searching.
  1029.  
  1030. /* ================================================================ */
  1031.  
  1032.     if ((strlen(ip) == 0)                /* null string */
  1033.         || (strcmp(ip+1, ":") == 0)        /* d: only */
  1034.         || (ip[strlen(ip)-1] == '\\'))    /* ends in backslash */
  1035.         {
  1036.             strcpy(lip, ip);            /* input is ok as prefix */
  1037.             strcpy(sp, ip);
  1038.             strcat(sp, "*.*");            /* add *.* for search */
  1039.             return (1);
  1040.         }
  1041.  
  1042. /* ================================================================ */
  1043. /*    Ok, we have a non-null string not ending in \ and not a lone
  1044.     drive letter.  Four possibilities remain:
  1045.     (1)    an explicit file reference, b:\mydir\mydat
  1046.     (2)    an explicit directory reference, \mydir
  1047.     (3)    an ambiguous file reference, *.* or b:\mydir\*.dat
  1048.     (4)    an unknown name, a:noway or b:\mydir\nonesuch.dat
  1049.  
  1050.     We can separate this with fair reliability using DOS function
  1051.     0x4300, get attributes from path.
  1052.  
  1053.  
  1054. /* ================================================================ */
  1055.  
  1056.     attr = 0x00;                        /* output area for get command */
  1057.     ret = chgattr('G', ip, &attr);        /* get attributes for this path */
  1058.  
  1059.     if (ret == 0x0002) 
  1060.  
  1061.     /*   the path is valid, in that all directories and drives
  1062.         named in it are valid, but the final name is unknown.  No
  1063.         files will be found in a search, so quit now.   */
  1064.  
  1065.         return (0);
  1066.  
  1067.     if ((ret == 0x0003) 
  1068.         || ((ret == 0) && ((attr & 0x0010) == 0))) {
  1069.  
  1070.     /* Error 3, path not found, could mean total junk or a 
  1071.         misspelt directory name, but most likely it just means
  1072.         the path ends in an ambiguous name.  If there's an error
  1073.         the initial search (function 4Eh) will fail.
  1074.  
  1075.         With no error and reg cx not showing the directory 
  1076.         attribute flags 0010, we have a normal, unambiguous file
  1077.         pathname like "b:\mydir\mydat" or just "myprog.com".
  1078.  
  1079.         In either case the search path is the whole input
  1080.         path.  The lead-in is that shorn of whatever follows the 
  1081.         rightmost \ or :, whichever comes last -- or just a null
  1082.         string if there is no \ or :.        */
  1083.  
  1084.         strcpy(sp, ip);            /* working copy of ip in sp */
  1085.         cp = sp + strlen(sp) -1;
  1086.         for (; cp > sp; --cp)
  1087.             if (('\\' == *cp) || (':' == *cp)) 
  1088.                 break;
  1089.         if (cp > sp)
  1090.             ++cp;                /* retain colon or slash */
  1091.         *cp = '\0';                /* make a null string */
  1092.         strcpy(lip, sp);
  1093.         strcpy(sp, ip);            /* whole input for search */
  1094.         return (1);
  1095.     }
  1096.  
  1097.     if ((ret == 0) && (attr & 0x0010)) {
  1098.  
  1099.     /* No error and the directory attribute returned in cx
  1100.         shows an unambiguous path that happpens to end in the 
  1101.         name of a directory, for instance "..\..\bin" or
  1102.         "b:\mydir".  Applying the rules of DIR or COPY, we
  1103.         have to treat these as global refs to all files named in 
  1104.         the directory.  The search path is the input with 
  1105.         "\*.*" tacked onto it.  The lead-in path is just the 
  1106.         input plus a backslash.            */
  1107.  
  1108.         strcpy(sp, ip);
  1109.         strcpy(lip, ip);
  1110.         strcat(sp, "\\*.*");
  1111.         strcat(lip, "\\");
  1112.         return (1);
  1113.     }
  1114.  
  1115.     else {
  1116.         /* unexpected events make me nervous, so give up */
  1117.         return (0);
  1118.     }
  1119. }
  1120.  
  1121. int rax, rcx;
  1122. char *rdx;
  1123.  
  1124. int chgattr(getset, path, attr)
  1125. char getset, *path, *attr;
  1126. {
  1127.     if (getset == 'S') {
  1128.         rcx = *attr;
  1129.         rax = 0x4301;
  1130.     }
  1131.     else
  1132.         rax = 0x4300;
  1133.     
  1134.     rdx = path;
  1135.  
  1136. #asm
  1137.     PUSH    DX
  1138.     PUSH    CX
  1139.     PUSH    AX
  1140.     MOV        DX,WORD rdx_
  1141.     MOV        AX,WORD rax_
  1142.     MOV        CX,WORD rcx_
  1143.     INT        21H
  1144.     MOV        WORD rax_,AX
  1145.     JC        D56X9
  1146.     MOV        WORD rax_,0
  1147. D56X9:    NOP
  1148.     MOV        WORD rcx_,CX
  1149.     POP        AX
  1150.     POP        CX
  1151.     POP        DX
  1152. #
  1153.     if (rax == 0)
  1154.         *attr = rcx;
  1155.     return(rax);
  1156. }        
  1157. #endif
  1158.