/*
	constfold.c

	expression constant folding

	Copyright (C) 2004 Bill Currie <bill@taniwha.org>

	Author: Bill Currie <bill@taniwha.org>
	Date: 2004/01/22

	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:

		Free Software Foundation, Inc.
		59 Temple Place - Suite 330
		Boston, MA  02111-1307, USA

*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include <stdlib.h>

#include <QF/dstring.h>
#include <QF/mathlib.h>

#include "tools/qfcc/include/diagnostic.h"
#include "tools/qfcc/include/expr.h"
#include "tools/qfcc/include/options.h"
#include "tools/qfcc/include/qfcc.h"
#include "tools/qfcc/include/strpool.h"
#include "tools/qfcc/include/type.h"
#include "tools/qfcc/include/value.h"

#include "tools/qfcc/source/qc-parse.h"

typedef expr_t *(*operation_t) (int op, expr_t *e, expr_t *e1, expr_t *e2);
typedef expr_t *(*unaryop_t) (int op, expr_t *e, expr_t *e1);

static __attribute__((pure)) int
valid_op (int op, int *valid_ops)
{
	while (*valid_ops && op != *valid_ops)
		valid_ops++;
	return *valid_ops == op;
}

static expr_t *
cmp_result_expr (int result)
{
	if (is_float (type_default)) {
		return new_float_expr (result);
	} else {
		return new_int_expr(result);
	}
}

static int
is_addsub (int op)
{
	return op == '+' || op == '-';
}

static int
inv_addsub (int op)
{
	return op == '+' ? '-' : '+';
}

static expr_t *
do_op_string (int op, expr_t *e, expr_t *e1, expr_t *e2)
{
	const char *s1, *s2;
	static dstring_t *temp_str;
	static int  valid[] = {'+', LT, GT, LE, GE, EQ, NE, 0};

	if (!valid_op (op, valid))
		return error (e1, "invalid operand for string");

	if (is_compare (op) || is_logic (op)) {
		if (options.code.progsversion > PROG_ID_VERSION)
			e->e.expr.type = &type_int;
		else
			e->e.expr.type = &type_float;
	} else {
		e->e.expr.type = &type_string;
	}

	if (!is_constant (e1) || !is_constant (e2))
		return e;

	s1 = expr_string (e1);
	s2 = expr_string (e2);
	if (!s1)
		s1 = "";
	if (!s2)
		s2 = "";

	switch (op) {
		case '+':
			if (!temp_str)
				temp_str = dstring_newstr ();
			dstring_clearstr (temp_str);
			dstring_appendstr (temp_str, s1);
			dstring_appendstr (temp_str, s2);
			e = new_string_expr (save_string (temp_str->str));
			break;
		case LT:
			e = cmp_result_expr (strcmp (s1, s2) < 0);
			break;
		case GT:
			e = cmp_result_expr (strcmp (s1, s2) > 0);
			break;
		case LE:
			e = cmp_result_expr (strcmp (s1, s2) <= 0);
			break;
		case GE:
			e = cmp_result_expr (strcmp (s1, s2) >= 0);
			break;
		case EQ:
			e = cmp_result_expr (strcmp (s1, s2) == 0);
			break;
		case NE:
			e = cmp_result_expr (strcmp (s1, s2));
			break;
		default:
			internal_error (e1, 0);
	}
	e->file = e1->file;
	e->line = e1->line;
	return e;
}

static expr_t *
convert_to_float (expr_t *e)
{
	if (is_float(get_type (e)))
		return e;

	expr_t     *n = cast_expr (&type_float, e);
	n->file = e->file;
	n->line = e->line;
	return n;
}

static expr_t *
convert_to_double (expr_t *e)
{
	if (is_double(get_type (e)))
		return e;

	expr_t     *n = cast_expr (&type_double, e);
	n->file = e->file;
	n->line = e->line;
	return n;
}

static expr_t *
do_op_float (int op, expr_t *e, expr_t *e1, expr_t *e2)
{
	float       f1, f2;
	expr_t     *conv;
	type_t     *type = &type_float;
	static int  valid[] = {
		'+', '-', '*', '/', '&', '|', '^', '%',
		SHL, SHR, AND, OR, LT, GT, LE, GE, EQ, NE, 0
	};

	if (!valid_op (op, valid))
		return error (e1, "invalid operator for float");

	if ((conv = convert_to_float (e1)) != e1) {
		e->e.expr.e1 = e1 = conv;
	}
	if ((conv = convert_to_float (e2)) != e2) {
		e->e.expr.e2 = e2 = conv;
	}
	if (is_compare (op) || is_logic (op)) {
		if (options.code.progsversion > PROG_ID_VERSION)
			type = &type_int;
		else
			type = &type_float;
	}
	e->e.expr.type = type;

	if (op == '*' && is_constant (e1) && expr_float (e1) == 1)
		return e2;
	if (op == '*' && is_constant (e2) && expr_float (e2) == 1)
		return e1;
	if (op == '*' && is_constant (e1) && expr_float (e1) == 0)
		return e1;
	if (op == '*' && is_constant (e2) && expr_float (e2) == 0)
		return e2;
	if (op == '/' && is_constant (e2) && expr_float (e2) == 1)
		return e1;
	if (op == '/' && is_constant (e2) && expr_float (e2) == 0)
		return error (e, "division by zero");
	if (op == '/' && is_constant (e1) && expr_float (e1) == 0)
		return e1;
	if (op == '+' && is_constant (e1) && expr_float (e1) == 0)
		return e2;
	if (op == '+' && is_constant (e2) && expr_float (e2) == 0)
		return e1;
	if (op == '-' && is_constant (e2) && expr_float (e2) == 0)
		return e1;

	if (!is_constant (e1) || !is_constant (e2))
		return e;

	f1 = expr_float (e1);
	f2 = expr_float (e2);

	switch (op) {
		case '+':
			e = new_float_expr (f1 + f2);
			break;
		case '-':
			e = new_float_expr (f1 - f2);
			break;
		case '*':
			e = new_float_expr (f1 * f2);
			break;
		case '/':
			if (!f2)
				return error (e1, "divide by zero");
			e = new_float_expr (f1 / f2);
			break;
		case '&':
			e = new_float_expr ((int)f1 & (int)f2);
			break;
		case '|':
			e = new_float_expr ((int)f1 | (int)f2);
			break;
		case '^':
			e = new_float_expr ((int)f1 ^ (int)f2);
			break;
		case '%':
			e = new_float_expr ((int)f1 % (int)f2);
			break;
		case SHL:
			e = new_float_expr ((int)f1 << (int)f2);
			break;
		case SHR:
			e = new_float_expr ((int)f1 >> (int)f2);
			break;
		case AND:
			e = cmp_result_expr (f1 && f2);
			break;
		case OR:
			e = cmp_result_expr (f1 || f2);
			break;
		case LT:
			e = cmp_result_expr (f1 < f2);
			break;
		case GT:
			e = cmp_result_expr (f1 > f2);
			break;
		case LE:
			e = cmp_result_expr (f1 <= f2);
			break;
		case GE:
			e = cmp_result_expr (f1 >= f2);
			break;
		case EQ:
			e = cmp_result_expr (f1 == f2);
			break;
		case NE:
			e = cmp_result_expr (f1 != f2);
			break;
		default:
			internal_error (e1, 0);
	}
	e->file = e1->file;
	e->line = e1->line;
	return e;
}

static expr_t *
do_op_double (int op, expr_t *e, expr_t *e1, expr_t *e2)
{
	double      d1, d2;
	expr_t     *conv;
	type_t     *type = &type_double;
	static int  valid[] = {
		'+', '-', '*', '/', '%',
		LT, GT, LE, GE, EQ, NE, 0
	};

	if (!valid_op (op, valid))
		return error (e1, "invalid operator for double");

	if ((conv = convert_to_double (e1)) != e1) {
		e->e.expr.e1 = e1 = conv;
	}
	if ((conv = convert_to_double (e2)) != e2) {
		e->e.expr.e2 = e2 = conv;
	}
	if (is_compare (op) || is_logic (op)) {
		type = &type_int;
	}
	e->e.expr.type = type;

	if (op == '*' && is_constant (e1) && expr_double (e1) == 1)
		return e2;
	if (op == '*' && is_constant (e2) && expr_double (e2) == 1)
		return e1;
	if (op == '*' && is_constant (e1) && expr_double (e1) == 0)
		return e1;
	if (op == '*' && is_constant (e2) && expr_double (e2) == 0)
		return e2;
	if (op == '/' && is_constant (e2) && expr_double (e2) == 1)
		return e1;
	if (op == '/' && is_constant (e2) && expr_double (e2) == 0)
		return error (e, "division by zero");
	if (op == '/' && is_constant (e1) && expr_double (e1) == 0)
		return e1;
	if (op == '+' && is_constant (e1) && expr_double (e1) == 0)
		return e2;
	if (op == '+' && is_constant (e2) && expr_double (e2) == 0)
		return e1;
	if (op == '-' && is_constant (e2) && expr_double (e2) == 0)
		return e1;

	if (!is_constant (e1) || !is_constant (e2))
		return e;

	d1 = expr_double (e1);
	d2 = expr_double (e2);

	switch (op) {
		case '+':
			e = new_double_expr (d1 + d2);
			break;
		case '-':
			e = new_double_expr (d1 - d2);
			break;
		case '*':
			e = new_double_expr (d1 * d2);
			break;
		case '/':
			if (!d2)
				return error (e1, "divide by zero");
			e = new_double_expr (d1 / d2);
			break;
		case '%':
			e = new_double_expr ((int)d1 % (int)d2);
			break;
		case LT:
			e = cmp_result_expr (d1 < d2);
			break;
		case GT:
			e = cmp_result_expr (d1 > d2);
			break;
		case LE:
			e = cmp_result_expr (d1 <= d2);
			break;
		case GE:
			e = cmp_result_expr (d1 >= d2);
			break;
		case EQ:
			e = cmp_result_expr (d1 == d2);
			break;
		case NE:
			e = cmp_result_expr (d1 != d2);
			break;
		default:
			internal_error (e1, 0);
	}
	e->file = e1->file;
	e->line = e1->line;
	return e;
}

static expr_t *
do_op_vector (int op, expr_t *e, expr_t *e1, expr_t *e2)
{
	const float *v1, *v2;
	vec3_t      v, float_vec;
	static int  valid[] = {'+', '-', '*', HADAMARD, SCALE, EQ, NE, 0};
	expr_t     *t;

	if (!is_vector(get_type (e1))) {

		if (op != '*')
			return error (e1, "invalid operator for vector");

		t = e1;
		e->e.expr.e1 = e1 = e2;
		e2 = t;
	}
	if (!is_vector(get_type (e2))) {
		e->e.expr.e2 = e2 = convert_to_float (e2);
		if (op != SCALE && op != '/')
			return error (e1, "invalid operator for vector");
	} else {
		if (!valid_op (op, valid))
			return error (e1, "invalid operator for vector");
	}
	if (is_compare (op) || is_logic (op)) {
		if (options.code.progsversion > PROG_ID_VERSION)
			e->e.expr.type = &type_int;
		else
			e->e.expr.type = &type_float;
	} else if (op == '*' && is_vector(get_type (e2))) {
		e->e.expr.type = &type_float;
	} else if (op == '/' && !is_constant (e1)) {
		e2 = fold_constants (binary_expr ('/', new_float_expr (1), e2));
		e = fold_constants (binary_expr ('*', e1, e2));
	} else {
		e->e.expr.type = &type_vector;
	}

	if (op == '*' && is_float_val (e2) && expr_float (e2) == 1)
		return e1;
	if (op == '*' && is_float_val (e2) && expr_float (e2) == 0)
		return new_vector_expr (vec3_origin);
	if (op == '/' && is_float_val (e2) && expr_float (e2) == 1)
		return e1;
	if (op == '/' && is_float_val (e2) && expr_float (e2) == 0)
		return error (e, "division by zero");
	if (op == '+' && is_constant (e1) && VectorIsZero (expr_vector (e1)))
		return e2;
	if (op == '+' && is_constant (e2) && VectorIsZero (expr_vector (e2)))
		return e1;
	if (op == '-' && is_constant (e1) && VectorIsZero (expr_vector (e1))
		&& is_constant (e2)) {
		vec3_t      v;
		VectorNegate (expr_vector (e2), v);
		e = new_vector_expr (v);
		e->file = e2->file;
		e->line = e2->line;
		return e;
	}
	if (op == '-' && is_constant (e2) && VectorIsZero (expr_vector (e2)))
		return e1;

	if (!is_constant (e1) || !is_constant (e2))
		return e;

	if (is_float_val (e1)) {
		float_vec[0] = expr_float (e1);
		v2 = float_vec;
		v1 = expr_vector (e2);
	} else if (is_float_val (e2)) {
		float_vec[0] = expr_float (e2);
		v2 = float_vec;
		v1 = expr_vector (e1);
	} else {
		v1 = expr_vector (e1);
		v2 = expr_vector (e2);
	}

	switch (op) {
		case '+':
			VectorAdd (v1, v2, v);
			e = new_vector_expr (v);
			break;
		case '-':
			VectorSubtract (v1, v2, v);
			e = new_vector_expr (v);
			break;
		case '/':
			if (!v2[0])
				return error (e1, "divide by zero");
			VectorScale (v1, 1 / v2[0], v);
			e = new_vector_expr (v);
			break;
		case '*':
			if (is_vector(get_type (e2))) {
				e = new_float_expr (DotProduct (v1, v2));
			} else {
				VectorScale (v1, v2[0], v);
				e = new_vector_expr (v);
			}
			break;
		case EQ:
			e = cmp_result_expr (VectorCompare (v1, v2));
			break;
		case NE:
			e = cmp_result_expr (!VectorCompare (v1, v2));
			break;
		default:
			internal_error (e1, 0);
	}
	e->file = e1->file;
	e->line = e1->line;
	return e;
}

static expr_t *
do_op_entity (int op, expr_t *e, expr_t *e1, expr_t *e2)
{
	type_t     *type = get_type (e2);

	if (op == '.' && type->type == ev_field) {
		return e;
	}
	if (op == EQ || op == NE) {
		if (options.code.progsversion > PROG_ID_VERSION)
			e->e.expr.type = &type_int;
		else
			e->e.expr.type = &type_float;
		return e;
	}
	if (!is_entity(type))
		return error (e1, "invalid operator for entity");
	e->e.expr.type = &type_entity;
	return e;
}

static expr_t *
do_op_field (int op, expr_t *e, expr_t *e1, expr_t *e2)
{
	return error (e1, "invalid operator for field");
}

static expr_t *
do_op_func (int op, expr_t *e, expr_t *e1, expr_t *e2)
{
	if (op == EQ || op == NE) {
		if (options.code.progsversion > PROG_ID_VERSION)
			e->e.expr.type = &type_int;
		else
			e->e.expr.type = &type_float;
		return e;
	}
	return error (e1, "invalid operator for func");
}

static expr_t *
do_op_pointer (int op, expr_t *e, expr_t *e1, expr_t *e2)
{
	type_t     *type;
	static int  valid[] = {'-', '.', EQ, NE, 0};

	if (is_integral (type = get_type (e2)) && (op == '-' || op == '+')) {
		// pointer arithmetic
		expr_t     *ptoi = new_alias_expr (type, e1);
		e->e.expr.e1 = ptoi;
		e = fold_constants (e);
		return new_alias_expr (get_type (e1), e);
	}
	if (!valid_op (op, valid))
		return error (e1, "invalid operator for pointer: %s",
					  get_op_string (op));

	if (op == '-') {
		type = get_type (e1);
		if (type != get_type (e2))
			return error (e2, "invalid operands to binary -");
		e1 = new_alias_expr (&type_int, e1);
		e2 = new_alias_expr (&type_int, e2);
		e = binary_expr ('-', e1, e2);
		if (type_size (type) != 1)
			e = binary_expr ('/', e, new_int_expr (type_size (type)));
		return e;
	}
	if (op == EQ || op == NE) {
		if (options.code.progsversion > PROG_ID_VERSION)
			e->e.expr.type = &type_int;
		else
			e->e.expr.type = &type_float;
	}
	if (op != '.' && extract_type (e1) != extract_type (e2))
		return type_mismatch (e1, e2, op);
	if (op == '.' && is_uint(get_type (e2)))
		e->e.expr.e2 = cast_expr (&type_int, e2);
	return e;
}

static expr_t *
do_op_quatvect (int op, expr_t *e, expr_t *e1, expr_t *e2)
{
	if (op != '*')
		return error (e1, "invalid operator for quaternion and vector");
	if (is_constant (e1) && QuatIsZero (expr_quaternion (e1)))
		return new_vector_expr (vec3_origin);
	if (is_constant (e2) && VectorIsZero (expr_vector (e2)))
		return new_vector_expr (vec3_origin);
	if (is_constant (e1) && is_constant (e2)) {
		const vec_t *q, *v;
		vec3_t      r;

		q = expr_quaternion (e1);
		v = expr_vector (e2);
		QuatMultVec (q, v, r);
		return new_vector_expr (r);
	}
	e->e.expr.type = &type_vector;
	return e;
}

static expr_t *
do_op_quaternion (int op, expr_t *e, expr_t *e1, expr_t *e2)
{
	const float *q1, *q2;
	quat_t      q, float_quat;
	static int  valid[] = {'+', '-', '*', EQ, NE, 0};
	expr_t     *t;

	if (!is_quaternion(get_type (e1))) {

		if (op != '*' && op != '/')
			return error (e1, "invalid operator for quaternion");

		if (op == '*') {
			t = e1;
			e->e.expr.e1 = e1 = e2;
			e2 = t;
		}
	}
	if (!is_quaternion(get_type (e2))) {
		e->e.expr.e2 = e2 = convert_to_float (e2);
		if (op != '*' && op != '/')
			return error (e1, "invalid operator for quaternion");
	} else {
		if (!valid_op (op, valid))
			return error (e1, "invalid operator for quaternion");
	}
	if (is_compare (op) || is_logic (op)) {
		if (options.code.progsversion > PROG_ID_VERSION)
			e->e.expr.type = &type_int;
		else
			e->e.expr.type = &type_float;
	} else if (op == '/' && !is_constant (e1)) {
		e2 = fold_constants (binary_expr ('/', new_float_expr (1), e2));
		e = fold_constants (binary_expr ('*', e1, e2));
	} else {
		e->e.expr.type = &type_quaternion;
	}

	if (op == '*' && is_float_val (e2) && expr_float (e2) == 1)
		return e1;
	if (op == '*' && is_float_val (e2) && expr_float (e2) == 0)
		return new_quaternion_expr (quat_origin);
	if (op == '/' && is_float_val (e2) && expr_float (e2) == 1)
		return e1;
	if (op == '/' && is_float_val (e2) && expr_float (e2) == 0)
		return error (e, "division by zero");
	if (op == '+' && is_constant (e1) && QuatIsZero (expr_quaternion (e1)))
		return e2;
	if (op == '+' && is_constant (e2) && QuatIsZero (expr_quaternion (e2)))
		return e1;
	if (op == '-' && is_constant (e2) && QuatIsZero (expr_quaternion (e2)))
		return e1;

	if (!is_constant (e1) || !is_constant (e2))
		return e;

	if (is_float_val (e1)) {
		QuatSet (0, 0, 0, expr_float (e1), float_quat);
		q2 = float_quat;
		q1 = expr_quaternion (e2);
	} else if (is_float_val (e2)) {
		QuatSet (0, 0, 0, expr_float (e2), float_quat);
		q2 = float_quat;
		q1 = expr_quaternion (e1);
	} else {
		q1 = expr_quaternion (e1);
		q2 = expr_quaternion (e2);
	}

	switch (op) {
		case '+':
			QuatAdd (q1, q2, q);
			e = new_quaternion_expr (q);
			break;
		case '-':
			QuatSubtract (q1, q2, q);
			e = new_quaternion_expr (q);
			break;
		case '/':
			if (is_float_val (e2)) {
				QuatScale (q1, 1 / expr_float (e2), q);
			} else {
				QuatInverse (q2, q);
				QuatScale (q2, expr_float (e1), q);
			}
			e = new_quaternion_expr (q);
			break;
		case '*':
			if (is_quaternion(get_type (e2))) {
				QuatMult (q1, q2, q);
			} else {
				QuatScale (q1, q2[3], q);
			}
			e = new_quaternion_expr (q);
			break;
		case EQ:
			e = cmp_result_expr (QuatCompare (q1, q2));
			break;
		case NE:
			e = cmp_result_expr (!QuatCompare (q1, q2));
			break;
		default:
			internal_error (e1, 0);
	}
	e->file = e1->file;
	e->line = e1->line;
	return e;
}

static expr_t *
do_op_int (int op, expr_t *e, expr_t *e1, expr_t *e2)
{
	int         isval1 = 0, isval2 = 0;
	int         val1 = 0, val2 = 0;
	static int  valid[] = {
		'+', '-', '*', '/', '&', '|', '^', '%',
		SHL, SHR, AND, OR, LT, GT, LE, GE, EQ, NE, 0
	};

	if (!valid_op (op, valid))
		return error (e1, "invalid operator for int");

	if (is_short_val (e1)) {
		isval1 = 1;
		val1 = expr_short (e1);
	}
	if (is_int_val (e1)) {
		isval1 = 1;
		val1 = expr_int (e1);
	}

	if (is_short_val (e2)) {
		isval2 = 1;
		val2 = expr_short (e2);
	}
	if (is_int_val (e2)) {
		isval2 = 1;
		val2 = expr_int (e2);
	}

	if (is_compare (op) || is_logic (op)) {
		if (options.code.progsversion > PROG_ID_VERSION)
			e->e.expr.type = &type_int;
		else
			e->e.expr.type = &type_float;
	} else {
		e->e.expr.type = &type_int;
	}

	if (op == '*' && isval1 && val1 == 1)
		return e2;
	if (op == '*' && isval2 && val2 == 1)
		return e1;
	if (op == '*' && isval1 && val1 == 0)
		return e1;
	if (op == '*' && isval2 && val2 == 0)
		return e2;
	if (op == '/' && isval2 && val2 == 1)
		return e1;
	if (op == '/' && isval2 && val2 == 0)
		return error (e, "division by zero");
	if (op == '/' && isval1 && val1 == 0)
		return e1;
	if (op == '+' && isval1 && val1 == 0)
		return e2;
	if (op == '+' && isval2 && val2 == 0)
		return e1;
	if (op == '-' && isval2 && val2 == 0)
		return e1;

	if (!isval1) {
		if (e1->type == ex_expr) {
			// at most one of the two sub-expressions is constant otherwise
			// e1 would be a constant
			if (is_constant (e1->e.expr.e1)) {
				if ((op == '*' && e1->e.expr.op == '*')
					|| (is_addsub (op) && is_addsub (e1->e.expr.op))) {
					expr_t     *c = binary_expr (op, e1->e.expr.e1, e2);
					e = binary_expr (e1->e.expr.op, c, e1->e.expr.e2);
				}
			} else if (is_constant (e1->e.expr.e2)) {
				if ((op == '*' && e1->e.expr.op == '*')
					|| (is_addsub (op) && e1->e.expr.op == '+')) {
					expr_t     *c = binary_expr (op, e1->e.expr.e2, e2);
					e = binary_expr (e1->e.expr.op, e1->e.expr.e1, c);
				} else if (is_addsub (op) && e1->e.expr.op == '-') {
					// must ivert op
					expr_t     *c = binary_expr (inv_addsub (op),
												 e1->e.expr.e2, e2);
					e = binary_expr (e1->e.expr.op, e1->e.expr.e1, c);
				}
			}
		}
		return e;
	} else if (!isval2) {
		if (e2->type == ex_expr) {
			// at most one of the two sub-expressions is constant otherwise
			// e2 would be a constant
			if (is_constant (e2->e.expr.e1)) {
				if ((op == '*' && e2->e.expr.op == '*')
					|| (op == '+' && is_addsub (e2->e.expr.op))) {
					expr_t     *c = binary_expr (op, e1, e2->e.expr.e1);
					e = binary_expr (e2->e.expr.op, c, e2->e.expr.e2);
				} else if (op == '-' && is_addsub (e2->e.expr.op)) {
					expr_t     *c = binary_expr (op, e1, e2->e.expr.e1);
					c = fold_constants (c);
					e = binary_expr (inv_addsub (e2->e.expr.op),
									 c, e2->e.expr.e2);
				}
			} else if (is_constant (e2->e.expr.e2)) {
				if ((op == '*' && e2->e.expr.op == '*')
					|| (op == '+' && is_addsub (e2->e.expr.op))) {
					expr_t     *c = binary_expr (e2->e.expr.op,
												 e1, e2->e.expr.e2);
					e = binary_expr (op, c, e2->e.expr.e1);
				} else if (op == '-' && is_addsub (e2->e.expr.op)) {
					expr_t     *c = binary_expr (inv_addsub (e2->e.expr.op),
												 e1, e2->e.expr.e2);
					e = binary_expr (op, c, e2->e.expr.e1);
				}
			}
		}
		return e;
	}
	if (!isval1 || !isval2)
		return e;

	switch (op) {
		case '+':
			e = new_int_expr (val1 + val2);
			break;
		case '-':
			e = new_int_expr (val1 - val2);
			break;
		case '*':
			e = new_int_expr (val1 * val2);
			break;
		case '/':
			if (options.warnings.integer_divide)
				warning (e2, "%d / %d == %d", val1, val2, val1 / val2);
			e = new_int_expr (val1 / val2);
			break;
		case '&':
			e = new_int_expr (val1 & val2);
			break;
		case '|':
			e = new_int_expr (val1 | val2);
			break;
		case '^':
			e = new_int_expr (val1 ^ val2);
			break;
		case '%':
			e = new_int_expr (val1 % val2);
			break;
		case SHL:
			e = new_int_expr (val1 << val2);
			break;
		case SHR:
			e = new_int_expr (val1 >> val2);
			break;
		case AND:
			e = cmp_result_expr (val1 && val2);
			break;
		case OR:
			e = cmp_result_expr (val1 || val2);
			break;
		case LT:
			e = cmp_result_expr (val1 < val2);
			break;
		case GT:
			e = cmp_result_expr (val1 > val2);
			break;
		case LE:
			e = cmp_result_expr (val1 <= val2);
			break;
		case GE:
			e = cmp_result_expr (val1 >= val2);
			break;
		case EQ:
			e = cmp_result_expr (val1 == val2);
			break;
		case NE:
			e = cmp_result_expr (val1 != val2);
			break;
		default:
			internal_error (e1, 0);
	}
	e->file = e1->file;
	e->line = e1->line;
	return e;
}

static expr_t *
do_op_uint (int op, expr_t *e, expr_t *e1, expr_t *e2)
{
	return e;
}

static expr_t *
do_op_short (int op, expr_t *e, expr_t *e1, expr_t *e2)
{
	short       i1, i2;
	static int  valid[] = {
		'+', '-', '*', '/', '&', '|', '^', '%',
		SHL, SHR, AND, OR, LT, GT, LE, GE, EQ, NE, 0
	};

	if (!valid_op (op, valid))
		return error (e1, "invalid operator for short");

	if (is_compare (op) || is_logic (op)) {
		if (options.code.progsversion > PROG_ID_VERSION)
			e->e.expr.type = &type_int;
		else
			e->e.expr.type = &type_float;
	} else {
		e->e.expr.type = &type_short;
	}

	if (!is_constant (e1) || !is_constant (e2))
		return e;

	i1 = expr_short (e1);
	i2 = expr_short (e2);

	switch (op) {
		case '+':
			e = new_short_expr (i1 + i2);
			break;
		case '-':
			e = new_short_expr (i1 - i2);
			break;
		case '*':
			e = new_short_expr (i1 * i2);
			break;
		case '/':
			if (options.warnings.integer_divide)
				warning (e2, "%d / %d == %d", i1, i2, i1 / i2);
			e = new_short_expr (i1 / i2);
			break;
		case '&':
			e = new_short_expr (i1 & i2);
			break;
		case '|':
			e = new_short_expr (i1 | i2);
			break;
		case '^':
			e = new_short_expr (i1 ^ i2);
			break;
		case '%':
			e = new_short_expr (i1 % i2);
			break;
		case SHL:
			e = new_short_expr (i1 << i2);
			break;
		case SHR:
			e = new_short_expr (i1 >> i2);
			break;
		case AND:
			e = new_short_expr (i1 && i2);
			break;
		case OR:
			e = new_short_expr (i1 || i2);
			break;
		case LT:
			e = cmp_result_expr (i1 < i2);
			break;
		case GT:
			e = cmp_result_expr (i1 > i2);
			break;
		case LE:
			e = cmp_result_expr (i1 <= i2);
			break;
		case GE:
			e = cmp_result_expr (i1 >= i2);
			break;
		case EQ:
			e = cmp_result_expr (i1 == i2);
			break;
		case NE:
			e = cmp_result_expr (i1 != i2);
			break;
		default:
			internal_error (e1, 0);
	}
	e->file = e1->file;
	e->line = e1->line;
	return e;
}

static expr_t *
do_op_struct (int op, expr_t *e, expr_t *e1, expr_t *e2)
{
	return error (e1, "invalid operator for struct");
}

static expr_t *
do_op_compound (int op, expr_t *e, expr_t *e1, expr_t *e2)
{
	type_t     *t1 = get_type (e1);
	type_t     *t2 = get_type (e2);
	if (is_struct (t1) && is_struct (t2))
		return do_op_struct (op, e, e1, e2);
	if (is_scalar (t1) && is_scalar (t2)) {
		if (is_enum (t1)) {
			if (t2->type == ev_double)
				return do_op_float (op, e, e1, e2);
			if (t2->type == ev_double)
				return do_op_float (op, e, e1, e2);
			return do_op_int (op, e, e1, e2);
		}
		if (is_enum (t2)) {
			if (t1->type == ev_double)
				return do_op_double (op, e, e1, e2);
			if (t1->type == ev_float)
				return do_op_float (op, e, e1, e2);
			return do_op_int (op, e, e1, e2);
		}
	}
	return error (e1, "invalid operator for compound");
}

static operation_t *do_op[ev_type_count];
static expr_t *
do_op_invalid (int op, expr_t *e, expr_t *e1, expr_t *e2)
{
	type_t     *t1 = get_type (e1);
	type_t     *t2 = get_type (e2);

	if (is_scalar (t1) && is_scalar (t2)) {
		// one or both expressions are an enum, and the other is one of
		// int, float or short. Treat the enum as the other type, or as
		// the default type if both are enum.
		etype_t     t;
		if (!is_enum (t1))
			t = t1->type;
		else if (!is_enum (t2))
			t = t2->type;
		else
			t = type_default->type;
		return do_op[t][t] (op, e, e1, e2);
	} else {
		dstring_t  *enc1 = dstring_newstr ();
		dstring_t  *enc2 = dstring_newstr ();

		print_type_str (enc1, t1);
		print_type_str (enc2, t2);

		//print_expr (e);
		e1 = error (e1, "invalid operands for binary %s: %s %s",
					get_op_string (op), enc1->str, enc2->str);
		dstring_delete (enc1);
		dstring_delete (enc2);
		return e1;
	}
}

static operation_t op_void[ev_type_count] = {
    [ev_void]       = do_op_invalid,
    [ev_string]     = do_op_invalid,
    [ev_float]      = do_op_invalid,
    [ev_vector]     = do_op_invalid,
    [ev_entity]     = do_op_invalid,
    [ev_field]      = do_op_invalid,
    [ev_func]       = do_op_invalid,
    [ev_ptr]        = do_op_invalid,
    [ev_quaternion] = do_op_invalid,
    [ev_int]        = do_op_invalid,
    [ev_uint]       = do_op_invalid,
    [ev_short]      = do_op_invalid,
    [ev_double]     = do_op_invalid,
    [ev_long]       = 0,
    [ev_ulong]      = 0,
    [ev_invalid]    = do_op_invalid,
};

static operation_t op_string[ev_type_count] = {
    [ev_void]       = do_op_invalid,
    [ev_string]     = do_op_string,
    [ev_float]      = do_op_invalid,
    [ev_vector]     = do_op_invalid,
    [ev_entity]     = do_op_invalid,
    [ev_field]      = do_op_invalid,
    [ev_func]       = do_op_invalid,
    [ev_ptr]        = do_op_invalid,
    [ev_quaternion] = do_op_invalid,
    [ev_int]        = do_op_invalid,
    [ev_uint]       = do_op_invalid,
    [ev_short]      = do_op_invalid,
    [ev_double]     = do_op_invalid,
    [ev_long]       = 0,
    [ev_ulong]      = 0,
    [ev_invalid]    = do_op_invalid,
};

static operation_t op_float[ev_type_count] = {
    [ev_void]       = do_op_invalid,
    [ev_string]     = do_op_invalid,
    [ev_float]      = do_op_float,
    [ev_vector]     = do_op_vector,
    [ev_entity]     = do_op_invalid,
    [ev_field]      = do_op_invalid,
    [ev_func]       = do_op_invalid,
    [ev_ptr]        = do_op_invalid,
    [ev_quaternion] = do_op_quaternion,
    [ev_int]        = do_op_float,
    [ev_uint]       = do_op_float,
    [ev_short]      = do_op_float,
    [ev_double]     = do_op_double,
    [ev_long]       = 0,
    [ev_ulong]      = 0,
    [ev_invalid]    = do_op_invalid,
};

static operation_t op_vector[ev_type_count] = {
    [ev_void]       = do_op_invalid,
    [ev_string]     = do_op_invalid,
    [ev_float]      = do_op_vector,
    [ev_vector]     = do_op_vector,
    [ev_entity]     = do_op_invalid,
    [ev_field]      = do_op_invalid,
    [ev_func]       = do_op_invalid,
    [ev_ptr]        = do_op_invalid,
    [ev_quaternion] = do_op_invalid,
    [ev_int]        = do_op_vector,
    [ev_uint]       = do_op_vector,
    [ev_short]      = do_op_vector,
    [ev_double]     = do_op_vector,
    [ev_long]       = 0,
    [ev_ulong]      = 0,
    [ev_invalid]    = do_op_invalid,
};

static operation_t op_entity[ev_type_count] = {
    [ev_void]       = do_op_invalid,
    [ev_string]     = do_op_invalid,
    [ev_float]      = do_op_invalid,
    [ev_vector]     = do_op_invalid,
    [ev_entity]     = do_op_entity,
    [ev_field]      = do_op_entity,
    [ev_func]       = do_op_invalid,
    [ev_ptr]        = do_op_invalid,
    [ev_quaternion] = do_op_invalid,
    [ev_int]        = do_op_invalid,
    [ev_uint]       = do_op_invalid,
    [ev_short]      = do_op_invalid,
    [ev_double]     = do_op_invalid,
    [ev_long]       = 0,
    [ev_ulong]      = 0,
    [ev_invalid]    = do_op_invalid,
};

static operation_t op_field[ev_type_count] = {
    [ev_void]       = do_op_invalid,
    [ev_string]     = do_op_invalid,
    [ev_float]      = do_op_invalid,
    [ev_vector]     = do_op_invalid,
    [ev_entity]     = do_op_invalid,
    [ev_field]      = do_op_field,
    [ev_func]       = do_op_invalid,
    [ev_ptr]        = do_op_invalid,
    [ev_quaternion] = do_op_invalid,
    [ev_int]        = do_op_invalid,
    [ev_uint]       = do_op_invalid,
    [ev_short]      = do_op_invalid,
    [ev_double]     = do_op_invalid,
    [ev_long]       = 0,
    [ev_ulong]      = 0,
    [ev_invalid]    = do_op_invalid,
};

static operation_t op_func[ev_type_count] = {
    [ev_void]       = do_op_func,
    [ev_string]     = do_op_func,
    [ev_float]      = do_op_func,
    [ev_vector]     = do_op_func,
    [ev_entity]     = do_op_func,
    [ev_field]      = do_op_func,
    [ev_func]       = do_op_func,
    [ev_ptr]        = do_op_func,
    [ev_quaternion] = do_op_func,
    [ev_int]        = do_op_func,
    [ev_uint]       = do_op_func,
    [ev_short]      = do_op_func,
    [ev_double]     = do_op_func,
    [ev_long]       = 0,
    [ev_ulong]      = 0,
    [ev_invalid]    = do_op_func,
};

static operation_t op_pointer[ev_type_count] = {
    [ev_void]       = do_op_pointer,
    [ev_string]     = do_op_pointer,
    [ev_float]      = do_op_pointer,
    [ev_vector]     = do_op_pointer,
    [ev_entity]     = do_op_pointer,
    [ev_field]      = do_op_pointer,
    [ev_func]       = do_op_pointer,
    [ev_ptr]        = do_op_pointer,
    [ev_quaternion] = do_op_pointer,
    [ev_int]        = do_op_pointer,
    [ev_uint]       = do_op_pointer,
    [ev_short]      = do_op_pointer,
    [ev_double]     = do_op_pointer,
    [ev_long]       = 0,
    [ev_ulong]      = 0,
    [ev_invalid]    = do_op_pointer,
};

static operation_t op_quaternion[ev_type_count] = {
    [ev_void]       = do_op_invalid,
    [ev_string]     = do_op_invalid,
    [ev_float]      = do_op_quaternion,
    [ev_vector]     = do_op_quatvect,
    [ev_entity]     = do_op_invalid,
    [ev_field]      = do_op_invalid,
    [ev_func]       = do_op_invalid,
    [ev_ptr]        = do_op_invalid,
    [ev_quaternion] = do_op_quaternion,
    [ev_int]        = do_op_quaternion,
    [ev_uint]       = do_op_quaternion,
    [ev_short]      = do_op_quaternion,
    [ev_double]     = do_op_quaternion,
    [ev_long]       = 0,
    [ev_ulong]      = 0,
    [ev_invalid]    = do_op_invalid,
};

static operation_t op_int[ev_type_count] = {
    [ev_void]       = do_op_invalid,
    [ev_string]     = do_op_invalid,
    [ev_float]      = do_op_float,
    [ev_vector]     = do_op_vector,
    [ev_entity]     = do_op_invalid,
    [ev_field]      = do_op_invalid,
    [ev_func]       = do_op_invalid,
    [ev_ptr]        = do_op_invalid,
    [ev_quaternion] = do_op_quaternion,
    [ev_int]        = do_op_int,
    [ev_uint]       = do_op_uint,
    [ev_short]      = do_op_int,
    [ev_double]     = do_op_double,
    [ev_long]       = 0,
    [ev_ulong]      = 0,
    [ev_invalid]    = do_op_invalid,
};

static operation_t op_uint[ev_type_count] = {
    [ev_void]       = do_op_invalid,
    [ev_string]     = do_op_invalid,
    [ev_float]      = do_op_float,
    [ev_vector]     = do_op_vector,
    [ev_entity]     = do_op_invalid,
    [ev_field]      = do_op_invalid,
    [ev_func]       = do_op_invalid,
    [ev_ptr]        = do_op_invalid,
    [ev_quaternion] = do_op_quaternion,
    [ev_int]        = do_op_uint,
    [ev_uint]       = do_op_uint,
    [ev_short]      = do_op_uint,
    [ev_double]     = do_op_double,
    [ev_long]       = 0,
    [ev_ulong]      = 0,
    [ev_invalid]    = do_op_invalid,
};

static operation_t op_short[ev_type_count] = {
    [ev_void]       = do_op_invalid,
    [ev_string]     = do_op_invalid,
    [ev_float]      = do_op_float,
    [ev_vector]     = do_op_vector,
    [ev_entity]     = do_op_invalid,
    [ev_field]      = do_op_invalid,
    [ev_func]       = do_op_invalid,
    [ev_ptr]        = do_op_invalid,
    [ev_quaternion] = do_op_quaternion,
    [ev_int]        = do_op_int,
    [ev_uint]       = do_op_uint,
    [ev_short]      = do_op_short,
    [ev_double]     = do_op_double,
    [ev_long]       = 0,
    [ev_ulong]      = 0,
    [ev_invalid]    = do_op_invalid,
};

static operation_t op_double[ev_type_count] = {
    [ev_void]       = do_op_invalid,
    [ev_string]     = do_op_invalid,
    [ev_float]      = do_op_float,
    [ev_vector]     = do_op_vector,
    [ev_entity]     = do_op_invalid,
    [ev_field]      = do_op_invalid,
    [ev_func]       = do_op_invalid,
    [ev_ptr]        = do_op_invalid,
    [ev_quaternion] = do_op_quaternion,
    [ev_int]        = do_op_int,
    [ev_uint]       = do_op_uint,
    [ev_short]      = do_op_short,
    [ev_double]     = do_op_double,
    [ev_long]       = 0,
    [ev_ulong]      = 0,
    [ev_invalid]    = do_op_invalid,
};

static operation_t op_compound[ev_type_count] = {
    [ev_void]       = do_op_invalid,
    [ev_string]     = do_op_invalid,
    [ev_float]      = do_op_compound,
    [ev_vector]     = do_op_invalid,
    [ev_entity]     = do_op_invalid,
    [ev_field]      = do_op_invalid,
    [ev_func]       = do_op_invalid,
    [ev_ptr]        = do_op_invalid,
    [ev_quaternion] = do_op_invalid,
    [ev_int]        = do_op_compound,
    [ev_uint]       = do_op_compound,
    [ev_short]      = do_op_compound,
    [ev_double]     = do_op_compound,
    [ev_long]       = do_op_compound,
    [ev_ulong]      = do_op_compound,
    [ev_invalid]    = do_op_compound,
};

static operation_t *do_op[ev_type_count] = {
    [ev_void]       = op_void,
    [ev_string]     = op_string,
    [ev_float]      = op_float,
    [ev_vector]     = op_vector,
    [ev_entity]     = op_entity,
    [ev_field]      = op_field,
    [ev_func]       = op_func,
    [ev_ptr]        = op_pointer,
    [ev_quaternion] = op_quaternion,
    [ev_int]        = op_int,
    [ev_uint]       = op_uint,
    [ev_short]      = op_short,
    [ev_double]     = op_double,
    [ev_long]       = 0,
    [ev_ulong]      = 0,
    [ev_invalid]    = op_compound,
};

static unaryop_t do_unary_op[ev_type_count];
static expr_t *
uop_invalid (int op, expr_t *e, expr_t *e1)
{
	type_t     *t1 = get_type (e1);
	if (is_scalar (t1)) {
		// The expression is an enum. Treat the enum as the default type.
		etype_t     t;
		t = type_default->type;
		return do_unary_op[t] (op, e, e1);
	} else {
		dstring_t  *enc1 = dstring_newstr ();

		print_type_str (enc1, t1);

		//print_expr (e);
		e1 = error (e1, "invalid operand for unary %s: %s",
					get_op_string (op), enc1->str);
		dstring_delete (enc1);
		return e1;
	}
}

static expr_t *
uop_string (int op, expr_t *e, expr_t *e1)
{
	// + - ! ~ *
	static int  valid[] = { '!', 0 };
	const char *s;

	if (!valid_op (op, valid))
		return error (e1, "invalid unary operator for string: %s",
					  get_op_string (op));

	if (!is_constant (e1))
		return e;

	s = expr_string (e1);
	return cmp_result_expr (!s || !s[0]);
}

static expr_t *
uop_float (int op, expr_t *e, expr_t *e1)
{
	static int  valid[] = { '+', '-', '!', '~', 'C', 0 };
	type_t     *type;

	if (!valid_op (op, valid))
		return error (e1, "invalid unary operator for float: %s",
					  get_op_string (op));
	if (op == '+')
		return e1;
	type = get_type (e);
	if (op == 'C' && !is_int(type) && !is_double(type))
		return error (e1, "invalid cast of float");
	if (!is_constant (e1))
		return e;
	switch (op) {
		case '-':
			return new_float_expr (-expr_float (e1));
		case '!':
			print_type (get_type (e));
			return cmp_result_expr (!expr_float (e1));
		case '~':
			return new_float_expr (~(int) expr_float (e1));
		case 'C':
			if (is_int(type)) {
				return new_int_expr (expr_float (e1));
			} else {
				return new_double_expr (expr_float (e1));
			}
	}
	internal_error (e, "float unary op blew up");
}

static expr_t *
uop_vector (int op, expr_t *e, expr_t *e1)
{
	static int  valid[] = { '+', '-', '!', 0 };
	vec3_t      v;

	if (!valid_op (op, valid))
		return error (e1, "invalid unary operator for vector: %s",
					  get_op_string (op));
	if (op == '+')
		return e1;
	if (!is_constant (e1))
		return e;
	switch (op) {
		case '-':
			VectorNegate (expr_vector (e), v);
			return new_vector_expr (v);
		case '!':
			return cmp_result_expr (!VectorIsZero (expr_vector (e1)));
	}
	internal_error (e, "vector unary op blew up");
}

static expr_t *
uop_entity (int op, expr_t *e, expr_t *e1)
{
	static int  valid[] = { '!', 0 };

	if (!valid_op (op, valid))
		return error (e1, "invalid unary operator for entity: %s",
					  get_op_string (op));
	if (!is_constant (e1))
		return e;
	switch (op) {
		case '!':
			internal_error (e, "!entity");
	}
	internal_error (e, "entity unary op blew up");
}

static expr_t *
uop_field (int op, expr_t *e, expr_t *e1)
{
	static int  valid[] = { '!', 0 };

	if (!valid_op (op, valid))
		return error (e1, "invalid unary operator for field: %s",
					  get_op_string (op));
	if (!is_constant (e1))
		return e;
	switch (op) {
		case '!':
			internal_error (e, "!field");
	}
	internal_error (e, "field unary op blew up");
}

static expr_t *
uop_func (int op, expr_t *e, expr_t *e1)
{
	static int  valid[] = { '!', 0 };

	if (!valid_op (op, valid))
		return error (e1, "invalid unary operator for func: %s",
					  get_op_string (op));
	if (!is_constant (e1))
		return e;
	switch (op) {
		case '!':
			internal_error (e, "!func");
	}
	internal_error (e, "func unary op blew up");
}

static expr_t *
uop_pointer (int op, expr_t *e, expr_t *e1)
{
	static int  valid[] = { '!', '.', 0 };

	if (!valid_op (op, valid))
		return error (e1, "invalid unary operator for pointer: %s",
					  get_op_string (op));
	if (!is_constant (e1))
		return e;
	switch (op) {
		case '!':
			internal_error (e, "!pointer");
		case '.':
			debug (e, ".pointer");
			return e;
	}
	internal_error (e, "pointer unary op blew up");
}

static expr_t *
uop_quaternion (int op, expr_t *e, expr_t *e1)
{
	static int  valid[] = { '+', '-', '!', '~', 0 };
	quat_t      q;

	if (!valid_op (op, valid))
		return error (e1, "invalid unary operator for quaternion: %s",
					  get_op_string (op));
	if (op == '+')
		return e1;
	if (!is_constant (e1))
		return e;
	switch (op) {
		case '-':
			QuatNegate (expr_vector (e), q);
			return new_quaternion_expr (q);
		case '!':
			return cmp_result_expr (!QuatIsZero (expr_quaternion (e1)));
		case '~':
			QuatConj (expr_vector (e), q);
			return new_quaternion_expr (q);
	}
	internal_error (e, "quaternion unary op blew up");
}

static expr_t *
uop_int (int op, expr_t *e, expr_t *e1)
{
	static int  valid[] = { '+', '-', '!', '~', 'C', 0 };

	if (!valid_op (op, valid))
		return error (e1, "invalid unary operator for int: %s",
					  get_op_string (op));
	if (op == '+')
		return e1;
	if (op == 'C' && !is_float(get_type (e)))
		return error (e1, "invalid cast of int");
	if (!is_constant (e1))
		return e;
	switch (op) {
		case '-':
			return new_int_expr (-expr_int (e1));
		case '!':
			return cmp_result_expr (!expr_int (e1));
		case '~':
			return new_int_expr (~expr_int (e1));
		case 'C':
			return new_float_expr (expr_int (e1));
	}
	internal_error (e, "int unary op blew up");
}

static expr_t *
uop_uint (int op, expr_t *e, expr_t *e1)
{
	static int  valid[] = { '+', '-', '!', '~', 0 };

	if (!valid_op (op, valid))
		return error (e1, "invalid unary operator for uint: %s",
					  get_op_string (op));
	if (op == '+')
		return e1;
	if (!is_constant (e1))
		return e;
	switch (op) {
		case '-':
			return new_uint_expr (-expr_uint (e1));
		case '!':
			return cmp_result_expr (!expr_uint (e1));
		case '~':
			return new_uint_expr (~expr_uint (e1));
	}
	internal_error (e, "uint unary op blew up");
}

static expr_t *
uop_short (int op, expr_t *e, expr_t *e1)
{
	static int  valid[] = { '+', '-', '!', '~', 0 };

	if (!valid_op (op, valid))
		return error (e1, "invalid unary operator for short: %s",
					  get_op_string (op));
	if (op == '+')
		return e1;
	if (!is_constant (e1))
		return e;
	switch (op) {
		case '-':
			return new_short_expr (-expr_short (e1));
		case '!':
			return cmp_result_expr (!expr_short (e1));
		case '~':
			return new_short_expr (~expr_short (e1));
	}
	internal_error (e, "short unary op blew up");
}

static expr_t *
uop_double (int op, expr_t *e, expr_t *e1)
{
	static int  valid[] = { '+', '-', '!', 'C', 0 };
	type_t     *type;

	if (!valid_op (op, valid))
		return error (e1, "invalid unary operator for double: %s",
					  get_op_string (op));
	if (op == '+')
		return e1;
	type = get_type (e);
	if (op == 'C' && !is_int(type) && !is_float(type))
		return error (e1, "invalid cast of double");
	if (!is_constant (e1))
		return e;
	switch (op) {
		case '-':
			return new_double_expr (-expr_double (e1));
		case '!':
			print_type (get_type (e));
			return cmp_result_expr (!expr_double (e1));
		case 'C':
			if (is_int(type)) {
				return new_int_expr (expr_double (e1));
			} else {
				return new_float_expr (expr_double (e1));
			}
	}
	internal_error (e, "float unary op blew up");
}

static expr_t *
uop_compound (int op, expr_t *e, expr_t *e1)
{
	type_t     *t1 = get_type (e1);

	if (is_scalar (t1)) {
		if (is_enum (t1)) {
			return uop_int (op, e, e1);
		}
	}
	return error (e1, "invalid operand for unary %s", get_op_string (op));
}

static unaryop_t do_unary_op[ev_type_count] = {
	uop_invalid,						// ev_void
	uop_string,							// ev_string
	uop_float,							// ev_float
	uop_vector,							// ev_vector
	uop_entity,							// ev_entity
	uop_field,							// ev_field
	uop_func,							// ev_func
	uop_pointer,						// ev_ptr
	uop_quaternion,						// ev_quaternion
	uop_int,							// ev_int
	uop_uint,							// ev_uint
	uop_short,							// ev_short
	uop_double,							// ev_double
	uop_compound,						// ev_invalid
};

expr_t *
fold_constants (expr_t *e)
{
	int         op;
	expr_t     *e1, *e2;
	etype_t     t1, t2;

	if (e->type == ex_uexpr) {
		e1 = e->e.expr.e1;
		if (!e1) {
			return e;
		}
		op = e->e.expr.op;
		t1 = extract_type (e1);
		if (t1 >= ev_type_count || !do_unary_op[t1]) {
			print_expr (e);
			internal_error (e, "invalid type: %d", t1);
		}
		return do_unary_op[t1] (op, e, e1);
	} else if (e->type == ex_expr) {
		e1 = e->e.expr.e1;
		e2 = e->e.expr.e2;
		if (!is_constant (e1) && !is_constant (e2)) {
			return e;
		}

		op = e->e.expr.op;

		t1 = extract_type (e1);
		t2 = extract_type (e2);

		if (t1 >= ev_type_count || t2 >= ev_type_count
			|| !do_op[t1] || !do_op[t1][t2])
			internal_error (e, "invalid type %d %d", t1, t2);
		return do_op[t1][t2] (op, e, e1, e2);
	}
	return e;
}
