Modules are collections of function definitions. They are loaded using the load function. They can be implemented in in any language that compiles into shared libraries with a C interface (binary modules).

Binary interface

A module is a shared library that must export the following interface:

const char * lisp_module_name();
atom_t lisp_module_setup(const lisp_t lisp);

The lisp_module_setup function returns an atom_t value that points to a list definition as follows:


The integer part represents a pointer to a function with the following signature:

lisp_function_NAME(const lisp_t lisp, const atom_t closure, const atom_t args);


Let's take the following code written for an add function and save it in a file called add.c:

#include <mnml/lisp.h>
#include <mnml/module.h>
#include <mnml/slab.h>

static atom_t
lisp_function_add(const lisp_t lisp, const atom_t closure)
  LISP_ARGS(closure, C, X, Y);
   * Make sure the arguments are numbers.
  if (!(IS_NUMB(x) && IS_NUMB(y))) {
    X(x, y);
    return UP(NIL);
   * Return the new value.
  atom_t result = lisp_make_number(x->number + y->number);
  X(x, y);
  return result;


Then, let's compile it:

$ cc -I${PREFIX}/include -L${PREFIX}/lib -lminimal -o libminimal_function_add.${LD_EXT} add.c

Where ${PREFIX} is Minima.l's installation prefix and ${LD_EXT} the linker extension for your platform (.so on most, .dylib on Mac).

Finally, place this shared object in your Minima.l cache ($HOME/.mnml) and fire the interpreter:

: (load 'add)
> add
: (add 1 2)
> 3

Programming interface

Atom type

The basic type in Minima.l is atom_t. This type represents numbers, characters, symbols, and pairs. Some symbols are defined as singletons, like NIL, TRUE, QUOTE, and WILDCARD.


Atom values are allocated using functions in the maker interface:

atom_t lisp_make_char(const char c);
atom_t lisp_make_number(const int64_t num);
atom_t lisp_make_string(const char * const s, const size_t len);
atom_t lisp_make_symbol(const symbol_t sym);

Pairs are created by using the cons and conc standard functions in the lisp interface:

atom_t lisp_cons(const atom_t a, const atom_t b);
atom_t lisp_conc(const atom_t a, const atom_t b);

Both functions consume their arguments as those are often created for the sole purpose of being cons'd or conc'd.


Atom values must be manually deallocated using the X(...) macro defined in the slab interface. Atom values are reference counted, so great care must be taken when handling them.


The LISP_MODULE_SETUP macro is used to register a new module:

#define LISP_MODULE_SETUP(__s, __n, ...)

The first argument __s is the suffix of the function. The second argument __n is the name of the symbol. The variadic arguments represent the function argument symbols. For instance, the function add is defined as follow:

static atom_t
lisp_function_add(const lisp_t lisp, const atom_t closure)
  /* ... */


Argument processing

The argument symbols are defined as follows:

C version Minima.l equivalent
LISP_MODULE_SETUP(fun, fun) (def fun () ...)
LISP_MODULE_SETUP(fun, fun, A) (def fun A ...)
LISP_MODULE_SETUP(fun, fun, A, NIL) (def fun (A) ...)
LISP_MODULE_SETUP(fun, fun, A, REM) (def fun (A . REM) ... )
LISP_MODULE_SETUP(fun, fun, A, B, NIL) (def fun (A B) ...)
LISP_MODULE_SETUP(fun, fun, A, B, REM) (def fun (A B . REM) ...)

Values for the declared symbols are passed to the module by the interpreter through the args parameter. They can be retrieved using the LISP_ARGS macro:

#define LISP_ARGS(_c, _t, ...)

The first argument is the closure passed to the function, which includes the arguments. The second argument is a name to use for the closure without the arguments. The remaining arguments are names of the arguments to pop.