Exploring the embedding and interop features of ECL. Continued.

[ Check out all posts in “lisp” series here. ]

In my previous post, I started exploring Embeddable Common-Lisp. I was looking into this example from the ECL repo as a starting point. I will share some of my learning so far.

The initial integration into a C/C++ library is pretty straightforward:1

  • There are various ways to build the lisp code, but I focused on the asdf based method. You just add a build rule that’s a few lines long. It triggers ecl to build a (static or shared) library from an ASDF based system you point at.
  • As part of the build rule, you also declare an “entry point” to generate. This entry point will be used to initialize the library later.
  • On the C/C++ side, you boot ecl, and call the library init function you just created.
  • You are now ready to interact with the system.
  • While building your C/C++ code, you link against the library you created earlier.

The API that ecl exports is pretty rich. It has:

  • C versions of many CL functions, which are cl_ prefixed.
  • Functions for interop-related functionality, convenience etc. These are ecl_ prefixed.

The naming conventions page in docs have the details.

CL Object

One interesting concept is the cl_object, which is the box for CL values. The definition:

typedef union cl_lispunion *cl_object;

Internals are detailed here. The type cl_lispunion is essentially a tagged union. But I am planning to start a series on methods used for boxing values in dynamic languages, anyway. So I will move on for now.

The cl_object is the input and the return type of most C side APIs. Obviously, the requirement to explicitly generate cl_objects for values makes it a slightly verbose to use the APIs from C side, but that is expected.

I am currently trying to have a better grasp of the memory management aspect.

Defun Preprocessor

While navigating the ECL codebase, I noticed that the src/c directory is full of files with .d extension. It took me a while to figure out that these source files are intended to be processed by the tool called “defun preprocessor” (dpp), which generates the corresponding C sources.

So those files are mostly normal C, with some parts that use special syntax to be expanded by this preprocessor, to C. Here is an example line from “src/c/file.d”:

  if (b == @':eof') b = ECL_NIL;

Above expands to a C form:

  if (b == ECL_SYM(":EOF",1256)) b = ECL_NIL;

And ECL_SYM macro just indexes an array called cl_symbols using the index (second argument) arg.

Another example: this defun declaration expands to a function with the prototype below:

cl_object cl_read(cl_narg narg, ...)

Cool!

Thanks for reading! If you find technical errors, please report in the blog’s Issues page.

  1. At least for the method I focused on.