/*
 * expr.c  -  Support routines for handling expressions
 *
 * Copyright (C) 1997-2007 Gero Kuhlmann   <gero@gkminix.han.de>
 *
 *  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
 *  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.
 *
 * $Id: expr.c,v 1.17 2007/01/06 18:31:27 gkminix Exp $
 */

#include "mknbi.h"
#include "mgl.h"



/*
 *****************************************************************************
 *
 * Delete whole expression tree
 */
void delexpr __F((ep), struct expr *ep)
{
  int i;

  if (ep != NULL) {
	/* Delete any possible string constant */
	if (isconst(ep) && exprtype(ep) == EXPR_STRING &&
	    ep->spec.cval.val.s != NULL)
		free(ep->spec.cval.val.s);

	/* Delete any possible IP address constant */
	if (isconst(ep) && exprtype(ep) == EXPR_IPADDR &&
	    ep->spec.cval.val.a != NULL)
		free(ep->spec.cval.val.a);

	/* Delete all variable info records */
	if (isvariable(ep)) {
		struct varinfo *vp1, *vp2;

		vp1 = ep->spec.var;
		while (vp1 != NULL) {
			if (vp1->cmd == arrayindex)
				delexpr(vp1->spec.index);
			vp2 = vp1->next;
			free(vp1);
			vp1 = vp2;
		}
	}

	/* Release any temporary variable */
	if (istempvar(ep))
		releasetmpvar(ep);

	/* Delete all subexpressions */
	for (i = 0; i < ep->exprnum; i++)
		delexpr(ep->exprlist[i]);
	free(ep);
  }
}



/*
 *****************************************************************************
 *
 * Return the ordinal number of a scalar expression
 */
int getord __F((ep), const struct expr *ep)
{
  int ret = 0;

#ifdef PARANOID
  if (!isconst(ep))
	interror(20, "cannot determine ordinal value from non-constant");
#endif

  switch (exprtype(ep)) {
	case EXPR_NUM:
		ret = ep->spec.cval.val.i;
		break;
	case EXPR_BOOL:
		ret = (ep->spec.cval.val.b ? 1 : 0);
		break;
	case EXPR_CHAR:
		ret = (int)(ep->spec.cval.val.c) & 0xff;
		break;
	case EXPR_ENUM:
		ret = ep->spec.cval.val.e;
		break;
#ifdef PARANOID
	default:
		interror(38, "cannot determine ordinal value from non-scalar");
		break;
#endif
  }
  return(ret);
}



/*
 *****************************************************************************
 *
 * Generate a new constant numeric expression
 */
static struct expr *newconst __F((constval, consttype),
				int constval AND
				struct typesdef *consttype)
{
  struct expr *ep;

#ifdef PARANOID
  if (!isscalar(consttype))
	interror(6, "new constant type is not scalar");
#endif

  /* Check scalar boundaries */
  if (constval < consttype->def.s.min) {
	warning("scalar underflow");
	constval = consttype->def.s.min;
  } else if (constval > consttype->def.s.max) {
	warning("scalar overflow");
	constval = consttype->def.s.max;
  }

  /* Create new constant expression */
  ep = (struct expr *)nbmalloc(sizeof(struct expr));
  ep->opcode = CMD_CONST;
  ep->type = consttype;
  ep->exprnum = 0;
  ep->spec.cval.t = consttype;
  switch (consttype->type) {
	case EXPR_NUM:
		ep->spec.cval.val.i = constval;
		break;
	case EXPR_BOOL:
		ep->spec.cval.val.b = constval;
		break;
	case EXPR_CHAR:
		ep->spec.cval.val.c = constval;
		break;
	case EXPR_ENUM:
		ep->spec.cval.val.e = constval;
		break;
	default:
		break;
  }
  return(ep);
}



/*
 *****************************************************************************
 *
 * Compare two strings
 */
static int str_compare __F((op, leftstr, rightstr),
				int op AND
				const byte_t *leftstr AND
				const byte_t *rightstr)
{
  size_t i;
  int diff;

#ifdef PARANOID
  if (leftstr == NULL || rightstr == NULL)
	interror(2, "NULL strings in expression");
#endif

  /* Compare both strings */
  for (i = 1; i <= (size_t)(leftstr[0]); i++)
	if (i > (size_t)(rightstr[0]) || leftstr[i] != rightstr[i])
		break;
  if (i > (size_t)(rightstr[0]))
	diff = -1;
  else
	diff = (int)(leftstr[i]) - (int)(rightstr[i]);

  /* Prepare return value */
  switch(op) {
	case CMD_EQ:	return(diff == 0 ? TRUE : FALSE);
	case CMD_GT:	return(diff >  0 ? TRUE : FALSE);
	case CMD_GE:	return(diff >= 0 ? TRUE : FALSE);
	case CMD_LT:	return(diff <  0 ? TRUE : FALSE);
	case CMD_LE:	return(diff <= 0 ? TRUE : FALSE);
	case CMD_NE:	return(diff != 0 ? TRUE : FALSE);
  }
  return(FALSE);
}



/*
 *****************************************************************************
 *
 * Compare two constant memory blocks
 */
__attribute__((pure)) static int mem_compare __F((op, len, leftmem, rightmem),
				int op AND
				size_t len AND
				const byte_t *leftmem AND
				const byte_t *rightmem)
{
  size_t i;
  int diff;

#ifdef PARANOID
  if (leftmem == NULL || rightmem == NULL)
	interror(4, "NULL pointers in expression");
#endif

  /* Compare both memory blocks */
  for (i = 0; i < len; i++)
	if (leftmem[i] != rightmem[i])
		break;
  if (i == len)
	diff = 0;
  else
	diff = (int)(leftmem[i]) - (int)(rightmem[i]);

  /* Prepare return value */
  switch(op) {
	case CMD_EQ:	return(diff == 0 ? TRUE : FALSE);
	case CMD_GT:	return(diff >  0 ? TRUE : FALSE);
	case CMD_GE:	return(diff >= 0 ? TRUE : FALSE);
	case CMD_LT:	return(diff <  0 ? TRUE : FALSE);
	case CMD_LE:	return(diff <= 0 ? TRUE : FALSE);
	case CMD_NE:	return(diff != 0 ? TRUE : FALSE);
  }
  return(FALSE);
}



/*
 *****************************************************************************
 *
 * Compare two integers
 */
__attribute__((const)) static int int_compare __F((op, leftint, rightint),
				int op AND
				int leftint AND
				int rightint)
{
  switch(op) {
	case CMD_EQ:	return(leftint == rightint ? TRUE : FALSE);
	case CMD_GT:	return(leftint >  rightint ? TRUE : FALSE);
	case CMD_GE:	return(leftint >= rightint ? TRUE : FALSE);
	case CMD_LT:	return(leftint <  rightint ? TRUE : FALSE);
	case CMD_LE:	return(leftint <= rightint ? TRUE : FALSE);
	case CMD_NE:	return(leftint != rightint ? TRUE : FALSE);
  }
  return(FALSE);
}



/*
 *****************************************************************************
 *
 * Collapse a runtime function. It returns TRUE if the function could get
 * collapsed. This handles only one node!
 */
static struct expr *rtfunc_collapse __F((ep), struct expr *ep)
{
  size_t destsize, l;
  byte_t *tmpstr;
  struct sym *sp;
  int opcode, boolval, isstr;
#ifdef PARANOID
  int i;
#endif

  /* Find the runtime function opcode */
  for (opcode = 0; opcode < RTSYM_NUM; opcode++)
	if (rtsymbols[opcode] == ep->spec.func)
		break;

  /* Check all expression arguments */
  sp = NULL;
  if (opcode < RTSYM_NUM) {
	sp = rtsymbols[opcode];
#ifdef PARANOID
	if (!isfuncsym(sp) ||
	    sp->def.f.argnum != ep->exprnum ||
	    (!isanytype(sp->def.f.ret) && sp->def.f.ret->type != exprtype(ep)))
		interror(8, "invalid runtime function call");
	for (i = 0; i < sp->def.f.argnum; i++)
		if (!isanytype(sp->def.f.args[i].t) &&
		    sp->def.f.args[i].t->type != exprtype(ep->exprlist[i]))
			interror(5, "invalid expression types");
#endif
  }
  if (sp == NULL)
	return(ep);

  /* If the result is a string, we need to determine the destination size */
  destsize = 0;
  if (exprtype(ep) == EXPR_STRING)
	destsize = ep->type->size - 1;

  /* Now handle some of the runtime functions */
  isstr = TRUE;
  boolval = FALSE;
  switch (opcode) {

	case RTCMD_STRCATCC:
	case RTCMD_STRCATCS:
	case RTCMD_STRCATSC:
	case RTCMD_STRCAT:
	  {
		/* String concatenation */
		size_t leftlen, rightlen;
		byte_t leftbuf[2], rightbuf[2];
		byte_t *leftstr, *rightstr;

		/* Make a string out of a character constant on the left side */
		leftstr = NULL;
		if (exprtype(ep->left) == EXPR_CHAR) {
			leftbuf[0] = 1;
			leftbuf[1] = ep->left->spec.cval.val.c;
			leftstr = leftbuf;
		} else
			leftstr = ep->left->spec.cval.val.s;

		/* Make a string out of a character constant on the right side */
		rightstr = NULL;
		if (exprtype(ep->right) == EXPR_CHAR) {
			rightbuf[0] = 1;
			rightbuf[1] = ep->right->spec.cval.val.c;
			rightstr = rightbuf;
		} else
			rightstr = ep->right->spec.cval.val.s;

		/* Adjust string length */
		leftlen = (size_t)(leftstr[0]);
		rightlen = (size_t)(rightstr[0]);
		if (leftlen + rightlen > destsize) {
			warning("string too long for concatenation, truncating");
			if (leftlen > destsize)
				leftlen = destsize;
			rightlen = destsize - leftlen;
		}

		/* Concatenate both strings */
		l = leftlen + rightlen;
		tmpstr = (byte_t *)nbmalloc((l + 1) * sizeof(byte_t));
		tmpstr[0] = (byte_t)l;
		while (rightlen > 0)
			tmpstr[l--] = rightstr[rightlen--];
		while (leftlen > 0)
			tmpstr[l--] = leftstr[leftlen--];
		goto rtfunc_str_const;
	  }

	case RTCMD_STRSET:
	  {
		/* Multiplication of a character with a number */
		l = getord(ep->right);
		if (l > destsize) {
			warning("string multiplication too long, truncating");
			l = destsize;
		}
		tmpstr = (byte_t *)nbmalloc((l + 1) * sizeof(byte_t));
		tmpstr[0] = (byte_t)l;
		while (l > 0)
			tmpstr[l--] = ep->left->spec.cval.val.c;
		goto rtfunc_str_const;
	  }

	case RTCMD_STRCMP:
	  {
		/* String comparison */
		byte_t *leftstr, *rightstr;
		int cmpcode;

		leftstr = ep->exprlist[0]->spec.cval.val.s;
		rightstr = ep->exprlist[1]->spec.cval.val.s;
		cmpcode = ep->exprlist[2]->spec.cval.val.i;
		boolval = str_compare(cmpcode, leftstr, rightstr);
		goto rtfunc_bool_const;
	  }

	case RTCMD_STRSUB:
	  {
		/* Get a substring */
		size_t leftlen, rightlen;

		l = (size_t)(ep->exprlist[0]->spec.cval.val.s[0]);
		leftlen = (size_t)(ep->exprlist[1]->spec.cval.val.i);
		rightlen = (size_t)(ep->exprlist[2]->spec.cval.val.i);
		if (rightlen > l)
			rightlen = l;
		if (leftlen > rightlen)
			leftlen = rightlen;
		l = (rightlen - leftlen + 1);
		if (l > destsize) {
			warning("substring too long, truncating");
			rightlen = leftlen + destsize - 1;
			l = destsize;
		}

		/* Copy into a new string */
		tmpstr = (byte_t *)nbmalloc((l + 1) * sizeof(byte_t));
		tmpstr[0] = (byte_t)l;
		while (l > 0)
			tmpstr[l--] =
				ep->exprlist[0]->spec.cval.val.s[rightlen--];
		goto rtfunc_str_const;
	  }

	case RTCMD_MEMCMP:
	  {
		/* Compare two memory blocks */
		byte_t *leftmem, *rightmem;
		int cmpcode;

		leftmem = ep->exprlist[0]->spec.cval.val.m;
		rightmem = ep->exprlist[1]->spec.cval.val.m;
		cmpcode = ep->exprlist[2]->spec.cval.val.i;
		destsize = ep->exprlist[0]->type->size;
		boolval = mem_compare(cmpcode, destsize, leftmem, rightmem);
		goto rtfunc_bool_const;
	  }

	case RTCMD_STR2IP:
	  {
		/* Convert a string into an IP address */
		char *ipaddrbuf;

		ipaddrbuf = get_string(ep->exprlist[0]->spec.cval.val.s);
		tmpstr = getinet(ipaddrbuf, FALSE);
		free(ipaddrbuf);
		if (tmpstr == NULL) {
			/*
			 * If we are unable to resolve the host name, leave it
			 * to the runtime routine to do it. The client, on which
			 * the compiled program runs might be on a different
			 * subnet or use a different name server (in case the
			 * runtime routine is able to any DNS resolving).
			 */
			return(ep);
		}
		isstr = FALSE;
		goto rtfunc_str_const;
	  }

	case RTCMD_IP2STR:
	  {
		/* Convert an IP address into a string */
		char ipaddrbuf[IPADDR_SIZE * 4 + 1];

		sprintf(ipaddrbuf, "%3hd.%3hd.%3hd.%3hd",
					(ep->spec.cval.val.a[0] & 0xff),
					(ep->spec.cval.val.a[1] & 0xff),
					(ep->spec.cval.val.a[2] & 0xff),
					(ep->spec.cval.val.a[3] & 0xff));

		/* Copy host character array into target string array */
		tmpstr = make_string(ipaddrbuf);
		goto rtfunc_str_const;
	  }

	default:
		/* Default for runtime functions which can't get collapsed */
		return(ep);
  }


/* Return a constant string or IP address */
rtfunc_str_const:
  delexpr(ep);
  ep = (struct expr *)nbmalloc(sizeof(struct expr));
  ep->opcode = CMD_CONST;
  ep->type = (isstr ? &string_type : &ipaddr_type);
  ep->exprnum = 0;
  ep->spec.cval.t = ep->type;
  if (isstr)
	ep->spec.cval.val.s = tmpstr;
  else
	ep->spec.cval.val.a = tmpstr;
  return(ep);


/* Return a constant boolean value */
rtfunc_bool_const:
  delexpr(ep);
  return(newconst(boolval, &bool_type));
}



/*
 *****************************************************************************
 *
 * Collapse a binary numerical expression. This handles only one node!
 */
static struct expr *num_collapse __F((ep), struct expr *ep)
{
  struct expr *tmpexpr;
  struct expr *leftexpr = ep->left;
  struct expr *rightexpr = ep->right;
  int opcode = ep->opcode;
  int leftval, rightval;
  int retval = 0;
  int isbool = FALSE;

#ifdef PARANOID
  if (exprtype(leftexpr) != EXPR_NUM || exprtype(rightexpr) != EXPR_NUM)
	interror(21, "invalid types in numerical operation");
  if (exprtype(ep) != EXPR_NUM && (!iscmdcond(ep) || exprtype(ep) != EXPR_BOOL))
	interror(25, "invalid result type for numerical expression");
#endif

  /*
   * If one of the two subexpressions is constant, handle some
   * special cases:
   *
   *   1.)  0 / num  -->  0
   *   2.)  num * 0  -->  0
   *   3.)  num & 0  -->  0
   *   4.)  num / 0  -->  error
   *   5.)  num % 0  -->  error
   *   6.)  num % 1  -->  0
   *   7.)  num + 0  -->  num
   *   8.)  num * 1  -->  num
   *   9.)  num / 1  -->  num
   *  10.)  num | 0  -->  num
   *  11.)  num ^ 0  -->  num
   *  12.)  num + 1  -->  succ(num)
   *  13.)  num - 1  -->  pred(num)
   *  14.)  num % 2  -->  and(num,1)
   *
   */
  if (!isconst(leftexpr) || !isconst(rightexpr)) {

	/* Both subexpressions are not constant --> nothing to do */
	if (!isconst(leftexpr) && !isconst(rightexpr))
		return(ep);

	/* Case (1): 0 / num --> 0 */
	if (opcode == '/' && isconst(leftexpr) &&
	    leftexpr->spec.cval.val.i == 0) {
		retval = 0;
		goto num_const_ret;
	}

	/* Case (2): num * 0 --> 0 */
	if (opcode == '*' &&
	    ((isconst(leftexpr) && leftexpr->spec.cval.val.i == 0) ||
	     (isconst(rightexpr) && rightexpr->spec.cval.val.i == 0))) {
		retval = 0;
		goto num_const_ret;
	}

	/* Case (3): num & 0 --> 0 */
	if (opcode == CMD_AND &&
	    ((isconst(leftexpr) && leftexpr->spec.cval.val.i == 0) ||
	     (isconst(rightexpr) && rightexpr->spec.cval.val.i == 0))) {
		retval = 0;
		goto num_const_ret;
	}

	/* Case (4) and case (5): num /|% 0 --> error */
	if ((opcode == '/' || opcode == '%') &&
	    (isconst(rightexpr) && rightexpr->spec.cval.val.i == 0)) {
		error("division by zero", FALSE);
		retval = 0;
		goto num_const_ret;
	}

	/* Case (6): num % 1 --> 0 */
	if (opcode == '%' &&
	    (isconst(rightexpr) && rightexpr->spec.cval.val.i == 1)) {
		retval = 0;
		goto num_const_ret;
	}

	/* Case (7): num + 0 --> num */
	if (opcode == '+' &&
	    ((isconst(leftexpr) && leftexpr->spec.cval.val.i == 0) ||
	     (isconst(rightexpr) && rightexpr->spec.cval.val.i == 0))) {
		opcode = CMD_NONE;
		goto num_one_ret;
	}

	/* Case (8): num * 1 --> num */
	if (opcode == '*' &&
	    ((isconst(leftexpr) && leftexpr->spec.cval.val.i == 1) ||
	     (isconst(rightexpr) && rightexpr->spec.cval.val.i == 1))) {
		opcode = CMD_NONE;
		goto num_one_ret;
	}

	/* Case (9): num / 1 --> num */
	if (opcode == '/' &&
	    (isconst(rightexpr) && rightexpr->spec.cval.val.i == 1)) {
		opcode = CMD_NONE;
		goto num_one_ret;
	}

	/* Case (10) and case (11): num |/^ 0 --> num */
	if ((opcode == CMD_OR || opcode == CMD_XOR) &&
	    ((isconst(leftexpr) && leftexpr->spec.cval.val.i == 0) ||
	     (isconst(rightexpr) && rightexpr->spec.cval.val.i == 0))) {
		opcode = CMD_NONE;
		goto num_one_ret;
	}

	/* Case (12) and case (13): num +/- 1 --> succ/pred(num) */
	if ((opcode == '+' || opcode == '-') &&
	    ((isconst(leftexpr) && leftexpr->spec.cval.val.i == 1) ||
	     (isconst(rightexpr) && rightexpr->spec.cval.val.i == 1))) {
		opcode = (opcode == '+' ? CMD_SUCC : CMD_PRED);
		goto num_one_ret;
	}

	/* Case (14): num % 2 --> and(num,1) */
	if (opcode == '%' &&
	    (isconst(rightexpr) && rightexpr->spec.cval.val.i == 2)) {
		ep->opcode = CMD_AND;
		rightexpr->spec.cval.val.i = 1;
		return(ep);
	}

	/* Nothing to optimize */
	return(ep);
  }

  /*
   * Both subexpressions are constant, so we can collapse them into one
   * single constant expression.
   */
  leftval = leftexpr->spec.cval.val.i;
  rightval = rightexpr->spec.cval.val.i;
  if (iscmdcond(ep)) {
	retval = int_compare(opcode, leftval, rightval);
	isbool = TRUE;
  } else switch (opcode) {
	case '+':
		retval = leftval + rightval;
		break;
	case '-':
		retval = leftval - rightval;
		break;
	case '*':
		retval = leftval * rightval;
		break;
	case '/':
		retval = 0;
		if (rightval == 0)
			error("division by zero", FALSE);
		else
			retval = leftval / rightval;
		break;
	case '%':
		retval = 0;
		if (rightval == 0)
			error("division by zero", FALSE);
		else
			retval = leftval % rightval;
		break;
	case CMD_AND:
		retval = leftval & rightval;
		break;
	case CMD_OR:
		retval = leftval | rightval;
		break;
	case CMD_XOR:
		retval = leftval ^ rightval;
		break;
#ifdef PARANOID
	default:
		interror(10, "invalid numerical operation");
#endif
  }


/* Collapse the numerical expression into a single constant value */
num_const_ret:
  delexpr(ep);
  return(newconst(retval, (isbool ? &bool_type : &int_type)));


/* Collapse the numerical expression by removing the constant subtree */
num_one_ret:
  tmpexpr = NULL;
  if (!isconst(leftexpr)) {
	tmpexpr = leftexpr;
	ep->left = NULL;
  } else if (!isconst(rightexpr)) {
	tmpexpr = rightexpr;
	ep->right = NULL;
  }
  delexpr(ep);
  if (opcode != CMD_NONE) {
	ep = (struct expr *)nbmalloc(sizeof(struct expr));
	ep->type = &int_type;
	ep->opcode = opcode;
	ep->exprnum = 1;
	ep->exprlist[0] = tmpexpr;
	tmpexpr = ep;
  }
  return(tmpexpr);
}



/*
 *****************************************************************************
 *
 * Collapse a binary boolean expression. This handles only one node!
 */
static struct expr *bool_collapse __F((ep), struct expr *ep)
{
  struct expr *tmpexpr;
  int leftval, rightval;
  int retval = 0;

#ifdef PARANOID
  if (exprtype(ep->left) != EXPR_BOOL || exprtype(ep->right) != EXPR_BOOL)
	interror(22, "invalid types in boolean operation");
  if (exprtype(ep) != EXPR_BOOL)
	interror(26, "invalid result type for boolean expression");
#endif

  /*
   * If one of the two subexpressions is constant, handle some
   * special cases:
   *
   *   1.)  num & FALSE  -->  FALSE
   *   2.)  num & TRUE   -->  num
   *   3.)  num ^ FALSE  -->  num
   *   4.)  num | FALSE  -->  num
   *   5.)  num | TRUE   -->  TRUE
   *
   */
  if (!isconst(ep->left) || !isconst(ep->right)) {

	/* Both subexpressions are not constant --> nothing to do */
	if (!isconst(ep->left) && !isconst(ep->right))
		return(ep);

	/* Case (1): num & FALSE --> FALSE */
	if (ep->opcode == CMD_AND &&
	    ((isconst(ep->left) && ep->left->spec.cval.val.b == FALSE) ||
	     (isconst(ep->right) && ep->right->spec.cval.val.b == FALSE))) {
		retval = FALSE;
		goto bool_const_ret;
	}

	/* Case (2): num & TRUE --> num */
	if (ep->opcode == CMD_AND &&
	    ((isconst(ep->left) && ep->left->spec.cval.val.b == TRUE) ||
	     (isconst(ep->right) && ep->right->spec.cval.val.b == TRUE)))
		goto bool_one_ret;

	/* Case (3) and case (4): num |/^ FALSE --> num */
	if ((ep->opcode == CMD_OR || ep->opcode == CMD_XOR) &&
	    ((isconst(ep->left) && ep->left->spec.cval.val.b == FALSE) ||
	     (isconst(ep->right) && ep->right->spec.cval.val.b == FALSE)))
		goto bool_one_ret;

	/* Case (5): num | TRUE --> TRUE */
	if (ep->opcode == CMD_OR &&
	    ((isconst(ep->left) && ep->left->spec.cval.val.b == TRUE) ||
	     (isconst(ep->right) && ep->right->spec.cval.val.b == TRUE))) {
		retval = TRUE;
		goto bool_const_ret;
	}

	/* Nothing to optimize */
	return(ep);
  }

  /*
   * Both subexpressions are constant, so we can collapse them into one
   * single constant expression.
   */
  leftval = ep->left->spec.cval.val.b;
  rightval = ep->right->spec.cval.val.b;
  if (iscmdcond(ep))
	retval = int_compare(ep->opcode, (leftval ? 1 : 0), (rightval ? 1 : 0));
  else switch (ep->opcode) {
	case CMD_AND:
		retval = (leftval && rightval ? TRUE : FALSE);
		break;
	case CMD_OR:
		retval = (leftval || rightval ? TRUE : FALSE);
		break;
	case CMD_XOR:
		retval = (leftval == rightval ? FALSE : TRUE);
		break;
#ifdef PARANOID
	default:
		interror(11, "invalid boolean operation");
#endif
  }


/* Collapse the boolean expression into a single constant value */
bool_const_ret:
  delexpr(ep);
  return(newconst(retval, &bool_type));


/* Collapse the boolean expression by removing the constant subtree */
bool_one_ret:
  tmpexpr = NULL;
  if (!isconst(ep->left)) {
	tmpexpr = ep->left;
	ep->left = NULL;
  } else if (!isconst(ep->right)) {
	tmpexpr = ep->right;
	ep->right = NULL;
  }
  delexpr(ep);
  return(tmpexpr);
}



/*
 *****************************************************************************
 *
 * Collapse a unary expression. This handles only one node!
 */
static struct expr *unary_collapse __F((ep), struct expr *ep)
{
  struct typesdef *rettype;
  int retval;

  /* If the subexpression is not constant, there is nothing to do */
  if (!isconst(ep->left))
	return(ep);

  /* Get an integer representation of the constant value */
  rettype = ep->left->type;
  if (!isscalar(rettype))
	return(ep);
  retval = getord(ep->left);

  /* Determine constant return value */
  switch (ep->opcode) {
	case '-':
#ifdef PARANOID
		if (exprtype(ep->left) != EXPR_NUM || exprtype(ep) != EXPR_NUM)
			interror(18, "invalid unary minus operation");
#endif
		retval = -retval;
		break;
	case CMD_NOT:
#ifdef PARANOID
		if (exprtype(ep->left) != exprtype(ep) ||
		    (exprtype(ep) != EXPR_NUM && exprtype(ep) != EXPR_BOOL))
			interror(19, "invalid unary not operation");
#endif
		if (exprtype(ep) == EXPR_NUM)
			retval = ~retval;
		else
			retval = (retval ?  FALSE : TRUE);
		break;
	case CMD_CHR:
#ifdef PARANOID
		if (exprtype(ep->left) != EXPR_NUM || exprtype(ep) != EXPR_CHAR)
			interror(33, "invalid call to 'chr'");
#endif
		rettype = &char_type;
		break;
	case CMD_ORD:
#ifdef PARANOID
		if (exprtype(ep) != EXPR_NUM)
			interror(17, "invalid call to 'ord'");
#endif
		rettype = &int_type;
		break;
	case CMD_ODD:
#ifdef PARANOID
		if (exprtype(ep->left) != EXPR_NUM || exprtype(ep) != EXPR_BOOL)
			interror(39, "invalid call to 'odd'");
#endif
		retval = (retval % 2 ? TRUE : FALSE);
		rettype = &bool_type;
		break;
	case CMD_ABS:
#ifdef PARANOID
		if (exprtype(ep->left) != EXPR_NUM || exprtype(ep) != EXPR_NUM)
			interror(34, "invalid call to 'abs'");
#endif
		retval = abs(retval);
		break;
	case CMD_SQR:
#ifdef PARANOID
		if (exprtype(ep->left) != EXPR_NUM || exprtype(ep) != EXPR_NUM)
			interror(35, "invalid call to 'sqr'");
#endif
		retval = retval ^ 2;
		break;
	case CMD_PRED:
#ifdef PARANOID
		if (rettype != ep->type || !isscalar(ep->type))
			interror(12, "invalid call to 'pred'");
#endif
		retval--;
		break;
	case CMD_SUCC:
#ifdef PARANOID
		if (rettype != ep->type || !isscalar(ep->type))
			interror(13, "invalid call to 'succ'");
#endif
		retval++;
#ifdef PARANOID
	default:
		interror(7, "invalid unary operation");
		break;
#endif
  }

  /* Collapse everything into a single constant expression */
  delexpr(ep);
  return(newconst(retval, rettype));
}



/*
 *****************************************************************************
 *
 * Collapse an expression
 */
static struct expr *collapse __F((ep), struct expr *ep)
{
  int i;

  /* Just for safety */
  if (ep == NULL || ep->exprnum == 0)
	return(ep);

  /* Function calls have their subtrees already reorganized */
  if (iscmdfunc(ep)) {
	/* Certain runtime functions can be collapsed */
	if (iscmdrtfunc(ep)) {
		/* Check if all subtrees are constant */
		for (i = 0; i < ep->exprnum; i++)
			if (!isconst(ep->exprlist[i]))
				break;
		/* Only collapse if all subtrees are constant */
		if (i == ep->exprnum)
			return(rtfunc_collapse(ep));
	}
	return(ep);
  }

  /* Handle unary operation */
  if (ep->exprnum == 1) {
	ep->left = collapse(ep->left);
	if (ep->left == NULL) {
		delexpr(ep);
		return(NULL);
	}
	return(unary_collapse(ep));
  }

  /* We can handle only binary expressions below */
#ifdef PARANOID
  if (ep->exprnum != 2)
	interror(16, "invalid number of subexpressions");
#endif

  /* Collapse both sides of the tree, and check for errors (NULL returned) */
  ep->left = collapse(ep->left);
  ep->right = collapse(ep->right);
  if (ep->left == NULL || ep->right == NULL) {
	delexpr(ep);
	return(NULL);
  }

  /* Handle binary operations */
  if (exprtype(ep->left) == EXPR_NUM)
	return(num_collapse(ep));
  else if (exprtype(ep->left) == EXPR_BOOL)
	return(bool_collapse(ep));
  return(ep);
}



/*
 *****************************************************************************
 *
 * Flatten an expression. With associative operations we can change the
 * branches so that the right tree is larger than the left one. This will
 * ease later optimization by the code generator.
 */
static struct expr *flatten __F((ep), struct expr *ep)
{
  basictypes typeleft, typeright;
  struct expr *tmp1, *tmp2;

  /*
   * Handle leaf node expressions and functions. In the latter case we
   * leave the reorganization of the function subexpressions to the
   * collapse step.
   */
  if (ep == NULL || isfunc(ep) || ep->exprnum == 0)
	return(ep);

  /* At unary nodes only reorganize one tree */
  if (ep->exprnum == 1) {
	ep->left = flatten(ep->left);
	return(ep);
  }

  /* Below we can only deal with binary expressions */
#ifdef PARANOID
  if (ep->exprnum != 2)
	interror(14, "invalid number of subexpressions");
#endif

  /*
   * Now we have only binary operations. If we have a commutative command
   * AND the right tree is larger than the left, we can swap both nodes so
   * that the left tree becomes the larger one. There are no commutative
   * commands for string expressions (string addition is NOT commutative!).
   * For conditional commands we need to change the condition when switching
   * the expression trees.
   */
  if (exprtype(ep) != EXPR_STRING &&
      (iscmdcommut(ep) || iscmdcond(ep)) &&
      (ep->left->exprnum < ep->right->exprnum ||
       (!isconst(ep->right) && isconst(ep->left)))) {
	switch (ep->opcode) {
		case CMD_GT:
			ep->opcode = CMD_LT;
			break;
		case CMD_GE:
			ep->opcode = CMD_LE;
			break;
		case CMD_LT:
			ep->opcode = CMD_GT;
			break;
		case CMD_LE:
			ep->opcode = CMD_GE;
			break;
		default:
			break;
	}
	tmp1 = ep->right;
	ep->right = ep->left;
	ep->left = tmp1;
  }

  /*
   * If we have a type change due to the operation or no scalar types, we
   * cannot continue but can only flatten the subexpressions. In this check,
   * character expressions count the same as string expressions.
   */
  if ((typeleft = exprtype(ep->left)) == EXPR_CHAR)
	typeleft = EXPR_STRING;
  if ((typeright = exprtype(ep->right)) == EXPR_CHAR)
	typeright = EXPR_STRING;
  if (!isscalar(ep->left->type) || exprtype(ep) == EXPR_ENUM ||
      exprtype(ep) != typeleft || exprtype(ep) != typeright) {
	ep->left = flatten(ep->left);
	ep->right = flatten(ep->right);
	return(ep);
  }
#ifdef PARANOID
  if (typeleft != typeright)
	interror(15, "invalid types in binary operation");
#endif

  /* Scan through the tree until we reach an end on the left side */
  ep->right = flatten(ep->right);
  if (ep->left->exprnum == 0 || isfunc(ep->left))
	return(ep);
  ep->left = flatten(ep->left);

  /*
   * Reorganize associative expressions, so that the right tree becomes
   * larger than the left one. This basically means to shift the
   * braces in an expression:
   *
   *	(A^B)^C  ==>  A^(B^C)
   */
  if (iscmdassoc(ep) && ep->opcode == ep->left->opcode) {
	tmp1 = ep->left->right;
	ep->left->right = ep;
	tmp2 = ep->left;
	ep->left = tmp1;
	tmp2->right = flatten(ep);
	ep = tmp2;
  }
  return(ep);
}



/*
 *****************************************************************************
 *
 * Collapse an expression by combining all constant nodes
 */
struct expr *reorg __F((ep), struct expr *ep)
{
  if (ep != NULL) {
	ep = flatten(ep);
	ep = collapse(ep);
  }
  return(ep);
}

