home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1992 March / Source_Code_CD-ROM_Walnut_Creek_March_1992.iso / usenet / altsrcs / 2 / 2829 < prev    next >
Internet Message Format  |  1991-02-21  |  14KB

  1. From: johnk@wrq.com
  2. Newsgroups: alt.sources
  3. Subject: REGEX Globber (Wild Card Matching)
  4. Message-ID: <16885@milton.u.washington.edu>
  5. Date: 21 Feb 91 18:36:53 GMT
  6.  
  7. 02-20-91 Seattle, WA
  8.  
  9.  
  10. Here is a *IX wildcard globber I butchered, hacked and cajoled together
  11. after seeing and hearing about and becoming disgusted with several similar
  12. routines which had one or more of the following attributes:  slow, buggy,
  13. required large levels of recursion on matches, required grotesque levels
  14. of recursion on failing matches using '*', full of caveats about usability
  15. or copyrights.
  16.  
  17. I submit this without copyright and with the clear understanding that
  18. this code may be used by anyone, for any reason, with any modifications
  19. and without any guarantees, warrantee or statements of usability of any
  20. sort.
  21.  
  22. Having gotten those cow chips out of the way, these routines are fairly
  23. well tested and reasonably fast.  I have made an effort to fail on all
  24. bad patterns and to quickly determine failing '*' patterns.  This parser
  25. will also do quite a bit of the '*' matching via quick linear loops versus
  26. the standard blind recursive descent.
  27.  
  28. This parser has been submitted to profilers at various stages of development
  29. and has come through quite well.  If the last millisecond is important to
  30. you then some time can be shaved by using stack allocated variables in
  31. place of many of the pointer follows (which may be done fairly often) found
  32. in regex_match and regex_match_after_star (ie *p, *t).
  33.  
  34. No attempt is made to provide general [pat,pat] comparisons.  The specific
  35. subcases supplied by these routines is [pat,text] which is sufficient
  36. for the large majority of cases (should you care).
  37.  
  38. Since regex_match may return one of three different values depending upon
  39. the pattern and text I have made a simple shell for convenience (match()).
  40. Also included is an is_pattern routine to quickly check a potential pattern
  41. for regex special characters.  I even placed this all in a header file for
  42. you lazy folks!
  43.  
  44. Having said all that, here is my own reinvention of the wheel.  Please
  45. enjoy it's use and I hope it is of some help to those with need ....
  46.  
  47.  
  48.                                 jbk
  49.  
  50.  
  51. ==================== BEGIN of Listing ====================
  52.  
  53.  
  54. Checksum: 3309008187   (verify or update this with "brik")
  55.  
  56.  
  57. ==================== match.h ====================
  58.  
  59. /*
  60.  EPSHeader
  61.  
  62.    File: match.h
  63.    Author: J. Kercheval
  64.    Created: Sat, 01/05/1991  22:27:18
  65. */
  66. /*
  67.  EPSRevision History
  68.  
  69.    J. Kercheval  Wed, 02/20/1991  22:28:37  Released to Public Domain
  70. */
  71.  
  72. /*
  73.    Wildcard Pattern Matching
  74. */
  75.  
  76. #ifndef BOOLEAN
  77. # define BOOLEAN int
  78. # define TRUE 1
  79. # define FALSE 0
  80. #endif
  81.  
  82. /*----------------------------------------------------------------------------
  83. *
  84. *  Match the pattern PATTERN against the string TEXT;
  85. *  return TRUE if it matches, FALSE otherwise.
  86. *
  87. *  A match means the entire string TEXT is used up in matching.
  88. *
  89. *  In the pattern string:
  90. *       `*' matches any sequence of characters
  91. *       `?' matches any character
  92. *       [SET] matches any character in the specified set,
  93. *       [!SET] or [^SET] matches any character not in the specified set.
  94. *
  95. *  Note: the standard regex character '+' (one or more) should by
  96. *        simulated by using "?*" which is equivelant here.
  97. *
  98. *  A set is composed of characters or ranges; a range looks like
  99. *  character hyphen character (as in 0-9 or A-Z).
  100. *  [0-9a-zA-Z_] is the set of characters allowed in C identifiers.
  101. *  Any other character in the pattern must be matched exactly.
  102. *
  103. *  To suppress the special syntactic significance of any of `[]*?!^-\',
  104. *  and match the character exactly, precede it with a `\'.
  105. *
  106. ----------------------------------------------------------------------------*/
  107.  
  108. BOOLEAN match (char *pattern, char *text);
  109.  
  110. /*----------------------------------------------------------------------------
  111. *
  112. * Return TRUE if PATTERN has any special wildcard characters
  113. *
  114. ----------------------------------------------------------------------------*/
  115.  
  116. BOOLEAN is_pattern (char *pattern);
  117.  
  118.  
  119. ==================== match.c ====================
  120.  
  121. /*
  122.  EPSHeader
  123.  
  124.    File: match.c
  125.    Author: J. Kercheval
  126.    Created: Sat, 01/05/1991  22:21:49
  127. */
  128. /*
  129.  EPSRevision History
  130.  
  131.    J. Kercheval  Wed, 02/20/1991  22:29:01  Released to Public Domain
  132. */
  133.  
  134. /*
  135.    Wildcard Pattern Matching
  136. */
  137.  
  138.  
  139. #include "match.h"
  140.  
  141. #define ABORT 2     /* end of search indicator */
  142.  
  143. BOOLEAN regex_match_after_star (char *pattern, char *text);
  144.  
  145. /*----------------------------------------------------------------------------
  146. *
  147. * Return TRUE if PATTERN has any special wildcard characters
  148. *
  149. ----------------------------------------------------------------------------*/
  150.  
  151. BOOLEAN is_pattern (char *p)
  152. {
  153.     while ( *p ) {
  154.         switch ( *p++ ) {
  155.             case '?':
  156.             case '*':
  157.             case '[':
  158.                 return TRUE;
  159.             case '\\':
  160.                 if ( !*p++ ) return FALSE;
  161.         }
  162.     }
  163.     return FALSE;
  164. }
  165.  
  166.  
  167. /*----------------------------------------------------------------------------
  168. *
  169. *  Match the pattern PATTERN against the string TEXT;
  170. *  return TRUE if it matches, FALSE otherwise.
  171. *
  172. *  A match means the entire string TEXT is used up in matching.
  173. *
  174. *  In the pattern string:
  175. *       `*' matches any sequence of characters
  176. *       `?' matches any character
  177. *       [SET] matches any character in the specified set,
  178. *       [!SET] or [^SET] matches any character not in the specified set.
  179. *
  180. *  Note: the standard regex character '+' (one or more) should by
  181. *        simulated by using "?*" which is equivelant here.
  182. *
  183. *  A set is composed of characters or ranges; a range looks like
  184. *  character hyphen character (as in 0-9 or A-Z).
  185. *  [0-9a-zA-Z_] is the set of characters allowed in C identifiers.
  186. *  Any other character in the pattern must be matched exactly.
  187. *
  188. *  To suppress the special syntactic significance of any of `[]*?!^-\',
  189. *  and match the character exactly, precede it with a `\'.
  190. *
  191. ----------------------------------------------------------------------------*/
  192.  
  193. BOOLEAN regex_match ( register char *p, register char *t )
  194. {
  195.     register char range_start, range_end;  /* start and end in range */
  196.  
  197.     BOOLEAN invert;             /* is this [..] or [!..] */
  198.     BOOLEAN member_match;       /* have I matched the [..] construct? */
  199.     BOOLEAN loop;               /* should I terminate? */
  200.  
  201.     for ( ; *p; p++, t++ ) {
  202.  
  203.         /* if this is the end of the text then this is the end of the match */
  204.         if (!*t) {
  205.             return ( *p == '*' && *++p == '\0' ) ? TRUE : ABORT;
  206.         }
  207.  
  208.         /* determine and react to pattern type */
  209.         switch ( *p ) {
  210.  
  211.             /* single any character match */
  212.             case '?':
  213.                 break;
  214.  
  215.             /* multiple any character match */
  216.             case '*':
  217.                 return regex_match_after_star (p, t);
  218.  
  219.             /* [..] construct, single member/exclusion character match */
  220.             case '[': {
  221.  
  222.                 /* move to beginning of range */
  223.                 p++;
  224.  
  225.                 /* check if this is a member match or exclusion match */
  226.                 invert = FALSE;
  227.                 if ( *p == '!' || *p == '^') {
  228.                     invert = TRUE;
  229.                     p++;
  230.                 }
  231.  
  232.                 /* if closing bracket here or at range start then we have a
  233.                    malformed pattern */
  234.                 if ( *p == ']' ) {
  235.                     return ABORT;
  236.                 }
  237.  
  238.                 member_match = FALSE;
  239.                 loop = TRUE;
  240.  
  241.                 while ( loop ) {
  242.  
  243.                     /* if end of construct then loop is done */
  244.                     if (*p == ']') {
  245.                         loop = FALSE;
  246.                         continue;
  247.                     }
  248.  
  249.                     /* matching a '!', '^', '-', '\' or a ']' */
  250.                     if ( *p == '\\' ) {
  251.                         range_start = range_end = *++p;
  252.                     }
  253.                     else {
  254.                         range_start = range_end = *p;
  255.                     }
  256.  
  257.                     /* if end of pattern then bad pattern (Missing ']') */
  258.                     if (!range_start)
  259.                         return ABORT;
  260.  
  261.                     /* move to next pattern char */
  262.                     p++;
  263.  
  264.                     /* check for range bar */
  265.                     if (*p == '-') {
  266.  
  267.                         /* get the range end */
  268.                         range_end = *++p;
  269.  
  270.                         /* special character range end */
  271.                         if (range_end == '\\')
  272.                             range_end = *++p;
  273.  
  274.                         /* if end of pattern or construct then bad pattern */
  275.                         if (range_end == '\0' || range_end == ']')
  276.                             return ABORT;
  277.                     }
  278.  
  279.                     /* if the text character is in range then match found.
  280.                        make sure the range letters have the proper
  281.                        relationship to one another before comparison */
  282.                     if ( range_start < range_end  ) {
  283.                         if (*t >= range_start && *t <= range_end) {
  284.                             member_match = TRUE;
  285.                             loop = FALSE;
  286.                         }
  287.                     }
  288.                     else {
  289.                         if (*t >= range_end && *t <= range_start) {
  290.                             member_match = TRUE;
  291.                             loop = FALSE;
  292.                         }
  293.                     }
  294.                 }
  295.  
  296.                 /* if there was a match in an exclusion set then no match */
  297.                 /* if there was no match in a member set then no match */
  298.                 if ((invert && member_match) ||
  299.                    !(invert || member_match))
  300.                     return FALSE;
  301.  
  302.                 /* if this is not an exclusion then skip the rest of the [...]
  303.                     construct that already matched. */
  304.                 if (member_match) {
  305.                     while (*p != ']') {
  306.  
  307.                         /* bad pattern (Missing ']') */
  308.                         if (!*p)
  309.                             return ABORT;
  310.  
  311.                         /* skip exact match */
  312.                         if (*p == '\\') {
  313.                             p++;
  314.                         }
  315.  
  316.                         /* move to next pattern char */
  317.                         p++;
  318.                     }
  319.                 }
  320.  
  321.                 break;
  322.             }
  323.  
  324.             /* next character is quoted and must match exactly */
  325.             case '\\':
  326.  
  327.                 /* move pattern pointer to quoted char and fall through */
  328.                 p++;
  329.  
  330.             /* must match this character exactly */
  331.             default:
  332.                 if (*p != *t)
  333.                     return FALSE;
  334.         }
  335.     }
  336.  
  337.     /* if end of text not reached then the pattern fails */
  338.     return !*t;
  339. }
  340.  
  341.  
  342. /*----------------------------------------------------------------------------
  343. *
  344. * recursively call regex_match with final segment of PATTERN and of TEXT.
  345. *
  346. ----------------------------------------------------------------------------*/
  347.  
  348. BOOLEAN regex_match_after_star (register char *p, register char *t)
  349. {
  350.     register BOOLEAN match;
  351.     register nextp;
  352.  
  353.     /* pass over existing ? and * in pattern */
  354.     while ( *p == '?' || *p == '*' ) {
  355.  
  356.         /* take one char for each ? */
  357.         if ( *p == '?' ) {
  358.  
  359.             /* if end of text then no match */
  360.             if ( !*t++ ) {
  361.                 return ABORT;
  362.             }
  363.         }
  364.  
  365.         /* move to next char in pattern */
  366.         p++;
  367.     }
  368.  
  369.     /* if end of pattern we have matched regardless of text left */
  370.     if ( !*p ) {
  371.         return TRUE;
  372.     }
  373.  
  374.     /* get the next character to match which must be a literal or '[' */
  375.     nextp = *p;
  376.     if ( nextp == '\\' )
  377.         nextp = p[1];
  378.  
  379.     /* Continue until we run out of text or definite result seen */
  380.     match = FALSE;
  381.     while ( match == FALSE ) {
  382.  
  383.         /* a precondition for matching is that the next character
  384.            in the pattern match the next character in the text or that
  385.            the next pattern is the beginning of a range.  Increment text
  386.            pointer as we go here */
  387.         if ( *p == *t || nextp == '[' ) {
  388.             match = regex_match(p, t);
  389.         }
  390.  
  391.         /* if the end of text is reached then no match */
  392.         if ( !*t++ ) match = ABORT;
  393.     }
  394.  
  395.     /* return result */
  396.     return match;
  397. }
  398.  
  399. /*----------------------------------------------------------------------------
  400. *
  401. * This is a shell to regex_match to return only a true BOOLEAN value
  402. *
  403. ----------------------------------------------------------------------------*/
  404.  
  405. BOOLEAN match( char *p, char *t)
  406. {
  407.     return ( regex_match(p,t) == TRUE ) ? TRUE : FALSE;
  408. }
  409.  
  410.  
  411. #ifdef TEST
  412.  
  413.     /*
  414.     * This test main expects as first arg the pattern and as second arg
  415.     * the match string.  Output is yaeh or nay on match.
  416.     */
  417.  
  418.     #include <stdio.h>
  419.  
  420.     int main(int argc, char *argv[])
  421.     {
  422.         if (argc == 3) {
  423.             if (is_pattern(argv[1])) {
  424.                 if (match(argv[1],argv[2])) {
  425.                     printf("    Match Successful\n");
  426.                 }
  427.                 else {
  428.                     printf("    Match Fails\n");
  429.                 }
  430.             }
  431.             else {
  432.                 printf("    Bad Pattern\n");
  433.             }
  434.         }
  435.         else printf("    Bad Breath\n");
  436.         return(0);
  437.     }
  438.  
  439. #endif
  440.  
  441.  
  442. ==================== END of Listing ====================
  443. -- 
  444. John Kercheval -- 127 NW Bowdion Pl #105 -- Seattle, WA  98107-4960
  445. Home(Voice): (206) 547-4676  --------  Work (Voice): (206) 324-0350
  446.