/**********************************************************
*                                                         *
*  FuncEval.cpp                                           *
*                                                         *
*  Implementation file for FuncEval.                      *
*                                                         *
*  Definition in FuncEval.hpp.                            *
*                                                         *
***********************************************************
*                                                         *
*  Copyright 1995  Randy C. Finch                         *
*                                                         *
**********************************************************/


/*--------------  INCLUDES  ----------------*/


#include "FuncEval.hpp"


/*----------  FUNCEVAL PRIVATE METHODS  ------------*/


char FuncEval::CharInStr(UCHAR *s, UCHAR c)
{
   while (*s != NULL) {
      if (*s == c) return TRUE;
      ++s;
   }
   return FALSE;
} /* CharInStr */


void FuncEval::Deposit(double num)
{
   Constants[CurConstant - SYMBASE] = num;
} /* Deposit */


void FuncEval::Substitute(UCHAR symb, UCHAR *ptr, unsigned long len)
{
   *ptr = symb;

   if (len > 1) {
      do {
         ++ptr;
         *ptr = *(ptr + len - 1);
      } while (*ptr != NULL);
   }
} /* Substitute */


void FuncEval::RemoveSpaces(UCHAR *str)
{
   UCHAR *ptr;

   while (*str != NULL) {
      if (*str == ' ') {
         ptr = str;
         do {
            *ptr = *(ptr + 1);
            ++ptr;
         } while ( *(ptr - 1) != NULL);
         --str;
      }
      ++str;
   }
} /* RemoveSpaces */


void FuncEval::AddZero(UCHAR *ptr)
{
   unsigned long len;
   UCHAR *i;

   len = strlen((char *)ptr);
   for (i=ptr+len+1; i>ptr; --i)
      *i = *(i - 1);
   *ptr = '0';
} /* AddZero */


UCHAR FuncEval::CPop()
{
   if (cstack.top == 0)
      return 0;
   else {
      --cstack.top;
      return cstack.c[cstack.top + 1];
   }
} /* CPop */


char FuncEval::CPush(UCHAR c)
{
   if (cstack.top == STACKSIZE)
      return FALSE;
   else {
      ++cstack.top;
      cstack.c[cstack.top] = c;
      return TRUE;
   }
} /* CPush */


UCHAR FuncEval::CTopOfStack()
{
   return cstack.c[cstack.top];
} /* CTopOfStack */


double FuncEval::NPop()
{
   --nstack.top;
   return nstack.n[nstack.top + 1];
} /* NPop */


void FuncEval::NPush(double n)
{
   ++nstack.top;
   nstack.n[nstack.top] = n;
} /* NPush */


char FuncEval::IsFunction(UCHAR c)
{
   if ( (c >= SIN) && (c <= LOG) )
      return TRUE;
   else
      return FALSE;
} /* IsFunction */


char FuncEval::IsSymbol(UCHAR c)
{
   if ((c >= SYMBASE) && (c < SYMBASE+NUMSYM))
      return TRUE;
   else
      return FALSE;
} /* IsSymbol */


char FuncEval::Precedence(UCHAR c1, UCHAR c2)
{
   if ( (CharInStr((UCHAR *)"+-*/",c1)) && (c2 == '^') )
      return FALSE;

   else if ( (CharInStr((UCHAR *)"+-",c1)) && (CharInStr((UCHAR *)"*/",c2)) )
      return FALSE;

   else if ( ((c1 == '(') && (c2 != ')')) || (c2 == '(') )
      return FALSE;

   else if ( (CharInStr((UCHAR *)"+-*/^",c1)) && (IsFunction(c2)) )
      return FALSE;

   else
      return TRUE;
} /* Precedence */


UCHAR *FuncEval::CheckSyntax(UCHAR *str)
{
   int numLP = 0,
       numRP = 0;

   if ( (CharInStr((UCHAR *)"/*^E)",*str)) && (strncmp((char *)str,(char *)"EXP",(UINT)3) != 0) ) {
      if (CharInStr((UCHAR *)"/*^",*str)) {
         SyntaxErr = MISPLACEDOP;
         return str;
      }
      else if (*str == 'E') {
         SyntaxErr = ILLEGALEXP;
         return str;
      }
      else {
         SyntaxErr = MISSINGLP;
         return str;
      }
   } /* if */

   for (;;) {   /* forever */

      if (*str == '(') {
         ++numLP;
         ++str;
         if ( (CharInStr((UCHAR *)"*/^E",*str)) && (strncmp((char *)str,(char *)"EXP",(UINT)3) != 0) ) {
            if (*str == 'E') {
               SyntaxErr = ILLEGALEXP;
               return str;
            }
            else {
               SyntaxErr = MISPLACEDOP;
               return str;
            }
         } /* if */
         if ( (*str == ')') || (*str == NULL) ) {
            SyntaxErr = MISSINGPARM;
            return str;
         }
      } /* if */

      else if (*str == ')') {
         ++numRP;
         ++str;
         if (numRP > numLP) {
            SyntaxErr = MISSINGLP;
            return (str-1);
         }
         else if ( (!CharInStr((UCHAR *)")+-*/^",*str)) && (*str != NULL) ) {
            SyntaxErr = MISSINGOP;
            return str;
         }
      } /* else if */

      else if ( (isdigit(*str)) || (*str == '.') ) {
         char ExitFlag = FALSE,

         OneDecimal = FALSE,
         OneE = FALSE;

         if (*str == '.') OneDecimal = TRUE;
         ++str;
         if ( (OneDecimal == TRUE) && (!isdigit(*str)) ) {
            SyntaxErr = LONEDECIMAL;
            return (str - 1);
         }
         while (((isdigit(*str)) || (CharInStr((UCHAR *)".E)-+",*str))
                || (*str == NULL)) && !ExitFlag ) {
            if (*str == '.') {
               ++str;
               if (OneE) {
                  SyntaxErr = ILLEGALEXP;
                  return (str-1);
               }
               else if (OneDecimal) {
                  SyntaxErr = EXTRADECIMAL;
                  return (str-1);
               }
               else if (strncmp((char *)str,(char *)"EXP",(UINT)3) == 0) {
                  SyntaxErr = MISSINGOP;
                  return str;
               }
               else if ( (!CharInStr((UCHAR *)"+-*/^E)",*str)) && (!isdigit(*str)) ) {
                  SyntaxErr = ILLEGALCHAR;
                  return str;
               }
               else {
                  OneDecimal = TRUE;
               }
            } /* if */
            else if (*str == 'E') {
               ++str;
               if (OneE) {
                  SyntaxErr = EXTRAE;
                  return (str-1);
               }
               else if ( (!CharInStr((UCHAR *)"+-",*str)) && (!isdigit(*str)) ) {
                  SyntaxErr = ILLEGALEXP;
                  return str;
               }
               else {
                  OneE = TRUE;
               }
            } /* else if */
            else if (CharInStr((UCHAR *)"+-",*str)) {
               if ( *(str-1) == 'E' )
                  ++str;
               else if ( !OneE || (OneE && isdigit(*(str-1))) )
                  ExitFlag = TRUE;
               else {
                  SyntaxErr = MISPLACEDOP;
                  return str;
               }
            } /* else if */
            else if ( (*str == ')') || (*str == NULL) ) {
               if (CharInStr((UCHAR *)"+-E",*(str-1))) {
                  SyntaxErr = ILLEGALEXP;
                  return str;
               }
               else {
                  ExitFlag = TRUE;
               }
            } /* else if */
            else {
               ++str;
            } /* else */

         } /* while */

         if( !CharInStr((UCHAR *)"+-*/)", *str) && (*str != NULL) ) {
            SyntaxErr = MISSINGOP;
            return str;
         }

      } /* else if */

      else if (CharInStr((UCHAR *)"+-*/^",*str)) {
         ++str;
         if ( (CharInStr((UCHAR *)")E+-*/^",*str)) || (*str == NULL) ) {
            if (strncmp((char *)str,(char *)"EXP",(UINT)3) != 0) {
               SyntaxErr = MISPLACEDOP;
               return (str-1);
            }
         }
      } /* else if */

      else if (CharInStr((UCHAR *)"XY",*str)) {
         ++str;
         if ( (!CharInStr((UCHAR *)")+-*/^",*str)) && (*str != NULL) ) {
            SyntaxErr = MISSINGOPRP;
            return str;
         }
      } /* else if */

      else if (isupper(*str)) {
         if (strncmp((char *)str,(char *)"LN",(UINT)2) == 0)
            str += 2;
         else if (strncmp((char *)str,(char *)"SINH",(UINT)4) == 0)
            str += 4;
         else if (strncmp((char *)str,(char *)"COSH",(UINT)4) == 0)
            str += 4;
         else if (strncmp((char *)str,(char *)"TANH",(UINT)4) == 0)
            str += 4;
         else if (strncmp((char *)str,(char *)"SIN",(UINT)3) == 0)
            str += 3;
         else if (strncmp((char *)str,(char *)"COS",(UINT)3) == 0)
            str += 3;
         else if (strncmp((char *)str,(char *)"TAN",(UINT)3) == 0)
            str += 3;
         else if (strncmp((char *)str,(char *)"EXP",(UINT)3) == 0)
            str += 3;
         else if (strncmp((char *)str,(char *)"LOG",(UINT)3) == 0)
            str += 3;
         else if (strncmp((char *)str,(char *)"SQRT",(UINT)4) == 0)
            str += 4;
         else if (strncmp((char *)str,(char *)"ASIN",(UINT)4) == 0)
            str += 4;
         else if (strncmp((char *)str,(char *)"ACOS",(UINT)4) == 0)
            str += 4;
         else if (strncmp((char *)str,(char *)"ATAN",(UINT)4) == 0)
            str += 4;
         else {
            SyntaxErr = ILLEGALFUNC;
            return str;
         }

         if (*str != '(') {
            SyntaxErr = MISSINGLP;
            return str;
         }
      } /* else if */

     else if (*str == NULL) {
        if (numLP < numRP) {
           SyntaxErr = MISSINGLP;
           return str;
        }
        else if (numLP > numRP) {
           SyntaxErr = MISSINGRP;
           return str;
        }
        else {
           SyntaxErr = FALSE;
           return 0L;
        }
     } /* else if */

     else {
        SyntaxErr = ILLEGALCHAR;
        return str;
     }

  } /* for */

} /* CheckSyntax */


char FuncEval::ConvertConstants(UCHAR *str)
{
   UCHAR *ptr;

   ptr = str;
   if ( CharInStr((UCHAR *)"+-",*ptr) ) {
      AddZero(str);
      ptr += 2;
   }

   while ( *ptr != NULL ) {
      if ( (CharInStr((UCHAR *)"+-",*ptr)) && (*(ptr-1) == '(') )
         AddZero(ptr);
      ++ptr;
   } /* while */

#if DEBUG
  printf("\nAddZero: %s\n", str);
#endif

   {  /* begin block */
   unsigned long j;
   UCHAR numstr[80];
   double number;

   ptr = str;
   CurConstant = SYMBASE;

   while ( *ptr != NULL) {
      if ( (*ptr == '.') || (isdigit(*ptr)) ) {
         unsigned long lennum = 1;

         while ( (CharInStr((UCHAR *)".E-+",*(ptr+lennum))) || (isdigit(*(ptr+lennum))) ) {
            if( (CharInStr((UCHAR *)"-+",*(ptr+lennum))) && (*(ptr+lennum-1) != 'E') )
               break;
            ++lennum;
         }

         for (j=0; j<lennum; ++j)
            *(numstr+j) = *(ptr+j);

         *(numstr+j) = NULL;

         number = atof((char *)numstr);
         Deposit(number);
         Substitute(CurConstant, ptr, lennum);

         ++CurConstant;
         if (CurConstant >= SYMBASE+NUMSYM) {
            SyntaxErr = TOOMANYCONST;
            return FALSE;
         }

      } /* if */

      ++ptr;

   } /* while */

   }  /* end block */

   return TRUE;

} /* ConvertConstants */


void FuncEval::ConvertFunctions(UCHAR *str)
{
   while ( *str != NULL ) {
      if ( (isupper(*str)) && (!CharInStr((UCHAR *)"XY",*str)) ) {
         if (strncmp((char *)str,(char *)"LN",(UINT)2) == 0)
            Substitute(LN,str,2L);
         else if (strncmp((char *)str,(char *)"SINH",(UINT)4) == 0)
            Substitute(SINH,str,4L);
         else if (strncmp((char *)str,(char *)"COSH",(UINT)4) == 0)
            Substitute(COSH,str,4L);
         else if (strncmp((char *)str,(char *)"TANH",(UINT)4) == 0)
            Substitute(TANH,str,4L);
         else if (strncmp((char *)str,(char *)"SIN",(UINT)3) == 0)
            Substitute(SIN,str,3L);
         else if (strncmp((char *)str,(char *)"COS",(UINT)3) == 0)
            Substitute(COS,str,3L);
         else if (strncmp((char *)str,(char *)"TAN",(UINT)3) == 0)
            Substitute(TAN,str,3L);
         else if (strncmp((char *)str,(char *)"EXP",(UINT)3) == 0)
            Substitute(EXP,str,3L);
         else if (strncmp((char *)str,(char *)"LOG",(UINT)3) == 0)
            Substitute(LOG,str,3L);
         else if (strncmp((char *)str,(char *)"SQRT",(UINT)4) == 0)
            Substitute(SQRT,str,4L);
         else if (strncmp((char *)str,(char *)"ASIN",(UINT)4) == 0)
            Substitute(ASIN,str,4L);
         else if (strncmp((char *)str,(char *)"ACOS",(UINT)4) == 0)
            Substitute(ACOS,str,4L);
         else if (strncmp((char *)str,(char *)"ATAN",(UINT)4) == 0)
            Substitute(ATAN,str,4L);
      } /* if */

      ++str;

   } /* while */

} /* ConvertFunctions */


char FuncEval::InfixToPostfix(UCHAR *str)
{
   unsigned long i1=0, i2=0;
   UCHAR NextChar, TopSymbol;

   cstack.top = 0; /* Initialize stack */
   NewExpr[0] = NULL; /* Initialize expression */

   while ( *(str+i1) != NULL ) {
      NextChar = *(str+i1);
      if ( (IsSymbol(NextChar)) || (NextChar == 'X') || (NextChar == 'Y') ) {
         NewExpr[i2] = NextChar;
         ++i2;
      }
      else {
         for (;;) {   /* Forever */
            if ( (cstack.top == 0) || (!Precedence(CTopOfStack(),NextChar)) )
               break;
            if ((TopSymbol = CPop()) == 0) {
               SyntaxErr = STACKUNDERFLOW;
               return FALSE;
            }
            if (cstack.top != 0) {
               if ( (IsFunction( CTopOfStack() )) && (NextChar == ')') ) {
                  TopSymbol = CPop();
                  NewExpr[i2] = TopSymbol;
                  ++i2;
                  break;
               } /* if */
            } /* if */
            if ( (TopSymbol == '(') && (NextChar == ')') )
               break;
            if (TopSymbol != '(') {
               NewExpr[i2] = TopSymbol;
               ++i2;
            }
         } /* for */

         if (NextChar != ')') {
            if (CPush(NextChar) == FALSE) {
               SyntaxErr = STACKOVERFLOW;
               return FALSE;
            }
         }

      } /* else */

      ++i1;

   } /* while */

   while (cstack.top != 0) {
      TopSymbol = CPop();
      if (TopSymbol != '(') {
         NewExpr[i2] = TopSymbol;
         ++i2;
      }
   }

   NewExpr[i2] = NULL;

   return TRUE;

} /* InfixToPostfix */


double FuncEval::Calculate(UCHAR s, double n2, double n1)
{
   switch (s) {
      case '+':
         return (n1 + n2);
      case '-':
         return (n1 - n2);
      case '*':
         return (n1 * n2);
      case '/':
         return (n1 / n2);
      case '^':
         return ( exp(n2*log(n1)) );
      case SIN:
         return ( sin(n2) );
      case COS:
         return ( cos(n2) );
      case TAN:
         return ( tan(n2) );
      case EXP:
         return ( exp(n2) );
      case SQRT:
         return ( sqrt(n2) );
      case LN:
         return ( log(n2) );
      case LOG:
         return ( log10(n2) );
      case ASIN:
         return ( asin(n2) );
      case ACOS:
         return ( acos(n2) );
      case ATAN:
         return ( atan(n2) );
      case SINH:
         return ( sinh(n2) );
      case COSH:
         return ( cosh(n2) );
      case TANH:
         return ( tanh(n2) );
   } /* switch */
   return 0.0;  // should never happen, here to prevent compile warning
} /* Calculate */


/*-------------- FUNCEVAL PUBLIC METHODS --------------*/


FuncEval::FuncEval()
{
   ErrPosition = 0;
}

 
int FuncEval::Convert(UCHAR *FunctionString)
{
   UCHAR *ptr;

   SyntaxErr = FALSE;

   RemoveSpaces(FunctionString);

#if DEBUG
  printf("\nRemoveSpaces: %s\n", FunctionString);
#endif

   strupr((char *)FunctionString);

#if DEBUG
  printf("\nstrupr: %s\n", FunctionString);
#endif

   if (ptr = CheckSyntax(FunctionString)) {
      ErrPosition = (int)(ptr-FunctionString)+1;
      return GetSyntaxErrNum();
   }
   
   strcpy((char *)fstr,(char *)FunctionString);

#if DEBUG
  printf("\nCheckSyntax: %s\n", fstr);
#endif

   if (!ConvertConstants(fstr)) return GetSyntaxErrNum();

#if DEBUG
  printf("\nConvertConstants: ");
  for(ptr = fstr; *ptr != NULL; ++ptr)
     printf("%d ", *ptr);
  printf("\n");
#endif

   ConvertFunctions(fstr);

#if DEBUG
  printf("\nConvertFunctions: ");
  for(ptr = fstr; *ptr != NULL; ++ptr)
     printf("%d ", *ptr);
  printf("\n");
#endif

   if (!InfixToPostfix(fstr)) return GetSyntaxErrNum();

#if DEBUG
  {
  UCHAR *ptr;

  printf("\nInfixToPostfix: ");
  for(ptr = NewExpr; *ptr != NULL; ++ptr)
     printf("%d ", *ptr);
  printf("\n");
  }
#endif

   return GetSyntaxErrNum();

} /* Convert */


double FuncEval::Evaluate(double x, double y)
{
   UCHAR symbol;
   long i = 0;
   double tempnum;
   
   nstack.top = 0;  /* Initialize stack */
   while (NewExpr[i] != NULL) {
      symbol = NewExpr[i];
      if (symbol == 'X')
         NPush(x);
      else if (symbol == 'Y')
         NPush(y);
      else if (IsSymbol(symbol)) {
         NPush( Constants[symbol-SYMBASE] );
      }
      else if (IsFunction(symbol)) {
         NPush( Calculate(symbol, NPop(), 0.0) );
      }
      else {
         tempnum = NPop();
         NPush( Calculate(symbol, tempnum, NPop()) );
      }
      ++i;
   } /* while */

   return NPop();

} /* Evaluate */


int FuncEval::GetSyntaxErrNum()
{
   return SyntaxErr;
}


char *FuncEval::GetSyntaxErrMessage()
{
   if (SyntaxErr) {
      if (SyntaxErr == STACKUNDERFLOW)
         ErrMessage = (char *)"Stack underflow";
      else if (SyntaxErr == STACKOVERFLOW)
         ErrMessage = (char *)"Stack overflow";
      else if (SyntaxErr == TOOMANYCONST)
         ErrMessage = (char *)"Too many constants in function";
      else {
         switch (SyntaxErr) {
            case MISPLACEDOP:
               ErrMessage = (char *)"Misplaced operator";
            break;
            case ILLEGALCHAR:
               ErrMessage = (char *)"Illegal character";
            break;
            case ILLEGALEXP:
               ErrMessage = (char *)"Illegal exponent";
            break;
            case ILLEGALFUNC:
               ErrMessage = (char *)"Illegal function";
            break;
            case MISSINGOP:
               ErrMessage = (char *)"Missing operator";
            break;
            case MISSINGOPRP:
               ErrMessage = (char *)"Missing operator or right parenthesis";
            break;
            case MISSINGLP:
               ErrMessage = (char *)"Missing left parenthesis";
            break;
            case MISSINGRP:
               ErrMessage = (char *)"Missing right parenthesis";
            break;
            case MISSINGPARM:
               ErrMessage = (char *)"Missing parameter";
            break;
            case LONEDECIMAL:
               ErrMessage = (char *)"Lone decimal point";
            break;
            case EXTRADECIMAL:
               ErrMessage = (char *)"Extra decimal";
            break;
            case EXTRAE:
               ErrMessage = (char *)"Extra E";
            break;

         } /* switch */
      } /* else */

   } /* if */

   else {
      ErrMessage = (char *) "Acceptable";
   }
   return ErrMessage;
}
 

int FuncEval::GetErrPosition()
{
   return ErrPosition;
}
