Skip to content

OlegTheCat/unlisp-llvm

Repository files navigation

unlisp-llvm

LLVM-based compiler for a toy Lisp language. It has interactive REPL, supports AOT compilation and expr-by-expr file execution.

Building

Prerequisites

  • rustup
  • cargo
  • clang
  • rlwrap

Build steps

  1. Switch to the latest nightly Rust: rustup update nightly && rustup default nightly
  2. Install LLVM-7 and make it's binaries available on PATH:
    • Ubuntu: sudo apt install llvm-7-dev
    • OS X: brew install llvm@7 && export PATH="/usr/local/opt/llvm@7/bin:$PATH"
  3. cargo build && cargo build --manifest-path ./unlisp_rt_staticlib/Cargo.toml

Running

To launch REPL execute: rlwrap cargo run -p unlisp repl.

For more info on how to run the compiler, refer to cargo run -p unlisp -- --help.

Features

Literals

>>> 1
1
>>> nil
nil
>>> "foo"
"foo"

Lisp special forms

>>> (let ((x 1) (y x)) (+ y x))
2
>>> (if (equal 2 (+ 1 1)) "foo" "bar")
"foo"

Lists

>>> (cons 1 nil)
(1)
>>> (rest (list 1 2))
(2)
>>> (first (list 1 2))
1

Varargs

>>> (defun my-list (& args) args)
nil
>>> (my-list 1 2 3)
(1 2 3)

Apply

>>> (apply (symf +) (quote (1 2)))
3
>>> (apply (symf +) 1 2 (quote (3 4)))
10

Functions & closures

>>> (defun foo (x) (lambda (y) (+ x y)))
nil
>>> (funcall (foo 1) 2)
3

Mutability

>>> (let ((x 0))
  (defun next ()
    (set! x (+ x 1))))
nil
>>> (next)
1
>>> (next)
2
>>> (next)
3

Global variables

>>> (defvar x 100)
nil
>>> x
100


>>> (defonce y 200)
nil
>>> y
200
>>> (defonce y 300)
nil
>>> y
200

"Standard library"

It is located in file stdlib.unl.

Macros & quasiquote

Quasiquote is implemented using Unlisp's macro system. There are three macros, namely qquote which is quasiquote (like a backtick in other popular lisps), unq which stands for "unquote", and unqs which stands for "unquote-splicing".

>>> (defmacro strange-let (bindings & body)
  (reduce
   (lambda (acc binding)
     (let ((sym (first binding))
           (val (first (rest binding))))
       (qquote
        (funcall
         (lambda ((unq sym))
           (unq acc))
         (unq val)))))
   (qquote (let () (unqs body)))
   (reverse bindings)))
nil
>>> (strange-let ((x 1) (y 2) (z 3)) (+ x y z))
6
>>> (macroexpand-1 (quote (strange-let ((x 1) (y 2) (z 3)) (+ x y z))))
(funcall (lambda (x) (funcall (lambda (y) (funcall (lambda (z) (let nil (+ x y z))) 3)) 2)) 1)

Printing and writing to stdout

>>> (print 1)
11
>>> (println 1)
1
1
>>> (println "foo")
"foo"
"foo"
>>> (stdout-write "foo")
foonil

Error reporting

>>> x
compilation error: undefined symbol x

>>> (defun x (y))
nil

>>> (x 1 2)
runtime error: wrong number of arguments (2) passed to x

>>> (+ 1 (quote x))
runtime error: cannot cast symbol to int

>>> (undefined-fn 1 2 3)
runtime error: undefined function undefined-fn

AOT compilation

To compile a file into a binary, the function named -main needs to be defined, which designates an entrypoint. Also make sure to run cargo build --manifest-path ./unlisp_rt_staticlib/Cargo.toml command before AOT compilation to build static runtime library (needs to be only done once).

$ cargo build --manifest-path ./unlisp_rt_staticlib/Cargo.toml
    Finished dev [unoptimized + debuginfo] target(s) in 0.03s

$ cat file.unl
(defun -main ()
  (println (fibo 10)))

$ cargo run -p unlisp -- compile -f file.unl -o binary
    Finished dev [unoptimized + debuginfo] target(s) in 0.03s
     Running `target/debug/unlisp compile -f file.unl -o binary`
Compiling file: file.unl...
Linking with runtime library: ./unlisp_rt_staticlib/target/debug/libunlisp_rt.a...

$ ./binary
89

About

Compiler for a toy Lisp language

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published