LLVM-based compiler for a toy Lisp language. It has interactive REPL, supports AOT compilation and expr-by-expr file execution.
rustup
cargo
clang
rlwrap
- Switch to the latest nightly Rust:
rustup update nightly && rustup default nightly
- 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"
- Ubuntu:
cargo build && cargo build --manifest-path ./unlisp_rt_staticlib/Cargo.toml
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
.
>>> 1
1
>>> nil
nil
>>> "foo"
"foo"
>>> (let ((x 1) (y x)) (+ y x))
2
>>> (if (equal 2 (+ 1 1)) "foo" "bar")
"foo"
>>> (cons 1 nil)
(1)
>>> (rest (list 1 2))
(2)
>>> (first (list 1 2))
1
>>> (defun my-list (& args) args)
nil
>>> (my-list 1 2 3)
(1 2 3)
>>> (apply (symf +) (quote (1 2)))
3
>>> (apply (symf +) 1 2 (quote (3 4)))
10
>>> (defun foo (x) (lambda (y) (+ x y)))
nil
>>> (funcall (foo 1) 2)
3
>>> (let ((x 0))
(defun next ()
(set! x (+ x 1))))
nil
>>> (next)
1
>>> (next)
2
>>> (next)
3
>>> (defvar x 100)
nil
>>> x
100
>>> (defonce y 200)
nil
>>> y
200
>>> (defonce y 300)
nil
>>> y
200
It is located in file stdlib.unl
.
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)
>>> (print 1)
11
>>> (println 1)
1
1
>>> (println "foo")
"foo"
"foo"
>>> (stdout-write "foo")
foonil
>>> 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
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