/*
 * Jeffrey Friedl
 * Omron Corporation			オムロン（株）
 * Nagaokakyoshi, Japan			〒617長岡京下海印寺
 *
 * jfriedl@nff.ncl.omron.co.jp
 *
 * This work is placed under the terms of the GNU General Purpose License
 * (the "GNU Copyleft").
 *
 * April 1994
 *
 */
#ifndef TEST
# include "lib/config.h"
# include "lib/jregex.h"
# include "lib/output.h"
# include "lookup.h"
# include "lib/assert.h"
#else
# include <assert.h>
# define outputf printf
#endif
#include <setjmp.h>
#include <ctype.h>
#include "eval.h"

#ifndef array_elements
# define array_elements(array)	(sizeof(array)/sizeof(array[0]))
#endif

static jmp_buf top_level;

const unsigned char *eval_errstr[] =
{
    (const unsigned char *)"ok",
    (const unsigned char *)"syntax error",
    (const unsigned char *)"division by zero",
    (const unsigned char *)"end of string unexpected",
    (const unsigned char *)"unknown symbol",
    (const unsigned char *)"unmatched close paren",
    (const unsigned char *)"unmatched open paren",
};

#define ERROR(VAL) longjmp(top_level, (VAL))

int eval_error_val = EVAL_OK;
const unsigned char *eval_error_loc;

static const unsigned char *str;
static unsigned long true = 1;
static unsigned long false = 0;

static struct
{
    const unsigned char *name;
    unsigned char namelen;
#define NAME(STRING) (const unsigned char *)(STRING), (sizeof(STRING)-1)
    enum { UNSIGNED_LONG, SIGNED_LONG } type;
    void *pointer;
} symtab[] = {
#ifndef TEST
  { NAME("checked"),  UNSIGNED_LONG, &lookup.count.checked },
  { NAME("matched"),  UNSIGNED_LONG, &lookup.count.matched },
  { NAME("printed"),  UNSIGNED_LONG, &lookup.count.printed },
  { NAME("nonword"),  UNSIGNED_LONG, &lookup.count.nonword },
  { NAME("filtered"), UNSIGNED_LONG, &lookup.count.filtered },
#endif
  { NAME("true"),     UNSIGNED_LONG, &true },
  { NAME("false"),    UNSIGNED_LONG, &false },
};

enum prec { P_EQUAL, P_BOOL, P_PLUS, P_MULT, P_HIGH };
#define P_LOWEST P_EQUAL

static int evaluate(int level /*recursion*/, enum prec plevel, int doit)
{
    int val = 0;

    /* skip any whitespace */
    while (str[0] == ' ' || str[0] == '\t')
	str++;

    #ifndef NDEBUG
    if (lookup.flag.debug)
	outputf("EVALU: level %d, plevel %d, val %2d, doit %d: {%s}\n",
		level, plevel, val, doit, str);
    #endif

    switch (str[0])
    {
      case 0: /* end of string */
	ERROR(EVAL_EOS);

      case '(':
	str++;
	val = evaluate(level+1, P_LOWEST, doit);
	if (*str != ')')
	    ERROR(EVAL_UNMATCHED_OPEN);
	str++;
	break;
    
      case ')':
	ERROR(EVAL_UNMATCHED_CLOSE); /* case '(' should nab all ')' */
    
      case '!': /* unary bang */
	str++;
	val = ! evaluate(level+1, P_HIGH, doit);
	break;

      case '-': /* unary minus */
	str++;
	val = - evaluate(level+1, P_HIGH, doit);
	break;

      case '+': /* unary plus */
	str++;
	val = evaluate(level+1, P_HIGH, doit);
	break;

      case '0': case '1': case '2': case '3': case '4':
      case '5': case '6': case '7': case '8': case '9':
	val = str[0] - '0';
	while (++str, isascii(str[0]) && isdigit(str[0]))
	    val = val * 10 + (str[0] - '0');
	break;
      default:
	if (!isascii(str[0]) || !isalpha(str[0]))
	    ERROR(EVAL_SYNTAX);
	else
	{
	    int i;
	    for (i = 0; i < array_elements(symtab); i++) {
		if (!isalnum(str[symtab[i].namelen]) &&
		    strncmp(str, symtab[i].name, symtab[i].namelen) == 0)
		        break;
	    }
	    if (i >= array_elements(symtab))
		ERROR(EVAL_NOSYM);
	    str += symtab[i].namelen;
	    switch(symtab[i].type) {
	      default:
		assert(0);
	      case UNSIGNED_LONG:
		val = *(unsigned long *)(symtab[i].pointer);
		break;
	      case SIGNED_LONG:
		val = *(long *)(symtab[i].pointer);
		break;
	    }
	}
	break;
    }

  again:
    /* skip any whitespace */
    while (str[0] == ' ' || str[0] == '\t')
	str++;

    #define threshold(PLEVEL) (plevel < PLEVEL ? PLEVEL : (PLEVEL + 1))

    #ifndef NDEBUG
    if (lookup.flag.debug)
	outputf("EVALU: level %d, plevel %d, val %2d, doit %d: {%s}\n",
		level, plevel, val, doit, str);
    #endif

    switch (str[0])
    {
	int val2; /* used as a temp by some cases */

      default:
      syntax_error:
	ERROR(EVAL_SYNTAX);

      case ')':
	if (level == 0)
	    ERROR(EVAL_UNMATCHED_OPEN);
	/*FALLTHROUGH*/
      case 0:
	return val;

      case '*':
	if (plevel > P_MULT)
	    return val;
	str++;
	val *= evaluate(level+1, threshold(P_MULT), doit);
	break;

      case '/':
	if (plevel > P_MULT)
	    return val;
	str++;
	val2 = evaluate(level+1, threshold(P_MULT), doit);
	if (doit) {
	    if (val2 == 0)
		ERROR(EVAL_DIVZERO);
	    val /= val2;
	}
	break;

      case '+':
	if (plevel > P_PLUS)
	    return val;
	str++;
	val += evaluate(level+1, threshold(P_PLUS), doit);
	break;

      case '-':
	if (plevel > P_PLUS)
	    return val;
	str++;
	val -= evaluate(level+1, threshold(P_PLUS), doit);
	break;

      case '&':
	if (plevel > P_BOOL)
	    return val;
	str++;
	if (str[0] == '&')  /* allow "&&" as well as "&" */
	    str++;
	val2 = evaluate(level+1, threshold(P_BOOL), doit && val);
	val = val && val2;
	break;

      case '|':
	if (plevel > P_BOOL)
	    return val;
	str++;
	if (str[0] == '|') /* allow "||" as well as "|" */
	    str++;
	val2 = evaluate(level+1, threshold(P_BOOL), doit && !val);
	val = val || val2;
	break;

      case '=':
	if (plevel > P_EQUAL)
	    return val;
	str++;
	if (str[0] == '=') /* allow "==" as well as "=" */
	    str++;
	val = (val == evaluate(level+1, P_EQUAL, doit));
	break;

      case '!':
	if (str[1] != '=')
	    goto syntax_error;
	if (plevel > P_EQUAL)
	    return val;
	str += 2;
	val = (val != evaluate(level+1, P_EQUAL, doit));
	break;
	
    }
    goto again;
}


int eval(const unsigned char *expression_string)
{
    str = expression_string;
    eval_error_val = setjmp(top_level);
    if (eval_error_val)
    {
	eval_error_loc = str;
	return 0;
    }
    return evaluate(/*level*/0, P_LOWEST, /*doit*/1);
}


#ifdef TEST
int main(int argc, const unsigned char *argv[0])
{
    const unsigned char *s = argv[1];
    int val = eval(s);
    if (eval_error_val != EVAL_OK) {
	printf("ERROR: %s\n%s\n", eval_errstr[eval_error_val], s);
	while (s++ < eval_error_loc)
	    putchar('-');
	printf("^\n");
    } else {
	printf("value is %d\n", val);
    }
    return 0;
}
#endif
