/**********************************************************
*							  *
*  Convert() converts a string containing a math          *
*  function in two variables, X and Y, to a postfix	  *
*  notation string with the numeric constants and	  *
*  functions converted to one byte symbols.  If any	  *
*  syntax errors occur they will be posted in the global  *
*  external variable SyntaxErr.  Definitions are in	  *
*  syntxerr.h.						  *
*							  *
*  Evaluate() will substitute the X and Y values passed   *
*  to it and return the value of the function.		  *
*							  *
*  This program may be freely used for non-profit	  *
*  purposes as long as the copyright notice remains	  *
*  in the code. 					  *
*							  *
***********************************************************
*							  *
*	      Copyright 1987  Randy C. Finch		  *
*							  *
**********************************************************/

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

#include "common.h"


/*----------------  DEFINES  -----------------*/

#define NUMSYM 128     /* Number of constants allowed in function */
#define SYMBASE 128    /* Base value for constants symbols */
#define STACKSIZE 256  /* Stack size */
#define SIN  1	       /* Symbol for sine */
#define COS  2	       /* Symbol for cosine */
#define TAN  3	       /* Symbol for tangent */
#define ASIN 4	       /* Symbol for arcsine */
#define ACOS 5	       /* Symbol for arccosine */
#define ATAN 6	       /* Symbol for arctangent */
#define SINH 7	       /* Symbol for hyperbolic sine */
#define COSH 8	       /* Symbol for hyperbolic cosine */
#define TANH 9	       /* Symbol for hyperbolic tangent */
#define EXP  10        /* Symbol for exponential */
#define SQRT 11        /* Symbol for square root */
#define LN   12        /* Symbol for natural logarithm */
#define LOG  13        /* Symbol for logarithn base 10 */
#define NULL 0	       /* Symbol for null */
#define TRUE 1	       /* Symbol for true condition */
#define FALSE 0        /* Symbol for false condition */


/*------------	EXTERNAL GLOBALS  ------------*/

unsigned char SyntaxErr;


/*----------------  GLOBALS  ----------------*/

struct CharStack {
  unsigned char c[STACKSIZE];
  long		top;
};

struct NumStack {
  double n[STACKSIZE];
  long	 top;
};

static struct CharStack cstack;

static struct NumStack nstack;

static double Constants[NUMSYM];

static unsigned char CurConstant;

static unsigned char NewExpr[256];


/*----------------  FUNCTIONS  ----------------*/

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


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


static void Substitute(symb, ptr, len)
  unsigned char symb;
  unsigned char *ptr;
  unsigned long len;
{
  *ptr = symb;

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


static void RemoveSpaces(str)
  unsigned char *str;
{
  unsigned char *ptr;

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

	--str;
     }

     ++str;
  }
} /* RemoveSpaces */


static void AddZero(ptr)
  char *ptr;
{
  unsigned long len;
  char *i;

  len = strlen(ptr);

  for (i=ptr+len+1; i>ptr; --i)
     *i = *(i - 1);

  *ptr = '0';
} /* AddZero */


static unsigned char CPop()
{
  if (cstack.top == 0) return 0;
  else {
     --cstack.top;
     return cstack.c[cstack.top + 1];
  }
} /* CPop */


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


static unsigned char CTopOfStack()
{
  return cstack.c[cstack.top];
} /* CTopOfStack */


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


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


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


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


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

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

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

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

  else
     return TRUE;
} /* Precedence */


static unsigned char *CheckSyntax(str)
  unsigned char *str;
{
  int numLP = 0,
      numRP = 0;

  if ( (CharInStr("/*^E)",*str)) && (strncmp(str,"EXP",3) != 0) ) {
     if (CharInStr("/*^",*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("*/^E",*str)) && (strncmp(str,"EXP",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(")+-*/^",*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(".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(str,"EXP",3) == 0) {
		 SyntaxErr = MISSINGOP;
		 return str;
	      }
	      else if ( (!CharInStr("+-*/^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("+-",*str)) && (!isdigit(*str)) ) {
		 SyntaxErr = ILLEGALEXP;
		 return str;
	      }
	      else {
		 OneE = TRUE;
	      }
	   } /* else if */

	   else if (CharInStr("+-",*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("+-E",*(str-1))) {
		 SyntaxErr = ILLEGALEXP;
		 return str;
	      }
	      else {
		 ExitFlag = TRUE;
	      }
	   } /* else if */

	   else {
	      ++str;
	   } /* else */

	} /* while */

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

     } /* else if */

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

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

     else if (isupper(*str)) {
	if (strncmp(str,"LN",2) == 0) str += 2;
	else if (strncmp(str,"SINH",4) == 0) str += 4;
	else if (strncmp(str,"COSH",4) == 0) str += 4;
	else if (strncmp(str,"TANH",4) == 0) str += 4;
	else if (strncmp(str,"SIN",3) == 0) str += 3;
	else if (strncmp(str,"COS",3) == 0) str += 3;
	else if (strncmp(str,"TAN",3) == 0) str += 3;
	else if (strncmp(str,"EXP",3) == 0) str += 3;
	else if (strncmp(str,"LOG",3) == 0) str += 3;
	else if (strncmp(str,"SQRT",4) == 0) str += 4;
	else if (strncmp(str,"ASIN",4) == 0) str += 4;
	else if (strncmp(str,"ACOS",4) == 0) str += 4;
	else if (strncmp(str,"ATAN",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 */


static char ConvertConstants(str)
  unsigned char *str;
{
  unsigned char *ptr;

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

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

     ++ptr;

  } /* while */

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

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

  ptr = str;

  CurConstant = SYMBASE;

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

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

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

	*(numstr+j) = NULL;

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

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

     } /* if */

     ++ptr;

  } /* while */

  }  /* end block */

  return TRUE;

} /* ConvertConstants */


static void ConvertFunctions(str)
  unsigned char *str;
{
  while ( *str != NULL ) {
     if ( (isupper(*str)) && (!CharInStr("XY",*str)) ) {

	if (strncmp(str,"LN",2) == 0) Substitute(LN,str,2L);
	else if (strncmp(str,"SINH",4) == 0) Substitute(SINH,str,4L);
	else if (strncmp(str,"COSH",4) == 0) Substitute(COSH,str,4L);
	else if (strncmp(str,"TANH",4) == 0) Substitute(TANH,str,4L);
	else if (strncmp(str,"SIN",3) == 0) Substitute(SIN,str,3L);
	else if (strncmp(str,"COS",3) == 0) Substitute(COS,str,3L);
	else if (strncmp(str,"TAN",3) == 0) Substitute(TAN,str,3L);
	else if (strncmp(str,"EXP",3) == 0) Substitute(EXP,str,3L);
	else if (strncmp(str,"LOG",3) == 0) Substitute(LOG,str,3L);
	else if (strncmp(str,"SQRT",4) == 0) Substitute(SQRT,str,4L);
	else if (strncmp(str,"ASIN",4) == 0) Substitute(ASIN,str,4L);
	else if (strncmp(str,"ACOS",4) == 0) Substitute(ACOS,str,4L);
	else if (strncmp(str,"ATAN",4) == 0) Substitute(ATAN,str,4L);

     } /* if */

     ++str;

  } /* while */

} /* ConvertFunctions */


static char InfixToPostfix(str)
  unsigned char *str;
{
  unsigned long i1=0, i2=0;
  unsigned char 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;
	   }
	}

     } /* if */

     ++i1;

  } /* while */

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

  NewExpr[i2] = NULL;

  return TRUE;

} /* InfixToPostfix */


static double Calculate(s,n2,n1)
  unsigned char s;
  double n1,n2;
{
  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 */

} /* Calculate */


unsigned char *Convert(FunctionString)
  unsigned char *FunctionString;
{
  unsigned char fstr[512];
  unsigned char *ptr;

  SyntaxErr = FALSE;

  RemoveSpaces(FunctionString);

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

  strupr(FunctionString);

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

  if ((ptr = CheckSyntax(FunctionString)) != 0) return ptr;

  strcpy(fstr,FunctionString);

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

  if (!ConvertConstants(fstr)) return fstr;

#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 fstr;

#if DEBUG
  {
  unsigned char *ptr;

  printf("\nInfixToPostfix: ");

  for(ptr = NewExpr; *ptr != NULL; ++ptr)
     printf("%d ", *ptr);

  printf("\n");
  }
#endif

  return NewExpr;

} /* Convert */


double Evaluate(x,y)
  double x, y;
{
  unsigned char symbol;
  long i = 0;

  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 {
	NPush( Calculate(symbol, NPop(), NPop()) );
     }

     ++i;

  } /* while */

  return NPop();

} /* Evaluate */
