/*
 *
 * Copyright 1992 Network Computing Devices, Inc.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of Network Computing Devices may not be
 * used in advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.  Network Computing Devices makes
 * no representations about the suitability of this software for any purpose.
 * It is provided ``as is'' without express or implied warranty.
 *
 * NETWORK COMPUTING DEVICES DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
 * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS,
 * IN NO EVENT SHALL NETWORK COMPUTING DEVICES BE LIABLE FOR ANY SPECIAL,
 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 *
 * Author:  Jim Fulton
 *          Network Computing Devices, Inc.
 *
 * Simple if statement processor
 *
 * This module can be used to evaluate string representations of C language
 * if constructs.  It accepts the following grammar:
 *
 *     EXPRESSION	:=	VALUE
 * 			 |	VALUE  BINOP	EXPRESSION
 *			 |	VALUE	'?'	EXPRESSION ':'	EXPRESSION
 *
 *     VALUE		:=	'('  EXPRESSION  ')'
 * 			 |	'!'  VALUE
 * 			 |	'-'  VALUE
 * 			 |	'+'  VALUE
 *			 |	'~'  VALUE
 * 			 |	'defined'  '('  variable  ')'
 * 			 |	'defined'  variable
 *			 |	# variable '(' variable-list ')'
 * 			 |	variable
 * 			 |	number
 *
 *     BINOP		:=	'*'	|  '/'	|  '%'
 * 			 |	'+'	|  '-'
 * 			 |	'<<'	|  '>>'
 * 			 |	'<'	|  '>'	|  '<='  |  '>='
 * 			 |	'=='	|  '!='
 * 			 |	'&'	|  '^'  |  '|'
 * 			 |	'&&'	|  '||'
 *
 * The normal C order of precedence is supported.
 *
 *
 * External Entry Points:
 *
 *     ParseIfExpression		parse a string for #if
 */

#include "ifparser.h"
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>

/****************************************************************************
		   Internal Macros and Utilities for Parser
 ****************************************************************************/

#define DO(val) if (!(val)) return NULL
#define CALLFUNC(ggg,fff) (*((ggg)->funcs.fff))
#define SKIPSPACE(ccc) while (isspace(*ccc)) ccc++
#define isvarfirstletter(ccc) (isalpha(ccc) || (ccc) == '_')


static const char *
parse_variable(IfParser *g, const char *cp, const char **varp)
{
    SKIPSPACE(cp);

    if (!isvarfirstletter(*cp))
        return CALLFUNC(g, handle_error) (g, cp, "variable name");

    *varp = cp;
    /* EMPTY */
    for (cp++; isalnum(*cp) || *cp == '_'; cp++);
    return cp;
}

static const char *
parse_number(IfParser *g, const char *cp, long *valp)
{
    long base = 10;

    SKIPSPACE(cp);

    if (!isdigit(*cp))
        return CALLFUNC(g, handle_error) (g, cp, "number");

    *valp = 0;

    if (*cp == '0') {
        cp++;
        if ((*cp == 'x') || (*cp == 'X')) {
            base = 16;
            cp++;
        }
        else {
            base = 8;
        }
    }

    /* Ignore overflows and assume ASCII, what source is usually written in */
    while (1) {
        int increment = -1;

        if (base == 8) {
            if ((*cp >= '0') && (*cp <= '7'))
                increment = *cp++ - '0';
        }
        else if (base == 16) {
            if ((*cp >= '0') && (*cp <= '9'))
                increment = *cp++ - '0';
            else if ((*cp >= 'A') && (*cp <= 'F'))
                increment = *cp++ - ('A' - 10);
            else if ((*cp >= 'a') && (*cp <= 'f'))
                increment = *cp++ - ('a' - 10);
        }
        else {                  /* Decimal */
            if ((*cp >= '0') && (*cp <= '9'))
                increment = *cp++ - '0';
        }
        if (increment < 0)
            break;
        *valp = (*valp * base) + increment;
    }

    /* Skip trailing qualifiers */
    while (*cp == 'U' || *cp == 'u' || *cp == 'L' || *cp == 'l')
        cp++;
    return cp;
}

static const char *
parse_character(IfParser *g, const char *cp, long *valp)
{
    char val;

    SKIPSPACE(cp);
    if (*cp == '\\')
        switch (cp[1]) {
        case 'n':
            val = '\n';
            break;
        case 't':
            val = '\t';
            break;
        case 'v':
            val = '\v';
            break;
        case 'b':
            val = '\b';
            break;
        case 'r':
            val = '\r';
            break;
        case 'f':
            val = '\f';
            break;
        case 'a':
            val = '\a';
            break;
        case '\\':
            val = '\\';
            break;
        case '?':
            val = '\?';
            break;
        case '\'':
            val = '\'';
            break;
        case '\"':
            val = '\"';
            break;
        case 'x':
            val = (char) strtol(cp + 2, NULL, 16);
            break;
        default:
            val = (char) strtol(cp + 1, NULL, 8);
            break;
        }
    else
        val = *cp;
    while (*cp != '\'')
        cp++;
    *valp = (long) val;
    return cp;
}

static const char *
parse_value(IfParser *g, const char *cp, long *valp)
{
    const char *var;

    *valp = 0;

    SKIPSPACE(cp);
    if (!*cp)
        return cp;

    switch (*cp) {
    case '(':
        DO(cp = ParseIfExpression(g, cp + 1, valp));
        SKIPSPACE(cp);
        if (*cp != ')')
            return CALLFUNC(g, handle_error) (g, cp, ")");

        return cp + 1;          /* skip the right paren */

    case '!':
        DO(cp = parse_value(g, cp + 1, valp));
        *valp = !(*valp);
        return cp;

    case '-':
        DO(cp = parse_value(g, cp + 1, valp));
        *valp = -(*valp);
        return cp;

    case '+':
        DO(cp = parse_value(g, cp + 1, valp));
        return cp;

    case '~':
        DO(cp = parse_value(g, cp + 1, valp));
        *valp = ~(*valp);
        return cp;

    case '#':
        DO(cp = parse_variable(g, cp + 1, &var));
        SKIPSPACE(cp);
        if (*cp != '(')
            return CALLFUNC(g, handle_error) (g, cp, "(");
        do {
            DO(cp = parse_variable(g, cp + 1, &var));
            SKIPSPACE(cp);
        } while (*cp && *cp != ')');
        if (*cp != ')')
            return CALLFUNC(g, handle_error) (g, cp, ")");
        *valp = 1;              /* XXX */
        return cp + 1;

    case '\'':
        DO(cp = parse_character(g, cp + 1, valp));
        if (*cp != '\'')
            return CALLFUNC(g, handle_error) (g, cp, "'");
        return cp + 1;

    case 'd':
        if (strncmp(cp, "defined", 7) == 0 && !isalnum(cp[7])) {
            int paren = 0;
            int len;

            cp += 7;
            SKIPSPACE(cp);
            if (*cp == '(') {
                paren = 1;
                cp++;
            }
            DO(cp = parse_variable(g, cp, &var));
            len = cp - var;
            SKIPSPACE(cp);
            if (paren && *cp != ')')
                return CALLFUNC(g, handle_error) (g, cp, ")");
            *valp = (*(g->funcs.eval_defined)) (g, var, len);
            return cp + paren;  /* skip the right paren */
        }
        /* fall out */
    }

    if (isdigit(*cp)) {
        DO(cp = parse_number(g, cp, valp));
    }
    else if (!isvarfirstletter(*cp))
        return CALLFUNC(g, handle_error) (g, cp, "variable or number");
    else {
        const char *varend;

        DO(cp = parse_variable(g, cp, &var));
        varend = cp;
        SKIPSPACE(cp);
        if (*cp != '(') {
            *valp = (*(g->funcs.eval_variable)) (g, var, varend - var);
        }
        else {
            do {
                long dummy;

                DO(cp = ParseIfExpression(g, cp + 1, &dummy));
                SKIPSPACE(cp);
                if (*cp == ')')
                    break;
                if (*cp != ',')
                    return CALLFUNC(g, handle_error) (g, cp, ",");
            } while (1);

            *valp = 1;          /* XXX */
            cp++;
        }
    }

    return cp;
}

static const char *
parse_product(IfParser *g, const char *cp, long *valp)
{
    long rightval;

    DO(cp = parse_value(g, cp, valp));
    SKIPSPACE(cp);

    switch (*cp) {
    case '*':
        DO(cp = parse_product(g, cp + 1, &rightval));
        *valp = (*valp * rightval);
        break;

    case '/':
        DO(cp = parse_product(g, cp + 1, &rightval));
        if (rightval)
            *valp = (*valp / rightval);
        else
            *valp = LONG_MAX;
        break;

    case '%':
        DO(cp = parse_product(g, cp + 1, &rightval));
        if (rightval)
            *valp = (*valp % rightval);
        else
            *valp = LONG_MAX;
        break;
    }
    return cp;
}

static const char *
parse_sum(IfParser *g, const char *cp, long *valp)
{
    long rightval;

    DO(cp = parse_product(g, cp, valp));
    SKIPSPACE(cp);

    switch (*cp) {
    case '+':
        DO(cp = parse_sum(g, cp + 1, &rightval));
        *valp = (*valp + rightval);
        break;

    case '-':
        DO(cp = parse_sum(g, cp + 1, &rightval));
        *valp = (*valp - rightval);
        break;
    }
    return cp;
}

static const char *
parse_shift(IfParser *g, const char *cp, long *valp)
{
    long rightval;

    DO(cp = parse_sum(g, cp, valp));
    SKIPSPACE(cp);

    switch (*cp) {
    case '<':
        if (cp[1] == '<') {
            DO(cp = parse_shift(g, cp + 2, &rightval));
            *valp = (*valp << rightval);
        }
        break;

    case '>':
        if (cp[1] == '>') {
            DO(cp = parse_shift(g, cp + 2, &rightval));
            *valp = (*valp >> rightval);
        }
        break;
    }
    return cp;
}

static const char *
parse_inequality(IfParser *g, const char *cp, long *valp)
{
    long rightval;

    DO(cp = parse_shift(g, cp, valp));
    SKIPSPACE(cp);

    switch (*cp) {
    case '<':
        if (cp[1] == '=') {
            DO(cp = parse_inequality(g, cp + 2, &rightval));
            *valp = (*valp <= rightval);
        }
        else {
            DO(cp = parse_inequality(g, cp + 1, &rightval));
            *valp = (*valp < rightval);
        }
        break;

    case '>':
        if (cp[1] == '=') {
            DO(cp = parse_inequality(g, cp + 2, &rightval));
            *valp = (*valp >= rightval);
        }
        else {
            DO(cp = parse_inequality(g, cp + 1, &rightval));
            *valp = (*valp > rightval);
        }
        break;
    }
    return cp;
}

static const char *
parse_equality(IfParser *g, const char *cp, long *valp)
{
    long rightval;

    DO(cp = parse_inequality(g, cp, valp));
    SKIPSPACE(cp);

    switch (*cp) {
    case '=':
        if (cp[1] == '=')
            cp++;
        DO(cp = parse_equality(g, cp + 1, &rightval));
        *valp = (*valp == rightval);
        break;

    case '!':
        if (cp[1] != '=')
            break;
        DO(cp = parse_equality(g, cp + 2, &rightval));
        *valp = (*valp != rightval);
        break;
    }
    return cp;
}

static const char *
parse_band(IfParser *g, const char *cp, long *valp)
{
    long rightval;

    DO(cp = parse_equality(g, cp, valp));
    SKIPSPACE(cp);

    switch (*cp) {
    case '&':
        if (cp[1] != '&') {
            DO(cp = parse_band(g, cp + 1, &rightval));
            *valp = (*valp & rightval);
        }
        break;
    }
    return cp;
}

static const char *
parse_bxor(IfParser *g, const char *cp, long *valp)
{
    long rightval;

    DO(cp = parse_band(g, cp, valp));
    SKIPSPACE(cp);

    switch (*cp) {
    case '^':
        DO(cp = parse_bxor(g, cp + 1, &rightval));
        *valp = (*valp ^ rightval);
        break;
    }
    return cp;
}

static const char *
parse_bor(IfParser *g, const char *cp, long *valp)
{
    long rightval;

    DO(cp = parse_bxor(g, cp, valp));
    SKIPSPACE(cp);

    switch (*cp) {
    case '|':
        if (cp[1] != '|') {
            DO(cp = parse_bor(g, cp + 1, &rightval));
            *valp = (*valp | rightval);
        }
        break;
    }
    return cp;
}

static const char *
parse_land(IfParser *g, const char *cp, long *valp)
{
    long rightval;

    DO(cp = parse_bor(g, cp, valp));
    SKIPSPACE(cp);

    switch (*cp) {
    case '&':
        if (cp[1] != '&')
            return CALLFUNC(g, handle_error) (g, cp, "&&");
        DO(cp = parse_land(g, cp + 2, &rightval));
        *valp = (*valp && rightval);
        break;
    }
    return cp;
}

static const char *
parse_lor(IfParser *g, const char *cp, long *valp)
{
    long rightval;

    DO(cp = parse_land(g, cp, valp));
    SKIPSPACE(cp);

    switch (*cp) {
    case '|':
        if (cp[1] != '|')
            return CALLFUNC(g, handle_error) (g, cp, "||");
        DO(cp = parse_lor(g, cp + 2, &rightval));
        *valp = (*valp || rightval);
        break;
    }
    return cp;
}

static const char *
parse_cond(IfParser *g, const char *cp, long *valp)
{
    long trueval, falseval;

    DO(cp = parse_lor(g, cp, valp));
    SKIPSPACE(cp);

    switch (*cp) {
    case '?':
        DO(cp = parse_cond(g, cp + 1, &trueval));
        SKIPSPACE(cp);
        if (*cp != ':')
            return CALLFUNC(g, handle_error) (g, cp, ":");
        DO(cp = parse_cond(g, cp + 1, &falseval));
        *valp = (*valp ? trueval : falseval);
        break;
    }
    return cp;
}


/****************************************************************************
			     External Entry Points
 ****************************************************************************/

const char *
ParseIfExpression(IfParser *g, const char *cp, long *valp)
{
    return parse_cond(g, cp, valp);
}
