August 5, 2019

Clojure to Common Lisp - Part 2 - Projects

This post is meant to be a quick start guide to setup a CL project and writing code.

If you do not have a CL Environment yet, please check out my previous post.

In Clojure you could use lein or boot to quickly create the skeleton of a project and you could start coding away. In common lisp we use a library called cl-project to achieve the same

Quick Note about Project Location

Modern CommonLisp implementations use asdf to define systems/projects. asdf by default will look for your projects in the following places:

  • ~/common-lisp
  • ~/.local/share/common-lisp/source

If you do not want to keep all your CL code in one of the above directories, check out this section of the asdf manual.

Creating a Project

Fire up a REPL (sbcl), and use quicklisp to pull the cl-project library first.

(ql:quickload "cl-project")

QuickLisp will load "cl-project" into your REPL (if it can’t find it locally, it will download it and any dependencies it needs first).

Now, you can use make-project to create a skeleton layout for your project:

(cl-project:make-project #p"~/common-lisp/first-app" :author "pvik" :license "MIT" :depends-on '(:alexandria))

You specify pathnames in CL with #p followed by the actual path enclosed in double-quotes.

The make-project function needs the path for you project and also takes some optional keyed parameters. You can find all the options here.

For our first-app we also included a library dependency alexandria

Now we should have the skeleton structure for our app in ~/common-lisp/first-app

$ cd ~/common-lisp/first-app
$ tree
.
├── first-app.asd
├── README.markdown
├── README.org
├── src
│   └── main.lisp
└── tests
    └── main.lisp

2 directories, 5 files

The first-app.asd defines our project (you can think of this like project.clj or build.boot in lein or boot) and it should like this:

(defsystem "first-app"
  :version "0.1.0"
  :author "pvik"
  :license "MIT"
  :depends-on ("alexandria")
  :components ((:module "src"
                :components
                ((:file "main"))))
  :description ""
  :in-order-to ((test-op (test-op "first-app/tests"))))

(defsystem "first-app/tests"
  :author "pvik"
  :license "MIT"
  :depends-on ("first-app"
               "rove")
  :components ((:module "tests"
                :components
                ((:file "main"))))
  :description "Test system for first-app"
  :perform (test-op (op c) (symbol-call :rove :run c)))

cl-project also included a testing framework rove for our tests. We will ignore this for now, and cover testing in a later blog post.

and our main.lisp (under src) should looks like this:

(defpackage first-app
  (:use :cl))
(in-package :first-app)

;; blah blah blah.

Writing Some Code

Let us add a function to our main.lisp

(defun hello-world ()
  (print "Hello World"))

Let us also update the package definition of first-app to include (:export :hello-world).

Our main.lisp should look like this:

(defpackage first-app
  (:use :cl)
  (:export :hello-world))
(in-package :first-app)


(defun hello-world ()
  (print "Hello World"))

Running Our Project

Fire up a REPL and require our project, as long as our project is in a location asdf can find, you should see something like this:

* (require "first-app")
; compiling file "/home/pvik/common-lisp/first-app/src/main.lisp" (written 09 AUG 2019 11:44:00 AM):
; compiling (DEFPACKAGE FIRST-APP ...)
; compiling (IN-PACKAGE :FIRST-APP)
; compiling (DEFUN HELLO-WORLD ...)

; wrote /home/pvik/.cache/common-lisp/sbcl-1.5.3-linux-x64/home/pvik/common-lisp/first-app/src/main-tmpAAURSO1.fasl
; compilation finished in 0:00:00.001
NIL

Now we can use our exported functions like this:

* (first-app:hello-world)

"Hello World" 

Generating a Binary

You can use asdf to generate a binary as well. Add the following lines to you .asd file:

:build-operation "program-op" ;; leave as is
:build-pathname "first-app.bin"
:entry-point "first-app:hello-world"	

under the system definition (defsystem) for "first-app".

Now you can generate the binary from the REPL like so:

* (asdf:make :first-app)
; compiling file "/home/pvik/common-lisp/first-app/src/main.lisp" (written 09 AUG 2019 11:44:00 AM):
; compiling (DEFPACKAGE FIRST-APP ...)
; compiling (IN-PACKAGE :FIRST-APP)
; compiling (DEFUN HELLO-WORLD ...)

; wrote /home/pvik/.cache/common-lisp/sbcl-1.5.3-linux-x64/home/pvik/common-lisp/first-app/src/main-tmpGHU3ALSV.fasl
; compilation finished in 0:00:00.001
[undoing binding stack and other enclosing state... done]
[performing final GC... done]
[defragmenting immobile space... (fin,inst,fdefn,code,sym)=1024+932+18740+19628+24991... done]
[saving current Lisp image into /home/pvik/common-lisp/first-app/first-app.bin:
writing 0 bytes from the read-only space at 0x50000000
writing 432 bytes from the static space at 0x50100000
writing 27492352 bytes from the dynamic space at 0x1000000000
writing 1347584 bytes from the immobile space at 0x50300000
writing 12600064 bytes from the immobile space at 0x52100000
done]

The fist-app.bin should be in your project root.

You should be setup to start coding away in your new project!

Powered by Hugo & Kiss.