/* *******************************************************************
 *                                                                   *
 * Copyright (c) L-DGO/MIT/JGOFS                                     *
 *                                                                   *
 *                                                                   *
 * File             : mathex.c                                       *
 *                                                                   *
 ******************************************************************* */
#define MATHEXW_VERSION "mathex  version 2.2c   6 Oct 2006"
/*
   6 Oct 06	v 2.2c	WJS
      Missed MISSING_VALUE_* parametrizations last time
  30 Aug 04	v 2.2c	WJS
	Using parameters from core.h - some renaming, etc, needed
	Add version function
  25 Aug 04	v 2.2c	WJS
	buildstring now in library - remove it from here and do
      necessary games w/err
	[Begin v 2.2c - deliberately skipping 2.2b to stay in step
	 w/math and with the "release number"]

  12 Nov 98	v 2.2a	WJS
	Bug fix: err #define should not have concluding ;
	Add return to buildstring even if code can't logically get to it
	[Begin v 2.2a]
   5 May 98	v 2.2	WJS
	Check that only new variables appear on the left of equations,
      and only old variables appear on the right
	Build error messages here and call error_ instead of returning 1.
      This gets around problem of using equation buffer as error message
      buffer and worrying about overflow.  In particular, allows equation
      to be in error message.
	Handle comma-separated equations.  Don't know why this was a
      problem, but it didn't seem to have been working.  Still not sure
      my implementation is correct.  The priority array has commas after
      parens, which suggests to me that commas are allowed within parens;
      eg, in a function w/2 args.  However, no function allows 2 args.
      Further, I suspect that such an embedded comma would NOT go on the
      stack, whereas math needs the equation-terminating comma to be on
      the stack.
	[Begin v 2.2]
  29 Apr 98	v 2.1	WJS
	Switch variable number in pcode from 7 bits to 15.  Unfortunately,
      this mod introduces meaningful zero bytes into middle of char
      array, so use of zero byte as terminator must be very carefully done.
      (Actually, there already were meaningful embedded zero bytes; they
      were terminators for embedded strings...)
	Switch to unsigned char for some accesses to pcode arrays.  
      See math for comments.
	Since we are generating new variables with sprintf, we know
      the minimum width.  Use it as a minimum.
	Parametrize PCODE_LEN
	[Begin v 2.1]
   1 Apr 98	v 2.0	WJS
	Allow attribute list after a new variable.  In any case,
      generate at least a width= attribute.  Use width= from attribute
      list, if present.  Otherwise, use width of variable name.  Allow
      variable name to be padded with _s to effectively provide width,
      and remove those _s
	Switched from type float to type double
	Replaced aint function (which was not in ANSI C book nor seemed
      to be picked up by -lm on Bob's box) with floor function.
	[Begin v 2.0]
  17 Oct 92	v 1.1	GRF
*/

/*
P-code form:
>128: push variable number into the 15 bits following the "128" bit
1: ascii number
2: push constant
3: quote next variable
s,c,e,l,'1','i': prefix ops
=,+,-,*,/,^: infix ops
0: end
*/

#include "jgmath.h"

char *buildstring();
void err1();
void execute();
void parsex();
  /*  outer functions							*/
void error_();

static char width_string[] = { ATTRIB_SEP, 'w', 'i', 'd', 't', 'h', '=', '\0' };

#define NPREFIX 6
static char prefix[NPREFIX][TOKEN]={"sin","cos","exp","ln","log10","int"};
static char prefixcode[NPREFIX]={'s','c','e','l','1','i'};

/************************************************************************/

char *mathexw_return_vers()
/*  Routine exists mostly to force .h file version string into this	*/
/*  module, but we could call it if we want.  Note string must not be	*/
/*  global or we'll have conflicts if another routine similarly		*/
/*  includes the version string						*/
{
  static char version[] = MATHEXW_VERSION"/"FULL_JGMATHH_VERSION;
  return version;
}

void err(s,t)
char *s,*t;
{
  error_(s,t);
  return;
}

void parsex(pcode,s,names,ntotal,new_attrs,new_count)
  /*  Since the pcode contains constants along with its encoded		*/
  /*  operators, operations, and variable numbers, can't easily		*/
  /*  calculate the size (but see jgmath.h for more info).  Further,	*/
  /*  it's hard to check for overflow because we have lots of		*/
  /*  "ptr++"-type things embedded in other statements			*/
unsigned char *pcode;
char *s;
char names[][VARNAMESIZE+1],new_attrs[][TOTATTRSIZE+1];
int *ntotal,*new_count;
{
int opstkp;
char opstack[STACK_DEPTH];	/* Each is a prefixcode	or operator	*/
int opprior[STACK_DEPTH];
char *t,*t1,tok[TOKEN+1],*attr_ptr;
int i,j,cntr;
int prio,highest_prio,defined_this_equation;
char lookop,right_side;
div_t var_div;

/* printf("%s\n",s); */

cntr = 0;

opstkp = 0;
opstack[0] = '\0';
opprior[0] = 0;

lookop = FALSE;
right_side = FALSE;
defined_this_equation = -1;

  /*  Following # is a constant but don't know how to generate it as macro */
highest_prio = (int) strlen(PRIORITY)/2-1;

strcat(s,"$");
t = s;

while (t != NULL)
  {
    if (isalpha(*t)) {


	/*  String of some sort.  Function or variable name...		*/
      i = strcspn(t,TOKEN_TERMINATORS);
      strncpy(tok,t,i);
      tok[i] = '\0';
      t += i;

      if (lookop)
        err1 (buildstring("Looking for operator, found ",tok,NULL,"Err 10"));
      else {
	for( i=0; i < NPREFIX; i++) if (strcmp(tok,prefix[i]) == 0)  break;
	if (i < NPREFIX) {
						/*  Function name...	*/
	  if (*t == *ATTR_DELIM)
	    err1 (buildstring
	      ("Attribute list found after function name ",tok,NULL,"Err 20"));
	  opstack[++opstkp] = prefixcode[i];
	  opprior[opstkp] = highest_prio;
	} else {
						/*  Variable...		*/
	    /*  Handle []s.  These enclose an attribute list		*/
	    /*  which appears at most once, after a newly defined variable. */
          if (*t == *(ATTR_DELIM+1))
	    err1 (buildstring("Unmatched ",ATTR_DELIM,NULL,"Err 30"));
	  else if (*t == *ATTR_DELIM) {
	    if (right_side) err1
	      (buildstring("Cannot redefine attributes of existing variable ",
			   tok,
			   NULL,
			   "Err 40")					      );
	    attr_ptr = t+1;
	    if (   ( t = strchr(attr_ptr,*(ATTR_DELIM+1)) ) == NULL   )
	      err1 (buildstring("Unmatched ",
			        ATTR_DELIM,
			        "s",
			        "Err 50"));
	    *(t++) = '\0';
	  } else
	    attr_ptr = NULL;

	  for (i=0; i < *ntotal; i++) if (strcmp(tok,names[i]) == 0)  break;
	  if (i == *ntotal) {
	      /*  Equation is defining a new variable			*/
	    if (right_side)
	      err1 (buildstring("Variable ",
				tok,
				" appears for first time on right of =",
				"Err 60")				);
	    if ((*new_count == MAX_NEW_VARS) || (*ntotal == NVAR))
	      err1 (buildstring("Too many vars trying to create var ",
				tok,
				NULL,
				"Err 70")			      );

              /*  Create an attribute list containing at least width    */
	      /*    We know something about the width we intend to use	*/
	      /*    for new vars.  Not clear whether we should print	*/
	      /*    according to what user says, or alter what user	*/
	      /*    to match what we will do.  Chose latter.  NEW_VAR_	*/
	      /*    WIDTH is absolute minimum, so if it's used, odds	*/
	      /*    are that displayed field is wider anyway...		*/
	    j = strlen(tok);
	    if (j < NEW_VAR_WIDTH) j = NEW_VAR_WIDTH;
	    if (attr_ptr == NULL)
	      sprintf(new_attrs[*new_count],"%s%d",width_string+1,j);
	    else {
		/*  See if width= is already in string.  Be sure it's	*/
		/*  not embedded in x_width=, etc...			*/
	      *(attr_ptr-1) = ATTRIB_SEP;	/* previously the [	*/
	      if (strstr(attr_ptr-1,width_string) == NULL)
	        sprintf(new_attrs[*new_count],"%s%s%d",attr_ptr,width_string,j);
	      else
		strcpy(new_attrs[*new_count],attr_ptr);
	      *(attr_ptr-1) = '\0';	/* not sure this is needed	*/
	    }

	      /*  Strip off trailing _s (after including them in width	*/
	      /*  if necessary; above)					*/
	    t1 = tok + strlen(tok);
	    while (*(--t1) == W_EXTEND) *t1 = '\0';

	    defined_this_equation = i;
            strcpy(names[i],tok);     
	    (*new_count)++;
	    (*ntotal)++;
	  } else {
	      /*  Existing variable.  NG if on left side of equation	*/
	      /*  If on right side, NG if defined in this equation	*/
	    if ( ! right_side )
	      err1 (buildstring("May not redefine variable ",
			        tok,
				NULL,
				"Err 80")			);
	    if (i == defined_this_equation)
	      err1 (buildstring("Variable ",
				tok,
				" appears for first time on right of =",
				"Err 90")				  );
	  }
	    /*  Use 1st bit to indicate that next 15 bits represent the	*/
	    /*  number of a variable.					*/
	  var_div = div(i,256);	  /* 256 = "1 byte's worth" of var num	*/
	  pcode[cntr++] = 128+var_div.quot;
	  pcode[cntr++] = var_div.rem;
	  lookop = TRUE;
	}
      }




    } else if ((isdigit(*t)) || (*t == '.')) {
	/*  Constant							*/
	/*    Don't know why we allow this in a lookop state...		*/
      strtod(t,&t1);	/* Just want terminating character...		*/
      if ( ! lookop) {
	pcode[cntr++] = 1;
	for (i=0; i<t1-t; i++) pcode[cntr++] = (unsigned char) *(t+i);
	pcode[cntr++] = 2; /*  Think this could be any strtod term-	*/
			   /*  inator that doesn't otherwise conflict..	*/
	lookop = TRUE;
      }
      t = t1;




    } else {
	/*  Operator of some sort					*/
      tok[0] = *t;
      tok[1] = '\0';
      prio = (int) strcspn(PRIORITY,tok)/2;
      if (lookop)
	if (*t == ')') {
	  while (prio < opprior[opstkp]) pcode[cntr++] = opstack[opstkp--];
          if (prio != opprior[opstkp]) err1 ("Right Paren Error");
	  opstkp--;
	    /*  Do not reset lookop state because this close paren	*/
	    /*  might terminate function argument			*/
	} else {
	  if (*t == '=') {
	      /*  Parse will eventually fail anyway w/o next test, but	*/
	      /*  message I got was weird				*/
	    if (right_side) err1 ("Equation contains 2 =s");
	    right_side = TRUE;
	    pcode[cntr] = 3;
	      /*  Copy variable number, which is 2 bytes that preceded	*/
	      /*  the original cntr pointer				*/
	    pcode[cntr+1] = pcode[cntr-2];
	    pcode[cntr+2] = pcode[cntr-1];
	    cntr += 3;
	  }
	  while (prio <= opprior[opstkp]) pcode[cntr++] = opstack[opstkp--];
	  if (*t == ',') {
	      /*  Finish off equation; set up for new one		*/
	      /*  (Original code put , on opstack, but it seemed to	*/
	      /*  come off in wrong spot...)				*/
	    pcode[cntr++] = *t;
	    right_side = FALSE;
	    defined_this_equation = -1;
	  } else {
	    opstack[++opstkp] = *t;
	    opprior[opstkp] = prio;
	  }
	  lookop = FALSE;
	}
      else
	switch (*t) {
          case '-':
	    opstack[++opstkp] = '#';
	    opprior[opstkp] = highest_prio;
	    break;
	  case '(':
	    opstack[++opstkp]='\0';
	    opprior[opstkp] = prio;
	    break;
	  default:
	    err1 ("Expected const, left paren or var. ");
	    break;
	}
      t++;
    }




    if (tok[0] == '$') t=NULL;
  }

pcode[cntr] = 0;	/*  This terminator is for real...		*/

return;
}

double conv(s)
char *s;
{
  double atof();

  if (isdigit(*s) || *s=='.' || *s=='-' || *s=='+') return atof(s);
  else return MISSING_VALUE_REAL;
}

int oneop(x)
double x;
{
  if (x == MISSING_VALUE_REAL) return 0;
  return 1;
}

int twoop(x,y)
double x,y;
{
  if  ( (x == MISSING_VALUE_REAL) || (y == MISSING_VALUE_REAL) )  return 0;
  return 1;
}

void execute(s,values)
unsigned char *s;
char values[][TOKEN+1];
{
char *t;
unsigned char *sp;
int i,j;
int stkp;
double stack[STACK_DEPTH];	/*  Not logically related to other	*/
				/*  stack depths, but use same constant	*/

sp=s;
stkp= -1;
while (*sp) {
  i=(int)(*sp);
		/* printf("%d %c\n",i,*sp); */

  if (i>127){
      /*  sp needs to be advanced to clear entry.  1 byte here; 1 below	*/
    j = 256*(i-128) + (int)*(++sp); /*  Variable number is next 15 bits	*/
    stack[++stkp] = conv(values[j]);
  } else
    switch(*sp){
     case 1:
       stack[++stkp] = strtod((char *)sp+1,&t);
       sp=(unsigned char *)t;
       break;
     case 3:
	 /*  Variable number is 15 bits following the "128" bit.  Ad-	*/
	 /*  vance stack pointer to clear variable number (but not	*/
	 /*  pcode value, which is skipped below).  I suspect		*/
	 /*  that none of the casting is necessary...			*/
       stack[++stkp] = (double) (256*((int)*(sp+1)-128) +(int)*(sp+2));
       sp += 2;
       break;
     case 's':
       if (oneop(stack[stkp])) stack[stkp] = sin(stack[stkp]);
       break;
     case 'c':
       if (oneop(stack[stkp])) stack[stkp] = cos(stack[stkp]);
       break;
     case 'e':
       if (oneop(stack[stkp])) stack[stkp] = exp(stack[stkp]);
       break;
     case 'l':
       if (oneop(stack[stkp])) stack[stkp] = log(stack[stkp]);
       break;
     case '1':
       if (oneop(stack[stkp])) stack[stkp] = log10(stack[stkp]);
       break;
     case 'i':
       if (oneop(stack[stkp])) stack[stkp] = floor(stack[stkp]);
       break;
     case '#':
       if (oneop(stack[stkp])) stack[stkp] = -stack[stkp];
       break;
     case '=':
       if (oneop(stack[stkp])) {
         stkp--;
         sprintf(values[(int)stack[stkp]],"%*f",NEW_VAR_WIDTH,stack[stkp+1]);
         stkp--;
       } else {
         stkp -= 2;
         strcpy(values[(int)stack[stkp+1]],MISSING_VALUE_STRING);
       }
       break;
     case '+':
       stkp--;
       stack[stkp] = (twoop(stack[stkp],stack[stkp+1])) ?
			  stack[stkp] + stack[stkp+1]  :  MISSING_VALUE_REAL;
       break;
     case '-':
       stkp--;
       stack[stkp] = (twoop(stack[stkp],stack[stkp+1])) ?
			  stack[stkp] - stack[stkp+1]  :  MISSING_VALUE_REAL;
       break;
     case '*':
       stkp--;
       stack[stkp] = (twoop(stack[stkp],stack[stkp+1])) ?
			  stack[stkp] * stack[stkp+1]  :  MISSING_VALUE_REAL;
       break;
     case '/':
       stkp--;
       stack[stkp] = (twoop(stack[stkp],stack[stkp+1])) ?
			  stack[stkp] / stack[stkp+1]  :  MISSING_VALUE_REAL;
       break;
     case '^':
       stkp--;
       if (twoop(stack[stkp],stack[stkp+1])) {
         if (stack[stkp] > 0) stack[stkp] = pow(stack[stkp],stack[stkp+1]);
         else if (stack[stkp] == 0) stack[stkp] = 0.0;
         else stack[stkp] = -pow(-stack[stkp],stack[stkp+1]);
       } else
         stack[stkp]= MISSING_VALUE_REAL;
       break;
     default:		/*  Comma, anyway...			*/
       break;
    }

  sp++;
	/* for(i=0;i<=stkp;i++)printf("%f ",stack[i]); */
	/* printf("\n"); */
}

return;
}

void err1(s)
char *s;
{
  error_("Parse error",s);
  return;
}

/*
main(argc,argv)
int argc;
char *argv[];
{
static char names[][TOKEN]={"press","temp","sal","",""};
static char values[][TOKEN]={"100","12.55","33.462","",""};
int ntotal=3;
char tmp[255];
int i;

strcpy (tmp,argv[1]);
parse (tmp,names,&ntotal);
for (i=0; i< strlen(tmp); i++) printf ("%d %c\n",(int)tmp[i],tmp[i]);
printf ("ntotal = %d\n",ntotal);
execute (tmp,values);
for (i=0; i < ntotal; i++) printf ("%s\n",values[i]);
}
*/
