home *** CD-ROM | disk | FTP | other *** search
/ Mac-Source 1994 July / Mac-Source_July_1994.iso / HyperCard / reg_exp.XFCN / regexp.c < prev    next >
Text File  |  1989-09-18  |  16KB  |  564 lines

  1. /*
  2. | | Regular Expression Evaluator:
  3. | |
  4. | | Greg Anderson
  5. | | 29 Kerr Hall
  6. | | Social Sciences Computing
  7. | | University of California, Santa Cruz
  8. | | sirkm@ssyx
  9. | |
  10. | | For use with HyperCard XCMDs, XFCNs, and possibly other things.
  11. | |
  12. | | Make this file part of your XCMD project.
  13. | | #include "regexp.h" in any of your files that may use routines
  14. | | from this package.
  15. | |
  16. | | The regular expressions this package matches is as follows:
  17. | |
  18. | | c            Any ordinary character 'c' not listed below matches that
  19. | |                character.
  20. | |
  21. | |    \c            A backslash (\) followed by a special character (one of
  22. | |                '.', '*', '+', '[' and '\') matches the special character
  23. | |                (i.e., the special meaning is removed).
  24. | |
  25. | | .            A period (.) matches any single character except RETURN.
  26. | |
  27. | |    [string]    A non-empty string of characters enclosed in square
  28. | |                brackets matches any single character found in the set.
  29. | |                If the first character of such a string is ^, then
  30. | |                any single character NOT in the set is matched.  '^'
  31. | |                looses its special meaning if it comes first in the
  32. | |                string.
  33. | |                The character '-' indicates a range of characters;
  34. | |                for example, [a-z] will match any lowercase letter.
  35. | |                '-' looses its special meaning if it comes first (or
  36. | |                after a leading '^') or last in the string.
  37. | |
  38. | | c*            Any one-character regular expression followed by a *
  39. | |                matches zero or more occurances of the single character.
  40. | |                If there is any choice, the longest leftmost string
  41. | |                that matches is returned.
  42. | |
  43. | | c+            Like '*', but matches one or more occurances of the
  44. | |                single character regular expression.
  45. | |
  46. | |    ^            A caret (^) at the beginning of an entire regular
  47. | |                expression constrains that regular expression to only
  48. | |                match strings found at the beginning of a line.
  49. | |
  50. | | $            A currency symbol ($) at the end of an entire line
  51. | |                constrains that regular expression to only match strings
  52. | |                found at the end of a line.
  53. | |
  54. | | The following regular expressions are NOT supported:
  55. | |
  56. | | \<            Beginning of word
  57. | | \>            End of word
  58. | | \( ... \)    "..." is treated as a regular expression
  59. | | \{n,m\}        Repeated matches of previous regular expression.
  60. | | 
  61.  */
  62. #include <MacTypes.h>
  63. #include <FileMgr.h>
  64. #include "regexp.h"
  65.  
  66. #define TRUE    1
  67. #define FALSE    0
  68.  
  69. #define toupper(c) ((c>='a')&&(c<='z') ? (c-('a'-'A')) : c)
  70.  
  71. int regexp_flags;
  72.  
  73. /*-----------------------------------------------------------------
  74. | end_of_line:
  75. |
  76. | Checks to see if the given character pointer points at the end
  77. | of a line.
  78. |
  79. | Lines end in either a return character (\r) or a null.
  80. |
  81. | If MULTILINE is true, then logical lines may be continued on
  82. | multiple physical lines if succeeding physical lines are indented.
  83. |
  84. | If FOLDEDLINE is true, then logical lines may be continued on
  85. | multiple physical lines by preceeding each return character with
  86. | a backslash.
  87. |
  88. | If NOBREAKS is true, then there are no line breaks; the entire text
  89. | field is treated as one long line.  ^ matches only at the beginning
  90. | of the text field, and $ matches only at the end.
  91. |
  92. | INPUTS:        line_ptr:    A pointer into the line
  93. |
  94. | OUTPUTS:        None save the return value.
  95. |
  96. | RETURNS:        TRUE        End of line reached
  97. |                FALSE        Not at the end of the line
  98. -----------------------------------------------------------------*/
  99. int end_of_line(line_ptr)
  100.     char        *line_ptr;
  101. {
  102.     if( !(*line_ptr) )                return(TRUE);
  103.     if( regexp_flags & NOBREAKS )    return(FALSE);
  104.     if( *line_ptr != '\r' )            return(FALSE);
  105.     if( !(*(line_ptr+1)) )            return(TRUE);
  106.     
  107.     if( (regexp_flags & MULTILINE) && (*(line_ptr+1) <= ' ') )
  108.         return(FALSE);
  109.     
  110.     if( (regexp_flags & FOLDEDLINE) && (*(line_ptr-1) == '\\') )
  111.         return(FALSE);
  112.     
  113.     return(TRUE);
  114. }
  115.  
  116. /*-----------------------------------------------------------------
  117. | find_regexp:
  118. |
  119. | Searches for occurances of 'regexp' inside of 'line'.
  120. |
  121. | 'regexp' must have had some prior processing--leading ^ and
  122. | trailing '$' should be stripped before calling.  Note that
  123. | 'greplen' will do this preprocessing.
  124. |
  125. | INPUTS:        regexp:        A pointer to the regular expression
  126. |                line:        A pointer to the line to search
  127. |                start:        If zero, then 'regexp' must match 'line'
  128. |                            starting with the first character of 'line'.
  129. |                end:        If zero, then 'regexp' must also match
  130. |                            'line' all the way to the end.
  131. |
  132. | OUTPUTS:        start:        If specified, start will be changed to
  133. |                            point to the first character in 'line'
  134. |                            that matched 'regexp'.  If 'regexp'
  135. |                            could be matched in multiple ways
  136. |                            (due to wildcards), the leftmost string
  137. |                            is returned.
  138. |                end:        If specified, end will be changed to
  139. |                            point to the first character in 'line'
  140. |                            that was not part of 'regexp'.  If
  141. |                            'regexp' could be matched in multiple ways
  142. |                            (due to wildcards), the longest string
  143. |                            that matches is selected.
  144. |
  145. | RETURNS:        TRUE        'regexp' was found in 'line'
  146. |                FALSE        'regexp' not found--'start' and 'end' are
  147. |                            invalid.
  148. -----------------------------------------------------------------*/
  149. int find_regexp(regexp,line,start,end)
  150.     char        *regexp,
  151.                 *line,
  152.                 **start,
  153.                 **end;
  154. {
  155.     if( !start )
  156.         return( strgrep(regexp,line,end) );
  157.         
  158.     while( !end_of_line(line) )
  159.     {
  160.         if( strgrep(regexp,line,end) )
  161.         {
  162.             *start = line;
  163.             return(TRUE);
  164.         }
  165.         ++line;
  166.     }
  167.     /*
  168.     | |  Special case -- searching for the end of a line and nothing else.
  169.      */
  170.     if( !(*regexp) && !(*end) )
  171.     {
  172.         *start = line;
  173.         return(TRUE);
  174.     }
  175.     return(FALSE);
  176. }
  177.  
  178. /*-----------------------------------------------------------------
  179. | strgrep:
  180. |
  181. | Checks to see if the regular expression 'regexp' matches the
  182. | search line provided.  The match must be EXACT:  'line' is not
  183. | searched for occurances of 'regexp', it is only checked to see
  184. | if 'regexp' matches 'line' starting with the first character.
  185. | ('line' may have unmatched trailing characters, however.)
  186. |
  187. | INPUTS:        regexp:        A pointer to the regular expression
  188. |                line:        A pointer to the line to search
  189. |                end:        If zero, then 'regexp' must also match
  190. |                            'line' all the way to the end.
  191. |
  192. | OUTPUTS:        end:        If specified, end will be changed to
  193. |                            point to the first character in 'line'
  194. |                            that was not part of 'regexp'.  If
  195. |                            'regexp' could be matched in multiple ways
  196. |                            (due to wildcards), the longest string
  197. |                            that matches is selected.
  198. -----------------------------------------------------------------*/
  199. int strgrep(regexp,line,end)
  200.     char        *regexp,
  201.                 *line,
  202.                 **end;
  203. {
  204.     char        *last = 0;
  205.     
  206.     /*
  207.     | |  Search over every character in the comparitor string
  208.      */
  209.     while( *regexp )
  210.     {
  211.         /*
  212.         | |  If we have reached the end of the line but there are
  213.         | |  still characters in the regular expression, then the
  214.         | |  search has probably failed.
  215.         | |
  216.         | |  Wildcards in the regular expression can make things
  217.         | |  a bit trickier, though.
  218.          */
  219.         if( end_of_line(line) )    
  220.         {
  221.             if( strcmp( regexp,"*" ) == 0 ) break;
  222.             if( strcmp( regexp+1,"*" ) == 0 ) break;
  223.             return(FALSE);
  224.         }
  225.         
  226.         if( !chargrep(®exp,&line,&last) )
  227.         {
  228.             /*
  229.             | |  The search character does not match:  if the next regular
  230.             | |  expression is not a '*', then the search has FAILED.
  231.              */
  232.             if( *regexp != '*' )
  233.                 return(FALSE);
  234.             else
  235.             {
  236.                 /*
  237.                 | |  Back up the line pointer so that the same
  238.                 | |  character may be checked against the next
  239.                 | |  element in the regular expression string
  240.                  */
  241.                 last = 0;
  242.                 --line;
  243.                 ++regexp;
  244.             }
  245.         }
  246.     }
  247.     /*
  248.     | |  If we are searching to the END of the line, then the input
  249.     | |  line must be out of valid characters in order to return
  250.     | |  a match.
  251.      */
  252.     if( !end )
  253.         return( end_of_line(line) );
  254.     
  255.     *end = line;
  256.     return(TRUE);
  257. }
  258.  
  259. /*-----------------------------------------------------------------
  260. | chargrep:
  261. |
  262. | Compares just one character in the regular expression
  263. |
  264. | INPUTS:    All inputs are pointers to pointers to strings, as
  265. |            chargrep will advance these pointers after comparing
  266. |            them.
  267. |
  268. |            regexp:        Points into the regular expression
  269. |            line:        Points into the line being searched
  270. |            last:        Points at the last character checked in
  271. |                        the regular expression; usually = (*regexp-1).
  272. |
  273. | OUTPUTS:    regexp:        Advanced to the next char in the reg exp.
  274. |            line:        Advanced to the next char in search line
  275. |            last:        Set to the initial value of 'regexp'.
  276. -----------------------------------------------------------------*/
  277. int chargrep(regexp,line,last)
  278.     char        **regexp,
  279.                 **line,
  280.                 **last;
  281. {
  282.     char        c        = **line,
  283.                 *look    = *regexp;
  284.     int            match;
  285.  
  286.     switch( **regexp )
  287.     {
  288.     /*
  289.     | |  Set search?
  290.      */
  291.     case '[':
  292.                 *last = look;
  293.                 ++(*line);
  294.                 return( searchset(regexp,c) );
  295.     /*
  296.     | |  '.' Wildcard matches any single character except newline / return
  297.     | |  c can only be a newline/return if one of the flags -m, -f or -b
  298.     | |  was specified.
  299.      */
  300.     case '.':
  301.                 if( (c != '\r') && (c != '\n') )
  302.                     c = '.';
  303.                 break;
  304.     /*
  305.     | |  Wildcards:
  306.      */
  307.     case '*':
  308.     case '+':
  309.                 /*
  310.                 | |  When a wild card is found, the line is scanned
  311.                 | |  until the last part of the regular expression
  312.                 | |     can be found somewhere in the line.
  313.                 | |
  314.                 | |  If the last part of the regular expression is
  315.                 | |     found multiple times, the longest applicable
  316.                 | |  match is returned.
  317.                  */
  318.                 if( !(*last) ) *last = ".";
  319.                 match = wild_scan(*regexp+1,line,*last);
  320.                 /*
  321.                 | |  Fixup for '*'-style searches.
  322.                  */
  323.                 if( !match && **regexp == '*' )
  324.                     match = strgrep(*regexp+1,(*line-1),line);
  325.                 ++(*line);
  326.                 *regexp = "";
  327.                 return(match);
  328.     /*
  329.     | |  Backslash escape: next character interpreted literally
  330.     | |
  331.     | |  Note:  Should check for \nnn (octal representation)
  332.      */
  333.     case '\\':
  334.                 ++(*regexp);
  335.                 break;
  336.     }
  337.     
  338.     /*
  339.     | |  At this point, 'c' contains the character from the search
  340.     | |  line that must be matched in the regular expression
  341.     | |  (EXACTLY).  If c does not match the regular expression,
  342.     | |  then the search still will not fail if the next character
  343.     | |  in the regexp is a '*'
  344.      */
  345.     if( regexp_flags & IGNORE )
  346.         c = toupper(c);
  347.     match = (**regexp == c);
  348.     /*
  349.     | |  Set 'last' = the initial value of the regular expression ptr
  350.     | |  and advance the regexp and line pointers.
  351.      */
  352.     ++(*regexp);
  353.     ++(*line);
  354.     *last = look;
  355.     
  356.     return(match);
  357. }
  358.  
  359. /*-----------------------------------------------------------------
  360. | searchset:
  361. |
  362. | Compares a [list] in the regular expression with just one
  363. | character in the input line.
  364. |
  365. | INPUTS:    regexp:        A pointer to a pointer into the regular
  366. |                        expression
  367. |            check_c:    The character to check.
  368. |            
  369. | Enter with a pointer to a pointer into the regular expression
  370. | Upon entry, the regexp pointer should point at the '['.
  371. | Upon exit, it will point to the character AFTER the ']'.
  372. |
  373. | RETURNS:    TRUE:        'check_c' was in the set
  374. |            FALSE:        'check_c' was not in the set
  375. -----------------------------------------------------------------*/
  376. int searchset(regexp,check_c)
  377.     char        **regexp,
  378.                 check_c;
  379. {
  380.     char        c,                    /* The char from the set    */
  381.                 lc = 0;                /* The last char from set    */
  382.     int            found    = 0,        /* Flag:  found check_c?    */
  383.                 invert    = 0;        /* Flag:  inverted search    */
  384.     
  385.     /*
  386.     | |  Advance past the '[' and check for a leading '^'
  387.      */
  388.     ++(*regexp);
  389.     c = **regexp;
  390.     if( c == '^' )
  391.     {
  392.         ++invert;
  393.         ++(*regexp);
  394.         c = **regexp;
  395.     }
  396.     ++(*regexp);
  397.     do
  398.     {
  399.         if( regexp_flags & IGNORE )
  400.             c = toupper(c);
  401.         if( (c == '-') && lc )
  402.         {
  403.             /*
  404.             | |  Check if the character lies within a range
  405.              */
  406.             if( (lc <= check_c) && (**regexp >= check_c) )
  407.                 found = 1;
  408.             lc = 0;
  409.         }
  410.         /*
  411.         | |  Check if this character in the regexp list matches the
  412.         | |  character being checked.
  413.          */
  414.         else if( c == check_c )
  415.             found = 1;
  416.         lc = c;
  417.     } while( (c = *((*regexp)++) ) != ']' );
  418.     
  419.     return( found ^ invert );
  420. }
  421.  
  422. /*-----------------------------------------------------------------
  423. | wild_scan:
  424. |
  425. | Regular expression wildcard handling.  Searches for the last part
  426. | of a regular expression (after a wildcard) in a line.
  427. |
  428. | INPUTS:    regexp:        A pointer to a pointer into the regular
  429. |                        expression (points to the character after
  430. |                        the wildcard)
  431. |            line:        A pointer to a pointer into the line being
  432. |                        searched (points at the character to start
  433. |                        searching at)
  434. |            last:        A pointer to the last character in the regexp
  435. |                        before the wildcard.
  436. |            
  437. | OUTPUTS:    regexp:        ALWAYS points to the null terminator at the
  438. |                        end of regexp.
  439. |            line:        points to the last character matched, if there
  440. |                        was a match.  Otherwise unchanged.
  441. |
  442. | RETURNS:    TRUE:        The pattern matched; line points to the
  443. |                        first character not matched.
  444. |            FALSE:        The pattern did not match.
  445. -----------------------------------------------------------------*/
  446. wild_scan(regexp,line,last)
  447.     char    *regexp,
  448.             **line,
  449.             *last;
  450. {
  451.     char    *scan    =    *line,
  452.             *copy_of_last,
  453.             *dummy;
  454.     int        result    =    FALSE;
  455.     
  456.     while( !end_of_line(scan) )
  457.     {
  458.         /*
  459.         | |  If the last part of the regexp is matched at the current
  460.         | |  possition of 'scan', then remember that a match has been
  461.         | |  found and keep scanning.
  462.         | |
  463.         | |  If (and only if) regexp is found, strgrep changes 'line' to
  464.         | |  point to the character after the last one matched by regexp.
  465.          */
  466.         if( strgrep(regexp,scan,line) )
  467.             result = TRUE;
  468.         /*
  469.         | |  If the character pointed to by scan does not match
  470.         | |  the regexp character before the wildcard, then
  471.         | |  the scan is terminated.
  472.          */
  473.         copy_of_last = last;
  474.         if( !chargrep(©_of_last,&scan,&dummy) ) break;
  475.     }
  476.     return(result);
  477. }
  478.  
  479. /*-----------------------------------------------------------------
  480. | greplen:
  481. |
  482. | Finds the length of a grep search string.  In the case of
  483. | strings containing wild cards, returns the MINIMUM length string
  484. | that could match the search string.
  485. |
  486. | greplen is also responsible for finding the occurance of ^ and $
  487. | at the beginning and end of the string (respectively).  If these
  488. | flags are specified, greplen notes this fact & then strips them
  489. | from the passed searchstring.
  490. |
  491. | If the grep search string is not valid, greplen returns -1.
  492. -----------------------------------------------------------------*/
  493. int greplen(searchstring)
  494.     char        **searchstring;
  495. {
  496.     char        c,
  497.                 *string;
  498.     int            len = 0;
  499.  
  500.     if( regexp_flags & IGNORE )
  501.         MakeUpper(*searchstring);
  502.  
  503.     /*
  504.     | |  Does the search string begin with '^'?
  505.      */
  506.     if( **searchstring == '^' )
  507.     {
  508.         ++(*searchstring);
  509.         regexp_flags |= BEGINFLAG;
  510.     }
  511.     string = *searchstring;
  512.     /*
  513.     | |  Count the characters in the search string
  514.      */
  515.     while( c = *string++ )
  516.     {
  517.         switch( c )
  518.         {
  519.         /*
  520.         | |  Since '*' might match zero characters, the length of
  521.         | |  the string is decremented by one, since the previous
  522.         | |  character does not have to be matched.
  523.          */
  524.         case '*':
  525.                     if( len ) --len;
  526.                     break;
  527.         /*
  528.         | |  If a '$' is found at the end, then set the 'END' flag.
  529.         | |  Otherwise, count the $ as a search character.
  530.          */
  531.         case '$':
  532.                     if( (*string) == 0 )
  533.                     {
  534.                         *(string-1) = 0;
  535.                         regexp_flags |= ENDFLAG;
  536.                     }
  537.                     else
  538.                         ++len;
  539.                     break;
  540.         /*
  541.         | |  Scan through an entire [string], counting it as only
  542.         | |  one character.  When this loop exits, string points to
  543.         | |  the ']', which will be counted in the search length on
  544.         | |  the next pass of the while() loop.
  545.          */
  546.         case '[':
  547.                     if( *string++ < ' ') return(-1);
  548.                     while( *string != ']' )
  549.                         if( *string++ < ' ' ) return(-1);
  550.                     break;
  551.         /*
  552.         | |  Backslash falls through to the default case, but it
  553.         | |  first advances past the character after the backslash
  554.          */
  555.         case '\\':
  556.                     if( *string++ < ' ') return(-1);
  557.         default:
  558.                     ++len;
  559.         }
  560.     }
  561.  
  562.     return(len);
  563. }
  564.