/
Genesis-1.0p36-DEV/
Genesis-1.0p36-DEV/bin/
Genesis-1.0p36-DEV/doc/
Genesis-1.0p36-DEV/etc/
Genesis-1.0p36-DEV/src/data/
/*
// Full copyright information is available in the file ../doc/CREDITS
//
// generic string frobbing and matching utilities
*/

#include "defs.h"

#include <string.h>
#include <ctype.h>
#include "strutil.h"
#include "util.h"
#include "execute.h"

/* We use MALLOC_DELTA to keep the memory allocated in fields thirty-two bytes
 * less than a power of two, assuming fields are twelve bytes. */
#define MALLOC_DELTA		3
#define FIELD_STARTING_SIZE	8

typedef struct {
    char *start;
    char *end;
    Bool strip;			/* Strip backslashes? */
} Field;

static char *match_coupled_wildcard(char *ctemplate, char *s);
static char *match_wildcard(char *ctemplate, char *s);
static char *match_word_pattern(char *ctemplate, char *s);
static void add_field(char *start, char *end, Bool strip);

static Field * fields;
static Int field_pos, field_size;
    
void init_match(void) {
    fields = EMALLOC(Field, FIELD_STARTING_SIZE);
    field_size = FIELD_STARTING_SIZE;
}

cList * match_template(char *ctemplate, char *s) {
    char *p;
    Int i, coupled;
    cList *l;
    cData *d;
    cStr *str;

    field_pos = 0;

    /* Strip leading spaces in template. */
    while (*ctemplate == ' ')
	ctemplate++;

    while (*ctemplate) {

	/* Skip over spaces in s. */
	while (*s == ' ')
	    s++;

	/* Is the next template entry a wildcard? */
	if (*ctemplate == '*') {
	    /* Check for coupled wildcard ("*=*"). */
	    if (ctemplate[1] == '=' && ctemplate[2] == '*') {
		ctemplate += 2;
		coupled = 1;
	    } else {
		coupled = 0;
	    }

	    /* Template is invalid if wildcard is not alone. */
	    if (ctemplate[1] && ctemplate[1] != ' ')
		return NULL;

	    /* Skip over spaces to find next token. */
	    while (*++ctemplate == ' ');

	    /* Two wildcards in a row is illegal. */
	    if (*ctemplate == '*')
		return NULL;

	    /* Match the wildcard.  This also matches the next token, if there
	     * is one, and adds the appropriate fields. */
	    if (coupled)
		s = match_coupled_wildcard(ctemplate, s);
	    else
		s = match_wildcard(ctemplate, s);
	    if (!s)
		return NULL;
	} else {
	    /* Match the word pattern.  This does not add any fields, so we
	     * do it ourselves.*/
	    p = match_word_pattern(ctemplate, s);
	    if (!p)
		return NULL;
	    add_field(s, p, FALSE);
	    s = p;
	}

	/* Get to next token in ctemplate, if there is one. */
	while (*ctemplate && *ctemplate != ' ')
	    ctemplate++;
	while (*ctemplate == ' ')
	    ctemplate++;
    }

    /* Ignore any trailing spaces in s. */
    while (*s == ' ')
	s++;

    /* If there's anything left over in s, the match failed. */
    if (*s)
	return NULL;

    /* The match succeeded.  Construct a list of the fields. */
    l = list_new(field_pos);
    d = list_empty_spaces(l, field_pos);
    for (i = 0; i < field_pos; i++) {
	s = fields[i].start;
	if (fields[i].strip && fields[i].end > s) {
	    str = string_new(fields[i].end - s);
	    p = (char *)memchr(s, '\\', fields[i].end - 1 - s);
	    while (p) {
		str = string_add_chars(str, s, p - s);
		str = string_addc(str, p[1]);
		s = p + 2;
		p = (char *)memchr(s, '\\', fields[i].end - 1 - s);
	    }
	    str = string_add_chars(str, s, fields[i].end - s);
	} else {
	    str = string_from_chars(s, fields[i].end - s);
	}
	d->type = STRING;
	d->u.str = str;
	d++;
    }

    return l;
}

/* Match a coupled wildcard as well as the next token, if there is one.  This
 * adds the fields that it matches. */
INTERNAL char *match_coupled_wildcard(char *ctemplate, char *s) {
    char *p, *q;

    /* Check for quoted text. */
    if (*s == '"') {
	/* Find the end of the quoted text. */
	p = s + 1;
	while (*p && *p != '"') {
	    if (*p == '\\' && p[1])
		p++;
	    p++;
	}

	/* Skip whitespace after quoted text. */
	for (q = p + 1; *q && *q == ' '; q++);

	/* Move on if next character is an equals sign. */
	if (*q == '=') {
	    for (q++; *q && *q == ' '; q++);
	    add_field(s + 1, p, TRUE);
	    return match_wildcard(ctemplate, q);
	} else {
	    return NULL;
	}
    }

    /* Find first occurrance of an equal sign. */
    p = strchr(s, '=');
    if (!p)
	return NULL;

    /* Add field up to first nonspace character before equalsign, and move on
     * starting from the first nonspace character after it. */
    for (q = p - 1; *q == ' '; q--);
    for (p++; *p == ' '; p++);
    add_field(s, q + 1, FALSE);
    return match_wildcard(ctemplate, p);
}

/* Match a wildcard.  Also match the next token, if there is one.  This adds
 * the fields that it matches. */
INTERNAL char * match_wildcard(char *ctemplate, char *s) {
    char *p, *q, *r;

    /* If no token follows the wildcard, then the match succeeds. */
    if (!*ctemplate) {
	p = s + strlen(s);
	add_field(s, p, FALSE);
	return p;
    }

    /* There's a word pattern to match.  Check if wildcard match is quoted. */
    if (*s == '"') {
	/* Find the end of the quoted text. */
	p = s + 1;
	while (*p && *p != '"') {
	    if (*p == '\\' && p[1])
		p++;
	    p++;
	}

	/* Skip whitespace after quoted wildcard match. */
	for (q = p + 1; *q == ' '; q++);

	/* Next token must match here. */
	r = match_word_pattern(ctemplate, q);
	if (r) {
	    add_field(s + 1, p, TRUE);
	    add_field(q, r, FALSE);
	    return r;
	} else {
	    return NULL;
	}
    } else if (*s == '\\') {
	/* Skip an initial backslash.  This is so a wildcard match can start
	 * with a double quote without being a quoted match. */
	s++;
    }

    /* There is an unquoted wildcard match.  Start by looking here. */
    p = match_word_pattern(ctemplate, s);
    if (p) {
	add_field(s, s, FALSE);
	add_field(s, p, FALSE);
	return p;
    }

    /* Look for more words to match. */
    p = s;
    while (*p) {
	if (*p == ' ') {
	    /* Skip to end of word separator and try next word. */
	    q = p + 1;
	    while (*q == ' ')
		q++;
	    r = match_word_pattern(ctemplate, q);
	    if (r) {
		/* It matches; add wildcard field and word field. */
		add_field(s, p, FALSE);
		add_field(q, r, FALSE);
		return r;
	    }
	    /* No match; continue looking at q. */
	    p = q;
	} else {
	    p++;
	}
    }

    /* No words matched, so the match fails. */
    return NULL;
}

/* Match a word pattern.  Do not add any fields. */
INTERNAL char * match_word_pattern(char *ctemplate, char *s) {
    char *p = s;
    Int abbrev = 0;

    while (*ctemplate && *ctemplate != ' ' && *ctemplate != '|') {

	if (*ctemplate == '"' || *ctemplate == '*') {
	    /* Invalid characters in a word pattern; match fails. */
	    return NULL;

	} else if (*ctemplate == '?') {
	    /* A question mark tells us that the matching string can be
	     * abbreviated down to this point. */
	    abbrev = 1;

	} else if (LCASE(*p) != LCASE(*ctemplate)) {
	    /* The match succeeds if we're at the end of the word in p and
	     * abbrev is set. */
	    if (abbrev && (!*p || *p == ' '))
		return p;

	    /* Otherwise the match against this word fails.  Try the next word
	     * if there is one, or fail if there isn't.  Also catch illegal
	     * characters (* and ") in the word and return NULL if we find
	     * them. */
	    while (*ctemplate && !strchr(" |*\"", *ctemplate))
		ctemplate++;
	    if (*ctemplate == '|')
		return match_word_pattern(ctemplate + 1, s);
	    else
		return NULL;

	} else {
	    p++;
	}

	ctemplate++;
    }

    /* We came to the end of the word in the template.  If we're also at the
     * end of the word in p, then the match succeeds.  Otherwise, try the next
     * word if there is one, and fail if there isn't. */
    if (!*p || *p == ' ')
	return p;
    else if (*ctemplate == '|')
	return match_word_pattern(ctemplate + 1, s);
    else
	return NULL;
}

/* Add a field.  strip should be true if this is a field for a wildcard not at
 * the end of the template. */
INTERNAL void add_field(char *start, char *end, Bool strip) {
    if (field_pos >= field_size) {
	field_size = field_size * 2 + MALLOC_DELTA;
	fields = EREALLOC(fields, Field, field_size);
    }
    fields[field_pos].start = start;
    fields[field_pos].end = end;
    fields[field_pos].strip = strip;
    field_pos++;
}

/* Returns a backwards list of fields if <s> matches the
   pattern <pattern>, or NULL if it doesn't. */
cList * match_pattern(char *pattern, char *s) {
    char *p, *q;
    cList *list;
    cStr *str;
    cData d;

    /* Locate wildcard in pattern, if any.  If there isn't any, return an empty
     * list if pattern and s are equivalent, or fail if they aren't. */
    p = strchr(pattern, '*');
    if (!p)
	return (strccmp(pattern, s) == 0) ? list_new(0) : NULL;

    /* Fail if s and pattern don't match up to wildcard. */
    if (strnccmp(pattern, s, p - pattern) != 0)
	return NULL;

    /* Consider s starting after the part that matches the pattern up to the
     * wildcard. */
    s += (p - pattern);

    /* Match always succeeds if wildcard is at end of line. */
    if (!p[1]) {
	list = list_new(1);
	str = string_from_chars(s, strlen(s));
	d.type = STRING;
	d.u.str = str;
	list = list_add(list, &d);
        string_discard(str);
	return list;
    }

    /* Find first potential match of rest of pattern. */
    for (q = s; *q && *q != p[1]; q++);

    /* As long as we have a potential match... */
    while (*q) {
	/* Check to see if this is actually a match. */
	list = match_pattern(p + 1, q);
	if (list) {
	    /* It matched.  Append a field and return the list. */
	    str = string_from_chars(s, q - s);
	    d.type = STRING;
	    d.u.str = str;
	    list = list_add(list, &d);
	    string_discard(str);
	    return list;
	}

	/* Find next potential match. */
	while (*++q && *q != p[1]);
    }

    /* Return failure. */
    return NULL;
}

cList * match_regexp(cStr * reg, char * s, Bool sensitive, Bool *error) {
    cList  * fields = (cList *) NULL,
           * elemlist; 
    regexp * rx;
    cData    d;
    Int      i;

    if ((rx = string_regexp(reg)) == NULL) {
        cthrow(regexp_id, "%s", regerror(NULL));
        *error = YES;
        return NULL;
    }

    *error = NO;
    if (regexec(rx, s, sensitive)) {
        fields = list_new(NSUBEXP);
        for (i = 0; i < NSUBEXP; i++) {
            elemlist = list_new(2);

            d.type = INTEGER; 
            if (!rx->startp[i]) {
                d.u.val = 0;
                elemlist = list_add(elemlist, &d);
                elemlist = list_add(elemlist, &d);
            } else {
                d.u.val = rx->startp[i] - s + 1;
                elemlist = list_add(elemlist, &d);
                d.u.val = rx->endp[i] - rx->startp[i];
                elemlist = list_add(elemlist, &d);
            }

            d.type = LIST;
            d.u.list = elemlist;
            fields = list_add(fields, &d);
            list_discard(elemlist);
        }
    }

    return fields;
}

/* similar to match_regexp, except for it returns the string(s)
   which actually matched. */

#define REGSTR(rx, pos) (string_from_chars(rx->startp[pos], \
                                    rx->endp[pos] - rx->startp[pos]))

cList * regexp_matches(cStr * reg, char * s, Bool sensitive, Bool * error) {
    cList * fields;
    regexp * rx;
    cData   d;
    Int      i,
             size;

    if ((rx = string_regexp(reg)) == (regexp *) NULL) {
        cthrow(regexp_id, "%s", regerror(NULL));
        *error = YES;
        return NULL;
    }
    *error = NO;

    if (!regexec(rx, s, sensitive))
        return NULL;

    /* size the results */
    for (size=1; size < NSUBEXP && rx->startp[size] != NULL; size++);

    d.type = STRING;
    
    if (size == 1) {
        fields = list_new(1);
        d.u.str = REGSTR(rx, 0); 
        fields = list_add(fields, &d);
        string_discard(d.u.str); 
    } else {
        fields = list_new(size-1);
        for (i = 1; i < size && rx->startp[i] != NULL; i++) {
            d.u.str = REGSTR(rx, i);
            fields = list_add(fields, &d);
            string_discard(d.u.str);
        }
    }

    return fields;
}

Int parse_regfunc_args(char * args, Int flags) {
    while (*args != (char) NULL) {
        switch (*args) {
            case 'b': /* keep blanks */
                flags |= RF_BLANKS;
                break;
            case 'g': /* global */
                flags |= RF_GLOBAL;
                break;
            case 's': /* single */
                flags &= ~RF_GLOBAL;
                break;
            case 'c': /* case sensitive */
                flags |= RF_SENSITIVE;
                break;
            case 'i': /* case insensitive */
                flags &= ~RF_SENSITIVE;
                break;
        }
        args++; 
    }
    return flags;
}

cStr * strsub(cStr * sstr, cStr * ssearch, cStr * sreplace, Int flags) {
    char * s,
         * search,
         * replace,
         * p,
         * q;
    int    len,
           slen,
           rlen;
    cStr * out;

    len = string_length(sstr);
    slen = string_length(ssearch);
    rlen = string_length(sreplace);
    s = string_chars(sstr);
    search = string_chars(ssearch);
    replace = string_chars(sreplace);

    if (!len || !slen)
        return string_dup(sstr);

    if (flags & RF_GLOBAL) {
        /* it'll be at least this big */
        out = string_new(rlen);
        p = s;
        if (flags & RF_SENSITIVE) {
            for (q = strstr(p, search); q; q = strstr(p, search)) {
                out = string_add_chars(out, p, q - p);
                out = string_add_chars(out, replace, rlen);
                p = q + slen;
            }
        } else {
            for (q = strcstr(p, search); q; q = strcstr(p, search)) {
                out = string_add_chars(out, p, q - p);
                out = string_add_chars(out, replace, rlen);
                p = q + slen;
            }
        }
        out = string_add_chars(out, p, len - (p - s));
    } else {
        if (flags & RF_SENSITIVE)
            q = strstr(s, search);
        else
            q = strcstr(s, search);
        if (q) {
            out = string_new(rlen);
            out = string_add_chars(out, s, q - s);
            out = string_add_chars(out, replace, rlen);
            q += slen;
            out = string_add_chars(out, q, len - (q - s));
        } else {
            out = string_dup(sstr);
        }
    }

    return out;
}

/*
// -------------------------------------------------------------------
//
// strsed() may throw an error, if it does, it returns NULL instead of
// a pointer to the modified string.
*/

/* the THROW() macro uses RETURN_FALSE */
#define OLD_RFALSE RETURN_FALSE
#undef RETURN_FALSE
#define RETURN_FALSE return NULL

cStr * strsed(cStr * reg,  /* the regexp string */
              cStr * ss,   /* the string to match against */
              cStr * rs,   /* the replacement string */
              Int    flags,
              Int    mult)
{
    register regexp * rx;
    cStr * out;
    char     * s = string_chars(ss),/* start */
             * p,                   /* pointer */
             * q,                   /* couldn't think of anything better */
             * r;                   /* replace */
    register Int i, x;
    Int      size=1,
             slen = string_length(ss),
             rlen = string_length(rs);
    Bool     sensitive = flags & RF_SENSITIVE;

    /*
    // make sure 's' is NULL terminated, this shouldn't be a
    // problem as string_prep() and string_new() always malloc
    // one more char--eventually we need to fix this problem.
    */
    s[slen] = (char) NULL;

    /* Compile the regexp, note: it is free'd by string_discard() */
    if ((rx = string_regexp(reg)) == (regexp *) NULL)
        THROW((regexp_id, "%s", regerror(NULL)))

    /* initial regexp execution */
    if (!regexec(rx, s, sensitive))
        return string_dup(ss);

    for (; size < NSUBEXP && rx->startp[size] != (char) NULL; size++);

    if (size == 1) { /* a constant, this is the easy one */
        if (flags & RF_GLOBAL) {
            /* die after 100 subs, magic numbers yay */
            Int depth = 100;
            p = s;
            out = string_new(slen + (rlen * mult));

            /* the sub loop; see, do/while loops can be useful */
            do {
                if (!--depth) {
                    string_discard(out);
                    THROW((maxdepth_id, "Max substitution depth exceeded"))
                }
                if ((i = rx->startp[0] - p))
                    out = string_add_chars(out, p, i);
                if (rlen)
                    out = string_add(out, rs);
                p = rx->endp[0];
            } while (p && regexec(rx, p, sensitive));

            /* add the end on */
            if ((i = (s + slen) - p))
                out = string_add_chars(out, p, i);

        } else {
            /* new string, exact size */
            out = string_new(slen + rlen - (rx->endp[0] - rx->startp[0]));

            if ((i = rx->startp[0] - s))
                out = string_add_chars(out, s, i);
            if (rlen)
                out = string_add(out, rs);
            if ((i = (s + slen) - rx->endp[0]))
                out = string_add_chars(out, rx->endp[0], i);
        }
    } else { /* rrg, now we have fun */
        if (flags & RF_GLOBAL) {  /* they would, the bastards */
            Int depth = 100;
            char * rxs = s;
    
            out = string_new(slen + ((rlen * size) * mult));
    
            if ((i = rx->startp[0] - s))
                out = string_add_chars(out, s, i);

            r = string_chars(rs);

            rxs = rx->startp[0];
            do {
                if (!--depth) {
                    string_discard(out);
                    THROW((maxdepth_id, "Max substitution depth exceeded"))
                }

                if ((i = rx->startp[0] - rxs))
                    out = string_add_chars(out, rxs, i);

                for (p = r, q = strchr(p, '%'); q; q = strchr(p, '%')) {
                    out = string_add_chars(out, p, q - p);

                    q++;

                    x = *q - (Int) '0';

                    if (!x || x > 9) {
                        string_discard(out);
                        THROW((perm_id, "Subs can only be 1-9"))
                    }

                    if (rx->startp[x] != NULL && (i=rx->endp[x]-rx->startp[x]))
                        out = string_add_chars(out, rx->startp[x], i);

                    q++;
                    p = q;
                }

                if ((i = (r + rlen) - p))
                    out = string_add_chars(out, p, i);

                rxs = rx->endp[0];
            } while (rxs && regexec(rx, rxs, sensitive));

            if ((i = (s + slen) - rxs))
                out = string_add_chars(out, rxs, i);

        } else {
            out = string_new(rlen + slen);

            if ((i = rx->startp[0] - s))
                out = string_add_chars(out, s, i);

            p = r = string_chars(rs);

            for (q = strchr(p, '%'); q; q = strchr(p, '%')) {
                out = string_add_chars(out, p, q - p);

                q++;

                x = *q - (Int) '0';

                if (!x || x > 9) {
                    string_discard(out);
                    THROW((perm_id, "Subs can only be 1-9"))
                }

                if (rx->startp[x] != NULL && (i=rx->endp[x]-rx->startp[x]))
                    out = string_add_chars(out, rx->startp[x], i);

                q++; 
                p = q;
            }

            if ((i = (r + rlen) - p))
                out = string_add_chars(out, p, i);

            if ((i = (s + slen) - rx->endp[0]))
                out = string_add_chars(out, rx->endp[0], i);
        }
    }

    return out;
}

/* fix RETURN_FALSE */
#undef RETURN_FALSE
#define RETURN_FALSE OLD_RFALSE
#undef OLD_RFALSE

/*
// -------------------------------------------------------------
// %<fmt_type>
//
// %<pad>.<precision>{fill}<fmt_type>
//
// Format Types:
//
//     d,D       literal data
//     s,S,l,L   any data, align left
//     r,R       any data, align right
//     c,C       any data, align centered
//     e         any data, align left with an elipse
//
// Caplitalized types will crop the string if/when it reaches the 'pad'
// length, otherwise the string will overflow past the pad length.
//
// Examples:
//
//    "%r", "test"        => "test"
//    "%l", "test"        => "test"
//    "%c", "test"        => "test"
//    "%d", "test"        => "\"test\""
//    "%10r", "test"      => "      test"
//    "%10l", "test"      => "test      "
//    "%10c", "test"      => "   test   "
//    "%10{|>}r", "test"  => "|>|>|>test"
//    "%10{|>}l", "test"  => "test|>|>|>"
//    "%10{|>}c", "test"  => "|>|test|>|"
//    "%.2l", 1.1214      => "1.12"
//    "%10.3{0}r", 1.1214 => "000001.121"
//    "%10.3{0}l", 1.1214 => "1.12100000"
//    "%5e", "testing"    => "te..."
//
// -------------------------------------------------------------
*/

#define NEXT_VALUE() \
    switch (args[cur].type) {\
        case STRING:\
            value = string_dup(args[cur].u.str);\
            break;\
        case SYMBOL:\
            tmp = ident_name(args[cur].u.symbol);\
            value = string_from_chars(tmp, strlen(tmp));\
            break;\
        case FLOAT:\
            sprintf(buf, "%.*f", (int) prec, (double) args[cur].u.fval); \
            value = string_from_chars(buf, strlen(buf));\
            break;\
        default:\
            value = data_to_literal(&args[cur], TRUE);\
            break;\
    }

#define x_THROW(_what_) { \
    cthrow _what_; \
    return NULL; \
}

cStr * strfmt(cStr * str, cData * args, Int argc) {
    cStr     * out,
             * value;
    register char * s;
    char     * fmt,
             * tmp,
               buf[LINE],
               fill[LINE];
    register Int pad, prec, trunc;
    Int        cur = -1;

    fmt = string_chars(str);

    /* better more than less and having to resize */
    out = string_new(string_length(str) * 2);

    forever {
        s = strchr(fmt, '%');

        if (s == (char) NULL || *s == (char) NULL) {
            out = string_add_chars(out, fmt, strlen(fmt));
            break;
        }

        out = string_add_chars(out, fmt, s - fmt);
        s++;

        if (*s == '%') {
            out = string_addc(out, '%');
            fmt = ++s;
            continue;
        }

        if (++cur >= argc) {
            string_discard(out);
            x_THROW((type_id, "Not enough arguments for format."))
        }

        pad = prec = trunc = 0;
        if (*s == '*') {
            if (args[cur].type != INTEGER)
                x_THROW((type_id, "Argument for '*' is not an integer."))
            pad = args[cur].u.val;
            s++;
            if (++cur >= argc) {
                string_discard(out);
                x_THROW((type_id, "Not enough arguments for format."))
            }
        } else {
            while (isdigit(*s))
                pad = pad * 10 + *s++ - '0';
        }

        if (*s == '.') {
            s++;
            if (*s == '*') {
                if (args[cur].type != INTEGER)
                    x_THROW((type_id, "Argument for '*' is not an integer."))
                prec = args[cur].u.val;
                s++;
                if (++cur >= argc) {
                    string_discard(out);
                    x_THROW((type_id, "Not enough arguments for format."))
                }
            } else {
                while (isdigit(*s))
                    prec = prec * 10 + *s++ - '0';
            }
        }

        /* get the pad char */
        if (*s == '{') {
            Int    x = 0;

            s++;
            for (; *s && *s != '}'; s++) {
                if (s[0] == '\\' && (s[1] == '\\' || s[1] == '}'))
                    s++;
                fill[x++] = *s;
            }
            fill[x] = (char) NULL;
            s++;
        } else {
            fill[0] = ' ';
            fill[1] = (char) NULL;
        }

        /* invalid format, just abort, they need to know when it is wrong */
        if (*s == (char) NULL) {
            string_discard(out);
            x_THROW((type_id, "Invalid format"))
        }

        switch (*s) {
            case 'D':
                trunc++;
            case 'd':
                value = data_to_literal(&args[cur], TRUE);
                goto fmt_left;
            case 'S':
            case 'L':
                trunc++;
            case 's':
            case 'l':
                NEXT_VALUE()

                fmt_left:

                if (pad) {
                    if (trunc && string_length(value) > pad)
                        value = string_truncate(value, pad);
                    else if (string_length(value) < pad)
                        value=string_add_padding(value,fill,strlen(fill),pad-string_length(value));
                }

                break;

            case 'R':
                trunc++;
            case 'r':
                NEXT_VALUE()

                if (pad) {
                    if (trunc && string_length(value) > pad)
                        value = string_truncate(value, pad);
                    else if (string_length(value) < pad) {
                        cStr * new = string_new(pad + string_length(value));
                        new = string_add_padding(new, fill, strlen(fill), pad-string_length(value));
                        new = string_add(new, value);
                        string_discard(value);
                        value = new;
                    }
                }

                break;
            case 'C':
                trunc++;
            case 'c':
                NEXT_VALUE()

                if (pad) {
                    if (trunc && string_length(value) > pad)
                        value = string_truncate(value, pad);
                    else if (string_length(value) < pad) {
                        Int size = (pad - string_length(value)) / 2;
                        cStr * new = string_new(pad + string_length(value));
                        new = string_add_padding(new, fill, strlen(fill),size);
                        new = string_add(new, value);
                        new = string_add_padding(new, fill, strlen(fill),size);
                        if ((pad - string_length(value))%2)
                            new = string_addc(new, fill[0]);
                        string_discard(value);
                        value = new;
                    }
                }

                break;
            case 'e':
                NEXT_VALUE();
                if (pad) {
                    if (pad <= 3) {
                        string_discard(out);
                        x_THROW((type_id,
                           "Elipse pad length must be at least 4 or more."))
                    }
                    if (string_length(value) > pad) {
                        value = string_truncate(value, pad - 3);
                        value = string_add_chars(value, "...", 3);
                    } else if (string_length(value) < pad) {
                        value=string_add_padding(value,fill,strlen(fill),
                                               pad-string_length(value));

                    }
                }
                break;
            default: {
                char fmttype[] = {(char) NULL, (char) NULL};
                fmttype[0] = *s;
                x_THROW((error_id, "Unknown format type '%s'.", fmttype))
            }
        }

        out = string_add(out, value);
        string_discard(value);

        fmt = ++s;
    }

    return out;
}

/*
// -------------------------------------------------------------
*/

#define ADD_WORD(_expression_) \
    word = string_from_chars _expression_ ; \
    d.u.str = word; \
    list = list_add(list, &d); \
    string_discard(word)

cList * strexplode(cStr * str, char * sep, Int sep_len, Bool blanks) {
    char     * s = string_chars(str),
             * p = s,
             * q;
    Int        len = string_length(str);
    cList   * list = list_new(0);
    cStr * word;
    cData     d;

    d.type = STRING;
    for (q = strcstr(p, sep); q; q = strcstr(p, sep)) {
        if (blanks || q > p) {
            ADD_WORD((p, q - p));
        }
        p = q + sep_len;
    }

    /* Add the last word. */
    if (*p || blanks) {
        ADD_WORD((p, len - (p - s)));
    }

    return list;
}

#undef ADD_WORD

#undef x_THROW

/*
// -------------------------------------------------------------
// we can make a much better implementation with a better regexp compiler
*/

#define x_THROW(_cthrow_) {\
        cthrow _cthrow_;\
        return NULL;\
    }

cList * strsplit(cStr * str, cStr * reg, Int flags) {
    register regexp * rx;
    register char * s, * p;
    register Int x, len, depth;
    cList   * list;
    cData     d;

    /* Compile the regexp, note: it is free'd by string_discard() */
    if ((rx = string_regexp(reg)) == (regexp *) NULL)
        x_THROW((regexp_id, "%s", regerror(NULL)))

    /* look at the regexp and see if its a simple one,
       which we can currently handle */
    for (len=reg->len, x=0, s=string_chars(reg); x < len; x++, s++) {
        if (*s == '\\') {
            s++; x++; len--; continue;
        }
        /* rrg */
        if (*s == '(')
            x_THROW((regexp_id,
                "split only supports simple regular expressions right now."))
    }

    /* set initial vars */
    d.type = STRING;
    s = p = string_chars(str);

    /* initial regexp execution */
    if (!regexec(rx, s, flags & RF_SENSITIVE)) {
        d.u.str = str;
        list = list_add(list_new(1), &d);
        return list;
    }

    /* more initial settings */
    list = list_new(0);
    len = string_length(str);
    depth = 10000; /* ugh, magic numbers */

    do {
        if (!--depth) {
            list_discard(list);
            x_THROW((maxdepth_id, "Max split depth exceeded"))
        }

        x = rx->startp[0] - p;

        if (x || (flags & RF_BLANKS)) {
            d.u.str = string_from_chars(p, x);
            list = list_add(list, &d);
            string_discard(d.u.str);
        }

        p = rx->endp[0];
    } while (p && regexec(rx, p, flags & RF_SENSITIVE));

    if ((x = (s + len) - p) || (flags & RF_BLANKS)) {
        d.u.str = string_from_chars(p, x);
        list = list_add(list, &d);
        string_discard(d.u.str);
    }

    return list;
}