Tutorial Introduction to Guile

This is a tutorial introduction to using Guile, the GNU extension language. I'm aiming at people who want to use Guile, and who don't want to mess around in the guts of Guile itself.

For example, if you've just written a spiffy new word processor/image manipulator/window manager, and you want your users to be able to run cunning scripts, then Guile is the library for you and (hopefully) this is the tutorial for you.

I'm going to assume that you know a little bit about the Scheme programming language, and I'm also going to assume that you have managed to get Guile successfully built and installed on your system. I'm also going to assume that you have Guile version 1.3 or higher.


The Fundamentals

Theory

Guile is an implementation of the Scheme programming language, made available as a library, together with conventions for interfacing to the interpreter from other programming languages.

The point of this is to allow extensibility. You can write a big piece of software in C, and then allow the users of your software to write Scheme scripts which call various C functions in your software. I'll show a simple example of this later on.

That's not all. One of the things that Scheme as a programming language is particularly good at is the emulation of other programming languages. This means that your users don't actually have to write their extension scripts in Scheme; instead, they can write the scripts in Tcl or Python, and just load up the appropriate chunk of Scheme code to emulate that language. (Or at least they will be able to, when the appropriate translators have been written.)

Practice

In concrete terms, you'll have a number of things available once you have Guile correctly installed.

Firstly, you should have guile, which is the Guile Scheme interpreter as a stand-alone executable. This is useful if you want to just write Scheme code; it's also useful for testing out bits of extension Scheme code interactively. This should be very small, since all it contains is a thin wrapper for the Guile shared library (on my system, it's less than 4k).

Secondly and most importantly, you should have libguile.so. This is the Guile shared library, which you can link to from your C (or C++ or whatever) code.

Thirdly, you'll probably also have some standard Scheme library functions installed somewhere. These will typically have a .scm filename extension; on my (RedHat 6.0 Linux) system these are installed under /usr/share/guile.

The things I've described so far make up the primary parts of Guile, that is the things you'll need if you want to run a program which uses Guile as its extension language. (In RedHat package terms, these are all in the guile package).

Given that we want to write code, we need a few more things (which are kept in the guile-devel RedHat package). . . .

Fourth is a collection of header files which cover the process of calling Guile from C and vice-versa. These might get installed into /usr/include/guile and /usr/include/libguile, for example.

Fifth is the utility function guile-snarf. This utility is used to ease the process of getting Guile and C to talk to one another, provided you stick to some coding conventions.

Sixth and last is the utility guile-config. This program scans your local configuration and tells you what options you need to compile and link Guile code into your programs.


The Tortoise

"Why did you call him Tortoise, if he wasn't one?" Alice asked.
"We called him Tortoise because he taught us," said the Mock Turtle angrily.
Lewis Carroll, Through the Looking Glass

To demonstrate Guile, we are going to write a very simple graphics program. It's written for the X Window system; however, it is so simple it should be easy to convert to other graphics systems.

The program draws graphics by assuming that there is a tortoise sitting in the middle of the screen. This tortoise is quite stupid, and can only understand a small number of instructions.

You can ask the tortoise to turn to the right by a number of degrees (negative numbers will get him to turn to the left). You can ask the tortoise to walk forward a certain number of steps. And you can ask him to hold a pen either in his paws or behind his ear, so that when he moves he will either leave a mark on the ground or not (respectively).

Finally, in case the tortoise gets utterly confused, you can ask the tortoise to return to the middle of the screen and turn to face the top of the screen again

The Core Program

To program this up, I'm going to write a C program where each of the tortoise instructions corresponds to a function call.

First, lets take care of the bookkeeping. The main() function will deal with the mechanics of getting some graphics - getting a window and a GC, in X terms.

#include <X11/Xlib.h>
#define WINDOW_SIZE 500
Display *theDisplay;
Window theWindow;
Screen *theScreen;
GC theGC;
int main(int argc, char *argv[])
{
    theDisplay = XOpenDisplay(NULL);
    XSynchronize(theDisplay, True);
    theScreen = DefaultScreenOfDisplay(theDisplay);
    theWindow = XCreateSimpleWindow(theDisplay, RootWindowOfScreen(theScreen),
                                    0, 0,
                                    WINDOW_SIZE, WINDOW_SIZE, 0,
                                    BlackPixelOfScreen(theScreen),
                                    WhitePixelOfScreen(theScreen));
    theGC = XCreateGC(theDisplay, theWindow, 0L, NULL);
    XSetForeground(theDisplay, theGC, BlackPixelOfScreen(theScreen));
    XMapWindow(theDisplay,theWindow);
    /* more stuff to come here . . */
    return 0;
}

Next, we have some variables which hold the current state of the tortoise:

double currentX;
double currentY;
double currentDirection;
int penDown;

We need to initialize this data, so we add the following function invocation to main():

    tortoise_reset();

The function being invoked here implements one of the things that the tortoise can do. There are similar functions which implement all of the other things that the tortoise can do:

#include <math.h>
#define DEGREES_TO_RADIANS  (3.1415926535897932384626433832795029L/180.0)

void tortoise_reset()
{
    currentX = currentY = WINDOW_SIZE/2;
    currentDirection = 0.0;
    penDown = 1;
}
void tortoise_pendown()
{
    penDown = 1;
}
void tortoise_penup()
{
    penDown = 0;
}
void tortoise_turn(int degrees)
{
    currentDirection += (double)degrees;
}
void tortoise_move(int steps)
{
    double newX, newY;
    /* first work out the new endpoint */
    newX = currentX + sin(currentDirection*DEGREES_TO_RADIANS)*(double)steps;
    newY = currentY - cos(currentDirection*DEGREES_TO_RADIANS)*(double)steps;
    /* if the pen is down, draw a line */
    if (penDown) XDrawLine(theDisplay, theWindow, theGC,
                           (int)currentX, (int)currentY, (int)newX, (int)newY);
    /* in either case, move the tortoise */
    currentX = newX;
    currentY = newY;
}

Testing it Out

We can test that we have written our primitives correctly by temporarily including some test code at the end of main():

    {
        int ii;
        tortoise_pendown();
        for (ii=0; ii<4; ii++) {
            tortoise_move(100);
            tortoise_turn(90.0);
        }
        /* sleep for a bit so the window stays visible */
        sleep(10);
    }

As expected, this draws a square in the window. (Compilation instructions here)

Square drawn by test code

That's it. We have all the code we need. Except, of course, that we have no way to get our tortoise to do anything. Now we want to let the user instruct the tortoise directly.


Becoming BeGuiled

Talking to the Tortoise

This is where Guile comes into play. Without Guile, we would have to write code to read input from the user, interpret it, and call the appropriate tortoise primitive.

Even if we did write this code, the user would only be able to instruct the tortoise directly. There would be no way for the user to perform loops or iterations; they would just have to do a lot of typing.

In fact, in order to get more programmable function out of the tortoise we would need to write an interpreter for a tortoise programming language.

This would be wasted effort; Guile already provides a perfectly good way of making our tortoise programmable. The next section talks about how to do this.

Making Guile available from the program

The first step is to get access to the Guile library from within the program. To do this, we first need to include the master header file:

#include <guile/gh.h>

You may need to add an extra include directory to your path to get at this header file; the command guile-config compile should tell you where to find it.

Next, we need to kick off the Guile interpreter. This is done in two steps; firstly, for arcane reasons we need to call gh_enter at the end of main().

    gh_enter(argc, argv, inner_main);
    return(0); /* never reached */

Secondly, gh_enter will call back into the inner_main function passed to it, and it is this inner_main function which actually calls gh_repl to start the Scheme interpreter (the repl stands for Read-Evaluate-Print Loop, in case you were interested).

void inner_main(int argc, char **argv)
{
    register_procs();
    gh_repl(argc, argv);
}

This code refers to the function register_procs. We'll come to this below

Finally, we need to link with the libguile.so library. On my system, this simply involved adding -lguile to the link line. Running guile-config link is a good way of finding out what you might need to do on your system.

Making the Primitives available from Guile

At this point, we now have our tortoise program invoking the Guile interpreter, but the user has no way of accessing all of our tortoise functionality. When the program starts, the user can interactively write programs in Scheme, but can't get the tortoise to wake up and do anything.

What we need to do is to make all of our tortoise primitives available from the Scheme interpreter.

To recap, these were:

We make Scheme accessible versions of all of these.

#include <math.h>
#define DEGREES_TO_RADIANS  (3.1415926535897932384626433832795029L/180.0)

SCM tortoise_reset()
{
    currentX = currentY = WINDOW_SIZE/2;
    currentDirection = 0;
    penDown = 1;
    return SCM_EOL;
}
SCM tortoise_pendown()
{
    penDown = 1;
    return SCM_EOL;
}
SCM tortoise_penup()
{
    penDown = 0;
    return SCM_EOL;
}
SCM tortoise_turn(SCM s_degrees)
{
    int degrees = SCM_INUM(s_degrees);
    currentDirection += (double)degrees;
    return SCM_EOL;
}
SCM tortoise_move(SCM s_steps)
{
    double newX, newY;
    int steps = SCM_INUM(s_steps);
    /* first work out the new endpoint */
    newX = currentX + sin(currentDirection*DEGREES_TO_RADIANS)*(double)steps;
    newY = currentY - cos(currentDirection*DEGREES_TO_RADIANS)*(double)steps;
    /* if the pen is down, draw a line */
    if (penDown) XDrawLine(theDisplay, theWindow, theGC,
                           (int)currentX, (int)currentY, (int)newX, (int)newY);
    /* in either case, move the tortoise */
    currentX = newX;
    currentY = newY;
    return SCM_EOL;
}

In this code, there are a few key points

Having set up all of these C functions in a way which is accessible from Guile, we now need to tell Guile that they are there. To do this, we define the function register_procs which was mentioned in the previous section.

void register_procs(void)
{
    gh_new_procedure("tortoise-reset",   tortoise_reset,   0, 0, 0);
    gh_new_procedure("tortoise-pendown", tortoise_pendown, 0, 0, 0);
    gh_new_procedure("tortoise-penup",   tortoise_penup,   0, 0, 0);
    gh_new_procedure("tortoise-turn",    tortoise_turn,    1, 0, 0);
    gh_new_procedure("tortoise-move",    tortoise_move,    1, 0, 0);
}

The registration of the new Guile-accessible primitives is done by the calls to gh_new_procedure. This function takes the following parameters

  1. A string giving the name of the function when seen from Guile.
  2. The C function providing the primitive
  3. The number of required arguments to the function (0 or 1 in all our examples).
  4. The number of optional arguments to the function (0 in all our examples).
  5. A boolean expression indicating whether the function takes a 'rest' list (false in all our examples)

Running It

Amazingly, that's it - we have the entire program. If we now compile and run the program, we get a Guile interpreter prompt:

guile>

We can now try a few commands:

guile> (tortoise-move 100)
()
guile> (tortoise-turn 90)
()
guile> (tortoise-move 100)
()

At this point, a couple of lines should have appeared in the program's window.

First lines drawn by the user

Given that this is Scheme, we can define a new top-level procedure.

guile> (define (move-n-turn angle) 
...            (tortoise-move 100) (tortoise-turn angle))
guile> (for-each move-n-turn '(80 80 80 80 80 80 80 80 80))
Nine pointed star

Next Steps

More Communication Between Scheme and C

All of the primitives so far have returned the empty list, because this was easy to do. However, we'd now like to pass some information back from C to Scheme - in particular:

Also, we've noticed that tortoise-move and tortoise-turn only allow an integer argument. It's time to change this so that we can specify floating point values for these arguments.

#include <math.h>
#define DEGREES_TO_RADIANS  (3.1415926535897932384626433832795029L/180.0)

SCM tortoise_reset()
{
    currentX = currentY = WINDOW_SIZE/2;
    currentDirection = 0;
    penDown = 1;
    return SCM_UNSPECIFIED;
}
SCM tortoise_pendown()
{
    int prevValue = penDown;
    penDown = 1;
    return (prevValue ? SCM_BOOL_T: SCM_BOOL_F);
}
SCM tortoise_penup()
{
    int prevValue = penDown;
    penDown = 0;
    return (prevValue ? SCM_BOOL_T: SCM_BOOL_F);
}
SCM tortoise_turn(SCM s_degrees)
{
    int prevValue = (int)currentDirection;
    double degrees;
    SCM_ASSERT(SCM_NUMBERP(s_degrees), s_degrees, SCM_ARG1, "tortoise-turn");
    degrees = SCM_NUM2DBL(s_degrees);
    currentDirection += degrees;
    return SCM_MAKINUM(prevValue);
}
SCM tortoise_move(SCM s_steps)
{
    double oldX = currentX;
    double oldY = currentY;
    double newX, newY;
    double steps;
    SCM_ASSERT(SCM_NUMBERP(s_steps), s_steps, SCM_ARG1, "tortoise-move");
    steps = SCM_NUM2DBL(s_steps);
    /* first work out the new endpoint */
    newX = currentX + sin(currentDirection*DEGREES_TO_RADIANS)*steps;
    newY = currentY - cos(currentDirection*DEGREES_TO_RADIANS)*steps;
    /* if the pen is down, draw a line */
    if (penDown) XDrawLine(theDisplay, theWindow, theGC,
                           (int)currentX, (int)currentY, (int)newX, (int)newY);
    /* in either case, move the tortoise */
    currentX = newX;
    currentY = newY;
    return gh_cons(gh_double2scm(oldX) , gh_cons(gh_double2scm(oldY), SCM_EOL));
}

So what's different about this version of the code? Well, firstly the pen up/down functions use SCM_BOOL_F and SCM_BOOL_T to return the Scheme values #f and #t, as appropriate. Secondly, tortoise_turn uses SCM_MAKINUM to build a Scheme integer from a C integer.

Thirdly, the function tortoise-move builds a list containing the previous coordinates using

Fourthly, tortoise_move and tortoise_turn use SCM_NUM2DBL instead of SCM_INUM to get a floating point value from their single arguments.

Finally, I mentioned before that the results of SCM_INUM are undefined if the Scheme value passed in is not an integer. The same is true for SCM_NUM2DBL if the Scheme value passed in is not some sort of number. However, in this version of the code I protect against this possibility.

The way to ensure that an argument passed from Scheme to C is of a particular type is to use SCM_ASSERT, combined with a test for the required type such as SCM_NUMBERP or SCM_INUMP. The arguments to SCM_ASSERT are:

  1. The test to perform. If this returns 0, then an error is signalled to the user.
  2. The SCM object which caused the error, in this case the argument to the C function.
  3. A number indicating which position the erroneous argument was in. In this case, the functions only have one argument, so we use the macro SCM_ARG1.
  4. The name of the function (as seen from Scheme) which raised the error.

We can take advantage of these changes (this version of the complete program):

guile> (tortoise-move 100)
(250.0 250.0)
guile> (tortoise-turn 90)
0
guile> (tortoise-move 100)
(250.0 150.0)
guile> (tortoise-turn (+ 90 45))
90
guile> (tortoise-move (sqrt (* 2 (* 100 100))))
(350.0 150.0)
guile> (tortoise-penup)
#t
guile> (tortoise-move "abc")
ERROR: In procedure tortoise-move in expression (tortoise-move "abc"):
ERROR: Wrong type argument in position 1: "abc"
ABORT: (wrong-type-arg)
Right-angled triangle, drawn using an irrational number of steps

Scripting

In the examples so far, the program has kicked off the Guile Scheme interpreter, allowing the user to interact with the program dynamically. For many of the potential uses of Guile, this is not what is needed; the program will only need to read in a configuration script at start-of-day, and then proceed with its normal processing

So we're now going to change the program so that it reads in and runs a .tortoise file at start-of-day. To do this, we still need to use gh_enter to get to our inner_main function. However, when we get there, we don't want to hand over control of the program to Guile with gh_repl.

Instead, we want to open the .tortoise file and explicitly pass the lines from it to the Scheme interpreter, one by one. When we're done with the file, we're done with the interpreter and we can get on with the main business of the program.

#define CONFIGFILENAME          ".tortoise"
#define DIRECTORYSEPARATOR      "/"
void read_config_file(void)
{
    char *homedir;
    char *filename;
    FILE *file;
    char inputLine[1024];  /* hack: assume each line less than 1024 chars */
    char *p;
    /* open $HOME/.tortoise */
    homedir = getenv("HOME");
    if (homedir == NULL) return;
    filename = (char *)malloc(strlen(homedir) + strlen(CONFIGFILENAME) +
                              strlen(DIRECTORYSEPARATOR) + 1);
    if (filename == NULL) return;
    sprintf(filename, "%s%s%s", homedir, DIRECTORYSEPARATOR, CONFIGFILENAME);
    file = fopen(filename, "r");
    free(filename);
    if (file == NULL) return;
    /* spin through the file */
    while (1) {
        p = fgets(inputLine, sizeof(inputLine), file);
        if (p == NULL) return;
        gh_eval_str(p);
    }
    fclose(file);
}
void inner_main(int argc, char **argv)
{
    register_procs();
    read_config_file();
    /* now get on with the main business of the program . . . */
    sleep(20);
}

Here, the key function is gh_eval_str, which gets the Scheme interpreter to evaluate one particular string and then return. (The complete program is here)

We can test this out with a sample .tortoise file, which defines a Scheme procedure for drawing a polygon with a given number of sides:

(define (move-n-turn angle) (tortoise-move 100) (tortoise-turn angle))
(define (polygon n) (do ((i n (- i 1))) ((zero? i)) (move-n-turn (/ 360 n))))
(polygon 7)
Heptagon drawn by the .tortoise file

Unfortunately, there is a downside to this approach. Each line of our .tortoise file has to be a complete Scheme expression.

It's very easy to fix this - we just let Guile do more of the work for us. There is a function gh_eval_file which will load an entire file and evaluate it:

#define CONFIGFILENAME          ".tortoise"
#define DIRECTORYSEPARATOR      "/"
void read_config_file(void)
{
    char *homedir;
    char *filename;
    /* build the $HOME/.tortoise filename */
    homedir = getenv("HOME");
    if (homedir == NULL) return;
    filename = (char *)malloc(strlen(homedir) + strlen(CONFIGFILENAME) +
                              strlen(DIRECTORYSEPARATOR) + 1);
    if (filename == NULL) return;
    sprintf(filename, "%s%s%s", homedir, DIRECTORYSEPARATOR, CONFIGFILENAME);
    /* get Guile to do all of the work */
    gh_eval_file(filename);
    free(filename);
}
void inner_main(int argc, char **argv)
{
    register_procs();
    read_config_file();
    /* now get on with the main business of the program . . . */
    gh_eval_str("(tortoise-turn 30)"); /* example: call C-defined procedure */
    gh_eval_str("(polygon 3)");   /* example: call Scheme-defined procedure */
    sleep(20);
}

With this version of the code, we can re-write our .tortoise file in a more easily readable version:

(define (move-n-turn angle)
  (tortoise-move 100)
  (tortoise-turn angle))
(define (polygon n)
  (do ((i n (- i 1)))
      ((zero? i))
    (move-n-turn (/ 360 n))))
(polygon 7)
Heptangle drawn by the .tortoise file, with triangle drawn by the main program

The Story So Far

Let's recap on what we've seen so far. We can now:

Further Reading

At this point, you probably need to find some more detailed information on Guile. Bizarrely, the official Guile documentation is not currently available on the Guile homepage, so you'll either have to scan the internet for guile-doc, or you'll have to get the current guile-doc images via anonymous CVS (I couldn't do this because my internet proxy won't allow it).

The Guile documentation includes a tutorial (guile-tut.info), a reference manual (guile-ref.info*) and a copy of the Revised^4 Report on the Algorithmic Language Scheme (r4rs.info*).

Some things you might need to find out next:

Have fun . .


Colophon

I wrote this page because I really needed something like it when I started playing with Guile - I actually wrote the page as I figured out how to do things.

Hopefully, this page will be useful to other people who are in the same position that I was. If you're one of those people, please let me know if it has been useful, and if not, how it could be improved so that it is. (Thanks to Neil Jerram and others who have already provided some useful feedback).

If you want to know the answers to deep tricky Guile questions, I'm probably not the best person to ask. Try joining one of the Guile mailing lists (see info here).

Copyright (c) 2000 David Drysdale

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation; with no Invariant Sections, with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the license is available here.