lex_regex.c

    #ifndef __LEX_REGEX_C__
    #define __LEX_REGEX_C__ 1

    // cc -o reg -DTEST_REGEX lex_regex.c
    // Can also be linked to as an object file, or included directly in another source file.

    // standard
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <ctype.h>
    // linux
    #include <regex.h>

    #include "lex_regex.h"

    const char *Parse_Version = "lex_regex.c: $Revision: 1.2 $";

    #ifndef FALSE
    #define FALSE (0!=0)
    #define TRUE (0==0)
    #endif

    #define MAX_MATCHES 32 // This is the maximum number allowed of matching () groups
                           // in the regex that may be used in replacement strings
                           // Although only \0 through \9 are currently accessible,

    // Note: ^ and $ in a regex match beginning and end of the string, not start-of-line and end-of-line
    //       However we assume that a string does not contain \n characters.  This will still work if
    //       it does but you had better know what you're doing - it would be more sensible to add
    //       an alternative version of this procedure that explicitly allowed newlines and used the
    //       eflag to handle a string which did not begin at the start of a line.  (If this
    //       all means nothing to you, go read "man regexec" until it does.

    int lex_regex(char *regexs, char *p, char *replace, char **matched) {

      // when a regex matches something in the source, this extracts the matched text as a string.
      // \0 is the entire string, \1 through \9 are the specific () grouped elements.
      auto char *extract(char *s, int start /* inclusive */, int end /* exclusive */) {
        char *t = malloc(end-start+1);
        int i;
        if (t == NULL) {
          fprintf(stderr, "lex_regex: running out of memory. Programmer may need to restructure the code.\n");
          return NULL;
        }
        for (i = 0; i < end-start; i++) t[i] = s[i+start];
        t[end-start] = '\0';
        return t;
      }

      regmatch_t matches[MAX_MATCHES];
      regex_t regex;
      int reti;
      char msgbuf[100];
      char *dest;

      if (p == NULL) return FALSE;
      if (strchr(p, '\n') != NULL) {
        fprintf(stderr, "lex_regex: this code probably does not do what you expect with respect to ^ and $ ...\n");
      }
      if (*regexs == '\0') return (TRUE); // empty string always matches

      /* Compile regular expression */
      if (regcomp(&regex, regexs, REG_EXTENDED) != 0) {
        fprintf(stderr, "lex_regex: programmer error - could not compile regex \"%s\" - we may have run out of malloc memory\n", regexs);
        // No need to regfree(&regex) - http://redhat.com/archives/libvir-list/2013-September/msg00276.html
        return FALSE;
      }

      /* Execute regular expression */
      reti = regexec(&regex, p, MAX_MATCHES, matches, 0);

      if (!reti) {
        char *original_replace = replace;
        if (matched != NULL) {
          dest = *matched;
          for (;;) {
            int c = *replace++;
            if (c == '\0') break;
            if (c == '\\') {
              int n = *replace++;
              char *insert;
              if (n < '0' || n > '9') {
                fprintf(stderr, "lex_regex: Bad replacement syntax '\\%c' in \"%s\" (should be \\0 to \\9)\n", n, original_replace);
                regfree(&regex);
                return (FALSE);
              }
              n -= '0';
              if (matches[n].rm_so == -1) {
                fprintf(stderr, "lex_regex: No such group '\\%c' for replacement in \"%s\"\n", n+'0', regexs);
                regfree(&regex);
                return (FALSE);
              }
              strcpy(dest, insert=extract(p, matches[n].rm_so, matches[n].rm_eo));
              dest += strlen(insert); // point to NUL
            } else {
              *dest++ = c;
            }
          }
          *dest = '\0';
        }
        regfree(&regex);
        return (TRUE);
      } else if (reti == REG_NOMATCH) {
        regfree(&regex);    /* Free memory allocated to the pattern buffer by regcomp() */
                            // NB http://redhat.com/archives/libvir-list/2013-September/msg00276.html
                            // only applies to failed regcomp, not failed regexec
        return (FALSE);
      }

      regerror(reti, &regex, msgbuf, sizeof(msgbuf));
      fprintf(stderr, "lex_regex: Regex match internal error: %s\n", msgbuf);
      fflush(stderr); fflush(stdout);
      return (FALSE);
    }

    #ifdef TEST_REGEX
    int main(int argc, char **argv) {
      char *matched = calloc(1, 128);
      if (lex_regex("[A-Z][A-Z]*", "... FRED ok?", "\\0", &matched)) fprintf(stderr, "Passed: %s\n", matched); else fprintf(stderr, "Failed\n");
      if (lex_regex("[A-Z][A-Z]*", "... fred ok?", "\\0", &matched)) fprintf(stderr, "Failed\n"); else fprintf(stderr, "Passed\n");
      if (lex_regex("[A-Z][A-Z]*", "... FRED ok?", "\\A", &matched)) fprintf(stderr, "Failed\n"); else fprintf(stderr, "Passed\n");
      if (lex_regex("^([^A-Z]*)([A-Z][A-Z]*)(.*)", "... FRED ok?", "\\1[[\\2]]\\3", &matched)) fprintf(stderr, "Passed: %s\n", matched); else fprintf(stderr, "Failed\n");
      if (lex_regex("[A-Z][A-Z]*", "... FRED ok?", "\\7", &matched)) fprintf(stderr, "Failed\n"); else fprintf(stderr, "Passed\n");
      if (lex_regex("[A-Z][A-Z]*", "... FRED ok?", "\\0", NULL)) fprintf(stderr, "Passed\n"); else fprintf(stderr, "Failed\n");
      fflush(stderr);

      exit(EXIT_SUCCESS);
      return EXIT_FAILURE;
    }
    #endif

    #endif // __LEX_REGEX_C__