diff options
author | rhaas <rhaas@17b73243-c579-4c4c-a9d2-2d5706c11dac> | 2012-04-11 20:50:07 +0000 |
---|---|---|
committer | rhaas <rhaas@17b73243-c579-4c4c-a9d2-2d5706c11dac> | 2012-04-11 20:50:07 +0000 |
commit | ce35b1de9d17c8cec1fa6fc60d9b25ba1bfcb295 (patch) | |
tree | a8c7d4094f2042e51f751e1b76e4bf9fa6067fa4 /src | |
parent | 92f8622dbef1d7322bf36500a947ba4474035504 (diff) |
Allow arithmetic expression in ParameterSet
Expression are of the form:
foo::bar = 2*sin(foo:baz)
ie. arithmetic and access to already set parameters. The new behaviour is
triggered if the parameter string (for real, boolean and int parameters) does
not parser properly as a double/int/bool. This last test is mostly an
optimization.
The largest change is actually in the expression parser which has been extended
to handle eg. exponential notation and negations. It now uses a state machine
to parse its input.
git-svn-id: http://svn.cactuscode.org/flesh/trunk@4797 17b73243-c579-4c4c-a9d2-2d5706c11dac
Diffstat (limited to 'src')
-rw-r--r-- | src/include/utili_Expression.h | 2 | ||||
-rw-r--r-- | src/main/Parameters.c | 300 | ||||
-rw-r--r-- | src/util/Expression.c | 191 |
3 files changed, 410 insertions, 83 deletions
diff --git a/src/include/utili_Expression.h b/src/include/utili_Expression.h index 148151a3..ad3d491c 100644 --- a/src/include/utili_Expression.h +++ b/src/include/utili_Expression.h @@ -31,6 +31,8 @@ typedef enum {OP_NONE, OP_TIMES, OP_POWER, OP_NOT, + OP_NEGATE, + OP_PASS, OP_ACOS, OP_ASIN, OP_ATAN, diff --git a/src/main/Parameters.c b/src/main/Parameters.c index d4c5c0a7..a1f84d93 100644 --- a/src/main/Parameters.c +++ b/src/main/Parameters.c @@ -14,6 +14,8 @@ #include <stdlib.h> #include <string.h> #include <stdarg.h> +#include <math.h> +#include <assert.h> #include "cctk_Flesh.h" #include "cctk_ActiveThorns.h" @@ -183,6 +185,7 @@ static int ParameterSetSentence (t_param *param, const char *value); static int ParameterSetInteger (t_param *param, const char *value); static int ParameterSetReal (t_param *param, const char *value); static int ParameterSetBoolean (t_param *param, const char *value); +static int SetVarEvaluator(int nvars, const char * const *vars, uExpressionValue *vals, void *data); static void GetBaseName(const char *name, char **basename, int *array_index); static char *ArrayParamName(const char *basename,int array_index); @@ -2185,10 +2188,47 @@ static int ParameterSetInteger (t_param *param, const char *value) const t_range *range; char *endptr; - retval = -1; + retval = 0; + + /* try parsing as number */ inval = strtol (value, &endptr, 0); - if(!*endptr) + if(*endptr) /* if we could not parse as a number, try an expression */ + { + int type = PARAMETER_INT; + int ierr; + uExpressionValue val; + uExpression *expr; + + expr = Util_ExpressionParse(value); + assert(expr); + ierr = Util_ExpressionEvaluate(expr, &val, SetVarEvaluator, &type); + Util_ExpressionFree(expr); + + if (ierr == 0) + { + assert(val.type == ival || val.type == rval); + + if(val.type == ival) + { + inval =(int)val.value.ival; + } + else if(fabs(round(val.value.rval) - val.value.rval) < 1e-12) /* enforce integer result */ + { + inval = (int)round(val.value.rval); + } + else + { + retval = -6; + } + } + else + { + retval = -6; + } + } + + if (!retval) { for (range = param->props->range; range; range = range->next) { @@ -2208,10 +2248,6 @@ static int ParameterSetInteger (t_param *param, const char *value) } } } - else - { - retval = -6; - } if (retval == -1) { @@ -2235,12 +2271,12 @@ static int ParameterSetInteger (t_param *param, const char *value) static int ParameterSetReal (t_param *param, const char *value) { int retval; - unsigned int p; const t_range *range; double inval; char *temp; char *endptr; + retval = 0; /* * Canonicalize the string by converting all exponent letters @@ -2248,7 +2284,7 @@ static int ParameterSetReal (t_param *param, const char *value) * to do the actual conversion) only groks [eE]. */ temp = strdup (value); - for (p = 0; p < strlen (temp); p++) + for (unsigned int p = 0; p < strlen (temp); p++) { if (temp[p] == 'E' || temp[p] == 'd' || temp[p] == 'D') { @@ -2257,11 +2293,40 @@ static int ParameterSetReal (t_param *param, const char *value) } } - retval = -1; + /* try parsing as number */ inval = strtod (temp, &endptr); + free(temp); + + if (*endptr) /* if we cannot parse as a number, try expression */ + { + int type = PARAMETER_REAL; + int ierr; + uExpressionValue val; + uExpression *expr; + + expr = Util_ExpressionParse(value); + assert(expr); + ierr = Util_ExpressionEvaluate(expr, &val, SetVarEvaluator, &type); + Util_ExpressionFree(expr); + + if (ierr == 0) + { + assert(val.type == ival || val.type == rval); + + if (val.type == ival) + inval =(int)val.value.ival; + else + inval = val.value.rval; + } + else + { + retval = -6; + } + } - if(!*endptr) + if (!retval) { + retval = -1; for (range = param->props->range; range; range = range->next) { if (CCTK_IsThornActive (range->origin) || @@ -2280,12 +2345,6 @@ static int ParameterSetReal (t_param *param, const char *value) } } } - else - { - retval = -6; - } - - free (temp); if (retval == -1) { @@ -2308,10 +2367,48 @@ static int ParameterSetReal (t_param *param, const char *value) static int ParameterSetBoolean (t_param *param, const char *value) { - int retval; + const int type = PARAMETER_BOOLEAN; + int retval, inval; + uExpressionValue val; + uExpression *expr; + + /* first try parsing as yes/no/true/false */ + retval = CCTK_SetBoolean (&inval, value); + if(retval) /* if we cannot parse as a boolean, try expression */ + { + expr = Util_ExpressionParse(value); + assert(expr); + int ierr = Util_ExpressionEvaluate(expr, &val, SetVarEvaluator, (void *)&type); + Util_ExpressionFree(expr); + + if (!ierr) + { + assert(val.type == ival || val.type == rval); + if (val.type == ival) + { + inval =(int)val.value.ival; + retval = 0; + } + else if(fabs(round(val.value.rval) - val.value.rval) < 1e-12) /* enforce integer result */ + { + inval = (int)round(val.value.rval); + retval = 0; + } + else + { + retval = -1; + } + } + else + { + retval = -1; + } + } + + if (!retval) + *(CCTK_INT *)param->data = inval != 0; - retval = CCTK_SetBoolean (param->data, value); if (retval == -1) { CCTK_VWarn (2, __LINE__, __FILE__, "Cactus", @@ -2493,6 +2590,173 @@ static int AccVarEvaluator(int nvars, const char * const *vars, uExpressionValue } /*@@ + @routine SetVarEvaluator + @date Wed Oct 26 16:25:49 PDT 2011 + @author Roland Haas + @desc + Routine called from the expression parser to evaluate + the vars in an parameter expression + @enddesc + + @var nvars + @vdesc Number of variables to evaluate + @vtype int + @vio in + @vcomment + + @endvar + @var vars + @vdesc an array of variable names + @vtype const char * const * + @vio in + @vcomment + Can be anything that GetParamter will recognize. Must be of type CCTK_INT, + CCTK_BOOLEAN or CCTK_REAL. + @endvar + @var vals + @vdesc Output array to hold values + @vtype uExpressionValue * + @vio out + @vcomment + + @endvar + @var data + @vdesc Data passed from expression evaluator + @vtype void * + @vio in + @vcomment + Not used. + @endvar + + @returntype int + @returndesc + 0 + @endreturndesc + @@*/ +static int SetVarEvaluator(int nvars, const char * const *vars, uExpressionValue *vals, void *data) +{ + const int restype = *(int *)data; + int retval = 0; + + for (int i=0; i < nvars; i++) + { + int ierr; + + if (strstr(vars[i], "::")) /* a variable [parameter] */ + { + const void *paramval; + int type; + char *name, *thorn; + + ierr = Util_SplitString(þ, &name, vars[i], "::"); + if (!ierr) + { + paramval = CCTK_ParameterGet(name, thorn, &type); + if (paramval) + { + switch(type) + { + case PARAMETER_REAL: + vals[i].type = rval; + vals[i].value.rval = *(CCTK_REAL *)paramval; + ierr = 0; + break; + case PARAMETER_INT: + vals[i].type = ival; + vals[i].value.ival = *(CCTK_INT *)paramval; + ierr = 0; + break; + case PARAMETER_BOOLEAN: + vals[i].type = ival; + vals[i].value.ival = *(CCTK_INT *)paramval; + ierr = 0; + break; + default: + CCTK_VWarn (0, __LINE__, __FILE__, "Cactus", + "SetVarEvaluator: cannot handle type %d for parameter '%s::%s'. Only REAL, INT and BOOLEAN are supported.", + type, thorn, name); + ierr = -1; + break; + } + } + else + { + CCTK_VWarn (2, __LINE__, __FILE__, "Cactus", + "SetVarEvaluator: could not find '%s::%s'", + thorn, name); + ierr = -1; + } + + free(thorn); + free(name); + } + else + { + CCTK_VWarn (2, __LINE__, __FILE__, "Cactus", + "SetVarEvaluator: cannot split '%s' into thorn::parameter: %d", + vars[i], ierr); + ierr = -1; + } + } + else /* a direct value */ + { + char *endptr, *temp; + if(restype == PARAMETER_BOOLEAN && CCTK_SetBoolean(&vals[i].value.ival, vars[i]) == 0) + { + vals[i].type = ival; + ierr = 0; + } + else if (strpbrk(vars[i], "eDdD.")) + { + /* + * Canonicalize the string by converting all exponent letters + * (we allow [eEdD]) to 'e', since strtod(3) (which we will use + * to do the actual conversion) only groks [eE]. + */ + temp = strdup (vars[i]); + for (unsigned int p = 0; p < strlen (temp); p++) + { + if (temp[p] == 'E' || temp[p] == 'd' || temp[p] == 'D') + { + temp[p] = 'e'; + break; + } + } + + vals[i].type = rval; + vals[i].value.rval = (CCTK_REAL)strtod (temp, &endptr); + if (!*endptr) + ierr = 0; + else + ierr = -1; + } + else + { + vals[i].type = ival; + vals[i].value.ival = (CCTK_INT)strtol (vars[i], &endptr, 0); + if (!*endptr) + ierr = 0; + else + ierr = -1; + } + } + + if (ierr) + { + CCTK_VWarn (2, __LINE__, __FILE__, "Cactus", + "SetVarEvaluator: Unable to set value - '%s' " + "does not evaluate to a real or integer or boolean", + vars[i]); + vals[i].type = rval; + vals[i].value.rval = (double)atof("nan"); + retval = ierr; + } + } + + return retval; +} + + /*@@ @routine AddAccumulators @date Mon May 20 07:27:09 2002 @author Tom Goodale diff --git a/src/util/Expression.c b/src/util/Expression.c index cf93b1be..1984cdc5 100644 --- a/src/util/Expression.c +++ b/src/util/Expression.c @@ -31,7 +31,10 @@ CCTK_FILEVERSION(util_Expression_c); #ifdef strdup #undef strdup #endif +#include "util_String.h" #define strdup(a) Util_Strdup(a) +#else +#define CCTK_VWarn(lvl, line, file, thorn, fmt, ...) printf("(%s) L%d at %s line %d: "fmt"\n", thorn, lvl, file, line, ## __VA_ARGS__) #endif /******************************************************************** @@ -103,6 +106,10 @@ static struct {">=", binary, 1,OP_GEQUALS}, {"&&", binary, 2,OP_AND}, {"||", binary, 2,OP_OR}, + /* Sign change. */ + {"_", unary, 3,OP_NEGATE}, /* unary '-' */ + {"@", unary, 3,OP_PASS}, /* unary '+' */ + /* Binary operators. */ {"+", binary, 3,OP_PLUS}, {"-", binary, 3,OP_MINUS}, {"/", binary, 4,OP_DIV}, @@ -191,7 +198,7 @@ uExpression Util_ExpressionParse(const char *expression) temp = list; /* Convert the list into a string in RPN order */ - if(!RPParse(&temp, buffer)) + if(!RPParse(&temp, buffer) && temp == NULL) { /* Check if it is a valid expression */ if(!VerifyParsedExpression(buffer)) @@ -308,7 +315,7 @@ int Util_ExpressionEvaluate(const uExpression buffer, case ival: printf("%d " ,stack[stackpointer-2].value.ival); break; case rval: - printf("%f " ,stack[stackpointer-2].value.rval); break; + printf("%g " ,stack[stackpointer-2].value.rval); break; default: ; } @@ -320,7 +327,7 @@ int Util_ExpressionEvaluate(const uExpression buffer, case ival: printf("%d " ,stack[stackpointer-1].value.ival); break; case rval: - printf("%f " ,stack[stackpointer-1].value.rval); break; + printf("%g " ,stack[stackpointer-1].value.rval); break; default: ; } @@ -352,7 +359,7 @@ int Util_ExpressionEvaluate(const uExpression buffer, case ival: printf("%d\n" ,stack[stackpointer-1].value.ival); break; case rval: - printf("%f\n" ,stack[stackpointer-1].value.rval); break; + printf("%g\n" ,stack[stackpointer-1].value.rval); break; default: ; } @@ -460,80 +467,114 @@ static pToken *Tokenise(const char *expression) const char *tokenend; const char *position; + /* class '@' below is "everything else", "e" can be either scientific + * notation or the literal character 'e' */ + const char *classes[] = {"-+", "0123456789", ".", "eEdD", "*/^=&|<>!", "(", ")", " \t", "@"}; + /* if we are in state s and read a charcter of class c we transition to state + * states[s][c] */ + /* if this is -1 then this is a transition that does not happen in well + * formed input and we complain, then transition to state 0 */ + int states[][sizeof(classes)/sizeof(classes[0])] = { + /* - 0 . e * ( ) ' ' @ */ +/* 0*/ {10, 2, 3, 9, -1, 0, -1, 0, 9}, /* beginning of subexpression */ +/* 1*/ {-1, 2, 3, 9, 1, 0, -1, 1, 9}, /* after binary operator or '!' */ +/* 2*/ { 1, 2, 3, 4, 1, -1, 5, 5, -1}, /* after first digit of integer */ +/* 3*/ {-1, 7, -1, 4, 1, -1, 5, 5, -1}, /* after decimal point */ +/* 4*/ { 8, 8, -1, -1, -1, -1, -1, -1, -1}, /* after 'e' of exponent */ +/* 5*/ { 1, -1, -1, -1, 1, -1, 5, 5, -1}, /* after space after number */ +/* 6*/ { 1, -1, -1, -1, 1, 0, 5, 6, -1}, /* after space after name*/ +/* 7*/ {-1, 7, -1, 4, 1, -1, 5, 5, -1}, /* after first digit of fraction */ +/* 8*/ {-1, 9, -1, -1, 1, -1, 5, 5, -1}, /* after first digit of exponent */ +/* 9*/ { 1, 9, -1, 9, 1, 0, 5, 6, 9}, /* after first letter of name */ +/*10*/ {-1, 2, 3, 9, -1, 0, -1, 10, 9}, /* after unary operator */ + }; + /* we terminate a token whenever we transition out of a state we check here + * is this transition could be a termination of a token */ + int end_of_token[][sizeof(classes)/sizeof(classes[0])] = { + /* - 0 . e * ( ) ' ' @ */ +/* 0*/ { 1, 1, 1, 1, -1, 1, -1, 0, 1}, /* beginning of subexpression */ +/* 1*/ {-1, 1, 1, 1, 0, 1, -1, 0, 1}, /* after binary operator or '!' */ +/* 2*/ { 1, 0, 0, 0, 1, 1, 1, 1, -1}, /* after first digit of integer */ +/* 3*/ {-1, 0, -1, 0, 1, -1, 1, 1, -1}, /* after decimal point */ +/* 4*/ { 0, 0, -1, -1, -1, -1, -1, -1, -1}, /* after 'e' of exponent */ +/* 5*/ { 1, -1, -1, -1, 1, -1, 1, 0, -1}, /* after space after number */ +/* 6*/ { 1, -1, -1, -1, 1, 1, 1, 0, -1}, /* after space after name*/ +/* 7*/ {-1, 0, -1, 0, 1, -1, 1, 1, -1}, /* after first digit of fraction */ +/* 8*/ {-1, 0, -1, -1, 1, -1, 1, 1, -1}, /* after first digit of exponent */ +/* 9*/ { 1, 0, -1, 0, 1, 1, 1, 1, 0}, /* after first letter of name */ +/*10*/ {-1, 0, 0, 0, -1, 1, -1, 1, 0}, /* after unary operator */ + }; + int tokenstate, state, class, error; + start = NULL; current = NULL; tokenstart = expression; - + state = 0; while(*tokenstart) { + /* Remove leading whitespace */ for(; *tokenstart == ' ' || *tokenstart == '\t'; tokenstart++); tokenend = NULL; + tokenstate = -1; - position = tokenstart; + /* classify first input character */ + for(class = 0; class < (int)(sizeof(classes)/sizeof(classes[0]))-1; class++) + { + if(strchr(classes[class], *tokenstart)) + break; + } + if(states[state][class] >= 0) + state = states[state][class]; + else + state = 0; +#ifdef TEST_EXPRESSION_PARSER + printf("Initial state: %d, token '%c', class '%c'\n", state, *tokenstart, classes[class][0]); +#endif - for(position=tokenstart; *position && *(position+1); position++) + error = 0; + for(position = tokenstart+1; *position && *tokenstart; position++) { - switch(*(position+1)) + /* classify input character */ + for(class = 0; class < (int)(sizeof(classes)/sizeof(classes[0]))-1; class++) { - case '+' : - case '-' : - case '/' : - case '*' : - case '^' : - case '(' : - case ')' : - case '<' : - case '>' : - tokenend = position; break; - case '=' : - if(*position != '<' && *position != '>') - { - tokenend = position; - } - break; - case '&' : - if(*position != '&') - { - tokenend = position; - } + if(strchr(classes[class], *position)) break; - case '|' : - if(*position != '|') - { - tokenend = position; - } - break; - default : - switch(*(position)) - { - case '+' : - case '-' : - case '/' : - case '*' : - case '^' : - case '(' : - case ')' : - case '=' : - case '&' : - case '|' : - tokenend = position; break; - case '<' : - case '>' : - if(*(position+1) && *(position+1) != '=') - { - tokenend = position; - } - break; - default : - ; - } + } + + /* see if transition closes the previous token */ + if(end_of_token[state][class]) + { + tokenend = position-1; + tokenstate = state; + } + +#ifdef TEST_EXPRESSION_PARSER + printf("read '%c', current state: %d, class '%c', will transition to %d, token = '%.*s', will %sappend token\n", + *position, state, classes[class][0], states[state][class], (int)(position-tokenstart), tokenstart, end_of_token[state][class] ? "" : "not "); +#endif + + /* transition to new state or state 0 upon errors */ + if(states[state][class] >= 0) + { + state = states[state][class]; + } + else + { + state = 0; + error = 1; } if(tokenend) { + if(error) + { + CCTK_VWarn (2, __LINE__, __FILE__, "Cactus", + "Tokenise: Parser in error state. Faulty token '%.*s', next input '%c'.", + (int)(position-tokenstart), tokenstart, *position); + } break; } } @@ -549,6 +590,15 @@ static pToken *Tokenise(const char *expression) if(new) { + if(tokenstate == 10) /* after we have seen a "-+" at beginning of subexpression */ + { + /* replace unary "+-" by different names */ + if(!strcmp(new->token, "+")) + new->token[0] = '@'; + else if(!strcmp(new->token, "-")) + new->token[0] = '_'; + } + /* Insert on list */ if(current) { @@ -1008,6 +1058,12 @@ static int EvaluateUnary(uExpressionValue *retval, case OP_NOT : \ (retval) = !(val); \ break; \ + case OP_PASS : \ + (retval) = +(val); \ + break; \ + case OP_NEGATE : \ + (retval) = -(val); \ + break; \ case OP_ACOS : \ (retval) = acos(val); \ break; \ @@ -1063,8 +1119,8 @@ static int EvaluateUnary(uExpressionValue *retval, if(value->type==ival) { - retval->type=rval; - EVALUATEUNARY(retval->value.rval,value->value.ival); + retval->type=ival; + EVALUATEUNARY(retval->value.ival,value->value.ival); } else /* if(val->type==rval) */ { @@ -1544,18 +1600,23 @@ static void printtokens(pToken *start) int evaluator(int nvars, const char * const *vars, uExpressionValue *vals, void *data) { int i; + char *endp; for(i = 0; i < nvars; i++) { - if(strchr(vars[i],'.')) + if(strchr(vars[i],'.') || strchr(vars[i],'e')) { vals[i].type = rval; - vals[i].value.rval = strtod(vars[i], NULL); + vals[i].value.rval = strtod(vars[i], &endp); + if(*endp) + printf("Unrecognisable number format '%s' read as %g, unread '%s'\n", vars[i], vals[i].value.rval, endp); } else { vals[i].type = ival; - vals[i].value.ival = strtol(vars[i], NULL,0); + vals[i].value.ival = strtol(vars[i], &endp,0); + if(*endp) + printf("Unrecognisable number format '%s' read as %d, unread '%s'\n", vars[i], vals[i].value.ival, endp); } } @@ -1588,7 +1649,7 @@ int main(int argc, char *argv[]) } else { - printf("Value is %f\n", value.value.rval); + printf("Value is %g\n", value.value.rval); } Util_ExpressionFree(buffer); |