Objects and structures

An object is an array of numbers (integers, real or complex, in simple or double precision) which has a name. A structure will have members which are objects or other structures or variables of the expression evaluator.
 
 
 


5.1 Object types

The object types are structures of the following type :

typedef  struct  TYPE_OBJET {
    int  type;
    int  nombre;
    int  nbdim;
    char  **nom_dim;
    char  *nom;
    char  *comment;
}  obj_typ;

This structure is not supposed to be used by anything except the interpreter itself. The user of the interpreter will have to manipulate objects rather than object types.

type  is the type of variables contained in the arrays :

- type 0 is for integers,

- type 1 for real numbers in simple precision,

- type 2 for real numbers in double precision,

- type 3 for complex numbers in simple precision  x + iy .

- type 4 for complex numbers in double precision  x + iy .

- type 5 for complex numbers in simple precision in polar coordinates .

- type 6 for complex numbers in double precision in polar coordinates .

The way complex numbers are represented is explained in section 10. There is an extra possible type : type 7 (undetermined type, cf. section 5.4).

nombre  is the maximal number of objects of this type that will be allowed.

nbdim  is the number of dimensions of the arrays. It is at least 1.

nom_dim[i]  will be the name of the i-th dimension of the arrays. When the objects will be created, their dimensions will be fixed using hidden variables having these names which are known by the expression evaluator.

nom  is the name of the command of the interpreter that is used to create objects of this type.

comment  is a description of the object. It is used only by the command list which gives the list of all the objects that have been created.
 
 
 
 
 


5.2 Objects

An object is a structure of this kind :

typedef  struct   _OBJET {
    obj_typ   *typ_obj;
    char   *adresse;
    int   occup;
    char   *nom_obj;
    int  *dim;
}  obj;

typ_obj  is the address of the object type of the object (cf. section 5.1).

adresse  is the address of the array. It is  NULL  if the object is not already initialized.

occup  is 0 if the object has not been initialized, 1 otherwise. If it is 1, the array is allocated.

nom_obj  is the name of the object.

dim[i]  is the  i-th dimension of the array.
 
 
 
 


5.3 How to create and access objects


Object types are created in the initialization file (cf. section 2.5). This allows objects to be created when the interpreter is running.

The object types that are available when the interpreter is running are contained in the array

obj_typ   *Obj_typ;

If the section  !def  of the initialization file contains for example the following :

!def
;Objects  of  type 1 (comment line)
defobj1
-1
0
2
Objects  of  type 1 :
ndim1_1
ndim1_2
nb1
;Objects  of  type  2 (comment line)
defobj2
0
2
3
Objects  of  type 2 :
ndim2_1
ndim2_2
ndim2_3
nb2
.

2 object types will be created, Obj_typ[0]  and  Obj_typ[1] .

The objects are contained in the array

obj   **Obj;

Obj[i][j]  will contain the  j-th object of type i . The type 0 corresponds to the first type of objects created in the initialization file, the type 1 to the second type of objects created in the initialization file, and so on.

When the interpreter is running, objects are created using the corresponding command. For example, in the preceeding example, the command

interpreter -> defobj2 xxx

will create an object of the second type, whose name is  xxx . Of course the dimensions of the arrays must have been defined previously. In the example the hidden variables  ndim2_1 , ndim2_2  and ndim2_3  (which are the names of the dimensions of the arrays) must have been filled with the appropriate dimensions. The way to do this will be explained later. In the previous example the function

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

is the one that is called to create objects. This is the function that implements the command  objdef . In the above example, defobj2  is in fact the following program

:defobj2
3
0
0
objdef  1  #1

The 1 after  objdef  means that the command  defobj2  will create objects of type 1 (which is the second type of objects defined in the initialization file). When the
initialization file is read, a program of this type is created for each object type. So the command

interpreter -> defobj2   xxx

is equivalent to this one :

interpreter -> objdef  1  xxx

but the first one is more explicit. It is possible to define many objects in one command, using arrays of objects. For example the command

interpreter -> defobj2   zz[3]

will define 3 objects, zz[1] , zz[2] and zz[3] . Note that it is now possible to use the name zz alone for another object.

Now we show how to fix the dimensions of the arrays. This is done using the function

int   S_convert_int (char *);

For example, if  ndim2_1 must be 100, one can do as follows :

char  h[100];

memset(h, 0, 100);
sprintf(h, "ndim2_1=%[d", 100);
S_convert_int(h);

Then the value of  ndim2_1  will be 100. This means that indices can be anything between 0 and 100 (including these values).

The simplest case is when the objects of type  defobj2  have always the same dimension. The variables  ndim2_1 , ndim2_2  and  ndim2_3 are then filled once for all in the beginning of the execution of the interpreter (or when one enters a running mode), or by a command before the creation of the objects. One can also assign values to variables by using the section !var  of the initialisation file (cf. section 2.3). But in some cases it may be useful to be able to create objects with non constant dimensions. This can be done by creating an extra command of creation of objects. For example suppose that I want to create the command Def_Obj2 that should be used in the following way :

interpreter -> Def_Obj2   xxx   nn1   nn2   nn3

should create an object of type  defobj2 whose name is  xxx , whith dimensions ndim2_1 = nn1 , ndim2_2 = nn2 and  ndim2_3 = nn3 . Here nn1 , nn2  and  nn3 could be integers or numeric expressions that the expression evaluator can parse. The command is implemented as follows

int
Def_Obj2_cmd(int argc, char *argv[])
{
    int   n1, n2, n3;
    char   h[100], *k[3];

    n1 = convert_int(argv[2]);
    n2 = convert_int(argv[3]);
    n3 = convert_int(argv[4]);
    memset(h, 0, 100);
    sprintf(ndim2_1=%[d", n1);
    S_convert_int(h);
    memset(h, 0, 100);
    sprintf(ndim2_2=%[d", n2);
    S_convert_int(h);
    memset(h, 0, 100);
    sprintf(ndim2_3=%[d", n3);
    S_convert_int(h);
    k[0] = ch_copy("objdef");
    k[1] = ch_copy("1");
    k[2] = ch_copy(argv[1]);
    obj_create(3, k);
    free(k[0]);
    free(k[1]);
    return 0;
}

Here the function  obj_create  is called. This means also that we emulate the command objdef described previously. Of course the function Def_Obj2_cmd could be improved. One should test for example the positivity of n1 , n2  and  n3  before calling  obj_create . One should also be sure that the name of the object (which is  argv[1] ) is not already used. This can be done with the function

int   sketch_obj(char *, int *);

that will be described later. One could also verify that the creation of the object was successful. If it is not the case, the function obj_create  returns -1. If there is a test of the value returned by  obj_create , it is no longer necessary to verify if the name of the object has already been used. In the preceeding example the function  convert_int  is used to parse numeric arguments (cf. sections 6.4 and 9).

Now we show how to access objects which have been created. Suppose that we have a command, called  Xcom1 , that uses an object of type  defobj2 . So the command should be used as follows :

interpreter -> Xcom1 xxx

where the argument  xxx  is supposed to be the name of an object of type  defobj2 . The function that implements this command could be as follows :

int
Xcom1_cmd(int argc, char *argv[])
{
    int   iw, i0, *dim;
    double   ***A;

    iw = sketch_obj_restr(argv[1], &i0,2) ;
    if (iw != 2) {
        error_mess(3);
        return 1;
    }
    dim = Obj[1][i0].dim;
    A = (double ***) Obj[1][i0].adresse;

/* Here should follow the useful part of the function */

    return 0;
}

The function

int   sketch_obj_restr (char *str, int *i0, int j);

works as follows : if  str  contains the name of an object of type   j-1  it returns j . Otherwise it returns 0. Recall that the first kind of objects defined in the initialization file has type 0, the second has type 1, and so on. If the function does not return 0,  i0  will be the index of the object of type  j-1  that has the given name. So the object will be  Obj[j-1][i0] . This function can be used also with structures instead of objects; in this case negative values for  j are used (cf.  section 5.8). In the above example we test if the string given as argument for the command  Xcom1  is the name of an object  defobj2 (i.e an object of type 1). If it is not the function prints an error message (cf. section 2.9) and returns. If the name is correct, we know that this object is  Obj[1][i0] . The member  dim = Obj[1][i0].dim  will give the dimensions of the corresponding array (a 3-dimensional array of double precision real numbers). The dimensions of this array are dim[0] , dim[1]  and dim[2] . The array is  A = (double ***) Obj[1][i0].adresse . It can then be used.

Another function can be used to extract an object from its name.

int   sketch_obj (char *str, int *i0);

If  str  contains the name of an object of type j this function returns  j+1 , and the object is  Obj[j][i0] . The function returns a negative value if  str  is the name of a structure, and 0 if the name has not been used at all. This function can be used if the type of the object is not known a priori. Otherwise, the function  sketch_obj_restr  is much faster (especially if many object types are defined).

It is possible to know the value of some element of an object from the interpreter. For example, recall that objects of type  defobj2  are 3-dimensional arrays of double precision real numbers. If  xxx  is one such object,  &xxx(1,2,5)  will be interpreted by the expression evaluator as the corresponding term of the array defined by  xxx .
 

Example :

interpreter -> defobj2  xxx
interpreter -> const  xxx  1
interpreter -> &xxx(1,2,5)
1.000000
interpreter -> i=2
2.000000
interpreter -> j=3
3.000000
interpreter -> x=5.5
interpreter -> x+&xxx(1,i,i+j)
6.500000

So values of objects at some points can be passed as arguments to commands. For objects containing complex numbers (type 3,4,5 or 6 of variable), the operator  will return the real part. It is also possible to have the real or imaginary part by using the suffixes .r or .i respectively. For example, if  zzz is an object which is a 2 dimensional array of complex numbers, &zzz(1,2).r  and  &zzz(1,2).i  are respectively the real part and the imaginary part of the term of indices (1,2) of zzz .

Some commands manipulating objects are present in the library : const , const_c , const_r , const_th , copy , multiply , substract , svg , restore (cf.  section 8).
 
 
 



 

5.4 Aliases of object types

It is possible to define faster several identical object types with different names using the keyword  alias. If the section !def of the initialization file contains for example the following :

!def
; Objects  of  type  1 (comment line)
defobj1
-1
0
2
Objects  of  type  1  :
ndim1_1
ndim1_2
nb1
; Objects  of  type  1b (comment line)
defobj1_b
alias defobj1
Objects  of  type  1b  :

the object type  defobj1  will first be created. Then another object type called  defobj1_b  will be created, which will be identical to  defobj1  (i.e. they have the same parameters, except the comment defining the objects). In this case the definition of  defobj1_b  needs only three lines : in the first is the name of the command defining the objects of this type, in the second we put the keyword  alias  followed by the name of a previously defined object type and in the third the comment defining the object type.

It is possible to see in a program if an object type is an alias of another. For this we use the global variable

int   *Obj_alias;

If the object type number  is not the alias of another object type, the integer  Obj_alias[i]  will be equal to  i. If this object type is the alias of the object type then Obj_alias[i]  will be equal to j (in this case  j is smaller than  i ). This array can be used for example if one wants to write commands using object types which are aliases of one object type.
 
 
 


5.5 Objects of undefined data type

This is the type 7 of variables for objects. It is suitable to store the user defined data types. For this kind of object there are no dimensions, but one should put at least one for compatibility reasons. For example, suppose that the user needs to manipulate some kind of data called Data0  (for example, it could be a structure). Then in the  !def  section of the initialization file, one could find

; Objects  of  undetermined  type
defobj3
-1
7
1
Struc0's :
10
nb_struc0

Here these objects are declared to have 1 dimension, which is 10, but this does not matter. We suppose also that this is the 3rd type of objects that we have in the initialization file. Now the command  defobj3 will not create any  Data0 , but rather some place to store the address of such a data type and a name associated to this address. To really create a  Data0  one needs a specific command, say  Def_Data0 . This command could work as follows :

interpreter -> Def_Data0   xxx

should create a  Data0  whose name is  xxx . One could also make a more complicated command, with more arguments such as parameters for the Data0  to be created. The command Def_Data0  can be implemented as follows :

int
Def_Data0_cmd(int argc, char *argv)
{
    int     i0, iw;
    char     *k[3],**e;
    Data0     *D;

    iw = sketch_obj(argv[1], &i0);
    if  (iw != 0) {
        error_mess(4);
        return 1;
    }
    k[0] = ch_copy ("objdef");
    k[1] = ch_copy("3");
    k[2] = argv[2];
    if  (obj_create(3,k) == -1) {
        error_mess(3);
        return 1;
    }

    D = Data0_create() /* user-supplied function that creates a Data0 */

    e = (char**) Obj[2][i0].adresse;
    e[0] = (char*) D;
    free(k[0]);
    free(k[1]);
    return 0;
}

Here the function  sketch_obj  is used to see if the name  xxx  has not been already used. If it has been used, the error message 4 is printed, and the function exits (this message could be  already used name ! ). Then the object  defobj3  is created and associated to the name  xxx , using the function obj_create . A new Data0  is then created, and its address is stored in the object.
 
 
 
 


5.6 Destruction of objects


It is possible to destroy objects with the command  destroy . For example,

interpreter -> destroy  xxx

will destroy the object  xxx  (if it exists). If the variable type is known (i.e. the type of variables in this object is between 0 and 6), the corresponding array is freed. When an object is destroyed, the function

void   dest_prop (int, int);

will be called. This is useful in particular if the type of variables is 7 (undetermined type, see  section 5.4). This function should contain the procedure provided by the user to free the corresponding data. This function is not in the library. It must be present in the program that uses the command interpreter. It can be simply

void
dest_prop(int typ, int i0)
{
}

The argument  typ  is the object type (the first to be defined in the initialization file is 0, the second 1, and so on),   i0  is the object number. So the object to free is actually  Obj[typ][i0] . In the example of section 5.4, we could put the following function

void
dest_prop(int typ, int i0)
{
    char   **e;
    Data0   *D;

    if  (typ == 2) {
        e = (char **) Obj[typ][i0].adresse;
        D = (Data0 *) e[0];
        free_Data0(D); /* function supplied by the user to free data of type Data0 */
    }
}

In more complicated situations a specific command can also be written to destroy some kind of object.

The objects of an array of objects must be destroyed individually.

The function

void   init_obj (int);

may be used to destroy all the objects of a given type : it deletes all the objects of the type given in argument.
 
 
 


5.7 Storage of objects

It is possible to store objects (not of undetermined type) in the results directory, using the command  svg . For example, if the object   XXX1  has been defined, the instruction

interpreter -> svg   XXX1   z.svg

will store the object  XXX1  in the file  z.svg  of the results directory. This is done in binary format. The file  z.svg  will not only contain the numbers of the array corresponding to  XXX1 , but also informations concerning this object (the type of the object and its dimensions). The object can be loaded by using the command restore . For example if the object  YYY1 has been defined, of the same type as  XXX1  and with the same dimensions, the instruction

interpreter -> restore   YYY1   z.svg

will fill  YYY1  with the object stored in  z.svg .
 
 
 


5.8 Structure types

The structure types are structures of the following type :

typedef  struct  TYPE_STRUC {
    int   nb_membres;
    int   *type_mb;
    char   **membre_id;
    char   *nom;
    char   *comment;
    int   nombre;
}  struc_typ;

This structure is not supposed to be used by anything except the interpreter itself. The user of the interpreter will have to manipulate structures rather than structure types.

nb_membres  is the number of members of the structure type.

type_mb  contains the types of the members. If the member i is an object of type j , type_mb[i]  will be  j . If this member is a structure of type   then type_mb[i] will be  -j-1 . Structure types are determined by their order in the initialization file (cf. section 2.6). The first to be defined has type 0, the second 1, and so on. A structure type must be defined before it appears as a member of another structure type.

membre_id  contains the names of the members.

nom  is the name of the command of the interpreter that is used to create structures of this type.

comment  is a description of the object. It is used only by the command   list which gives the list of all the objects and structures that have been created.
 
 
 


5.9 Structures

A structure is a C-structure of this kind :

typedef  struct  _STRUC {
    char   *nom_struc;
    int   occup;
    struc_typ   *type;
    char   **nom_mb;
} strucb;

nom_struc  is the name of the structure.

occup  is 0 if the structure has not been initialized, 1 otherwise.

type  is the address of the structure type of the structure (cf. section 5.7).

nom_mb[i]  is the name of the object (or structure) which is the member i of the structure. It will be NULL if this member has not been assigned.
 
 
 
 


5.10 How to create and access structures

Structure types are created in the initialization file (cf. section 2.6). This allows structures to be created when the interpreter is running.

The structure types that are available when the interpreter is running are contained in the array

struc_typ   *Struc_typ;

If the section  !struct  of the initialization file contains for example the following :

!struct
; Structures  of  the  first  type (comment line)
defstruc1
2
member_1
defobj1
member_2
defobj2
member_3
X
Structures 1 :
-1
nb1
; Structures  of  the  second  type (comment line)
defstruc2
3
member_1b
defobj1
member_2b
defobj2
member_3b
defstruc1
Structures 1 :
0
nb2
.

2 structure types will be created,  Struc_typ[0]  and  Struc_typ[1]. If the object types defobj1defobj2  have been created previously, but no object or structure type called X, the structures  defstruc1  have 3 members : an object of type  defobj1, an object of type  defobj2  and a variable of the expression evaluator. The structures  defstruc2 have 3 members : an object of type  defobj1, an object of type  defobj2  and a structure of type  defstruc1.

The structures are contained in the array

strucb   **Struc;

Struc[i][j]  will contain the j -th structure of type i . The type 0 corresponds to the first type of structures created in the initialization file, the type 1 to the second type of structures created in the initialization file, and so on.

When the interpreter is running, structures are created using the corresponding command. For example, in the preceeding example, the command

interpreter -> defstruc1   xxx

will create a structure of the first type, whose name is xxx . It is also possible to create arrays of structures. For example, the command

interpreter -> defstruc1   zz[3]

will create the structures  zz[1] , zz[2] and zz[3] . Note that it is now possible to use the name  zz  for another structure or object. The command  assign  can be used to fix the members of the structure. The command  desassign  can be used to free them. With the command  desc  it is poosible to have the description of a structure.
 
 

Example :

interpreter -> defstruc1  xxx
interpreter -> desc  xxx
member_1 (object defobj1) :  (null)
member_2 (object defobj2) :  (null)
member_3  (variable xxx_member_3)  :  0
interpreter -> defobj1  xxx1
interpreter -> defobj2  xxx2
interpreter -> assign  xxx  member_1  xxx1
interpreter -> assign  xxx  member_2  xxx2
interpreter -> desc  xxx
member_1 (object defobj1) :  xxx1
member_2 (object defobj2) :  xxx2
member_3  (variable xxx_member_3)  :  0
interpreter -> desassign  xxx  member_1
interpreter -> assign  xxx  member_3  2.5
interpreter -> desc  xxx
member_1 (object defobj1) :  (null)
member_2 (object defobj2) :  xxx2
member_3 (variable xxx_member_3) :  2.5

In this example the third member of the structure  xxx  is a hidden variable of the expression evaluator (cf. section 6), called xxx_member_3  (so its name is the name of the structure, followed by a _ and by the name of the member). The command  assign  is also used here to set the value of the member. Its value will be the last argument of the command  assign. If a member of a structure which is a variable of the expression evaluator is not assigned any value, the corresponding variable remains undefined.  In the previous example the function

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

is the one that is called to create structures. This is the function that implements the command  strucdef . In the above example, defstruc1  is in fact the following program

:defstruc1
3
0
0
strucdef  0  #1

The 0 after  strucdef  means that the command  defstruc1  will create structures of type 0 (which is the first type of structures defined in the initialization file). When the initialization file is read, a program of this type is created for each structure type. So the command

interpreter -> defstruc1  xxx

is equivalent to this one :

interpreter -> strucdef  0  xxx

but the first one is more explicit.

Suppose we want to define the members of a structure together with the structure itself. For example, we want a command called  Def_Struc1 such that

interpreter -> Def_Struc1  xxx

would create the structure xxx , the objects xxx.memb1 , xxx.memb2 , of type defobj1 , defobj2  respectively, and assign these objects to the members of  xxx . The function that implements this comment could be :

int
Def_struc1_cmd(int argc, char *argv[])
{
    char   *k[4], h[100];

/* we create the structure */
    k[0] = ch_copy("strucdef");
    k[1] = ch_copy("0");
    k[2] = argv[1];
    if  (struc_create(3,k)== -1) {   /* creation of the structure */
        error_mess(5);
        return 1;
    }
    free(k[0]);
/*--------------------------------------*/

/* we create the members of the structure */
    memset(h, 0, 100);
    sprintf(h,"%[s.memb1", argv[1]);
    k[0] = ch_copy("objdef");
    k[2] = h;
    if  (obj_create(3,k) == -1) { /* creation of member 1 */
        error_mess(5);
        return 1;
    }
    memset(h, 0, 100);
    sprintf(h,"%[s.memb2", argv[1]);
    k[1][0]='1';
    k[2] = h;
    if  (obj_create(3,k) == -1) { /* creation of member 2 */
        error_mess(5);
        return 1;
    }

/*--------------------------------------*/
/* we assign the objects to the members of the structure */
    free(k[0]);
    k[0] = ch_copy("assign");
    free(k[1]);
    k[1] = argv[1];
    k[2] = ch_copy("member_1");
    memset(h,0,100);
    sprintf(h,"%[s.memb1", argv[1]);
    k[3] = h;
    assign_membre(4,k);
    k[2][7] = '2';
    memset(h,0,100);
    sprintf(h,"%[s.memb2", argv[1]);
    k[3] = h;
    assign_membre(4,k);
    free(k[0]);
    free(k[2]);

/*--------------------------------------*/
    return 0;
}

Here the function  assign_membre  (which corresponds to the command   assign ) is used to assign objects to members of a structure (we make a simulation of the command  assign... ). The function struc_create  (which creates structures) returns -1 if it fails (as   obj_create  does). The preceeding function could be improved : for example, if it is impossible to create a member of the structure, the structure (and already created members) could be destroyed.

Now we show how to access structures which have been created, and their members. Suppose that we have a command, called  Xcom1b , that uses a structure of type  defstr1 . So the command should be used as follows :

interpreter -> Xcom1b   xxx

where the argument  xxx  is supposed to be the name of a structure of type  defstr1 . The function that implements this command could be as follows :

int
Xcom1b_cmd(int argc, char *argv[])
{
    int   i0, i1, i2, **I;
    double   ***A;

/* we test if the structure exists */
    if  (sketch_obj_restr(argv[1], &i0,-1) != -1) {
        error_mess(5);
        return 1;
    }

/*-------------------------------------*/
/* we extract the members of the structure */
    if  (sketch_struc(0, i0, "member1", &i1) == 0) {
        error_mess(6);
        return 1;
    }
    I = (int **) Obj[0][i1].adresse;
    if  (sketch_struc(0, i0, "member2", &i2) == 0) {
        error_mess(6);
        return 1;
    }
    A = (double ***) Obj[1][i2].adresse;

/*-------------------------------------*/
/* Here should follow the useful part of the function */

    return 0;
}

Here the error message 6 could be unassigned member !. The function

int   sketch_obj_restr (char *str, int *i0, int j);

is used as for objects to find a structure with a given name. If j is a negative number,  sketch_obj_restr(str, &i0, j)   will return j if  str is the name of a structure of type  -j-1 with name   str . In this case, i0  will contain the number of the structure. So this structure will be Struc[-j-1][i0] .

To know the members of a structure it is possible to use it itself, or to use the function

int   sketch_struc (int i, int i0, char *memb, int *j);

Here  sketch_struc(i, i0, memb, &j) will return 0 if  memb  is not the name of a member of the structure number  i0 of type  i , or if this structure is not defined. If  memb  is the name of a member of this structure, and if this member is not assigned, it returns also 0. In the remaining case, if the member is an object of type k , the function returns  k+1 ,  j  will contain the number of the object, and if it is a structure of type   k , the function returns   -k-1j  will contain the number of the structure. So the member will be  Obj[k][j]  or Struc[k][j] .
 
 
 
 


5.11 Destruction of structures

It is possible to destroy structures with the command  destroy . For example,

interpreter -> destroy   xxx

will destroy the structure  xxx (if it exists).

The structures of an array of structures must be destroyed individually.

The function

void   init_str (int);

may be used to destroy all the structures of a given type : it deletes all the structures of the type given in argument.
 
 
 
 


Main page
Chapters   1   2   3   4   5   6   7   8    9    10   11