Scripting with Guile

Extension language enhances C and Scheme

Scheme is a programming language, but Guile—an interpreter and library for Scheme—transforms it into an embedded scripting language, making it ideal for bringing dynamic new life to your static applications. Take a quick tour of Guile, and discover its powerful features for building extensible applications.

M. Tim Jones (mtj@mtjones.com), Consultant Engineer, Emulex Corp.

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.



20 January 2009

Also available in Russian Japanese

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
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.

A simple example

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.

Scripts in gaming

Modern games commonly include scripting languages, from traditional interpreted languages like Python and Ruby to special-purpose scripting languages like UnrealScript (see the Resources section for links). In game systems, these scripting languages can be used to implement the behaviors of non-player characters and even the behavior of objects that appear in the game. Game development is more convenient with scripts, because long compile cycles are unnecessary to introduce new behaviors. If you dig down into the subdirectories of your favorite PC-based game, more often than not, you'll find scripts.

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.

Variables

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))

Procedures

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

Conditionals

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

Loops

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.

scm_call limitations

Guile provides five variants of scm_call. A Scheme function can be called with zero arguments (scm_call_0) or as many as four arguments (scm_call_4), restricting the number of Scheme variables passed through Guile to four. Additionally, variable argument functions are not supported. If more than four arguments or a variable number of arguments need to be passed, a Scheme list object can be constructed with the desired number of arguments.

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.


Epilogue

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.

Resources

Learn

Get products and technologies

  • With IBM trial software, available for download directly from developerWorks, build your next development project on Linux.

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Linux on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Linux
ArticleID=365602
ArticleTitle=Scripting with Guile
publish-date=01202009