Guile, which launched in 1995, is an interpreter for the Scheme language, a simplified derivative of the Lisp language, first introduced by John McCarthy in 1958. But Guile makes Scheme embeddable, which makes the interpreter ideal for embedded scripting. Guile isn't just another extension language: it's the official extension language of the GNU project. You'll find Guile used for scripting in a number of open source applications—from gEDA CAD tools to the Scheme Constraints Window Manager (Scwm), which provides dynamic configurability through Scheme scripting (see the Resources section for links). Guile follows a very successful history of application extension through scripting, from GNU Emacs, the GIMP, and Apache Web Server.
The key behind Guile is extensibility; see Figure 1. With Guile, you can interpret Scheme scripts, dynamically bind scheme scripts into compiled C programs, and even integrate compiled C functions into Scheme scripts. This valuable feature means that users can tailor or customize your applications to add their own value.
Figure 1. Scripting use models for Guile
One of the best examples of application customization is in the video gaming industry. Video games permit a tremendous amount of customization through scripting. Many game programs even use scripting in their core design to implement certain aspects (such as non-player character behavior) with scripts.
Let's now look at a simple example of integrating Guile into a C language program. In this case, I use a C program that calls a Scheme script. Listing 1 and Listing 2 show the source for this first example.
Listing 1 presents the C application that invokes the
Scheme script. The first thing to notice is the inclusion of the
libguile.h header file, which makes available the necessary Guile symbols.
Next, notice a new type defined: SCM. This type
is an abstract C type that represents all Scheme objects contained within
Guile. Here, I'm representing the Scheme function that I call later.
The first thing that needs to be done for any thread using Guile is to make
a call to scm_init_guile. This function
initializes the global state of Guile and must be called prior to any
other Scheme function. Next, prior to calling a Scheme function, the file
in which this function resides must be loaded. You do this by using the
scm_c_primitive_load function. Note the naming
here: the _c_ in the function indicates that it
is passed a C variable (rather than a Scheme variable).
Next, I use scm_c_lookup to find and return the
variable bound by the symbol (the Scheme function in the model), which is
then dereferenced with scm_variable_ref and
stored in the Scheme variable func. Finally, I
call the Scheme function using scm_call_0. This
Guile function calls a previously defined Scheme function with zero
arguments.
Listing 1. A C program that invokes a Scheme script
#include <stdio.h>
#include <libguile.h>
int main( int argc, char **arg )
{
SCM func;
scm_init_guile();
scm_c_primitive_load( "script.scm" );
func = scm_variable_ref( scm_c_lookup( "simple-script" ) );
scm_call_0( func );
return 0;
}
|
Listing 2 provides the Scheme function that is invoked from within the C
program. This function uses the display
procedure to print a string to the screen. This function is followed by a
call to the procedure newline, which outputs a
carriage return.
Listing 2. A Scheme script that is called from C (script.scm)
(define simple-script
(lambda ()
(display "script called") (newline)))
|
What's interesting here is that the script is not statically bound to the C program; it is dynamically bound. The Scheme script can be changed, and when the previously compiled C program is executed, it will execute the new behavior implemented in the script. That's the power of embedded scripting: you take the speed of compiled applications and provide the extensible power of dynamic scripting.
Now that you have a simple example under your belt, let's dig in a little further to explore some of the other elements of Scheme scripting within the C language.
A short introduction to Scheme
As Scheme may seem a bit foreign to some, let's look at a few examples that illustrate the power of the language. These examples illustrate variables, conditionals, and loops in addition to some of the key features of Scheme. A full treatment of Scheme is outside the scope of this article, but you can find links to references in the Resources section.
In these examples, I use the Guile interpreter, which allows me to work with Scheme in real time, providing Scheme code and seeing the results immediately.
Scheme is a dynamically typed language; therefore, the type of a variable is not generally known until run time. Scheme variables are then simply containers whose type can be defined later.
Variables are created using the define
primitive, then changed with the set!
primitive. Here, I do just that:
guile> (define my-var 3) guile> (begin (display my-var) (newline)) guile> (set! my-var (* my-var my-var)) |
Not surprisingly, it's also possible to create procedures in
Scheme—also with the define primitive.
Procedures can be anonymous (lambda procedures) or named. In the
case of named procedures, they're stored in a variable, as shown here:
(define (square val) (* val val)) |
This form differs from the traditional Lisp syntax, if you happen to be familiar with that, but it's somewhat simpler to read. I can then use this new procedure just like any other primitive, as shown here:
guile> (square 5) 25 |
Scheme contains a few ways to do conditionals. The most basic is the simple
if condition. It defines a test conditional, a
true expression, and an optional false expression. In the example below,
you can see the list processing perspective of Scheme. The list begins
with if and ends with
(display "less"). Recall that Scheme is a
derivative of Lisp and therefore is built of lists. Scheme represents both
code and data as lists, which allows the language to blur the line (code
as data and data as code).
guile> (define my-var 3) guile> (if (> my-var 20) (display "more") (display "less")) less |
Scheme implements loops through recursion, which forces a particular
mindset when implementing a loop. However, it's a natural way to iterate.
The following example illustrates a Scheme script that iterates from 0 to
9, then prints done. This example uses what in
Scheme is called tail recursion. Note at the end of the loop that I
recursively call the same function with an argument that is one greater
than the previous, implementing the iteration of the loop. In traditional
languages, this recursion eats away at the stack to maintain a history of
the calls; in Scheme, it's different. The last call (the tail)
simply invokes the function without any procedure call or stack
maintenance overhead.
(let countup ((i 0))
(if (= i 10) (begin (display "done") (newline))
(begin
(display i)
(newline)
(countup (+ i 1)))))
|
Another interesting way to loop in Scheme is through the
map procedure. This concept simply applies (or
maps) a procedure to a list, as shown in the following example.
This approach is both readable and simple.
guile> (define my-list '(1 2 3 4 5)) guile> (define (square val) (* val val)) guile> (map square my-list) (1 4 9 16 25) |
Extending C programs with Scheme scripts
As you saw in Listing 1, extending C programs with Scheme is relatively painless. Now, here's another example that explores some of the other application programming interfaces (APIs) available for bridging C to Scheme. In most applications, you need to not only make calls to Scheme but also pass argument to Scheme functions, receive return values, and share variables between the two environments. Guile provides a rich set of functions to enable this functionality.
Guile attempts to straddle the line between the two environments and extend to C the power of Scheme. In this regard, you'll find dynamic types, continuations, garbage collection, and other Scheme concepts extended to C through the Guile API.
One example of extending Scheme concepts into C is the ability to
dynamically create new Scheme variables from the C environment. The C
function for creating Scheme variables is
scm_c_define. Recall that
_c_ indicates that you're providing a C type as
the argument. If you already had the Scheme variable (as provided by the
scm_c_lookup function), you could instead use
scm_define. In addition to creating Scheme
variables in C, you can also dereference Scheme variables and convert
values between the two environments. I explore examples of these in
Listing 3.
Listing 3 and Listing 4 present two examples of interactions between C and Scheme. The first example illustrates calling a Scheme function from C, passing in an argument, and capturing the return value. The second example creates a Scheme variable to pass in the argument. Listing 4 presents the Scheme functions, which implement the same behavior, but the first with an argument and the second with a static variable.
In the first example in Listing 3, I simply use the
scm_call_1 function to call the Scheme function
with one argument. Note that here you must pass in Scheme values to the
function: The scm_int2num function is used to
convert a C integer into a Scheme numerical data type. You use the
opposite scm_num2int to convert the Scheme
variable ret_val into a C integer value.
The second example in Listing 3 begins by creating a new Scheme variable
with scm_c_define, identified by a C string
variable (sc_arg). This variable is
auto-initialized using the type conversion function
scm_int2num. Now that the Scheme variable has
been created, you can simply call the Scheme function
square2 (this time without an argument) and
follow the same process to grab and dereference the return value.
Listing 3. Exploring Scheme functions and variables with C
#include <stdio.h>
#include <libguile.h>
int main( int argc, char *argv[] )
{
SCM func;
SCM ret_val;
int sqr_result;
scm_init_guile();
/* Calling the square script with a passed argument */
scm_c_primitive_load( "script.scm" );
func = scm_variable_ref( scm_c_lookup( "square" ) );
ret_val = scm_call_1( func, scm_int2num(7) );
sqr_result = scm_num2int( ret_val, 0, NULL );
printf( "result of square is %d\n", sqr_result );
/* Calling the square2 script using a Scheme variable */
scm_c_define( "sc_arg", scm_int2num(9) );
func = scm_variable_ref( scm_c_lookup( "square2" ) );
ret_val = scm_call_0( func );
sqr_result = scm_num2int( ret_val, 0, NULL );
printf( "result of square2 is %d\n", sqr_result );
return 0;
}
|
Listing 4 presents the two Scheme procedures that are used by the C program
shown in Listing 3. The first procedure,
square, is a traditional Scheme function that
accepts a single argument and returns a result. The second procedure,
square2, accepts no arguments, but instead
operates on a Scheme variable (sc_arg). As with
the previous procedure, this variable also returns the result.
Listing 4. Scheme scripts that are called from Listing 3 (script.scm)
(define square (lambda (x) (* x x))) (define square2 (lambda () (* sc_arg sc_arg))) |
Extending Scheme scripts with C functions
In this final example, I explore the process of calling C functions from
Scheme scripts. I start with the Scheme-callable function in Listing 5.
The first thing you'll notice is that although this is a C function, it
receives a Scheme object and returns a Scheme object in response
(SCM type). I begin by creating a C variable
that I use to grab the SCM argument using the
scm_num2int function (converting the Scheme
numerical type to a C int). With this, I square
the argument and return it through another call to
scm_from_int.
The remainder of the program in Listing 5 sets up the environment to boot
into Scheme. After initializing the Guile environment, I export the C
function to Scheme with a call to
scm_c_define_gsubr, which takes as arguments
the name of the function in Scheme, the number of arguments (required,
optional, rest), and the actual C function to be exported. The rest you've
seen before. I load the Scheme script, get a reference to the particular
Scheme function, and call it with no arguments.
Listing 5. C program for setting up the environment for Scheme
#include <stdio.h>
#include <libguile.h>
SCM c_square( SCM arg)
{
int c_arg = scm_num2int( arg, 0, NULL );
return scm_from_int( c_arg * c_arg );
}
int main( int argc, char *argv[] )
{
SCM func;
scm_init_guile();
scm_c_define_gsubr( "c_square", 1, 0, 0, c_square );
scm_c_primitive_load( "script.scm" );
func = scm_variable_ref( scm_c_lookup("main-script") );
scm_call_0( func );
return 0;
}
|
Listing 6 provides the Scheme script. This script displays the response to
the call to c_square, which is the function
exported in the C program in Listing 5.
Listing 6. Scheme script that calls the C function (script.scm)
(define main-script
(lambda ()
(begin (display (c_square 8))
(newline))))
|
A trivial example, but it illustrates the ease with which you can share code and variables between the two language environments.
The days of building and delivering static software and products are over. Today, users expect their products to be dynamic and easily customizable. Although this evolution comes with new complexity, it ultimately allows users to show us the way to create new value in our applications. Hopefully, this article helps you glimpse the power of Guile. Scheme may be one of the oldest programming languages still in use, but it also remains one of the most powerful. Guile has succeeded in making it even more powerful and useful.
Learn
- The
GNU extension language home page
provides the latest release of Guile, FAQs, and documentation (the freely
available
Guile manual
is very extensive). Also at the GNU Web site is a set of
Guile resources,
a great place for getting started with Guile.
- The
GNU Electronic Design Automation (gEDU)
package and the
Scheme Constraints Window Manager
(Scwm) are two projects that make use of
Guile for embedded scripting.
- UnrealScript is
scripting language that
was designed for the Unreal Engine by Epic Games. This scripting language
allowed the gaming community to write new in-game content.
- Read about
game scripting in Python
at Gamasutra.
- See the Scheme Wiki
and the Scheme resources
at the Guile Web site, including a scheme code repository, Scheme language standards, and lecture
notes.
Although Scheme is an older language,
it's well worth learning for the properties it teaches. Recursion, for
example, is an introductory topic in most languages, but for Scheme, it's
imperative.
- Glenn Vanderburg's site archives
UseNet postings that show where the Guile got its start: in the infamous "Tcl
War" UseNet discussion. The flamewar began with the benign title, "Why you
should not use Tcl," by Richard Stallman and burst into a month-long
heated debate.
- In the
developerWorks Linux zone,
find more resources for Linux developers (including developers who are
new to Linux),
and scan our
most popular articles and
tutorials.
- See all
Linux tips
and
Linux tutorials
on developerWorks.
- Stay current with
developerWorks technical events and Webcasts.
Get products and technologies
- With
IBM trial software,
available for download directly from developerWorks, build your next
development project on Linux.
Discuss
- Get involved in the
developerWorks community
through blogs, forums, podcasts, and spaces.
M. Tim Jones is an embedded firmware architect and the author of Artificial Intelligence: A Systems Approach, GNU/Linux Application Programming (now in its second edition), AI Application Programming (in its second edition), and BSD Sockets Programming from a Multilanguage Perspective. His engineering background ranges from the development of kernels for geosynchronous spacecraft to embedded systems architecture and networking protocols development. Tim is a Consultant Engineer for Emulex Corp. in Longmont, Colorado.





