Back: 14.1 What modules are good for Forward: 14.2.1 Utility functions to determine variables values   FastBack: 14. Writing Modules Up: 14. Writing Modules FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.2 How fsc2's variables work and how to use them

All functions in a module that are going to be invoked from EDL scripts get their input parameters in the form of a special type of variable and fsc2 also expects that each function returns a value in this form.

Let's start with a look at the way fsc2 internally stores these variables. Here is the (actually somewhat simplified) typedef of the structure for such variables:

 
typedef struct Var
{
    Var_Type_T type;           /* type of the variable */
    union
    {
        long          lval;   /* value of integer values */
        double        dval;   /* value of float values */
        long   *      lpnt;   /* pointer to integer arrays */
        double *      dpnt;   /* pointer to floating point arrays */
        char   *      sptr;   /* for string constants */
        struct Var ** vptr;   /* for array references, used for
                                multi-dimensional arrays */
    } val;
    int dim;                  /* dimension of array */
    ssize_t len;              /* length of array */
    struct Var * next;        /* next variable on stack */
} Var_T;

There are only six types of variables (defined by an enumeration with type Var_Type_T) you have to know about:

INT_VARa variable for integer values
FLOAT_VARa variable for floating point values
INT_ARRa one-dimensional array of integer values
FLOAT_ARRa one-dimensional array of floating point values
INT_REFa 2- or more-dimensional array of integer values
FLOAT_REFa 2- or more-dimensional array of floating point values

Actually, there are a few more, but those are only used by fsc2 internally. If you test for a variables type in an switch you may get compiler warnings because Var_Type_T is an enum. To avoid the warning simply add a default for all the other types, you should never receive a variable of such a type and if you should do you should throw an exception (see below) - for that to a serious bug in fsc2 must have been triggered.

To give you a better idea what these variables are good for let's assume that you want to write a function that returns the curve between the two cursor bars of your new LeCronix digitizer. For that you may want to write a function that has the two positions of the cursor bars as input arguments and which returns the data of the curve between the cursor bars. Let's call this function

 
digitizer_get_curve_between_cursors( cursor_1, cursor_2 )

A typical C declaration for this function is

 
Var_T * digitizer_get_curve_between_cursors( Var_T * var );

Perhaps surprisingly, there seems to be only a single argument! And how to return an array of data?

Actually, it's not very complicated. The pointer to the variable structure var points to the variable with the first of the two parameters. And if you look back at the typedef for fsc2's variables above, it contains a next pointer. This is the key to accessing further function arguments - var->next points to next of the input parameters. If the function expects further arguments, var->next->next etc. let's you get at them. I.e. the input variables are organized as a linked list:

 
  var                               pointer passed to function
   |                                  |
   V                                  |
  ---------------                     V
 |        | next |                  first input parameter
  ---------------                         |
              |                           |
              V                           |
             ---------------              V
            |        | next |       second input parameter
             ---------------                  |
                         |                    |
                         V                    V
                        NULL        no more parameters...

This method allows to pass the function an arbitrary number of arguments and you can check how many you've got by simply counting while following the pointers until the next pointer of a variable is NULL.

When you later tell fsc2 about the function (by adding it to the `Functions' file, see below) you can explicitely state if the functions allows a variable number of arguments or only a certain fixed number. A function that only accepts e.g. 3 arguments will always get 3 - when the EDL function is called with less arguments an error message gets printed and executing the EDL script is stopped. And if it is called with too many arguments the superfluous ones are discarded and an error message is printed before your function gets invoked with the correct number of arguments.

But in case you defined the function to accept a variable number of arguments you probably better check in your function that there aren't too many and if necessary print out a warning.

One word of warning: Never ever change the variables you get passed to your functions in any way, especially the next-pointers! Even though the variables get thrown away automatically when you return from the function changing something within the variables may break the mechanism for clearing up the variables and lead to all kinds of weird errors.

What fsc2 can't check is if the arguments it passes to your function have the types you expect them to have. Let's assume that you expect two integer values. What you should do first is to check if the parameters you got are really integers. There is a function that can do this for you, vars_check(). All you have to do is to call vars_check() with the pointer to the variable and the type you expect it to have, e.g.

 
vars_check( var, INT_VAR );
vars_check( var->next, FLOAT_VAR );

If vars_check() finds that everything is ok it simply returns, otherwise an error message will be printed (telling the user that a variable of an unexpected type was detected in the function call) and the program stops, so you don't have to take care of error handling. If you're prepared to accept integers as well as floating point numbers, call vars_check() instead with

 
vars_check( var, INT_VAR | FLOAT_VAR );

i.e. just logically or the types of variables you're prepared to accept in your function.

You can also check if the argument is a string by testing a type of STR_VAR, i.e.

 
vars_check( var, STR_VAR );

And the same holds, of course, for variables of type INT_ARR, FLOAT_ARR, INT_REF and FLOAT_REF.

vars_check() not only checks that the variables has the correct type but also does some internal consistency checks to make sure that the variable actually exists and has been assigned a value. Failure of these tests also lead to the function not returning and instead aborting the execution of the EDL script.

A function that expects just integer arguments could start like this, just running through the linked list of parameters:

 
Var_T * my_function( Var_T * var )
{
    Var_T * cur;

    for ( cur = var; cur != NULL; cur = cur->next )
        vars_check( cur, INT_VAR );

    ....
}

The next question is how to access the value of the variable. As you can see from the typedef for variables above the value is stored in a union called val. If the variable has integer type, you can access it as

var->val.lval    or the macro var->INT

and what you get is a value of type long int - fsc2 is using long integers internally. On the other hand, if the type of the variable is FLOAT_VAR you get at the data with

var->val.dval    or the macro var->FLOAT

in which case you get a value of type double. Finally you may use

var->val.sptr    or var->STRING

to get the address of the start of a string variable.


Back: 14.2 How fsc2's variables work and how to use them Forward: 14.2.2 Getting at the data of one-dimensional arrays   FastBack: 14. Writing Modules Up: 14.2 How fsc2's variables work and how to use them FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.2.1 Utility functions to determine variables values

There are some utility functions that make it even easier to evaluate the parameters your function receives (and, if you use those, you don't need to check them with the var_check() function). The first one is for the case that you expect an integer variable but would also be prepared to deal with a floating point number after it has been rounded to the nearest integer. This is the function get_long(), declared as

 
long get_long( Var_T *      var,
               const char * snippet );

The first argument is a pointer to the variable you want to evaluate. The second parameter is used to create a warning message when the variable isn't an integer variable but a float value. This message always starts with the name of the currently interpreted EDL file, followed by the line number in the EDL script your function was invoked from and the device name. The second parameter is a string that gets embedded into the message. For example, if the currently interpreted EDL file is named `foo.edl', the line where your function is called is 17 and the device name is LECRONIX, your function (that expects an integer but got a floating point number) is named abc() and the string you pass to the function get_long() as the second argument is "channel number", then on calling

 
get_long( var, "channel number" );

with a FLOAT_VAR instead of an INT_VAR the following warning message will be printed:

 
foo.edl:17: LECRONIX: abc(): Floating point number used as channel number.

If, on the other hand, you expect a floating point number but are also prepared to accept an integer, you can use the function

 
long get_double( Var_T *     var,
                const char * snippet );

The arguments of this function are the same you would pass to the previous function and the only difference is that it will return a double and print a warning message if the variable is an integer variable instead of the expected floating point variable.

If your function can accept an integer variable only there's a third function:

 
long get_strict_long( Var_T *      var,
                      const char * snippet );

This function has the same arguments as the two other functions but it will throw an exception (see below what this means) when getting passed a floating point number, stopping the interpretation of the EDL script. That's actually only true for the test run, during the real experiment only a warning message is printed and the floating point number is converted to the nearest integer, which then is returned, thus avoiding the termination of a running experiment. But usually the wrong parameter should already have been found during the test run, forcing the user to correct the script.

There's no function for requirung a double variable only because basically all functions should accept an integer in place of a double and the get_double() function always returns a double, if necessary after converting the integer value.

Then there is a function for the case where you want a boolean variable, i.e. a variable that can be either true or false. This function is declared as

 
bool get_boolean( Var_T * var );

This function will return true (i.e. a value of 1) when the variable passed to it is either an integer variable with a non-zero value or a string variable with the string "ON" (it is case-insensitive, so "on", "On" or even "oN" will also do). False (i.e. 0 is returned when it receives an integer variable with a value of 0 or a string with the text "OFF" (again this is checked in a case-insensitive manner).

If the variable passed to the function is a floating point variable normally an error message like

 
foo.edl:17: LECRONIX: abc(): Floating point number found where
                             boolean value was expected.

is printed and an exception is thrown. Should fsc2 already be running the experiment (instead of just doing the test run) a warning message is printed and instead of terminating the experiment the floating point value is converted to the nearest integer value and the truth value of this number (i.e. if it's non-zero) is returned to avoid stopping the experiment.

Finally, if the get_boolean() function receives a string variable that is neither "ON" nor "OFF" (including variations of the case of the characters) an error message is printed:

 
foo.edl:17: LECRONIX: abc(): Invalid boolean argument ("bla").

(assuming that the string passed to the function was "bla") and an exception is thrown in all cases, even during the experiment since there is no obvious way how to define the truth value of an arbitrary string.


Back: 14.2.1 Utility functions to determine variables values Forward: 14.2.3 More-dimensional arrays   FastBack: 14. Writing Modules Up: 14.2 How fsc2's variables work and how to use them FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.2.2 Getting at the data of one-dimensional arrays

When a complete one-dimensional array gets passed to your function the type of the variable is either INT_ARR or FLOAT_ARR. As for single value variables you can check these variables by calling vars_check().

You can find the length of the array by checking the len part of the variable structure. Dynamically sized arrays can have a still undefined length, in which case the len field has a value of 0, make sure you check for this possibility in your code.

The actual data of the array can be accessed via the lpnt or the dpnt elements of the union named val in the variables structure. When you have to deal with an array of integer values (i.e. a variable named var of type INT_ARR) the values (of type long int) are in var->val.lpnt[0] to var->val.lpnt[var->len - 1]. For an array of floating point numbers the values (of type double) are stored in val->var.dpnt[0] to var->val.dpnt[var->len - 1].


Back: 14.2.2 Getting at the data of one-dimensional arrays Forward: 14.2.4 Returning data from EDL functions   FastBack: 14. Writing Modules Up: 14.2 How fsc2's variables work and how to use them FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.2.3 More-dimensional arrays

For arrays of 2 or more dimensions (i.e. variables of type INT_REF and FLOAT_REF) the rank of the "matrix" is stored in the dim field of the variables structure. fsc2 doesn't have real more-dimensional arrays but fakes them in a similar way like e.g. Perl by using arrays of pointers. For the data of such arrays the vptr field in the val union of the variable structure is relevant. vptr points to an array of variable pointers, each pointing to the next lower-dimension sub-arrays. How many of such sub-arrays exist can be determined from the len field of the variable structure. As already for the one-dimensional arrays care has to be taken to check that len isn't 0 in case none of the sub-arrays have been defined yet for variable sized arrays.

If the rank of a variable is 2 all the sub-arrays are one-dimensional arrays (i.e. have a type of INT_ARR or FLOAT_ARR and one can the values of these sub-arrays as described in the previous section.

For arrays of higher rank the pointers in the val.vptr array point to variables that again have a type of INT_REF or FLOAT_REF and a rank of one less.

Thus to find out the element [3][2][5] of variable var pointing a three-dimensional floating point array one would have to use

 
  var->val.vptr[ 3 ]->val.vptr[ 2 ]->val.dpnt[ 5 ]

Of course, before one tries to access the element one always should check that var->len is at least 4, var->val.vptr[3]->len is at least 3 and var->val.vptr[3]->val.vptr[2]->len is at least 6.

But there's also a function that does all the required checks automatically and returns an element. It is called get_element() and is declared as

 
   Var_T * get_element( Var_T * v, int len, ... );

It takes a variable number of arguments (but requires at least three). The first one is a pointer to the array or matrix from which you want an element and the second is the number of arguments to follow. This number may not be larger than the rank of the array or matrix. All the remaining arguments are indices into the array or matrix.

The function returns on success a variable that is either an integer or float value or a pointer to an array or matrix. On failure (e.g. because the accessed element does not exist) an exception is thrown.

If you have a 1-dimensional array of integers named i_arr_1d and you want to determine its fourth element you can call this function like this

 
    Var_T elem = get_element( i_arr_1d, 1, 3 );

and the returned pointer to a new fsc2 variable named elem will be of type INT_VAR, having the value of the fourth element of the array (please remember that array indices start at 0, you're now writting C not EDL, so the fourth element has the index 3)

If, in contrast, you want the element [3][2][5] of the variable f_arr_3d, pointing to a three-dimensional floating point matrix, you would call the function as

 
    elem = get_element( f_arr_3d, 3, 3, 2, 5 );

You can also call the function with less indices than the rank of a matrix. In this case the function returns a variable pointing to the indexed array or sub-matrix.


Back: 14.2.3 More-dimensional arrays Forward: 14.3 How to write a new module   FastBack: 14. Writing Modules Up: 14.2 How fsc2's variables work and how to use them FastForward: A. Installation         Top: fsc2 Contents: Table of Contents Index: Index About: About This Document

14.2.4 Returning data from EDL functions

If your function just wants to return an integer or a floating point value, things are very simple: just call the function vars_push() with the type of the value to be returned as the first and the value itself as the second argument, e.g.

 
return vars_push( INT_VAR, i_value );

or

 
return vars_push( FLOAT_VAR, f_value );

where i_value is a long int and f_value must be a value of type double (take good care not to get this wrong!). Of course, you don't have to use vars_push() in return statements only, it simply returns a pointer to a new variable holding the value.

For arrays vars_push() the first argument is either INT_ARRAY or FLOAT_ARRAY, The second argument is a pointer to the array (i.e. its first argument). When creating an INT_ARR this array must be an array of long int values, for creating a FLOAT_ARR it has to be an array of double values. (mak sure that this is the case since the vars_push() function can't test for this an neither can the compiler!_

For creation of an array variable also third argument is needed, the length of the array (which must be a long integer). If you want to return an EDL variable that is an array of two integer you would use for example

 
long int data[ ] = { 1, 2 };
return vars_push( INT_ARRAY, data, 2L );

Keep in mind that vars_push() always makes a copy of the array you pass it - if what you pass it is a pointer to allocated memory it's your responsibility to T_free() it.

As a complete example here is a rather simple but working function named square() that returns the square of the value it got passed:

 
Var_T * square( Var_T * var )
{
    vars_check( var, INT_VAR | FLOAT_VAR );   /* is it a number ? */ 

    Var_T * ret_var;

    if ( var->type == INT_VAR )
    {
        long int_square = var->INT * var->INT;
        ret_var = vars_push( INT_VAR, int_square );
    }
    else
    {
        double float_square = var->FLOAT * var->FLOAT;
        ret_var = vars_push( FLOAT_VAR, float_square );
    }

    return ret_var;
}

As you see it's checked first that the variable passed to the function has the correct type - both integer and floating point values are ok (otherwise the interpretation of the EDL script would stop). Next we distinguish between the possibilities that the value is an integer or a floating point number by testing the type field of the variable. Then we create either a new integer variable by calling vars_push() with the square of the integer value or a new floating point variable. Finally, we return the variable pointer vars_push() had delivered.

Of course, we could also have written the function in a more compact way:

 
Var_T * square( Var_T * var )
{
    vars_check( var, INT_VAR | FLOAT_VAR );

    if ( var->type == INT_VAR )
        return vars_push( INT_VAR, var->INT * var->INT );
    else
        return vars_push( FLOAT_VAR, var->FLOAT * var->FLOAT );
}

If your function does not have to return a value at all just return the integer value 1, which can be interpreted as success.

What if you want to write to function that returns more than one value? Again we use a function for a digitizer that has to return a curve stored in an array as an example. Let's assume the data you got from the digitizer are stored in an array of integers called data which has len elements (where len is a long). Now all you've got to do is call the function vars_push() as

 
Var_T * ret_var;

...
ret = vars_push( INT_ARR, data, len );
...
return ret_var;

Actually, at some point of your function you may have allocated memory for storing the data. It is your responsibility to free this memory before you return from your function, fsc2 just uses a copy of the data you pass to it using vars_push(). As you probably already guessed, if you want to return a float array, you will have to use FLOAT_ARR instead of INT_ARR in the call to vars_push().

The same method may be used if your function has to return two different values and both have the same type. Again an array can be returned

 
VARIABLES:

V1; V2;         // results of call to my_function()
Dummy[ * ];     // variable sized array for values returned by my_function()

...             // lots of stuff left out

Dummy = my_function( );      // automagically sets the dimension 
                             // of array Dummy to 2
V1 = Dummy[ 1 ];
V2 = Dummy[ 2 ];

and the C code for function my_function() would look like

 
Var_T * my_function( Var_T * var )
{
    long v[ 2 ];

    v[ 0 ] = ...;    /* just fill in all the stuff you */
    v[ 1 ] = ...;    /* need to calculate both data    */

    return vars_push( INT_ARR, v, 2 );
}

An alternative (e.g. if the type of the variables you need to return differs) is to write two functions where the first one does the calculations needed and stores the second value in a global variable. All the second function has to do is just to return the value of the global variable. This way, the EDL file might look like

 
V1 = my_function_1( );
v2 = my_function_2( );

while the C code would define both functions as

 
static double v2;   /* global variable used by my_function_1() 
                       and my_function_2() */
Var_T * my_function_1( Var_T * v )
{
    long V1;

    V1 = ...;       /* just fill in all the stuff you */
    v2 = ...;       /* need to calculate both data    */

    return vars_push( INT_VAR, V1 );
}

Var_T * my_function_2( Var_T * v )
{
    return vars_push( FLOAT_VAR, v2 );
}

Alternatively, you also could write the function in a way that it counts the number of times it has been called and returns values accordingly, e.g.

 
V1 = my_function( );
v2 = my_function( );

with the corresponding C code

 
Var_T * my_function( Var_T * v )
{
    long V1;
    static double v2;
    static int call_count = 0;


    if ( call_count > 0 )    /* on second call return second value */
    {
        call_count = 0;      /* don't forget to reset the call counter! */
        return vars_push( FLOAT_VAR, v2 );
    }
        
    V1 = ...                 /* just fill in all the stuff you */
    v2 = ...                 /* need to calculate both data    */

    return vars_push( INT_VAR, V1 );
}

Of course, in both cases one has to be careful to call the function(s) in the correct sequence, so it's not completely foolproof.


Back: 14.2.3 More-dimensional arrays Forward: 14.3 How to write a new module   FastBack: 14. Writing Modules Up: 14.2 How fsc2's variables work and how to use them FastForward: A. Installation

This document was generated by Jens Thoms Toerring on September 6, 2017 using texi2html 1.82.