

/*
#    Sfront, a SAOL to C translator    
#    This file: Collapses expressions used in preset template map
#    Copyright (C) 1999  Regents of the University of California
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License (Version 2) as
#    published by the Free Software Foundation.
#
#    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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#    Maintainer: John Lazzaro, lazzaro@cs.berkeley.edu
*/

#include "tree.h"


/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/*                void co_constcollapse()                              */
/*                                                                     */
/* This function does constant optimization on a SAOL expression. It   */
/* it now only used for template expressions for MIDI preset numbers,  */
/* which need to be resolved to a constant during parsing. Since this  */
/* function used to do constant optimization for all of sfront, it has */
/* much more capability than is needed for the MIDI preset task.       */
/*                                                                     */
/*_____________________________________________________________________*/

extern void co_identcollapse(tnode *); 
extern int co_unarycollapse(tnode *); 
extern int co_floatcastcollapse(tnode *); 
extern int co_parencollapse(tnode *); 
extern int co_multicollapse(tnode *); 
extern int co_binarycollapse(tnode *); 
extern int co_switchcollapse(tnode *);
extern int co_opcodecollapse(tnode *); 

/***********************************************************************/
/*               constant collapse main routine                        */      
/*                                                                     */
/*expr  (done)    : IDENT                                              */
/*   (done)       | const                                              */
/*   (done)       | IDENT LB expr RB                                   */
/* treeupdate!    | SASBF LP exprlist RP                               */
/*   (done)       | IDENT LP exprlist RP                               */
/*   (done)       | IDENT LB expr RB LP exprlist RP                    */
/*   (done)       | expr Q expr COL expr %prec Q                       */
/*   (done)       | expr LEQ expr                                      */
/*   (done)       | expr GEQ expr                                      */
/*   (done)       | expr NEQ expr                                      */
/*   (done)       | expr EQEQ expr                                     */
/*   (done)       | expr GT expr                                       */
/*   (done)       | expr LT expr                                       */
/*   (done)       | expr AND expr                                      */
/*   (done)       | expr OR expr                                       */
/*   (done)       | expr PLUS expr                                     */
/*   (done)       | expr MINUS expr                                    */
/*   (done)       | expr STAR expr                                     */
/*   (done)       | expr SLASH expr                                    */
/*   (done)       | NOT expr %prec UNOT                                */
/*   (done)       | MINUS expr %prec UMINUS                            */
/*   (done)       | LP expr RP                                         */
/* (generated by parsing)                                              */
/*                  FLOATCAST LP expr RP     (int->float)              */ 
/***********************************************************************/

void co_constcollapse(tnode * tptr) 

{

  while (tptr != NULL)
    {
      switch (tptr->ttype)
	{
	case S_EXPR:
	  if (tptr->down->next == NULL)   /* consts and idents */
	    {
	      if ((tptr->down->ttype == S_NUMBER) || 
		  (tptr->down->ttype == S_INTGR))
		break;
	      else
		{
		  co_identcollapse(tptr);
		  break;
		}
	    }
	  if (co_unarycollapse(tptr))
	    break;
	  if (co_floatcastcollapse(tptr))
	    break;
	  if (co_parencollapse(tptr))
	    break;
	  if (co_multicollapse(tptr))
	    break;
	  if (co_binarycollapse(tptr))
	    break;
	  if (co_switchcollapse(tptr))
	    break;
	  if (co_opcodecollapse(tptr))
	    break;
	  co_constcollapse(tptr->down); /* array index, oparray call */ 
	  break;
	default:
	  if (tptr->down != NULL)
	    co_constcollapse(tptr->down);
	  break;
	}
      tptr = tptr->next;
    }

}


/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/*                                                                     */
/* The second-level functions called by co_constcollapse().            */
/*                                                                     */
/*_____________________________________________________________________*/

/***********************************************************************/
/*                      collapses S_IDENT                              */      
/***********************************************************************/

void co_identcollapse(tnode * tptr) 

{
  if (standardname(tptr->down) && strcmp(tptr->down->val,"inchan") &&
      strcmp(tptr->down->val,"outchan"))
    {
      tptr->vol = standardcollapse(tptr->down);
      if (tptr->vol == CONSTANT)
	tptr->res = tptr->down->res;
    }
}

/***********************************************************************/
/*          collapses constants in unary <expr>                        */      
/***********************************************************************/

int co_unarycollapse(tnode * tptr)

{
  char name[128];
  tnode * t_one;
  tnode * t_two;

  if (((t_one = tptr->down) == NULL) || ((t_two = tptr->down->next) == NULL) ||
      (tptr->down->next->next != NULL))
    return 0;

  if ((t_one->ttype != S_MINUS) && (t_one->ttype != S_NOT)) 
    return 0;
  if (t_two->ttype != S_EXPR)
    return 0;

  co_constcollapse(t_two);

  if (t_two->vol == VARIABLE)
    return 1;

  tptr->vol = CONSTANT;

  switch(t_one->ttype) {
  case S_MINUS:   
    if (t_two->down->ttype == S_NUMBER)
      {
	sprintf(name, "%e", - atof(t_two->down->val));
	t_two->ttype = S_NUMBER;
      }
    else
      {
	sprintf(name, "%li", - atol(t_two->down->val));
	t_two->ttype = S_INTGR;
      }
    break;
  case S_NOT:
    t_two->res = ASINT;
    t_two->ttype = S_INTGR;
    sprintf(name, "%i", !atof(t_two->down->val));
    break;
  default:
    internalerror("collapse.c", "unarycollapse illegal operator");
  }

  t_two->linenum = t_two->down->linenum;
  t_two->filename = t_two->down->filename;
  t_two->val = dupval(name);
  t_two->down = NULL;
  t_two->rate = IRATETYPE;
  tptr->down = t_two;
  tptr->res = t_two->res;
  return 1;

}


/***********************************************************************/
/*                  collapses floatcast expr                           */      
/***********************************************************************/

int co_floatcastcollapse(tnode * tptr)

{
  tnode * t_three;

  if ((tptr->down == NULL) || (tptr->down->ttype != S_FLOATCAST))
    return 0;

  t_three = tptr->down->next->next;

  if ((tptr->down->next->next->next == NULL) ||
      (tptr->down->next->next->next->next != NULL))
    return 0;

  co_constcollapse(t_three);

  if (t_three->vol == VARIABLE)
    return 1;

  tptr->down = t_three->down;
  tptr->down->next = tptr->down->down = NULL;
  tptr->vol = CONSTANT;
  tptr->res = tptr->down->res = ASFLOAT;
  if (tptr->down->ttype == S_INTGR)
    {
      tptr->down->ttype = S_NUMBER;
      strcat(tptr->down->val,".0");
    }
  return 1;

}

    
/***********************************************************************/
/*          collapses constants in  (<expr>)                           */      
/***********************************************************************/

int co_parencollapse(tnode * tptr)

{
  tnode * t_one, * t_two, * t_three;

  if (((t_one = tptr->down) == NULL) || 
      ((t_two = tptr->down->next) == NULL) ||
      ((t_three = tptr->down->next->next) == NULL) || 
      (tptr->down->next->next->next != NULL) ) 
    return 0;

  if ((t_one->ttype != S_LP) || (t_two->ttype != S_EXPR) ||
      (t_three->ttype != S_RP))
    return 0;

  co_constcollapse(t_two);
  if (t_two->vol == VARIABLE)
    return 1;
  tptr->down = t_two->down;
  t_two->next = NULL;
  tptr->vol = CONSTANT;
  tptr->res = t_two->res;
  return 1;

}
    
/***********************************************************************/
/*          collapses constants in multi-operator *, +, -              */      
/***********************************************************************/

int co_multicollapse(tnode * tptr)

{
  char name[128];
  tnode * eptr;
  tnode * first = NULL;
  int asafloat = 0;
  int tot = 0;
  float fval = 0.0F;
  long lval = 0;

  if ((tptr->down == NULL) || (tptr->down->ttype != S_EXPR) ||
      (tptr->down->next == NULL) ||
      ( (tptr->down->next->ttype != S_STAR) &&
	(tptr->down->next->ttype != S_PLUS) &&
	(tptr->down->next->ttype != S_MINUS)))
    return 0;

  eptr = tptr->down;

  while (eptr != NULL)
    {
      if (eptr->ttype == S_EXPR)
	{
	  co_constcollapse(eptr);
	  if (eptr->vol == CONSTANT)
	    {
	      tot++;
	      if (tot == 1)
		first = eptr;
	      if (eptr->down->ttype == S_NUMBER)
		asafloat = 1;
	    }
	}
      eptr = eptr->next;
    }

  if (tot < 2)
    return 0;

  if (asafloat)
    fval = (float)atof(first->down->val);
  else
    lval = atol(first->down->val);
  if (tptr->down != first)
    {
      eptr = tptr->down;
      while (eptr->next != first)
	eptr = eptr->next;
      if (eptr->ttype == S_MINUS)
	{
	  eptr->ttype = S_PLUS;
	  eptr->val[0] = '+';
	  if (asafloat)
	    fval = - fval;
	  else
	    lval = - lval;
	}
    }

  eptr = first;

  while (eptr->next != NULL)
    {
      if (eptr->next->next->vol == CONSTANT)
	{
	  switch (eptr->next->ttype) {
	  case S_STAR:
	    if (asafloat)
	      fval *= (float)atof(eptr->next->next->down->val);
	    else
	      lval *= atol(eptr->next->next->down->val);
	    break;
	  case S_PLUS:
	    if (asafloat)
	      fval += (float)atof(eptr->next->next->down->val);
	    else
	      lval += atol(eptr->next->next->down->val);
	    break;
	  case S_MINUS:
	    if (asafloat)
	      fval -= (float)atof(eptr->next->next->down->val);
	    else
	      lval -= atol(eptr->next->next->down->val);
	    break;
	  }
	  eptr->next = eptr->next->next->next;
	}
      else
	eptr = eptr->next->next;
    }

  if ((tptr->down == first) && (first->next == NULL))
    {
      eptr = first;
      eptr->linenum = eptr->down->linenum;
      eptr->filename = eptr->down->filename;
    }
  else
    eptr = first->down;

  if (asafloat)
    {
      eptr->ttype = S_NUMBER;
      eptr->res = ASFLOAT;
      sprintf(name, "%e", fval);
    }
  else
    {
      eptr->ttype = S_INTGR;
      eptr->res = ASINT;
      sprintf(name, "%li", lval);
    }
  eptr->vol = CONSTANT;
  eptr->val = dupval(name);
  eptr->down = NULL;
  eptr->rate = IRATETYPE;

  if ((tptr->down == first) && (first->next == NULL))
    {
      tptr->res = eptr->res; 
      tptr->vol = CONSTANT;
    }

  return 1;

}


extern void co_dividereduction(tnode *, tnode *);

/***********************************************************************/
/*      collapses constants in binary <expr> (except for +,-,*)        */      
/***********************************************************************/

int co_binarycollapse(tnode * tptr)

{
  char name[128];
  tnode * t_one, * t_two, * t_three;


  if (((t_one = tptr->down) == NULL) || ((t_two = tptr->down->next) == NULL) ||
      ((t_three = tptr->down->next->next) == NULL) ||
      (tptr->down->next->next->next != NULL))
    return 0;

  /* binary ops below */

  if ((t_one->ttype != S_EXPR) || (t_three->ttype != S_EXPR))
    return 0;

  if ((t_two->ttype != S_LEQ) && (t_two->ttype != S_GEQ) && 
      (t_two->ttype != S_NEQ) && (t_two->ttype != S_EQEQ) && 
      (t_two->ttype != S_GT) &&  (t_two->ttype != S_LT) &&  
      (t_two->ttype != S_AND) && (t_two->ttype != S_OR) &&  
      (t_two->ttype != S_SLASH)) 
    return 0;

  co_constcollapse(t_one);
  co_constcollapse(t_three);

  if ((t_one->vol == VARIABLE) && (t_two->ttype == S_SLASH) &&
      (t_three->vol == CONSTANT))
    {
      co_dividereduction(t_two, t_three);
      return 1;
    }

  if ((t_one->vol == VARIABLE) || (t_three->vol == VARIABLE))
    return 1;

  tptr->vol = CONSTANT;
  
  switch (t_two->ttype)
    {
    case S_LEQ:
      sprintf(name,"%i", atof(t_one->down->val) <=
	      atof(t_three->down->val) );
      t_one->res = ASINT;
      t_one->ttype = S_INTGR;
      break;
    case S_GEQ:
      sprintf(name,"%i", atof(t_one->down->val) >=
	      atof(t_three->down->val) );
      t_one->res = ASINT;
      t_one->ttype = S_INTGR;
      break;
    case S_NEQ:   
      sprintf(name,"%i", atof(t_one->down->val) !=
	      atof(t_three->down->val) );
      t_one->res = ASINT;
      t_one->ttype = S_INTGR;
      break;
    case S_EQEQ:
      sprintf(name,"%i", atof(t_one->down->val) ==
	      atof(t_three->down->val) );
      t_one->res = ASINT;
      t_one->ttype = S_INTGR;
      break;
    case S_GT:
      sprintf(name,"%i", atof(t_one->down->val) >
	      atof(t_three->down->val) );
      t_one->res = ASINT;
      t_one->ttype = S_INTGR;
      break;
    case S_LT:
      sprintf(name,"%i", atof(t_one->down->val) <
	      atof(t_three->down->val) );
      t_one->res = ASINT;
      t_one->ttype = S_INTGR;
      break;
    case S_AND:
      sprintf(name,"%i", atof(t_one->down->val) &&
	      atof(t_three->down->val) );
      t_one->res = ASINT;
      t_one->ttype = S_INTGR;
      break;
    case S_OR:   
      sprintf(name,"%i", atof(t_one->down->val) ||
	      atof(t_three->down->val) );
      t_one->res = ASINT;
      t_one->ttype = S_INTGR;
      break;
    case S_SLASH:
      if (atof(t_three->down->val) == 0.0F)
	{
	   printf("Error: Divide by zero in constant expression.\n\n");
	   showerrorplace(t_three->down->linenum, t_three->down->filename);
	}
      sprintf(name,"%e", atof(t_one->down->val) /
	      atof(t_three->down->val) );
      t_one->res = ASFLOAT;
      t_one->ttype = S_NUMBER;
      break;
    default:
      internalerror("parsehelp.c", "co_constcollapse illegal operator");
    }
  t_one->linenum = t_one->down->linenum;
  t_one->filename = t_one->down->filename;
  t_one->val = dupval(name);
  t_one->next = t_one->down = NULL;
  t_one->rate = IRATETYPE;
  tptr->res = t_one->res;

  return 1;

}
    
/***********************************************************************/
/*          collapses constant switch statement                        */      
/***********************************************************************/

int co_switchcollapse(tnode * tptr)

{
  tnode * t_one; 
  tnode * t_two; 
  tnode * t_three;
  tnode * t_four;
  tnode * t_five;

  if (((t_one = tptr->down) == NULL) || ((t_two = tptr->down->next) == NULL) ||
      ((t_three = tptr->down->next->next) == NULL) || 
      ((t_four = tptr->down->next->next->next) == NULL) || 
      ((t_five = tptr->down->next->next->next->next) == NULL) || 
      (tptr->down->next->next->next->next->next != NULL))  
    return 0;

  if ((t_one->ttype != S_EXPR) || (t_two->ttype != S_Q) ||
      (t_three->ttype != S_EXPR) || (t_four->ttype != S_COL) ||
      (t_five->ttype != S_EXPR))
    return 0;

  co_constcollapse(t_one);
  co_constcollapse(t_three);
  co_constcollapse(t_five);

  if (t_one->vol == VARIABLE)
    return 1;

  if ((t_three->vol == VARIABLE) || (t_five->vol == VARIABLE))
    {
      if (atof(t_one->down->val))
	{
	  tptr->down = make_tnode("(", S_LP);
	  tptr->down->next = t_three;
	  t_three->next = make_tnode(")", S_RP);
	  tptr->res = t_three->res;
	  tptr->vartype = t_three->vartype;
	  return 1;
	}
      else
	{
	  tptr->down = make_tnode("(", S_LP);
	  tptr->down->next = t_five;
	  t_five->next = make_tnode(")", S_RP);
	  tptr->res = t_five->res;
	  tptr->vartype = t_five->vartype;
	  return 1;
	}
    }

  tptr->vol = CONSTANT;

  if (atof(t_one->down->val))
    {
      t_one->val = t_three->down->val;
      t_one->ttype =  t_three->down->ttype;
      t_one->res = t_three->down->res;
      t_one->linenum = t_three->down->linenum;
      t_one->filename = t_three->down->filename;
    }
  else
    {
      t_one->val = t_five->down->val;
      t_one->ttype =  t_five->down->ttype;
      t_one->res = t_five->down->res;
      t_one->linenum = t_five->down->linenum;
      t_one->filename = t_five->down->filename;
    }
  t_one->down = t_one->next = NULL;
  t_one->rate = IRATETYPE;
  tptr->res = t_one->res;
  return 1;
}


extern int co_deleteopcode(tnode *, tnode **);

/***********************************************************************/
/*          collapses constant-filled opcode calls                     */      
/***********************************************************************/

int co_opcodecollapse(tnode * tptr)

{
  tnode * t_one;
  tnode * t_two;
  tnode * t_three;
  tnode * t_four;
  tnode * iptr;
  int allconst;

  if (((t_one = tptr->down) == NULL) || ((t_two = tptr->down->next) == NULL) ||
      ((t_three = tptr->down->next->next) == NULL) ||
      ((t_four = tptr->down->next->next->next) == NULL) ||
      (tptr->down->next->next->next->next != NULL))
    return 0;

  if ((t_one->ttype != S_IDENT) || (t_two->ttype != S_LP) ||
      (t_three->ttype != S_EXPRLIST) || (t_four->ttype != S_RP))
    return 0;


  iptr = t_three->down;

  allconst = 1;
  while (iptr != NULL)
    {
      if (iptr->ttype == S_EXPR)
	{
	  co_constcollapse(iptr);
	  if (iptr->vol == VARIABLE)
	    allconst = 0;
	}
      iptr = iptr->next;
    }

  if (!allconst)
    return 1;
  if (!coreopcodename(t_one))       
    return 1;                       
  if (!coreopcodeprecompute(t_one))
    return 1;

  tptr->vol = t_one->vol = CONSTANT;

  hascoreopcode(t_one, -1);
  coreopcodecollapse(t_one, t_three->down);

  /* not used/needed for MIDI preset application -- commented out */

  /*
  if (currinstrument)
    co_deleteopcode(t_one->optr, &(currinstrument->defnode->optr));
  else
    co_deleteopcode(t_one->optr, &globalopcodecalls);
  */

  tptr->optr = t_one->optr = NULL;
  t_one->next = NULL;
  tptr->res = t_one->res;
  return 1;

}


/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/*                                                                     */
/* Utility functions, used by second level optconst calls.             */
/*                                                                     */
/*_____________________________________________________________________*/


/***********************************************************************/
/*          strength reduction for constant divide                     */      
/***********************************************************************/

void co_dividereduction(tnode * t_two, tnode * t_three)

{
  char name[128];

  if (atof(t_three->down->val) == 0.0F)
    {
      printf("Error: Divide by zero in constant expression.\n\n");
      showerrorplace(t_three->down->linenum, t_three->down->filename);
    }
  sprintf(name,"%e", 1/atof(t_three->down->val));
  t_three->down->val = dupval(name);
  t_three->down->ttype = S_NUMBER;
  t_three->res = t_three->down->res = ASFLOAT;
  t_two->val = "*";
  t_two->ttype = S_STAR;

}

/***********************************************************************/
/*                deletes optimized-away opcode code                   */      
/***********************************************************************/

int co_deleteopcode(tnode * optr, tnode ** tlist) 

{
  tnode * tptr;

  if ((tlist == NULL)||(*tlist == NULL)||(optr == NULL))
    return NOTPRESENT;
     
  if ((*tlist) == optr)
    {
      *tlist = optr->next;
      return DELETED;
    }

  tptr = *tlist;
  while (tptr != NULL)
    {
      if (tptr->next == optr)
	{
	  tptr->next = optr->next;
	  return DELETED;
	}
      tptr = tptr->next;
    }
  return NOTPRESENT;

}
  
