Wednesday, June 15, 2011

gEDA and Guile — reducing boilerplate with "snarfing macros"

This is the seventh in a series of blog posts on extensibility in gEDA using Guile Scheme.

  1. Finding Scheme API code in gEDA
  2. Compiling against multiple Guile versions
  3. Safe handling of non-local exits
  4. Dealing with deprecated libguile functions
  5. Checking arguments to Scheme functions in C
  6. How and when to use Scheme errors
  7. Reducing boilerplate with "snarfing macros"
  8. Opportunities to get involved

In this post, I will explain how you can use the guile-snarf tool to reduce the boilerplate needed to define Scheme functions and variables from C.

At the time of writing, this post isn't really relevant to the main gEDA unstable development branch, since it doesn't currently use "snarfing". It might be an interesting read if you're interested in hacking on my guile-scheme-api branch.

Why is "snarfing" needed?

One of the problems with using libguile to define Scheme functions from C is there's a certain amount of boilerplate setup code. For example, if I wish to define a function called "myfunc" in C to be usable from Scheme, I need to do something like:

static SCM
my_func (SCM arg)
{
  SCM_ASSERT (scm_is_string (arg), arg, SCM_ARG1, "my-func");

  /* ... do something useful ... */
}

/* Called during application initialisation */
void
my_init ()
{
  scm_c_define_gsubr ("my-func", 1, 0, 0, (SCM (*)()) my_func);
}

There are a few problems with this.

  • If the number of arguments to the function changes, an update needs to be made in two different places.
  • If the string name of the function changes ("myfunc" in this case), an update needs to be made in two places, and also to every other place the name is used (e.g. SCM_ASSERT() for arguments and any other place a Scheme error is raised).
  • If an extra function is added...

You get the idea.

Similarly, if there is a permanent Scheme value that's needed in multiple functions (such as a symbol) it's necessary to separately define the static variable and add a line to the initialisation function.

Finally, if the libguile API for defining a function or variable changes, it's necessary to individually update the initialisation code for every single one.

The guile-snarf tool

Guile comes with a tool called guile-snarf, which is run against a C source file to generate an additional C source file for inclusion, usually given a .x extension.

The tool recognises some special macros in the C source, which expand normally when compiled, but which are used by the tool to generate Scheme initialisation boilerplate. The example above would become:

SCM_DEFINE (my_func,   /* Function name in C */
            "my-func", /* Function name in Scheme */
            1, 0,      /* No. of required/optional args */
            0,         /* Whether accepts "rest" arg */
            (SCM arg), /* C argument list */
            "Do something exciting.") /* Docstring */
{
  SCM_ASSERT (scm_is_string (arg), arg, SCM_ARG1, s_my_func);

  /* ... do something useful ... */
}

/* Called during application initialisation */
void
my_init ()
{
  #include "mycfile.x";
}

This may seem like a small gain for this trivial example, but for files which define a large number of Scheme procedures there's a definite benefit! There are a few things to note:

  • Functions defined using SCM_DEFINE() are always static. This shouldn't normally be a problem, since they will usually be intended to be called from Scheme, not from other C functions.
  • For each function foo, SCM_DEFINE() also defines a static string called s_foo. This can be pretty handy, especially when needing to pass the Scheme name of the function to SCM_ASSERT() macros. (Its use is discouraged by the official documentation, but libguile itself makes use of it, so hey — it can't be that much of a problem!)

Although SCM_DEFINE is the main reason to use guile-snarf, there are several other really useful macros, such as SCM_SYMBOL("foo"), which creates & defines a variable containing the symbol "foo". In my guile-scheme-api branch, you will fairly commonly see a block at the top of a C source file looking like:

SCM_SYMBOL (lower_left_sym , "lower-left");
SCM_SYMBOL (middle_left_sym , "middle-left");
SCM_SYMBOL (upper_left_sym , "upper-left");
SCM_SYMBOL (lower_center_sym , "lower-center");
SCM_SYMBOL (middle_center_sym , "middle-center");
SCM_SYMBOL (upper_center_sym , "upper-center");
SCM_SYMBOL (lower_right_sym , "lower-right");
SCM_SYMBOL (middle_right_sym , "middle-right");
SCM_SYMBOL (upper_right_sym , "upper-right");

This then allows functions in the file to use the symbols to construct Scheme expressions to be evaluated without needing to recreate the symbols (e.g. using scm_from_utf8_symbol()) every time the function is run.

Conclusion

Currently, when wishing to alter the prototype a Scheme function provided by a gEDA program, you need to make changes in at least three places:

  1. The function definition itself.
  2. The function prototype in a header file.
  3. The function metadata exported to Scheme, usually in a file called g_register.c.

Using the SCM_DEFINE() "snarfing" macro and the guile-snarf tool would allow two of those places to be eliminated, and that's why my guile-scheme-api branch uses them.

The next and final post in this series will summarise the things I've written about, and give a list of easy introductory gEDA development tasks based on some of the issues I've raised.

No comments: