| How to extend C programs with Guile | ||
|---|---|---|
| <<< Previous | Data and Values | Next >>> |
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.
Look up a Guile symbol by its name.
Dereference the symbol to get its variable contained in a SCM.
Test the SCM variable to ensure it has an appropriate type.
Convert the SCM variable's data into C data.
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>.
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:
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.
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.
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.
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().)
![]() | Note that a SCM inum of 0 or real of 0 will evaluate to a C ``true.'' |
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.
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.
![]() | 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);
}
|
Here are the macros that test the type of Guile numbers.
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.
The macro SCM_INUM converts a Guile inum into a C long
Here are the functions that convert Guile numbers to C integer-like types. They behave the same as the integer conversion functions.
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.
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.
The macro SCM_COMPLEXP tests to see if an SCM contains a complex number.
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.
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.
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.
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);
}
|
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.
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.
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.
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.
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.
The difference between this and the previous is that a string needs to be allocated.
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;
}
|
| <<< Previous | Home | Next >>> |
| Creating Guile Top-Level Variables from C | Up | Functions I |