Functions I

In this chapter, I'll explain the basics of calling Guile functions from C and calling C functions from Guile.

First, I'll describe how to write functions in Guile, and then call them from C. Then I'll show how to write functions in C and make them available to Guile. Finally, I'll discuss some macros to simplify that process.

So far, I haven't really discussed the compound data types like arrays and lists, so functions that deal with compound data types will be discussed separately in the chapter called Functions II>.

Calling Guile functions from C

Now we are getting to where we are actually using Guile to extend C. By writing some of the functionality of the program in Guile, one can change the program without having to recompile it.

If you've followed the discussion so far on conversion of simple data types, then functions of simple data type should be fairly straightforward. Because we haven't discussed arrays or compound data types, all of the Guile functions in this section will be either thunks, procedures of simple data, or pure functions of simple data.

A thunk is a procedure that takes no input parameters and has no return value. An example of a thunk is the one that prints "Hello World" in Example 2 in the section called Simple Guile Example in the chapter called Getting Started>.

A procedure takes input data but has no return value.

A pure function takes input data and returns a single value.

There are two ways to call Guile functions from C: an easy and slower way, and a harder and faster way.

The easy, slower way is by using the function scm_c_eval_string. This function takes a snippet of Guile code, packed as a C string, and runs it, returning the result.

SCM scm_c_eval_string(const char * cmd);

The function scm_c_eval_string takes some Guile code in the string cmd, executes it, and returns the return value of the function call as an SCM. Generally, one would create some Guile code using sprintf and pass it to scm_c_eval_string to be executed.

The harder way is to call Guile functions by accessing them in the same manner as we access Guile variables. This is speedier that the previous method, since the Guile parser has less work to do. Calling Guile functions from C has much the same process as reading Guile variables from C.

Looking up a Guile function by name to get its symbol is the same process as looking up a Guile variable by name to get its symbol. This has been covered in the section called Looking Up a Guile Symbol By Name in the chapter called Symbols, Variables and Values>. The function scm_c_lookup takes a C string and returns the Guile symbol, if found.

Once the symbol is found, it can be dereferenced to get the SCM that contains the procedure. Again, just as for Guile variables, the important function is scm_variable_ref, which takes the Guile symbol and returns the SCM that contains the procedure.

Calling from C a Guile function with no parameters

The simplest Guile function that can be called is one that takes no parameters, such as the do-hello in Example 2 in the section called Simple Guile Example in the chapter called Getting Started>. To call a Guile function with no input parameters from C, the scm_call_0 function is used.

SCM scm_call_0(SCM proc);

The function scm_call_0 takes a procedure, such as might be returned by a call to scm_variable_ref, executes it, and returns its return value as an SCM. The zero in the name scm_call_0 indicates that zero parameters are being passed to the function. If the Guile function returns no value, scm_call_0 will return the constant SCM_UNDEFINED.

Calling from C a Guile function with parameters

Just as scm_call_0 is used to call a function with zero arguments, the functions scm_call_1, scm_call_2, scm_call_3, and scm_call_4 are used to call a Guile function with one, two, three or four arguments.

SCM scm_call_1(SCM proc, SCM arg1);

SCM scm_call_2(SCM proc, SCM arg1, SCM arg2);

SCM scm_call_3(SCM proc, SCM arg1, SCM arg2, SCM arg3);

SCM scm_call_4(SCM proc, SCM arg1, SCM arg2, SCM arg3, SCMarg4);

The trick to using these functions is that all the input parameters must be converted to type SCM to make the function call. Everything passed must be type SCM.

Limitations of the scm_call functions

There are limits to calling Guile procedures using the scm_call functions.

The first limitation, is that no more than four parameters can be passed. To pass more than four parameters, you need to pass them as a Guile list. Lists will be covered in the chapter in aggregate data types.

Second, these functions don't allow you to call a Guile function that takes a variable number of arguments. Again, for this, you need to pass a Guile list. Functions of variable arguments will be covered in FIXME.

Calling Guile Functions from C, an Example

To put this knowledge to good use, here is an example that uses Guile to compute tax and shipping given a price, length and weight. Tax and shipping are good examples of rarely-changing but not never-changing functions. Because they are only adjusted on a yearly basis, one might be tempted to put tax tables and shipping tables in the C code of a program. But to modify those tables when they change would require a recompile of the whole program. Sticking those troublesome functions in a Guile script would allow them to be more easily modified, even after a system is fielded.

In the example, the tax table and shipping table are coded into Guile functions in script.scm.

Example 1. Calling Guile Functions with Simple Data from C: main.c

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

int main (int argc, char *argv[])
{
	SCM func_symbol;
	SCM func;
	SCM ret_val;
	double subtotal, tax, shipping, total;
	double weight, length;
	
	scm_init_guile();
	
	/* Load the scheme function definitions */
	scm_c_primitive_load ("script.scm");	
	
	/* Call a thunk, a procedure with no parameters */
	func_symbol = scm_c_lookup("do-hello");
	func = scm_variable_ref(func_symbol);
	scm_call_0 (func);

	subtotal = 80.00;
	weight = 10.0;
	length = 10.0;

	/* Call a procedure that takes one argument */
	func_symbol = scm_c_lookup("compute-tax");
	func = scm_variable_ref(func_symbol);

	ret_val = scm_call_1 (func, scm_double2num(subtotal));

	tax = scm_num2double(ret_val, 0, NULL);

	/* Call a function that takes two arguments */
	func_symbol = scm_c_lookup("compute-shipping");
	func = scm_variable_ref(func_symbol);

	ret_val 
	  = scm_call_2 (func, scm_double2num(weight), scm_double2num(length));

	shipping = scm_num2double(ret_val, 0, NULL);

	total = subtotal + tax + shipping;
	printf("Subtotal %7.2f\n", subtotal);
	printf("Tax      %7.2f\n", tax);
	printf("Shipping %7.2f\n", shipping);
	printf("-----------------\n");
	printf("Total    %7.2f\n", total);

	return(EXIT_SUCCESS);
} 


	

Example 2. Calling Guile Functions with Simple Data from C: script.scm

(define (do-hello)
  (begin
    (display "Welcome to FloorMart")
    (newline)))

(define (compute-tax subtotal)
  (* subtotal 0.0875))


(define (compute-shipping weight length)

  ;; For small, light packages, charge the minimum
  (if (and (< weight 20) (< length 5))
      0.95

      ;; Otherwise for long packages, charge a lot
      (if (> length 100)
	  (+ 0.95 (* weight 0.1))
	  
	  ;; Otherwise, charge the usual
	  (+ 0.95 (* weight 0.05)))))
      

	

Example 3. Calling Guile Functions with Simple Data from C: output

Welcome to FloorMart
Subtotal   80.00
Tax         7.00
Shipping    1.45
-----------------
Total      88.45

	

Here is the same example using the scm_c_eval_string instead of scm_c_lookup. This method is conceputally simpler, but, is slower because the Guile parser gets involved in interpreting the code. Otherwise, it is the same.

Example 4. Using scm_c_eval_string

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

int main (int argc, char *argv[])
{
	SCM ret_val;
	double subtotal, tax, shipping, total;
	double weight, length;
	char cmd[500];
	
	scm_init_guile();
	
	/* Load the scheme function definitions */
	scm_c_primitive_load ("script.scm");	
	
	/* Call a thunk, a procedure with no parameters */
	scm_c_eval_string("(do-hello)");

	subtotal = 80.00;
	weight = 10.0;
	length = 10.0;

	/* Call a procedure that takes one argument */
	sprintf(cmd, "(compute-tax %f)", subtotal);
	ret_val = scm_c_eval_string(cmd);

	tax = scm_num2double(ret_val, 0, NULL);

	/* Call a function that takes two arguments */
	sprintf(cmd, "(compute-shipping %f %f)", weight, length);
	ret_val = scm_c_eval_string(cmd);

	shipping = scm_num2double(ret_val, 0, NULL);

	total = subtotal + tax + shipping;
	printf("Subtotal %7.2f\n", subtotal);
	printf("Tax      %7.2f\n", tax);
	printf("Shipping %7.2f\n", shipping);
	printf("-----------------\n");
	printf("Total    %7.2f\n", total);

	return(EXIT_SUCCESS);
}