This post shows very basic steps in order to get started on writing applications in Common LISP. The steps include:

  • Define packages, import/export functions
  • Write test cases and test your Common LISP program
  • Make a stand-alone executable

The software I used are:

  • SBCL 1.1.13 - A Common LISP implementation
  • Buildapp 1.5 - Used to generate stand-alone executable
  • FiveAm 1.1 - A testing framework in Common LISP

Our example will be a simple command-line application that takes two numbers as arguments and does addition and multiplication. Here, I write addition in add.lisp and multiplication in mul.lisp. Their test cases will be add-test.lisp and mul-test.lisp.

add.lisp

;;; package definition
(in-package :cl-user)
(defpackage my.add
  (:nicknames #:add)
  (:use :common-lisp)
  ;; We want to export the following functions
  (:export #:add-one 
           #:sub-one
           #:add))
(in-package #:my.add)

(defun add-one (a)
  "Add one to a"
  (1+ a))

(defun sub-one (a)
  "Subtract one from a"
  (1- a))

(defun add (a b)
  "Add two numbers recursively"
  (cond ((= b 0) a)
        (t (add (add-one a) (sub-one b)))))

mul.lisp

;;; package definition
(in-package :cl-user)
(defpackage my.mul
  (:nicknames #:mul)
  (:use :common-lisp)
  ;; We need add from add.lisp
  (:import-from #:my.add
                #:add)
  (:export #:mul))
(in-package #:my.mul)

(defun mul (a b)
  (let ((result 0))
    (dotimes (i b)
      (setf result (add result a)))
    result))

add-test.lisp

;;; package definition
(in-package :cl-user)
(defpackage my.add.test      ; our package name
  (:use :common-lisp         ; we will use functions from common
        :fiveam)             ; lisp and fiveam
  (:import-from #:my.add     ; this is our package for addition
                #:add-one    ; add one to a number
                #:sub-one    ; subtract one from a number
                #:add)       ; add two numbers
  (:export #:run-test))
(in-package #:my.add.test)

;;; define test suite
(def-suite add-suite :description "Test suite for addition.")
(in-suite add-suite)

;;; all the test cases
(defconstant +add-one-test-data+ '((1 0)
                                   (2 1)))
(test add-one
  (mapcar #'(lambda (pair)
              (let ((result (add-one (second pair)))
                    (truth (first pair)))
                (is (equal result truth)
                    (format nil "Expect ~A. Got ~A" truth result))))
          +add-one-test-data+))

(defconstant +sub-one-test-data+ '((-2 -1)
                                   (-1 0)))
(test sub-one
  (mapcar #'(lambda (pair)
              (let ((result (sub-one (second pair)))
                    (truth (first pair)))
                (is (equal result truth)
                    (format nil "Expect ~A. Got ~A" truth result))))
          +sub-one-test-data+))

(defconstant +add-test-data+ '((0 0 0)
                               (1 1 0)
                               (2 1 1)
                               (4 2 2)))
(test (add :depends-on add-one) ; this test case depends on add-one
  (mapcar #'(lambda (lst)
              (let ((truth (first lst))
                    (result (add (first (rest lst))
                                 (second (rest lst)))))
                (is (equal result truth)
                    (format nil "Expect ~A. Got ~A" truth result))))
          +add-test-data+))

(defun run-test ()
  (run! 'add-suite))

mul-test.lisp

;;; package definition
(in-package :cl-user)
(defpackage my.mul.test
  (:use :common-lisp
        :fiveam)
  (:import-from #:my.mul
                #:mul)
  (:export #:run-test))
(in-package #:my.mul.test)

;;; test suite
(def-suite mul-suite :description "Test suite for multiplication.")
(in-suite mul-suite)

;;; test cases
(defconstant +mul-test-data+ '((0 0 0)
                               (0 1 0)
                               (1 1 1)
                               (8 2 4)))
(test mul
  (mapcar #'(lambda (lst)
              (let ((truth (first lst))
                    (result (mul (first (rest lst))
                                 (second (rest lst)))))
                (is (equal result truth)
                    (format nil "Expect ~A. Got ~A" truth result))))
          +mul-test-data+))

(defun run-test ()
  (run! 'mul-suite))

You can fire up Emacs and SLIME now if you want to run the program and the test cases, but if you want a stand-alone executable file, you need to first write a driver and a makefile that uses BuildApp to generate your executable.

First, let's write our driver.

main.lisp

(defun print-options ()
  (format t "Usage: main [options] n1 n2 ~%")
  (format t "  Options:~%")
  (format t "    -a       add n1 n2~%")
  (format t "    -m       mul n1 n2~%"))

(defun main (argv)
  ;; no options are given
  (when (null (rest argv))
    (print-options)
    (return-from main))

  (let ((opt (second argv))
        (n1 (parse-integer (first (rest (rest argv)))))
        (n2 (parse-integer (second (rest (rest argv))))))
    (when (or (null n1) (null n2))
      (print-options)
      (return-from main))

    (cond ((string= opt "-a") (format t "~A~%" (my.add:add n1 n2)))
          ((string= opt "-m") (format t "~A~%" (my.mul:mul n1 n2)))
          (t (print-options)))))

Then we need a makefile:

# You can ignore the following two lines if buildapp and sbcl can
# be found in PATH and SBCL_HOME is set
PATH := ${PATH}:/path/to/buildapp_and_sbcl
SBCL_HOME := /path/to/sbcl/dir/that/has/sbcl.core

TARGETS = main

# We need to include the external libraries we used
ASDF_TREE = --asdf-tree ~/quicklisp/dists/quicklisp/software/
SYSTEM = --load-system fiveam

all: $(TARGETS)

main: main.lisp add.lisp mul.lisp
    buildapp --output main \
    --load add.lisp \
    --load mul.lisp \
    --load main.lisp \
    --entry main

test: add.lisp mul.lisp add-test.lisp mul-test.lisp
    buildapp --output test \
    $(ASDF_TREE) \
    $(SYSTEM) \
    --load add.lisp \
    --load mul.lisp \
    --load add-test.lisp \
    --load mul-test.lisp \
    --load main.lisp \
    --eval '(defun main (argv) (declare (ignore argv)) (my.add.test:run-test) (my.mul.test:run-test))' \
    --entry main

clean:
    rm -rf ./test ./main

That's it. Let's create our executable and try to add or multiply some numbers:

$ make
buildapp --output main \
    --load add.lisp \
    --load mul.lisp \
    --load main.lisp \
    --entry main
;; loading file #P"/private/tmp/foo/add.lisp"
;; loading file #P"/private/tmp/foo/mul.lisp"
;; loading file #P"/private/tmp/foo/main.lisp"
[undoing binding stack and other enclosing state... done]
[saving current Lisp image into main:
writing 5936 bytes from the read-only space at 0x0x20000000
writing 4032 bytes from the static space at 0x0x20100000
writing 44957696 bytes from the dynamic space at 0x0x1000000000
done]
$ main -a 2 3
5
$ main -m 2 3
6

Let's test our program:

$ make test
buildapp --output test \
    --asdf-tree ~/quicklisp/dists/quicklisp/software/ \
    --load-system fiveam \
    --load add.lisp \
    --load mul.lisp \
    --load add-test.lisp \
    --load mul-test.lisp \
    --load main.lisp \
    --eval '(defun main (argv) (declare (ignore argv)) (my.add.test:run-test) (my.mul.test:run-test))' \
    --entry main
;; loading system "fiveam"
;; loading file #P"/private/tmp/foo/add.lisp"
;; loading file #P"/private/tmp/foo/mul.lisp"
;; loading file #P"/private/tmp/foo/add-test.lisp"
;; loading file #P"/private/tmp/foo/mul-test.lisp"
;; loading file #P"/private/tmp/foo/main.lisp"
STYLE-WARNING: redefining COMMON-LISP-USER::MAIN in DEFUN
[undoing binding stack and other enclosing state... done]
[saving current Lisp image into test:
writing 5936 bytes from the read-only space at 0x0x20000000
writing 4032 bytes from the static space at 0x0x20100000
writing 49872896 bytes from the dynamic space at 0x0x1000000000
done]
$ ./test
........
 Did 8 checks.
    Pass: 8 (100%)
    Skip: 0 ( 0%)
    Fail: 0 ( 0%)

....
 Did 4 checks.
    Pass: 4 (100%)
    Skip: 0 ( 0%)
    Fail: 0 ( 0%)

I love LISP and I've been trying to look for oppurtunities to write programs in LISP. Last year in my COMP 412 class at Rice, I wrote two compiler projects (one was an LL1 parser generator and another one was an instruction scheduler) in Common LISP and that was a fantastic experience.

I know there are still a lot of things I need to learn about Common LISP. If you spot any bad practice in my code, please send me email.

I noticed that there is a pretty cool program called Quickproject that creates a Common LISP project skeleton. Sadly, I never tried it, because I didn't have time to fully understand it.