OSSP CVS Repository

ossp - ossp-pkg/var/var.c 1.72
Not logged in
[Honeypot]  [Browse]  [Directory]  [Home]  [Login
[Reports]  [Search]  [Ticket]  [Timeline
  [Raw

ossp-pkg/var/var.c 1.72
/*
**  OSSP var - Variable Expansion
**  Copyright (c) 2001-2002 The OSSP Project (http://www.ossp.org/)
**  Copyright (c) 2001-2002 Cable & Wireless Deutschland (http://www.cw.com/de/)
**
**  This file is part of OSSP var, a variable expansion
**  library which can be found at http://www.ossp.org/pkg/lib/var/.
**
**  Permission to use, copy, modify, and distribute this software for
**  any purpose with or without fee is hereby granted, provided that
**  the above copyright notice and this permission notice appear in all
**  copies.
**
**  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
**  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
**  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
**  IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
**  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
**  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
**  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
**  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
**  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
**  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
**  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
**  SUCH DAMAGE.
**
**  var.c: library implementation
*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#if defined(HAVE_PCREPOSIX)
#  include <pcreposix.h>
#else
#  include <regex.h>
#endif
#include "var.h"

/* unique library identifier */
const char var_id[] = "OSSP var";
        
/* support for OSSP ex based exception throwing */
#ifdef WITH_EX
#include "ex.h"
#define VAR_RC(rv) \
    (  (rv) != VAR_OK && (ex_catching && !ex_shielding) \
     ? (ex_throw(var_id, NULL, (rv)), (rv)) : (rv) )
#else
#define VAR_RC(rv) (rv)
#endif /* WITH_EX */

#ifndef NUL
#define NUL '\0'
#endif

/*
**
**  ==== INTERNAL DATA STRUCTURES ====
**
*/

typedef char char_class_t[256]; /* 256 == 2 ^ sizeof(unsigned char)*8 */

/* the external context structure */
struct var_st {
    var_syntax_t   syntax;
    char_class_t   syntax_nameclass;
    var_cb_value_t cb_value_fct;
    void          *cb_value_ctx;
};

/* the internal expansion context structure */
struct var_parse_st {
    struct var_parse_st *lower;
    int force_expand;
    int rel_lookup_flag;
    int rel_lookup_cnt;
    int index_this;
};
typedef struct var_parse_st var_parse_t;

/* the default syntax configuration */
static const var_syntax_t var_syntax_default = {
    '\\',         /* escape */
    '$',          /* delim_init */
    '{',          /* delim_open */
    '}',          /* delim_close */
    '[',          /* index_open */
    ']',          /* index_close */
    '#',          /* index_mark */
    "a-zA-Z0-9_"  /* name_chars */
};

/*
**
**  ==== PARSE CONTEXT FUNCTIONS ====
**
*/

static var_parse_t *
var_parse_push(
    var_parse_t *lower, var_parse_t *upper)
{
    if (upper == NULL)
        return NULL;
    memcpy(upper, lower, sizeof(var_parse_t));
    upper->lower = lower;
    return upper;
}

static var_parse_t *
var_parse_pop(
    var_parse_t *upper)
{
    if (upper == NULL)
        return NULL;
    return upper->lower;
}

/*
**
**  ==== TOKEN BUFFER FUNCTIONS ====
**
*/

#define TOKENBUF_INITIAL_BUFSIZE 64

typedef struct {
    const char *begin;
    const char *end;
    size_t buffer_size;
} tokenbuf_t;

static void tokenbuf_init(tokenbuf_t *buf)
{
    buf->begin = NULL;
    buf->end = NULL;
    buf->buffer_size = 0;
    return;
}

static void tokenbuf_set(tokenbuf_t *buf, const char *begin, const char *end, size_t buffer_size)
{
    buf->begin = begin;
    buf->end = end;
    buf->buffer_size = buffer_size;
    return;
}

static void tokenbuf_copy(tokenbuf_t *src, tokenbuf_t *dst)
{
    dst->begin = src->begin;
    dst->end = src->end;
    dst->buffer_size = src->buffer_size;
    return;
}

static void tokenbuf_move(tokenbuf_t *src, tokenbuf_t *dst)
{
    dst->begin = src->begin;
    dst->end = src->end;
    dst->buffer_size = src->buffer_size;
    tokenbuf_init(src);
    return;
}

static int tokenbuf_assign(tokenbuf_t *buf, const char *data, size_t len)
{
    char *p;

    if ((p = malloc(len + 1)) == NULL)
        return 0;
    memcpy(p, data, len);
    buf->begin = p;
    buf->end = p + len;
    buf->buffer_size = len + 1;
    *((char *)(buf->end)) = NUL;
    return 1;
}

static int tokenbuf_append(tokenbuf_t *output, const char *data, size_t len)
{
    char *new_buffer;
    size_t new_size;
    char *tmp;

    /* Is the tokenbuffer initialized at all? If not, allocate a
       standard-sized buffer to begin with. */
    if (output->begin == NULL) {
        if ((output->begin = output->end = malloc(TOKENBUF_INITIAL_BUFSIZE)) == NULL)
            return 0;
        output->buffer_size = TOKENBUF_INITIAL_BUFSIZE;
    }

    /* Does the token contain text, but no buffer has been allocated yet? */
    if (output->buffer_size == 0) {
        /* Check whether data borders to output. If, we can append
           simly by increasing the end pointer. */
        if (output->end == data) {
            output->end += len;
            return 1;
        }
        /* OK, so copy the contents of output into an allocated buffer
           so that we can append that way. */
        if ((tmp = malloc(output->end - output->begin + len + 1)) == NULL)
            return 0;
        memcpy(tmp, output->begin, output->end - output->begin);
        output->buffer_size = output->end - output->begin;
        output->begin = tmp;
        output->end = tmp + output->buffer_size;
        output->buffer_size += len + 1;
    }

    /* Does the token fit into the current buffer? If not, realloc a
       larger buffer that fits. */
    if ((output->buffer_size - (output->end - output->begin)) <= len) {
        new_size = output->buffer_size;
        do {
            new_size *= 2;
        } while ((new_size - (output->end - output->begin)) <= len);
        if ((new_buffer = realloc((char *)output->begin, new_size)) == NULL)
            return 0;
        output->end = new_buffer + (output->end - output->begin);
        output->begin = new_buffer;
        output->buffer_size = new_size;
    }

    /* Append the data at the end of the current buffer. */
    memcpy((char *)output->end, data, len);
    output->end += len;
    *((char *)output->end) = NUL;
    return 1;
}

static int tokenbuf_merge(tokenbuf_t *output, tokenbuf_t *input)
{
    return tokenbuf_append(output, input->begin, input->end - input->begin);
}

static void tokenbuf_free(tokenbuf_t *buf)
{
    if (buf->begin != NULL && buf->buffer_size > 0)
        free((char *)buf->begin);
    buf->begin = buf->end = NULL;
    buf->buffer_size = 0;
    return;
}

static size_t tokenbuf_toint(tokenbuf_t *number)
{
    const char *p;
    size_t num;

    num = 0;
    for (p = number->begin; p != number->end; ++p) {
        num *= 10;
        num += *p - '0';
    }
    return num;
}

/*
**
**  ==== ESCAPE SEQUENCE FUNCTIONS ====
**
*/

static void expand_range(char a, char b, char_class_t class)
{
    do {
        class[(int)a] = 1;
    }
    while (++a <= b);
    return;
}

static var_rc_t expand_character_class(const char *desc, char_class_t class)
{
    size_t i;

    /* Clear the class array. */

    for (i = 0; i < 256; ++i)
        class[i] = 0;

    /* Walk through the class description and set the appropriate
       entries in the array. */

    while (*desc != NUL) {
        if (desc[1] == '-' && desc[2] != NUL) {
            if (desc[0] > desc[2])
                return VAR_ERR_INCORRECT_CLASS_SPEC;
            expand_range(desc[0], desc[2], class);
            desc += 3;
        } else {
            class[(int) *desc] = 1;
            ++desc;
        }
    }

    return VAR_OK;
}

static int isoct(char c)
{
    if (c >= '0' && c <= '7')
        return 1;
    else
        return 0;
}

static var_rc_t expand_octal(const char **src, char **dst, const char *end)
{
    unsigned char c;

    if (end - *src < 3)
        return VAR_ERR_INCOMPLETE_OCTAL;
    if (!isoct(**src) || !isoct((*src)[1]) || !isoct((*src)[2]))
        return VAR_ERR_INVALID_OCTAL;

    c = **src - '0';
    if (c > 3)
        return VAR_ERR_OCTAL_TOO_LARGE;
    c *= 8;
    ++(*src);

    c += **src - '0';
    c *= 8;
    ++(*src);

    c += **src - '0';

    **dst = (char) c;
    ++(*dst);
    return VAR_OK;
}

static int ishex(char c)
{
    if ((c >= '0' && c <= '9') ||
        (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))
        return 1;
    else
        return 0;
}

static var_rc_t expand_simple_hex(const char **src, char **dst,
                                  const char *end)
{
    unsigned char c = 0;

    if (end - *src < 2)
        return VAR_ERR_INCOMPLETE_HEX;
    if (!ishex(**src) || !ishex((*src)[1]))
        return VAR_ERR_INVALID_HEX;

    if (**src >= '0' && **src <= '9')
        c = **src - '0';
    else if (c >= 'a' && c <= 'f')
        c = **src - 'a' + 10;
    else if (c >= 'A' && c <= 'F')
        c = **src - 'A' + 10;

    c = c << 4;
    ++(*src);

    if (**src >= '0' && **src <= '9')
        c += **src - '0';
    else if (**src >= 'a' && **src <= 'f')
        c += **src - 'a' + 10;
    else if (**src >= 'A' && **src <= 'F')
        c += **src - 'A' + 10;

    **dst = (char) c;
    ++(*dst);
    return VAR_OK;
}

static var_rc_t expand_grouped_hex(const char **src, char **dst,
                                   const char *end)
{
    var_rc_t rc;

    while (*src < end && **src != '}') {
        if ((rc = expand_simple_hex(src, dst, end)) != VAR_OK)
            return rc;
        ++(*src);
    }
    if (*src == end)
        return VAR_ERR_INCOMPLETE_GROUPED_HEX;

    return VAR_OK;
}

static var_rc_t expand_hex(const char **src, char **dst, const char *end)
{
    if (*src == end)
        return VAR_ERR_INCOMPLETE_HEX;
    if (**src == '{') {
        ++(*src);
        return expand_grouped_hex(src, dst, end);
    } else
        return expand_simple_hex(src, dst, end);
}

/* 
**
**  ==== RECURSIVE-DESCEND VARIABLE EXPANSION PARSER ====
**
*/

/* forward declarations */
static int parse_variable(var_t *var, var_parse_t *ctx, const char *begin, const char *end, tokenbuf_t *result);
static int parse_command (var_t *var, var_parse_t *ctx, const char *begin, const char *end, tokenbuf_t *data);
static int parse_num_exp (var_t *var, var_parse_t *ctx, const char *begin, const char *end, int *result, int *failed);

/* parse plain text */
static int 
parse_text(
    var_t *var, var_parse_t *ctx,
    const char *begin, const char *end)
{
    const char *p;

    /* parse until delim_init (variable construct) 
       or index_open (loop construct) is found */
    for (p = begin; p != end; p++) {
        if (*p == var->syntax.escape) {
            p++; /* skip next character */
            if (p == end)
                return VAR_ERR_INCOMPLETE_QUOTED_PAIR;
        }
        else if (*p == var->syntax.delim_init)
            break;
        else if (   var->syntax.index_open != NUL
                 && (   *p == var->syntax.index_open 
                     || *p == var->syntax.index_close))
            break;
    }
    return (p - begin);
}

/* parse variable name */
static int 
parse_varname(
    var_t *var, var_parse_t *ctx,
    const char *begin, const char *end)
{
    const char *p;

    /* parse as long as name class characters are found */
    for (p = begin; p != end && var->syntax_nameclass[(int)(*p)]; p++)
        ;
    return (p - begin);
}

/* parse number */
static int 
parse_number(
    var_t *var, var_parse_t *ctx,
    const char *begin, const char *end)
{
    const char *p;

    /* parse as long as digits are found */
    for (p = begin; p != end && isdigit((int)(*p)); p++)
        ;
    return (p - begin);
}

/* parse substitution text */
static int 
parse_substext(
    var_t *var, var_parse_t *ctx,
    const char *begin, const char *end)
{
    const char *p;

    /* parse until delim_init or '/' */
    for (p = begin; p != end && *p != var->syntax.delim_init && *p != '/'; p++) {
        if (*p == var->syntax.escape) {
            if (p + 1 == end)
                return VAR_ERR_INCOMPLETE_QUOTED_PAIR;
            p++;
        }
    }
    return (p - begin);
}

/* parse expression? XXX text */
static int 
parse_exptext(
    var_t *var, var_parse_t *ctx,
    const char *begin, const char *end)
{
    const char *p;

    /* parse until delim_init or delim_close or ':' */
    for (p = begin;     p != end
                    && *p != var->syntax.delim_init
                    && *p != var->syntax.delim_close
                    && *p != ':'; p++) {
        if (*p == var->syntax.escape) {
            if (p + 1 == end)
                return VAR_ERR_INCOMPLETE_QUOTED_PAIR;
            p++;
        }
    }
    return (p - begin);
}

/* convert a string into a decimal number */
static int 
convert_num_exp_read_int(const char **begin, const char *end)
{
    int num = 0;

    do {
        num *= 10;
        num += **begin - '0';
        ++(*begin);
    } while (isdigit(**begin) && *begin != end);
    return num;
}

/* parse numerical expression operand */
static int 
parse_num_exp_operand(
    var_t *var, var_parse_t *ctx,
    const char *begin, const char *end, 
    int *result, 
    int *failed)
{
    const char *p;
    tokenbuf_t tmp;
    int rc;
    var_parse_t myctx;

    p = begin;
    tokenbuf_init(&tmp);

    if (begin == end)
        return VAR_ERR_INCOMPLETE_INDEX_SPEC;

    if (*p == '(') {
        rc = parse_num_exp(var, ctx, ++p, end, result, failed);
        if (rc < 0)
            return rc;
        p += rc;
        if (p == end)
            return VAR_ERR_INCOMPLETE_INDEX_SPEC;
        if (*p != ')')
            return VAR_ERR_UNCLOSED_BRACKET_IN_INDEX;
        ++p;
    }
    else if (*p == var->syntax.delim_init) {
        ctx = var_parse_push(ctx, &myctx);
        ctx->force_expand = 1;
        rc = parse_variable(var, ctx, p, end, &tmp);
        ctx = var_parse_pop(ctx);
        if (rc == VAR_ERR_UNDEFINED_VARIABLE) {
            *failed = 1;
            ctx = var_parse_push(ctx, &myctx);
            ctx->force_expand = 0;
            rc = parse_variable(var, ctx, p, end, &tmp);
            ctx = var_parse_pop(ctx);
            if (rc < 0)
                return rc;
            p += rc;
            *result = 0;
        }
        else {
            if (rc < 0)
                return rc;
            p += rc;
            rc = parse_num_exp(var, ctx, tmp.begin, tmp.end, result, failed);
            tokenbuf_free(&tmp);
            if (rc < 0)
                return rc;
        }
    }
    else if (var->syntax.index_mark && *p == var->syntax.index_mark) {
        p++;
        *result = ctx->index_this;
        if (ctx->rel_lookup_flag)
            ctx->rel_lookup_cnt++;
    }
    else if (isdigit(*p)) {
        *result = convert_num_exp_read_int(&p, end);
    }
    else if (*p == '+') {
        if (end - p > 1 && isdigit(p[1])) {
            p++;
            *result = convert_num_exp_read_int(&p, end);
        }
        else
            return VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC;
    }
    else if (*p == '-') {
        if (end - p > 1 && isdigit(p[1])) {
            p++;
            *result = convert_num_exp_read_int(&p, end);
            *result = 0 - *result;
        }
        else
            return VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC;
    }
    else
        return VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC;

    return (p - begin);
}

/* parse numerical expression */
static int 
parse_num_exp(
    var_t *var, var_parse_t *ctx,
    const char *begin, const char *end, 
    int *result, int *failed)
{
    const char *p;
    char operator;
    int right;
    int rc;

    p = begin;
    if (p == end)
        return VAR_ERR_INCOMPLETE_INDEX_SPEC;

    rc = parse_num_exp_operand(var, ctx, p, end, result, failed);
    if (rc < 0)
        return rc;
    p += rc;

    while (p != end) {
        if (*p == '+' || *p == '-') {
            operator = *p++;
            rc = parse_num_exp(var, ctx, p, end, &right, failed);
            if (rc < 0)
                return rc;
            p += rc;
            if (operator == '+')
                *result = *result + right;
            else
                *result = *result - right;
        }
        else if (*p == '*' || *p == '/' || *p == '%') {
            operator = *p++;
            rc = parse_num_exp_operand(var, ctx, p, end, &right, failed);
            if (rc < 0)
                return rc;
            p += rc;
            if (operator == '*') {
                *result = *result * right;
            }
            else if (operator == '/') {
                if (right == 0) {
                    if (*failed)
                        *result = 0;
                    else
                        return VAR_ERR_DIVISION_BY_ZERO_IN_INDEX;
                }
                else
                    *result = *result / right;
            }
            else if (operator == '%') {
                if (right == 0) {
                    if (*failed)
                        *result = 0;
                    else
                        return VAR_ERR_DIVISION_BY_ZERO_IN_INDEX;
                }
                else
                    *result = *result % right;
            }
        }
        else
            break;
    }
    return p - begin;
}

/* lookup a variable value by callin the callback function */
static int 
lookup_value(
    var_t *var, var_parse_t *ctx,
    const char  *var_ptr, size_t  var_len, int     var_idx,
    const char **val_ptr, size_t *val_len, size_t *val_size)
{
    char buf[1];
    int rc;

    /* pass through to original callback */
    rc = (*var->cb_value_fct)(var, var->cb_value_ctx,
                              var_ptr, var_len, var_idx, 
                              val_ptr, val_len, val_size);

    /* convert undefined variable into empty variable if relative
       lookups are counted. This is the case inside an active loop
       construct if no limits are given. There the parse_input()
       has to proceed until all variables have undefined values. 
       This trick here allows it to determine this case. */
    if (ctx->rel_lookup_flag && rc == VAR_ERR_UNDEFINED_VARIABLE) {
        ctx->rel_lookup_cnt--;
        buf[0] = NUL;
        *val_ptr  = buf;
        *val_len  = 0;
        *val_size = 0;
        return VAR_OK;
    }

    return rc;
}

static int 
parse_expression(
    var_t *var, var_parse_t *ctx,
    const char *begin, const char *end,
    tokenbuf_t *result)
{
    const char *p = begin;
    const char *data;
    size_t len, buffer_size;
    int failed = 0;
    int rc;
    int idx = 0;
    tokenbuf_t name;
    tokenbuf_t tmp;

    /* Clear the tokenbufs to make sure we have a defined state. */

    tokenbuf_init(&name);
    tokenbuf_init(&tmp);
    tokenbuf_init(result);

    /* Expect STARTDELIM. */

    if (p == end || *p != var->syntax.delim_open)
        return 0;

    if (++p == end)
        return VAR_ERR_INCOMPLETE_VARIABLE_SPEC;

    /* Get the name of the variable to expand. The name may consist of
       an arbitrary number of VARNAMEs and VARIABLEs. */

    do {
        rc = parse_varname(var, ctx, p, end);
        if (rc < 0)
            goto error_return;
        if (rc > 0) {
            if (!tokenbuf_append(&name, p, rc)) {
                rc = VAR_ERR_OUT_OF_MEMORY;
                goto error_return;
            }
            p += rc;
        }

        rc = parse_variable(var, ctx, p, end, &tmp);
        if (rc < 0)
            goto error_return;
        if (rc > 0) {
            if (!tokenbuf_append(&name, tmp.begin, tmp.end - tmp.begin)) {
                rc = VAR_ERR_OUT_OF_MEMORY;
                goto error_return;
            }
            p += rc;
        }
    } while (rc > 0);

    /* We must have the complete variable name now, so make sure we
       do. */

    if (name.begin == name.end) {
        if (ctx->force_expand) {
            rc = VAR_ERR_INCOMPLETE_VARIABLE_SPEC;
            goto error_return;
        }
        else {
            /* If no force_expand is requested, we have to back-off.
               We're not sure whether our approach here is 100% correct,
               because it _could_ have side-effects according to Peter
               Simons, but as far as we know and tried it, it is
               correct. But be warned -- RSE */
            result->begin = begin - 1;
            result->end = p;
            result->buffer_size = 0;
            goto goahead;
        }
    }

    /* If the next token is START-INDEX, read the index specification. */

    if (var->syntax.index_open && *p == var->syntax.index_open) {
        rc = parse_num_exp(var, ctx, ++p, end, &idx, &failed);
        if (rc < 0)
            goto error_return;
        if (rc == 0) {
            rc = VAR_ERR_INCOMPLETE_INDEX_SPEC;
            goto error_return;
        }
        p += rc;

        if (p == end) {
            rc = VAR_ERR_INCOMPLETE_INDEX_SPEC;
            goto error_return;
        }
        if (*p != var->syntax.index_close) {
            rc = VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC;
            goto error_return;
        }
        p++;
    }

    /* Now we have the name of the variable stored in "name". The next
       token here must either be an END-DELIM or a ':'. */

    if (p == end || (*p != var->syntax.delim_close && *p != ':')) {
        rc = VAR_ERR_INCOMPLETE_VARIABLE_SPEC;
        goto error_return;
    }
    p++;

    /* Use the lookup callback to get the variable's contents. */

    if (failed) {
        result->begin = begin - 1;
        result->end = p;
        result->buffer_size = 0;
    }
    else {
        rc = lookup_value(var, ctx, name.begin, name.end - name.begin, idx,
                          &data, &len, &buffer_size);
        if (rc == VAR_ERR_UNDEFINED_VARIABLE) {
            /* The variable is undefined. What we'll do now depends on the
               force_expand flag. */
            if (ctx->force_expand)
                goto error_return;

            /* Initialize result to point back to the original text in
               the buffer. */
            result->begin = begin - 1;
            result->end = p;
            result->buffer_size = 0;
            failed = 1;
        }
        else if (rc < 0 /* != VAR_OK */) {
            goto error_return;
        }
        else {
            /* The preliminary result is the contents of the variable.
               This may be modified by the commands that may follow. */
            result->begin = data;
            result->end = data + len;
            result->buffer_size = buffer_size;
        }
    }

    goahead:
    if (p[-1] == ':') {
        /* Parse and execute commands. */

        tokenbuf_free(&tmp);
        p--;
        while (p != end && *p == ':') {
            p++;
            if (!failed)
                rc = parse_command(var, ctx, p, end, result);
            else
                rc = parse_command(var, ctx, p, end, &tmp);
            if (rc < 0)
                goto error_return;
            p += rc;
            if (failed)
                result->end += rc;
        }

        if (p == end || *p != var->syntax.delim_close) {
            rc = VAR_ERR_INCOMPLETE_VARIABLE_SPEC;
            goto error_return;
        }
        p++;
        if (failed)
            result->end++;
    }

    /* Exit gracefully. */

    tokenbuf_free(&name);
    tokenbuf_free(&tmp);
    return p - begin;

    /* Exit in case of an error. */

  error_return:
    tokenbuf_free(&name);
    tokenbuf_free(&tmp);
    tokenbuf_free(result);
    return rc;
}

static int 
parse_variable(
    var_t *var, 
    var_parse_t *ctx,
    const char *begin, const char *end,
    tokenbuf_t *result)
{
    const char *p = begin;
    const char *data;
    size_t len, buffer_size;
    int rc, rc2;

    /* Clear the result tokenbuf to make sure we're in a defined
       state. */

    tokenbuf_init(result);

    /* Expect VARINIT. */

    if (p == end || *p != var->syntax.delim_init)
        return 0;

    if (++p == end)
        return VAR_ERR_INCOMPLETE_VARIABLE_SPEC;

    /* Try to read the variable name. If that fails, we're parsing a
       complex expression. */

    rc = parse_varname(var, ctx, p, end);
    if (rc < 0)
        return rc;
    if (rc > 0) {
        rc2 = lookup_value(var, ctx, p, rc, 0, &data, &len, &buffer_size);
        if (rc2 == VAR_ERR_UNDEFINED_VARIABLE && !ctx->force_expand) {
            result->begin = begin;
            result->end = begin + 1 + rc;
            result->buffer_size = 0;
            return 1 + rc;
        }
        if (rc2 < 0 /* != VAR_OK */)
            return rc2;
        result->begin = data;
        result->end = data + len;
        result->buffer_size = buffer_size;
        return 1 + rc;
    }

    /* OK, we're dealing with a complex expression here. */

    rc = parse_expression(var, ctx, p, end, result);
    if (rc > 0)
        rc++;
    return rc;
}

static int 
parse_exptext_or_variable(
    var_t *var, var_parse_t *ctx,
    const char *begin, const char *end,
    tokenbuf_t *result)
{
    const char *p = begin;
    tokenbuf_t tmp;
    int rc;

    tokenbuf_init(result);
    tokenbuf_init(&tmp);

    if (begin == end)
        return 0;

    do {
        rc = parse_exptext(var, ctx, p, end);
        if (rc < 0)
            goto error_return;
        if (rc > 0) {
            if (!tokenbuf_append(result, p, rc)) {
                rc = VAR_ERR_OUT_OF_MEMORY;
                goto error_return;
            }
            p += rc;
        }

        rc = parse_variable(var, ctx, p, end, &tmp);
        if (rc < 0)
            goto error_return;
        if (rc > 0) {
            p += rc;
            if (!tokenbuf_append
                (result, tmp.begin, tmp.end - tmp.begin)) {
                rc = VAR_ERR_OUT_OF_MEMORY;
                goto error_return;
            }
        }
    } while (rc > 0);

    tokenbuf_free(&tmp);
    return p - begin;

  error_return:
    tokenbuf_free(&tmp);
    tokenbuf_free(result);
    return rc;
}

static int 
parse_substext_or_variable(
    var_t *var, var_parse_t *ctx,
    const char *begin, const char *end,
    tokenbuf_t *result)
{
    const char *p = begin;
    tokenbuf_t tmp;
    int rc;

    tokenbuf_init(result);
    tokenbuf_init(&tmp);

    if (begin == end)
        return 0;

    do {
        rc = parse_substext(var, ctx, p, end);
        if (rc < 0)
            goto error_return;
        if (rc > 0) {
            if (!tokenbuf_append(result, p, rc)) {
                rc = VAR_ERR_OUT_OF_MEMORY;
                goto error_return;
            }
            p += rc;
        }

        rc = parse_variable(var, ctx, p, end, &tmp);
        if (rc < 0)
            goto error_return;
        if (rc > 0) {
            p += rc;
            if (!tokenbuf_append
                (result, tmp.begin, tmp.end - tmp.begin)) {
                rc = VAR_ERR_OUT_OF_MEMORY;
                goto error_return;
            }
        }
    } while (rc > 0);

    tokenbuf_free(&tmp);
    return p - begin;

  error_return:
    tokenbuf_free(&tmp);
    tokenbuf_free(result);
    return rc;
}

static int 
parse_class_description(tokenbuf_t *src, tokenbuf_t *dst)
{
    unsigned char c, d;
    const char *p = src->begin;

    while (p != src->end) {
        if ((src->end - p) >= 3 && p[1] == '-') {
            if (*p > p[2])
                return VAR_ERR_INCORRECT_TRANSPOSE_CLASS_SPEC;
            for (c = *p, d = p[2]; c <= d; ++c) {
                if (!tokenbuf_append(dst, (char *)&c, 1))
                    return VAR_ERR_OUT_OF_MEMORY;
            }
            p += 3;
        } else {
            if (!tokenbuf_append(dst, p, 1))
                return VAR_ERR_OUT_OF_MEMORY;
            p++;
        }
    }
    return VAR_OK;
}

static int 
op_transpose(
    tokenbuf_t *data, 
    tokenbuf_t *search,
    tokenbuf_t *replace)
{
    tokenbuf_t srcclass, dstclass;
    const char *p;
    int rc;
    size_t i;

    tokenbuf_init(&srcclass);
    tokenbuf_init(&dstclass);

    if ((rc = parse_class_description(search, &srcclass)) != VAR_OK)
        goto error_return;
    if ((rc = parse_class_description(replace, &dstclass)) != VAR_OK)
        goto error_return;

    if (srcclass.begin == srcclass.end) {
        rc = VAR_ERR_EMPTY_TRANSPOSE_CLASS;
        goto error_return;
    }
    if ((srcclass.end - srcclass.begin) != (dstclass.end - dstclass.begin)) {
        rc = VAR_ERR_TRANSPOSE_CLASSES_MISMATCH;
        goto error_return;
    }

    if (data->buffer_size == 0) {
        tokenbuf_t tmp;
        if (!tokenbuf_assign(&tmp, data->begin, data->end - data->begin)) {
            rc = VAR_ERR_OUT_OF_MEMORY;
            goto error_return;
        }
        tokenbuf_move(&tmp, data);
    }

    for (p = data->begin; p != data->end; ++p) {
        for (i = 0; i <= (srcclass.end - srcclass.begin); ++i) {
            if (*p == srcclass.begin[i]) {
                *((char *)p) = dstclass.begin[i];
                break;
            }
        }
    }

    tokenbuf_free(&srcclass);
    tokenbuf_free(&dstclass);
    return VAR_OK;

  error_return:
    tokenbuf_free(search);
    tokenbuf_free(replace);
    tokenbuf_free(&srcclass);
    tokenbuf_free(&dstclass);
    return rc;
}

static int 
op_cut_out_offset(
    tokenbuf_t *data, 
    tokenbuf_t *number1,
    tokenbuf_t *number2, 
    int isrange)
{
    tokenbuf_t res;
    const char *p;
    size_t num1;
    size_t num2;

    num1 = tokenbuf_toint(number1);
    num2 = tokenbuf_toint(number2);

    /* Determine begin of result string. */

    if ((data->end - data->begin) < num1)
        return VAR_ERR_OFFSET_OUT_OF_BOUNDS;
    else
        p = data->begin + num1;

    /* If num2 is zero, we copy the rest from there. */

    if (num2 == 0) {
        if (!tokenbuf_assign(&res, p, data->end - p))
            return VAR_ERR_OUT_OF_MEMORY;
    } else {
        /* OK, then use num2. */
        if (isrange) {
            if ((p + num2) > data->end)
                return VAR_ERR_RANGE_OUT_OF_BOUNDS;
            if (!tokenbuf_assign(&res, p, num2))
                return VAR_ERR_OUT_OF_MEMORY;
        } else {
            if (num2 < num1)
                return VAR_ERR_OFFSET_LOGIC;
            if ((data->begin + num2) > data->end)
                return VAR_ERR_RANGE_OUT_OF_BOUNDS;
            if (!tokenbuf_assign(&res, p, num2 - num1 + 1))
                return VAR_ERR_OUT_OF_MEMORY;
        }
    }
    tokenbuf_free(data);
    tokenbuf_move(&res, data);
    return VAR_OK;
}

static int 
parse_regex_replace(
    const char *data, 
    tokenbuf_t *orig,
    regmatch_t *pmatch, 
    tokenbuf_t *expanded)
{
    const char *p = orig->begin;
    size_t i;

    tokenbuf_init(expanded);

    while (p != orig->end) {
        if (*p == '\\') {
            if (orig->end - p <= 1) {
                tokenbuf_free(expanded);
                return VAR_ERR_INCOMPLETE_QUOTED_PAIR;
            }
            p++;
            if (*p == '\\') {
                if (!tokenbuf_append(expanded, p, 1)) {
                    tokenbuf_free(expanded);
                    return VAR_ERR_OUT_OF_MEMORY;
                }
                p++;
                continue;
            }
            if (!isdigit((int)*p)) {
                tokenbuf_free(expanded);
                return VAR_ERR_UNKNOWN_QUOTED_PAIR_IN_REPLACE;
            }
            i = *p - '0';
            p++;
            if (pmatch[i].rm_so == -1) {
                tokenbuf_free(expanded);
                return VAR_ERR_SUBMATCH_OUT_OF_RANGE;
            }
            if (!tokenbuf_append(expanded, data + pmatch[i].rm_so,
                                    pmatch[i].rm_eo - pmatch[i].rm_so)) {
                tokenbuf_free(expanded);
                return VAR_ERR_OUT_OF_MEMORY;
            }
        } else {
            if (!tokenbuf_append(expanded, p, 1)) {
                tokenbuf_free(expanded);
                return VAR_ERR_OUT_OF_MEMORY;
            }
            p++;
        }
    }

    return VAR_OK;
}

static int 
op_search_and_replace(
    tokenbuf_t *data, 
    tokenbuf_t *search,
    tokenbuf_t *replace, 
    tokenbuf_t *flags)
{
    const char *p;
    int case_insensitive = 0;
    int global = 0;
    int no_regex = 0;
    int rc;

    if (search->begin == search->end)
        return VAR_ERR_EMPTY_SEARCH_STRING;

    for (p = flags->begin; p != flags->end; ++p) {
        switch (tolower(*p)) {
        case 'i':
            case_insensitive = 1;
            break;
        case 'g':
            global = 1;
            break;
        case 't':
            no_regex = 1;
            break;
        default:
            return VAR_ERR_UNKNOWN_REPLACE_FLAG;
        }
    }

    if (no_regex) {
        tokenbuf_t tmp;
        tokenbuf_init(&tmp);

        for (p = data->begin; p != data->end;) {
            if (case_insensitive)
                rc = strncasecmp(p, search->begin,
                                 search->end - search->begin);
            else
                rc = strncmp(p, search->begin,
                             search->end - search->begin);
            if (rc != 0) {
                /* no match, copy character */
                if (!tokenbuf_append(&tmp, p, 1)) {
                    tokenbuf_free(&tmp);
                    return VAR_ERR_OUT_OF_MEMORY;
                }
                ++p;
            } else {
                tokenbuf_append(&tmp, replace->begin,
                                replace->end - replace->begin);
                p += search->end - search->begin;
                if (!global) {
                    if (!tokenbuf_append(&tmp, p, data->end - p)) {
                        tokenbuf_free(&tmp);
                        return VAR_ERR_OUT_OF_MEMORY;
                    }
                    break;
                }
            }
        }

        tokenbuf_free(data);
        tokenbuf_move(&tmp, data);
    } else {
        tokenbuf_t tmp;
        tokenbuf_t mydata;
        tokenbuf_t myreplace;
        regex_t preg;
        regmatch_t pmatch[10];
        int regexec_flag;

        /* Copy the pattern and the data to our own buffer to make
           sure they're terminated with a null byte. */

        if (!tokenbuf_assign(&tmp, search->begin, search->end - search->begin))
            return VAR_ERR_OUT_OF_MEMORY;
        if (!tokenbuf_assign(&mydata, data->begin, data->end - data->begin)) {
            tokenbuf_free(&tmp);
            return VAR_ERR_OUT_OF_MEMORY;
        }

        /* Compile the pattern. */

        rc = regcomp(&preg, tmp.begin, REG_NEWLINE | REG_EXTENDED|((case_insensitive)?REG_ICASE:0));
        tokenbuf_free(&tmp);
        if (rc != 0) {
            tokenbuf_free(&mydata);
            return VAR_ERR_INVALID_REGEX_IN_REPLACE;
        }

        /* Match the pattern and create the result string in the tmp
           buffer. */

        for (p = mydata.begin; p != mydata.end; ) {
            if (p == mydata.begin || p[-1] == '\n')
                regexec_flag = 0;
            else
                regexec_flag = REG_NOTBOL;
            if (regexec(&preg, p, sizeof(pmatch) / sizeof(regmatch_t), pmatch, regexec_flag) == REG_NOMATCH ||
                p + pmatch[0].rm_so == mydata.end) {
                tokenbuf_append(&tmp, p, mydata.end - p);
                break;
            } else {
                rc = parse_regex_replace(p, replace, pmatch, &myreplace);
                if (rc != VAR_OK) {
                    regfree(&preg);
                    tokenbuf_free(&tmp);
                    tokenbuf_free(&mydata);
                    return rc;
                }
                if (!tokenbuf_append(&tmp, p, pmatch[0].rm_so) ||
                    !tokenbuf_append(&tmp, myreplace.begin,
                                        myreplace.end - myreplace.begin)) {
                    regfree(&preg);
                    tokenbuf_free(&tmp);
                    tokenbuf_free(&mydata);
                    tokenbuf_free(&myreplace);
                    return VAR_ERR_OUT_OF_MEMORY;
                } else {
                    p += pmatch[0].rm_eo;
                    if (pmatch[0].rm_eo - pmatch[0].rm_so == 0)
                        {
                        if (!tokenbuf_append(&tmp, p, 1))
                            {
                            regfree(&preg);
                            tokenbuf_free(&tmp);
                            tokenbuf_free(&mydata);
                            tokenbuf_free(&myreplace);
                            return VAR_ERR_OUT_OF_MEMORY;
                            }
                        ++p;
                        }
                    tokenbuf_free(&myreplace);
                }
                if (!global) {
                    if (!tokenbuf_append(&tmp, p, mydata.end - p)) {
                        regfree(&preg);
                        tokenbuf_free(&tmp);
                        tokenbuf_free(&mydata);
                        return VAR_ERR_OUT_OF_MEMORY;
                    }
                    break;
                }
            }
        }

        regfree(&preg);
        tokenbuf_free(data);
        tokenbuf_move(&tmp, data);
        tokenbuf_free(&mydata);
    }

    return VAR_OK;
}

static int 
op_padding(
    tokenbuf_t *data, 
    tokenbuf_t *widthstr, 
    tokenbuf_t *fill,
    char position)
{
    tokenbuf_t result;
    size_t width = tokenbuf_toint(widthstr);
    int i;

    if (fill->begin == fill->end)
        return VAR_ERR_EMPTY_PADDING_FILL_STRING;

    tokenbuf_init(&result);

    if (position == 'l') {
        i = width - (data->end - data->begin);
        if (i > 0) {
            i = i / (fill->end - fill->begin);
            while (i > 0) {
                if (!tokenbuf_append
                    (data, fill->begin, fill->end - fill->begin))
                    return VAR_ERR_OUT_OF_MEMORY;
                i--;
            }
            i = (width - (data->end - data->begin))
                % (fill->end - fill->begin);
            if (!tokenbuf_append(data, fill->begin, i))
                return VAR_ERR_OUT_OF_MEMORY;
        }
    } else if (position == 'r') {
        i = width - (data->end - data->begin);
        if (i > 0) {
            i = i / (fill->end - fill->begin);
            while (i > 0) {
                if (!tokenbuf_append
                    (&result, fill->begin, fill->end - fill->begin)) {
                    tokenbuf_free(&result);
                    return VAR_ERR_OUT_OF_MEMORY;
                }
                i--;
            }
            i = (width - (data->end - data->begin))
                % (fill->end - fill->begin);
            if (!tokenbuf_append(&result, fill->begin, i)) {
                tokenbuf_free(&result);
                return VAR_ERR_OUT_OF_MEMORY;
            }
            if (!tokenbuf_append(&result, data->begin, data->end - data->begin)) {
                tokenbuf_free(&result);
                return VAR_ERR_OUT_OF_MEMORY;
            }

            tokenbuf_free(data);
            tokenbuf_move(&result, data);
        }
    } else if (position == 'c') {
        i = (width - (data->end - data->begin)) / 2;
        if (i > 0) {
            /* Create the prefix. */

            i = i / (fill->end - fill->begin);
            while (i > 0) {
                if (!tokenbuf_append(&result, fill->begin, fill->end - fill->begin)) {
                    tokenbuf_free(&result);
                    return VAR_ERR_OUT_OF_MEMORY;
                }
                i--;
            }
            i = ((width - (data->end - data->begin)) / 2)
                % (fill->end - fill->begin);
            if (!tokenbuf_append(&result, fill->begin, i)) {
                tokenbuf_free(&result);
                return VAR_ERR_OUT_OF_MEMORY;
            }

            /* Append the actual data string. */

            if (!tokenbuf_append(&result, data->begin, data->end - data->begin)) {
                tokenbuf_free(&result);
                return VAR_ERR_OUT_OF_MEMORY;
            }

            /* Append the suffix. */

            i = width - (result.end - result.begin);
            i = i / (fill->end - fill->begin);
            while (i > 0) {
                if (!tokenbuf_append
                    (&result, fill->begin, fill->end - fill->begin)) {
                    tokenbuf_free(&result);
                    return VAR_ERR_OUT_OF_MEMORY;
                }
                i--;
            }
            i = width - (result.end - result.begin);
            if (!tokenbuf_append(&result, fill->begin, i)) {
                tokenbuf_free(&result);
                return VAR_ERR_OUT_OF_MEMORY;
            }

            /* Move string from temporary buffer to data buffer. */

            tokenbuf_free(data);
            tokenbuf_move(&result, data);
        }
    }

    return VAR_OK;
}

static int 
parse_command(
    var_t *var, var_parse_t *ctx,
    const char *begin, const char *end,
    tokenbuf_t *data)
{
    const char *p = begin;
    tokenbuf_t tmptokbuf;
    tokenbuf_t search, replace, flags;
    tokenbuf_t number1, number2;
    int isrange;
    int rc;

    tokenbuf_init(&tmptokbuf);
    tokenbuf_init(&search);
    tokenbuf_init(&replace);
    tokenbuf_init(&flags);
    tokenbuf_init(&number1);
    tokenbuf_init(&number2);

    if (begin == end)
        return 0;

    switch (tolower(*p)) {
        case 'l':                   /* Turn data to lowercase. */
            if (data->begin) {
                char *ptr;
                /* If the buffer does not live in an allocated buffer,
                   we have to copy it before modifying the contents. */

                if (data->buffer_size == 0) {
                    if (!tokenbuf_assign(data, data->begin, data->end - data->begin)) {
                        rc = VAR_ERR_OUT_OF_MEMORY;
                        goto error_return;
                    }
                }
                for (ptr = (char *)data->begin; ptr != data->end; ++ptr)
                    *ptr = tolower(*ptr);
            }
            p++;
            break;

        case 'u':                   /* Turn data to uppercase. */
            if (data->begin) {
                char *ptr;
                if (data->buffer_size == 0) {
                    if (!tokenbuf_assign
                        (data, data->begin, data->end - data->begin)) {
                        rc = VAR_ERR_OUT_OF_MEMORY;
                        goto error_return;
                    }
                }
                for (ptr = (char *) data->begin; ptr != data->end; ++ptr)
                    *ptr = toupper(*ptr);
            }
            ++p;
            break;

        case 'o':                   /* Cut out substrings. */
            ++p;
            rc = parse_number(var, ctx, p, end);
            if (rc == 0) {
                rc = VAR_ERR_MISSING_START_OFFSET;
                goto error_return;
            }
            number1.begin = p;
            number1.end = p + rc;
            number1.buffer_size = 0;
            p += rc;

            if (*p == ',') {
                isrange = 0;
                ++p;
            } else if (*p == '-') {
                isrange = 1;
                ++p;
            } else {
                rc = VAR_ERR_INVALID_OFFSET_DELIMITER;
                goto error_return;
            }

            rc = parse_number(var, ctx, p, end);
            number2.begin = p;
            number2.end = p + rc;
            number2.buffer_size = 0;
            p += rc;
            if (data->begin) {
                rc = op_cut_out_offset(data, &number1, &number2, isrange);
                if (rc < 0)
                    goto error_return;
            }
            break;

        case '#':                   /* Substitute length of the string. */
            if (data->begin) {
                char buf[((sizeof(int)*8)/3)+10]; /* sufficient size: <#bits> x log_10(2) + safety */
                sprintf(buf, "%d", (int)(data->end - data->begin));
                tokenbuf_free(data);
                if (!tokenbuf_assign(data, buf, strlen(buf))) {
                    rc = VAR_ERR_OUT_OF_MEMORY;
                    goto error_return;
                }
            }
            ++p;
            break;

        case '-':                   /* Substitute parameter if data is empty. */
            p++;
            rc = parse_exptext_or_variable(var, ctx, p, end, &tmptokbuf);
            if (rc < 0)
                goto error_return;
            if (rc == 0) {
                rc = VAR_ERR_MISSING_PARAMETER_IN_COMMAND;
                goto error_return;
            }
            p += rc;
            if (data->begin != NULL && data->begin == data->end) {
                tokenbuf_free(data);
                tokenbuf_move(&tmptokbuf, data);
            }
            break;

        case '*':                   /* Return "" if data is not empty, parameter otherwise. */
            p++;
            rc = parse_exptext_or_variable(var, ctx, p, end, &tmptokbuf);
            if (rc < 0)
                goto error_return;
            if (rc == 0) {
                rc = VAR_ERR_MISSING_PARAMETER_IN_COMMAND;
                goto error_return;
            }
            p += rc;
            if (data->begin != NULL) {
                if (data->begin == data->end) {
                    tokenbuf_free(data);
                    tokenbuf_move(&tmptokbuf, data);
                } else {
                    tokenbuf_free(data);
                    data->begin = data->end = "";
                    data->buffer_size = 0;
                }
            }
            break;

        case '+':                   /* Substitute parameter if data is not empty. */
            p++;
            rc = parse_exptext_or_variable(var, ctx, p, end, &tmptokbuf);
            if (rc < 0)
                goto error_return;
            if (rc == 0) {
                rc = VAR_ERR_MISSING_PARAMETER_IN_COMMAND;
                goto error_return;
            }
            p += rc;
            if (data->begin != NULL && data->begin != data->end) {
                tokenbuf_free(data);
                tokenbuf_move(&tmptokbuf, data);
            }
            break;

        case 's':                   /* Search and replace. */
            p++;

            if (*p != '/')
                return VAR_ERR_MALFORMATTED_REPLACE;
            p++;

            rc = parse_substext_or_variable(var, ctx, p, end, &search);
            if (rc < 0)
                goto error_return;
            p += rc;

            if (*p != '/') {
                rc = VAR_ERR_MALFORMATTED_REPLACE;
                goto error_return;
            }
            p++;

            rc = parse_substext_or_variable(var, ctx, p, end, &replace);
            if (rc < 0)
                goto error_return;
            p += rc;

            if (*p != '/') {
                rc = VAR_ERR_MALFORMATTED_REPLACE;
                goto error_return;
            }
            p++;

            rc = parse_exptext(var, ctx, p, end);
            if (rc < 0)
                goto error_return;
            flags.begin = p;
            flags.end = p + rc;
            flags.buffer_size = 0;
            p += rc;

            if (data->begin) {
                rc = op_search_and_replace(data, &search, &replace, &flags);
                if (rc < 0)
                    goto error_return;
            }
            break;

        case 'y':                   /* Transpose characters from class A to class B. */
            p++;

            if (*p != '/')
                return VAR_ERR_MALFORMATTED_TRANSPOSE;
            p++;

            rc = parse_substext_or_variable(var, ctx, p, end, &search);
            if (rc < 0)
                goto error_return;
            p += rc;

            if (*p != '/') {
                rc = VAR_ERR_MALFORMATTED_TRANSPOSE;
                goto error_return;
            }
            p++;

            rc = parse_substext_or_variable(var, ctx, p, end, &replace);
            if (rc < 0)
                goto error_return;
            p += rc;

            if (*p != '/') {
                rc = VAR_ERR_MALFORMATTED_TRANSPOSE;
                goto error_return;
            } else
                ++p;

            if (data->begin) {
                rc = op_transpose(data, &search, &replace);
                if (rc < 0)
                    goto error_return;
            }
            break;


        case 'p':                   /* Padding. */
            p++;

            if (*p != '/')
                return VAR_ERR_MALFORMATTED_PADDING;
            p++;

            rc = parse_number(var, ctx, p, end);
            if (rc == 0) {
                rc = VAR_ERR_MISSING_PADDING_WIDTH;
                goto error_return;
            }
            number1.begin = p;
            number1.end = p + rc;
            number1.buffer_size = 0;
            p += rc;

            if (*p != '/') {
                rc = VAR_ERR_MALFORMATTED_PADDING;
                goto error_return;
            }
            p++;

            rc = parse_substext_or_variable(var, ctx, p, end, &replace);
            if (rc < 0)
                goto error_return;
            p += rc;

            if (*p != '/') {
                rc = VAR_ERR_MALFORMATTED_PADDING;
                goto error_return;
            }
            p++;

            if (*p != 'l' && *p != 'c' && *p != 'r') {
                rc = VAR_ERR_MALFORMATTED_PADDING;
                goto error_return;
            }
            p++;

            if (data->begin) {
                rc = op_padding(data, &number1, &replace, p[-1]);
                if (rc < 0)
                    goto error_return;
            }
            break;

        default:
            return VAR_ERR_UNKNOWN_COMMAND_CHAR;
    }

    /* Exit gracefully. */

    tokenbuf_free(&tmptokbuf);
    tokenbuf_free(&search);
    tokenbuf_free(&replace);
    tokenbuf_free(&flags);
    tokenbuf_free(&number1);
    tokenbuf_free(&number2);
    return p - begin;

  error_return:
    tokenbuf_free(data);
    tokenbuf_free(&tmptokbuf);
    tokenbuf_free(&search);
    tokenbuf_free(&replace);
    tokenbuf_free(&flags);
    tokenbuf_free(&number1);
    tokenbuf_free(&number2);
    return rc;
}

/* parse loop construct limits ("[...]{b,s,e}") */
static var_rc_t 
parse_looplimits(
    var_t *var,
    var_parse_t *ctx,
    const char *begin, const char *end,
    int *start, int *step, int *stop, int *open_stop)
{
    const char *p;
    int rc;
    int failed;

    /* initialization */
    p = begin;

    /* we are happy if nothing is to left to parse */
    if (p == end)
        return 0;

    /* parse start delimiter */
    if (*p != var->syntax.delim_open)
        return 0;
    p++;

    /* parse loop start value */
    failed = 0;
    rc = parse_num_exp(var, ctx, p, end, start, &failed);
    if (rc == VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC)
        *start = 0; /* use default */
    else if (rc < 0)
        return rc;
    else
        p += rc;
    if (failed)
        return VAR_ERR_UNDEFINED_VARIABLE;

    /* parse separator */
    if (*p != ',')
        return VAR_ERR_INVALID_CHAR_IN_LOOP_LIMITS;
    p++;

    /* parse loop step value */
    failed = 0;
    rc = parse_num_exp(var, ctx, p, end, step, &failed);
    if (rc == VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC)
        *step = 1; /* use default */
    else if (rc < 0)
        return rc;
    else
        p += rc;
    if (failed)
        return VAR_ERR_UNDEFINED_VARIABLE;

    /* parse separator */
    if (*p != ',') {
        /* if not found, parse end delimiter */
        if (*p != var->syntax.delim_close)
            return VAR_ERR_INVALID_CHAR_IN_LOOP_LIMITS;
        p++;

        /* shift step value to stop value */
        *stop = *step;
        *step = 1;

        /* determine whether loop end is open */
        if (rc > 0)
            *open_stop = 0;
        else
            *open_stop = 1;
        return (p - begin);
    }
    p++;

    /* parse loop stop value */
    failed = 0;
    rc = parse_num_exp(var, ctx, p, end, stop, &failed);
    if (rc == VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC) {
        *stop = 0; /* use default */
        *open_stop = 1;
    }
    else if (rc < 0)
        return rc;
    else {
        *open_stop = 0;
        p += rc;
    }
    if (failed)
        return VAR_ERR_UNDEFINED_VARIABLE;

    /* parse end delimiter */
    if (*p != var->syntax.delim_close)
        return VAR_ERR_INVALID_CHAR_IN_LOOP_LIMITS;
    p++;

    /* return amount of parsed input */
    return (p - begin);
}

/* expand input in general */
static var_rc_t 
parse_input(
    var_t *var, var_parse_t *ctx,
    const char *begin, const char *end,
    tokenbuf_t *output, int recursion_level)
{
    const char *p;
    int rc, rc2;
    tokenbuf_t result;
    int start, step, stop, open_stop;
    int i;
    tokenbuf_t output_backup;
    int rel_lookup_cnt;
    int loop_limit_length;
    var_parse_t myctx;

    /* initialization */
    p = begin;

    do {
        /* try to parse a loop construct */
        if (   p != end 
            && var->syntax.index_open != NUL 
            && *p == var->syntax.index_open) {
            p++;

            /* loop preparation */
            loop_limit_length = -1;
            rel_lookup_cnt = ctx->rel_lookup_cnt;
            open_stop = 1;
            rc = 0;
            start = 0;
            step  = 1;
            stop  = 0;
            tokenbuf_copy(output, &output_backup);

            /* iterate over loop construct, either as long as there is
               (still) nothing known about the limit, or there is an open
               (=unknown) limit stop and there are still defined variables
               or there is a stop limit known and it is still not reached */
            re_loop:
            for (i = start;
                 (   (   open_stop 
                      && (   loop_limit_length < 0 
                          || rel_lookup_cnt > ctx->rel_lookup_cnt)) 
                  || (   !open_stop 
                      && i <= stop)                                );
                 i += step) {

                /* remember current output end for restoring */
                tokenbuf_copy(output, &output_backup);

                /* open temporary context for recursion */ 
                ctx = var_parse_push(ctx, &myctx);
                ctx->force_expand    = 1;
                ctx->rel_lookup_flag = 1;
                ctx->index_this      = i;

                /* recursive parse input through ourself */
                rc = parse_input(var, ctx, p, end, 
                                 output, recursion_level+1);

                /* retrieve info and close temporary context */
                rel_lookup_cnt = ctx->rel_lookup_cnt;
                ctx = var_parse_pop(ctx);

                /* error handling */
                if (rc < 0)
                    goto error_return;

                /* make sure the loop construct is closed */
                if (p[rc] != var->syntax.index_close) {
                    rc = VAR_ERR_UNTERMINATED_LOOP_CONSTRUCT;
                    goto error_return;
                }

                /* try to parse loop construct limit specification */
                if (loop_limit_length < 0) {
                    rc2 = parse_looplimits(var, ctx, p+rc+1, end, 
                                           &start, &step, &stop, &open_stop);
                    if (rc2 < 0)
                        goto error_return;
                    else if (rc2 == 0)
                        loop_limit_length = 0;
                    else if (rc2 > 0) {
                        loop_limit_length = rc2;
                        /* restart loop from scratch */
                        tokenbuf_copy(&output_backup, output);
                        goto re_loop;
                    }
                }
            }

            /* if stop value is open, restore to the output end
               because the last iteration was just to determine the loop
               termination and its result has to be discarded */
            if (open_stop)
                tokenbuf_copy(&output_backup, output);

            /* skip parsed loop construct */
            p += rc;
            p++;
            p += loop_limit_length;

            continue;
        }

        /* try to parse plain text */
        rc = parse_text(var, ctx, p, end);
        if (rc > 0) {
            if (!tokenbuf_append(output, p, rc)) {
                rc = VAR_ERR_OUT_OF_MEMORY;
                goto error_return;
            }
            p += rc;
            continue;
        } else if (rc < 0)
            goto error_return;

        /* try to parse a variable construct */
        tokenbuf_init(&result);
        rc = parse_variable(var, ctx, p, end, &result);
        if (rc > 0) {
            if (!tokenbuf_merge(output, &result)) {
                tokenbuf_free(&result);
                rc = VAR_ERR_OUT_OF_MEMORY;
                goto error_return;
            }
            tokenbuf_free(&result);
            p += rc;
            continue;
        }    
        tokenbuf_free(&result);
        if (rc < 0)
            goto error_return;

    } while (p != end && rc > 0);

    /* We do not know whether this really could happen, but because we
       are paranoid, report an error at the outer most parsing level if
       there is still any input. Because this would mean that we are no
       longer able to parse the remaining input as a loop construct, a
       text or a variable construct. This would be very strange, but
       could perhaps happen in case of configuration errors!?... */
    if (recursion_level == 0 && p != end) {
        rc = VAR_ERR_INPUT_ISNT_TEXT_NOR_VARIABLE;
        goto error_return;
    }

    /* return amount of parsed text */
    return (p - begin);

    /* return with an error where as a special case the output begin is
       set to the input begin and the output end to the last input parsing
       position. */
    error_return:
    tokenbuf_free(output);
    tokenbuf_set(output, begin, p, 0);
    return rc;
}

/* 
**
**  ==== APPLICATION PROGRAMMING INTERFACE (API) ====
**
*/

var_rc_t 
var_create(
    var_t **pvar)
{
    var_t *var;

    if (pvar == NULL)
        return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
    if ((var = (var_t *)malloc(sizeof(var_t))) == NULL)
        return VAR_RC(VAR_ERR_OUT_OF_MEMORY);
    memset(var, 0, sizeof(var));
    var_config(var, VAR_CONFIG_SYNTAX, &var_syntax_default);
    *pvar = var;
    return VAR_OK;
}

var_rc_t 
var_destroy(
    var_t *var)
{
    if (var == NULL)
        return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
    free(var);
    return VAR_OK;
}

var_rc_t 
var_config(
    var_t *var, 
    var_config_t mode, 
    ...)
{
    va_list ap;
    var_rc_t rc;

    if (var == NULL)
        return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
    va_start(ap, mode);
    switch (mode) {
        case VAR_CONFIG_SYNTAX: {
            var_syntax_t *s;
            s = (var_syntax_t *)va_arg(ap, void *);
            if (s == NULL)
                return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
            var->syntax.escape      = s->escape;
            var->syntax.delim_init  = s->delim_init;
            var->syntax.delim_open  = s->delim_open;
            var->syntax.delim_close = s->delim_close;
            var->syntax.index_open  = s->index_open;
            var->syntax.index_close = s->index_close;
            var->syntax.index_mark  = s->index_mark;
            var->syntax.name_chars  = NULL; /* unused internally */
            if ((rc = expand_character_class(s->name_chars, var->syntax_nameclass)) != VAR_OK)
                return VAR_RC(rc);
            if (   var->syntax_nameclass[(int)var->syntax.delim_init] 
                || var->syntax_nameclass[(int)var->syntax.delim_open]
                || var->syntax_nameclass[(int)var->syntax.delim_close]
                || var->syntax_nameclass[(int)var->syntax.escape])
                return VAR_RC(VAR_ERR_INVALID_CONFIGURATION);
            break;
        }
        case VAR_CONFIG_CB_VALUE: {
            var_cb_value_t fct;
            void *ctx;
            fct = (var_cb_value_t)va_arg(ap, void *);
            ctx = (void *)va_arg(ap, void *);
            var->cb_value_fct = fct;
            var->cb_value_ctx = ctx;
            break;
        }
        default:
            return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
    }
    va_end(ap);
    return VAR_OK;
}

var_rc_t 
var_unescape(
    var_t *var, 
    const char *src, size_t srclen, 
    char *dst, size_t dstlen, 
    int all)
{
    const char *end;
    var_rc_t rc;

    if (var == NULL || src == NULL || dst == NULL)
        return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
    end = src + srclen;
    while (src < end) {
        if (*src == '\\') {
            if (++src == end)
                return VAR_RC(VAR_ERR_INCOMPLETE_NAMED_CHARACTER);
            switch (*src) {
                case '\\':
                    if (!all) {
                        *dst++ = '\\';
                    }
                    *dst++ = '\\';
                    break;
                case 'n':
                    *dst++ = '\n';
                    break;
                case 't':
                    *dst++ = '\t';
                    break;
                case 'r':
                    *dst++ = '\r';
                    break;
                case 'x':
                    ++src;
                    if ((rc = expand_hex(&src, &dst, end)) != VAR_OK)
                        return VAR_RC(rc);
                    break;
                case '0': case '1': case '2': case '3': case '4':
                case '5': case '6': case '7': case '8': case '9':
                    if (   end - src >= 3 
                        && isdigit((int)src[1]) 
                        && isdigit((int)src[2])) {
                        if ((rc = expand_octal(&src, &dst, end)) != 0)
                            return VAR_RC(rc);
                        break;
                    }
                default:
                    if (!all) {
                        *dst++ = '\\';
                    }
                    *dst++ = *src;
            }
            ++src;
        } else
            *dst++ = *src++;
    }
    *dst = NUL;
    return VAR_OK;
}

var_rc_t 
var_expand(
    var_t *var, 
    const char *src_ptr, size_t src_len, 
    char **dst_ptr, size_t *dst_len, 
    int force_expand)
{
    var_parse_t ctx;
    tokenbuf_t output;
    var_rc_t rc;

    /* argument sanity checks */
    if (var == NULL || src_ptr == NULL || src_len == 0 || dst_ptr == NULL)
        return VAR_RC(VAR_ERR_INVALID_ARGUMENT);

    /* prepare internal expansion context */
    ctx.lower           = NULL;
    ctx.force_expand    = force_expand;
    ctx.rel_lookup_flag = 0;
    ctx.rel_lookup_cnt  = 0;
    ctx.index_this      = 0;

    /* start the parsing */
    tokenbuf_init(&output);
    rc = parse_input(var, &ctx, src_ptr, src_ptr+src_len, &output, 0);

    /* post-processing */
    if (rc >= 0) {
        /* always NUL-terminate output for convinience reasons 
           but do not count the NUL-terminator in the length */
        if (!tokenbuf_append(&output, "\0", 1)) {
            tokenbuf_free(&output);
            return VAR_RC(VAR_ERR_OUT_OF_MEMORY);
        }
        output.end--;

        /* provide result */
        *dst_ptr = (char *)output.begin;
        if (dst_len != NULL)
            *dst_len = (output.end - output.begin);
        rc = VAR_OK;
    }
    else {
        /* provide result */
        *dst_ptr = (char *)src_ptr;
        if (dst_len != NULL)
            *dst_len = (output.end - output.begin);
    }

    return VAR_RC(rc);
}

static const char *var_errors[] = {
    "everything ok",                                           /* VAR_OK = 0 */
    "incomplete named character",                              /* VAR_ERR_INCOMPLETE_NAMED_CHARACTER */
    "incomplete hexadecimal value",                            /* VAR_ERR_INCOMPLETE_HEX */
    "invalid hexadecimal value",                               /* VAR_ERR_INVALID_HEX */
    "octal value too large",                                   /* VAR_ERR_OCTAL_TOO_LARGE */
    "invalid octal value",                                     /* VAR_ERR_INVALID_OCTAL */
    "incomplete octal value",                                  /* VAR_ERR_INCOMPLETE_OCTAL */
    "incomplete grouped hexadecimal value",                    /* VAR_ERR_INCOMPLETE_GROUPED_HEX */
    "incorrect character class specification",                 /* VAR_ERR_INCORRECT_CLASS_SPEC */
    "invalid expansion configuration",                         /* VAR_ERR_INVALID_CONFIGURATION */
    "out of memory",                                           /* VAR_ERR_OUT_OF_MEMORY */
    "incomplete variable specification",                       /* VAR_ERR_INCOMPLETE_VARIABLE_SPEC */
    "undefined variable",                                      /* VAR_ERR_UNDEFINED_VARIABLE */
    "input is neither text nor variable",                      /* VAR_ERR_INPUT_ISNT_TEXT_NOR_VARIABLE */
    "unknown command character in variable",                   /* VAR_ERR_UNKNOWN_COMMAND_CHAR */
    "malformatted search and replace operation",               /* VAR_ERR_MALFORMATTED_REPLACE */
    "unknown flag in search and replace operation",            /* VAR_ERR_UNKNOWN_REPLACE_FLAG */
    "invalid regex in search and replace operation",           /* VAR_ERR_INVALID_REGEX_IN_REPLACE */
    "missing parameter in command",                            /* VAR_ERR_MISSING_PARAMETER_IN_COMMAND */
    "empty search string in search and replace operation",     /* VAR_ERR_EMPTY_SEARCH_STRING */
    "start offset missing in cut operation",                   /* VAR_ERR_MISSING_START_OFFSET */
    "offsets in cut operation delimited by unknown character", /* VAR_ERR_INVALID_OFFSET_DELIMITER */
    "range out of bounds in cut operation",                    /* VAR_ERR_RANGE_OUT_OF_BOUNDS */
    "offset out of bounds in cut operation",                   /* VAR_ERR_OFFSET_OUT_OF_BOUNDS */
    "logic error in cut operation",                            /* VAR_ERR_OFFSET_LOGIC */
    "malformatted transpose operation",                        /* VAR_ERR_MALFORMATTED_TRANSPOSE */
    "source and target class mismatch in transpose operation", /* VAR_ERR_TRANSPOSE_CLASSES_MISMATCH */
    "empty character class in transpose operation",            /* VAR_ERR_EMPTY_TRANSPOSE_CLASS */
    "incorrect character class in transpose operation",        /* VAR_ERR_INCORRECT_TRANSPOSE_CLASS_SPEC */
    "malformatted padding operation",                          /* VAR_ERR_MALFORMATTED_PADDING */
    "width parameter missing in padding operation",            /* VAR_ERR_MISSING_PADDING_WIDTH */
    "fill string missing in padding operation",                /* VAR_ERR_EMPTY_PADDING_FILL_STRING */
    "unknown quoted pair in search and replace operation",     /* VAR_ERR_UNKNOWN_QUOTED_PAIR_IN_REPLACE */
    "sub-matching reference out of range",                     /* VAR_ERR_SUBMATCH_OUT_OF_RANGE */
    "invalid argument",                                        /* VAR_ERR_INVALID_ARGUMENT */
    "incomplete quoted pair",                                  /* VAR_ERR_INCOMPLETE_QUOTED_PAIR */
    "lookup function does not support variable arrays",        /* VAR_ERR_ARRAY_LOOKUPS_ARE_UNSUPPORTED */
    "index of array variable contains an invalid character",   /* VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC */
    "index of array variable is incomplete",                   /* VAR_ERR_INCOMPLETE_INDEX_SPEC */
    "bracket expression in array variable's index not closed", /* VAR_ERR_UNCLOSED_BRACKET_IN_INDEX */
    "division by zero error in index specification",           /* VAR_ERR_DIVISION_BY_ZERO_IN_INDEX */
    "unterminated loop construct",                             /* VAR_ERR_UNTERMINATED_LOOP_CONSTRUCT */
    "invalid character in loop limits"                         /* VAR_ERR_INVALID_CHAR_IN_LOOP_LIMITS */
};

var_rc_t var_strerror(var_t *var, var_rc_t rc, char **str)
{
    if (str == NULL)
        return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
    rc = 0 - rc;
    if (rc < 0 || rc >= sizeof(var_errors) / sizeof(char *))
        *str = "unknown error";
    else
        *str = (char *)var_errors[rc];
    return VAR_OK;
}


CVSTrac 2.0.1