#include <sys/types.h>
#include "ansi.h"
#include "host.h"
#include "files.h"
#include "hash.h"
#include "buffer.h"
#include "cpp.h"
#include "cpp_hide.h"
#include "cpp_eval.h"
#include "allocate.h"
#include "config.h"

#undef NULL
#define NULL	0

#undef getc
#define getc()				cpp_getc_from(&evalbuf)

#define MAKE_FAIL(x)		(x).eval_result_kind = eval_failed
#define MAKE_INT(x,v,b)		{(x).eval_result_kind = eval_int; (x).eval_result.ival = (v); (x).base = (b);}
#define MAKE_FLOAT(x,v)		{(x).eval_result_kind = eval_float; (x).eval_result.fval = (v);}
#define MAKE_STRING(x,v)	{(x).eval_result_kind = eval_string; (x).eval_result.sval = (v);}

static cpp_eval_result_t result;
static buffer_t evalbuf;
static int c;
static int recover;

static int curtok, nexttok;

typedef cpp_eval_result_t tokval_t;
tokval_t curval, nextval;

enum {
	tok_error,
	tok_eof,
	tok_discrete,
	tok_float,
	tok_string,
	tok_eq,
	tok_neq,
	tok_geq,
	tok_leq,
	tok_shift_left,
	tok_shift_right,
	tok_or,
	tok_and,
	tok_sizeof
};

static int
promote(l,r)
	tokval_t *l, *r;
{
	switch(l->eval_result_kind) {
	  case eval_int:
		switch(r->eval_result_kind) {
		  case eval_int:
			return eval_int;
		  case eval_float:
			l->eval_result_kind = eval_float;
			l->eval_result.fval = (host_float_t) l->eval_result.ival;
			return eval_float;
		}
		break;
	  case eval_float:
		switch(r->eval_result_kind) {
		  case eval_int:
			r->eval_result_kind = eval_float;
			r->eval_result.fval = (host_float_t) r->eval_result.ival;
			/* fall through */
		  case eval_float:
			return eval_float;
		}
		break;
	}
	return eval_failed;
}

static tokval_t
failed()
{
	tokval_t tmp;
	recover = 1;
	MAKE_FAIL(tmp);
	return tmp;
}

static int
escaped_char(c, cp)
	int c, *cp;
{
	int val, i;

	switch (c) {
	  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 '0':
	  case '1':
	  case '2':
	  case '3':
	  case '4':
	  case '5':
	  case '6':
	  case '7':
		for (i = 0, val = 'c' - 0; is_octal_digit(c) && i < 3; i++, c = getc()) {
			val = val * 8 + (c - '0');
		}
		*cp = c;
		return val;

	  case 'x':
		for (i = 0, val = 0; is_hex_digit(c) && i < 2; i++, c = getc()) {
			val *= 16;
			if (is_digit(c)) {
				val += (c - '0');
			}
			else if (c <= 'F') {
				val += (c - ('A' - 10));
			}
			else {
				val += (c - ('a' - 10));
			}
		}
		*cp = c;
		return val;

	  default:
		val = c;
		break;
	}

	*cp = getc();
	return val;
}

static int
scan_string(c)
	int c;
{
	buffer_t buf;
	int len;
	char *s;

	buf_init(&buf);

	for (;;) {
		if (is_eof(c)) {
			goto end_of_string;
		}
		if (is_eol(c)) {
			goto end_of_string;
		}
		switch (c) {
		  case '"':
			c = getc();
			goto end_of_string;
		  case '\\':
			c = getc();
			switch (c) {
			  case '\n':
				c = getc();
				break;
			  case '0':
			  case '1':
			  case '2':
			  case '3':
			  case '4':
			  case '5':
			  case '6':
			  case '7':
			  case 'x':
			  case 'n':
			  case 't':
			  case 'v':
			  case 'b':
			  case 'r':
			  case 'f':
			  case 'a':
			  case '?':
			  case '\\':
			  case '\'':
			  case '\"':
				buf_add(&buf, escaped_char(c, &c));
				break;
			  default:
				buf_add(&buf, c);
				c = getc();
				break;
			}
			break;
		  default:
			buf_add(&buf, c);
			c = getc();
			break;
		}
	}

  end_of_string:
	s = buf_get_str(&buf);
	MAKE_STRING(nextval, s);

	return c;
}

static int
scan_char_const(c)
	int c;
{
	host_int_t cval = 0;

	for (;;) {
		if (is_eof(c)) {
			goto end_char_const;
		}
		if (is_eol(c)) {
			goto end_char_const;
		}
		switch (c) {
		  case '\'':
			c = getc();
			goto end_char_const;
		  case '\\':
			c = getc();
			switch (c) {
			  case '\n':
				c = getc();
				break;
			  case '0':
			  case 'x':
			  case 'n':
			  case 't':
			  case 'v':
			  case 'b':
			  case 'r':
			  case 'f':
			  case 'a':
			  case '?':
			  case '\\':
			  case '\'':
			  case '\"':
				cval <<= 8;
				cval |= escaped_char(c);
				c = getc();
				break;
			  default:
				cval <<= 8;
				cval |= c;
				c = getc();
				break;
			}
			break;
		  default:
			cval <<= 8;
			cval |= c;
			c = getc();
			break;
		}
	}

  end_char_const:
	MAKE_INT(nextval, cval, 10);
	return c;
}


static int
magnitude(c, val)
	int c;
	host_int_t *val;
{
	host_int_t m;
	int sign;

	if (! is_magnitude(c)) {
		*val = 1;
		return c;
	}


	c = getc();
	sign = 1;

	if (c == '-') {
		sign = -1;
		c = getc();
	}
	else if (c == '+') {
		c = getc();
	}

	for (m = 0; is_digit(c); ) {
		m = m * 10 + (c - '0');
		c = getc();
	}

	*val = m * sign;
	return c;
}

static int
scan_digit(c)
	int c;
{
	host_int_t val;
	int base = 10;

	if (c == '0') {
		val = 0;
		c = getc();
		if (c == 'x' || c == 'X') {
			base = 16;
			for (;;) {
				c = getc();
				if (is_digit(c)) {
					val = (val<<4) + (c - '0');
				}
				else if (c >= 'A' && c <= 'F') {
					val = (val<<4) + 10 + (c - 'A');
				}
				else if (c >= 'a' && c <= 'f') {
					val = (val<<4) + 10 + (c - 'a');
				}
				else {
					break;
				}
			}
		}
		else {
			base = 8;
			for (;;) {
				if (!is_octal_digit(c)) {
					break;
				}
				val = (val<<3) + (c - '0');
				c = getc();
			}
		}
	}
	else {
		val = c - '0';
		for (;;) {
			c = getc();
			if (! is_digit(c)) break;
			val = val * 10 + (c - '0');
		}
	}

	if (int_modifier(c)) {
		c = getc();
	}

	if (c == '.') {
		host_float_t dval, d;
		int tmp;

		dval = (host_float_t) val;
		d = 0.1;

		for (;;) {
			c = getc();
			if (! is_digit(c)) break;
			tmp = c - '0';
			dval = dval + (d * (host_float_t)tmp);
			d = d * 0.1;
		}


		/* handle magnitude */
		if (is_magnitude(c)) {
			host_int_t vm;
			host_float_t m;

			c = magnitude(c, &vm);

			if (vm > 0) {
				for (m = 1.0; vm; vm--) {
					m *= 10.0;
				}
			}
			else {
				for (m = 1.0; vm; vm++) {
					m /= 10.0;
				}
			}

			dval *= m;
		}

		if (float_modifier(c)) {
			c = getc();
		}

		MAKE_FLOAT(nextval, dval);
		nexttok = tok_float;
		return c;
	}

	while (int_modifier(c)) {
		c = getc();
	}

	MAKE_INT(nextval, val, base);
	nexttok = tok_discrete;
	return c;
}

static int
skip_c_comment(c)
	int c;
{
	for (;;) {
		if (c == BAD_INPUT) {
			return BAD_INPUT;
		}
		if (c == 0 || is_eof(c) || is_eol(c)) {
			return c;
		}
		switch (c) {
		  case '\\':
			c = getc();
			c = getc();
			break;
		  case '*':
			c = getc();
			if (c == '/') {
				return getc();
			}
			break;
		  default:
			c = getc();
			break;
		}
	}
}

static int
skip_cpp_comment(c)
	int c;
{
	for (;;) {
		if (c == BAD_INPUT) {
			return BAD_INPUT;
		}
		if ((c == 0) || is_eof(c))
			return c;
		else if (is_eol(c))
			return getc();
		else
			c = getc();
	}
}

static int
advance()
{
	curtok = nexttok;
	curval = nextval;

	for (;;) {
		if (c == BAD_INPUT) {
			nexttok = tok_error;
			break;
		}
		if (c == 0 || is_eof(c) || is_eol(c)) {
			nexttok = tok_eof;
			break;
		}
		if (is_white(c)) {
			c = getc();
			continue;
		}
		if (is_digit(c)) {
			c = scan_digit(c);
			break;
		}
		if (is_alpha(c)) {
			nexttok = tok_error;
			break;
		}

		nexttok = c;
		c = getc();

		switch (nexttok) {
		  case '/':
			if (c == '*') {
				c = skip_c_comment(getc());
				continue;
			}
            else if (c == '/') {
				c = skip_cpp_comment(getc());
				continue;
			}
			break;
		  case '\"':
			c = scan_string(c);
			nexttok = tok_string;
			break;
		  case '\'':
			c = scan_char_const(c);
			nexttok = tok_discrete;
			break;
		  case '<':
			if (c == '<') {
				c = getc();
				nexttok = tok_shift_left;
			}
			else if (c == '=') {
				c = getc();
				nexttok = tok_leq;
			}
			break;
		  case '>':
			if (c == '>') {
				c = getc();
				nexttok = tok_shift_right;
			}
			else if (c == '=') {
				c = getc();
				nexttok = tok_geq;
			}
			break;
		  case '=':
			if (c == '=') {
				c = getc();
				nexttok = tok_eq;
			}
			break;
		  case '!':
			if (c == '=') {
				c = getc();
				nexttok = tok_neq;
			}
			break;
		  case '|':
			if (c == '|') {
				c = getc();
				nexttok = tok_or;
			}
			break;
		  case '&':
			if (c == '&') {
				c = getc();
				nexttok = tok_and;
			}
			break;
		}

		break;
	}

  end_of_subp:
	return curtok;
}

static int
expect(tok)
	int tok;
{
	if (curtok != tok) {
		recover = 1;
		return 0;
	}
	return 1;
}

static tokval_t eval();

static tokval_t
term()
{
	tokval_t val;
	char *p;

	switch (curtok) {
	  case tok_discrete:
	  case tok_float:
	  case tok_string:
		val = curval;
		advance();
		return val;
	  case '-':
		advance();
		val = term();
		if (recover) {
			return val;
		}
		if (IS_EVAL_INT(val)) {
			EVAL_INT(val) = -EVAL_INT(val);
			return val;
		}
		if (IS_EVAL_FLOAT(val)) {
			EVAL_FLOAT(val) = -EVAL_FLOAT(val);
			return val;
		}
		MAKE_FAIL(val);
		return val;
	  case '+':
		advance();
		val = term();
		if (recover) {
			return val;
		}
		MAKE_FAIL(val);
		return val;
	  case '!':
		advance();
		val = term();
		if (recover) {
			return val;
		}
		if (IS_EVAL_INT(val)) {
			EVAL_INT(val) = !EVAL_INT(val);
			return val;
		}
		MAKE_FAIL(val);
		return val;
	  case '~':
		advance();
		val = term();
		if (recover) {
			return val;
		}
		if (IS_EVAL_INT(val)) {
			EVAL_INT(val) = ~EVAL_INT(val);
			return val;
		}
		MAKE_FAIL(val);
		return val;
	  case '(':
		advance();
		val = eval();
		if (recover) {
			return val;
		}
		if (expect(')')) {
			advance();
		}
		else {
			MAKE_FAIL(val);
		}
		return val;
	  default:
		return failed();
	}
}

static tokval_t
f10()
{
	tokval_t l,r;
	int op;

	if (recover) {
		MAKE_FAIL(l);
		return l;
	}

	l = term();
	if (recover) {
		return l;
	}

	for (;;) {
		switch (curtok) {
		  case '*':
		  case '/':
		  case '%':
			op = curtok;
			break;
		  default:
			return l;
		}

		advance();
      
		r = term();
		if (recover) {
			return r;
		}

		switch (promote(&l,&r)) {
		  case eval_int:
			switch (op) {
			  case '*':
				EVAL_INT(l) = EVAL_INT(l) * EVAL_INT(r);
				break;
			  case '/':
				if (EVAL_INT(r) == 0) {
					return failed();
				}
				EVAL_INT(l) = EVAL_INT(l) / EVAL_INT(r);
				break;
			  case '%':
				if (EVAL_INT(r) == 0) {
					return failed();
				}
				EVAL_INT(l) = EVAL_INT(l) % EVAL_INT(r);
				break;
			}
			break;
		  case eval_float:
			switch (op) {
			  case '*':
				EVAL_FLOAT(l) = EVAL_FLOAT(l) * EVAL_FLOAT(r);
				break;
			  case '/':
				if (EVAL_FLOAT(r) == 0.0) {
					return failed();
				}
				EVAL_FLOAT(l) = EVAL_FLOAT(l) / EVAL_FLOAT(r);
				break;
			  case '%':
				if (EVAL_FLOAT(r) == 0.0) {
					return failed();
				}
				EVAL_FLOAT(l) = EVAL_FLOAT(l) / EVAL_FLOAT(r);
				break;
			}
			break;
		  default:
			return failed();
		}
	}
}

static tokval_t
f9()
{
	tokval_t l,r;
	int op;

	if (recover) {
		MAKE_FAIL(l);
		return l;
	}

	l = f10();
	if (recover) {
		return l;
	}

	for (;;) {
		switch (curtok) {
		  case '+':
		  case '-':
			op = curtok;
			break;
		  default:
			return l;
		}

		advance();
      
		r = f10();
		if (recover) {
			return r;
		}

		switch (promote(&l,&r)) {
		  case eval_int:
			switch (op) {
			  case '+':
				EVAL_INT(l) = EVAL_INT(l) + EVAL_INT(r);
				break;
			  case '-':
				EVAL_INT(l) = EVAL_INT(l) - EVAL_INT(r);
				break;
			}
			break;
		  case eval_float:
			switch (op) {
			  case '+':
				EVAL_FLOAT(l) = EVAL_FLOAT(l) + EVAL_FLOAT(r);
				break;
			  case '-':
				EVAL_FLOAT(l) = EVAL_FLOAT(l) - EVAL_FLOAT(r);
				break;
			}
			break;
		  default:
			return failed();
		}
	}
}

static tokval_t
f8()
{
	tokval_t l,r;
	int op, tmp;

	if (recover) {
		MAKE_FAIL(l);
		return l;
	}

	l = f9();
	if (recover) {
		return l;
	}

	for (;;) {
		switch (curtok) {
		  case tok_shift_left:
		  case tok_shift_right:
			op = curtok;
			break;
		  default:
			return l;
		}

		advance();
      
		r = f9();
		if (recover) {
			return r;
		}

		if (IS_EVAL_INT(l) & IS_EVAL_INT(r)) {
			switch (op) {
			  case tok_shift_left:
				EVAL_INT(l) = EVAL_INT(l) << EVAL_INT(r);
				break;
			  case tok_shift_right:
				EVAL_INT(l) = EVAL_INT(l) >> EVAL_INT(r);
				break;
			}
		}
		else {
			return failed();
		}
	}
}

static tokval_t
f7()
{
	tokval_t l,r;
	int op, tmp;

	if (recover) {
		MAKE_FAIL(l);
		return l;
	}

	l = f8();
	if (recover) {
		return l;
	}

	for (;;) {
		switch (curtok) {
		  case '>':
		  case tok_geq:
		  case '<':
		  case tok_leq:
			op = curtok;
			break;
		  default:
			return l;
		}

		advance();
      
		r = f8();
		if (recover) {
			return r;
		}

		switch (promote(&l,&r)) {
		  case eval_int:
			switch (op) {
			  case '>':
				EVAL_INT(l) = EVAL_INT(l) > EVAL_INT(r);
				break;
			  case tok_geq:
				EVAL_INT(l) = EVAL_INT(l) >= EVAL_INT(r);
				break;
			  case '<':
				EVAL_INT(l) = EVAL_INT(l) < EVAL_INT(r);
				break;
			  case tok_leq:
				EVAL_INT(l) = EVAL_INT(l) <= EVAL_INT(r);
				break;
			}
			break;
		  case eval_float:
			switch (op) {
			  case '>':
				tmp = EVAL_FLOAT(l) > EVAL_FLOAT(r);
				break;
			  case tok_geq:
				tmp = EVAL_FLOAT(l) >= EVAL_FLOAT(r);
				break;
			  case '<':
				tmp = EVAL_FLOAT(l) < EVAL_FLOAT(r);
				break;
			  case tok_leq:
				tmp = EVAL_FLOAT(l) <= EVAL_FLOAT(r);
				break;
			}
			MAKE_INT(l, tmp, 10);
			break;
		  default:
			return failed();
		}
	}
}

static tokval_t
f6()
{
	tokval_t l,r;
	int op, tmp;

	if (recover) {
		MAKE_FAIL(l);
		return l;
	}

	l = f7();
	if (recover) {
		return l;
	}

	for (;;) {
		switch (curtok) {
		  case tok_eq:
		  case tok_neq:
			op = curtok;
			break;
		  default:
			return l;
		}

		advance();
      
		r = f7();
		if (recover) {
			return r;
		}

		switch (promote(&l,&r)) {
		  case eval_int:
			if (op == tok_eq) {
				EVAL_INT(l) = EVAL_INT(l) == EVAL_INT(r);
			}
			else {
				EVAL_INT(l) = EVAL_INT(l) != EVAL_INT(r);
			}
			break;
		  case eval_float:
			if (op == tok_eq) {
				tmp = EVAL_FLOAT(l) == EVAL_FLOAT(r);
			}
			else {
				tmp = EVAL_FLOAT(l) != EVAL_FLOAT(r);
			}
			MAKE_INT(l, tmp, 10);
			break;
		  default:
			return failed();
		}
	}
}

static tokval_t
f5()
{
	tokval_t l,r;

	if (recover) {
		MAKE_FAIL(l);
		return l;
	}

	l = f6();
	if (recover) {
		return l;
	}

	for (;;) {
		if (curtok != '&') {
			return l;
		}

		advance();
      
		r = f6();
		if (recover) {
			return r;
		}

		if (IS_EVAL_INT(l) & IS_EVAL_INT(r)) {
			EVAL_INT(l) = EVAL_INT(l) & EVAL_INT(r);
		}
		else {
			return failed();
		}
	}
}

static tokval_t
f4()
{
	tokval_t l,r;

	if (recover) {
		MAKE_FAIL(l);
		return l;
	}

	l = f5();
	if (recover) {
		return l;
	}

	for (;;) {
		if (curtok != '^') {
			return l;
		}

		advance();
      
		r = f5();
		if (recover) {
			return r;
		}

		if (IS_EVAL_INT(l) & IS_EVAL_INT(r)) {
			EVAL_INT(l) = EVAL_INT(l) ^ EVAL_INT(r);
		}
		else {
			return failed();
		}
	}
}

static tokval_t
f3()
{
	tokval_t l,r;

	if (recover) {
		MAKE_FAIL(l);
		return l;
	}

	l = f4();
	if (recover) {
		return l;
	}

	for (;;) {
		if (curtok != '|') {
			return l;
		}

		advance();
      
		r = f4();
		if (recover) {
			return r;
		}

		if (IS_EVAL_INT(l) && IS_EVAL_INT(r)) {
			EVAL_INT(l) = EVAL_INT(l) | EVAL_INT(r);
		}
		else {
			return failed();
		}
	}
}

static tokval_t
f2()
{
	tokval_t l,r;

	if (recover) {
		MAKE_FAIL(l);
		return l;
	}

	l = f3();
	if (recover) {
		return l;
	}

	for (;;) {
		if (curtok != tok_and) {
			return l;
		}

		advance();
      
		r = f3();
		if (recover) {
			return r;
		}

		if (IS_EVAL_INT(l) && IS_EVAL_INT(r)) {
			EVAL_INT(l) = EVAL_INT(l) && EVAL_INT(r);
		}
		else {
			return failed();
		}
	}
}

static tokval_t
f1()
{
	tokval_t l,r;

	if (recover) {
		MAKE_FAIL(l);
		return l;
	}

	l = f2();
	if (recover) {
		return l;
	}

	for (;;) {
		if (curtok != tok_or) {
			return l;
		}

		advance();
      
		r = f2();
		if (recover) {
			return r;
		}

		if (IS_EVAL_INT(l) && IS_EVAL_INT(r)) {
			EVAL_INT(l) = EVAL_INT(l) || EVAL_INT(r);
		}
		else {
			return failed();
		}
	}
}

static tokval_t
eval()
{
	tokval_t cond,tru,fals,tmp;
	
	if (recover) {
		MAKE_FAIL(tmp);
		return tmp;
	}
	
	tmp = f1();

	if (recover) {
		return tmp;
	}
	
	for (;;) {
		if (curtok != '?') {
			return tmp;
		}
		
		advance();
		
		tru = eval();
		if (recover) {
			return tru;
		}
		
		if (!expect(':')) {
			MAKE_FAIL(tmp);
			return tmp;
		}

		advance();
		
		fals = eval();
		if (recover) {
			return fals;
		}
		
		if (IS_EVAL_INT(cond)) {
			tmp = (EVAL_INT(cond) != 0) ? tru : fals;
		}
		else {
			return failed();
		}
	}
}

cpp_eval_result_t
cpp_eval(str)
	char *str;
{
	scan_position_t *newpos;
	scan_position_t *savepos;
	cpp_control_state_t save_state;
	cpp_control_state_t new_state;

	assert(str != NULL);

	buf_init(&evalbuf);

	result.eval_result_kind = eval_int;
	result.eval_result.ival = 0;

	new_state.skip_else = 0;
	new_state.cur_scope = 0;
	new_state.gen_scope = 0;
	new_state._parsing = 1;

	newpos = (scan_position_t*) allocate(sizeof(scan_position_t));

	newpos->scan_kind = scan_text;
	newpos->scan.text = str;

	cpp_set_state(newpos, &new_state, &savepos, &save_state);

	if (savepos != NULL) {
		newpos->scan_pos = savepos->scan_pos;
	}

	c = ' ';
	recover = 0;

	advance();
	advance();

	result = eval();

	cpp_set_state(savepos, &save_state, &newpos, &new_state);

	if (curtok != tok_eof) {
		MAKE_FAIL(result);
	}

	return result;
}


syntax highlighted by Code2HTML, v. 0.9.1