Converting values between C and uLisp


#1

Here’s a draft article aimed at anyone who wants to extend uLisp, summarising how to convert between C variables of different types and the corresponding uLisp objects.

I’d welcome suggestions for improving it, or requests for other information it would be useful to add.

Integers

C to uLisp

The built-in function number() takes an int argument and returns a uLisp integer object.

For example, here’s the definition of the uLisp function (millis) which returns the value of the Arduino function millis() :

object *fn_millis (object *args, object *env) {
  (void) args, (void) env;
  return number(millis());
}

uLisp to C

The built-in function integer() takes a uLisp object and returns a C int . It causes an error if the parameter isn’t a uLisp integer.

For example, here’s the definition of the uLisp function (delay n) which takes one integer parameter and calls the Arduino function delay(n) . It returns n:

object *fn_delay (object *args, object *env) {
  (void) env;
  object *arg1 = first(args);
  delay(integer(arg1));
  return arg1;
}

Floats

C to uLisp

On 32-bit platforms the built-in function makefloat() takes a float argument and returns a uLisp floating-point object.

uLisp to C

On 32-bit platforms the built-in function fromfloat() takes a uLisp object and returns a C float . It causes an error if the parameter isn’t a uLisp floating-point number.

For example, you could define a uLisp function radians that converted a floating-point object from degrees to radians as follows:

object *fn_radians (object *args, object *env) {
  (void) env;
  object* arg = first(args);
  return makefloat(fromfloat(arg) * 3.14159 / 180.0);
}

Characters

C to uLisp

The built-in function character() takes a char argument and returns a uLisp character object.

For example, here’s the definition of the Lisp function code-char which takes a uLisp integer object and converts it to a uLisp character object:

object *fn_codechar (object *args, object *env) {
  (void) env;
  return character(integer(first(args)));
}

uLisp to C

The built-in function fromchar() takes a uLisp object and returns a C integer. It causes an error if the parameter isn’t a uLisp character object.

For example, here’s the definition of the Lisp function char-code which takes a uLisp character object and converts it to a uLisp integer object:

object *fn_charcode (object *args, object *env) {
  (void) env;
  return number(fromchar(first(args)));
}

Strings

C to uLisp

The function lispstring() will take a C char* string and return a uLisp string object. This function is currently only provided in the ESP version of uLisp, as it is needed for the Wi-Fi extensions, but you can add it from the following definition:

object *lispstring (char *s) {
  object *obj = myalloc();
  obj->type = STRING;
  char ch = *s++;
  object *head = NULL;
  int chars = 0;
  while (ch) {
    if (ch == '\\') ch = *s++;
    buildstring(ch, &chars, &head);
    ch = *s++;
  }
  obj->cdr = head;
  return obj;
}

For example, here’s the definition of the uLisp Wi-Fi extension function (wifi-localip) which returns the local ip address as a uLisp string:

object *fn_wifilocalip (object *args, object *env) {
  (void) args, (void) env;
  return lispstring((char*)WiFi.localIP().toString().c_str());
}

uLisp to C

The function cstring() takes a uLisp string, a buffer, and the buffer length, and returns the buffer containing the uLisp string converted to a C string. This function is already provided in the ESP version of uLisp:

char *cstring (object *form, char *buffer, int buflen) {
  int index = 0;
  form = cdr(form);
  while (form != NULL) {
    int chars = form->integer;
    for (int i=(sizeof(int)-1)*8; i>=0; i=i-8) {
      char ch = chars>>i & 0xFF;
      if (ch) {
        if (index >= buflen-1) error(PSTR("no room for string"));
        buffer[index++] = ch;
      }
    }
    form = car(form);
  }
  buffer[index] = '\0';
  return buffer;
}

This function assumes that you’ve already checked that the uLisp object is a string. If necessary do this first using the stringp() macro.

A second function cstringbuf() is provided in the ESP version of uLisp. It’s simpler to use when you only need to buffer one string at a time because it uses uLisp’s built-in buffer for the string:

char *cstringbuf (object *arg) {
  cstring(arg, SymbolTop, SYMBOLTABLESIZE-(SymbolTop-SymbolTable));
  return SymbolTop;
}

The built-in buffer lives at the top of the symbol table and is guaranteed to have a minimum size of BUFFERSIZE , by default 34 characters.


#2

Here is a bit more information, explaining how to convert between a uLisp list and multiple C values such as in variables, a struct, or an array.

Lists

C to uLisp

As an example of converting multiple values between C and Lisp, here’s an example of a C routine to read the x and y values from an analogue joystick connected to the analogue inputs A0 and A1. It uses pointers to pass out the values x and y from the joystick:

void Joystick (int *x, int *y) {
  *x = analogRead(A0);
  *y = analogRead(A1);
}

We will convert this to a uLisp function (joystick) which returns the x and y values as a list of two integers:

> (joystick)
(512 250)

I usually find it useful to draw a diagram of the Lisp structure I want to construct:

Result

The ‘4’ label identifies an integer object. Here’s the definition of the joystick function:

object *fn_joystick (object *args, object *env) {
  (void) args, (void) env;
  int x, y;
  Joystick(&x, &y);
  object *result = cons(number(x), cons(number(y), NULL));
  return result;
}

uLisp to C

To transfer multiple values from uLisp to C one way is to write a function that takes a list argument. Here’s a function servo that takes a list of two values and writes them to analogue outputs:

object *fn_servo (object *args, object *env) {
  (void) args, (void) env;
  object *arg1 = first(args);
  if (arg1 == NULL || cdr(arg1) == NULL) error(PSTR("argument is not a list of two items"));
  pinMode(5, OUTPUT); pinMode(6, OUTPUT);
  analogWrite(5, integer(first(arg1)));
  analogWrite(6, integer(second(arg1)));
  return arg1;
}

The tests in the if statement check that the subsequent calls to first() and second() will access valid memory addresses; otherwise on some platforms such as the ESP32 an exception will be caused if you call the function with a list of fewer than two items.

To test the function you could write, for example:

> (servo '(255 255))
(255 255)