SeExpr
|
Getting started with SeExpr is relatively easy. SeExpr gives you a way to evaluate one or many evaluations of an expression. What changes between different applications of expressions is mainly the particular variables that are accessible (sometimes also the set of functions). Each application of expressions generally has it's own subclass of Expression that gets instantiated. To get started we're going to go through a simple application that is an ascii graphing calculator. This is located in the src/demos/asciiGraph.cpp part of the source tree.
./asciiGraph "val=.5*PI*x;7*sin(val)/val"we would get
| | ### # |# ## |## # | # ## | ## # | # ## | ## # | # # | ## #### # | # #### #######-----##--###-----#-----|----##-----##--###-----###### ###### ## # | # # ###### ## ## | ## ## ### | ### | | |or if we did
./asciiGraph "x-3"we'd get
| #### | #### ------------------------------|-----------------####-------- | #### | ##### | ##### | #### #### #### | #### | #### | ##### | #### | #### | #### | |
class GrapherExpr:public SeExpr2::Expression { public: //! Constructor that takes the expression to parse GrapherExpr(const std::string& expr) :SeExpr2::Expression(expr) {} ... };
class GrapherExpr:public SeExpr2::Expression { ... //! Simple variable that just returns its internal value struct SimpleVar:public SeExpr2::ExprVarRef { SimpleVar() : SeExpr2::ExprVarRef(SeExpr2::ExprType().FP(1).Varying()), val(0.0) {} double val; // independent variable void eval(double* result) {result[0] = val;} void eval(const char** result) {} } ... };Once we have this we need an instance to store our variable and provide a reference to that. We make it mutable, because resolveVar() is const. One does not need to store a variable reference in a given expression. In fact, it may be useful to use the same ExprVarRef from multiple expressions! For example, if you have 10 expressions that all have access to the same variables, this is an important optimization.
class GrapherExpr:public SeExpr2::Expression { ... mutable SimpleVar x; ... };
//! resolve function that only supports one external variable 'x' SeExpr2::ExprVarRef* resolveVar(const std::string& name) const { if(name == "x") return &x; return 0; } ... };
class GrapherExpr:public SeExpr2::Expression { ... void setX(double x_input) {x.val=x_input;} ... };
GrapherExpr expr("x+x^2");However, there might be errors in the expression you must check with isValid() before you can evaluate. Then you can print a parse error as well
if(!expr.isValid()){ std::cerr"expression faield "<<expr.parseError()<<std::endl; exit(1); }Finally, we can loop through all the x points in the graph and find out the y value as follows:
// evaluate the graph const int samplesPerPixel=10; const double one_over_samples_per_pixel=1./samplesPerPixel; for(int i=0;i<w;i++){ for(int sample=0;sample<samplesPerPixel;sample++){ // transform from device to logical coordinatex double dx=double(sample)*one_over_samples_per_pixel; double x=double(dx+i)/double(w)*(xmax-xmin)+xmin; // prep the expression engine for evaluation expr.setX(x); // evaluate and pull scalar value Vec3d val=expr.evaluate(); double y=val[0]; // transform from logical to device coordinate int j=(y-ymin)/(ymax-ymin)*h; // store to the buffer if(j>=0 && j<h) buffer[i+j*w]='#'; } } for(int i=0;i<w;i++){
./asciiGraphBe sure to put the expressions in quotation marks i.e. Some fun examples.
./asciiGraph 'x' ./asciiGraph 'x^2' ./asciiGraph '.1*x^3-2*x' ./asciiGraph 'fit(noise(.5*x),0,1,-10,10)' ./asciiGraph 'fit(smoothstep(x,-8,8),0,1,-9,9)'