/*
 * Copyright (C) 2007-2019 S[&]T, The Netherlands.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS 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 COPYRIGHT HOLDER OR 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.
 */

#include "coda-internal.h"
#include "coda-expr.h"

#include <assert.h>
#include <ctype.h>
#include <math.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>

#include "coda-ascii.h"
#include "ipow.h"

#include "pcre.h"

/** \defgroup coda_expression CODA Expression
 * CODA comes with a powerful expression language that can be used to perform calculations based on product data.
 * This expression system is used internally with the product format definition (codadef) files that CODA uses to
 * interpret products, but it can also be used by you as a user for your own purposes.
 * More information on the CODA expression language and its ascii syntax can be found in the CODA documentation.
 *
 * The example below shows how to evaluate a simple integer expression that does not make use of any product data:
 * \code{.c}
 * const char *equation = "1+2";
 * coda_expression *expr;
 * long result;
 *
 * coda_expression_from_string(equation, &expr);
 * coda_expression_eval_integer(expr, NULL, &result);
 * coda_expression_delete(expr);
 * printf("%d\n", result);
 * \endcode
 *
 * However, in most cases you will want to run an expression on actual product data. In the example below the expression
 * expects a cursor that points to a record which has two fields, 'numerator' and 'denominator', and it will return a
 * floating point value with the division of those two field values.
 * \code{.c}
 * const char *equation = "float(./numerator)/float(./denominator)";
 * coda_cursor cursor;
 * coda_expression *expr;
 * double result;
 *
 * coda_expression_from_string(equation, &expr);
 *
 * ... loop over all cursors for which you want to calculate the division ....
 * coda_expression_eval_integer(expr, &cursor, &result);
 * printf("%f\n", result);
 * ... end of loop ...
 *
 * coda_expression_delete(expr);
 * \endcode
 *
 * Note that, unlike most other CODA functions, the coda_expression_from_string() and coda_expression_delete() functions
 * do not require that CODA is initialised with coda_init(). This also holds for the coda_expression_eval functions if
 * no cursor is provided as parameter (i.e. when a static evaluation of the expression is performed).
 */

/** \typedef coda_expression
 * CODA Expression
 * \ingroup coda_expression
 */

/** \enum coda_expression_type_enum
 * Result types of CODA expressions.
 * \ingroup coda_expression
 */

/** \typedef coda_expression_type
 * Result types of CODA expressions.
 * \ingroup coda_expression
 */


#define REGEX_MAX_NUM_SUBSTRING 15

static int iswhitespace(char a)
{
    return (a == ' ' || a == '\t' || a == '\n' || a == '\r');
}

static int compare_strings(long off_a, long len_a, char *a, long off_b, long len_b, char *b)
{
    long index = 0;

    while (index < len_a && index < len_b && a[off_a + index] == b[off_b + index])
    {
        index++;
    }
    if (index == len_a)
    {
        if (index == len_b)
        {
            return 0;
        }
        return -1;
    }
    if (index == len_b || ((uint8_t)a[off_a + index]) > ((uint8_t)b[off_b + index]))
    {
        return 1;
    }
    return -1;
}

static long decode_escaped_string(char *str)
{
    long from;
    long to;

    if (str == NULL)
    {
        return 0;
    }

    from = 0;
    to = 0;

    while (str[from] != '\0')
    {
        if (str[from] == '\\')
        {
            from++;
            switch (str[from])
            {
                case 'e':
                    str[to++] = '\033'; /* windows does not recognize '\e' */
                    break;
                case 'a':
                    str[to++] = '\a';
                    break;
                case 'b':
                    str[to++] = '\b';
                    break;
                case 'f':
                    str[to++] = '\f';
                    break;
                case 'n':
                    str[to++] = '\n';
                    break;
                case 'r':
                    str[to++] = '\r';
                    break;
                case 't':
                    str[to++] = '\t';
                    break;
                case 'v':
                    str[to++] = '\v';
                    break;
                case '\\':
                    str[to++] = '\\';
                    break;
                case '"':
                    str[to++] = '"';
                    break;
                case '\'':
                    str[to++] = '\'';
                    break;
                default:
                    if (str[from] < '0' || str[from] > '9')
                    {
                        coda_set_error(CODA_ERROR_INVALID_FORMAT, "invalid escape sequence in string");
                        return -1;
                    }
                    str[to] = (str[from] - '0') * 64;
                    from++;
                    if (str[from] < '0' || str[from] > '9')
                    {
                        coda_set_error(CODA_ERROR_INVALID_FORMAT, "invalid escape sequence in string");
                        return -1;
                    }
                    str[to] += (str[from] - '0') * 8;
                    from++;
                    if (str[from] < '0' || str[from] > '9')
                    {
                        coda_set_error(CODA_ERROR_INVALID_FORMAT, "invalid escape sequence in string");
                        return -1;
                    }
                    str[to] += str[from] - '0';
                    to++;
            }
        }
        else
        {
            str[to++] = str[from];
        }
        from++;
    }

    str[to] = '\0';

    return to;
}

static coda_expression *boolean_constant_new(char *string_value)
{
    coda_expression_bool_constant *expr;

    expr = malloc(sizeof(coda_expression_bool_constant));
    if (expr == NULL)
    {
        coda_set_error(CODA_ERROR_OUT_OF_MEMORY, "out of memory (could not allocate %lu bytes) (%s:%u)",
                       (long)sizeof(coda_expression_bool_constant), __FILE__, __LINE__);
        free(string_value);
        return NULL;
    }
    expr->tag = expr_constant_boolean;
    expr->result_type = coda_expression_boolean;
    expr->is_constant = 1;
    expr->value = (*string_value == 't' || *string_value == 'T');
    free(string_value);

    return (coda_expression *)expr;
}

static coda_expression *float_constant_new(char *string_value)
{
    coda_expression_float_constant *expr;
    double value;

    if (coda_ascii_parse_double(string_value, (long)strlen(string_value), &value, 0) < 0)
    {
        free(string_value);
        return NULL;
    }
    free(string_value);

    expr = malloc(sizeof(coda_expression_float_constant));
    if (expr == NULL)
    {
        coda_set_error(CODA_ERROR_OUT_OF_MEMORY, "out of memory (could not allocate %lu bytes) (%s:%u)",
                       (long)sizeof(coda_expression_float_constant), __FILE__, __LINE__);
        return NULL;
    }
    expr->tag = expr_constant_float;
    expr->result_type = coda_expression_float;
    expr->is_constant = 1;
    expr->value = value;

    return (coda_expression *)expr;
}

static coda_expression *integer_constant_new(char *string_value)
{
    coda_expression_integer_constant *expr;
    int64_t value;

    if (coda_ascii_parse_int64(string_value, (long)strlen(string_value), &value, 0) < 0)
    {
        free(string_value);
        return NULL;
    }
    free(string_value);

    expr = malloc(sizeof(coda_expression_integer_constant));
    if (expr == NULL)
    {
        coda_set_error(CODA_ERROR_OUT_OF_MEMORY, "out of memory (could not allocate %lu bytes) (%s:%u)",
                       (long)sizeof(coda_expression_integer_constant), __FILE__, __LINE__);
        return NULL;
    }
    expr->tag = expr_constant_integer;
    expr->result_type = coda_expression_integer;
    expr->is_constant = 1;
    expr->value = value;

    return (coda_expression *)expr;
}

static coda_expression *rawstring_constant_new(char *string_value)
{
    coda_expression_string_constant *expr;

    expr = malloc(sizeof(coda_expression_string_constant));
    if (expr == NULL)
    {
        coda_set_error(CODA_ERROR_OUT_OF_MEMORY, "out of memory (could not allocate %lu bytes) (%s:%u)",
                       (long)sizeof(coda_expression_string_constant), __FILE__, __LINE__);
        return NULL;
    }
    expr->tag = expr_constant_rawstring;
    expr->result_type = coda_expression_string;
    expr->is_constant = 1;
    expr->length = (long)strlen(string_value);
    expr->value = string_value;

    return (coda_expression *)expr;
}

static coda_expression *string_constant_new(char *string_value)
{
    coda_expression_string_constant *expr;
    long length;

    length = decode_escaped_string(string_value);
    if (length < 0)
    {
        free(string_value);
        return NULL;
    }

    expr = malloc(sizeof(coda_expression_string_constant));
    if (expr == NULL)
    {
        coda_set_error(CODA_ERROR_OUT_OF_MEMORY, "out of memory (could not allocate %lu bytes) (%s:%u)",
                       (long)sizeof(coda_expression_string_constant), __FILE__, __LINE__);
        return NULL;
    }
    expr->tag = expr_constant_string;
    expr->result_type = coda_expression_string;
    expr->is_constant = 1;
    expr->length = length;
    expr->value = string_value;

    return (coda_expression *)expr;
}

coda_expression *coda_expression_new(coda_expression_node_type tag, char *string_value, coda_expression *op1,
                                     coda_expression *op2, coda_expression *op3, coda_expression *op4)
{
    coda_expression_operation *expr;

    switch (tag)
    {
        case expr_constant_boolean:
            return boolean_constant_new(string_value);
        case expr_constant_float:
            return float_constant_new(string_value);
        case expr_constant_integer:
            return integer_constant_new(string_value);
        case expr_constant_rawstring:
            return rawstring_constant_new(string_value);
        case expr_constant_string:
            return string_constant_new(string_value);
        default:
            break;
    }

    if (tag == expr_neg)
    {
        /* turn constant numbers into negative constant values */
        if (op1->tag == expr_constant_float)
        {
            ((coda_expression_float_constant *)op1)->value = -((coda_expression_float_constant *)op1)->value;
            return op1;
        }
        if (op1->tag == expr_constant_integer)
        {
            ((coda_expression_integer_constant *)op1)->value = -((coda_expression_integer_constant *)op1)->value;
            return op1;
        }
    }

    expr = malloc(sizeof(coda_expression_operation));
    if (expr == NULL)
    {
        coda_set_error(CODA_ERROR_OUT_OF_MEMORY, "out of memory (could not allocate %lu bytes) (%s:%u)",
                       (long)sizeof(coda_expression_operation), __FILE__, __LINE__);
        if (string_value != NULL)
        {
            free(string_value);
        }
        if (op1 != NULL)
        {
            coda_expression_delete(op1);
        }
        if (op2 != NULL)
        {
            coda_expression_delete(op2);
        }
        if (op3 != NULL)
        {
            coda_expression_delete(op3);
        }
        if (op4 != NULL)
        {
            coda_expression_delete(op4);
        }
        return NULL;
    }
    expr->tag = tag;
    expr->identifier = string_value;
    expr->operand[0] = op1;
    expr->operand[1] = op2;
    expr->operand[2] = op3;
    expr->operand[3] = op4;

    switch (tag)
    {
        case expr_array_all:
        case expr_array_exists:
        case expr_equal:
        case expr_exists:
        case expr_greater_equal:
        case expr_greater:
        case expr_isinf:
        case expr_ismininf:
        case expr_isnan:
        case expr_isplusinf:
        case expr_less_equal:
        case expr_less:
        case expr_logical_and:
        case expr_logical_or:
        case expr_not_equal:
        case expr_not:
        case expr_variable_exists:
            expr->result_type = coda_expression_boolean;
            break;
        case expr_power:
        case expr_ceil:
        case expr_float:
        case expr_floor:
        case expr_round:
        case expr_time:
            expr->result_type = coda_expression_float;
            break;
        case expr_and:
        case expr_array_count:
        case expr_array_index:
        case expr_bit_offset:
        case expr_bit_size:
        case expr_byte_offset:
        case expr_byte_size:
        case expr_dim:
        case expr_file_size:
        case expr_index:
        case expr_index_var:
        case expr_integer:
        case expr_length:
        case expr_num_dims:
        case expr_num_elements:
        case expr_or:
        case expr_product_version:
        case expr_unbound_array_index:
        case expr_variable_index:
        case expr_variable_value:
            expr->result_type = coda_expression_integer;
            break;
        case expr_bytes:
        case expr_filename:
        case expr_ltrim:
        case expr_product_class:
        case expr_product_format:
        case expr_product_type:
        case expr_rtrim:
        case expr_string:
        case expr_strtime:
        case expr_substr:
        case expr_trim:
            expr->result_type = coda_expression_string;
            break;
        case expr_for:
        case expr_goto:
        case expr_sequence:
        case expr_variable_set:
            expr->result_type = coda_expression_void;
            break;
        case expr_asciiline:
        case expr_goto_array_element:
        case expr_goto_attribute:
        case expr_goto_begin:
        case expr_goto_field:
        case expr_goto_here:
        case expr_goto_parent:
        case expr_goto_root:
            expr->result_type = coda_expression_node;
            break;
        case expr_abs:
        case expr_neg:
            expr->result_type = op1->result_type;
            break;
        case expr_add:
        case expr_divide:
        case expr_max:
        case expr_min:
        case expr_modulo:
        case expr_multiply:
        case expr_subtract:
            if (op1->result_type == coda_expression_float || op2->result_type == coda_expression_float)
            {
                /* allow one of the arguments to be an integer */
                expr->result_type = coda_expression_float;
            }
            else
            {
                expr->result_type = op1->result_type;
            }
            break;
        case expr_array_add:
        case expr_array_max:
        case expr_array_min:
        case expr_at:
        case expr_if:
        case expr_with:
            expr->result_type = op2->result_type;
            break;
        case expr_regex:
            if (op3 == NULL)
            {
                expr->result_type = coda_expression_boolean;
            }
            else
            {
                expr->result_type = coda_expression_string;
            }
            break;
        case expr_constant_boolean:
        case expr_constant_float:
        case expr_constant_integer:
        case expr_constant_rawstring:
        case expr_constant_string:
            assert(0);
            exit(1);
    }

    switch (expr->tag)
    {
        case expr_file_size:
        case expr_filename:
        case expr_product_class:
        case expr_product_format:
        case expr_product_type:
        case expr_product_version:
        case expr_variable_index:
        case expr_variable_set:
        case expr_variable_value:
            expr->is_constant = 0;
            break;
        default:
            expr->is_constant = expr->result_type != coda_expression_node && (op1 == NULL || op1->is_constant) &&
                (op2 == NULL || op2->is_constant) && (op3 == NULL || op3->is_constant) &&
                (op4 == NULL || op4->is_constant);
    }

    return (coda_expression *)expr;
}

typedef struct eval_info_struct
{
    const coda_cursor *orig_cursor;
    coda_cursor cursor;
    int64_t index[3];
    int64_t variable_index;
    const char *variable_name;
} eval_info;

static void init_eval_info(eval_info *info, const coda_cursor *cursor)
{
    info->orig_cursor = cursor;
    if (cursor != NULL)
    {
        info->cursor = *cursor;
    }
    info->index[0] = 0;
    info->index[1] = 0;
    info->index[2] = 0;
    info->variable_index = 0;
    info->variable_name = NULL;
}

static int eval_boolean(eval_info *info, const coda_expression *expr, int *value);
static int eval_float(eval_info *info, const coda_expression *expr, double *value);
static int eval_integer(eval_info *info, const coda_expression *expr, int64_t *value);
static int eval_string(eval_info *info, const coda_expression *expr, long *offset, long *length, char **value);
static int eval_cursor(eval_info *info, const coda_expression *expr);
static int eval_void(eval_info *info, const coda_expression *expr);

static int eval_boolean(eval_info *info, const coda_expression *expr, int *value)
{
    const coda_expression_operation *opexpr;

    if (expr->tag == expr_constant_boolean)
    {
        *value = ((coda_expression_bool_constant *)expr)->value;
        return 0;
    }

    opexpr = (const coda_expression_operation *)expr;
    switch (opexpr->tag)
    {
        case expr_equal:
            if (opexpr->operand[0]->result_type == coda_expression_float ||
                opexpr->operand[1]->result_type == coda_expression_float)
            {
                double a, b;

                if (eval_float(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                if (eval_float(info, opexpr->operand[1], &b) != 0)
                {
                    return -1;
                }
                *value = (a == b);
            }
            else if (opexpr->operand[0]->result_type == coda_expression_integer)
            {
                int64_t a, b;

                if (eval_integer(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                if (eval_integer(info, opexpr->operand[1], &b) != 0)
                {
                    return -1;
                }
                *value = (a == b);
            }
            else if (opexpr->operand[0]->result_type == coda_expression_string)
            {
                long off_a, off_b;
                long len_a, len_b;
                char *a;
                char *b;

                if (eval_string(info, opexpr->operand[0], &off_a, &len_a, &a) != 0)
                {
                    return -1;
                }
                if (eval_string(info, opexpr->operand[1], &off_b, &len_b, &b) != 0)
                {
                    free(a);
                    return -1;
                }
                *value = (compare_strings(off_a, len_a, a, off_b, len_b, b) == 0);
                if (len_a > 0)
                {
                    free(a);
                }
                if (len_b > 0)
                {
                    free(b);
                }
            }
            else
            {
                assert(0);
                exit(1);
            }
            break;
        case expr_not_equal:
            if (opexpr->operand[0]->result_type == coda_expression_float ||
                opexpr->operand[1]->result_type == coda_expression_float)
            {
                double a, b;

                if (eval_float(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                if (eval_float(info, opexpr->operand[1], &b) != 0)
                {
                    return -1;
                }
                *value = (a != b);
            }
            else if (opexpr->operand[0]->result_type == coda_expression_integer)
            {
                int64_t a, b;

                if (eval_integer(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                if (eval_integer(info, opexpr->operand[1], &b) != 0)
                {
                    return -1;
                }
                *value = (a != b);
            }
            else if (opexpr->operand[0]->result_type == coda_expression_string)
            {
                long off_a, off_b;
                long len_a, len_b;
                char *a;
                char *b;

                if (eval_string(info, opexpr->operand[0], &off_a, &len_a, &a) != 0)
                {
                    return -1;
                }
                if (eval_string(info, opexpr->operand[1], &off_b, &len_b, &b) != 0)
                {
                    free(a);
                    return -1;
                }
                *value = (compare_strings(off_a, len_a, a, off_b, len_b, b) != 0);
                if (len_a > 0)
                {
                    free(a);
                }
                if (len_b > 0)
                {
                    free(b);
                }
            }
            else
            {
                assert(0);
                exit(1);
            }
            break;
        case expr_greater:
            if (opexpr->operand[0]->result_type == coda_expression_float ||
                opexpr->operand[1]->result_type == coda_expression_float)
            {
                double a, b;

                if (eval_float(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                if (eval_float(info, opexpr->operand[1], &b) != 0)
                {
                    return -1;
                }
                *value = (a > b);
            }
            else if (opexpr->operand[0]->result_type == coda_expression_integer)
            {
                int64_t a, b;

                if (eval_integer(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                if (eval_integer(info, opexpr->operand[1], &b) != 0)
                {
                    return -1;
                }
                *value = (a > b);
            }
            else if (opexpr->operand[0]->result_type == coda_expression_string)
            {
                long off_a, off_b;
                long len_a, len_b;
                char *a;
                char *b;

                if (eval_string(info, opexpr->operand[0], &off_a, &len_a, &a) != 0)
                {
                    return -1;
                }
                if (eval_string(info, opexpr->operand[1], &off_b, &len_b, &b) != 0)
                {
                    free(a);
                    return -1;
                }
                *value = (compare_strings(off_a, len_a, a, off_b, len_b, b) > 0);
                if (len_a > 0)
                {
                    free(a);
                }
                if (len_b > 0)
                {
                    free(b);
                }
            }
            else
            {
                assert(0);
                exit(1);
            }
            break;
        case expr_greater_equal:
            if (opexpr->operand[0]->result_type == coda_expression_float ||
                opexpr->operand[1]->result_type == coda_expression_float)
            {
                double a, b;

                if (eval_float(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                if (eval_float(info, opexpr->operand[1], &b) != 0)
                {
                    return -1;
                }
                *value = (a >= b);
            }
            else if (opexpr->operand[0]->result_type == coda_expression_integer)
            {
                int64_t a, b;

                if (eval_integer(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                if (eval_integer(info, opexpr->operand[1], &b) != 0)
                {
                    return -1;
                }
                *value = (a >= b);
            }
            else if (opexpr->operand[0]->result_type == coda_expression_string)
            {
                long off_a, off_b;
                long len_a, len_b;
                char *a;
                char *b;

                if (eval_string(info, opexpr->operand[0], &off_a, &len_a, &a) != 0)
                {
                    return -1;
                }
                if (eval_string(info, opexpr->operand[1], &off_b, &len_b, &b) != 0)
                {
                    free(a);
                    return -1;
                }
                *value = (compare_strings(off_a, len_a, a, off_b, len_b, b) >= 0);
                if (len_a > 0)
                {
                    free(a);
                }
                if (len_b > 0)
                {
                    free(b);
                }
            }
            else
            {
                assert(0);
                exit(1);
            }
            break;
        case expr_less:
            if (opexpr->operand[0]->result_type == coda_expression_float ||
                opexpr->operand[1]->result_type == coda_expression_float)
            {
                double a, b;

                if (eval_float(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                if (eval_float(info, opexpr->operand[1], &b) != 0)
                {
                    return -1;
                }
                *value = (a < b);
            }
            else if (opexpr->operand[0]->result_type == coda_expression_integer)
            {
                int64_t a, b;

                if (eval_integer(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                if (eval_integer(info, opexpr->operand[1], &b) != 0)
                {
                    return -1;
                }
                *value = (a < b);
            }
            else if (opexpr->operand[0]->result_type == coda_expression_string)
            {
                long off_a, off_b;
                long len_a, len_b;
                char *a;
                char *b;

                if (eval_string(info, opexpr->operand[0], &off_a, &len_a, &a) != 0)
                {
                    return -1;
                }
                if (eval_string(info, opexpr->operand[1], &off_b, &len_b, &b) != 0)
                {
                    free(a);
                    return -1;
                }
                *value = (compare_strings(off_a, len_a, a, off_b, len_b, b) < 0);
                if (len_a > 0)
                {
                    free(a);
                }
                if (len_b > 0)
                {
                    free(b);
                }
            }
            else
            {
                assert(0);
                exit(1);
            }
            break;
        case expr_less_equal:
            if (opexpr->operand[0]->result_type == coda_expression_float ||
                opexpr->operand[1]->result_type == coda_expression_float)
            {
                double a, b;

                if (eval_float(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                if (eval_float(info, opexpr->operand[1], &b) != 0)
                {
                    return -1;
                }
                *value = (a <= b);
            }
            else if (opexpr->operand[0]->result_type == coda_expression_integer)
            {
                int64_t a, b;

                if (eval_integer(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                if (eval_integer(info, opexpr->operand[1], &b) != 0)
                {
                    return -1;
                }
                *value = (a <= b);
            }
            else if (opexpr->operand[0]->result_type == coda_expression_string)
            {
                long off_a, off_b;
                long len_a, len_b;
                char *a;
                char *b;

                if (eval_string(info, opexpr->operand[0], &off_a, &len_a, &a) != 0)
                {
                    return -1;
                }
                if (eval_string(info, opexpr->operand[1], &off_b, &len_b, &b) != 0)
                {
                    free(a);
                    return -1;
                }
                *value = (compare_strings(off_a, len_a, a, off_b, len_b, b) <= 0);
                if (len_a > 0)
                {
                    free(a);
                }
                if (len_b > 0)
                {
                    free(b);
                }
            }
            else
            {
                assert(0);
                exit(1);
            }
            break;
        case expr_not:
            if (eval_boolean(info, opexpr->operand[0], value) != 0)
            {
                return -1;
            }
            *value = !(*value);
            break;
        case expr_logical_and:
            if (eval_boolean(info, opexpr->operand[0], value) != 0)
            {
                return -1;
            }
            if (*value == 0)
            {
                return 0;
            }
            if (eval_boolean(info, opexpr->operand[1], value) != 0)
            {
                return -1;
            }
            break;
        case expr_logical_or:
            if (eval_boolean(info, opexpr->operand[0], value) != 0)
            {
                return -1;
            }
            if (*value != 0)
            {
                return 0;
            }
            if (eval_boolean(info, opexpr->operand[1], value) != 0)
            {
                return -1;
            }
            break;
        case expr_isnan:
            {
                double a;

                if (eval_float(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                *value = coda_isNaN(a);
            }
            break;
        case expr_isinf:
            {
                double a;

                if (eval_float(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                *value = coda_isInf(a);
            }
            break;
        case expr_isplusinf:
            {
                double a;

                if (eval_float(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                *value = coda_isPlusInf(a);
            }
            break;
        case expr_ismininf:
            {
                double a;

                if (eval_float(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                *value = coda_isMinInf(a);
            }
            break;
        case expr_regex:
            {
                int ovector[(REGEX_MAX_NUM_SUBSTRING + 1) * 3];
                const char *error;
                int erroffset;
                long matchstring_offset;
                long matchstring_length;
                char *matchstring;
                long pattern_offset;
                long pattern_length;
                char *pattern;
                pcre *re;
                int rc;

                if (eval_string(info, opexpr->operand[0], &pattern_offset, &pattern_length, &pattern) != 0)
                {
                    return -1;
                }
                if (eval_string(info, opexpr->operand[1], &matchstring_offset, &matchstring_length, &matchstring) != 0)
                {
                    if (pattern != NULL)
                    {
                        free(pattern);
                    }
                    return -1;
                }

                if (pattern_length > 0)
                {
                    pattern[pattern_offset + pattern_length] = '\0';    /* add terminating zero */
                    re = pcre_compile(&pattern[pattern_offset], PCRE_DOTALL | PCRE_DOLLAR_ENDONLY, &error, &erroffset,
                                      NULL);
                }
                else
                {
                    re = pcre_compile("", PCRE_DOTALL | PCRE_DOLLAR_ENDONLY, &error, &erroffset, NULL);
                }
                if (pattern != NULL)
                {
                    free(pattern);
                }
                if (re == NULL)
                {
                    coda_set_error(CODA_ERROR_EXPRESSION,
                                   "invalid format for regex pattern ('%s' at position %d)", error, erroffset);
                    if (matchstring != NULL)
                    {
                        free(matchstring);
                    }
                    return -1;
                }

                if (matchstring == NULL)
                {
                    /* pcre_exec does not except NULL for an empty matchstring */
                    matchstring = strdup("");
                    if (matchstring == NULL)
                    {
                        coda_set_error(CODA_ERROR_OUT_OF_MEMORY, "out of memory (could not duplicate string) (%s:%u)",
                                       __FILE__, __LINE__);
                        pcre_free(re);
                        return -1;
                    }
                }

                rc = pcre_exec(re, NULL, &matchstring[matchstring_offset], matchstring_length, 0, 0, ovector,
                               (REGEX_MAX_NUM_SUBSTRING + 1) * 3);
                free(matchstring);
                pcre_free(re);
                if (rc < 0 && rc != PCRE_ERROR_NOMATCH)
                {
                    coda_set_error(CODA_ERROR_EXPRESSION, "could not evaluate regex pattern (error code %d)", rc);
                    return -1;
                }
                if (rc == 0)
                {
                    coda_set_error(CODA_ERROR_EXPRESSION, "regex pattern contains too many subexpressions");
                    return -1;
                }
                *value = (rc > 0);
            }
            break;
        case expr_exists:
            {
                coda_cursor prev_cursor;
                coda_type_class type_class;

                assert(info->orig_cursor != NULL);
                prev_cursor = info->cursor;
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    if (coda_errno != CODA_ERROR_EXPRESSION)
                    {
                        /* could not access path */
                        coda_errno = 0;
                        *value = 0;
                        info->cursor = prev_cursor;
                        return 0;
                    }
                    return -1;
                }
                if (coda_cursor_get_type_class(&info->cursor, &type_class) != 0)
                {
                    return -1;
                }
                if (type_class == coda_special_class)
                {
                    coda_special_type special_type;

                    if (coda_cursor_get_special_type(&info->cursor, &special_type) != 0)
                    {
                        return -1;
                    }
                    if (special_type == coda_special_no_data)
                    {
                        *value = 0;
                        info->cursor = prev_cursor;
                        return 0;
                    }
                }
                *value = 1;
                info->cursor = prev_cursor;
            }
            break;
        case expr_array_all:
            {
                coda_cursor prev_cursor;
                long num_elements;

                assert(info->orig_cursor != NULL);
                prev_cursor = info->cursor;
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
                if (coda_cursor_get_num_elements(&info->cursor, &num_elements) != 0)
                {
                    return -1;
                }
                if (num_elements > 0)
                {
                    long i;

                    if (coda_cursor_goto_first_array_element(&info->cursor) != 0)
                    {
                        return -1;
                    }
                    for (i = 0; i < num_elements; i++)
                    {
                        int condition;

                        if (eval_boolean(info, opexpr->operand[1], &condition) != 0)
                        {
                            return -1;
                        }
                        if (!condition)
                        {
                            *value = 0;
                            return 0;
                        }
                        if (i < num_elements - 1)
                        {
                            if (coda_cursor_goto_next_array_element(&info->cursor) != 0)
                            {
                                return -1;
                            }
                        }
                    }
                }
                *value = 1;
                info->cursor = prev_cursor;
            }
            break;
        case expr_array_exists:
            {
                coda_cursor prev_cursor;
                long num_elements;

                assert(info->orig_cursor != NULL);
                prev_cursor = info->cursor;
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
                if (coda_cursor_get_num_elements(&info->cursor, &num_elements) != 0)
                {
                    return -1;
                }
                if (num_elements > 0)
                {
                    long i;

                    if (coda_cursor_goto_first_array_element(&info->cursor) != 0)
                    {
                        return -1;
                    }
                    for (i = 0; i < num_elements; i++)
                    {
                        int condition;

                        if (eval_boolean(info, opexpr->operand[1], &condition) != 0)
                        {
                            return -1;
                        }
                        if (condition)
                        {
                            *value = 1;
                            return 0;
                        }
                        if (i < num_elements - 1)
                        {
                            if (coda_cursor_goto_next_array_element(&info->cursor) != 0)
                            {
                                return -1;
                            }
                        }
                    }
                }
                *value = 0;
                info->cursor = prev_cursor;
            }
            break;
        case expr_variable_exists:
            {
                long size;
                long i;

                assert(info->orig_cursor != NULL);
                if (info->variable_name != NULL)
                {
                    coda_set_error(CODA_ERROR_EXPRESSION, "cannot perform search within search for product variables");
                    return -1;
                }

                if (coda_product_variable_get_size(info->orig_cursor->product, opexpr->identifier, &size) != 0)
                {
                    return -1;
                }
                info->variable_name = opexpr->identifier;
                for (i = 0; i < size; i++)
                {
                    int condition;

                    info->variable_index = i;
                    if (eval_boolean(info, opexpr->operand[0], &condition) != 0)
                    {
                        return -1;
                    }
                    if (condition)
                    {
                        *value = 1;
                        info->variable_name = NULL;
                        return 0;
                    }
                }
                *value = 0;
                info->variable_name = NULL;
            }
            break;
        case expr_if:
            {
                int condition;

                if (eval_boolean(info, opexpr->operand[0], &condition) != 0)
                {
                    return -1;
                }
                if (condition)
                {
                    if (eval_boolean(info, opexpr->operand[1], value) != 0)
                    {
                        return -1;
                    }
                }
                else
                {
                    if (eval_boolean(info, opexpr->operand[2], value) != 0)
                    {
                        return -1;
                    }
                }
            }
            break;
        case expr_at:
            {
                coda_cursor prev_cursor;

                assert(info->orig_cursor != NULL);
                prev_cursor = info->cursor;
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
                if (eval_boolean(info, opexpr->operand[1], value) != 0)
                {
                    return -1;
                }
                info->cursor = prev_cursor;
            }
            break;
        case expr_with:
            {
                int64_t prev_index;
                int index_id = opexpr->identifier[0] - 'i';

                prev_index = info->index[index_id];
                if (eval_integer(info, opexpr->operand[0], &info->index[index_id]) != 0)
                {
                    return -1;
                }
                if (eval_boolean(info, opexpr->operand[1], value) != 0)
                {
                    return -1;
                }
                info->index[index_id] = prev_index;
            }
            break;
        default:
            assert(0);
            exit(1);
    }

    return 0;
}

static int eval_float(eval_info *info, const coda_expression *expr, double *value)
{
    const coda_expression_operation *opexpr;

    /* we allow auto conversion of integer to double */
    if (expr->result_type == coda_expression_integer)
    {
        int64_t intvalue;

        if (eval_integer(info, expr, &intvalue) != 0)
        {
            return -1;
        }
        *value = (double)intvalue;
        return 0;
    }

    if (expr->tag == expr_constant_float)
    {
        *value = ((coda_expression_float_constant *)expr)->value;
        return 0;
    }

    opexpr = (const coda_expression_operation *)expr;
    switch (opexpr->tag)
    {
        case expr_float:
            if (opexpr->operand[0]->result_type == coda_expression_node)
            {
                coda_cursor prev_cursor;
                int perform_conversions;

                assert(info->orig_cursor != NULL);
                prev_cursor = info->cursor;
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
                perform_conversions = coda_get_option_perform_conversions();
                coda_set_option_perform_conversions(0);
                if (coda_cursor_read_double(&info->cursor, value) != 0)
                {
                    coda_set_option_perform_conversions(perform_conversions);
                    return -1;
                }
                coda_set_option_perform_conversions(perform_conversions);
                info->cursor = prev_cursor;
            }
            else if (opexpr->operand[0]->result_type == coda_expression_string)
            {
                long offset;
                long length;
                char *str;

                if (eval_string(info, opexpr->operand[0], &offset, &length, &str) != 0)
                {
                    return -1;
                }
                if (length == 0)
                {
                    coda_set_error(CODA_ERROR_INVALID_FORMAT,
                                   "invalid format for ascii floating point value (no digits)");
                    return -1;
                }
                if (coda_ascii_parse_double(&str[offset], length, value, 0) < 0)
                {
                    free(str);
                    return -1;
                }
                free(str);
            }
            else
            {
                int64_t intvalue;

                if (eval_integer(info, opexpr->operand[0], &intvalue) != 0)
                {
                    return -1;
                }
                *value = (double)intvalue;
            }
            break;
        case expr_neg:
            if (eval_float(info, opexpr->operand[0], value) != 0)
            {
                return -1;
            }
            *value = -(*value);
            break;
        case expr_abs:
            if (eval_float(info, opexpr->operand[0], value) != 0)
            {
                return -1;
            }
            *value = ((*value) >= 0 ? *value : -(*value));
            break;
        case expr_ceil:
            if (eval_float(info, opexpr->operand[0], value) != 0)
            {
                return -1;
            }
            *value = ceil(*value);
            break;
        case expr_floor:
            if (eval_float(info, opexpr->operand[0], value) != 0)
            {
                return -1;
            }
            *value = floor(*value);
            break;
        case expr_round:
            if (eval_float(info, opexpr->operand[0], value) != 0)
            {
                return -1;
            }
            if (*value < 0)
            {
                *value = ceil(*value - 0.5);
            }
            else
            {
                *value = floor(*value + 0.5);
            }
            break;
        case expr_add:
            {
                double a, b;

                if (eval_float(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                if (eval_float(info, opexpr->operand[1], &b) != 0)
                {
                    return -1;
                }
                *value = a + b;
            }
            break;
        case expr_subtract:
            {
                double a, b;

                if (eval_float(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                if (eval_float(info, opexpr->operand[1], &b) != 0)
                {
                    return -1;
                }
                *value = a - b;
            }
            break;
        case expr_multiply:
            {
                double a, b;

                if (eval_float(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                if (eval_float(info, opexpr->operand[1], &b) != 0)
                {
                    return -1;
                }
                *value = a * b;
            }
            break;
        case expr_divide:
            {
                double a, b;

                if (eval_float(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                if (eval_float(info, opexpr->operand[1], &b) != 0)
                {
                    return -1;
                }
                if (b == 0)
                {
                    coda_set_error(CODA_ERROR_EXPRESSION, "division by 0 in expression");
                    return -1;
                }
                *value = a / b;
            }
            break;
        case expr_modulo:
            {
                double a, b;

                if (eval_float(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                if (eval_float(info, opexpr->operand[1], &b) != 0)
                {
                    return -1;
                }
                if (b == 0)
                {
                    coda_set_error(CODA_ERROR_EXPRESSION, "modulo by 0 in expression");
                    return -1;
                }
                *value = fmod(a, b);
            }
            break;
        case expr_power:
            {
                double a, b;

                if (eval_float(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                if (opexpr->operand[1]->result_type == coda_expression_integer)
                {
                    int64_t intvalue;

                    /* try to use a more accurate algorithm if the exponent is a small integer */
                    if (eval_integer(info, opexpr->operand[1], &intvalue) != 0)
                    {
                        return -1;
                    }
                    if (intvalue >= -64 && intvalue <= 64)
                    {
                        *value = ipow(a, (int)intvalue);
                    }
                    else
                    {
                        *value = pow(a, (double)intvalue);
                    }
                }
                else
                {
                    if (eval_float(info, opexpr->operand[1], &b) != 0)
                    {
                        return -1;
                    }
                    *value = pow(a, b);
                }
            }
            break;
        case expr_max:
            {
                double a, b;

                if (eval_float(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                if (eval_float(info, opexpr->operand[1], &b) != 0)
                {
                    return -1;
                }
                *value = (a > b ? a : b);
            }
            break;
        case expr_min:
            {
                double a, b;

                if (eval_float(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                if (eval_float(info, opexpr->operand[1], &b) != 0)
                {
                    return -1;
                }
                *value = (a < b ? a : b);
            }
            break;
        case expr_time:
            {
                long off_timestr, off_format;
                long len_timestr, len_format;
                char *timestr;
                char *format;

                if (eval_string(info, opexpr->operand[0], &off_timestr, &len_timestr, &timestr) != 0)
                {
                    return -1;
                }
                if (len_timestr < 0)
                {
                    coda_set_error(CODA_ERROR_EXPRESSION, "negative length for time string");
                    return -1;
                }
                if (len_timestr == 0)
                {
                    coda_set_error(CODA_ERROR_EXPRESSION, "time string is empty");
                    return -1;
                }
                timestr[off_timestr + len_timestr] = '\0';      /* add terminating zero */
                if (eval_string(info, opexpr->operand[1], &off_format, &len_format, &format) != 0)
                {
                    free(timestr);
                    return -1;
                }
                if (len_format < 0)
                {
                    coda_set_error(CODA_ERROR_EXPRESSION, "negative length for time format");
                    return -1;
                }
                if (len_format == 0)
                {
                    coda_set_error(CODA_ERROR_EXPRESSION, "time format is empty");
                    return -1;
                }
                format[off_format + len_format] = '\0'; /* add terminating zero */
                if (coda_time_string_to_double(&format[off_format], &timestr[off_timestr], value) != 0)
                {
                    free(format);
                    free(timestr);
                    return -1;
                }
                free(format);
                free(timestr);
            }
            break;
        case expr_if:
            {
                int condition;

                if (eval_boolean(info, opexpr->operand[0], &condition) != 0)
                {
                    return -1;
                }
                if (condition)
                {
                    if (eval_float(info, opexpr->operand[1], value) != 0)
                    {
                        return -1;
                    }
                }
                else
                {
                    if (eval_float(info, opexpr->operand[2], value) != 0)
                    {
                        return -1;
                    }
                }
            }
            break;
        case expr_array_add:
            {
                coda_cursor prev_cursor;
                long num_elements;

                assert(info->orig_cursor != NULL);
                prev_cursor = info->cursor;
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
                if (coda_cursor_get_num_elements(&info->cursor, &num_elements) != 0)
                {
                    return -1;
                }
                *value = 0;
                if (num_elements > 0)
                {
                    long i;

                    if (coda_cursor_goto_first_array_element(&info->cursor) != 0)
                    {
                        return -1;
                    }
                    for (i = 0; i < num_elements; i++)
                    {
                        double element_value;

                        if (eval_float(info, opexpr->operand[1], &element_value) != 0)
                        {
                            return -1;
                        }
                        *value += element_value;
                        if (i < num_elements - 1)
                        {
                            if (coda_cursor_goto_next_array_element(&info->cursor) != 0)
                            {
                                return -1;
                            }
                        }
                    }
                }
                info->cursor = prev_cursor;
            }
            break;
        case expr_array_max:
            {
                coda_cursor prev_cursor;
                long num_elements;

                assert(info->orig_cursor != NULL);
                prev_cursor = info->cursor;
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
                if (coda_cursor_get_num_elements(&info->cursor, &num_elements) != 0)
                {
                    return -1;
                }
                *value = coda_NaN();
                if (num_elements > 0)
                {
                    long i;

                    if (coda_cursor_goto_first_array_element(&info->cursor) != 0)
                    {
                        return -1;
                    }
                    for (i = 0; i < num_elements; i++)
                    {
                        double element_value;

                        if (eval_float(info, opexpr->operand[1], &element_value) != 0)
                        {
                            return -1;
                        }
                        if (i == 0 || element_value > *value)
                        {
                            *value = element_value;
                        }
                        if (i < num_elements - 1)
                        {
                            if (coda_cursor_goto_next_array_element(&info->cursor) != 0)
                            {
                                return -1;
                            }
                        }
                    }
                }
                info->cursor = prev_cursor;
            }
            break;
        case expr_array_min:
            {
                coda_cursor prev_cursor;
                long num_elements;

                assert(info->orig_cursor != NULL);
                prev_cursor = info->cursor;
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
                if (coda_cursor_get_num_elements(&info->cursor, &num_elements) != 0)
                {
                    return -1;
                }
                *value = coda_NaN();
                if (num_elements > 0)
                {
                    long i;

                    if (coda_cursor_goto_first_array_element(&info->cursor) != 0)
                    {
                        return -1;
                    }
                    for (i = 0; i < num_elements; i++)
                    {
                        double element_value;

                        if (eval_float(info, opexpr->operand[1], &element_value) != 0)
                        {
                            return -1;
                        }
                        if (i == 0 || element_value < *value)
                        {
                            *value = element_value;
                        }
                        if (i < num_elements - 1)
                        {
                            if (coda_cursor_goto_next_array_element(&info->cursor) != 0)
                            {
                                return -1;
                            }
                        }
                    }
                }
                info->cursor = prev_cursor;
            }
            break;
        case expr_at:
            {
                coda_cursor prev_cursor;

                assert(info->orig_cursor != NULL);
                prev_cursor = info->cursor;
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
                if (eval_float(info, opexpr->operand[1], value) != 0)
                {
                    return -1;
                }
                info->cursor = prev_cursor;
            }
            break;
        case expr_with:
            {
                int64_t prev_index;
                int index_id = opexpr->identifier[0] - 'i';

                prev_index = info->index[index_id];
                if (eval_integer(info, opexpr->operand[0], &info->index[index_id]) != 0)
                {
                    return -1;
                }
                if (eval_float(info, opexpr->operand[1], value) != 0)
                {
                    return -1;
                }
                info->index[index_id] = prev_index;
            }
            break;
        default:
            assert(0);
            exit(1);
    }

    return 0;
}

static int eval_integer(eval_info *info, const coda_expression *expr, int64_t *value)
{
    const coda_expression_operation *opexpr;

    if (expr->tag == expr_constant_integer)
    {
        *value = ((coda_expression_integer_constant *)expr)->value;
        return 0;
    }

    opexpr = (const coda_expression_operation *)expr;
    switch (opexpr->tag)
    {
        case expr_integer:
            if (opexpr->operand[0]->result_type == coda_expression_node)
            {
                coda_cursor prev_cursor;
                coda_native_type read_type;
                int perform_conversions;

                assert(info->orig_cursor != NULL);
                prev_cursor = info->cursor;
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
                perform_conversions = coda_get_option_perform_conversions();
                coda_set_option_perform_conversions(0);
                if (coda_cursor_get_read_type(&info->cursor, &read_type) != 0)
                {
                    coda_set_option_perform_conversions(perform_conversions);
                    return -1;
                }
                if (read_type == coda_native_type_uint64)
                {
                    uint64_t uvalue;

                    /* read it as an uint64 and then cast it to a int64 */
                    if (coda_cursor_read_uint64(&info->cursor, &uvalue) != 0)
                    {
                        coda_set_option_perform_conversions(perform_conversions);
                        return -1;
                    }
                    *value = (int64_t)uvalue;
                }
                else
                {
                    if (coda_cursor_read_int64(&info->cursor, value) != 0)
                    {
                        coda_set_option_perform_conversions(perform_conversions);
                        return -1;
                    }
                }
                coda_set_option_perform_conversions(perform_conversions);
                info->cursor = prev_cursor;
            }
            else if (opexpr->operand[0]->result_type == coda_expression_boolean)
            {
                int bvalue;

                if (eval_boolean(info, opexpr->operand[0], &bvalue) != 0)
                {
                    return -1;
                }
                *value = (int64_t)bvalue;
            }
            else
            {
                long offset;
                long length;
                char *str;

                if (eval_string(info, opexpr->operand[0], &offset, &length, &str) != 0)
                {
                    return -1;
                }
                if (length == 0)
                {
                    coda_set_error(CODA_ERROR_INVALID_FORMAT,
                                   "invalid format for ascii floating point value (no digits)");
                    return -1;
                }
                if (coda_ascii_parse_int64(&str[offset], length, value, 0) < 0)
                {
                    free(str);
                    return -1;
                }
                free(str);
            }
            break;
        case expr_neg:
            if (eval_integer(info, opexpr->operand[0], value) != 0)
            {
                return -1;
            }
            *value = -(*value);
            break;
        case expr_abs:
            if (eval_integer(info, opexpr->operand[0], value) != 0)
            {
                return -1;
            }
            *value = ((*value) >= 0 ? *value : -(*value));
            break;
        case expr_add:
            {
                int64_t a, b;

                if (eval_integer(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                if (eval_integer(info, opexpr->operand[1], &b) != 0)
                {
                    return -1;
                }
                *value = a + b;
            }
            break;
        case expr_subtract:
            {
                int64_t a, b;

                if (eval_integer(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                if (eval_integer(info, opexpr->operand[1], &b) != 0)
                {
                    return -1;
                }
                *value = a - b;
            }
            break;
        case expr_multiply:
            {
                int64_t a, b;

                if (eval_integer(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                if (eval_integer(info, opexpr->operand[1], &b) != 0)
                {
                    return -1;
                }
                *value = a * b;
            }
            break;
        case expr_divide:
            {
                int64_t a, b;

                if (eval_integer(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                if (eval_integer(info, opexpr->operand[1], &b) != 0)
                {
                    return -1;
                }
                if (b == 0)
                {
                    coda_set_error(CODA_ERROR_EXPRESSION, "division by 0 in expression");
                    return -1;
                }
                *value = a / b;
            }
            break;
        case expr_modulo:
            {
                int64_t a, b;

                if (eval_integer(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                if (eval_integer(info, opexpr->operand[1], &b) != 0)
                {
                    return -1;
                }
                if (b == 0)
                {
                    coda_set_error(CODA_ERROR_EXPRESSION, "modulo by 0 in expression");
                    return -1;
                }
                *value = a % b;
            }
            break;
        case expr_and:
            {
                int64_t a, b;

                if (eval_integer(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                if (eval_integer(info, opexpr->operand[1], &b) != 0)
                {
                    return -1;
                }
                *value = a & b;
            }
            break;
        case expr_or:
            {
                int64_t a, b;

                if (eval_integer(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                if (eval_integer(info, opexpr->operand[1], &b) != 0)
                {
                    return -1;
                }
                *value = a | b;
            }
            break;
        case expr_max:
            {
                int64_t a, b;

                if (eval_integer(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                if (eval_integer(info, opexpr->operand[1], &b) != 0)
                {
                    return -1;
                }
                *value = (a > b ? a : b);
            }
            break;
        case expr_min:
            {
                int64_t a, b;

                if (eval_integer(info, opexpr->operand[0], &a) != 0)
                {
                    return -1;
                }
                if (eval_integer(info, opexpr->operand[1], &b) != 0)
                {
                    return -1;
                }
                *value = (a < b ? a : b);
            }
            break;
        case expr_if:
            {
                int condition;

                if (eval_boolean(info, opexpr->operand[0], &condition) != 0)
                {
                    return -1;
                }
                if (condition)
                {
                    if (eval_integer(info, opexpr->operand[1], value) != 0)
                    {
                        return -1;
                    }
                }
                else
                {
                    if (eval_integer(info, opexpr->operand[2], value) != 0)
                    {
                        return -1;
                    }
                }
            }
            break;
        case expr_array_count:
            {
                coda_cursor prev_cursor;
                long num_elements;

                assert(info->orig_cursor != NULL);
                prev_cursor = info->cursor;
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
                if (coda_cursor_get_num_elements(&info->cursor, &num_elements) != 0)
                {
                    return -1;
                }
                *value = 0;
                if (num_elements > 0)
                {
                    long i;

                    if (coda_cursor_goto_first_array_element(&info->cursor) != 0)
                    {
                        return -1;
                    }
                    for (i = 0; i < num_elements; i++)
                    {
                        int condition;

                        if (eval_boolean(info, opexpr->operand[1], &condition) != 0)
                        {
                            return -1;
                        }
                        if (condition)
                        {
                            (*value)++;
                        }
                        if (i < num_elements - 1)
                        {
                            if (coda_cursor_goto_next_array_element(&info->cursor) != 0)
                            {
                                return -1;
                            }
                        }
                    }
                }
                info->cursor = prev_cursor;
            }
            break;
        case expr_array_add:
            {
                coda_cursor prev_cursor;
                long num_elements;

                assert(info->orig_cursor != NULL);
                prev_cursor = info->cursor;
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
                if (coda_cursor_get_num_elements(&info->cursor, &num_elements) != 0)
                {
                    return -1;
                }
                *value = 0;
                if (num_elements > 0)
                {
                    long i;

                    if (coda_cursor_goto_first_array_element(&info->cursor) != 0)
                    {
                        return -1;
                    }
                    for (i = 0; i < num_elements; i++)
                    {
                        int64_t element_value;

                        if (eval_integer(info, opexpr->operand[1], &element_value) != 0)
                        {
                            return -1;
                        }
                        *value += element_value;
                        if (i < num_elements - 1)
                        {
                            if (coda_cursor_goto_next_array_element(&info->cursor) != 0)
                            {
                                return -1;
                            }
                        }
                    }
                }
                info->cursor = prev_cursor;
            }
            break;
        case expr_array_max:
            {
                coda_cursor prev_cursor;
                long num_elements;

                assert(info->orig_cursor != NULL);
                prev_cursor = info->cursor;
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
                if (coda_cursor_get_num_elements(&info->cursor, &num_elements) != 0)
                {
                    return -1;
                }
                *value = 0;
                if (num_elements > 0)
                {
                    long i;

                    if (coda_cursor_goto_first_array_element(&info->cursor) != 0)
                    {
                        return -1;
                    }
                    for (i = 0; i < num_elements; i++)
                    {
                        int64_t element_value;

                        if (eval_integer(info, opexpr->operand[1], &element_value) != 0)
                        {
                            return -1;
                        }
                        if (i == 0 || element_value > *value)
                        {
                            *value = element_value;
                        }
                        if (i < num_elements - 1)
                        {
                            if (coda_cursor_goto_next_array_element(&info->cursor) != 0)
                            {
                                return -1;
                            }
                        }
                    }
                }
                info->cursor = prev_cursor;
            }
            break;
        case expr_array_min:
            {
                coda_cursor prev_cursor;
                long num_elements;

                assert(info->orig_cursor != NULL);
                prev_cursor = info->cursor;
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
                if (coda_cursor_get_num_elements(&info->cursor, &num_elements) != 0)
                {
                    return -1;
                }
                *value = 0;
                if (num_elements > 0)
                {
                    long i;

                    if (coda_cursor_goto_first_array_element(&info->cursor) != 0)
                    {
                        return -1;
                    }
                    for (i = 0; i < num_elements; i++)
                    {
                        int64_t element_value;

                        if (eval_integer(info, opexpr->operand[1], &element_value) != 0)
                        {
                            return -1;
                        }
                        if (i == 0 || element_value < *value)
                        {
                            *value = element_value;
                        }
                        if (i < num_elements - 1)
                        {
                            if (coda_cursor_goto_next_array_element(&info->cursor) != 0)
                            {
                                return -1;
                            }
                        }
                    }
                }
                info->cursor = prev_cursor;
            }
            break;
        case expr_array_index:
            {
                coda_cursor prev_cursor;
                long num_elements;

                assert(info->orig_cursor != NULL);
                prev_cursor = info->cursor;
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
                if (coda_cursor_get_num_elements(&info->cursor, &num_elements) != 0)
                {
                    return -1;
                }
                *value = 0;
                if (num_elements > 0)
                {
                    long i;

                    if (coda_cursor_goto_first_array_element(&info->cursor) != 0)
                    {
                        return -1;
                    }
                    for (i = 0; i < num_elements; i++)
                    {
                        int condition;

                        if (eval_boolean(info, opexpr->operand[1], &condition) != 0)
                        {
                            return -1;
                        }
                        if (condition)
                        {
                            *value = i;
                            info->cursor = prev_cursor;
                            return 0;
                        }
                        if (i < num_elements - 1)
                        {
                            if (coda_cursor_goto_next_array_element(&info->cursor) != 0)
                            {
                                return -1;
                            }
                        }
                    }
                }
                *value = -1;
                info->cursor = prev_cursor;
            }
            break;
        case expr_unbound_array_index:
            {
                coda_cursor prev_cursor;
                int prev_option;
                int condition = 0;

                assert(info->orig_cursor != NULL);
                prev_cursor = info->cursor;
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
                prev_option = coda_option_perform_boundary_checks;
                coda_option_perform_boundary_checks = 0;
                if (coda_cursor_goto_first_array_element(&info->cursor) != 0)
                {
                    coda_option_perform_boundary_checks = prev_option;
                    return -1;
                }
                *value = 0;
                while (!condition)
                {
                    if (opexpr->operand[2] != NULL)
                    {
                        if (eval_boolean(info, opexpr->operand[1], &condition) != 0)
                        {
                            coda_option_perform_boundary_checks = prev_option;
                            return -1;
                        }
                    }
                    if (condition)
                    {
                        *value = -1;
                    }
                    else
                    {
                        if (eval_boolean(info, opexpr->operand[1], &condition) != 0)
                        {
                            coda_option_perform_boundary_checks = prev_option;
                            return -1;
                        }
                        if (!condition)
                        {
                            (*value)++;
                            if (coda_cursor_goto_next_array_element(&info->cursor) != 0)
                            {
                                coda_option_perform_boundary_checks = prev_option;
                                return -1;
                            }
                        }
                    }
                }
                coda_option_perform_boundary_checks = prev_option;
                info->cursor = prev_cursor;
            }
            break;
        case expr_length:
            if (opexpr->operand[0]->result_type == coda_expression_node)
            {
                coda_cursor prev_cursor;
                long length;

                assert(info->orig_cursor != NULL);
                prev_cursor = info->cursor;
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
                if (coda_cursor_get_string_length(&info->cursor, &length) != 0)
                {
                    return -1;
                }
                *value = length;
                info->cursor = prev_cursor;
            }
            else
            {
                long offset;
                long length;
                char *str;

                if (eval_string(info, opexpr->operand[0], &offset, &length, &str) != 0)
                {
                    return -1;
                }
                if (length > 0)
                {
                    free(str);
                }
                *value = length;
            }
            break;
        case expr_dim:
            {
                coda_cursor prev_cursor;
                long dim[CODA_MAX_NUM_DIMS];
                int num_dims;
                int64_t dim_id;

                assert(info->orig_cursor != NULL);
                prev_cursor = info->cursor;
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
                if (eval_integer(info, opexpr->operand[1], &dim_id) != 0)
                {
                    return -1;
                }
                if (coda_cursor_get_array_dim(&info->cursor, &num_dims, dim) != 0)
                {
                    return -1;
                }
                if (dim_id < 0)
                {
                    coda_set_error(CODA_ERROR_EXPRESSION, "dimension index (%ld) is negative", (long)dim_id);
                    return -1;
                }
                if (dim_id >= num_dims)
                {
                    coda_set_error(CODA_ERROR_EXPRESSION, "dimension index (%ld) exceeds number of dimensions (%d)",
                                   (long)dim_id, num_dims);
                    return -1;
                }
                info->cursor = prev_cursor;
                *value = dim[dim_id];
            }
            break;
        case expr_num_dims:
            {
                coda_cursor prev_cursor;
                coda_type *type;
                int num_dims;

                assert(info->orig_cursor != NULL);
                prev_cursor = info->cursor;
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
                if (coda_cursor_get_type(&info->cursor, &type) != 0)
                {
                    return -1;
                }
                if (coda_type_get_array_num_dims(type, &num_dims) != 0)
                {
                    return -1;
                }
                info->cursor = prev_cursor;
                *value = num_dims;
            }
            break;
        case expr_num_elements:
            {
                coda_cursor prev_cursor;
                long num_elements;

                assert(info->orig_cursor != NULL);
                prev_cursor = info->cursor;
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
                if (coda_cursor_get_num_elements(&info->cursor, &num_elements) != 0)
                {
                    return -1;
                }
                info->cursor = prev_cursor;
                *value = num_elements;
            }
            break;
        case expr_bit_size:
            {
                coda_cursor prev_cursor;
                int use_fast_size_expression;

                assert(info->orig_cursor != NULL);
                prev_cursor = info->cursor;
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
                use_fast_size_expression = coda_get_option_use_fast_size_expressions();
                coda_set_option_use_fast_size_expressions(0);
                if (coda_cursor_get_bit_size(&info->cursor, value) != 0)
                {
                    coda_set_option_use_fast_size_expressions(use_fast_size_expression);
                    return -1;
                }
                coda_set_option_use_fast_size_expressions(use_fast_size_expression);
                info->cursor = prev_cursor;
            }
            break;
        case expr_byte_size:
            {
                coda_cursor prev_cursor;
                int use_fast_size_expression;

                assert(info->orig_cursor != NULL);
                prev_cursor = info->cursor;
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
                use_fast_size_expression = coda_get_option_use_fast_size_expressions();
                coda_set_option_use_fast_size_expressions(0);
                if (coda_cursor_get_byte_size(&info->cursor, value) != 0)
                {
                    coda_set_option_use_fast_size_expressions(use_fast_size_expression);
                    return -1;
                }
                coda_set_option_use_fast_size_expressions(use_fast_size_expression);
                info->cursor = prev_cursor;
            }
            break;
        case expr_bit_offset:
            {
                coda_cursor prev_cursor;

                assert(info->orig_cursor != NULL);
                prev_cursor = info->cursor;
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
                if (coda_cursor_get_file_bit_offset(&info->cursor, value) != 0)
                {
                    return -1;
                }
                info->cursor = prev_cursor;
            }
            break;
        case expr_byte_offset:
            {
                coda_cursor prev_cursor;

                assert(info->orig_cursor != NULL);
                prev_cursor = info->cursor;
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
                if (coda_cursor_get_file_byte_offset(&info->cursor, value) != 0)
                {
                    return -1;
                }
                info->cursor = prev_cursor;
            }
            break;
        case expr_file_size:
            assert(info->orig_cursor != NULL);
            if (coda_get_product_file_size(info->orig_cursor->product, value) != 0)
            {
                return -1;
            }
            break;
        case expr_product_version:
            {
                int version;

                assert(info->orig_cursor != NULL);
                if (coda_get_product_version(info->orig_cursor->product, &version) != 0)
                {
                    return -1;
                }
                *value = version;
            }
            break;
        case expr_index:
            {
                coda_cursor prev_cursor;
                long index;

                assert(info->orig_cursor != NULL);
                prev_cursor = info->cursor;
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
                if (coda_cursor_get_index(&info->cursor, &index) != 0)
                {
                    return -1;
                }
                info->cursor = prev_cursor;
                *value = index;
            }
            break;
        case expr_index_var:
            *value = info->index[opexpr->identifier[0] - 'i'];
            break;
        case expr_variable_index:
            {
                long size;
                long i;

                assert(info->orig_cursor != NULL);
                if (info->variable_name != NULL)
                {
                    coda_set_error(CODA_ERROR_EXPRESSION, "cannot perform search within search for product variables");
                    return -1;
                }

                if (coda_product_variable_get_size(info->orig_cursor->product, opexpr->identifier, &size) != 0)
                {
                    return -1;
                }
                info->variable_name = opexpr->identifier;
                for (i = 0; i < size; i++)
                {
                    int condition;

                    info->variable_index = i;
                    if (eval_boolean(info, opexpr->operand[0], &condition) != 0)
                    {
                        return -1;
                    }
                    if (condition)
                    {
                        *value = i;
                        info->variable_name = NULL;
                        return 0;
                    }
                }
                *value = -1;
                info->variable_name = NULL;
            }
            break;
        case expr_variable_value:
            {
                int64_t *varptr;
                int64_t index = 0;

                assert(info->orig_cursor != NULL);
                if (opexpr->operand[0] != NULL)
                {
                    if (info->variable_name != NULL && strcmp(opexpr->identifier, info->variable_name) == 0)
                    {
                        coda_set_error(CODA_ERROR_EXPRESSION,
                                       "cannot use index on product variable '%s' when performing a search",
                                       opexpr->identifier);
                        return -1;
                    }
                    if (eval_integer(info, opexpr->operand[0], &index) != 0)
                    {
                        return -1;
                    }
                }
                else if (info->variable_name != NULL && strcmp(info->variable_name, opexpr->identifier) == 0)
                {
                    index = info->variable_index;
                }
                if (coda_product_variable_get_pointer(info->orig_cursor->product, opexpr->identifier, (long)index,
                                                      &varptr) != 0)
                {
                    return -1;
                }
                *value = *varptr;
            }
            break;
        case expr_at:
            {
                coda_cursor prev_cursor;

                assert(info->orig_cursor != NULL);
                prev_cursor = info->cursor;
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
                if (eval_integer(info, opexpr->operand[1], value) != 0)
                {
                    return -1;
                }
                info->cursor = prev_cursor;
            }
            break;
        case expr_with:
            {
                int64_t prev_index;
                int index_id = opexpr->identifier[0] - 'i';

                prev_index = info->index[index_id];
                if (eval_integer(info, opexpr->operand[0], &info->index[index_id]) != 0)
                {
                    return -1;
                }
                if (eval_integer(info, opexpr->operand[1], value) != 0)
                {
                    return -1;
                }
                info->index[index_id] = prev_index;
            }
            break;
        default:
            assert(0);
            exit(1);
    }

    return 0;
}

/* string values (if not equal to NULL) are always one byte longer than 'length' to allow for a terminating zero */
static int eval_string(eval_info *info, const coda_expression *expr, long *offset, long *length, char **value)
{
    const coda_expression_operation *opexpr;

    if (expr->tag == expr_constant_string || expr->tag == expr_constant_rawstring)
    {
        *offset = 0;
        *length = ((coda_expression_string_constant *)expr)->length;
        if (*length > 0)
        {
            *value = malloc(*length + 1);
            if (*value == NULL)
            {
                coda_set_error(CODA_ERROR_OUT_OF_MEMORY, "out of memory (could not duplicate string) (%s:%u)", __FILE__,
                               __LINE__);
                return -1;
            }
            memcpy(*value, ((coda_expression_string_constant *)expr)->value, *length);
        }
        else
        {
            *value = NULL;
        }
        return 0;
    }

    opexpr = (const coda_expression_operation *)expr;
    switch (opexpr->tag)
    {
        case expr_string:
            if (opexpr->operand[0]->result_type == coda_expression_node)
            {
                coda_cursor prev_cursor;

                assert(info->orig_cursor != NULL);
                prev_cursor = info->cursor;
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
                *offset = 0;
                if (coda_cursor_get_string_length(&info->cursor, length) != 0)
                {
                    return -1;
                }
                if (opexpr->operand[1] != NULL)
                {
                    int64_t maxlength;

                    if (eval_integer(info, opexpr->operand[1], &maxlength) != 0)
                    {
                        return -1;
                    }
                    if (*length > maxlength)
                    {
                        *length = (long)maxlength;
                    }
                }
                if (*length > 0)
                {
                    *value = malloc(*length + 1);       /* add room for zero termination */
                    if (*value == NULL)
                    {
                        coda_set_error(CODA_ERROR_OUT_OF_MEMORY, "out of memory (could not allocate %ld bytes) (%s:%u)",
                                       *length, __FILE__, __LINE__);
                        return -1;
                    }
                    if (coda_cursor_read_string(&info->cursor, *value, *length + 1) != 0)
                    {
                        free(*value);
                        return -1;
                    }
                }
                else
                {
                    *value = NULL;
                }
                info->cursor = prev_cursor;
            }
            else
            {
                int64_t intvalue;
                char s[21];

                if (eval_integer(info, opexpr->operand[0], &intvalue) != 0)
                {
                    return -1;
                }
                coda_str64(intvalue, s);
                *value = strdup(s);
                if (*value == NULL)
                {
                    coda_set_error(CODA_ERROR_OUT_OF_MEMORY, "out of memory (could not duplicate string) (%s:%u)",
                                   __FILE__, __LINE__);
                    return -1;
                }
                *offset = 0;
                *length = (long)strlen(s);
            }
            break;
        case expr_bytes:
            {
                coda_cursor prev_cursor;
                int64_t byte_offset = 0;
                int64_t num_bytes;
                int64_t num_bits = -1;

                assert(info->orig_cursor != NULL);
                prev_cursor = info->cursor;
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
                if (opexpr->operand[2] != NULL)
                {
                    if (eval_integer(info, opexpr->operand[1], &byte_offset) != 0)
                    {
                        return -1;
                    }
                    if (eval_integer(info, opexpr->operand[2], &num_bytes) != 0)
                    {
                        return -1;
                    }
                    if (num_bytes > 0)
                    {
                        num_bits = num_bytes << 3;
                    }
                }
                else if (opexpr->operand[1] != NULL)
                {
                    if (eval_integer(info, opexpr->operand[1], &num_bytes) != 0)
                    {
                        return -1;
                    }
                    if (num_bytes > 0)
                    {
                        num_bits = num_bytes << 3;
                    }
                }
                else
                {
                    if (coda_cursor_get_bit_size(&info->cursor, &num_bits) != 0)
                    {
                        return -1;
                    }
                }
                if (num_bits < 0)
                {
                    coda_set_error(CODA_ERROR_EXPRESSION, "negative byte size in bytes expression");
                    return -1;
                }
                num_bytes = (num_bits >> 3) + (num_bits & 0x7 ? 1 : 0);
                *offset = 0;
                *length = (long)num_bytes;
                if (num_bytes > 0)
                {
                    *value = malloc((long)num_bytes + 1);       /* add room for zero termination at a later time */
                    if (*value == NULL)
                    {
                        coda_set_error(CODA_ERROR_OUT_OF_MEMORY, "out of memory (could not allocate %ld bytes) (%s:%u)",
                                       (long)num_bytes, __FILE__, __LINE__);
                        return -1;
                    }
                    if (coda_cursor_read_bits(&info->cursor, (uint8_t *)*value, byte_offset * 8, num_bits) != 0)
                    {
                        free(*value);
                        return -1;
                    }
                }
                else
                {
                    *value = NULL;
                }
                info->cursor = prev_cursor;
            }
            break;
        case expr_add:
            {
                long off_a, off_b;
                long len_a, len_b;
                char *a;
                char *b;

                if (eval_string(info, opexpr->operand[0], &off_a, &len_a, &a) != 0)
                {
                    return -1;
                }
                if (eval_string(info, opexpr->operand[1], &off_b, &len_b, &b) != 0)
                {
                    free(a);
                    return -1;
                }
                *offset = 0;
                *length = len_a + len_b;
                if (*length > 0)
                {
                    *value = malloc(*length + 1);       /* add room for zero termination at a later time */
                    if (*value == NULL)
                    {
                        coda_set_error(CODA_ERROR_OUT_OF_MEMORY, "out of memory (could not allocate %ld bytes) (%s:%u)",
                                       *length + 1, __FILE__, __LINE__);
                        return -1;
                    }
                    if (len_a > 0)
                    {
                        memcpy(*value, &a[off_a], len_a);
                    }
                    if (len_b > 0)
                    {
                        memcpy(&(*value)[len_a], &b[off_b], len_b);
                    }
                }
                else
                {
                    *value = NULL;
                }
                if (len_a > 0)
                {
                    free(a);
                }
                if (len_b > 0)
                {
                    free(b);
                }
            }
            break;
        case expr_min:
            {
                long off_a, off_b;
                long len_a, len_b;
                char *a;
                char *b;

                if (eval_string(info, opexpr->operand[0], &off_a, &len_a, &a) != 0)
                {
                    return -1;
                }
                if (eval_string(info, opexpr->operand[1], &off_b, &len_b, &b) != 0)
                {
                    free(a);
                    return -1;
                }
                if (compare_strings(off_a, len_a, a, off_b, len_b, b) <= 0)
                {
                    *offset = off_a;
                    *length = len_a;
                    *value = a;
                    if (len_b > 0)
                    {
                        free(b);
                    }
                }
                else
                {
                    *offset = off_b;
                    *length = len_b;
                    *value = b;
                    if (len_a > 0)
                    {
                        free(a);
                    }
                }
            }
            break;
        case expr_max:
            {
                long off_a, off_b;
                long len_a, len_b;
                char *a;
                char *b;

                if (eval_string(info, opexpr->operand[0], &off_a, &len_a, &a) != 0)
                {
                    return -1;
                }
                if (eval_string(info, opexpr->operand[1], &off_b, &len_b, &b) != 0)
                {
                    free(a);
                    return -1;
                }
                if (compare_strings(off_a, len_a, a, off_b, len_b, b) >= 0)
                {
                    *offset = off_a;
                    *length = len_a;
                    *value = a;
                    if (len_b > 0)
                    {
                        free(b);
                    }
                }
                else
                {
                    *offset = off_b;
                    *length = len_b;
                    *value = b;
                    if (len_a > 0)
                    {
                        free(a);
                    }
                }
            }
            break;
        case expr_substr:
            {
                int64_t new_offset;
                int64_t new_length;

                if (eval_integer(info, opexpr->operand[0], &new_offset) != 0)
                {
                    return -1;
                }
                if (new_offset < 0)
                {
                    coda_set_error(CODA_ERROR_EXPRESSION, "negative offset in substr expression");
                    return -1;
                }
                if (eval_integer(info, opexpr->operand[1], &new_length) != 0)
                {
                    return -1;
                }
                if (new_length == 0)
                {
                    *offset = 0;
                    *length = 0;
                    *value = NULL;
                    return 0;
                }
                if (new_length < 0)
                {
                    coda_set_error(CODA_ERROR_EXPRESSION, "negative length in substr expression");
                    return -1;
                }
                if (eval_string(info, opexpr->operand[2], offset, length, value) != 0)
                {
                    return -1;
                }
                if (*length == 0)
                {
                    return 0;
                }
                if (new_offset >= *length)
                {
                    *offset = 0;
                    *length = 0;
                    free(*value);
                    *value = NULL;
                    return 0;
                }
                *offset += (long)new_offset;
                *length -= (long)new_offset;
                if (new_length < *length)
                {
                    *length = (long)new_length;
                }
            }
            break;
        case expr_ltrim:
            {
                if (eval_string(info, opexpr->operand[0], offset, length, value) != 0)
                {
                    return -1;
                }
                while (*length > 0 && iswhitespace((*value)[*offset]))
                {
                    (*length)--;
                    (*offset)++;
                }
            }
            break;
        case expr_rtrim:
            {
                if (eval_string(info, opexpr->operand[0], offset, length, value) != 0)
                {
                    return -1;
                }
                while (*length > 0 && iswhitespace((*value)[*offset + *length - 1]))
                {
                    (*length)--;
                }
            }
            break;
        case expr_trim:
            {
                if (eval_string(info, opexpr->operand[0], offset, length, value) != 0)
                {
                    return -1;
                }
                while (*length > 0 && iswhitespace((*value)[*offset]))
                {
                    (*length)--;
                    (*offset)++;
                }
                while (*length > 0 && iswhitespace((*value)[*offset + *length - 1]))
                {
                    (*length)--;
                }
            }
            break;
        case expr_array_add:
            {
                coda_cursor prev_cursor;
                long num_elements;

                assert(info->orig_cursor != NULL);
                prev_cursor = info->cursor;
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
                if (coda_cursor_get_num_elements(&info->cursor, &num_elements) != 0)
                {
                    return -1;
                }
                *offset = 0;
                *length = 0;
                *value = NULL;
                if (num_elements > 0)
                {
                    long i;

                    if (coda_cursor_goto_first_array_element(&info->cursor) != 0)
                    {
                        return -1;
                    }
                    for (i = 0; i < num_elements; i++)
                    {
                        long el_offset;
                        long el_length;
                        char *el_value;

                        if (eval_string(info, opexpr->operand[1], &el_offset, &el_length, &el_value) != 0)
                        {
                            return -1;
                        }
                        if (el_length > 0)
                        {
                            char *new_value;

                            /* add room for zero termination at a later time */
                            new_value = realloc(*value, *length + el_length + 1);
                            if (new_value == NULL)
                            {
                                coda_set_error(CODA_ERROR_OUT_OF_MEMORY,
                                               "out of memory (could not allocate %ld bytes) (%s:%u)",
                                               *length + el_length, __FILE__, __LINE__);
                                free(el_value);
                                return -1;
                            }
                            memcpy(&new_value[*length], &el_value[el_offset], el_length);
                            free(el_value);
                            *length += el_length;
                            *value = new_value;
                        }
                        if (i < num_elements - 1)
                        {
                            if (coda_cursor_goto_next_array_element(&info->cursor) != 0)
                            {
                                return -1;
                            }
                        }
                    }
                }
                info->cursor = prev_cursor;
            }
            break;
        case expr_array_min:
            {
                coda_cursor prev_cursor;
                long num_elements;

                assert(info->orig_cursor != NULL);
                prev_cursor = info->cursor;
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
                if (coda_cursor_get_num_elements(&info->cursor, &num_elements) != 0)
                {
                    return -1;
                }
                *offset = 0;
                *length = 0;
                *value = NULL;
                if (num_elements > 0)
                {
                    long i;

                    if (coda_cursor_goto_first_array_element(&info->cursor) != 0)
                    {
                        return -1;
                    }
                    for (i = 0; i < num_elements; i++)
                    {
                        long el_offset;
                        long el_length;
                        char *el_value;

                        if (eval_string(info, opexpr->operand[1], &el_offset, &el_length, &el_value) != 0)
                        {
                            return -1;
                        }
                        if (compare_strings(el_offset, el_length, el_value, *offset, *length, *value) < 0)
                        {
                            if (*length > 0)
                            {
                                free(value);
                            }
                            *offset = el_offset;
                            *length = el_length;
                            *value = el_value;
                        }
                        else if (el_length > 0)
                        {
                            free(el_value);
                        }
                        if (i < num_elements - 1)
                        {
                            if (coda_cursor_goto_next_array_element(&info->cursor) != 0)
                            {
                                return -1;
                            }
                        }
                    }
                }
                info->cursor = prev_cursor;
            }
            break;
        case expr_array_max:
            {
                coda_cursor prev_cursor;
                long num_elements;

                assert(info->orig_cursor != NULL);
                prev_cursor = info->cursor;
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
                if (coda_cursor_get_num_elements(&info->cursor, &num_elements) != 0)
                {
                    return -1;
                }
                *offset = 0;
                *length = 0;
                *value = NULL;
                if (num_elements > 0)
                {
                    long i;

                    if (coda_cursor_goto_first_array_element(&info->cursor) != 0)
                    {
                        return -1;
                    }
                    for (i = 0; i < num_elements; i++)
                    {
                        long el_offset;
                        long el_length;
                        char *el_value;

                        if (eval_string(info, opexpr->operand[1], &el_offset, &el_length, &el_value) != 0)
                        {
                            return -1;
                        }
                        if (compare_strings(el_offset, el_length, el_value, *offset, *length, *value) > 0)
                        {
                            if (*length > 0)
                            {
                                free(*value);
                            }
                            *offset = el_offset;
                            *length = el_length;
                            *value = el_value;
                        }
                        else if (el_length > 0)
                        {
                            free(el_value);
                        }
                        if (i < num_elements - 1)
                        {
                            if (coda_cursor_goto_next_array_element(&info->cursor) != 0)
                            {
                                return -1;
                            }
                        }
                    }
                }
                info->cursor = prev_cursor;
            }
            break;
        case expr_if:
            {
                int condition;

                if (eval_boolean(info, opexpr->operand[0], &condition) != 0)
                {
                    return -1;
                }
                if (condition)
                {
                    if (eval_string(info, opexpr->operand[1], offset, length, value) != 0)
                    {
                        return -1;
                    }
                }
                else
                {
                    if (eval_string(info, opexpr->operand[2], offset, length, value) != 0)
                    {
                        return -1;
                    }
                }
            }
            break;
        case expr_filename:
            {
                const char *filepath;
                const char *filename;

                assert(info->orig_cursor != NULL);
                if (coda_get_product_filename(info->orig_cursor->product, &filepath) != 0)
                {
                    return -1;
                }
                filename = filepath;
                while (*filepath != '\0')
                {
                    if (*filepath == '/' || *filepath == '\\')
                    {
                        filename = &filepath[1];
                    }
                    filepath++;
                }
                *offset = 0;
                *length = (long)strlen(filename);
                *value = malloc(*length + 1);   /* add room for zero termination at a later time */
                if (*value == NULL)
                {
                    coda_set_error(CODA_ERROR_OUT_OF_MEMORY, "out of memory (could not allocate %ld bytes) (%s:%u)",
                                   *length, __FILE__, __LINE__);
                    return -1;
                }
                memcpy(*value, filename, *length);
            }
            break;
        case expr_regex:
            {
                int ovector[(REGEX_MAX_NUM_SUBSTRING + 1) * 3];
                const char *error;
                int erroffset;
                long matchstring_offset;
                long matchstring_length;
                char *matchstring;
                long pattern_offset;
                long pattern_length;
                char *pattern = NULL;
                pcre *re;
                int index = 0;
                int rc;

                if (eval_string(info, opexpr->operand[0], &pattern_offset, &pattern_length, &pattern) != 0)
                {
                    return -1;
                }
                if (eval_string(info, opexpr->operand[1], &matchstring_offset, &matchstring_length, &matchstring) != 0)
                {
                    if (pattern != NULL)
                    {
                        free(pattern);
                    }
                    return -1;
                }

                if (pattern_length > 0)
                {
                    pattern[pattern_offset + pattern_length] = '\0';    /* add terminating zero */
                    re = pcre_compile(&pattern[pattern_offset], PCRE_DOTALL | PCRE_DOLLAR_ENDONLY, &error, &erroffset,
                                      NULL);
                }
                else
                {
                    re = pcre_compile("", PCRE_DOTALL | PCRE_DOLLAR_ENDONLY, &error, &erroffset, NULL);
                }
                if (pattern != NULL)
                {
                    free(pattern);
                }
                if (re == NULL)
                {
                    coda_set_error(CODA_ERROR_EXPRESSION,
                                   "invalid format for regex pattern ('%s' at position %d)", error, erroffset);
                    if (matchstring != NULL)
                    {
                        free(matchstring);
                    }
                    return -1;
                }

                /* determine substring index of substring that we need to return */
                if (opexpr->operand[2]->result_type == coda_expression_integer)
                {
                    int64_t intvalue;

                    /* get subexpression by index */
                    if (eval_integer(info, opexpr->operand[2], &intvalue) != 0)
                    {
                        pcre_free(re);
                        return -1;
                    }
                    index = (int)intvalue;
                }
                else
                {
                    long substrname_offset;
                    long substrname_length;
                    char *substrname;

                    /* get subexpression by name */
                    if (eval_string(info, opexpr->operand[2], &substrname_offset, &substrname_length, &substrname) != 0)
                    {
                        pcre_free(re);
                        return -1;
                    }
                    if (length == 0)
                    {
                        coda_set_error(CODA_ERROR_EXPRESSION,
                                       "invalid substring name parameter for regex (empty string)");
                        if (substrname != NULL)
                        {
                            free(substrname);
                        }
                        pcre_free(re);
                        return -1;
                    }
                    index = pcre_get_stringnumber(re, substrname);
                    if (index < 0)
                    {
                        coda_set_error(CODA_ERROR_EXPRESSION,
                                       "invalid substring name parameter for regex (substring name not in pattern)");
                        free(substrname);
                        pcre_free(re);
                        return -1;
                    }
                    free(substrname);
                }

                if (matchstring == NULL)
                {
                    /* pcre_exec does not except NULL for an empty matchstring */
                    matchstring = strdup("");
                    if (matchstring == NULL)
                    {
                        coda_set_error(CODA_ERROR_OUT_OF_MEMORY, "out of memory (could not duplicate string) (%s:%u)",
                                       __FILE__, __LINE__);
                        pcre_free(re);
                        return -1;
                    }
                }

                rc = pcre_exec(re, NULL, &matchstring[matchstring_offset], matchstring_length, 0, 0, ovector,
                               (REGEX_MAX_NUM_SUBSTRING + 1) * 3);
                pcre_free(re);
                if (rc < 0 && rc != PCRE_ERROR_NOMATCH)
                {
                    coda_set_error(CODA_ERROR_EXPRESSION, "could not evaluate regex pattern (error code %d)", rc);
                    free(matchstring);
                    return -1;
                }
                if (rc == 0)
                {
                    coda_set_error(CODA_ERROR_EXPRESSION, "regex pattern contains too many subexpressions");
                    free(matchstring);
                    return -1;
                }
                if (index >= rc)
                {
                    /* there was no match for this subexpression -> return empty string */
                    *offset = 0;
                    *length = 0;
                    *value = NULL;
                    free(matchstring);
                }
                else
                {
                    *offset = ovector[2 * index];
                    *length = ovector[2 * index + 1] - ovector[2 * index];
                    *value = matchstring;
                }
            }
            break;
        case expr_product_class:
            {
                const char *product_class;

                assert(info->orig_cursor != NULL);
                if (coda_get_product_class(info->orig_cursor->product, &product_class) != 0)
                {
                    return -1;
                }
                *offset = 0;
                *length = 0;
                if (product_class != NULL)
                {
                    *length = (long)strlen(product_class);
                    *value = malloc(*length + 1);       /* add room for zero termination at the end */
                    if (*value == NULL)
                    {
                        coda_set_error(CODA_ERROR_OUT_OF_MEMORY, "out of memory (could not allocate %ld bytes) (%s:%u)",
                                       *length, __FILE__, __LINE__);
                        return -1;
                    }
                    memcpy(*value, product_class, *length);
                }
            }
            break;
        case expr_product_format:
            {
                coda_format format;
                const char *product_format;

                assert(info->orig_cursor != NULL);
                if (coda_get_product_format(info->orig_cursor->product, &format) != 0)
                {
                    return -1;
                }
                product_format = coda_type_get_format_name(format);
                *offset = 0;
                *length = 0;
                if (product_format != NULL)
                {
                    *length = (long)strlen(product_format);
                    *value = malloc(*length + 1);       /* add room for zero termination at the end */
                    if (*value == NULL)
                    {
                        coda_set_error(CODA_ERROR_OUT_OF_MEMORY, "out of memory (could not allocate %ld bytes) (%s:%u)",
                                       *length, __FILE__, __LINE__);
                        return -1;
                    }
                    memcpy(*value, product_format, *length);
                }
            }
            break;
        case expr_product_type:
            {
                const char *product_type;

                assert(info->orig_cursor != NULL);
                if (coda_get_product_type(info->orig_cursor->product, &product_type) != 0)
                {
                    return -1;
                }
                *offset = 0;
                *length = 0;
                if (product_type != NULL)
                {
                    *length = (long)strlen(product_type);
                    *value = malloc(*length + 1);       /* add room for zero termination at the end */
                    if (*value == NULL)
                    {
                        coda_set_error(CODA_ERROR_OUT_OF_MEMORY, "out of memory (could not allocate %ld bytes) (%s:%u)",
                                       *length + 1, __FILE__, __LINE__);
                        return -1;
                    }
                    memcpy(*value, product_type, *length);
                }
            }
            break;
        case expr_strtime:
            {
                double timevalue;
                long off_format;
                long len_format;
                char *format;

                if (eval_float(info, opexpr->operand[0], &timevalue) != 0)
                {
                    return -1;
                }
                if (opexpr->operand[1] != NULL)
                {
                    if (eval_string(info, opexpr->operand[1], &off_format, &len_format, &format) != 0)
                    {
                        return -1;
                    }
                    if (len_format < 0)
                    {
                        coda_set_error(CODA_ERROR_EXPRESSION, "negative length for time format");
                        return -1;
                    }
                    if (len_format == 0)
                    {
                        coda_set_error(CODA_ERROR_EXPRESSION, "empty time format");
                        return -1;
                    }
                    format[off_format + len_format] = '\0';     /* add terminating zero */
                }
                else
                {
                    format = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS";
                    len_format = (long)strlen(format);
                    off_format = 0;
                }
                *value = malloc(len_format + 1);        /* add room for zero termination at the end */
                if (*value == NULL)
                {
                    if (opexpr->operand[1] != NULL)
                    {
                        free(format);
                    }
                    coda_set_error(CODA_ERROR_OUT_OF_MEMORY, "out of memory (could not allocate %ld bytes) (%s:%u)",
                                   *length + 1, __FILE__, __LINE__);
                    return -1;
                }
                if (coda_time_double_to_string(timevalue, &format[off_format], *value) != 0)
                {
                    if (opexpr->operand[1] != NULL)
                    {
                        free(format);
                    }
                    return -1;
                }
                *offset = 0;
                *length = (long)strlen(*value);
                if (opexpr->operand[1] != NULL)
                {
                    free(format);
                }
            }
            break;
        case expr_at:
            {
                coda_cursor prev_cursor;

                assert(info->orig_cursor != NULL);
                prev_cursor = info->cursor;
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
                if (eval_string(info, opexpr->operand[1], offset, length, value) != 0)
                {
                    return -1;
                }
                info->cursor = prev_cursor;
            }
            break;
        case expr_with:
            {
                int64_t prev_index;
                int index_id = opexpr->identifier[0] - 'i';

                prev_index = info->index[index_id];
                if (eval_integer(info, opexpr->operand[0], &info->index[index_id]) != 0)
                {
                    return -1;
                }
                if (eval_string(info, opexpr->operand[1], offset, length, value) != 0)
                {
                    return -1;
                }
                info->index[index_id] = prev_index;
            }
            break;
        default:
            assert(0);
            exit(1);
    }
    return 0;
}

static int eval_void(eval_info *info, const coda_expression *expr)
{
    const coda_expression_operation *opexpr;

    opexpr = (const coda_expression_operation *)expr;
    switch (opexpr->tag)
    {
        case expr_for:
            {
                int64_t prev_index;
                int64_t from;
                int64_t to;
                int64_t step = 1;
                int index_id = opexpr->identifier[0] - 'i';

                prev_index = info->index[index_id];
                if (eval_integer(info, opexpr->operand[0], &from) != 0)
                {
                    return -1;
                }
                if (eval_integer(info, opexpr->operand[1], &to) != 0)
                {
                    return -1;
                }
                if (opexpr->operand[2] != NULL)
                {
                    if (eval_integer(info, opexpr->operand[2], &step) != 0)
                    {
                        return -1;
                    }
                    if (step == 0)
                    {
                        coda_set_error(CODA_ERROR_EXPRESSION, "step is 0 in for loop in expression");
                        return -1;
                    }
                }
                if (step > 0)
                {
                    for (info->index[index_id] = from; info->index[index_id] <= to; info->index[index_id] += step)
                    {
                        if (eval_void(info, opexpr->operand[3]) != 0)
                        {
                            return -1;
                        }
                    }
                }
                else
                {
                    for (info->index[index_id] = from; info->index[index_id] >= to; info->index[index_id] += step)
                    {
                        if (eval_void(info, opexpr->operand[3]) != 0)
                        {
                            return -1;
                        }
                    }
                }
                info->index[index_id] = prev_index;
            }
            break;
        case expr_goto:
            if (eval_cursor(info, opexpr->operand[0]) != 0)
            {
                return -1;
            }
            break;
        case expr_sequence:
            if (eval_void(info, opexpr->operand[0]) != 0)
            {
                return -1;
            }
            if (eval_void(info, opexpr->operand[1]) != 0)
            {
                return -1;
            }
            break;
        case expr_variable_set:
            {
                int64_t *varptr;
                int64_t index = 0;
                int64_t value;

                assert(info->orig_cursor != NULL);
                if (opexpr->operand[0] != NULL)
                {
                    if (eval_integer(info, opexpr->operand[0], &index) != 0)
                    {
                        return -1;
                    }
                }
                if (eval_integer(info, opexpr->operand[1], &value) != 0)
                {
                    return -1;
                }
                if (coda_product_variable_get_pointer
                    (info->orig_cursor->product, opexpr->identifier, (long)index, &varptr) != 0)
                {
                    return -1;
                }
                *varptr = value;
            }
            break;
        default:
            assert(0);
            exit(1);
    }

    return 0;
}

static int eval_cursor(eval_info *info, const coda_expression *expr)
{
    const coda_expression_operation *opexpr;

    assert(info->orig_cursor != NULL);
    opexpr = (const coda_expression_operation *)expr;
    switch (opexpr->tag)
    {
        case expr_goto_here:
            /* do nothing */
            break;
        case expr_goto_begin:
            info->cursor = *info->orig_cursor;
            break;
        case expr_goto_root:
            if (coda_cursor_set_product(&info->cursor, info->orig_cursor->product) != 0)
            {
                return -1;
            }
            break;
        case expr_goto_field:
            {
                coda_type_class type_class;

                if (opexpr->operand[0] != NULL)
                {
                    if (eval_cursor(info, opexpr->operand[0]) != 0)
                    {
                        return -1;
                    }
                }
                if (coda_cursor_get_type_class(&info->cursor, &type_class) != 0)
                {
                    return -1;
                }
                if (type_class == coda_special_class)
                {
                    /* for special types we use the base type for traversing records */
                    if (coda_cursor_use_base_type_of_special_type(&info->cursor) != 0)
                    {
                        return -1;
                    }
                }
                if (opexpr->identifier != NULL)
                {
                    if (coda_cursor_goto_record_field_by_name(&info->cursor, opexpr->identifier) != 0)
                    {
                        return -1;
                    }
                }
                else
                {
                    int64_t index;

                    if (eval_integer(info, opexpr->operand[1], &index) != 0)
                    {
                        return -1;
                    }
                    if (coda_cursor_goto_record_field_by_index(&info->cursor, (long)index) != 0)
                    {
                        return -1;
                    }
                }
            }
            break;
        case expr_goto_array_element:
            {
                int64_t index;

                if (opexpr->operand[0] != NULL)
                {
                    if (eval_cursor(info, opexpr->operand[0]) != 0)
                    {
                        return -1;
                    }
                }
                else
                {
                    if (coda_cursor_set_product(&info->cursor, info->orig_cursor->product) != 0)
                    {
                        return -1;
                    }
                }
                if (eval_integer(info, opexpr->operand[1], &index) != 0)
                {
                    return -1;
                }
                if (!coda_option_perform_boundary_checks)
                {
                    long num_elements;
                    coda_type_class type_class;

                    /* if boundary checking is disabled globally, we still want to perform boundary checks on
                     * expressions since these can also go wrong when files are corrupted. Therefore, if the
                     * coda_cursor_goto_array_element_by_index() does not perform such a check itself, we perform a
                     * boundary check here.
                     */

                    if (coda_cursor_get_type_class(&info->cursor, &type_class) != 0)
                    {
                        return -1;
                    }
                    if (type_class != coda_array_class)
                    {
                        coda_set_error(CODA_ERROR_INVALID_TYPE,
                                       "cursor does not refer to an array (current type is %s)",
                                       coda_type_get_class_name(type_class));
                        return -1;
                    }

                    if (coda_cursor_get_num_elements(&info->cursor, &num_elements) != 0)
                    {
                        return -1;
                    }
                    if (index < 0 || index >= num_elements)
                    {
                        coda_set_error(CODA_ERROR_ARRAY_OUT_OF_BOUNDS, "array index (%ld) exceeds array range [0:%ld)",
                                       (long)index, num_elements);
                        return -1;
                    }
                }
                if (coda_cursor_goto_array_element_by_index(&info->cursor, (long)index) != 0)
                {
                    return -1;
                }
            }
            break;
        case expr_goto_parent:
            if (opexpr->operand[0] != NULL)
            {
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
            }
            if (coda_cursor_goto_parent(&info->cursor) != 0)
            {
                return -1;
            }
            break;
        case expr_goto_attribute:
            if (opexpr->operand[0] != NULL)
            {
                if (eval_cursor(info, opexpr->operand[0]) != 0)
                {
                    return -1;
                }
            }
            if (coda_cursor_goto_attributes(&info->cursor) != 0)
            {
                return -1;
            }
            if (coda_cursor_goto_record_field_by_name(&info->cursor, opexpr->identifier) != 0)
            {
                return -1;
            }
            break;
        case expr_asciiline:
            if (info->orig_cursor->product->format != coda_format_ascii)
            {
                coda_set_error(CODA_ERROR_EXPRESSION, "'asciiline' not allowed for %s files",
                               coda_type_get_format_name(info->orig_cursor->product->format));
                return -1;
            }
            if (coda_ascii_cursor_set_asciilines(&info->cursor, info->orig_cursor->product) != 0)
            {
                return -1;
            }
            break;
        default:
            assert(0);
            exit(1);
    }

    return 0;
}

int coda_expression_eval_void(const coda_expression *expr, const coda_cursor *cursor)
{
    eval_info info;

    if (expr->result_type != coda_expression_void)
    {
        coda_set_error(CODA_ERROR_INVALID_ARGUMENT, "expression is not a 'void' expression");
        return -1;
    }
    if (cursor == NULL && !expr->is_constant)
    {
        coda_set_error(CODA_ERROR_INVALID_ARGUMENT, "cursor argument may not be NULL if expression is not constant");
        return -1;
    }

    init_eval_info(&info, cursor);
    return eval_void(&info, expr);
}

static void print_escaped_string(const char *str, int length, int (*print) (const char *, ...), int xml, int html)
{
    int i = 0;

    if (length == 0 || str == NULL)
    {
        return;
    }

    if (length < 0)
    {
        length = (int)strlen(str);
    }

    while (i < length)
    {
        switch (str[i])
        {
            case '\033':       /* windows does not recognize '\e' */
                print("\\e");
                break;
            case '\a':
                print("\\a");
                break;
            case '\b':
                print("\\b");
                break;
            case '\f':
                print("\\f");
                break;
            case '\n':
                print("\\n");
                break;
            case '\r':
                print("\\r");
                break;
            case '\t':
                print("\\t");
                break;
            case '\v':
                print("\\v");
                break;
            case '\\':
                print("\\\\");
                break;
            case '"':
                print(xml ? "\\&quot;" : "\\\"");
                break;
            case '<':
                print((xml || html) ? "&lt;" : "<");
                break;
            case '>':
                print((xml || html) ? "&gt;" : ">");
                break;
            case '&':
                print((xml || html) ? "&amp;" : "&");
                break;
            case ' ':
                print((xml || html) ? "&nbsp;" : " ");
                break;
            default:
                if (!isprint(str[i]))
                {
                    print("\\%03o", (int)(unsigned char)str[i]);
                }
                else
                {
                    print("%c", str[i]);
                }
                break;
        }
        i++;
    }
}

/* precedence
 1: unary minus, not
 2: pow
 3: mul, div, mod
 4: add, sub
 5: lt, le, gt, ge
 6: eq, ne
 7: and
 8: or
 9: logical_and
 10: logical_or
 15: <start>
 */
static int print_expression(const coda_expression *expr, int (*print) (const char *, ...), int xml, int html,
                            int precedence)
{
    assert(expr != NULL);

    switch (expr->tag)
    {
        case expr_abs:
            print(html ? "<b>abs</b>(" : "abs(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(")");
            break;
        case expr_add:
            if (precedence < 4)
            {
                print("(");
            }
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 4);
            print(" + ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 4);
            if (precedence < 4)
            {
                print(")");
            }
            break;
        case expr_array_add:
            print(html ? "<b>add</b>(" : "add(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(", ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 15);
            print(")");
            break;
        case expr_array_max:
            print(html ? "<b>max</b>(" : "max(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(", ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 15);
            print(")");
            break;
        case expr_array_min:
            print(html ? "<b>min</b>(" : "min(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(", ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 15);
            print(")");
            break;
        case expr_array_all:
            print(html ? "<b>all</b>(" : "all(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(", ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 15);
            print(")");
            break;
        case expr_and:
            if (precedence < 7)
            {
                print("(");
            }
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 7);
            print((html || xml) ? " &amp; " : " & ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 7);
            if (precedence < 7)
            {
                print(")");
            }
            break;
        case expr_ceil:
            print(html ? "<b>ceil</b>(" : "ceil(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(")");
            break;
        case expr_array_count:
            print(html ? "<b>count</b>(" : "count(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(", ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 15);
            print(")");
            break;
        case expr_array_exists:
            print(html ? "<b>exists</b>(" : "exists(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(", ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 15);
            print(")");
            break;
        case expr_array_index:
            print(html ? "<b>index</b>(" : "index(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(", ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 15);
            print(")");
            break;
        case expr_asciiline:
            print(html ? "<b>asciiline</b>" : "asciiline");
            break;
        case expr_bit_offset:
            print(html ? "<b>bitoffset</b>(" : "bitoffset(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(")");
            break;
        case expr_bit_size:
            print(html ? "<b>bitsize</b>(" : "bitsize(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(")");
            break;
        case expr_byte_offset:
            print(html ? "<b>byteoffset</b>(" : "byteoffset(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(")");
            break;
        case expr_byte_size:
            print(html ? "<b>bytesize</b>(" : "bytesize(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(")");
            break;
        case expr_bytes:
            print(html ? "<b>bytes</b>(" : "bytes(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            if (((coda_expression_operation *)expr)->operand[1] != NULL)
            {
                print(",");
                print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 15);
            }
            if (((coda_expression_operation *)expr)->operand[2] != NULL)
            {
                print(",");
                print_expression(((coda_expression_operation *)expr)->operand[2], print, xml, html, 15);
            }
            print(")");
            break;
        case expr_constant_boolean:
            if (((coda_expression_bool_constant *)expr)->value)
            {
                print(html ? "<b>true</b>" : "true");
            }
            else
            {
                print(html ? "<b>false</b>" : "false");
            }
            break;
        case expr_constant_float:
            {
                char s[24];

                coda_strfl(((coda_expression_float_constant *)expr)->value, s);
                print("%s", s);
            }
            break;
        case expr_constant_integer:
            {
                char s[21];

                coda_str64(((coda_expression_integer_constant *)expr)->value, s);
                print("%s", s);
            }
            break;
        case expr_constant_rawstring:
        case expr_constant_string:
            print(xml ? "&quot;" : "\"");
            print_escaped_string(((coda_expression_string_constant *)expr)->value,
                                 ((coda_expression_string_constant *)expr)->length, print, xml, html);
            print(xml ? "&quot;" : "\"");
            break;
        case expr_dim:
            print(html ? "<b>dim</b>(" : "dim(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(",");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 15);
            print(")");
            break;
        case expr_divide:
            if (precedence < 3)
            {
                print("(");
            }
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 3);
            print(" / ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 3);
            if (precedence < 3)
            {
                print(")");
            }
            break;
        case expr_equal:
            if (precedence < 6)
            {
                print("(");
            }
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 6);
            print(" == ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 6);
            if (precedence < 6)
            {
                print(")");
            }
            break;
        case expr_exists:
            print(html ? "<b>exists</b>(" : "exists(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(")");
            break;
        case expr_file_size:
            print(html ? "<b>filesize</b>()" : "filesize()");
            break;
        case expr_filename:
            print(html ? "<b>filename</b>()" : "filename()");
            break;
        case expr_float:
            print(html ? "<b>float</b>(" : "float(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(")");
            break;
        case expr_floor:
            print(html ? "<b>floor</b>(" : "floor(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(")");
            break;
        case expr_for:
            print(html ? "<b>for</b> <i>%s</i> = " : "for %s = ", ((coda_expression_operation *)expr)->identifier);
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(html ? " <b>to</b> " : " to ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 15);
            if (((coda_expression_operation *)expr)->operand[2] != NULL)
            {
                print(html ? " <b>step</b> " : " step ");
                print_expression(((coda_expression_operation *)expr)->operand[2], print, xml, html, 15);
            }
            print(html ? " <b>do</b><br />" : " do ");
            print_expression(((coda_expression_operation *)expr)->operand[3], print, xml, html, 15);
            break;
        case expr_goto_array_element:
            if (((coda_expression_operation *)expr)->operand[0] != NULL)
            {
                print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            }
            print("[");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 15);
            print("]");
            break;
        case expr_goto_attribute:
            if (((coda_expression_operation *)expr)->operand[0] != NULL)
            {
                print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            }
            print("@%s", ((coda_expression_operation *)expr)->identifier);
            break;
        case expr_goto_begin:
            print(":");
            break;
        case expr_goto_field:
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            if (((coda_expression_operation *)expr)->operand[0]->tag != expr_goto_root)
            {
                print("/");
            }
            print("%s", ((coda_expression_operation *)expr)->identifier);
            break;
        case expr_goto_here:
            print(".");
            break;
        case expr_goto_parent:
            if (((coda_expression_operation *)expr)->operand[0] != NULL)
            {
                print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
                print("/");
            }
            print("..");
            break;
        case expr_goto_root:
            print("/");
            break;
        case expr_goto:
            print(html ? "<b>goto</b>(" : "goto(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(")");
            break;
        case expr_greater_equal:
            if (precedence < 5)
            {
                print("(");
            }
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 5);
            print((html || xml) ? " &gt;= " : " >= ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 5);
            if (precedence < 5)
            {
                print(")");
            }
            break;
        case expr_greater:
            if (precedence < 5)
            {
                print("(");
            }
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 5);
            print((html || xml) ? " &gt; " : " > ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 5);
            if (precedence < 5)
            {
                print(")");
            }
            break;
        case expr_if:
            print("if(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(", ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 15);
            print(", ");
            print_expression(((coda_expression_operation *)expr)->operand[2], print, xml, html, 15);
            print(")");
            break;
        case expr_index:
            print(html ? "<b>index</b>(" : "index(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(")");
            break;
        case expr_index_var:
            print(html ? "<i>%s</i>" : "%s", ((coda_expression_operation *)expr)->identifier);
            break;
        case expr_integer:
            print(html ? "<b>int</b>(" : "int(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(")");
            break;
        case expr_isinf:
            print(html ? "<b>isinf</b>(" : "isinf(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(")");
            break;
        case expr_ismininf:
            print(html ? "<b>ismininf</b>(" : "ismininf(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(")");
            break;
        case expr_isnan:
            print(html ? "<b>isnan</b>(" : "isnan(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(")");
            break;
        case expr_isplusinf:
            print(html ? "<b>isplusinf</b>(" : "isplusinf(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(")");
            break;
        case expr_length:
            print(html ? "<b>length</b>(" : "length(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(")");
            break;
        case expr_less_equal:
            if (precedence < 5)
            {
                print("(");
            }
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 5);
            print((html || xml) ? " &lt;= " : " <= ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 5);
            if (precedence < 5)
            {
                print(")");
            }
            break;
        case expr_less:
            if (precedence < 5)
            {
                print("(");
            }
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 5);
            print((html || xml) ? " &lt; " : " < ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 5);
            if (precedence < 5)
            {
                print(")");
            }
            break;
        case expr_logical_and:
            if (precedence < 9)
            {
                print("(");
            }
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 9);
            print(html ? " <b>and</b> " : " and ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 9);
            if (precedence < 9)
            {
                print(")");
            }
            break;
        case expr_logical_or:
            if (precedence < 10)
            {
                print("(");
            }
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 10);
            print(html ? " <b>or</b> " : " or ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 10);
            if (precedence < 10)
            {
                print(")");
            }
            break;
        case expr_ltrim:
            print(html ? "<b>ltrim</b>(" : "ltrim(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(")");
            break;
        case expr_max:
            print(html ? "<b>max</b>(" : "max(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(", ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 15);
            print(")");
            break;
        case expr_min:
            print(html ? "<b>min</b>(" : "min(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(", ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 15);
            print(")");
            break;
        case expr_modulo:
            if (precedence < 3)
            {
                print("(");
            }
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 3);
            print(" %% ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 3);
            if (precedence < 3)
            {
                print(")");
            }
            break;
        case expr_multiply:
            if (precedence < 3)
            {
                print("(");
            }
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 3);
            print(" * ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 3);
            if (precedence < 3)
            {
                print(")");
            }
            break;
        case expr_neg:
            print("-");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 1);
            break;
        case expr_not_equal:
            if (precedence < 6)
            {
                print("(");
            }
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 6);
            print(" != ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 6);
            if (precedence < 6)
            {
                print(")");
            }
            break;
        case expr_not:
            print("!");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 1);
            break;
        case expr_num_dims:
            print(html ? "<b>numdims</b>(" : "numdims(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(")");
            break;
        case expr_num_elements:
            print(html ? "<b>numelements</b>(" : "numelements(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(")");
            break;
        case expr_or:
            if (precedence < 7)
            {
                print("(");
            }
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 7);
            print(" | ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 7);
            if (precedence < 7)
            {
                print(")");
            }
            break;
        case expr_power:
            if (precedence < 2)
            {
                print("(");
            }
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 2);
            print(" ^ ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 2);
            if (precedence < 2)
            {
                print(")");
            }
            break;
        case expr_product_class:
            print(html ? "<b>productclass</b>()" : "productclass()");
            break;
        case expr_product_format:
            print(html ? "<b>productformat</b>()" : "productformat()");
            break;
        case expr_product_type:
            print(html ? "<b>producttype</b>()" : "producttype()");
            break;
        case expr_product_version:
            print(html ? "<b>productversion</b>()" : "productversion()");
            break;
        case expr_regex:
            print(html ? "<b>regex</b>(" : "regex(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(", ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 15);
            if (((coda_expression_operation *)expr)->operand[2] != NULL)
            {
                print(", ");
                print_expression(((coda_expression_operation *)expr)->operand[2], print, xml, html, 15);
            }
            print(")");
            break;
        case expr_round:
            print(html ? "<b>round</b>(" : "round(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(")");
            break;
        case expr_rtrim:
            print(html ? "<b>rtrim</b>(" : "rtrim(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(")");
            break;
        case expr_sequence:
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(html ? ";<br />" : "; ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 15);
            break;
        case expr_string:
            print(html ? "<b>str</b>(" : "str(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            if (((coda_expression_operation *)expr)->operand[1] != NULL)
            {
                print(", ");
                print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 15);
            }
            print(")");
            break;
        case expr_strtime:
            print(html ? "<b>strtime</b>(" : "strtime(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            if (((coda_expression_operation *)expr)->operand[1] != NULL)
            {
                print(", ");
                print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 15);
            }
            print(")");
            break;
        case expr_substr:
            print(html ? "<b>substr</b>(" : "substr(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(", ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 15);
            print(", ");
            print_expression(((coda_expression_operation *)expr)->operand[2], print, xml, html, 15);
            print(")");
            break;
        case expr_subtract:
            if (precedence < 4)
            {
                print("(");
            }
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 4);
            print(" - ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 4);
            if (precedence < 4)
            {
                print(")");
            }
            break;
        case expr_time:
            print(html ? "<b>time</b>(" : "time(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(", ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 15);
            print(")");
            break;
        case expr_trim:
            print(html ? "<b>trim</b>(" : "trim(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(")");
            break;
        case expr_unbound_array_index:
            print(html ? "<b>unboundindex</b>(" : "unboundindex(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(", ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 15);
            print(")");
            break;
        case expr_variable_exists:
            print(html ? "<b>exists</b>(<i>$%s</i>, " : "exists($%s, ",
                  ((coda_expression_operation *)expr)->identifier);
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(")");
            break;
        case expr_variable_index:
            print(html ? "<b>index</b>(<i>$%s</i>, " : "index($%s, ", ((coda_expression_operation *)expr)->identifier);
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(")");
            break;
        case expr_variable_set:
            print(html ? "<i>$%s</i>" : "$%s", ((coda_expression_operation *)expr)->identifier);
            if (((coda_expression_operation *)expr)->operand[0] != NULL)
            {
                print("[");
                print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
                print("]");
            }
            print(" = ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 15);
            break;
        case expr_variable_value:
            print("$%s", ((coda_expression_operation *)expr)->identifier);
            if (((coda_expression_operation *)expr)->operand[0] != NULL)
            {
                print("[");
                print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
                print("]");
            }
            break;
        case expr_at:
            print(html ? "<b>at</b>(" : "at(");
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(", ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 15);
            print(")");
            break;
        case expr_with:
            print(html ? "<b>with</b>(<i>%s</i> = " : "with(%s = ", ((coda_expression_operation *)expr)->identifier);
            print_expression(((coda_expression_operation *)expr)->operand[0], print, xml, html, 15);
            print(", ");
            print_expression(((coda_expression_operation *)expr)->operand[1], print, xml, html, 15);
            print(")");
            break;
    }

    return 0;
}

int coda_expression_print_html(const coda_expression *expr, int (*print) (const char *, ...))
{
    return print_expression(expr, print, 1, 1, 15);
}

int coda_expression_print_xml(const coda_expression *expr, int (*print) (const char *, ...))
{
    return print_expression(expr, print, 1, 0, 15);
}

/** \addtogroup coda_expression
 * @{
 */

/** Write the full expression using a printf compatible function.
 * This function will produce a string representation of the expression itself
 * (it won't evaluate the expression, nor write its result).
 * The \a print function parameter should be a function that resembles printf().
 * The printed string representation is something that can be passed to #coda_expression_from_string()
 * again to reproduce the expression object.
 * \param expr Pointer to the expression object.
 * \param print Reference to a printf compatible function.
 * \return
 *   \arg \c  0, Succes.
 *   \arg \c -1, Error occurred (check #coda_errno).
 */
LIBCODA_API int coda_expression_print(const coda_expression *expr, int (*print) (const char *, ...))
{
    return print_expression(expr, print, 0, 0, 15);
}

/** \fn int coda_expression_from_string(const char *exprstring, coda_expression **expr)
 * Create a new CODA expression object by parsing a string containing a CODA expression.
 * The string should contain a valid CODA expression.
 * The returned expression object should be cleaned up using coda_expression_delete() after it has been used.
 * \param exprstring A string containing the string representation of the CODA expression 
 * \param expr Pointer to the variable where the expression object will be stored.
 * \return
 *   \arg \c 0, Success.
 *   \arg \c -1, Error occurred (check #coda_errno).
 */


/** Returns the name of an expression type.
 * \param type CODA expression type
 * \return if the type is known a string containing the name of the type, otherwise the string "unknown".
 */
LIBCODA_API const char *coda_expression_get_type_name(coda_expression_type type)
{
    switch (type)
    {
        case coda_expression_boolean:
            return "boolean";
        case coda_expression_integer:
            return "integer";
        case coda_expression_float:
            return "float";
        case coda_expression_string:
            return "string";
        case coda_expression_node:
            return "node";
        case coda_expression_void:
            return "void";
    }

    return "unknown";
}


/** Delete the CODA expression object.
 * \param expr A CODA expression object
 */
LIBCODA_API void coda_expression_delete(coda_expression *expr)
{
    switch (expr->tag)
    {
        case expr_constant_boolean:
        case expr_constant_float:
        case expr_constant_integer:
            break;
        case expr_constant_rawstring:
        case expr_constant_string:
            if (((coda_expression_string_constant *)expr)->value != NULL)
            {
                free(((coda_expression_string_constant *)expr)->value);
            }
            break;
        default:
            {
                coda_expression_operation *opexpr;
                int i;

                opexpr = (coda_expression_operation *)expr;
                if (opexpr->identifier != NULL)
                {
                    free(opexpr->identifier);
                }
                for (i = 0; i < 4; i++)
                {
                    if (opexpr->operand[i] != NULL)
                    {
                        coda_expression_delete(opexpr->operand[i]);
                    }
                }
            }
            break;
    }
    free(expr);
}

/** Return whether an expression is constant or not.
 * An expression is constant if it does not depend on the contents of a product and if the expression evaluation
 * function can be called with cursor=NULL.
 * \param expr A CODA expression object
 * \return
 *   \arg \c 0, Expression will depend on the contents of a product.
 *   \arg \c 1, Expression is constant.
 *   \arg \c -1, Error occurred (check #coda_errno).
 */
LIBCODA_API int coda_expression_is_constant(const coda_expression *expr)
{
    if (expr == NULL)
    {
        coda_set_error(CODA_ERROR_INVALID_ARGUMENT, "invalid expression argument (%s:%u)", __FILE__, __LINE__);
        return -1;
    }
    return expr->is_constant;
}

/** Determines whether two expressions are equal or not
 * This function will return 1 if both expressions are equal, and 0 if they are not.
 *
 * The comparison will not be on the evaluated result of the expressions but on the operators and operands of the
 * expressions themselves.  For two expressions to be equal, all operands to an operation need to be equal and
 * operands need to be provided in the same order.
 * So the expression '1!=2' will not be considered equal to the expression '2!=1'.
 *
 * Providing two NULL pointers will be considered as a case of equal expressions.
 *
 * \param expr1 A CODA expression object
 * \param expr2 A CODA expression object
 * \return
 *   \arg \c 0, expr1 and expr2 consist of the exact same sequence of operators and operands.
 *   \arg \c 1, expr1 and expr2 differ in terms of operators and operands.
 */
LIBCODA_API int coda_expression_is_equal(const coda_expression *expr1, const coda_expression *expr2)
{
    if (expr1 == NULL)
    {
        return expr2 == NULL;
    }
    if (expr2 == NULL)
    {
        return 0;
    }

    if (expr1->tag != expr2->tag)
    {
        return 0;
    }

    switch (expr1->tag)
    {
        case expr_constant_boolean:
            return ((coda_expression_bool_constant *)expr1)->value == ((coda_expression_bool_constant *)expr2)->value;
        case expr_constant_float:
            return ((coda_expression_float_constant *)expr1)->value == ((coda_expression_float_constant *)expr2)->value;
        case expr_constant_integer:
            return ((coda_expression_integer_constant *)expr1)->value ==
                ((coda_expression_integer_constant *)expr2)->value;
        case expr_constant_rawstring:
        case expr_constant_string:
            if (((coda_expression_string_constant *)expr1)->length !=
                ((coda_expression_string_constant *)expr2)->length)
            {
                return 0;
            }
            return memcmp(((coda_expression_string_constant *)expr1)->value,
                          ((coda_expression_string_constant *)expr2)->value,
                          ((coda_expression_string_constant *)expr1)->length) == 0;
        default:
            {
                coda_expression_operation *op1 = (coda_expression_operation *)expr1;
                coda_expression_operation *op2 = (coda_expression_operation *)expr2;
                int i;

                if (op1->identifier != NULL)
                {
                    if (op2->identifier == NULL)
                    {
                        return 0;
                    }
                    if (strcmp(op1->identifier, op2->identifier) != 0)
                    {
                        return 0;
                    }
                }
                else if (op2->identifier != NULL)
                {
                    return 0;
                }
                for (i = 0; i < 4; i++)
                {
                    if (!coda_expression_is_equal(op1->operand[i], op2->operand[i]))
                    {
                        return 0;
                    }
                }
            }
    }

    return 1;
}

/** Retrieve the result type of a CODA expression.
 * \param expr A CODA expression object
 * \param type Pointer to the variable where the result type of the CODA expression will be stored.
 * \return
 *   \arg \c 0, Success.
 *   \arg \c -1, Error occurred (check #coda_errno).
 */
LIBCODA_API int coda_expression_get_type(const coda_expression *expr, coda_expression_type *type)
{
    if (expr == NULL)
    {
        coda_set_error(CODA_ERROR_INVALID_ARGUMENT, "invalid expression argument (%s:%u)", __FILE__, __LINE__);
        return -1;
    }
    *type = expr->result_type;
    return 0;
}

/** Evaluate a boolean expression.
 * The expression object should be a coda_expression_bool expression.
 * The function will evaluate the expression at the given cursor position and return the resulting boolean value (which
 * will be 0 for False and 1 for True).
 * \param expr A boolean expression object
 * \param cursor Cursor pointing to a location in the product where the boolean expression should be evaluated (can be
 * NULL for constant expressions).
 * \param value Pointer to the variable where the resulting boolean value will be stored.
 * \return
 *   \arg \c 0, Success.
 *   \arg \c -1, Error occurred (check #coda_errno).
 */
LIBCODA_API int coda_expression_eval_bool(const coda_expression *expr, const coda_cursor *cursor, int *value)
{
    eval_info info;

    if (expr->result_type != coda_expression_boolean)
    {
        coda_set_error(CODA_ERROR_INVALID_ARGUMENT, "expression is not a 'boolean' expression");
        return -1;
    }
    if (cursor == NULL && !expr->is_constant)
    {
        coda_set_error(CODA_ERROR_INVALID_ARGUMENT, "cursor argument may not be NULL if expression is not constant");
        return -1;
    }

    init_eval_info(&info, cursor);
    if (eval_boolean(&info, expr, value) != 0)
    {
        if (cursor != NULL && coda_cursor_compare(cursor, &info.cursor) != 0)
        {
            coda_cursor_add_to_error_message(&info.cursor);
        }
        return -1;
    }

    return 0;
}

/** Evaluate an integer expression.
 * The expression object should be a coda_expression_integer expression.
 * The function will evaluate the expression at the given cursor position and return the resulting integer value.
 * \param expr An integer expression object
 * \param cursor Cursor pointing to a location in the product where the integer expression should be evaluated (can be
 * NULL for constant expressions).
 * \param value Pointer to the variable where the resulting integer value will be stored.
 * \return
 *   \arg \c 0, Success.
 *   \arg \c -1, Error occurred (check #coda_errno).
 */
LIBCODA_API int coda_expression_eval_integer(const coda_expression *expr, const coda_cursor *cursor, int64_t *value)
{
    eval_info info;

    if (expr->result_type != coda_expression_integer)
    {
        coda_set_error(CODA_ERROR_INVALID_ARGUMENT, "expression is not an 'integer' expression");
        return -1;
    }
    if (cursor == NULL && !expr->is_constant)
    {
        coda_set_error(CODA_ERROR_INVALID_ARGUMENT, "cursor argument may not be NULL if expression is not constant");
        return -1;
    }

    init_eval_info(&info, cursor);
    if (eval_integer(&info, expr, value) != 0)
    {
        if (cursor != NULL && coda_cursor_compare(cursor, &info.cursor) != 0)
        {
            coda_cursor_add_to_error_message(&info.cursor);
        }
        return -1;
    }

    return 0;
}

/** Evaluate a floating point expression.
 * The function will evaluate the expression at the given cursor position and return the resulting floating point value.
 * The expression object should be a coda_expression_float expression.
 * \param expr A floating point expression object
 * \param cursor Cursor pointing to a location in the product where the floating point expression should be evaluated
 * (can be NULL for constant expressions).
 * \param value Pointer to the variable where the resulting floating point value will be stored.
 * \return
 *   \arg \c 0, Success.
 *   \arg \c -1, Error occurred (check #coda_errno).
 */
LIBCODA_API int coda_expression_eval_float(const coda_expression *expr, const coda_cursor *cursor, double *value)
{
    eval_info info;

    if (expr->result_type != coda_expression_float)
    {
        coda_set_error(CODA_ERROR_INVALID_ARGUMENT, "expression is not a 'double' expression");
        return -1;
    }
    if (cursor == NULL && !expr->is_constant)
    {
        coda_set_error(CODA_ERROR_INVALID_ARGUMENT, "cursor argument may not be NULL if expression is not constant");
        return -1;
    }

    init_eval_info(&info, cursor);
    if (eval_float(&info, expr, value) != 0)
    {
        if (cursor != NULL && coda_cursor_compare(cursor, &info.cursor) != 0)
        {
            coda_cursor_add_to_error_message(&info.cursor);
        }
        return -1;
    }

    return 0;
}

/** Evaluate a string expression.
 * The function will evaluate the expression at the given cursor position (if provided) and return the resulting string
 * and length.
 * If length is 0 then no string will be returned and \c value will be set to NULL.
 * If a string is returned then it will be zero terminated. However, in the case where the string itself also contains
 * zero characters, strlen() can not be used and the \c length parameter will give the actual string length of \c value.
 * The expression object should be a coda_expression_string expression.
 * \note The caller of this function is responsible for freeing the memory of the result that is stored in \c value.
 * It is recommended to do this with coda_free().
 * \param expr A string expression object
 * \param cursor Cursor pointing to a location in the product where the string expression should be evaluated (can be
 * NULL for constant expressions).
 * \param value Pointer to the variable where the result string will be stored (will be NULL if length == 0).
 * \param length Pointer to the variable where the length of the result string will be stored.
 * \return
 *   \arg \c 0, Success.
 *   \arg \c -1, Error occurred (check #coda_errno).
 */
LIBCODA_API int coda_expression_eval_string(const coda_expression *expr, const coda_cursor *cursor, char **value,
                                            long *length)
{
    eval_info info;
    long offset;

    if (expr->result_type != coda_expression_string)
    {
        coda_set_error(CODA_ERROR_INVALID_ARGUMENT, "expression is not a 'string' expression");
        return -1;
    }
    if (cursor == NULL && !expr->is_constant)
    {
        coda_set_error(CODA_ERROR_INVALID_ARGUMENT, "cursor argument may not be NULL if expression is not constant");
        return -1;
    }

    init_eval_info(&info, cursor);
    if (eval_string(&info, expr, &offset, length, value) != 0)
    {
        if (cursor != NULL && coda_cursor_compare(cursor, &info.cursor) != 0)
        {
            coda_cursor_add_to_error_message(&info.cursor);
        }
        return -1;
    }

    if (*length > 0)
    {
        if (offset != 0)
        {
            char *truncated_value;

            truncated_value = malloc(*length + 1);
            if (truncated_value == NULL)
            {
                coda_set_error(CODA_ERROR_OUT_OF_MEMORY, "out of memory (could not allocate %ld bytes) (%s:%u)",
                               *length, __FILE__, __LINE__);
                return -1;
            }
            memcpy(truncated_value, &(*value)[offset], *length);
            free(*value);
            *value = truncated_value;
        }
        (*value)[*length] = '\0';
    }
    else
    {
        if (*value != NULL)
        {
            free(*value);
        }
        *value = NULL;
    }

    return 0;
}

/** Evaluate a node expression.
 * The function will moves the cursor to a different position in a product based on the node expression.
 * The expression object should be a coda_expression_node expression.
 * \param expr A node expression object
 * \param cursor Cursor pointing to a location in the product where the node expression should be evaluated.
 * \return
 *   \arg \c 0, Success.
 *   \arg \c -1, Error occurred (check #coda_errno).
 */
LIBCODA_API int coda_expression_eval_node(const coda_expression *expr, coda_cursor *cursor)
{
    eval_info info;

    if (expr->result_type != coda_expression_node)
    {
        coda_set_error(CODA_ERROR_INVALID_ARGUMENT, "expression is not a 'node' expression");
        return -1;
    }
    if (cursor == NULL)
    {
        coda_set_error(CODA_ERROR_INVALID_ARGUMENT,
                       "cursor argument may not be NULL for evaluation of node expression");
        return -1;
    }

    init_eval_info(&info, cursor);
    if (eval_cursor(&info, expr) != 0)
    {
        if (cursor != NULL && coda_cursor_compare(cursor, &info.cursor) != 0)
        {
            coda_cursor_add_to_error_message(&info.cursor);
        }
        return -1;
    }

    *cursor = info.cursor;

    return 0;
}

/** @} */
