Interacting with Guile Compound Data Types in C

Creating Guile compound data types from C is usually a two step process. First, one converts the simple data types that are going to be elements of the compound data type from C values into SCM. Then, one collects the constituent SCMs into a list, pair or vector, which is in turn its own SCM.

After the SCM is created, it can be passed to Guile as a top-level variable or used as the return value of a gsubr, a C function for the Guile interpreter.

The inverse operation, decoding compound data types, is a little trickier. The big challenge in decoding compound types from Guile is ensuring that the C data structures have the right type and size to hold the Guile data being passed. If Guile data is to be converted into C data, there must be a contract of sorts between the two languages, so that Guile does not pass a type that the C code hasn't been set up to receive.

The first step in unpacking Guile compound data types is getting them into an SCM. A discussion of how to get top-level variables from Guile is in the section called Reading Guile Top-Level Variables into C in the chapter called Data and Values>. A discussion of how to call a Guile function, which would return an SCM, is in the section called Calling Guile functions from C in the chapter called Functions I>.

Pairs

To convert two SCMs into a Guile pair, the function scm_cons is used.

SCM scm_cons(SCM x, SCM y);

The function scm_cons returns an SCM which is a pair that has x as the car and y as the cdr.

The following example program creates a pair and defines it as a guile top-level variable.

Example 1. Packing a pair

#include <libguile.h>
#include <stdio.h>

int main(int argc, char **argv)
{
    SCM s_car, s_cdr;
    SCM s_pair;

    /* Start the interpreter */ 
    scm_init_guile();

    /* Create the elements of the pair */ 
    s_car = scm_int2num(1);
    s_cdr = scm_double2num(3.14);

    /* Create the pair itself */ 
    s_pair = scm_cons(s_car, s_cdr);

    /* Create a Guile top-level variable */ 
    scm_c_define("a-pair", s_pair);

    /* Use the Guile interpreter to print the var */ 
    scm_c_eval_string ("(display a-pair)");
    printf("\n");

    return(0);
}


	

To test if an SCM is a pair, the macro SCM_CONSP can be used.

int SCM_CONSP(SCM x);

The macro SCM_CONSP return non-zero if x is a pair.

For the other compound data types, the next step would be to test the length of it, but, the length of a pair is always two.

To access the first element, the car, macro SCM_CAR can be used. To access the second element, the cdr, the macro SCM_CDR can be used.

SCM SCM_CAR(SCM x);

SCM SCM_CDR(SCM x);

These two functions return the car and cdr of the pair x.

The following example loads a guile script in which there is a top-level variable a-pair defined. It reads in the pair and prints its contents. Note that in this example there is an implicit agreement that the pair must contain an int and a double. If the pair contained other types of data, the program would exit with an error message.

#include <libguile.h>
#include <stdio.h>

int main()
{
    SCM s_symbol;
    SCM s_value;
    int c_car;
    double c_cdr;

    /* Start the Guile interpreter */
    scm_init_guile();

    /* Load some script */ 
    scm_c_primitive_load ("script.scm");	

    /* Look up a top-level variable that is a pair */
    s_symbol = scm_c_lookup("a-pair");
    s_value = scm_variable_ref(s_symbol);

    /* Check that it is a pair */ 
    if (SCM_CONSP(s_value)) {
	
	/* Unpack it. */
	c_car = scm_num2int (SCM_CAR (s_value), 0, "main()");
	c_cdr = scm_num2double (SCM_CDR (s_value), 0, "main()");
    }

    /* Print the output */ 
    printf("%d : %f\n", c_car, c_cdr);

    return(0);
}

	

Lists

To convert a set of SCMs into a Guile list, one of the family of scm_list_n functions can be used.

SCM scm_list_1(SCM e1);

SCM scm_list_2(SCM e1, SCM e2);

SCM scm_list_3(SCM e1, SCM e2, SCM e3);

SCM scm_list_4(SCM e1, SCM e2, SCM e3, SCM e4);

SCM scm_list_5(SCM e1, SCM e2, SCM e3, SCM e4, SCM e5);

SCM scm_list_n(SCM el, ...);

The family of scm_list_n functions convert a set of elements (e1, e2, etc.) of SCM into a Guile list. Note that the specific function scm_list_n can actually be used to make lists of as few as two elements. One does not have to wait until there are 6 or more elements in a list before using scm_list_n.

In practice, using the scm_list_n functions to create short lists is similar to using the scm_cons function to create a pair as demonstrated in Example 1 in the section called Pairs>

For the creation of long lists, it can sometimes be easier to create the list by appending values one by one, instead of creating the list in one function call. To append one list to another, the function scm_append is used.

SCM scm_append(SCM args);

The function scm_append take a parameter args which is a list whose elements are themselves lists, and it returns a list created by stringing the list elements together. I realize the previous sentence sounds non-sensical. In this case, it is easier to explain with an example.

#include <libguile.h>
#include <stdlib.h>

int
main (int argc, char ** argv)
{
    SCM s_element;
    SCM s_element_list;
    SCM s_total_list;
    int element;
    int i;

    /* Init the Guile interpreter */ 
    scm_init_guile();

    /* Initialize the list of values to a Guile empty list */
    s_total_list = SCM_EOL;
    
    for (i = 0; i < 100; i++) {

	/* Get a C value */ 
	element = rand();

 	/* Convert the C integer into an SCM inum */ 
	s_element = scm_int2num(element);

	/* Convert the SCM inum into a list with one element */
	s_element_list = scm_list_1 (s_element);

	/* Append the list with one element to the total list */ 
	s_total_list = scm_append (scm_list_2 (s_total_list, s_element_list));
    }

    scm_c_define ("randomvals", s_total_list);
    scm_c_eval_string ("(display randomvals)");

    return (0);
}
	

      

The example begins with an empty list SCM_EOL and repeatedly appends a list that contains just one number to it.

With lists, one can use them either like a C array, where the data is accessed using indices, or one can access them like a C-style singly-liked list, where one iterates forward the list.

SCM scm_list_p(SCM x);

SCM scm_null_p(SCM x);

scm_list_p returns Guile true (#t) if x is a Guile vector and false (#f) if it is not. scm_null_p returns Guile true (#t) if x is an empty list, or false (#f) if it is not.

SCM scm_length(SCM x);

scm_length returns the length of the Guile list x. The length is returned as an SCM.

SCM scm_list_ref(SCM x, SCM idx);

scm_list_ref returns the element number idx of a the Guile list x. Note that lists are zero-indexed, where the first element is zero and the last is N-1, just as in C arrays. Also, since idx is an SCM, a C integer index would need to be converted into an inum by using scm_int2num.

To access the list in a more list-like fashion, one would use repeated applications of the scm_car and scm_cdr. scm_car, when applied to a list, returns the first element of the list. scm_cdr returns the list that contains the remaining elements. scm_null_p will return Guile true if the the list is empty. These functions can be used to in a C while loop to iteratively operate on all members of the list.

The following examples load in a list, and then access it in two ways, using indices like a vector, as shown in Example 2>, and iteratively using the car/cdr macros, as demonstrated in Example 3>

Example 2. Unpacking a list with scm_list_ref

#include <libguile.h>
#include <stdio.h>

int main()
{
    SCM s_symbol;
    SCM s_list;
    SCM s_value;
    unsigned long list_length;
    double *values;
    int i;

    /* Start the Guile interpreter */
    scm_init_guile();

    /* Load a script that defines a list */
    scm_c_primitive_load("script.scm");

    /* Look up the variable */
    s_symbol = scm_c_lookup("list1");
    s_list = scm_variable_ref(s_symbol);

    /* Check that it is a list */ 
    if (SCM_NFALSEP(scm_list_p(s_list))) {
	
	/* Find its length */
	list_length = scm_num2ulong(scm_length(s_list), 0, "main()");
	
	/* Allocate a C array to hold the values */ 
	values = (double *)malloc(sizeof(double) * list_length);
	
	/* Copy the Guile list into the C array */ 
	for (i=0; i < list_length; i++) {

	    /* Get the i-th element of the list */ 
	    s_value = scm_list_ref(s_list, scm_int2num(i));

	    /* Convert it into a C double */
	    values[i] = scm_num2double(s_value, 0, "main()");
	}

	/* Print the C array's values */ 
	for (i=0; i < list_length; i++) {
	    printf("%d : %f\n", i, values[i]);
	}
    }


    return(0);
}

	

Example 3. Iteratively unpacking a list with SCM_CAR

#include <libguile.h>
#include <stdio.h>

int main()
{
    SCM s_symbol;
    SCM s_list;
    SCM s_value;
    unsigned long list_length;
    double *values;
    int i;

    /* Start the Guile interpreter */
    scm_init_guile();

    /* Load a script that defines a list */
    scm_c_primitive_load("script.scm");

    /* Look up the variable */
    s_symbol = scm_c_lookup("list1");
    s_list = scm_variable_ref(s_symbol);

    /* Check that it is a list */ 
    if (SCM_NFALSEP(scm_list_p(s_list))) {
	
	/* Find its length */
	list_length = scm_num2ulong(scm_length(s_list), 0, "main()");
	
	/* Allocate a C array to hold the values */ 
	values = (double *)malloc(sizeof(double) * list_length);
	
	i = 0;
	/* Iterate through the list */ 
	while (SCM_FALSEP (scm_null_p (s_list))) {

	    /* Get the head of the list */ 
	    s_value = SCM_CAR(s_list);

	    /* Convert it into a C double */
	    values[i++] = scm_num2double(s_value, 0, "main()");

	    /* Discard the head of the list */
	    s_list = SCM_CDR(s_list);
	}

	/* Print the C array's values */ 
	for (i=0; i < list_length; i++) {
	    printf("%d : %f\n", i, values[i]);
	}
    }

    return(0);
}

	

Vectors

When creating vectors, it is important to note that the length of the vector is set when it is created, and can't be modified afterward.

For short vectors, the easiest method is to create a list and then convert it into a vector. The function that converts a list into a vector is scm_vector.

SCM scm_vector(SCM l);

The function scm_vector take the parameter l, which is a list, and returns a vector. The length of the vector is set to be equal to the length of the list.

Another method, more suitable for longer vectors, is to create a vector with scm_c_make_vector, and then set the elements using scm_vector_set_x.

SCM scm_c_make_vector(unsigned long k, SCM fill);

SCM scm_vector_set_x(SCM v, SCM k, SCM obj);

The function scm_c_make_vector returns a vector of length k with each element set to the initial value fill. The function scm_vector_set_x takes a vector v and sets the kth element to the value obj. The following code show how they might be used together.

#include <libguile.h>
#include <stdlib.h>

int
main (int argc, char ** argv)
{
    SCM s_element;
    SCM s_vector;
    int element;
    int i;

    /* Init the Guile interpreter */ 
    scm_init_guile();

    /* Initialize the list of values to a Guile empty list */
    s_vector = scm_c_make_vector(20, scm_int2num(0));
    
    for (i = 0; i < 20; i++) {

	/* Get a C value */ 
	element = i*i;

 	/* Convert the C integer into an SCM inum */ 
	s_element = scm_int2num(element);
	
	/* set the i-th element in the vector to "s_element" */
	scm_vector_set_x (s_vector, scm_int2num(i), s_element);
    }

    scm_c_define ("squares", s_vector);
    scm_c_eval_string ("(display squares)");

    return (0);
}
	

      

int SCM_VECTORP(SCM x);

SCM_VECTORP returns true if x is a Guile vector.

unsigned long SCM_VECTOR_LENGTH(SCM x);

SCM_VECTOR_LENGTH returns the length of the Guile vector x.

SCM scm_vector_ref(SCM x, SCM idx);

scm_vector_ref returns the element number idx of a the Guile vector x. Note that vectors are zero-indexed, where the first element is zero and the last is N-1, just as in C arrays. Also, since idx is an SCM, a C integer index would need to be converted into an inum by using scm_int2num.

The following example uses scm_c_eval_string to call a snippet of Guile code that creates a vector. It then unpack the vector into a C array, and prints it out.

#include <libguile.h>
#include <stdio.h>

int main()
{
    SCM s_vector;
    SCM s_value;
    unsigned long vec_length;
    double *values;
    int i;

    /* Start the Guile interpreter */
    scm_init_guile();

    /* Call a function that returns a vector */
    s_vector = scm_c_eval_string("(make-vector 5 0.1)");

    /* Check that it is a vector */ 
    if (SCM_VECTORP(s_vector)) {
	
	/* Find its length */
	vec_length = SCM_VECTOR_LENGTH(s_vector);
	
	/* Allocate a C array to hold the values */ 
	values = (double *)malloc(sizeof(double) * vec_length);
	
	/* Copy the Guile vector into the C array */ 
	for (i=0; i < vec_length; i++) {

	    /* Get the i-th element of the list */ 
	    s_value = scm_vector_ref(s_vector, scm_int2num(i));

	    /* Convert it into a C double */
	    values[i] = scm_num2double(s_value, 0, "main()");
	}
    }

    /* Print the C array's values */ 
    for (i=0; i < vec_length; i++) {
	printf("%d : %f\n", i, values[i]);
    }

    return(0);
}

      

Conventional Arrays

Rather unusually among all the basic types, the function that creates an empty conventional array is a Guile function that has no C equivalent. An explanation on how to call a Guile function from C is in the section called Calling Guile functions from C in the chapter called Functions I>.

(make-array fill . args)
      

SCM scm_array_set_x(SCM v, SCM obj, SCM args);

The Guile procedure make-array returns a conventional array in which the elements of the array are initialized to fill. It takes a variable number of args. Each arg is the size of the array in one dimension. If there are two args, a two-dimensional array is created. Also, each arg can be either a single integer, or a list of two integers. If it is a list of two integers, then they are the lower and upper limits of the indices in that dimension. For example

(make-array 0 '(1 3) '(1 4))
creates a two dimension array, initialized to zero, in which the indices range from 1 to 3 in the first dimension and 1 to 4 in the second dimension. The command
(make-array 0 3 4)
also creates a 3 by 4 matrix, but, in this one, the indices range from zero to N-1, much like C arrays.

The following example creates a top-level variable that contains a two-dimensional array. The code is in Example 4> and the output of the code is in Example 5>.

Example 4. Creating a conventional array in C

#include <libguile.h>
#include <stdio.h>

int main()
{
    SCM s_array;
    int i, j;

    /* Start the Guile interpreter */
    scm_init_guile();

    /* Create an empty 5x5 2D array, with indices that range from -2 to 2 */ 
    s_array = scm_c_eval_string ("(make-array 0 '(-2 2) '(-2 2))");

    /* Fill the array with some integers */
    for (j = -2; j <= 2; j++) {
	for (i = -2; i <= 2; i++) {
	    if (i == 0 && j == 0)
		scm_array_set_x (s_array, scm_makfrom0str("hi mom"), 
				 scm_list_2 (scm_int2num (i), scm_int2num (j)));
	    else
		scm_array_set_x (s_array, scm_int2num(i * j), 
				 scm_list_2 (scm_int2num (i), scm_int2num (j)));

	}
    }

    /* Create a Guile top-level variable */ 
    scm_c_define("a-array", s_array);

    /* Use the Guile interpreter to print the var */ 
    scm_c_eval_string ("(display a-array)");
    printf("\n");

    return(0);
}

	

Example 5. Output from Example 4>

#2((4 2 0 -2 -4) (2 1 0 -1 -2) (0 0 hi mom 0 0) (-2 -1 0 1 2) (-4 -2 0 2 4))

	

To convert a Guile array to a C array, we must first assume that there is a programming contract between Guile and C that there is an intention only to pass a specific type of array. Since the general Guile array can have any type of data, any number of dimensions, and may not be zero-indexed in each dimension, the general problem of converting it to C is complex and not likely to be a common problem.

But if we assert that we know the number of dimensions of the array and the type of data it contains, unpacking it into a C array is not overly difficult. The relevant functions in this process are SCM_ARRAYP, scm_array_rank, scm_array_dimension, and scm_uniform_vector_ref.

int SCM_ARRAYP(SCM arr);

SCM scm_array_rank(SCM arr);

SCM scm_array_dimension(SCM arr);

SCM scm_uniform_vector_ref(SCM arr, SCM inds);

The macro SCM_ARRAYP returns non-zero if the parameter arr is an array. The function scm_array_rank returns, as an SCM, the number of dimension of the array.

The function scm_array_dimensions returns a list that contains the dimensions of the array. If the array is one-dimensional (a vector) the list will contain one element. For two-dimensional arrays, there are two elements, and so on. Each element is either a scalar, or a list of two numbers. If it is a scalar N, then the indices for that dimension range from zero to N - 1. If it is a list of two numbers, the first number is the minimum index for that dimension, and the second number is the maximum index. For example, if scm_array_dimensions returned '((1 3) 3), it would be saying that the array arr is a two dimensional array in which the first dimensions indices range from 1 to 3, and the seconds dimension indices range from 0 to 2.

The function scm_uniform_vector_ref returns the element of array arr at the location inds, where inds is a Guile list of array indices. For example, if inds is '(2 2), this is analagous to arr[2][2] in C.

Despite the name, scm_uniform_vector_ref works on both conventional and uniform arrays.

The following example gets a conventional array from Guile, checks that is has two-dimensions, gets the range of array indices, and then prints its contents.

Example 6. Reading a Guile array: main.c

#include <stdio.h>
#include <stdlib.h>
#include <libguile.h>


int main(int argc, char **argv)
{
    SCM s_array, s_val, s_dims;
    int val;
    int i, j;
    int j_min, j_max, i_min, i_max;

    scm_init_guile();
    
    /* Set up the Guile functions */ 
    scm_c_primitive_load ("script.scm");
    
    /* Call a function that returns an array */ 
    s_array = scm_c_eval_string("(make-foo)");

    /* Check to see if it is an array */
    if (SCM_ARRAYP (s_array)) {
	
	/* Ensure that this is two-dimensional */ 
	if ( scm_num2int(scm_array_rank(s_array), 0, "main") == 2) {
	    
	    /* Get the dimensions of the array */
	    s_dims = scm_array_dimensions (s_array);
	    
	    /* For each dimension, if it is a single number, it is the
	     * size of the array in that dimension.  If it is a list
	     * of two numbers, it is the minimum and maximum index for
	     * that dimension. */
	    if ( SCM_FALSEP( scm_list_p (SCM_CAR (s_dims)))) {
		i_min = 0;
		i_max = scm_num2int (SCM_CAR (s_dims), 0, "main") - 1;
	    } else {
		i_min = scm_num2int (SCM_CAAR (s_dims), 0, "main");
		i_max = scm_num2int (SCM_CADAR (s_dims), 0, "main");
	    }
	    if ( SCM_FALSEP (scm_list_p (SCM_CADR (s_dims)))) {
		j_min = 0;
		j_max = scm_num2int (SCM_CADR (s_dims), 0, "main") - 1;
 	    } else {
		j_min = scm_num2int (SCM_CAADR (s_dims), 0, "main");
		j_max = scm_num2int (SCM_CADADR (s_dims), 0, "main");
	    }
	} else {
	    printf("Was expecting a 2D array.  This array is %d dimensions\n",
		   scm_num2int(scm_array_rank(s_array), 0, "main"));		   
	    exit(1);
	}
	    
	for (j = j_min; j <= j_max; j ++) {
	    for (i = i_min; i <= i_max; i ++) {
		/* Get the Guile value at that location in the array */ 
		s_val = scm_uniform_vector_ref(s_array, 
					       scm_list_2(SCM_MAKINUM(i), SCM_MAKINUM(j)));
		val = scm_num2int(s_val, 0, "main");
		
		printf("foo(%2d,%2d) = %d\n", i, j, val);
	    }
	}
    }
    printf("\n");
    
    return(EXIT_SUCCESS);
}


	

Example 7. Reading a Guile array: script.scm

(define (make-foo)
  (let ((foo (make-array -1 '(1 3) 3)))
    (begin
      (array-set! foo 10 2 2)
      foo)))


	

Example 8. Reading a Guile array: output

foo( 1, 0) = -1
foo( 2, 0) = -1
foo( 3, 0) = -1
foo( 1, 1) = -1
foo( 2, 1) = -1
foo( 3, 1) = -1
foo( 1, 2) = -1
foo( 2, 2) = 10
foo( 3, 2) = -1


	

Uniform Arrays

Working with uniform arrays is very similar to working with conventional arrays.

Creating a uniform array can be done in the same manner as creating a conventional array, using this Guile-only function:

(make-uniform-array prototype . args)
      

make-uniform-array replaces make-array in Example 4 in the section called Conventional Arrays> and otherwise everything remains the same.

Alternately, one can use C functions to create the array. The important functions in creating an uniform array is scm_list_to_uniform_array and scm_dimenstions_to_uniform_array.

SCM scm_list_to_uniform_array(SCM ndim, SCM prot, SCM list);

SCM scm_dimensions_to_uniform_array(SCM dims, SCM prot, SCM fill);

SCM scm_array_set_x(SCM array, SCM obj, SCM args);

The function scm_list_to_uniform_array converts the list list into an array. ndims is the number of dimensions of the final array, and prot is a sample entry that demonstrates the type of value that each element of the array is supposed to contain.

The following table lists what the parameter prot must contain to get the desired type of uniform array and it shows how to generate such a type in C.

Table 1. Example Prototypes for Uniform Vectors

Desired TypeProt Must Containin C
boolean (bit-vector)#tSCM_BOOL_T
char (string)non-nul charSCM_MAKE_CHAR('a')
byte (integer)#\nulSCM_MAKE_CHAR(0)
short'sscm_str2symbol("s")
unsigned longa positive inumscm_int2num(1)
signed longa negative inumscm_int2num(-1)
long long'lscm_str2symbol("l")
floata real val fits in a floatscm_double2num(1.0)
doublea real that the doesn't fit in a floatscm_double2num(1.0/3.0)
complexa complex numscm_make_complex(0.0, 1.0)
vector()SCM_EOL

The function scm_dimensions_to_uniform_array creates a uniform array. The dimensions of the array are given by the list dims. If dims were

'(10)
it would create a 1D array of length 10. A dims of
'(5 4)
creates a 2D array that is 5 rows by 4 columns. A dims of
'( '(1 3) '(1 3))
creates a two dimensional array that is 3 by 3 where the indices, instead of ranging from zero to 2, range from 1 to 3. The parameter prot sets the type of the uniform array, as in the previous table. The value of fill is the initial value of all of the elements in the array.

Once the array has been created, its entries can be set by using scm_array_set_x, where array is the array to be modified, obj is the value that an element is to be set to, and args is a list that contains the coordinates of the array element that is to be updated.

The array creation in Example 4 in the section called Conventional Arrays>, re-written using the C functions to create a uniform array of floating point numbers, could be done as in Example 9>

Example 9. Creating a Uniform Array in C

#include <libguile.h>
#include <math.h>
#include <stdio.h>

int main()
{
    SCM s_array;
    SCM s_dims;
    float c_val;
    int i, j;

    /* Start the Guile interpreter */
    scm_init_guile();

    /* Create a the list '((-2 2) (-2 2)), which indicates that our
     *  array will have indices from -2 to 2 in the X dimension and -2
     *  to 2 in the Y dimension. */ 
    s_dims = scm_list_2 (
	scm_list_2 (scm_int2num(-2), scm_int2num(2)), 
	scm_list_2 (scm_int2num(-2), scm_int2num(2))
	);
    
    /* Create a uniform array of floats, initialized to the value
     * 0.0f */ 
    s_array = scm_dimensions_to_uniform_array (s_dims, 
					       scm_double2num(1.0), 
					       scm_double2num(0.0));

    /* Fill the array with some floats */
    for (j = -2; j <= 2; j++) {
	for (i = -2; i <= 2; i++) {
	    c_val = (float)j / (1.0f + (float)(i*i));
	    scm_array_set_x (s_array, scm_float2num(c_val), 
			     scm_list_2 (scm_int2num (i), scm_int2num (j)));
	}
    }

    /* Create a Guile top-level variable */ 
    scm_c_define("a-array", s_array);

    /* Use the Guile interpreter to print the var */ 
    scm_c_eval_string ("(display a-array)");
    printf("\n");

    return(0);
}

	

To read a uniform array into C is the same a reading a conventional array, as demonstrated in Example 6 in the section called Conventional Arrays>