Automatic uLisp to C converter

This article describes a Lisp-to-C converter that automatically converts a uLisp program to valid C, for use as the starting point in creating a C version of the application.

The Lisp-to-C converter will run in uLisp, or any Common Lisp implementation. I ran it using uLisp on an Adafruit ItsyBitsy M4. Although the C program it generates isn’t perfect, it normally only requires a bit of tidying up to create a working C version of the program.

This program is a work in progress, but I thought it would be worth sharing it now in case it's useful to other uLisp users.

Introduction

When I'm developing a project for a microcontroller, such as the ATmega328 used in the Arduino Uno, or the ATtiny85, I often prototype the application in uLisp before converting it to C for use in the final application. For example, I tested my recent Tiny TFT Graphics Library in uLisp before developing a C version.

Running it in uLisp makes it easy to try out the concept and fine-tune parameters simply by typing uLisp commands at the serial monitor, without needing to compile and upload new code after every change. It's also easier to insert debugging statements in uLisp, to print out intermediate values and work out what's going on if the program doesn't behave as expected.

I wrote this Lisp-to-C converter to help in converting the uLisp prototype to a C version. It automates much of the work of translating the uLisp functions into working C code, leaving just a bit of tidying up to do.

Operation

To convert a function load it into uLisp; for example:

(defun area (w h) (print (* w h)))

Then execute:

(lisp-to-c 'area)

You can also convert a function by giving it as an argument to convert; in this case first zero ind:

(setq *ind* 0)

Then call convert; for example:

(convert '(defun area (w h) (print (* w h))))

In either case the generated C will be printed:

// Function area
void area (int w, int h) {
  print((w * h));
}

Limitations

The lisp-to-c code-converter converts a Lisp function at a time, and works best on uLisp programs that are written in a C-like style, avoiding constructs that have no obvious equivalent in C.

  • All variables are assumed to be of type int. Lisp lists are converted to array references in C.
  • Functions are assumed to take int parameters, and return void. If the function returns a value you will need to edit the generated C as appropriate.
  • All constants are expressed in decimal.
  • The converter puts in a few unnecessary brackets and semicolons.

Example

Here's a practical example of the code-converter in action, converting the uLisp program Blinking primes. The program consists of two functions, pri and bli:

(defun pri (n)
  (let ((d 2))
    (loop
     (when (> (* d d) n) (return t))
     (when (zerop (mod n d)) (return nil))
     (incf d))))
(defun bli (pin)
  (pinmode pin t)
  (dotimes (x 32767)
    (when (and (> x 1) (pri x))
      (dotimes (f (* x 2))
        (digitalwrite pin (evenp f))
        (delay 250))
      (delay 1500))))

To convert pri to C give the command:

(lisp-to-c 'pri)

This generates the following C:

// Function pri
void pri (int n) {
  int d = 2;
    for (;;) {
    if ((d * d) > n) {
      return(true);
    };
    if ((n % d) == 0) {
      return(false);
    };
    d++;
  };
}

Next, to convert bli to C:

(lisp-to-c 'bli)

This generates:

// Function bli
void bli (int pin) {
  pinMode(pin, true);
  for (int x=0; x<32767; x++) {
    if ((x > 1) && pri(x)) {
      for (int f=0; f<(x * 2); f++) {
        digitalWrite(pin, ((f & 1) == 0));
        delay(250);
      }
      delay(1500);
    };
  }
} 

Copy and paste these two function definitions into a new document in the Arduino IDE. The only tidy-up in this case is to edit the definition of pri to:

boolean pri (int n)

since it returns a boolean value, and to add:

void setup() {
  bli(13);
}

void loop () {
}

The C program then compiles without errors and works perfectly. Other conversions may need a bit more tidying up than this.

How it works

The program works by calling convert for each Lisp element. The main section of this is a case statement that deals appropriately with each Lisp construct:

(defun convert (form)
  (cond
   ((consp form)
    (let ((fn (first form))
          (rest (cdr form)))
      (case fn
        ; defun
        (defun (do-defun rest))
        ; let let*
        ((let let*) (do-let rest))
        ; Binary operator
        ((+ - * / > < >= <= and or = /= mod logand logior logxor) 
         (op (trans fn) rest))
        (ash (if (minusp (second rest)) 
                 (op '>> (list (first rest) (- (second rest)))) 
               (op '<< rest)))
        ; Inline function of one argument
        (abs
         (out (trans fn) "(") (convert (first rest)) (princ ")"))
        ; Inline function of two arguments
        ((max min)
         (out (trans fn) "(") (convert (first rest)) 
         (princ ", ") (convert (second rest)) (princ ")"))
        ; Array
        (nth (convert (second rest)) (princ "[") (convert (first rest)) (princ "]"))
        ; Special things
        (progn (do-block rest))
        (1+ (op "+" (list (first rest) 1)))
        (1- (op "-" (list (first rest) 1)))
        (evenp (princ "(") (op "&" (list (first rest) 1)) (princ " == 0)"))
        (oddp (princ "(") (op "&" (list (first rest) 1)) (princ " == 1)"))
        (zerop (op "==" (list (first rest) 0)))
        ((incf decf) 
         (convert (second form)) (princ (trans fn)))
        ((setq setf)
         (convert (first rest)) (princ " = ") (convert (second rest)))
        ; Loop
        (loop (indent) (princ "for (;;) ") (do-block rest))
        (dotimes (do-dotimes rest))
        ; Conditional
        (when (princ "if ") (do-when rest))
        (unless (princ "if (!") (convert (first rest)) (princ ") ") (do-block (cdr rest)))
        (if (do-if rest))
        (cond (do-cond rest))
        ; Function
        (t (do-fun (trans fn) rest)))))
   (t (princ (trans form)))))

For tidyness the code to handle each construct is in a separate function, with names such as do-defun and do-loop.

Here's the full program: uLisp to C converter program.