/*
 * Copyright (C) 1997-2004, Michael Jennings
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies of the Software, its documentation and marketing & publicity
 * materials, and acknowledgment shall be given in the documentation, materials
 * and software packages that this Software was used.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

/**
 * @file strings.c
 * String Utility Routine Source File
 *
 * This file contains string utility functions.
 *
 * @author Michael Jennings <mej@eterm.org>
 */

static const char __attribute__((unused)) cvs_ident[] = "$Id: strings.c 51650 2010-08-26 01:34:13Z lucas $";

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <libast_internal.h>

#if !(HAVE_MEMMEM)
/* Find first occurance of bytestring needle of size needlelen in memory region
   haystack of size haystacklen */
void *
memmem(const void *haystack, register size_t haystacklen, const void *needle, register size_t needlelen)
{
    register char *hs = (char *) haystack;
    register char *n = (char *) needle;
    register unsigned long i;
    register size_t len = haystacklen - needlelen;

    REQUIRE_RVAL(needle != (spif_ptr_t) NULL, (spif_ptr_t) NULL);
    REQUIRE_RVAL(haystack != (spif_ptr_t) NULL, (spif_ptr_t) NULL);
    REQUIRE_RVAL(needlelen > 0, (spif_ptr_t) NULL);
    REQUIRE_RVAL(haystacklen > 0, (spif_ptr_t) NULL);
    REQUIRE_RVAL(haystacklen > needlelen, (spif_ptr_t) NULL);
    for (i = 0; i < len; i++) {
        if (!memcmp(hs + i, n, needlelen)) {
            return (hs + i);
        }
    }
    return (NULL);
}
#endif

#if !(HAVE_STRNLEN)
size_t
strnlen(register const char *s, size_t maxlen)
{
    register size_t n;

    REQUIRE_RVAL(s, (size_t) 0);
    for (n = 0; *s && n < maxlen; s++, n++);
    return n;
}
#endif

#if !(HAVE_USLEEP)
void
usleep(unsigned long usec)
{
    struct timeval delay;

    delay.tv_sec = 0;
    delay.tv_usec = usec;
    select(0, NULL, NULL, NULL, &delay);

}

#endif

/***** Not needed ******
#if !(HAVE_NANOSLEEP)
__inline__ void
nanosleep(unsigned long nsec) {
    usleep(nsec / 1000);
}
#endif
************************/

#if !(HAVE_STRCASESTR)
char *
strcasestr(const char *haystack, register const char *needle)
{
    register const spif_uchar_t *t;
    register size_t len;

    REQUIRE_RVAL(needle != (spif_ptr_t) NULL, (spif_ptr_t) NULL);
    REQUIRE_RVAL(haystack != (spif_ptr_t) NULL, (spif_ptr_t) NULL);
    len = strlen(needle);
    for (t = (spif_uchar_t *) haystack; t && *t; t++) {
        if (!strncasecmp(t, needle, len)) {
            return ((char *) t);
        }
    }
    return (NULL);
}
#endif

#if !(HAVE_STRCASECHR)
char *
strcasechr(const char *haystack, register const char needle)
{
    register const char *t;

    REQUIRE_RVAL(haystack != (spif_ptr_t) NULL, (spif_ptr_t) NULL);
    for (t = haystack; t && *t; t++) {
        if (tolower(*t) == tolower(needle)) {
            return ((char *) t);
        }
    }
    return (NULL);
}
#endif

#if !(HAVE_STRCASEPBRK)
char *
strcasepbrk(const char *haystack, register const char *needle)
{
    register const char *t;

    REQUIRE_RVAL(needle != (spif_ptr_t) NULL, (spif_ptr_t) NULL);
    REQUIRE_RVAL(haystack != (spif_ptr_t) NULL, (spif_ptr_t) NULL);
    for (t = haystack; t && *t; t++) {
        if (strcasechr(needle, *t)) {
            return ((char *) t);
        }
    }
    return (NULL);
}
#endif

#if !(HAVE_STRREV)
char *
strrev(register char *str)
{
    register int i, j;

    REQUIRE_RVAL(str != (spif_ptr_t) NULL, (spif_ptr_t) NULL);
    i = strlen(str);
    for (j = 0, i--; i > j; i--, j++) {
        (void) SWAP(str[j], str[i]);
    }
    return (str);

}
#endif

#if !(HAVE_STRSEP)
char *
strsep(char **str, register char *sep)
{
    register char *s = *str;
    char *sptr;

    REQUIRE_RVAL(str != (spif_ptr_t) NULL, (spif_ptr_t) NULL);
    REQUIRE_RVAL(sep != (spif_ptr_t) NULL, (spif_ptr_t) NULL);
    D_STRINGS(("strsep(%s, %s) called.\n", *str, sep));
    sptr = s;
    for (; *s && !strchr(sep, *s); s++);
    if (!*s) {
        if (s != sptr) {
            *str = s;
            D_STRINGS(("Reached end of string with token \"%s\" in buffer\n", sptr));
            return (sptr);
        } else {
            D_STRINGS(("Reached end of string\n"));
            return ((char *) NULL);
        }
    }
    *s = 0;
    *str = s + 1;
    D_STRINGS(("Got token \"%s\", *str == \"%s\"\n", sptr, *str));
    return (sptr);
}
#endif

/**
 * Safer strncpy() with no NUL padding or wasted calculations.
 */
spif_bool_t
spiftool_safe_strncpy(spif_charptr_t dest, const spif_charptr_t src, spif_int32_t size)
{
    spif_char_t c;
    spif_charptr_t s = src, pbuff = dest;
    spif_charptr_t max_pbuff = dest + size - 1;

    ASSERT_RVAL(!SPIF_PTR_ISNULL(dest), FALSE);
    REQUIRE_RVAL(!SPIF_PTR_ISNULL(src), FALSE);
    REQUIRE_RVAL(size > 0, FALSE);

    for (; (c = *s) && (pbuff < max_pbuff); s++, pbuff++) {
        *pbuff = c;
    }
    *pbuff = 0;
    return ((c == 0) ? (TRUE) : (FALSE));
}

/**
 * Variant of strncat() which uses the safe strncpy() replacement above.
 */
spif_bool_t
spiftool_safe_strncat(spif_charptr_t dest, const spif_charptr_t src, spif_int32_t size)
{
    spif_int32_t len;

    ASSERT_RVAL(!SPIF_PTR_ISNULL(dest), FALSE);
    REQUIRE_RVAL(!SPIF_PTR_ISNULL(src), FALSE);
    REQUIRE_RVAL(size > 0, FALSE);

    len = strnlen((char *) dest, size);
    if (len >= size) {
        return FALSE;
    } else {
        return spiftool_safe_strncpy(dest + len, src, size - len);
    }
}

/**
 * Returns a portion of a larger string.
 */
spif_charptr_t
spiftool_substr(spif_charptr_t str, spif_int32_t idx, spif_int32_t cnt)
{
    spif_charptr_t newstr;
    spif_uint32_t start_pos, char_count;
    spif_uint32_t len;

    REQUIRE_RVAL(str != (spif_charptr_t) NULL, (spif_charptr_t) NULL);

    len = (spif_uint32_t) strlen((char *) str);

    if (idx < 0) {
        start_pos = len + idx;
    } else {
        start_pos = idx;
    }
    REQUIRE_RVAL(start_pos < len, (spif_charptr_t) NULL);

    if (cnt <= 0) {
        char_count = len - start_pos + cnt;
    } else {
        char_count = cnt;
    }
    UPPER_BOUND(char_count, len - start_pos);

    newstr = (spif_charptr_t) MALLOC(char_count + 1);
    memcpy(newstr, str + start_pos, char_count);
    newstr[char_count] = 0;
    return newstr;
}

#if HAVE_REGEX_H
/**
 * Compare a string to a regular expression.
 */
spif_bool_t
spiftool_regexp_match(const spif_charptr_t str, const spif_charptr_t pattern)
{
    static regex_t *rexp = NULL;
    register int result;
    char errbuf[256];

    if (!str) {
        /* If we're passed a NULL str, we want to free our static storage. */
        FREE(rexp);
        return FALSE;
    } else if (!rexp) {
        /* If we don't have static storage yet, make some. */
        rexp = (regex_t *) MALLOC(sizeof(regex_t));
    }

    if (pattern) {
        /* We have a pattern, so we need to compile it. */
        if ((result = regcomp(rexp, pattern, REG_EXTENDED)) != 0) {
            regerror(result, rexp, errbuf, 256);
            libast_print_error("Unable to compile regexp %s -- %s.\n", pattern, errbuf);
            return (FALSE);
        }
    }

    /* Compare the string to the compiled pattern. */
    if (((result = regexec(rexp, str, (size_t) 0, (regmatch_t *) NULL, 0)) != 0)
        && (result != REG_NOMATCH)) {
        regerror(result, rexp, errbuf, 256);
        libast_print_error("Error testing input string %s -- %s.\n", str, errbuf);
        return (FALSE);
    }
    return ((result == REG_NOMATCH) ? (FALSE) : (TRUE));
}

/**
 * Thread-safe way to compare a string to a regular expression.
 */
spif_bool_t
spiftool_regexp_match_r(register const spif_charptr_t str, register const spif_charptr_t pattern, register regex_t **rexp)
{
    register int result;
    char errbuf[256];

    ASSERT_RVAL(rexp != NULL, FALSE);
    if (!*rexp) {
        *rexp = (regex_t *) MALLOC(sizeof(regex_t));
    }

    if (pattern) {
        if ((result = regcomp(*rexp, pattern, REG_EXTENDED)) != 0) {
            regerror(result, *rexp, errbuf, 256);
            libast_print_error("Unable to compile regexp %s -- %s.\n", pattern, errbuf);
            FREE(*rexp);
            return (FALSE);
        }
    }

    if (((result = regexec(*rexp, str, (size_t) 0, (regmatch_t *) NULL, 0))
         != 0) && (result != REG_NOMATCH)) {
        regerror(result, *rexp, errbuf, 256);
        libast_print_error("Error testing input string %s -- %s.\n", str, errbuf);
        return (FALSE);
    }
    return ((result == REG_NOMATCH) ? (FALSE) : (TRUE));
}
#endif

#define IS_DELIM(c)  ((delim) ? (strchr((char *)delim, (c))) : (isspace(c)))
#define IS_QUOTE(c)  (quote && quote == (c))

spif_charptr_t *
spiftool_split(const spif_charptr_t delim, const spif_charptr_t str)
{
    spif_charptr_t *slist;
    register spif_charptr_t pstr;
    register spif_charptr_t pdest;
    char quote = 0;
    unsigned short cnt = 0;
    unsigned long len;

    REQUIRE_RVAL(str != NULL, (spif_charptr_t *) NULL);

    if (!(slist = (spif_charptr_t *)MALLOC(sizeof(spif_charptr_t)))) {
        libast_print_error("split():  Unable to allocate memory -- %s\n", strerror(errno));
        return ((spif_charptr_t *) NULL);
    }

    /* Before we do anything, skip leading "whitespace." */
    for (pstr = (spif_charptr_t) str; *pstr && IS_DELIM(*pstr); pstr++);

    /* The outermost for loop is where we traverse the string.  Each new
       word brings us back to the top where we resize our string list. */
    for (; *pstr; cnt++) {
        /* First, resize the list to two bigger than our count.  Why two?
           One for the string we're about to do, and one for a trailing NULL. */
        if (!(slist = (spif_charptr_t *)REALLOC(slist, sizeof(spif_charptr_t) * (cnt + 2)))) {
            libast_print_error("split():  Unable to allocate memory -- %s\n", strerror(errno));
            return ((spif_charptr_t *) NULL);
        }

        /* The string we're about to create can't possibly be larger than the remainder
           of the string we have yet to parse, so allocate that much space to start. */
        len = strlen((char *) pstr) + 1;
        if (!(slist[cnt] = (spif_charptr_t)MALLOC(len))) {
            libast_print_error("split():  Unable to allocate memory -- %s.\n", strerror(errno));
            return ((spif_charptr_t *) NULL);
        }
        pdest = slist[cnt];

        /* This for loop is where we process each character. */
        for (; *pstr && (quote || !IS_DELIM(*pstr));) {
            if (*pstr == '\"' || *pstr == '\'') {
                /* It's a quote character, so set or reset the quote variable. */
                if (quote) {
                    if (quote == *pstr) {
                        quote = 0;
                    } else {
                        /* It's a single quote inside double quotes, or vice versa.  Leave it alone. */
                        *pdest++ = *pstr++;
                    }
                } else {
                    quote = *pstr;
                }
                pstr++;
            } else {
                /* Handle any backslashes that are escaping delimiters or quotes. */
                if ((*pstr == '\\') && (IS_DELIM(*(pstr + 1)) || IS_QUOTE(*(pstr + 1)))) {
                    /* Incrementing pstr here moves us past the backslash so that the line
                       below will copy the next character to the new token, no questions asked. */
                    pstr++;
                }
                *pdest++ = *pstr++;
            }
        }
        /* Add the trailing \0 to terminate the new string. */
        *pdest = 0;

        /* Reallocate the new string to be just the right size. */
        len = strlen((char *) slist[cnt]) + 1;
        slist[cnt] = (spif_charptr_t) REALLOC(slist[cnt], len);

        /* Move past any trailing "whitespace." */
        for (; *pstr && IS_DELIM(*pstr); pstr++);
    }
    if (cnt == 0) {
        FREE(slist);
        return NULL;
    } else {
        /* The last element of slist[] should be NULL. */
        slist[cnt] = 0;
        return slist;
    }
}

spif_charptr_t *
spiftool_split_regexp(const spif_charptr_t regexp, const spif_charptr_t str)
{
    USE_VAR(regexp);
    USE_VAR(str);
    return (NULL);
}

spif_charptr_t 
spiftool_join(spif_charptr_t sep, spif_charptr_t *slist)
{
    register unsigned long i;
    size_t len, slen;
    spif_charptr_t new_str;

    ASSERT_RVAL(slist != (spif_ptr_t) NULL, (spif_ptr_t) NULL);
    REQUIRE_RVAL(*slist != (spif_ptr_t) NULL, (spif_ptr_t) NULL);
    if (!sep) {
        sep = SPIF_CHARPTR("");
    }
    slen = strlen((char *) sep);
    for (i = len = 0; slist[i]; i++) {
        len += strlen((char *) slist[i]);
    }
    len += slen * (i - 1);
    new_str = (spif_charptr_t) MALLOC(len + 1);
    strcpy((char *) new_str, (char *) slist[0]);
    for (i = 1; slist[i]; i++) {
        if (slen) {
            strcat((char *) new_str, (char *) sep);
        }
        strcat((char *) new_str, (char *) slist[i]);
    }
    return new_str;
}

/* Return malloc'd pointer to index-th word in str.  "..." counts as 1 word. */
#undef IS_DELIM
#define IS_DELIM(c)  (delim ? ((c) == delim) : isspace(c))

spif_charptr_t 
spiftool_get_word(unsigned long index, const spif_charptr_t str)
{
    spif_charptr_t tmpstr;
    char delim = 0;
    register unsigned long i, j, k;

    ASSERT_RVAL(str != (spif_ptr_t) NULL, (spif_ptr_t) NULL);
    k = strlen((char *) str) + 1;
    if (!(tmpstr = (spif_charptr_t)MALLOC(k))) {
        libast_print_error("get_word(%lu, %s):  Unable to allocate memory -- %s.\n", index, str, strerror(errno));
        return ((spif_charptr_t) NULL);
    }
    *tmpstr = 0;
    for (i = 0, j = 0; j < index && str[i]; j++) {
        for (; isspace(str[i]); i++);
        switch (str[i]) {
          case '\"':
              delim = '\"';
              i++;
              break;
          case '\'':
              delim = '\'';
              i++;
              break;
          default:
              delim = 0;
        }
        for (k = 0; str[i] && !IS_DELIM(str[i]);) {
            if (str[i] == '\\') {
                if (str[i + 1] == '\'' || str[i + 1] == '\"') {
                    i++;
                }
            }
            tmpstr[k++] = str[i++];
        }
        switch (str[i]) {
          case '\"':
          case '\'':
              i++;
              break;
        }
        tmpstr[k] = 0;
    }

    if (j != index) {
        FREE(tmpstr);
        D_STRINGS(("get_word(%lu, %s) returning NULL.\n", index, str));
        return ((spif_charptr_t) NULL);
    } else {
        tmpstr = (spif_charptr_t) REALLOC(tmpstr, strlen((char *) tmpstr) + 1);
        D_STRINGS(("get_word(%lu, %s) returning \"%s\".\n", index, str, tmpstr));
        return (tmpstr);
    }
}

/* Return pointer into str to index-th word in str.  "..." counts as 1 word. */
spif_charptr_t 
spiftool_get_pword(unsigned long index, const spif_charptr_t str)
{
    register spif_charptr_t tmpstr = (spif_charptr_t) str;
    register unsigned long j;

    ASSERT_RVAL(str != (spif_ptr_t) NULL, (spif_ptr_t) NULL);
    for (; isspace(*tmpstr) && *tmpstr; tmpstr++);
    for (j = 1; j < index && *tmpstr; j++) {
        for (; !isspace(*tmpstr) && *tmpstr; tmpstr++);
        for (; isspace(*tmpstr) && *tmpstr; tmpstr++);
    }

    if (*tmpstr == '\"' || *tmpstr == '\'') {
        tmpstr++;
    }
    if (*tmpstr == '\0') {
        D_STRINGS(("get_pword(%lu, %s) returning NULL.\n", index, str));
        return ((spif_charptr_t) NULL);
    } else {
        D_STRINGS(("get_pword(%lu, %s) returning \"%s\"\n", index, str, tmpstr));
        return (spif_charptr_t) tmpstr;
    }
}

/* Returns the number of words in str, for use with get_word() and get_pword().  "..." counts as 1 word. */
unsigned long
spiftool_num_words(const spif_charptr_t str)
{
    register unsigned long cnt = 0;
    char delim = 0;
    register unsigned long i;

    ASSERT_RVAL(str != (spif_ptr_t) NULL, (unsigned long) -1);
    for (i = 0; str[i] && IS_DELIM(str[i]); i++);
    for (; str[i]; cnt++) {
        switch (str[i]) {
          case '\"':
              delim = '\"';
              i++;
              break;
          case '\'':
              delim = '\'';
              i++;
              break;
          default:
              delim = 0;
        }
        for (; str[i] && !IS_DELIM(str[i]); i++);
        switch (str[i]) {
          case '\"':
          case '\'':
              i++;
              break;
        }
        for (; str[i] && isspace(str[i]); i++);
    }

    D_STRINGS(("num_words() returning %lu\n", cnt));
    return (cnt);
}

/* chomp() removes leading and trailing whitespace from a string */
spif_charptr_t 
spiftool_chomp(spif_charptr_t s)
{
    register spif_charptr_t front, back;

    ASSERT_RVAL(s != NULL, NULL);
    REQUIRE_RVAL(*s, s);

    for (front = s; *front && isspace(*front); front++);
    for (back = s + strlen((char *) s) - 1; *back && isspace(*back) && back > front; back--);

    *(++back) = 0;
    if (front != s) {
        memmove(s, front, back - front + 1);
    }
    return (s);
}

spif_charptr_t 
spiftool_downcase_str(spif_charptr_t str)
{
    register spif_charptr_t tmp;

    ASSERT_RVAL(str != (spif_ptr_t) NULL, (spif_ptr_t) NULL);
    for (tmp = str; *tmp; tmp++) {
        *tmp = tolower(*tmp);
    }
    D_STRINGS(("downcase_str() returning %s\n", str));
    return (str);
}

spif_charptr_t 
spiftool_upcase_str(spif_charptr_t str)
{
    register spif_charptr_t tmp;

    ASSERT_RVAL(str != (spif_ptr_t) NULL, (spif_ptr_t) NULL);
    for (tmp = str; *tmp; tmp++) {
        *tmp = toupper(*tmp);
    }
    D_STRINGS(("upcase_str() returning %s\n", str));
    return (str);
}

spif_charptr_t 
spiftool_condense_whitespace(spif_charptr_t s)
{

    register unsigned char gotspc = 0;
    register spif_charptr_t pbuff = s, pbuff2 = s;

    ASSERT_RVAL(s != (spif_ptr_t) NULL, (spif_ptr_t) NULL);
    D_STRINGS(("condense_whitespace(%s) called.\n", s));
    for (; *pbuff2; pbuff2++) {
        if (isspace(*pbuff2)) {
            if (!gotspc) {
                *pbuff = ' ';
                gotspc = 1;
                pbuff++;
            }
        } else {
            *pbuff = *pbuff2;
            gotspc = 0;
            pbuff++;
        }
    }
    if ((pbuff >= s) && (isspace(*(pbuff - 1))))
        pbuff--;
    *pbuff = 0;
    D_STRINGS(("condense_whitespace() returning \"%s\".\n", s));
    return ((spif_charptr_t) REALLOC(s, strlen((char *) s) + 1));
}

spif_charptr_t 
spiftool_safe_str(register spif_charptr_t str, unsigned short len)
{
    register unsigned short i;

    ASSERT_RVAL(str != (spif_ptr_t) NULL, (spif_ptr_t) NULL);
    for (i = 0; i < len; i++) {
        if (iscntrl(str[i])) {
            str[i] = '.';
        }
    }

    return (str);
}

void
spiftool_hex_dump(void *buff, register size_t count)
{
    register unsigned long j, k, l;
    register spif_charptr_t ptr;
    spif_char_t buffr[9];

    ASSERT(buff != (spif_ptr_t) NULL);
    fprintf(stderr, "  Address  |  Size  | Offset  | 00 01 02 03 04 05 06 07 |  ASCII  \n");
    fprintf(stderr, "-----------+--------+---------+-------------------------+---------\n");
    for (ptr = buff, j = 0; j < count; j += 8) {
        fprintf(stderr, " %10p | %06lu | %07x | ", buff, (unsigned long) count, (unsigned int) j);
        l = ((count - j < 8) ? (count - j) : (8));
        memcpy(buffr, ptr + j, l);
        memset(buffr + l, 0, 9 - l);
        for (k = 0; k < l; k++) {
            fprintf(stderr, "%02x ", buffr[k]);
        }
        for (; k < 8; k++) {
            fprintf(stderr, "   ");
        }
        fprintf(stderr, "| %-8s\n", spiftool_safe_str((spif_charptr_t) buffr, l));
    }
}

#define CHAR_CLASS_MATCH(a, b)      ((isalpha(a) && isalpha(b)) \
                                     || (isdigit(a) && isdigit(b)) \
                                     || (!isalnum(a) && !isalnum(b)))
spif_cmp_t
spiftool_version_compare(spif_charptr_t v1, spif_charptr_t v2)
{
    spif_char_t buff1[128], buff2[128];

    D_CONF(("Comparing version strings \"%s\" and \"%s\"\n", NONULL(v1), NONULL(v2)));
    SPIF_COMP_CHECK_NULL(v1, v2);

    for (; *v1 && *v2; ) {
        D_CONF((" -> Looking at \"%s\" and \"%s\"\n", v1, v2));
        if (isalpha(*v1) && isalpha(*v2)) {
            spif_charptr_t p1 = buff1, p2 = buff2;
            spif_int8_t ival1 = 6, ival2 = 6;

            /* Compare words.  First, copy each word into buffers. */
            for (; *v1 && isalpha(*v1); v1++, p1++) *p1 = *v1;
            for (; *v2 && isalpha(*v2); v2++, p2++) *p2 = *v2;
            *p1 = *p2 = 0;

            /* Change the buffered strings to lowercase for easier comparison. */
            spiftool_downcase_str(buff1);
            spiftool_downcase_str(buff2);
            D_CONF(("     -> Comparing as words \"%s\" vs. \"%s\"\n", buff1, buff2));

            /* Some strings require special handling. */
            if (!strcmp((char *) buff1, "snap")) {
                ival1 = 1;
            } else if (!strcmp((char *) buff1, "pre")) {
                ival1 = 2;
            } else if (!strcmp((char *) buff1, "alpha")) {
                ival1 = 3;
            } else if (!strcmp((char *) buff1, "beta")) {
                ival1 = 4;
            } else if (!strcmp((char *) buff1, "rc")) {
                ival1 = 5;
            }
            if (!strcmp((char *) buff2, "snap")) {
                ival2 = 1;
            } else if (!strcmp((char *) buff2, "pre")) {
                ival2 = 2;
            } else if (!strcmp((char *) buff2, "alpha")) {
                ival2 = 3;
            } else if (!strcmp((char *) buff2, "beta")) {
                ival2 = 4;
            } else if (!strcmp((char *) buff2, "rc")) {
                ival2 = 5;
            }
            if (ival1 != ival2) {
                /* If the values are different, compare them. */
                D_CONF(("     -> %d\n", (int) SPIF_CMP_FROM_INT(ival1 - ival2)));
                return SPIF_CMP_FROM_INT(ival1 - ival2);
            } else if (ival1 == 6) {
                int c;

                /* Two arbitrary strings.  Compare them too. */
                if ((c = strcmp((char *) buff1, (char *) buff2)) != 0) {
                    D_CONF(("     -> %d\n", (int) SPIF_CMP_FROM_INT(c)));
                    return SPIF_CMP_FROM_INT(c);
                }
            }
        } else if (isdigit(*v1) && isdigit(*v2)) {
            spif_charptr_t p1 = buff1, p2 = buff2;
            spif_int32_t ival1, ival2;
            spif_cmp_t c;

            /* Compare numbers.  First, copy each number into buffers. */
            for (; *v1 && isdigit(*v1); v1++, p1++) *p1 = *v1;
            for (; *v2 && isdigit(*v2); v2++, p2++) *p2 = *v2;
            *p1 = *p2 = 0;

            /* Convert the strings into actual integers. */
            ival1 = (spif_int32_t) strtol((char *) buff1, (char **) NULL, 10);
            ival2 = (spif_int32_t) strtol((char *) buff2, (char **) NULL, 10);
            D_CONF(("     -> Comparing as integers %d vs. %d\n", (int) ival1, (int) ival2));

            /* Compare the integers and return if not equal. */
            c = SPIF_CMP_FROM_INT(ival1 - ival2);
            if (!SPIF_CMP_IS_EQUAL(c)) {
                D_CONF(("     -> %d\n", (int) c));
                return c;
            }
        } else if (!isalnum(*v1) && !isalnum(*v2)) {
            spif_charptr_t p1 = buff1, p2 = buff2;
            spif_cmp_t c;

            /* Compare non-alphanumeric strings. */
            for (; *v1 && !isalnum(*v1); v1++, p1++) *p1 = *v1;
            for (; *v2 && !isalnum(*v2); v2++, p2++) *p2 = *v2;
            *p1 = *p2 = 0;

            D_CONF(("     -> Comparing as non-alphanumeric strings \"%s\" vs. \"%s\"\n", buff1, buff2));
            c = SPIF_CMP_FROM_INT(strcasecmp((char *) buff1, (char *) buff2));
            if (!SPIF_CMP_IS_EQUAL(c)) {
                D_CONF(("     -> %d\n", (int) c));
                return c;
            }
        } else {
            D_CONF(("     -> Comparing as alphanumeric strings \"%s\" vs. \"%s\"\n", buff1, buff2));
            D_CONF(("     -> %d\n", (int) SPIF_CMP_FROM_INT(strcasecmp((char *) buff1, (char *) buff2))));
            return SPIF_CMP_FROM_INT(strcasecmp((char *) buff1, (char *) buff2));
        }
    }

    /* We've reached the end of one of the strings. */
    if (*v1) {
        if (!BEG_STRCASECMP((char *) v1, "snap") || !BEG_STRCASECMP((char *) v1, "pre")
            || !BEG_STRCASECMP((char *) v1, "alpha") || !BEG_STRCASECMP((char *) v1, "beta")) {
            D_CONF(("     -> <\n"));
            return SPIF_CMP_LESS;
        } else {
            D_CONF(("     -> >\n"));
            return SPIF_CMP_GREATER;
        }
    } else if (*v2) {
        if (!BEG_STRCASECMP((char *) v2, "snap") || !BEG_STRCASECMP((char *) v2, "pre")
            || !BEG_STRCASECMP((char *) v2, "alpha") || !BEG_STRCASECMP((char *) v2, "beta")) {
            D_CONF(("     -> >\n"));
            return SPIF_CMP_GREATER;
        } else {
            D_CONF(("     -> <\n"));
            return SPIF_CMP_LESS;
        }
    }
    D_CONF(("     -> ==\n"));
    return SPIF_CMP_EQUAL;
}

/**
 * @defgroup DOXGRP_STRINGS String Utility Routines
 *
 * This group of functions/defines/macros provides oft-needed string
 * manipulation functionality which is not found (at least not
 * portably) in libc.
 *
 * Over the past several years, I have had to implement some simple
 * string routines which, for whatever reason, are not part of the
 * Standard C Library.  I have gathered these macros, functions,
 * etc. into LibAST in the hopes that others might not have to
 * reimplement them themselves.  Unlike the memory/debugging/object
 * stuff, there really isn't a well-defined architecture surrounding
 * these.  They're just utility functions and implementations for
 * non-portable functionality.
 *
 * A small sample program demonstrating some of these routines can be
 * found @link strings_example.c here @endlink.
 */

/**
 * @example strings_example.c
 * Example code for using the string routines.
 *
 */
