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.
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.
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).
!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 i 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 j 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.
; 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.
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.
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 .
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 j 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.
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.
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 defobj1, defobj2 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-1 , j
will contain the number of the structure. So the member will be Obj[k][j]
or Struc[k][j] .
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.