next up previous contents index
Next: 8 Question files Up: $FILE Previous: 6 Objects and structures   Contents   Index

Subsections


7 Expression evaluator

The expression evaluator is due to Mark Morley. Some modifications have been made, to add the possibility to evaluate objects. It is possible to use the 4 operators +,-, *, / and parentheses. One can also define and use variables, and use the usual mathematical functions.

Everything that is not recognized by the interpreter as a command or a program is supposed to be a numerical expression. It is computed and the result is printed on screen. For instance

- interpcom -> a=2
                 2.000000
- interpcom -> b=3
                 3.000000
- interpcom -> a+b
                 5.000000
- interpcom -> cos(a)
                 -0.416147

It is possible to use numerical expressions as arguments of commands (or command files and programs) (cf. 7.5 and 6).


7.1 Variables

Variables are defined when they are assigned for the first time, except two predefined variables, e and pi. If a numerical expression contains a non defined variable, the result is set to 0. Uppercase and lowercase characters are distinguished.

Each thread has its particular sets of variables (cf. chapter 9). The same names of variables may be used in several threads, except for the variables whose name begins with a _ (underscore character), which are global variables. For instance the variable num1 may have distinct values in two threads, but _num1 will be the same in all threads. Global variables can be used by threads to communicate with each other.

Each thread has also a particular set of variables : the quick access variables. These variables begin with the character @ followed by a single letter. So there are only 52 quick access variables available for each thread. These variables are treated more quickly by the expression evaluator than the ordinary ones.

It is possible to know which variables are defined and their values by using the command varlist.


Example :

- interpcom -> varlist
                 a=2.000000
                 b=3.000000
                 @x=2.250000

Only variables defined in the running thread or global variables will be shown. It is possible to undefine variables by using the command undef.


Example :

- interpcom -> undef a
- interpcom -> varlist
                 b=3.000000
                 @x=2.250000

The instruction

undef *

will destroy all the variables defined in the running thread, and

undef **

will destroy also the global variables.


The command interpreter uses hidden variables, for the names of the dimensions of objects and for members of structures (cf. 6). These variables are not thread-specific, i.e. they are global variables. A hidden variable is simply a variable whith a name whose first character is not printable. So it is not possible to access directly these variables. Inside a program one may use the functions S_convert_int or S_convert_float. Their unique argument is a string of characters. These functions add a non printable character at the beginning of the string, send this new string to the expression evaluator and returns the evaluation of the string. For instance

S_convert_float("sq_root_of_2=sqrt(2)", flow_interp);

will create the hidden variable sq_root_of_2 (if it did not exist) and assign the square root of 2 to this variable. It is possible to define and fix hidden variables in the !var section of the initialization file. With the command initvar it is possible to use the values of hidden variables. This command will create (or modify) local variables which have the same names as the hidden ones (except of course for the first character that is omitted) and give them the values of the corresponding hidden variables. Subsequent modifications of the visible variables will not affect the values of the hidden ones.

The maximal number of variables that can be defined is 500. It is fixed in the source file interp.h :

#define MAXXVARS 500 /* Max user-defined variables */


7.2 Functions

The mathematical functions that the expression evaluator knows are in the array Funcs[] (it is an array of structures FUNCTION). This array must be defined by the user. The total number of functions is _NBFONC, which must be set by the user. An array of functions is predefined in the library, it is called Func_interp, and the number of functions defined in Func_interp is contained in the integer _NBFONC0.

The structure FUNCTION is defined as follows

typedef struct
{
   char* name;                    /* Function name                 */
   int   args;                    /* Number of arguments to expect */
   double  (*func)(double *);     /* Pointer to function           */
} FUNCTION;

The first member of the structure FUNCTION is the character string that will be used to call the function. The second member is the number of parameters of the function. It must be between 1 and the constant MAX_F_ARGS defined in interp.h (it is 10). The third member is the name of the function. It must be a function returning a double value, having only one argument : an array of double precision real numbers. In the array Func_interp the usual mathematical functions are used. It is also possible to put other functions found in some libraries of special functions, like cephes for instance, or defined by the user. The array Func_interp looks like this

FUNCTION        Funcs_interp[] =
{
   /* name, funtion to call */
   { "sin",   1,    _I_sin },      /*  0 */
   { "cos",   1,    _I_cos },      /*  1 */
   { "tan",   1,    _I_tan },      /*  2 */
   { "asin",  1,    _I_asin },     /*  3 */
   { "acos",  1,    _I_acos },     /*  4 */
   { "atan",  1,    _I_atan },     /*  5 */
   { "sinh",  1,    _I_sinh },     /*  6 */
   { "cosh",  1,    _I_cosh },     /*  7 */
   { "tanh",  1,    _I_tanh },     /*  8 */
   { "exp",   1,    _I_exp },      /*  9 */
   { "log",   1,    _I_log },      /* 10 */
   { "log10", 1,    _I_log10 },    /* 11 */
   { "sqrt",  1,    _I_sqrt },     /* 12 */
   { "floor", 1,    _I_floor },    /* 13 */
   { "ceil",  1,    _I_ceil },     /* 14 */
   { "abs",   1,    _I_fabs },     /* 15 */
   { "deg",   1,    _I_deg },      /* 16 */
   { "rad",   1,    _I_rad },      /* 17 */
   { 0 }                           /* 18 */
};

int    _NBFONC0=18;

The function _I_sin is defined as follows :

double
_I_sin(double *x)
{
    return sin(*x);
}

and the other functions in the array are defined in the same way. For instance if the program uses only the predefined array of functions, the main source file would look as follows :

 ......
FUNCTION *Funcs;

int main(int argc, char *argv[])
{ 
     Funcs = Funcs_interp;
     _NBFONC = _NBFONC0;
 ......
 }


7.3 Objects

The expression evaluator can use the values of the objects defined by the user. This is explained in the end of 6.3. For instance if the object xxx is a 2-dimensional array of real numbers, &xxx(2,5) will be the value of the term [2][5] of the array represented by xxx. It is possible also to use expressions such as &xxx(i,2*i+j), or even something like
&xxx(cos(2*z)+&tt(i,k),exp(u)). If the indices are incorrect, i.e if the integer parts of the indices are negative or bigger that the dimensions of the object, the value of the expression will be zero.

For a complex object, zzz for instance, &zzz(2,5) will be the real part of the value of the term [2][5] of the array represented by zzz. Equivalently, on can use &zzz(2,5).r, and &zzz(2,5).i will give the imaginary part.


7.4 Speed considerations

Several methods can be used to accelerate programs that use mainly the expression evaluator. The first one is to use quick access variables. It is also possible to send the instructions directly to the expression evaluator. This can be done with the instruction

[

All subsequent instructions will be sent directly to the expression evaluator until the instruction

]

is reached. Of course such sections of programs cannot contain ordinary commands or call programs. If the instruction

[0

is given instead of [, the evaluation of object terms (cf. 7.3) is disabled, and this increases again the speed of this program part. If the instruction

[1

is given, some other optimizations are done.

For instance consider the following program that can be found in the file test/com/test3.cmd

:prog1
2
0
-1
;
;
;
time 0
a=0
b=0
c=0
d=0
e=0
f=0
g=0
h=0
i=0
j=0
k=0
l=0
m=0
n=0
o=0
p=0
do z 1 #1
do x 1 1000
a=a+0.000001
b=b+0.000001
c=c+0.000001
d=d+0.000001
e=e+0.000001
f=f+0.000001
g=g+0.000001
h=h+0.000001
i=i+a+b
j=j+b+c
k=a+b+d+i+h
l=i+k+d
m=m+e+f
n=m+k+g
o=o+e+h+n
p=p+e
enddo
enddo
echof a
undef *
echo \n
;
;

It contains an inner loop that performs 1000 times 16 computations involving 16 variables. So prog1 n will make n times this inner loop. In the file test3.cmd are seven other versions of this program, called prog2 to prog8.

prog2 uses the instruction [

prog3 uses the instruction [0

prog4 uses the instruction [1

prog5 is similar to prog1, with quick access variables.

prog6 is similar to prog2, with quick access variables.

prog7 is similar to prog3, with quick access variables.

prog8 is similar to prog4, with quick access variables.

:prog8
2
0
-1
;
;
;
time 0
[1
@a=0
@b=0
@c=0
@d=0
@e=0
@f=0
@g=0
@h=0
@i=0
@j=0
@k=0
@l=0
@m=0
@n=0
@o=0
@p=0
do @z 1 #1
do @x 1 1000
@a=@a+0.000001
@b=@b+0.000001
@c=@c+0.000001
@d=@d+0.000001
@e=@e+0.000001
@f=@f+0.000001
@g=@g+0.000001
@h=@h+0.000001
@i=@i+@a+@b
@j=@j+@b+@c
@k=@a+@b+@d+@i+@h
@l=@i+@k+@d
@m=@m+@e+@f
@n=@m+@k+@g
@o=@o+@e+@h+@n
@p=@p+@e
enddo
enddo
]
echof @a
echo \n
;
;

There is also a program, called tout_3 that executes prog1 to prog8, and prints the execution time of each program. On my PentiumPro 180Mhz the execution of tout_3 100 gives the following :

 ------------------> prog1 100 
0.1 
 ------------------> time 
Time : 78 s
 ------------------> prog2 100 
0.1 
 ------------------> time 
Time : 62 s
 ------------------> prog3 100 
0.1 
 ------------------> time 
Time : 53 s
 ------------------> prog4 100 
0.1 
 ------------------> time 
Time : 40 s
 ------------------> prog5 100 
0.1 
 ------------------> time 
Time : 63 s
 ------------------> prog6 100 
0.1 
 ------------------> time 
Time : 49 s
 ------------------> prog7 100 
0.1 
 ------------------> time 
Time : 38 s
 ------------------> prog8 100 
0.1 
 ------------------> time 
Time : 26 s

In this example the most optimized program runs 3 times faster than prog1.


7.5 Numerical expressions as arguments

The functions

int convert_int(char *, flow_data *);

double convert_float(char *, flow_data *) ;

of the library can be used to parse numeric arguments. For instance if the command Xcom3 corresponding to the function

int Xcom3_cmd(int argc, char* argv[]);

needs an argument which is an integer, the function Xcom3_cmd should contain something like

    int  n;
    flow_data *flow_interp;
    
    flow_interp = (flow_data *) argv[-1];
    n  =  convert_int(argv[1], flow_interp);

In this argument the argument argv[1] will be sent to the expression evaluator and converted into a double precision number. Here flow_interp is a pointer to the flow_data structure corresponding to the running thread (cf. 9.4). The function convert_int will send the string of characters to the expression evaluator and return the integer part of the evaluation of this string. The function convert_float can be used to evaluate real arguments. This allows instructions like

- interpcom -> Xcom3 i+2

for instance.


7.6 Additional expression evaluators

Other expression evaluators can be added in an easy way, just by specifying the kind of numbers they use and the basic operations on them. Only one extra expression evaluator is contained in the library : it uses complex numbers. The supplementary expression evaluators are used like the ordinary one. In particular it is possible to define variables.


7.6.1 The expression evaluator using complex numbers

This expression evaluator is very similar to the usual one. It can be defined as the default one with the command ch_expr (cf. 7.6.3, 10).

The mathematical functions that the complex expression evaluator knows are in the array Funcs_C[] (it is an array of structures FUNCTIONC). This array must be defined by the user. The total number of functions is _NBFONC_C, which must be set by the user. An array of functions is predefined in the library, it is called Func_interp_C, and the number of functions defined in Func_interp is contained in the integer _NBFONC0_C.

FUNCTIONC Funcs_interp_C[] =
{
   /* name, funtion to call */
   { "Csin",    1,    _I_dCsin_c },        /* 0    */
   { "Ccos",    1,    _I_dCcos_c },        /* 1    */
   { "Ctan",    1,    _I_dCtan_c },        /* 2    */
   { "Csinh",   1,    _I_dCsinh_c },       /* 3    */
   { "Ccosh",   1,    _I_dCcosh_c },       /* 4    */
   { "Ctanh",   1,    _I_dCtanh_c },       /* 5    */
   { "Cexp",    1,    _I_dCexp_c },        /* 6    */
   { "Clog",    1,    _I_dClog_c },        /* 7    */
   { "Csqrt",   1,    _I_xdCsqrt },        /* 8    */
   { "Cabs",    1,    _I_CdCabs },         /* 9    */
   { 0 }                                   /* 10   */
};

int    _NBFONC0_C=10;

For instance if the program uses only the predefined array of functions, the main source file would look as follows :

 ......
FUNCTIONC *Funcs_C;

int main(int argc, char *argv[])
{ 
 ......
     Funcs_C = Funcs_interp_C;
     _NBFONC_C = _NBFONC0_C;
 ......
 }

The structure FUNCTIONC is defined as follows :

typedef struct
{
   char*      name;                   /* Function name                 */
   int        args;                   /* Number of arguments to expect */
   dcomplex   (*func)(dcomplex *);    /* Pointer to function           */
} FUNCTIONC;

Its first member is the character string that will be used to call the function. The second member is the number of parameters of the function. It must be between 1 and the constant MAX_F_ARGS defined in interp.h (it is 10). The third member is the name of the function. It must be a function returning a double precision complex value, having only one argument : an array of double precision complex numbers. In the first term of the array Func_interp_C the function _I_dCsin_c looks like this

dcomplex
_I_dCsin_c(dcomplex *x)
{
    return dCsin_c(*x);
}

(the function dCsin_c is defined in complex.c).


7.6.2 How to define new expression evaluators

Here is the part of the file interp.h that corresponds to additional expression evaluators :

typedef void  (*oper2_expr)(void *, void *, void *);
typedef void  (*oper1_expr)(void *);
typedef int   (*oper0_expr)(void *);
typedef void  (*oper3_expr)(void *, char *);
typedef void  (*oper4_expr)(void **);
typedef char* (*oper5_expr)(void *);
typedef void* (*oper6_expr)(void *);
typedef void* (*oper)(char *);

typedef struct
{
    oper1_expr    clear;
    oper1_expr    Zero;
    oper1_expr    neg;
    oper0_expr    iszero;
    oper2_expr    add;
    oper2_expr    sub;
    oper2_expr    mul;
    oper2_expr    pow;
    oper2_expr    div;
    oper3_expr    set;
    oper4_expr    Init;
    oper5_expr    print;
    oper6_expr    copy;
    oper0_expr    sign;
    int           n_oper;
    char        **oper_name;
    oper         *OPER;
    int           n_var;
    char         *desc;
} EXPREVAL_GEN;

EXPREVAL_GEN   *Expreval_ops;

typedef struct {
    char *name;
    void **value;
} Vars_Gen;

Vars_Gen  **VArs_global_Gen;
int        *_NBFONC_Gen;
typedef struct
{
   char*        name;                     /* Function name                 */
   int          args;                     /* Number of arguments to expect */
   void         (*func)(void**, void**);  /* Pointer to function           */
} FUNCTIONGen;
extern FUNCTIONGen *Funcs_Gen[];
int             NB_expr_eval;          /* Number of supplementary 
                                          evaluators */

Here it is assumed that the numbers we work with are represented by pointers to void. These pointers should actually contain the addresses of the numbers.

The directory expr_evals contains the definition of several expression evaluators. To each expression evaluator corresponds a main file in the test/extra directory that produces a test program for the interpcom library where this new expression evaluator can be used. The file main_all of the test/extra directory produces a test program where all the expression evaluators can be used. The expression evaluators in the expr_evals directory are :

-
_dummy.c : an expression evaluator that uses double precision numbers. It should work just like the ordinary expression evaluator, perhaps more slowly. The reader should look at this file to see how an expression evaluator is implemented.
-
_mapm.c : an expression evaluator that uses the library mapm (Mike's Arbitrary Precision Math Library) by Michael C. Ring This library can be found at
http://tc.umn.edu/~ringx004. The default precision used here is 50 digits. It is stored in the integer _mapm_dec_places (see the file main_mapm.c in the test directory). It would be possible to create a new command to change this default precision.
-
_GMP.c : an expression evaluator that uses the gmp library (that can be found at many places, beeing a part of the GNU project). More precisely this expression evaluator uses rational numbers, i.e. quotients of arbitrary big integers. In this expression evaluator some operations will produce nothing (i.e. give a zero result) : pow, sqrt.
-
_matrix.c : an expression evaluator that uses 2X2 matrices. It comes with the command set_mat that is used to set the values of a matrix. Example :
- interpcom -> a=1
[1.0000000000000000,0.0000000000000000,0.0000000000000000,1.0000000000000000]
- interpcom -> set_mat a 1 -2.5 4 2.1
- interpcom -> a
[1.0000000000000000,-2.5000000000000000,4.0000000000000000,2.1000000000000001]
Here also the functions pow and sqrt are not implemented.


Now we will indicate how new expression evaluators should be implemented. First of all an EXPREVAL_GEN structure must be created, with the appropriate functions that are listed below. The "numbers" that are used in the new expression evaluator must be represented by pointers to void.


Member clear : this number destroys the number represented in its argument, i.e. it frees the memory used to define this number.

Member Zero : this functions gives the value 0 to the number represented by its argument.

Member neg : this function negates the number represented by its argument.

Member iszero : this function returns 1 if its argument is the number 0, and 1 otherwise.

Member add : this function adds the two numbers represented by the two last arguments, and puts the result in the number represented by the first argument.

Member sub : this function substracts the two numbers represented by the two last arguments, and puts the result in the number represented by the first argument.

Member mul : this function multiplies the two numbers represented by the two last arguments, and puts the result in the number represented by the first argument.

Member pow : this function computes the power of the number represented by the second argument to the number represented by the third argument, and puts the result in the number represented by the first argument.

Member div : this function divides the two numbers represented by the two last arguments, and puts the result in the number represented by the first argument.

Member set : this function gives the value represented by the second argument to the number represented by the first argument.

Member Init : if the argument is void **s, this function creates a new number a puts its address in s[0].

Member print : this function returns a character string representing the number corresponding to its first argument.

Member copy : this function returns the address of a new number having the same value as the number represented by the first argument.

Member sign : this function returns the the number having represented by the first argument (i.e. -1 if the number is negative, 0 if it is 0, and 1 if it is positive).

The members n_oper, oper_name, OPER and n_var are not yet used and should be respectively 0, NULL, NULL and 1.

The functions used by the new expression evaluator should be put in a FUNCTIONGen array (see _dummy.c). Each function F should be of type

void    func(void **res, void **x)

Here res is the address of the result (so the number computed by the function is represented by res[0]), and the other argument represent the array of arguments of F.

Now the function init_expr_GEN must be created to indicate which expression evaluators will be used, and the array Funcs_Gen must be defined and filled with the new corresponding function arrays. This can be done for instance in the main source file.

For instance, if two new expression evaluators are created, we could write the following :

FUNCTIONGen *Funcs_Gen[] = {
        Funcs_1,      /* array of functions for the first additional
	                 expression evaluator */
        Funcs_2,      /* array of functions for the second additional
	                 expression evaluator */
};

void init_expr_GEN(flow_data *flow_interp)
{
    NB_expr_eval = 4;
    _NBFONC_Gen[0] = _NBFONC_1;
    Expreval_ops[0] = _exprev_1;
    _NBFONC_Gen[1] = _NBFONC_2;
    Expreval_ops[1] = _exprev_2;
}

Here NB_expr_eval is the total number of expression evaluators (the two that are defined in the library plus the two supplementary ones). The EXPREVAL_GEN structure corresponding to the first new expression evaluator is _exprev_1, which is put in Expreval_ops[0], the number of corresponding functions (defined in Funcs_1) is _NBFONC_1, which is put in _NBFONC_Gen[0], and so on.

The function init_expr_GEN can also contain some initializations needed by the new expression evaluators (see the file main_all.c where three additional expression evaluators are defined).


7.6.3 How to use additional expression evaluators

The command ch_expr is used to change the current expression evaluator.

- interpcom -> ch_expr n

(where n is an integer) will define the n-th expression evaluator as the current one.

The 0-th one is the ordinary one.

The 1-th uses complex numbers. In this case the variable I contains $\sqrt{-1}$. For instance

- interpcom -> A=2-3*I

defines the variable A containing the complex number $2-3i$.

The next expression evaluators are the supplementary ones.

The command varlist, used to give the list of all the variables of the usual expression evaluator, can also be used to see the variables of all the other ones:

- interpcom -> varlist n

gives the list of all the variables of the n-th expression evaluator, and

- interpcom -> varlist all

gives the list of all the variables of all the expression evaluators.


It is possible to use the variables of new expression evaluators in loops, when the sign of the corresponding numbers has a meaning. For example, in the following program

ch_expr 1
x=4+3*I
do i 1 10
x=x+1+I
enddo
ch_expr 0

the variable $i$ in the loop is a complex number. It is possible to let the loop use ordinary variables by using ch_expr inside the loop :

ch_expr 1
x=4+3*I
ch_expr 0
do i 1 10
ch_expr 1
x=x+1+I
ch_expr 0
enddo

In this case it is still possible to use the variable of the loop inside expressions parsed by other expression evaluators. For example

ch_expr 1
x=1
ch_expr 0
do i 1 10
ch_expr 1
x=x*!(i)
ch_expr 0
enddo

(cf. 4.5).


We show now how to call directly a new expression evaluator. This can be useful for instance if one wants to parse the arguments of a new command that needs a different kind of numbers. For this the function

int  Evaluate_Gen(int n, char *e, void **res, int *a, flow_data *flow_interp);

is used. Here n is the number of the supplementary expression evaluator (0 for the first), e contains the string that must be evaluated, result[0] will be the address of the result, and flow_interp is the address of the flow_data structure corresponding to the thread from which the function is called. For example

    void    *result;
    int      dum;

    result = NULL;
    Evaluate_Gen(n, "x=2", &result, &dum, flow_interp);
        /* Makes "x=2" in the additional expression evaluator n */

    if (result != NULL) {
        ... 
        Expreval_ops[n].clear(result); 
           /* frees the memory occupied by the result */
    }

Note that in this example, the expression evaluator used in the thread is flow_interp->expr_ev, corresponding to n = flow_interp->expr_ev - 2). But it is possible to use a different n.


next up previous contents index
Next: 8 Question files Up: $FILE Previous: 6 Objects and structures   Contents   Index
jmdr 2001-12-07