Go to the previous, next section.

More interesting programming with libguile

The learn0 program shows how you can invoke scheme commands from a C program. That is not such a great achievement: the same could have been done by opening a pipe to SCM or any other scheme interpreter.

A true extension language must allow callbacks. Callbacks allow you to write C routines that can be invoked as scheme procedures, thus adding new builting procedures to scheme. This also means that a scheme procedure can modify a C data structure.

Guile allows you to define new scheme procedures from C, and provides a mechanism to go back and forth between C and scheme data types.

Here is a second program, learn1, which demonstrates these features. It is split into three source files: learn1.c, c_builtins.h and c_builtins.c. I am including the code here, but you might just want to look at the source code and the Makefile that come in the learn_libguile distribution.

learn1.c

Here is `learn1.c':

#include <stdio.h>

#include <gscm.h>
/*
#include <tcl.h>
#include <tk.h>
*/

#include "c_builtins.h"


static GSCM_status guile_init()
{
#if 0				/* don't need fancy packages */
  scm_init_ctax();
  scm_init_unix();
  scm_init_posix();
  scm_init_ioext();
  scm_init_gtcl();
  scm_init_gtk();
#endif /* 0 */
}

main(int argc, char *argv[])
{
  GSCM_status status;
  GSCM_top_level toplev;
  char *eval_answer;
  char input_str[200];		/* ugly hack: assume strlen(line) < 200 */
  int done;

  printf("hello guile\n");

  /* start an interpreter */
#if 0				/* this is the old way */
  status = initialize_gscm(0, argc, argv);
#endif /* 0 */
  /* start a scheme interpreter */
  status = gscm_run_scm(argc, argv, 0, stdout, stderr, guile_init, 0, "#t");
  if (status != GSCM_OK) {
    fputs(gscm_error_msg(status), stderr);
    fputc('\n', stderr);
    exit(1);
  }

  /* create the top level environment */
  status = gscm_create_top_level(&toplev);
  if (status != GSCM_OK) {
    fputs(gscm_error_msg(status), stderr);
    fputc('\n', stderr);
    exit(1);
  }

  /* now some code to prepare a Tk main window; the scheme code would be
   * something like
   * (define tk-main (tk-init-main-window (tcl-create-interp) "papageno:0"
   *                   "john" "john2")`w)
   * where I am still confused about the "name" and "class" parameters
   * involved.
   */
  /* ...abandoned for a while; can't figure it out */
  

  /* for fun, evaluate some simple scheme expressions here */
  status = gscm_eval_str(NULL, toplev, "(define (square x) (* x x))");
  status = gscm_eval_str(NULL, toplev, "(define (factorial n) (if (= n 1) 1 (* n (factorial (- n 1)))))");
  status = gscm_eval_str(NULL, toplev, "(square 9)");
  status = gscm_eval_str(NULL, toplev, "(factorial 100)");

  /* now try to define some new builtins, coded in C, so that they
   * are available in scheme.  note that I have put junk in the documentation
   * strings because I have not yet read the manual on how doc strings
   * should be written :-|
   */
  gscm_define_procedure("c-factorial", c_factorial, 1, 1, 0, "hi there");
  gscm_define_procedure("c-sin", c_sin, 1, 1, 0, "hi there");
  gscm_define_procedure("v-t", vector_test, 1, 1, 0, "hi there");

  /* now sit in a scheme eval loop: I input the expressions, have guile
   * evaluate them, and then get another expression.
   */
  done = 0;
  fputs("learn1> ", stdout);
  while (!done) {
    if (gets(input_str) == NULL || strcmp(input_str, "(quit)") == 0) {
      done = 1;
    } else {
      status = gscm_eval_str(NULL, toplev, input_str);
      fputs("learn1> ", stdout);
    }
  }

  /* now clean up and quit */
  gscm_destroy_top_level(toplev);
  exit(0);
}

c_builtins.h

Here is `c_builtins.h':

/* builtin function prototypes */

SCM c_factorial(SCM n);
SCM c_sin(SCM n);
SCM vector_test(SCM s_length);

c_builtins.c

Here is `c_builtins.c':

#include <stdio.h>
#include <math.h>

#include <gscm.h>

#include "c_builtins.h"

/* this is a factorial routine in C, made to be callable by scheme */
SCM c_factorial(SCM s_n)
{
  int i;
  unsigned long result = 1, n;

  n = gscm_2_ulong(s_n);

  GSCM_DEFER_INTS;
  for (i = 1; i <= n; ++i) {
    result = result*i;
  }
  GSCM_ALLOW_INTS;
  return gscm_ulong(result);
}

/* a sin routine in C, callable from scheme.  it is named c_sin()
 * to distinguish it from the default scheme sin function
 */
SCM c_sin(SCM s_x)
{
  double x = gscm_2_double(s_x);

  return gscm_double(sin(x));
}

/* play around with vectors in guile: this routine creates a
 * vector of the given length, initializes it all to zero except
 * element 2 which is set to 1.9.
 */
SCM vector_test(SCM s_length)
{
  SCM xvec;
  unsigned long c_length;

  c_length = gscm_2_ulong(s_length);
  printf("requested length for vector: %ld\n", c_length);

  /* create a vector */
  xvec = gscm_vector(c_length, gscm_double(0.0));
  /* set the second element in it */
  gscm_vset(xvec, 2, gscm_double(1.9));

  return xvec;
}

What learn1 is doing

If you compare learn1 to learn0, you will find that learn1 uses a new guile construct: the function gscm_define_procedure():

  /* now try to define some new builtins, coded in C, so that they
   * are available in scheme.  note that I have put junk in the documentation
   * strings because I have not yet read the manual on how doc strings
   * should be written :-|
   */
  gscm_define_procedure("c-factorial", c_factorial, 1, 1, 0, "hi there");
  gscm_define_procedure("c-sin", c_sin, 1, 1, 0, "hi there");
  gscm_define_procedure("v-t", vector_test, 1, 1, 0, "hi there");

It is clear that gscm_define_procedure() adds a new builtin routine written in C which can be invoked from scheme. We can now revise our checklist for programming with libguile, so it includes adding callbacks.

  1. #include <gscm.h>
  2. You need a guile_init()-like routine, which could even be trivial like the one in this example, to pass to gscm_run_scm(). This allows you to tell rscm_run_scm() which optional guile packages to use. Note that in this example I am only interested in the scheme interpreter, and not in the extra packages, so all the extra package initialization has been #if-ed out.
  3. You must invoke gscm_run_scm() and gscm_create_top_level() at the beginning of your program. This sets up the scheme interpreter to which you can then pass strings for evaluation. I believe that this routine will soon be renamed to start with the prefix gscm_: the prefix of all the routines in the guile programmer's interface.
  4. You can now register callbacks; i.e. define new builtin scheme functions, with the gscm_define_procedure() routine.
  5. You pass strings to the scheme interpreter with the gscm_eval_str() routine.
  6. You quit with gscm_destroy_top_level() (which currently gives some silly error message).
  7. You link your program with -lguile.

Compiling and running learn1

gcc -g -I/packages/include/guile   -c learn1.c -o learn1.o
gcc -g -I/packages/include/guile   -c c_builtins.c -o c_builtins.o
gcc -o learn1 learn1.o c_builtins.o -L/packages/lib/scm -L/packages/lib -lguile -lm

If you run learn1, it will prompt you for a one-line scheme expression, just as learn0 did. The difference is that you can use the new C builtin procedures (c-factorial, c-sin, v-t).


<shell-prompt> ./learn1
hello guile

SLIB "2a2" on GUILE iii on UNIX 
edit GUILE ".init" to set (scheme-implementation-version) 
(IMPLEMENTATION-VICINITY) is "/packages/lib/gls/" 
type (slib:report) for configuration 

learn1> (print (c-factorial 6))
720 
learn1> (print (c-factorial 20))
2192834560 
learn1> (print (c-factorial 100))
0 
learn1> (print (c-sin 1.5))
997.49498660405e-3 
learn1> (print (sin 1.5))
997.49498660405e-3 
learn1> (print (v-t 10))
requested length for vector: 10
#(0.0 0.0 1.9 0.0 0.0 0.0 0.0 0.0 0.0 0.0) 
learn1> (print (v-t 15))
requested length for vector: 15
#(0.0 0.0 1.9 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0) 
learn1> (quit)

ERROR: unbound variable:   %gscm-indirect
<153 sst10b->proto1> 

Go to the previous, next section.