OSSP CVS Repository

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

ossp-pkg/var/var.c 1.65
/*
**  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 */

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

/* the internal expansion context structure */
typedef struct {
    int            force_expand;
} var_expand_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 */
};

/* Routines for manipulation of token buffers. */

#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_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)) = '\0';
    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) = '\0';
    return 1;
}

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;
}

/* Routines for the expansion of quoted-pair expressions. */

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

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

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 != '\0') {
        if (desc[1] == '-' && desc[2] != '\0') {
            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);
}

/* The recursive-descent parser for variable expressions. */

static int variable(const char *begin, const char *end,
                    const var_syntax_t *config, const char_class_t nameclass,
                    var_cb_value_t lookup, void *lookup_context,
                    int force_expand, tokenbuf_t *result, int index_mark,
                    int* rel_lookup_flag);
static int command(const char *begin, const char *end,
                   const var_syntax_t *config, const char_class_t nameclass,
                   var_cb_value_t lookup, void *lookup_context, int force_expand,
                   tokenbuf_t *data, int index_mark, int* rel_lookup_flag);
static int num_exp(const char *begin, const char *end, int index_mark,
                   int* result, int* failed, int* rel_lookup_flag,
                   const var_syntax_t *config,
                   const char_class_t nameclass,
                   var_cb_value_t lookup, void* lookup_context);

static int text(const char *begin, const char *end, char delim_init,
                char index_open, char index_close, char escape)
{
    const char *p;

    for (p = begin; p != end; ++p) {
        if (*p == escape) {
            if (++p == end)
                return VAR_ERR_INCOMPLETE_QUOTED_PAIR;
        }
        else if (*p == delim_init)
            break;
        else if (index_open && (*p == index_open || *p == index_close))
            break;
    }
    return p - begin;
}

static int varname(const char *begin, const char *end,
                   const char_class_t nameclass)
{
    const char *p;

    for (p = begin; p != end && nameclass[(int) *p]; p++)
        ;
    return p - begin;
}

static int number(const char *begin, const char *end)
{
    const char *p;

    for (p = begin; p != end && isdigit((int)*p); p++)
        ;
    return p - begin;
}

static int substext(const char *begin, const char *end,
                    const var_syntax_t *config)
{
    const char *p;

    for (p = begin; p != end && *p != config->delim_init && *p != '/'; p++) {
        if (*p == config->escape) {
            if (p + 1 == end)
                return VAR_ERR_INCOMPLETE_QUOTED_PAIR;
            p++;
        }
    }
    return p - begin;
}

static int exptext(const char *begin, const char *end,
                   const var_syntax_t *config)
{
    const char *p;

    for (p = begin;
            p != end
         && *p != config->delim_init
         && *p != config->delim_close
         && *p != ':'; p++) {
        if (*p == config->escape) {
            if (p + 1 == end)
                return VAR_ERR_INCOMPLETE_QUOTED_PAIR;
            p++;
        }
    }
    return p - begin;
}

static int 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;
}

static int num_exp_read_operand(const char *begin, const char *end, int index_mark,
                                int *result, int *failed, int *rel_lookup_flag,
                                const var_syntax_t *config,
                                const char_class_t nameclass,
                                var_cb_value_t lookup, void *lookup_context)
{
    const char* p = begin;
    tokenbuf_t tmp;
    int rc;

    tokenbuf_init(&tmp);

    if (begin == end)
        return VAR_ERR_INCOMPLETE_INDEX_SPEC;

    if (*p == '(') {
        rc = num_exp(++p, end, index_mark, result, failed,
                     rel_lookup_flag, config, nameclass, lookup, lookup_context);
        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 == config->delim_init) {
        rc = variable(p, end, config, nameclass, lookup,
                      lookup_context, 1, &tmp, index_mark, rel_lookup_flag);
        if (rc == VAR_ERR_UNDEFINED_VARIABLE) {
            *failed = 1;
            rc = variable(p, end, config, nameclass, lookup,
                          lookup_context, 0, &tmp, index_mark, rel_lookup_flag);
            if (rc < 0)
                return rc;
            p += rc;
            *result = 0;
        }
        else {
            if (rc < 0)
                return rc;
            p += rc;
            rc = num_exp(tmp.begin, tmp.end, index_mark, result,
                         failed, rel_lookup_flag, config, nameclass, lookup, lookup_context);
            tokenbuf_free(&tmp);
            if (rc < 0)
                return rc;
        }
    }
    else if (config->index_mark && *p == config->index_mark) {
        p++;
        *result = index_mark;
        (*rel_lookup_flag)++;
    }
    else if (isdigit(*p)) {
        *result = num_exp_read_int(&p, end);
    }
    else if (*p == '+') {
        if (end - p > 1 && isdigit(p[1])) {
            p++;
            *result = 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 = 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;
}

static int num_exp(const char *begin, const char *end, int index_mark,
                   int *result, int *failed, int *rel_lookup_flag,
                   const var_syntax_t *config,
                   const char_class_t nameclass,
                   var_cb_value_t lookup, void *lookup_context)
{
    const char *p = begin;
    char operator;
    int right;
    int rc;

    if (begin == end)
        return VAR_ERR_INCOMPLETE_INDEX_SPEC;

    rc = num_exp_read_operand(p, end, index_mark, result,
                              failed, rel_lookup_flag, config, nameclass,
                              lookup, lookup_context);
    if (rc < 0)
        return rc;
    p += rc;

    while (p != end) {
        if (*p == '+' || *p == '-') {
            operator = *p++;
            rc = num_exp(p, end, index_mark, &right, failed,
                         rel_lookup_flag, config, nameclass, lookup, lookup_context);
            if (rc < 0)
                return rc;
            p += rc;
            if (operator == '+')
                *result = *result + right;
            else
                *result = *result - right;
        }
        else if (*p == '*' || *p == '/' || *p == '%') {
            operator = *p++;
            rc = num_exp_read_operand(p, end, index_mark, &right, failed,
                                      rel_lookup_flag, config, nameclass, lookup, lookup_context);
            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;
}

static int expression(const char *begin, const char *end,
                      const var_syntax_t *config,
                      const char_class_t nameclass, var_cb_value_t lookup,
                      void *lookup_context, int force_expand,
                      tokenbuf_t *result, int index_mark, int *rel_lookup_flag)
    {
    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 != config->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 = varname(p, end, nameclass);
        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 = variable(p, end, config, nameclass, lookup, lookup_context,
                      force_expand, &tmp, index_mark, rel_lookup_flag);
        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 (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 (config->index_open && *p == config->index_open) {
        rc = num_exp(++p, end, index_mark, &idx, &failed,
                     rel_lookup_flag, config, nameclass, lookup, lookup_context);
        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 != config->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 != config->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) (lookup_context, 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 (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 = command(p, end, config, nameclass, lookup,
                             lookup_context, force_expand, result,
                             index_mark, rel_lookup_flag);
            else
                rc = command(p, end, config, nameclass, lookup,
                             lookup_context, force_expand, &tmp,
                             index_mark, rel_lookup_flag);
            if (rc < 0)
                goto error_return;
            p += rc;
            if (failed)
                result->end += rc;
        }

        if (p == end || *p != config->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 variable(const char *begin, const char *end,
                    const var_syntax_t *config, const char_class_t nameclass,
                    var_cb_value_t lookup, void *lookup_context,
                    int force_expand, tokenbuf_t *result, int index_mark,
                    int *rel_lookup_flag)
{
    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 != config->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 = varname(p, end, nameclass);
    if (rc < 0)
        return rc;
    if (rc > 0) {
        rc2 = (*lookup)(lookup_context, p, rc, 0, &data, &len, &buffer_size);
        if (rc2 == VAR_ERR_UNDEFINED_VARIABLE && !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 = expression(p, end, config, nameclass, lookup, lookup_context,
                    force_expand, result, index_mark, rel_lookup_flag);
    if (rc > 0)
        rc++;
    return rc;
}

static int exptext_or_variable(const char *begin, const char *end,
                               const var_syntax_t *config,
                               const char_class_t nameclass, var_cb_value_t lookup,
                               void *lookup_context, int force_expand,
                               tokenbuf_t *result, int index_mark,
                               int *rel_lookup_flag)
{
    const char *p = begin;
    tokenbuf_t tmp;
    int rc;

    tokenbuf_init(result);
    tokenbuf_init(&tmp);

    if (begin == end)
        return 0;

    do {
        rc = exptext(p, end, config);
        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 = variable(p, end, config, nameclass, lookup, lookup_context,
                      force_expand, &tmp, index_mark, rel_lookup_flag);
        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 substext_or_variable(const char *begin, const char *end,
                                const var_syntax_t *config,
                                const char_class_t nameclass, var_cb_value_t lookup,
                                void *lookup_context, int force_expand,
                                tokenbuf_t *result, int index_mark,
                                int *rel_lookup_flag)
{
    const char *p = begin;
    tokenbuf_t tmp;
    int rc;

    tokenbuf_init(result);
    tokenbuf_init(&tmp);

    if (begin == end)
        return 0;

    do {
        rc = substext(p, end, config);
        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 = variable(p, end, config, nameclass, lookup, lookup_context,
                      force_expand, &tmp, index_mark, rel_lookup_flag);
        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 expand_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 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 = expand_class_description(search, &srcclass)) != VAR_OK)
        goto error_return;
    if ((rc = expand_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 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 expand_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 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 = expand_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 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 command(const char *begin, const char *end,
                   const var_syntax_t *config, const char_class_t nameclass,
                   var_cb_value_t lookup, void *lookup_context, int force_expand,
                   tokenbuf_t *data, int index_mark, int *rel_lookup_flag)
{
    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 = number(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 = number(p, end);
        number2.begin = p;
        number2.end = p + rc;
        number2.buffer_size = 0;
        p += rc;
        if (data->begin) {
            rc = 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 = exptext_or_variable(p, end, config, nameclass, lookup,
                                 lookup_context, force_expand, &tmptokbuf,
                                 index_mark, rel_lookup_flag);
        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 = exptext_or_variable(p, end, config, nameclass, lookup,
                                 lookup_context, force_expand, &tmptokbuf, index_mark, rel_lookup_flag);
        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 = exptext_or_variable(p, end, config, nameclass, lookup,
                                 lookup_context, force_expand, &tmptokbuf, index_mark, rel_lookup_flag);
        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 = substext_or_variable(p, end, config, nameclass, lookup,
                                  lookup_context, force_expand, &search, index_mark, rel_lookup_flag);
        if (rc < 0)
            goto error_return;
        p += rc;

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

        rc = substext_or_variable(p, end, config, nameclass, lookup,
                                  lookup_context, force_expand, &replace, index_mark, rel_lookup_flag);
        if (rc < 0)
            goto error_return;
        p += rc;

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

        rc = exptext(p, end, config);
        if (rc < 0)
            goto error_return;
        flags.begin = p;
        flags.end = p + rc;
        flags.buffer_size = 0;
        p += rc;

        if (data->begin) {
            rc = 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 = substext_or_variable(p, end, config, nameclass, lookup,
                                  lookup_context, force_expand, &search, index_mark, rel_lookup_flag);
        if (rc < 0)
            goto error_return;
        p += rc;

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

        rc = substext_or_variable(p, end, config, nameclass, lookup,
                                  lookup_context, force_expand, &replace, index_mark, rel_lookup_flag);
        if (rc < 0)
            goto error_return;
        p += rc;

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

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


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

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

        rc = number(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 = substext_or_variable(p, end, config, nameclass, lookup,
                                  lookup_context, force_expand, &replace, index_mark, rel_lookup_flag);
        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 = 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;
}

struct wrapper_context {
    var_cb_value_t lookup;
    void    *context;
    int     *rel_lookup_flag;
};

static int lookup_wrapper(void *context,
                          const char *name, size_t name_len, int idx,
                          const char **data, size_t *data_len,
                          size_t *buffer_size)
{
    static char buf[1];
    struct wrapper_context *wcon = context;
    int rc;

    rc = (*wcon->lookup)(wcon->context, name, name_len,
                         idx, data, data_len, buffer_size);
    if (rc == VAR_ERR_UNDEFINED_VARIABLE) {
        (*wcon->rel_lookup_flag)--;
        *data = buf;
        *data_len = 0;
        *buffer_size = 0;
        return VAR_OK;
    }
    return rc;
}

static var_rc_t loop_limits(const char *begin, const char *end,
			    const var_syntax_t *config,
                            const char_class_t nameclass,
                            var_cb_value_t lookup, void* lookup_context,
                            int* start, int* step, int* stop, int* open_end)
    {
    const char* p = begin;
    int rc;
    int failed;
    int dummy;

    if (begin == end)
        return 0;

    if (*p != config->delim_open)
        return 0;
    else
        ++p;

    /* Read start value for the loop. */

    failed = 0;
    rc = num_exp(p, end, 0, start, &failed, &dummy,
                 config, nameclass, lookup, lookup_context);
    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;

    if (*p != ',')
        return VAR_ERR_INVALID_CHAR_IN_LOOP_LIMITS;
    else
        ++p;

    /* Read step value for the loop. */

    failed = 0;
    rc = num_exp(p, end, 0, step, &failed, &dummy,
                 config, nameclass, lookup, lookup_context);
    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;

    if (*p != ',')
        {
        if (*p != config->delim_close)
            return VAR_ERR_INVALID_CHAR_IN_LOOP_LIMITS;
        else
            {
            ++p;
            *stop = *step;
            *step = 1;
            if (rc > 0)
                *open_end = 0;
            else
                *open_end = 1;
            return p - begin;
            }
        }
    else
        ++p;

    /* Read stop value for the loop. */

    failed = 0;
    rc = num_exp(p, end, 0, stop, &failed, &dummy,
                 config, nameclass, lookup, lookup_context);
    if (rc == VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC)
        {
        *stop = 0;          /* use default */
        *open_end = 1;
        }
    else if (rc < 0)
        return rc;
    else
        {
        *open_end = 0;
        p += rc;
        }
    if (failed)
        return VAR_ERR_UNDEFINED_VARIABLE;

    if (*p != config->delim_close)
        return VAR_ERR_INVALID_CHAR_IN_LOOP_LIMITS;

    return ++p - begin;
    }

static var_rc_t input(const char *begin, const char *end,
                      const var_syntax_t *config,
                      const char_class_t nameclass, var_cb_value_t lookup,
                      void *lookup_context, int force_expand,
                      tokenbuf_t *output, int index_mark,
                      size_t recursion_level, int *rel_lookup_flag)
{
    const char *p = begin;
    int rc, rc2;
    tokenbuf_t result;
    int start, step, stop, open_end;
    int i;
    int output_backup;
    struct wrapper_context wcon;
    int my_rel_lookup_flag;
    int original_rel_lookup_state;
    int loop_limit_length;

    tokenbuf_init(&result);

    if (rel_lookup_flag == NULL) {
        rel_lookup_flag  = &my_rel_lookup_flag;
        *rel_lookup_flag = 0;
    }

    do {
        if (begin != end && config->index_open && *begin == config->index_open) {
            original_rel_lookup_state = *rel_lookup_flag;
            loop_limit_length = -1;
            wcon.lookup = lookup;
            wcon.context = lookup_context;
            wcon.rel_lookup_flag = rel_lookup_flag;
            begin++;
            start = 0;
            step  = 1;
            stop  = 0;
            open_end = 1;
            rc = 0;
            output_backup = 0;
      re_loop:
            for (i = start;
                 (open_end  && (loop_limit_length < 0 || *rel_lookup_flag > original_rel_lookup_state)) ||
                     (!open_end && i <= stop);
                 i += step) {
                *rel_lookup_flag = original_rel_lookup_state;
                output_backup = output->end - output->begin;
                rc = input(begin, end, config, nameclass, &lookup_wrapper,
                           &wcon, 1, output, i, recursion_level+1, rel_lookup_flag);
                if (rc < 0)
                    goto error_return;
                if (begin[rc] != config->index_close) {
                    rc = VAR_ERR_UNTERMINATED_LOOP_CONSTRUCT;
                    goto error_return;
                }
                if (loop_limit_length < 0)
                    {
                    rc2 = loop_limits(begin + rc + 1, end, config, nameclass,
                                      lookup, lookup_context, &start, &step,
                                      &stop, &open_end);
                    if (rc2 < 0)
                        {
                        goto error_return;
                        }
                    else if (rc2 == 0)
                        {
                        loop_limit_length = 0;
                        }
                    else if (rc2 > 0)
                        {
                        loop_limit_length = rc2;
                        output->end = output->begin + output_backup;
                        goto re_loop;
                        }
                    }
            }
            if (open_end)
                output->end = output->begin + output_backup;
            else
                *rel_lookup_flag = original_rel_lookup_state;
            begin += rc;
            begin++;
            begin += loop_limit_length;
            continue;
        }

        rc = text(begin, end, config->delim_init, config->index_open,
                  config->index_close, config->escape);
        if (rc > 0) {
            if (!tokenbuf_append(output, begin, rc)) {
                rc = VAR_ERR_OUT_OF_MEMORY;
                goto error_return;
            }
            begin += rc;
            continue;
        } else if (rc < 0)
            goto error_return;

        rc = variable(begin, end, config, nameclass, lookup,
                      lookup_context, force_expand, &result,
                      index_mark, rel_lookup_flag);
        if (rc > 0) {
            if (!tokenbuf_append(output, result.begin, result.end - result.begin)) {
                rc = VAR_ERR_OUT_OF_MEMORY;
                goto error_return;
            }
            begin += rc;
            continue;
        }
        if (rc < 0)
            goto error_return;
    } while (begin != end && rc > 0);

    if (recursion_level == 0 && begin != end) {
        rc = VAR_ERR_INPUT_ISNT_TEXT_NOR_VARIABLE;
        goto error_return;
    }

    return begin - p;

  error_return:
    tokenbuf_free(output);
    tokenbuf_free(&result);
    output->begin = p;
    output->end = begin;
    output->buffer_size = 0;
    return rc;
}

static var_rc_t internal_expand(
    const char *input_buf, size_t input_len,
    char **result, size_t *result_len,
    var_cb_value_t lookup, void *lookup_context,
    const var_syntax_t *config, int force_expand)
{
    char_class_t nameclass;
    var_rc_t rc;
    tokenbuf_t output;

    /* Argument sanity checks */
    if (input_buf == NULL || input_len == 0 ||
        result == NULL ||
        lookup == NULL)
        return VAR_RC(VAR_ERR_INVALID_ARGUMENT);

    /* Optionally use default configuration */
    if (config == NULL)
        config = &var_syntax_default;

    /* Set the result pointer to the begining of the input buffer so
       that it is correctly initialized in case we fail with an error. */
    *result = (char *)input_buf;

    /* Expand the class description for valid variable names. */
    if ((rc = expand_character_class(config->name_chars, nameclass)) != VAR_OK)
        return VAR_RC(rc);

    /* Make sure that the specials defined in the configuration do not
       appear in the character name class. */
    if (nameclass[(int)config->delim_init] ||
        nameclass[(int)config->delim_open] ||
        nameclass[(int)config->delim_close] ||
        nameclass[(int)config->escape])
        return VAR_RC(VAR_ERR_INVALID_CONFIGURATION);

    /* Call the parser. */
    tokenbuf_init(&output);
    rc = input(input_buf, input_buf + input_len, config, nameclass,
               lookup, lookup_context, force_expand, &output, 0, 0, NULL);

    /* Post-process output */
    if (rc >= 0) {
        /* always NUL-terminate output for convinience reasons */
        if (!tokenbuf_append(&output, "\0", 1)) {
            tokenbuf_free(&output);
            return VAR_ERR_OUT_OF_MEMORY;
        }
        output.end--;

        /* Provide results */
        *result = (char *)output.begin;
        if (result_len != NULL)
            *result_len = output.end - output.begin;

        /* canonify all positive answers */
        rc = VAR_OK;
    }
    else {
        /* Provide error results */
        *result = (char *)input_buf;
        if (result_len != NULL)
            *result_len = output.end - output.begin;
    }

    return VAR_RC(rc);
}

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

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;

    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;
            if (var->syntax.name_chars != NULL)
                free(var->syntax.name_chars);
            var->syntax.name_chars = strdup(s->name_chars);
            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 = '\0';
    return VAR_OK;
}

var_rc_t 
var_expand(
    var_t *var, 
    const char *src, size_t srclen, 
    char **dst, size_t *dstlen, 
    int force_expand)
{
    /* var_expand_t ctx; */
    var_rc_t rc;

    /* ctx.force_expand = force_expand; */
    rc = internal_expand(src, srclen, dst, dstlen, 
                         var->cb_value_fct, var->cb_value_ctx,
                         &var->syntax, force_expand);
    return rc;
}

var_rc_t var_strerror(var_t *var, var_rc_t rc, char **str)
{
    static char *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 regular expression 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 destination classes do not match 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 specification of array variable contains an invalid character",  /* VAR_ERR_INVALID_CHAR_IN_INDEX_SPEC */
        "index specification of array variable is incomplete",                  /* VAR_ERR_INCOMPLETE_INDEX_SPEC */
        "bracket expression in array variable's index is 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 */
    };

    if (str == NULL)
        return VAR_RC(VAR_ERR_INVALID_ARGUMENT);
    rc = 0 - rc;
    if (rc < 0 || rc >= sizeof(errors) / sizeof(char *))
        *str = "unknown error";
    else
        *str = errors[rc];
    return VAR_OK;
}


CVSTrac 2.0.1