/*
 * This source code is part of hsc, a html-preprocessor,
 * Copyright (C) 1995-1998  Thomas Aglassinger
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */
/*
 * hsclib/eval.c
 *
 * attribute value evaluation functions
 *
 * updated: 16-Dec-1997
 * created: 11-Oct-1995
 */

#define NOEXTERN_HSCLIB_EVAL_H

#include <ctype.h>
#include <time.h>

#include "hsclib/inc_base.h"

#include "hsclib/eval.h"
#include "hsclib/input.h"
#include "hsclib/skip.h"
#include "hsclib/uri.h"

/* maximul length of an operator identifer */
#define MAX_OP_LEN 8

/* unknown operator */
#define OP_NONE 0

/* step-size for temp. string */
#define TMP_STEPSIZE 128

/*
 * equation operators
 */
#define OP_EQ_STR  "="
#define OP_NEQ_STR "<>"
#define OP_GT_STR  ">"
#define OP_LT_STR  "<"
#define OP_GTE_STR ">="
#define OP_LTE_STR "<="
#define OP_CEQ_STR "=="         /* TODO: case sensitive string comparison */
#define OP_INSIDE_STR "IN"
#define OP_CL_BRACKET_STR ")"   /* closing bracket */

#define OP_EQ   1
#define OP_NEQ  2
#define OP_GT   3
#define OP_LT   4
#define OP_GTE  5
#define OP_LTE  6
#define OP_CEQ  7
#define OP_INSIDE 8
#define OP_CL_BRACKET  10

/*
 * boolean operators
 */
#define OP_AND_STR "AND"
#define OP_NOT_STR "NOT"
#define OP_OR_STR  "OR"
#define OP_XOR_STR "XOR"

#define OP_AND 11
#define OP_NOT 12
#define OP_OR  13
#define OP_XOR 14

/*
 * string/arithmetik operators
 */
#define OP_CAT_STR "+"
#define OP_SUB_STR "-"
#define OP_MUL_STR "*"
#define OP_DIV_STR "/"
#define OP_MOD_STR "MOD"        /* modulo */

#define OP_CAT      15
#define OP_SUB      16
#define OP_MUL      17
#define OP_DIV      18
#define OP_MOD      19

typedef BYTE op_t;

/*
 * forward references
 */
STRPTR eval_expression(HSCPRC * hp, HSCATTR * dest, STRPTR endstr);

/*
 * 
 * global funcs
 *
 */

/*
 * err_op: unknown binary operator
 */
static VOID err_op(HSCPRC * hp, STRPTR opstr)
{
    hsc_message(hp, MSG_UNKN_BINOP, "unknown binary operator %q", opstr);
}

/*
 * eval_boolstr
 */
static BOOL eval_boolstr(STRPTR s)
{
    if (s[0])
        return (TRUE);
    else
        return (FALSE);
}

/* check for empty brakets (after GetTime/GetGmTime) */
static VOID check_brakets(HSCPRC * hp)
{
    if (parse_wd(hp, "("))
        parse_wd(hp, ")");
}

/*
 * gettimestr
 */
static EXPSTR *gettimestr(HSCPRC * hp, struct tm *time)
{
#define TIMEBUF_INC 20
    STRPTR timefmt = get_vartext_byname(hp->defattr, TIMEFORMAT_ATTR);
    EXPSTR *timebuf = init_estr(TIMEBUF_INC);
    BOOL strftrc = 0;           /* result of strftime() */
    size_t i;                   /* loop var */

    /* set default time format */
    if (!timefmt)
    {
        timefmt = "%d-%b-%Y, %H:%M";
    }

    while (!(hp->fatal) && !strftrc)
    {
        /* expand timebuffer */
        for (i = 0; i < TIMEBUF_INC; i++)
            app_estrch(timebuf, '.');

        D(fprintf(stderr, DHL "    timebuf: inc+%d\n", TIMEBUF_INC));

        /* output time */
        strftrc = strftime(estr2str(timebuf), estrlen(timebuf),
                           timefmt, time);
    }

    if (!strftrc)
    {
        del_estr(timebuf);
        timebuf = NULL;
    }

    return (timebuf);
}

/*
 * getfilesize
 *
 * get size of a specific file
 *
 * templates for HSC.FORMAT.FILESIZE:
 * %b   size in byte
 * %k   size in Kbyte
 * %m   size in MByte
 * %g   size in Gbyte
 * %a   size, unit computed automatically
 * %u   unit for %A (none, "K", "M" or "G")
 */
static STRPTR getfilesize(HSCPRC * hp, EXPSTR * dest, STRPTR uri)
{
    STRPTR filesizestr = NULL;
    FILE *file = NULL;
    LONG filesize = 0;          /* filesize in byte */
    LONG filesize_k = 0;        /* filesize in Kbyte */
    LONG filesize_m = 0;        /* filesize in Mbyte */
    LONG filesize_g = 0;        /* filesize in Gbyte */
    LONG filesize_auto = 0;     /* filesize in auto-units (%A) */
    EXPSTR *efilename = init_estr(32);
    STRPTR filename = NULL;
    STRPTR sizeunit = "";
    STRPTR s = get_vartext_byname(hp->defattr,
                                  FILESIZEFORMAT_ATTR);

    conv_hscuri2file(hp, efilename, uri);
    filename = estr2str(efilename);

    D(fprintf(stderr, DHL "  GETFILESIZE(`%s')\n", filename));
    errno = 0;
    file = fopen(filename, "rb");
    if (file)
    {
        /* retrieve size */
        fseek(file, 0L, SEEK_END);
        filesize = ftell(file);
        fclose(file);

        /* compute size in units, */
        filesize_k = (filesize + 512) >> 10;
        filesize_m = (filesize_k + 512) >> 10;
        filesize_g = (filesize_m + 512) >> 10;

        /* compute auto-size */
        if (filesize_g > 10)
        {
            filesize_auto = filesize_g;
            sizeunit = "G";
        }
        else if (filesize_m > 10)
        {
            filesize_auto = filesize_m;
            sizeunit = "M";
        }
        else if (filesize_k > 10)
        {
            filesize_auto = filesize_k;
            sizeunit = "K";
        }
        else
        {
            filesize_auto = filesize;
            sizeunit = "";
        }
    }
    else
    {
        /* file not found */
        filesize = 0;
        filesize_k = 0;
        filesize_m = 0;
        filesize_g = 0;
        filesize_auto = 0;
        sizeunit = "";
        hsc_msg_nouri(hp, filename, uri, "get filesize");
    }

    /* parse template */
    clr_estr(dest);
    if (s)
    {
        while (s[0])
        {
            if (s[0] == '%')
            {
                if (s[1])
                    s++;
                switch (s[0])
                {
                case 'b':
                    app_estr(dest, long2str(filesize));
                    break;

                case 'k':
                    app_estr(dest, long2str(filesize_k));
                    break;

                case 'm':
                    app_estr(dest, long2str(filesize_m));
                    break;

                case 'g':
                    app_estr(dest, long2str(filesize_g));
                    break;

                case 'a':
                    app_estr(dest, long2str(filesize_auto));
                    break;

                case 'u':
                    app_estr(dest, sizeunit);
                    break;

                default:
                    app_estrch(dest, '%');
                    app_estrch(dest, s[0]);
                    break;
                }
            }
            else
                app_estrch(dest, s[0]);
            s++;
        }
    }
    else
    {
        D(panic("no template for filesize-format"));
    }

    /* set filesize-str */
    filesizestr = estr2str(dest);
    D(fprintf(stderr, DHL "  =`%s'\n", filesizestr));

    del_estr(efilename);

    return (filesizestr);
}

/*
 * check_attrname
 *
 * check string for legal attribute name
 */
BOOL check_attrname(HSCPRC * hp, STRPTR name)
{
    BOOL ok = FALSE;

    if (hsc_normch(name[0]))
    {
        ok = TRUE;
    }
    else
    {
        hsc_message(hp, MSG_ILLG_ATTRNAME,
                    "illegal attribute identifier %q", name);
    }

    return (ok);
}

/*
 * eval_attrname
 *
 * read next word and check it for a legal
 * attribute identifier
 */
static STRPTR eval_attrname(HSCPRC * hp)
{
    STRPTR result = NULL;
    STRPTR nw = infgetw(hp->inpf);

    if (nw)
    {
        if (check_attrname(hp, nw))
        {
            result = nw;
        }
    }
    else
    {
        hsc_msg_eof(hp, "attribute identifier expected");
    }

    return (result);
}

/*
 * quotestr
 *
 * return readable string for quote-kind
 */
STRPTR quotestr(int quote)
{
    STRPTR s = "UNKNOWN";

    if (quote == DOUBLE_QUOTE)
    {
        s = "[double]";
    }
    else if (quote == SINGLE_QUOTE)
    {
        s = "[single]";
    }
    else if (quote == BACK_QUOTE)
    {
        s = "[back]";
    }
    else if (quote == VQ_NO_QUOTE)
    {
        s = "[none]";
    }
    else
    {
        STRARR tmp[60];
        sprintf(tmp, "unknown quote-kind: $%02x #%03d", quote, quote);
        panic(tmp);
    }

    return (s);
}

/*
 * choose_quote
 *
 * choose quote to be used for attr, depending on
 * hp->quotemode and quotes used inside the value
 */
VOID choose_quote(HSCPRC * hp, HSCATTR * attr)
{
    int quote = attr->quote;
    LONG qm = hp->quotemode;    /* lazy.. */
    BOOL single_quote = FALSE;
    BOOL double_quote = FALSE;
    BOOL nasty_char = FALSE;

    STRPTR value = get_vartext(attr);

    D(fprintf(stderr, DHL "  choosing quote\n"));

    if (attr->vartype == VT_BOOL)
    {
        D(fprintf(stderr, DHL "    forget it, it's just a boolean attrib\n"));
    }
    else if (value[0])
    {
        /* scan attribute value for quotes */
        while (value[0])
        {
            if (value[0] == SINGLE_QUOTE)
            {
                D(fprintf(stderr, DHL "    single quote detected\n"));
                single_quote = TRUE;
                nasty_char = TRUE;
            }
            else if (value[0] == DOUBLE_QUOTE)
            {
                D(fprintf(stderr, DHL "    double quote detected\n"));
                double_quote = TRUE;
                nasty_char = TRUE;
            }
            else if ((!hsc_normch(value[0]) || (value[0] == '_'))
                     && !nasty_char)
            {
                D(fprintf(stderr, DHL "    nasty-char #%d detected\n", value[0]));
                nasty_char = TRUE;
            }

            value++;
        }
    }
    else
    {
        /* empty value */
        nasty_char = TRUE;
    }

    if (qm == QMODE_KEEP)
    {
        /* check, if quote is missing */
        if ((attr->quote == VQ_NO_QUOTE)
            && nasty_char)
        {
            hsc_message(hp, MSG_REQU_QUOTE,
                        "value for %A requires quotes", attr);
        }
    }
    else
    {
        /* choose quote */
        if (single_quote && double_quote)
        {
            /* both kind of quotes appeared in value:
             * replace double-quotes by "&quot;" */
            EXPSTR *newval = init_estr(32);     /* new attribute value */

            /* scan old value for `\"', replace them by `&quot'
             * and store new value in newval */
            value = get_vartext(attr);
            while (value[0])
            {
                if (value[0] == DOUBLE_QUOTE)
                {
                    D(fprintf(stderr, DHL "    replace by `&quot;' in value\n"));
                    /* TODO: message */
                    app_estr(newval, "&quot;");
                }
                else
                    app_estrch(newval, value[0]);
                value++;
            }

            /* update attribute value */
            set_vartext(attr, estr2str(newval));

            quote = DOUBLE_QUOTE;

            del_estr(newval);
        }
        else
        {
            if (single_quote)
            {
                D(fprintf(stderr, DHL "    double quote forced\n"));
                quote = DOUBLE_QUOTE;
            }
            else if (double_quote)
            {
                D(fprintf(stderr, DHL "    single quote forced\n"));
                quote = SINGLE_QUOTE;
            }
            else
            {
                /* no quote in value: choose quote user prefers */
                if (qm == QMODE_SINGLE)
                {
                    D(fprintf(stderr, DHL "    single quote preferd\n"));
                    quote = SINGLE_QUOTE;
                }
                else if (qm == QMODE_DOUBLE)
                {
                    D(fprintf(stderr, DHL "    double quote prefered\n"));
                    quote = DOUBLE_QUOTE;
                }
                else if (qm == QMODE_NONE)
                {
                    if (nasty_char)
                    {
                        D(fprintf(stderr, DHL "    quote needed (nasty char)\n"));
                        quote = DOUBLE_QUOTE;
                    }
                    else
                    {
                        D(fprintf(stderr, DHL "    no quote needed\n"));
                        quote = VQ_NO_QUOTE;
                    }
                }
                else
                {
                    panic("illegal quote-mode");
                }
            }
        }
        /* check, if quote has changed */
        if (attr->quote != quote)
        {
            hsc_message(hp, MSG_CHANGED_QUOTE,
                        "changed quotes for %A from %s to %s",
                        attr, quotestr(attr->quote), quotestr(quote));
        }
    }

    attr->quote = quote;
}

/*
 * try_eval_unary_op
 *
 * reads next word and tries to interpret it as an unary
 * operator; if no fitting operator exists, return NULL,
 * else immediatly process the operator and return its
 * result
 */
static STRPTR try_eval_unary_op(HSCPRC * hp, HSCATTR * dest, BOOL * err)
{
    STRPTR eval_result = NULL;
    INFILE *inpf = hp->inpf;
    STRPTR nw = eval_attrname(hp);
    HSCATTR *tmpdest = new_hscattr(PREFIX_TMPATTR "unary.operator");

    tmpdest->vartype = VT_STRING;

    *err = FALSE;
    if (nw)
    {
        if (!upstrcmp(nw, "NOT"))
        {
            /* TODO: this part looks a bit stupid... */
            STRPTR nw = infgetw(inpf);

            if (nw)
            {
                BOOL err_rec = FALSE;   /* error var for recursive call */
                STRPTR endstr = NULL;

                if (strcmp(nw, "("))
                {
                    /* try to process another unary operator */
                    inungetcw(inpf);
                    eval_result = try_eval_unary_op(hp, dest, &err_rec);
                }
                else
                    endstr = ")";

                /* if not, process another expression */
                if (!eval_result && !err_rec)
                    eval_result = eval_expression(hp, dest, endstr);
            }
            else
                hsc_msg_eof(hp, "after NOT");

            /* set result or return error */
            if (eval_result)
            {
                set_varbool(dest, !get_varbool(dest));
                eval_result = get_vartext(dest);
            }
            else
                *err = TRUE;
        }
        else if (!upstrcmp(nw, "DEFINED"))
        {
            nw = eval_attrname(hp);
            if (nw)
            {
                HSCATTR *attr = find_varname(hp->defattr, nw);

                if (attr)
                    set_varbool(dest, TRUE);
                else
                    set_varbool(dest, FALSE);
                eval_result = get_vartext(dest);
            }
        }
        else if (!upstrcmp(nw, "EXISTS"))
        {
            /* check existence of file (relative to destination dir) */
            eval_result = eval_expression(hp, tmpdest, NULL);
            if (eval_result)
            {
                FILE *file = NULL;
                EXPSTR *dest_fname = init_estr(64);

                D(fprintf(stderr, DHL "  EXISTS(`%s')\n", eval_result));

                conv_hscuri2file(hp, dest_fname, eval_result);
                file = fopen(estr2str(dest_fname), "r");
                if (file)
                {
                    fclose(file);
                    set_varbool(dest, TRUE);
                }
                else
                    set_varbool(dest, FALSE);

                del_estr(dest_fname);
                eval_result = get_vartext(dest);
            }
        }
        else if (!upstrcmp(nw, "FEXISTS"))
        {
            /* check existence of file */
            eval_result = eval_expression(hp, tmpdest, NULL);
            if (eval_result)
            {
                FILE *file = NULL;
                EXPSTR *dest_fname = init_estr(64);

                D(fprintf(stderr, DHL "  EXISTS(`%s')\n", eval_result));

                file = fopen(eval_result, "r");
                if (file)
                {
                    fclose(file);
                    set_varbool(dest, TRUE);
                }
                else
                    set_varbool(dest, FALSE);

                del_estr(dest_fname);
                eval_result = get_vartext(dest);
            }
        }
        else if (!upstrcmp(nw, "GETENV"))
        {
            /* get environment variable */
            eval_result = eval_expression(hp, dest, NULL);
            if (eval_result)
            {
                STRPTR env_value = getenv(get_vartext(dest));

                D(fprintf(stderr, DHL "  GETENV(`%s')\n", eval_result));
                if (!env_value)
                {
                    hsc_message(hp, MSG_UNKN_ENVVAR,
                                "unknown environment variable %q",
                                get_vartext(dest));

                    env_value = "";
                }
                set_vartext(dest, env_value);
                eval_result = get_vartext(dest);
                D(fprintf(stderr, DHL "  =`%s'\n", eval_result));
            }
        }
        else if (!upstrcmp(nw, "GETFILESIZE"))
        {
            /* retrieve size of a file */
            EXPSTR *filesizestr = init_estr(0);
            HSCATTR *filedestattr = new_hscattr(PREFIX_TMPATTR "get.filesize");
            STRPTR filename = NULL;

            eval_result = NULL;
            filedestattr->vartype = VT_STRING;
            filename = eval_expression(hp, filedestattr, NULL);
            if (filename)
            {
                eval_result = getfilesize(hp, filesizestr, filename);
            }
            if (eval_result)
            {
                set_vartext(dest, eval_result);
                eval_result = get_vartext(dest);
            }
            del_hscattr(filedestattr);
            del_estr(filesizestr);
        }
        else if (!upstrcmp(nw, "GETTIME"))
        {
            /* get local time */
            EXPSTR *timestr = gettimestr(hp, localtime(&(hp->start_time)));

            D(fprintf(stderr, DHL "  GETTIME\n"));
            if (timestr)
            {
                set_vartext(dest, estr2str(timestr));
                del_estr(timestr);
                eval_result = get_vartext(dest);
            }
            check_brakets(hp);
        }
        else if (!upstrcmp(nw, "GETGMTIME"))
        {
            /* get greenwich mean time */
            EXPSTR *timestr = gettimestr(hp, gmtime(&(hp->start_time)));

            D(fprintf(stderr, DHL "  GETGMTIME\n"));
            if (timestr)
            {
                set_vartext(dest, estr2str(timestr));
                del_estr(timestr);
                eval_result = get_vartext(dest);
            }
            check_brakets(hp);
        }
        else if (!upstrcmp(nw, "SET"))
        {

            nw = eval_attrname(hp);
            if (nw)
            {
                HSCATTR *attr = find_varname(hp->defattr, nw);

                if (attr)
                {
                    if (attr->vartype == VT_BOOL)
                    {
                        set_varbool(dest, get_varbool(attr));
                    }
                    else if (get_vartext(attr))
                        set_varbool(dest, TRUE);
                    else
                        set_varbool(dest, FALSE);
                    eval_result = get_vartext(dest);
                }
                else
                {
                    hsc_msg_unkn_attr_ref(hp, nw);
                    *err = TRUE;
                }
            }
        }
        else
            inungetcw(inpf);
    }

    del_hscattr(tmpdest);

    if (!nw)
        *err = TRUE;

    return (eval_result);
}

/*
 * eval_op
 *
 * evaluate binary operator string
 */
static BYTE eval_op(HSCPRC * hp)
{
    BYTE op = OP_NONE;
    BOOL op_eof = FALSE;        /* flag: end of file reached */
    INFILE *inpf = hp->inpf;
    STRPTR nw = infgetw(inpf);

    D(fprintf(stderr, DHL "  operator \"%s", nw));

    if (nw)
    {
        /* boolean operators */
        if (!upstrcmp(nw, OP_AND_STR))
        {
            op = OP_AND;
        }
        else if (!upstrcmp(nw, OP_OR_STR))
        {
            op = OP_OR;
        }
        else if (!upstrcmp(nw, OP_XOR_STR))
        {
            op = OP_XOR;
        }
        else if (!strcmp(nw, OP_CAT_STR))
        {
            /* concatenation operator */
            op = OP_CAT;
        }
        else if (!strcmp(nw, OP_SUB_STR))
        {
            /* subtraction operator */
            op = OP_SUB;
        }
        else if (!strcmp(nw, OP_MUL_STR))
        {
            /* multiplication operator */
            op = OP_MUL;
        }
        else if (!strcmp(nw, OP_DIV_STR))
        {
            /* division operator */
            op = OP_DIV;
        }
        else if (!strcmp(nw, OP_MOD_STR))
        {
            /* modulo operator */
            op = OP_MOD;
        }
        else if (!strcmp(nw, OP_INSIDE_STR))
        {
            /* substring search */
            op = OP_INSIDE;
        }
        /* closing braket */
        else if (!strcmp(nw, OP_CL_BRACKET_STR))
        {
            op = OP_CL_BRACKET;
        }
        else if (strenum(nw, "<|=|>", '|', STEN_CASE))
        {
            /* comparison operators */
            STRARR opstr[3];
            int ch;

            /* determine whole comparison operator:
             * take first word, and check for next
             * single character, if it is one of
             * "<", "=" or ">", too. if so, append
             * it to the string that tells the
             * operator.
             */
            strcpy(opstr, nw);
            ch = infgetc(inpf);
            if (ch != EOF)
            {
                D(fprintf(stderr, "%c", (char) ch));

                if (strchr("<=>", ch))
                {
                    opstr[1] = ch;
                    opstr[2] = 0;
                }
                else
                    inungetc(ch, inpf);
            }
            else
                op_eof = TRUE;

            /* find out comparison operator */
            if (!strcmp(nw, OP_EQ_STR))
                op = OP_EQ;
            else if (!strcmp(nw, OP_NEQ_STR))
                op = OP_EQ;
            else if (!strcmp(nw, OP_GT_STR))
                op = OP_GT;
            else if (!strcmp(nw, OP_LT_STR))
                op = OP_LT;
            else if (!strcmp(nw, OP_LTE_STR))
                op = OP_LTE;
            else if (!strcmp(nw, OP_GTE_STR))
                op = OP_GTE;
            else if (!strcmp(nw, OP_CEQ_STR))
                op = OP_CEQ;
            else
                err_op(hp, opstr);
        }
        else
        {
            err_op(hp, nw);
        }
    }
    else
    {
        op_eof = TRUE;
    }

    D(fprintf(stderr, "\"\n"));

    if (op_eof)
    {
        hsc_msg_eof(hp, "operator expected");
    }

    return (op);
}

/*
 * stroptol
 *
 * convert string operand to long int; treat "" as 0,
 * any non-numeric values as 1
 *
 * this enables to use boolean vars in numeric expressions
 *
 * result: TRUE
 */
static BOOL stroptol(HSCPRC * hp, LONG * dest, STRPTR str)
{
    BOOL ok = TRUE;

    *dest = 0;

    if (str[0])
    {
        STRPTR last_char = NULL;
        *dest = strtol(str, &last_char, 0);
        if (!last_char || (last_char[0]))
        {
            *dest = 1;
        }
    }
    else
    {
        /* empty string "" counts as 0, too */
    }

    return ok;
}

/*
 * process_arithmetic_op
 */
static BOOL process_arithmetic_op(HSCPRC * hp, EXPSTR * result, BYTE op, STRPTR str1, STRPTR str2)
{
    BOOL result_set = TRUE;
    LONG int1 = 0;              /* integer value of string operands */
    LONG int2 = 0;
    LONG intr = 0;              /* integer result */

    /* convert both string operands to integers */
    result_set = stroptol(hp, &int1, str1);
    result_set &= stroptol(hp, &int2, str2);

    switch (op)
    {
    case OP_CAT:
        intr = int1 + int2;
        break;
    case OP_SUB:
        intr = int1 - int2;
        break;
    case OP_MUL:
        intr = int1 * int2;
        break;
    case OP_DIV:
        intr = int1 / int2;
        break;
    case OP_MOD:
        intr = int1 % int2;
        break;
    default:
        panic("unknown arithmetic operator");
        break;
    }

    /* set result */
    if (result_set)
    {
        STRARR buf[20];
        sprintf(buf, "%ld", intr);
        set_estr(result, buf);
    }

    return result_set;
}

static BOOL process_boolean_op(HSCPRC * hp, HSCATTR * dest, BYTE op, STRPTR str1, STRPTR str2)
{
    BOOL bool_val1 = eval_boolstr(str1);
    BOOL bool_val2 = eval_boolstr(str2);
    BOOL bool_valr = FALSE;

    switch (op)
    {
    case OP_AND:
        if (bool_val1 && bool_val2)
        {
            bool_valr = TRUE;
        }
        break;

    case OP_OR:
        if (bool_val1 || bool_val2)
        {
            bool_valr = TRUE;
        }
        break;

    case OP_XOR:
        if ((bool_val1 || bool_val2)
            && !(bool_val1 && bool_val2)
            )
        {
            bool_valr = TRUE;
        }
        break;
    default:
        panic("unknown boolean operator");
        break;
    }

    set_varbool(dest, bool_valr);

    return FALSE;
}

/*
 * process_op
 */
static VOID process_op(HSCPRC * hp, HSCATTR * dest, BYTE op, STRPTR str1, STRPTR str2)
{
    EXPSTR *result = init_estr(40);
    BOOL result_set = FALSE;

    D(fprintf(stderr, DHL "  \"%s\", \"%s\"\n", str1, str2));
    if (str2 && (op != OP_NONE))
    {
        switch (op)
        {
        case OP_CAT:

            if (dest->vartype != VT_NUM)
            {
                /* concat two expressions */
                set_estr(result, str1);
                app_estr(result, str2);
                result_set = TRUE;
            }
            else
            {
                result_set = process_arithmetic_op(hp, result, op, str1, str2);
            }

            break;

        case OP_INSIDE:

            /* sub-string search, ignore case */
            if (upstrstr(str2, str1))
            {
                set_varbool(dest, TRUE);
            }
            else
            {
                set_varbool(dest, FALSE);
            }
            break;

        case OP_AND:
        case OP_OR:
        case OP_XOR:
            result_set = process_boolean_op(hp, dest, op, str1, str2);
            break;

        case OP_SUB:
        case OP_MUL:
        case OP_DIV:
        case OP_MOD:
            result_set = process_arithmetic_op(hp, result, op, str1, str2);
            break;

        case OP_EQ:

            /* string comparison, ignore case */
            if (!upstrcmp(str1, str2))
            {
                set_varbool(dest, TRUE);
            }
            else
            {
                set_varbool(dest, FALSE);
            }
            break;

        case OP_NEQ:

            /* string comparison "<>" */
            if (upstrcmp(str1, str2))
            {
                set_varbool(dest, TRUE);
            }
            else
            {
                set_varbool(dest, FALSE);
            }
            break;

        case OP_GT:

            /* string comparison ">" */
            if (upstrcmp(str1, str2) > 0)
            {
                set_varbool(dest, TRUE);
            }
            else
            {
                set_varbool(dest, FALSE);
            }
            break;

        case OP_LT:

            /* string comparison "<" */
            if (upstrcmp(str1, str2) < 0)
            {
                set_varbool(dest, TRUE);
            }
            else
            {
                set_varbool(dest, FALSE);
            }
            break;

        case OP_GTE:

            /* string comparison ">=" */
            if (upstrcmp(str1, str2) >= 0)
            {
                set_varbool(dest, TRUE);
            }
            else
            {
                set_varbool(dest, FALSE);
            }
            break;

        case OP_LTE:

            /* string comparison "<=" */
            if (upstrcmp(str1, str2) <= 0)
            {
                set_varbool(dest, TRUE);
            }
            else
            {
                set_varbool(dest, FALSE);
            }
            break;

        case OP_CEQ:

            /* string comparison, case sensitive */
            if (!strcmp(str1, str2))
            {
                set_varbool(dest, TRUE);
            }
            else
            {
                set_varbool(dest, FALSE);
            }
            break;
        default:
            panic("empty operator");
            break;
        }
    }
    /* store result in destination attribute,
     * if this has not happened yet
     */
    if (result_set)
    {
        set_vartext(dest, estr2str(result));
    }

    /* remove temp. string for result */
    del_estr(result);
}

/*
 *-------------------------------------
 * eval_expression: evaluate expression
 *-------------------------------------
 */

/*
 * eval_string_expr
 *
 * evaluate string expression WITH enclosing quotes
 */
static STRPTR eval_string_expr(HSCPRC * hp, HSCATTR * dest)
{
    INFILE *inpf = hp->inpf;
    STRPTR eval_result = NULL;
    EXPSTR *tmpstr = init_estr(TMP_STEPSIZE);
    int quote;

    /* get quote char */
    quote = infgetc(inpf);
    if (quote != EOF)
    {
        BOOL end = FALSE;

        while (!end)
        {
            int ch = infgetc(inpf);
            if (ch == EOF)
            {
                hsc_msg_eof(hp, "reading string constant");
                eval_result = NULL;
                end = TRUE;
            }
            else if (ch != quote)
            {
                /* check for LF inside string */
                if (ch == '\n')
                    hsc_message(hp, MSG_STR_LF,
                                "linefeed found inside string");

                /* append next char to string */
                app_estrch(tmpstr, ch);
            }
            else
            {
                /* closing quote reached */
                eval_result = estr2str(tmpstr);
                end = TRUE;
            }
        }
    }
    else
        hsc_msg_eof(hp, "reading string constant");

    /* set new attribute value */
    if (eval_result)
    {
        /* set new quotes */
        dest->quote = quote;
        /* set new value */
        set_vartext(dest, eval_result);
        eval_result = get_vartext(dest);
    }
    /* remove temp. string */
    del_estr(tmpstr);

    return (eval_result);
}

/*
 * eval_string_expr_noquote
 *
 * evaluate string expression WITHOUT enclosing quotes
 */
STRPTR eval_string_expr_noquote(HSCPRC * hp, HSCATTR * dest)
{
    INFILE *inpf = hp->inpf;
    STRPTR eval_result = NULL;
    EXPSTR *tmpstr = init_estr(TMP_STEPSIZE);
    BOOL end = FALSE;

    /*
     * read next char from input file until a
     * closing quote if found.
     * if the arg had no quote, a white space
     * or a '>' is used to detect end of arg.
     * if a LF is found, view error message
     */
    while (!end)
    {

        int ch = infgetc(inpf);
        if (ch == EOF)
        {
            hsc_msg_eof(hp, "reading attribute");

            end = TRUE;
        }
        else if ((ch == '\n')
                 || (inf_isws(ch, inpf) || (ch == '>')))
        {
            /* end of arg reached */
            inungetc(ch, inpf);
            eval_result = estr2str(tmpstr);
            end = TRUE;
        }
        else
        {
            /* append next char to tmpstr */
            app_estrch(tmpstr, ch);
        }
    }

    /* set new attribute value */
    if (eval_result)
    {
        set_vartext(dest, eval_result);
        eval_result = get_vartext(dest);
    }
    /* remove temp. string */
    del_estr(tmpstr);

    return (eval_result);
}

/*
 * eval_attrref
 *
 * evaluate reference to attribute
 *
 */
static STRPTR eval_attrref(HSCPRC * hp, HSCATTR * destattr)
{
    STRPTR eval_result = NULL;
    STRPTR nw = eval_attrname(hp);

    if (nw)
    {
        HSCATTR *refvar = find_varname(hp->defattr, nw);

        if (refvar)
        {
            destattr->quote = refvar->quote;
            eval_result = refvar->text;

            /* check empty/circular reference */
            if (!eval_result)
                hsc_message(hp, MSG_EMPTY_SYMB_REF,
                            "empty reference to %A", destattr);

            /* debugging message */
            DDA(fprintf(stderr, DHL "   %s refers to (%s)\n",
                        destattr->name, refvar->name));
        }
        else
        {
            /* reference to unknown destattr */
            hsc_msg_unkn_attr_ref(hp, nw);
        }

        /* set empty value for reference to NULL */
        if ((!refvar) || (!eval_result))
        {
            /* return empty destattr */
            destattr->quote = '"';
            eval_result = "";
        }
    }

    /* set value of destination attribute */
    if (eval_result)
        set_vartext(destattr, eval_result);

    return (eval_result);
}

/*
 * check_color - check if a color constant is legal
 */
static VOID check_color(HSCPRC * hp, HSCATTR * dest, STRPTR value)
{
    BOOL ok = FALSE;
    size_t color_len = strlen("#rrggbb");

    if (value[0] == '#')
    {
        /* check RGB-value */
        if (strlen(value) == color_len)
        {
            size_t i = 1;

            ok = TRUE;
            for (; i < color_len; i++)
            {
                if (!isxdigit(value[i]))
                {
                    ok = FALSE;
                }
            }
        }
    }
    else
    {
        /* check color name */
        if (hp->color_names)
        {
            if (strenum(value, hp->color_names, '|', STEN_NOCASE))
                ok = TRUE;
        }
        else
            ok = TRUE;
    }

    if (!ok)
    {
        /* illegal color value */
        hsc_message(hp, MSG_ILLG_COLOR,
                    "illegal color value %q for %A",
                    value, dest);
    }
}

/*
 * check_integer - check if a integer constant is legal
 */
static VOID check_integer(HSCPRC * hp, HSCATTR * dest, STRPTR value)
{
    BOOL ok = FALSE;
    int i = 0;

    if ((value[0] == '+')
        || (value[0] == '-'))
    {
        i = 1;
    }

    if (strlen(value) - i)
    {
        ok = TRUE;
        while (value[i] && ok)
        {
            if (!isdigit(value[i]))
                ok = FALSE;
            else
                i++;
        }
    }

    if (!ok)
    {
        /* illegal integer value */
        hsc_message(hp, MSG_ILLG_NUM,
                    "illegal numeric value %q for %A",
                    value, dest);
    }
}

/*
 * new_cloned_attr
 *
 * create a new attribute and clone the type and flags
 * of another attribute.
 *
 * Normally the cloned attribute has the same name as the
 * initial target attribute, but in debug mode a more
 * informative name is used
 */
static HSCATTR *new_cloned_attr(STRPTR debug_name, HSCATTR *attr)
{
#if DEBUG
    STRPTR cloned_name = debug_name;
#else
    STRPTR cloned_name = attr->name;
#endif
    BYTE new_type = attr->vartype;
    HSCATTR *cloned_attr = new_hscattr(cloned_name);

    /* init cloned attribute */
    cloned_attr->vartype = attr->vartype;
    cloned_attr->varflag = attr->varflag;
    cloned_attr->quote = attr->quote;



    return cloned_attr;
}

/*
 * eval_expression
 *
 * params: dest....attribute where to store result in
 *         inpf....input file
 *         err.....flag that is set to TRUE if an error occured
 *         endstr..word that is expected at end of expession;
 *                 NULL for no special ending word
 * result: result of evaluation (IF_TRUE or FALSE)
 *
 * NOTE: if endstr==NULL, that means that eval_expression() is called
 *   immediatly after the "=" of an attribute. In this case, if no
 *   quotes are found as next char, all chars are read until the next
 *   white-space or LF occures.
 *
 */
STRPTR eval_expression(HSCPRC * hp, HSCATTR * dest, STRPTR endstr)
{
    INFILE *inpf = hp->inpf;

    /* used as destination by eval_string_exprXX() */
    EXPSTR *vararg = init_estr(TMP_STEPSIZE);

    /* create cloned attribute to hold result */
    HSCATTR *dest1 = new_cloned_attr(PREFIX_TMPATTR "1st.operand", dest);

    STRPTR exprstr = NULL;      /* return value */
    int ch;                     /* char read from input */

    /* skip white spaces */
    infskip_ws(inpf);

    /* read dest->quote char */
    ch = infgetc(inpf);
    if (!strchr(VQ_STR_QUOTE, ch))
    {
        if (ch != EOF)
        {
            dest1->quote = VQ_NO_QUOTE;
        }
        else
        {
            hsc_msg_eof(hp, "reading attribute");
        }
    }
    else
    {
        dest1->quote = ch;
    }

    if (ch == '(')
    {
        /* process braket */
        exprstr = eval_expression(hp, dest1, ")");

        /* set generic double quote */
        if (!endstr)
        {
            dest1->quote = '\"';
        }
    }
    else if (ch != EOF)
    {
        /* write current char read back to input buffer */
        inungetc(ch, inpf);

        if (dest1->quote != VQ_NO_QUOTE)
        {
            /* process string expression with quotes */
            exprstr = eval_string_expr(hp, dest1);
        }
        else if (endstr)
        {
            BOOL err = FALSE;

            /* try to process unary operator */
            exprstr = try_eval_unary_op(hp, dest1, &err);

            /* process attribute reference */
            if (!exprstr && !err)
            {
                exprstr = eval_attrref(hp, dest1);
            }
        }
        else
        {
            /* process string expression without quotes */
            exprstr = eval_string_expr_noquote(hp, dest1);
        }
    }

    if (exprstr && endstr)
    {
        BYTE op;

        /* evaluate operator */
        op = eval_op(hp);

        if (op == OP_CL_BRACKET)
        {
            DMSG("  END mark operator reached");
        }
        else if (op != OP_NONE)
        {
            /* read second operator */
            if (op != OP_NONE)
            {
                HSCATTR *dest2 = new_cloned_attr(PREFIX_TMPATTR "2nd.operand",
                                                 dest1);
                STRPTR str1 = get_vartext(dest1);
                STRPTR str2 = NULL;

                /* compute second operand */
                str2 = eval_expression(hp, dest2, endstr);

                if (str2)
                {
                    process_op(hp, dest1, op, str1, str2);
                }
                else
                {
                    exprstr = NULL;
                }

                /* remove temporary resources */
                del_hscattr((APTR) dest2);
            }
            else
            {
                exprstr = NULL;
            }

            if (exprstr)
            {
                /* store result */
                exprstr = get_vartext(dest1);
            }
        }
        else
        {
            DMSG("  NO operator");
        }
    }

    if (!endstr)
    {
        if (exprstr && !endstr)
        {
            /* choose quote (only for tag attributes) */
            if ((dest1->vartype != VT_BOOL)
                && !(dest1->varflag & (VF_MACRO | VF_KEEP_QUOTES)))
            {
                choose_quote(hp, dest1);
            }

            /* check enum type */
            if (dest1->vartype == VT_ENUM)
            {
                if (!strenum(exprstr, dest1->enumstr, '|', STEN_NOCASE))
                {
                    /* unknown enum value */
                    hsc_message(hp, MSG_ENUM_UNKN,
                                "unknown value %q for enumerator %A",
                                exprstr, dest1);
                }
            }
            else if (dest1->vartype == VT_COLOR)
            {
                /* check color value */
                check_color(hp, dest1, exprstr);
            }
            else if (dest1->vartype == VT_NUM)
            {
                /* check numeric value */
                check_integer(hp, dest1, exprstr);
            }
            else if (dest1->vartype == VT_BOOL)
            {
                /*
                 * for boolean attributes, set the name
                 * of the attribute if TRUE, or set an
                 * empty string, if FALSE
                 */
                if (eval_boolstr(exprstr))
                {
                    set_vartext(dest1, dest1->name);
                }
                else
                {
                    set_vartext(dest1, "");
                }
            }

            /*
             * checks performed only for tags,
             * but are skipped for macros
             */
            if (!(dest1->varflag & VF_MACRO))
            {
                /*
                 * parse uri (convert abs.uris, check existence)
                 */
                if (dest1->vartype == VT_URI)
                {
                    EXPSTR *dest_uri = init_estr(32);

                    parse_uri(hp, dest_uri, exprstr);
                    set_vartext(dest1, estr2str(dest_uri));

                    del_estr(dest_uri);
                }
            }
            exprstr = get_vartext(dest1);
        }
        else
        {
            /* if error occured,
             * skip until ">", unread ">"
             */
            skip_until_eot(hp, NULL);
            if (!hp->fatal)
            {
                inungetcw(inpf);
            }
        }
    }

    /* assign result to destination attribute */
    if (exprstr)
    {
        set_vartext(dest, exprstr);
        dest->quote = dest1->quote;
        exprstr = get_vartext(dest);
    }

    /* remove temporary resources */
    del_hscattr((APTR) dest1);
    del_estr(vararg);

    return (exprstr);
}

/*
 * assign_conditional_attr
 *
 * validate and find name of source attrib, if set, copy value
 * to destination attribute
 *
 * params: hp...........hsc-process
 *         dest.........detination attribute where to store value
 *         source_attr..name of source attribute
 * result: value of attribute, if it has been set, or NULL
 *         if attribute is empty or unknown or other error
 *         has occured.
 */
static STRPTR assign_conditional_attr(HSCPRC * hp, HSCATTR * dest, STRPTR source_name)
{
    STRPTR attrval = NULL;

    if (source_name)
    {
        if (check_attrname(hp, source_name))
        {
            HSCATTR *attr = find_varname(hp->defattr, source_name);

            if (attr)
            {
                attrval = get_vartext(attr);
                dest->quote = attr->quote;
            }
            else
            {
                hsc_msg_unkn_attr_ref(hp, source_name);
            }
        }
    }
    else
    {
        panic("no source attribute");
    }

    /* update attribute value and quotes */
    if (attrval)
    {
        set_vartext(dest, attrval);
        attrval = get_vartext(dest);
        choose_quote(hp, dest);
    }

    return (attrval);
}

/*
 * eval_conditional_expression
 *
 * evaluate a conditional expression like
 *   SEPP?=HUGO or SEPP?=("hu"+"go")
 * and modify destination attribute only if the source
 * attribute really exists
 *
 * params: hp...hsc-process
 *         dest..target attribute to update
 * result: new value or NULL in case of no update or error
 */
STRPTR eval_conditional_assignment(HSCPRC * hp, HSCATTR * dest)
{
    STRPTR nw = infgetw(hp->inpf);
    STRPTR attr_val = NULL;

    D(fprintf(stderr, DHL "  conditional assignment\n"));

    if (nw)
    {
        /* temp. attribute to store name of source attribute if it
         * is specified with an expression */
        HSCATTR *tmp_attr = NULL;
        STRPTR source_name = NULL;      /* name of source attribute */

        if (!strcmp(nw, "("))
        {
            /* get attribute name from expression */
            tmp_attr = new_hscattr(PREFIX_TMPATTR "conditional.assignment");
            source_name = eval_expression(hp, tmp_attr, ")");
        }
        else
        {
            /* attribute name was simply specified */
            source_name = nw;
        }

        if (source_name)
        {
            D(fprintf(stderr, DHL "    assign from %s\n", source_name));
            attr_val = assign_conditional_attr(hp, dest, source_name);
        }

        /* free resources */
        if (tmp_attr)
        {
            del_hscattr(tmp_attr);
        }

    }
    else
    {
        hsc_msg_eof(hp, "conditional attribute identifier expected");
    }

    return (attr_val);
}
