Adding your own functions

How functions are implemented

As an example of how a normal function is implemented in C, let's have a look at a typical function, fn_add, which handles the Lisp "+" operator:

object *fn_add (object *args, object *env) {
  (void) env;
  int result = 0;
  while (args != NULL) {
    result = result + integer(car(args));
    args = cdr(args);
  return number(result);

This will get called when you evaluate a form such as:

(+ (* 2 3) (* 4 5) (* 6 7))

First, each of the arguments is evaluated, giving:

(+ 6 20 42)

The first item in this list is looked up to give the function entry point, fn_add, and this is then called with args pointing to the cdr of this list:

(6 20 42)

If the function took a fixed list of arguments you could access each argument with statements such as:

arg1 = first(args);
arg2 = second(args);

However, in this case the function will take a variable number of arguments, so we need to iterate along the list of arguments with a loop until the args list is empty (NULL):

while (args != NULL) {
  // do something
  args = cdr(args);

In the case of fn_add we start with the integer result set to 0, then repeatedly get the first item in the args list, call integer to convert it to an integer, and add it to result.

The second argument to each function, env, contains the current environment: an association list of local variables and their values. It is used by functions that call eval, but is not required by fn_add.

Adding your own function to uLisp

It's quite easy to add your own functions to uLisp, to extend the language or provide access to specific external hardware. Proceed as follows:

  • After the comment
// Insert your own function definitions here

write the C definition of your function, which should have the declaration:

object *fn_myfun (object *args, object *env) {
  • Add a string defining the name of the function in the list below the comment:
// Built-in functions - stored in PROGMEM

Number the string variable with the next sequential number; for example:

const char string66[] PROGMEM = "myfun";

The function name doesn't have to match the C function name, and provided it's distinct from any existing function name it can contain arbitrary characters, apart from "(", ")", "'", or ".", and it must not start with a digit or "-".

  • Add an entry at the end of lookup_table[] specifying the name of the string you defined, the C function name, and the minimum and maximum permitted number of arguments; for example:
{ string66, fn_myfun, 1, 1 },
  • Add an uppercase symbol representing your function in the enum function definition near the beginning of the uLisp source file. Your new symbol should go just before ENDFUNCTIONS in the list; for example:
enum function { BRA, KET, QUO, ... MYFUN, ENDFUNCTIONS };
  • Now recompile uLisp and your function should be available in the listener.

Providing error handling

You can provide error handling by calling error. For example:

if (arg1->type != NUMBER) error(F("'myfun' needs an integer argument"));