/* ******************************************************************* * * * 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; i127){ /* 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]); } */