Reading Guile Top-Level Variables into C

Because Guile is a weakly typed language, converting C values to Guile values is a relatively robust process. If an integer can't convert to a Guile inum, Guile can just upgrade it to a bignum. It is free to change representations of data to whatever is most convenient.

Converting from Guile to C, however, is much more difficult process. A Guile variable, because it is weakly typed, must be tested to ensure that it has the type that the C code is expecting.

The Guile to C variable conversion has these steps.

The source code for that process could look like the following.

  s_symbol = scm_c_lookup("g_sc");
  s_value = scm_variable_ref(s_symbol);
  if (SCM_NUMBERP(s_value)) {
    c_value = scm_num2double (s_value);
  }
    

Before a more serious example program is presented, some of these steps should be explained in further detail.

The first step, looking up a Guile symbol by its name, was the subject in the section called Looking Up a Guile Symbol By Name in the chapter called Symbols, Variables and Values>.

Getting the variable from the symbol

Once the symbol has been successfully looked up, the next step is to see if the symbol binds to a variable, and if so, to dereference that symbol.

The workhorse function for this step is scm_variable_ref:

SCM scm_variable_ref(SCM var);

The function scm_variable_ref, given a SCM var that contains a Guile symbol, dereferences the symbol and returns a SCM that contains its variable or function. If the symbol is not bound to a variable or function, the functions prints an error and exits.

Note that unless you are going out of your way to create symbols without variables, there should always be a variable attached to a symbol.

Converting a SCM variable into a C value

After scm_variable_ref has been used to get an SCM that contains the variable, the next step is to convert the SCM into a useable C data type. This process requires that the SCM be properly type checked, since a SCM can contain any type of Guile data. The Guile routines that convert SCM data to C-type data usually exit with fatal errors if the SCM data can not be sensibly converted into the desired C data type.

Converting to C ``boolean''

While I just got finished saying that one must type check SCM data before trying to convert it to C, Guile logicals are a special case. In Guile, any number that is not #f evaluates to true. To test if an SCM is equal to #f, the following macro can be used.

int SCM_FALSEP(SCM x);

The macro SCM_FALSEP returns C ``true'' if the SCM variable x equals #f.

So, to convert an SCM value to a C logical, one could define the following helper function:

int scm2bool (SCM obj)
{
  return (SCM_FALSEP (obj)) ? 0 : 1;
}
	  

(This helper function is actually defined in the deprecated gh interface as gh_scm2bool().)

Caution

Note that a SCM inum of 0 or real of 0 will evaluate to a C ``true.''

Converting to C signed char or unsigned char

This conversion can be a bit tricky. First, Guile stores it character types as 8-bit unsigned integers. If your platform stored characters as 8-bit signed values, a bit of caution is needed: one must cast all C signed character to unsigned characters when converting them to Guile and must cast them back to signed characters when converting from Guile to C.

int SCM_CHARP(SCM x);

int SCM_CHAR(SCM x);

The macro SCM_CHARP returns non-zero if x is a Guile char. The macro SCM_CHAR Converts a Guile char into a C int.

To effect the conversion, one would use SCM_CHARP to test if the SCM contains a Guile char type and then use SCM_CHAR to convert it into an integer.

Caution

Since these are macros, and not type-checked functions, it is possible to create unnoticed errors by not enforcing unsigned characters. As an example, see the program in Example 4>. That program converts a signed char from C to Guile and then back to C. The result is not what is desired, as can be seen from that program's output in Example 5>.

Example 4. Don't pass signed chars to from C to Guile

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

int main (int argc, char *argv[])
{
  signed char sc;
  unsigned char uc;
  SCM s_symbol, s_value;

  scm_init_guile();

  sc = 'ñ';
  uc = 'ñ';

  printf("Before conversion...\n");
  printf("signed c char '%c' is %hhd \n", sc, sc);
  printf("unsigned c char '%c' is %hhd \n\n", uc, uc);
	
  /* Convert the C characters into Guile characters */
  scm_c_define("g_sc", SCM_MAKE_CHAR(sc));
  scm_c_define("g_uc", SCM_MAKE_CHAR(uc));

  /* Convert them back to C values */
  printf("After C -> Guile -> C...\n");

  s_symbol = scm_c_lookup("g_sc");
  s_value = scm_variable_ref(s_symbol);
  printf("signed c char '%c' has become %hhd\n", 
	 SCM_CHAR(s_value), 
	 SCM_CHAR(s_value));
 
  s_symbol = scm_c_lookup("g_uc");
  s_value = scm_variable_ref(s_symbol);
  printf("unsigned c char '%c' has become %hhd\n", 
	 SCM_CHAR(s_value), 
	 SCM_CHAR(s_value));

  return(EXIT_SUCCESS);
  
} 

	  

Example 5. The output

Before conversion...
signed c char 'ñ' is -15 
unsigned c char 'ñ' is 241 

After C -> Guile -> C...
signed c char 'ñ' has become 16777201
unsigned c char 'ñ' has become 241

	  

Converting to C integers

Here are the macros that test the type of Guile numbers.

int SCM_INUMP(SCM x);

int SCM_BIGP(SCM x);

int SCM_NUMP(SCM x);

int SCM_NUMBERP(SCM x);

SCM_INUMP returns non-zero if the SCM x is an inum. Since numbers of type inum are two bits shy of C long integers, they are guaranteed to unpack cleanly into a C long. SCM_BIGP returns non-zero if the SCM is a bignum. SCM_NUMP returns non-zero if the SCM is a numerical value of some type other than an inum. SCM_NUMBERP returns non-zero if the SCM is a number. It is the logical union of SCM_INUMP and NUMP.

Here are the functions that convert Guile numbers to C integers.

short scm_num2short(SCM x, unsigned long pos, const char *s_caller);

unsigned short scm_num2ushort(SCM x, unsigned long pos, const char *s_caller);

int scm_num2int(SCM x, unsigned long pos, const char *s_caller);

unsigned int scm_num2uint(SCM x, unsigned long pos, const char *s_caller);

long scm_num2long(SCM x, unsigned long pos, const char *s_caller);

unsigned long scm_num2ulong(SCM x, unsigned long pos, const char *s_caller);

All of the functions below take the same three arguments: num, pos, and s_caller.

The SCM num contains the number to be converted.

If pos and the s_caller are set, they are used as descriptive information in the error message if the conversion can't be accomplished. Here is an example of an error message resulting from trying to convert a Guile 3.14156 to a C integer. In this example pos and s_caller are not used: pos is set to 0, and s_caller is set to NULL:

ERROR: Wrong type (inexact) argument: 3.14159
	  

Here is the same error when pos is set to 1, and s_caller is set to ``my_func''.

ERROR: In procedure my_func: 
ERROR: Wrong type (inexact) argument in position 1: 3.14159
	

All of the above functions will not do Guile real to C integral conversion even if the Guile real number appears to be an integer. They will raise an error if, for example, one tries to convert a Guile 3.0 into a C integer 3.

In addition to the above, there is an integer conversion macro as well.

long SCM_INUM(SCM x);

The macro SCM_INUM converts a Guile inum into a C long

Converting to C integer-like types

Here are the functions that convert Guile numbers to C integer-like types. They behave the same as the integer conversion functions.

ptrdiff_t scm_num2ptrdiff(SCM x, unsigned long pos, const char *s_caller);

size_t scm_num2size(SCM x, unsigned long pos, const char *s_caller);

Converting to C float and double

Here are the functions that convert Guile numbers to C floats and doubles. The behave the same as the integer conversion functions.

float scm_num2float(SCM x, unsigned long pos, const char *s_caller);

double scm_num2double(SCM x, unsigned long pos, const char *s_caller);

The SCM doesn't have to be a Guile real for these functions to work. These functions will convert Guile inum and bignum as well as real types to C floating point types.

int SCM_REALP(SCM x);

The macro SCM_REALP returns non-zero if the x is a Guile real number. But since these functions will convert Guile integers as well, the macro SCM_NUMBERP is what one would use to ensure that the conversion will be effective.

Converting to C complex numbers

int SCM_COMPLEXP(SCM x);

The macro SCM_COMPLEXP tests to see if an SCM contains a complex number.

double SCM_COMPLEX_REAL(SCM x);

double SCM_COMPLEX_IMAG(SCM x);

To convert a Guile complex to a C complex number, first ensure that the Guile complex number has not been automatically been converted to real number. Guile can convert complex to real when the imaginary part goes to zero.

If it is, use the macros SCM_COMPLEX_REAL and the SCM_COMPLEX_IMAG to extract the real and imaginary parts.

If it is not complex, use scm_num2double to extract the real part.

Converting to C strings

int SCM_STRINGP(SCM x);

The macro SCM_STRINGP tests to see if an SCM contains a complex number.

There isn't a short function in the standard Guile library that will convert a Guile string into a C string. There should be in the next major release (right, guys?).

Until then, the following function from the gh library (which is part of Guile, but whose function declarations live in a separate header file) will take a Guile string and allocate a new C string of the same length and copy it into the C string.

	    #include <guile/gh.h>
	  

char *gh_scm2newstr(SCM str, size_t *lenp);

Given a Guile string str, it returns a newly allocated C string. The length of the string is returned in the variable lenp.

This same function can be used to return blocks of arbitrary 8-bit data, even if it contains zeros. The return parameter lenp gives the length of the returned blocks in bytes. Note that when returning arbitrary 8-bit data, lenp may be greater than the strlen of the returned string, since strlen will only give the length from the beginning of the block to the first (char)0.

Reading Guile Top-Level Variables Into C: An Example

Now to put is all together into an example. In this example, it is assumed that it is acceptable for Guile to stop execution if it finds an error. Therefore, there is little error proofing going on.

The example reads in three variables from a scheme script and converts them to C variables. So the scheme script acts like a simple configuration file. After converting them to C, it prints them out.

The main C file is in Figure FIXME, and the Guile script is in Figure FIXME. The output of the program is in FIXME.

Example 6. Read SCM variables into C: main.c

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <libguile.h>
#include <guile/gh.h>

int main(int argc, char **argv)
{
	double interest_rate;
	long account_num;
	char *client_name;
	size_t len;
	SCM s_symbol, s_value;

	scm_init_guile();
	
	// If there is a config file, parse it
	scm_c_primitive_load ("config.scm");

	// Convert Guile var to C double
	s_symbol = scm_c_lookup("interest-rate");
	s_value = scm_variable_ref(s_symbol);
	interest_rate = scm_num2double(s_value, 0, "main");

	// Now for a string...
	s_symbol = scm_c_lookup("client-name");
	s_value = scm_variable_ref(s_symbol);
	client_name = gh_scm2newstr(s_value, &len);

	// And a long integer...
	s_symbol = scm_c_lookup("account-num");
	s_value = scm_variable_ref(s_symbol);
	account_num = scm_num2long(s_value, 0, "main");

	// Print it out
	printf("Account #: %ld\n", account_num);
	printf("Client: %s\n", client_name);
	printf("Interest rate: %.2f%%\n", interest_rate);

	return(EXIT_SUCCESS);
}


	  

Example 7. Read SCM variables into C: config.scm

(define client-name "Artoo Gonzalez")
(define interest-rate 3.5)
(define account-num 8675309)

	  

Example 8. Read SCM variables into C: Output

Account #: 8675309
Client: Artoo Gonzalez
Interest rate: 3.50%

	  

Guile Scripts as Configuration Files

Now, after many pages of fuffing about, the time has come to propose a serious use for Guile. In the section called Reading Guile Top-Level Variables Into C: An Example>, it was demonstrated that a Guile script could be used to set some C variables, like a poor-man's config file.

Unfortunately, that example doesn't work so well in the real world. If the C program looks for a variable in the config.scm file that doesn't exist, it prints and error and exits. It doesn't do aggressive type checking. It isn't clear at the top level, and there is repeated code.

In this section, some helper functions will be assembled, and from them, a Guile solution for configuration files will be proposed. Finally, there will be a section on why configuration files written this way are inherently cool.

Constraining the problem

As can be seen from the complications of this chapter, not all C data types lend themselves to easy conversion between C and Guile. In creating a configuration file solution, there is no need to account for every variable type.

Let us say that we wish to create a configuration file library that will allow a program's configuration file to have strings, doubles, integers, and boolean values. Also, at no point should unexpected information in the configuration file cause the program to halt.

Looking up variables by name

The issue of robustly looking up variables has been addressed by the utility function does_scm_symbol_exist in the section called Looking Up a Guile Symbol By Name in the chapter called Symbols, Variables and Values>. That utility function takes a Guile top-level variable's name as a C string and tests if it is defined.

If it is defined, then one can dereference it so that its value is captured in an SCM.

Robust conversion to integers

Converting an SCM to an integer can cause the program to halt with an error if the Guile integer is too large to be converted into the desired C type.

To ensure that a Guile integer is not too large, it is necessary to test its size before conversion. This means that its size needs to be tested on the Guile side of things, instead of

The easiest way to deal with this, if it is okay that your config file integers be limited to be a signed 30-bit int, is to require that the SCM variable be an inum. This way, the conversion from inum to C long is guaranteed to work.

This function, given a C string that is supposed to be a Guile name, uses the previously described utility functions to make sure that it can be dereferenced. If it can, it tests that it is an inum, and if so, converts it to a C long.

Robust conversion to a double

In this conversion, the Guile number can be an inum, a bignum, or a real. Any of these will convert into a double without much difficulty. In theory it would be possible to cause the program to halt by entering a bignum that is longer than a C double can hold.

Conversion to string

The difference between this and the previous is that a string needs to be allocated.

An Example

Here is one version of how a Guile config file might be implemented. It looks at the config file for the values of some configuration variables, and if they are not found, it uses the hard-coded defaults.

In this case, two of the variables are set in the config file, and one is not. It picks up its hard-coded default value from the main.

Example 9. Guile Config Files: main.c

/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>

#include <libguile.h>
#include <guile/gh.h>


int does_scm_var_exist(const char *name);
int get_scm_int_value(const char *name, int *val);
int get_scm_real_value(const char *name, double *val);
int get_scm_string_value (const char *name, char **c_string);
int get_scm_variable(const char *name, SCM *v);

int main(int argc, char **argv)
{
	double precision;
	int max_iterations;
	char *out_filename;

	scm_init_guile();
	
	// If there is a config file, parse it
	scm_c_primitive_load ("config.scm");

	// Check if EPS was set in the config file.
	// If it isn't use the default
	if (!get_scm_real_value ("EPS", &precision)) 
	{
		 precision = 1.0e-5;
	}

	if (!get_scm_int_value ("MAX_ITER", &max_iterations)) 
	{
		max_iterations = 20;
	}
	
	if (!get_scm_string_value ("OUT_FILE", &out_filename))
	{
			out_filename = strdup("output.txt");
	}
	
	printf ("Desired fractional precision: %g\n", precision);
	printf ("Maximum iterations: %d\n", max_iterations);
	printf ("Output filename: %s\n", out_filename);
		
	return(EXIT_SUCCESS);
}

int get_scm_real_value(const char *name, double *c_value)
{
	 SCM s_value;				/* A scheme varaible */

	 assert (name != (const char *)NULL);
	 assert (c_value != (double *)NULL);
	
	 if (get_scm_variable(name, &s_value)) 
	 {
		  /* Check to see if it is a real num or non-huge int */
		  if (SCM_NUMBERP(s_value)) 
		  {
			   *c_value = scm_num2double (s_value, 0, "get_scm_real_value");
			   return 1;
		  }
		  
		  fprintf (stderr, "Warning. config variable %s "
				   "was supposed to be a real number\n", name);
		  return 0;
	 }
	 
	 return 0;
}

int get_scm_int_value (const char *name, int *c_value)
{
	 SCM s_value;				/* A scheme varaible */

	 assert (name != (const char *)NULL);
	 assert (c_value != (int *)NULL);

	 if (get_scm_variable (name, &s_value)) 
	 {
		  // Check to see if it is an INUM, a small integer
		  if (SCM_INUMP (s_value)) 
		  {
			   *c_value = SCM_INUM (s_value);
			   return 1;
		  }
		  
		  // It looks like the variable exists, but, is not an integer
		  fprintf(stderr, "Warning. Config variable %s was "
				  "supposed to be an integer\n", name);
		  return 0;
	 }
	 return 0;
}

int get_scm_string_value (const char *name, char **c_string)
{
	SCM s_value; 				/* A scheme variable*/
	int length;

	assert (name != (const char *)NULL);
	assert (c_string != (char **)NULL);

	 if (get_scm_variable (name, &s_value)) 
	 {
		  // Check to see if it is an string
		  if (SCM_STRINGP (s_value)) 
		  {
			   *c_string = gh_scm2newstr (s_value, &length);
			   return 1;
		  }
		  
		  // It looks like the variable exists, but, is not a string
		  fprintf(stderr, "Warning. Config variable %s was "
				  "supposed to be an string\n", name);
		  return 0;
	 }
	 return 0;
}

int get_scm_variable (const char *name, SCM *s_value)
{
	 SCM s_symbol;

	 assert (name != (const char *)NULL);
	 assert (s_value != (SCM *)NULL);

	 // Test to see if a variable by that name exists
	 if (!does_scm_var_exist (name)) 
	 {
		  return 0;
	 }
	
	 // Get the variable by its name
	 s_symbol = scm_c_lookup (name);

	 // Get the value of the variable
	 *s_value = scm_variable_ref (s_symbol);
	 
	 return 1;
}


int does_scm_var_exist(const char *name)
{
	 SCM s_symbol;
	 SCM s_var;
	
	 assert(name != (const char *)NULL);

	 s_symbol = scm_str2symbol(name);

	 // Check to see if the symbol exists
	 s_var = scm_sym2var 
		  (s_symbol, scm_current_module_lookup_closure(), SCM_BOOL_F);
	
	 if (s_var != SCM_BOOL_F) 
	 {
		  return 1;
	 }
	 
	 return 0;
}


	  

Example 10. Config Files: config.scm

(define EPS 1e-6)
(define OUT_FILE "output.new")

	  

Example 11. Config Files: Output

Desired fractional precision: 1e-06
Maximum iterations: 20
Output filename: output.new