lwhlisp is a lisp interpreter written in rust, based on this tutorial.
Install Rust and clone this repository. Then, inside the repository:
cargo run --release
This will compile the project and launch a REPL. The finished binary is target/release/lwhlisp
.
NOTE: The interactive session will start by loading the small included standard library (you can find the library in lib/lib.lisp).
If no such file is found, it will fail to load and you will get an error that looks like this:
Error:
0: While opening library file
1: While opening file lib/lib.lisp
2: No such file or directory (os error 2)
You can solve this problem by manually indicating where lwhlisp can find the library file:
cargo run --release -- --library /path/to/library/file.lisp
(The --
separates arguments to cargo and arguments to lwhlisp. It can be omitted when calling the lwhlisp
binary directly.)
The REPL should look something like this:
user>
Once you are at the user>
prompt, you may enter lisp code to be evaluated.
user> (+ 1 2 3)
=> 6
user> (if (> 5 4) (println "Five is bigger than Four") (println "Five is smaller than Four"))
Five is bigger than Four
=> "Five is bigger than Four"
You can also run files:
cargo run --release -- -f file.lisp
factorial.lisp:
(define (factorial x)
(if (= x 0)
1
(* x (factorial (- x 1)))))
(println (factorial 10))
$ cargo run --release -- -f file.lisp
3628800
()
is converted into nil
at parse time.
Takes a single argument, and returns it without evaluating
user> (quote (a b c))
=> (a b c)
Since this is used frequently, a shorthand is provided:
user> '(a b c)
=> (a b c)
The shorthand gets converted into the full version at parse time.
user> (lambda (x) (* x x))
=> (lambda (x) (* x x))
You can have multiple s-expressions in the body:
user> (lambda (x) (println x) (* x x))
=> (lambda (x) (println x) (* x x))
Lambdas can be directly evaluated:
user> ((lambda (x) (* x x)) 7)
=> 49
user> ((lambda (x) (println x) (* x x)) 7)
7
=> 49
Note that only the last s-expression is returned.
Binds a symbol to a value.
Basic syntax:
user> (define x 7)
=> x
user> x
=> 7
Creating a function:
user> (define square (lambda (x) (* x x)))
=> square
user> (square 7)
=> 49
Since this is a frequent action, there is special syntax for this:
user> (define (square x) (* x x))
=> square
user> (square 7)
=> 49
You can choose to get the arguments as a list instead:
user> (define (x . a) a)
=> x
user> (x 1 2 3)
=> (1 2 3)
Or have one (or more) required arguments, and get the rest as a list:
user> (define (x a . b) (println a) (println b))
=> x
user> (x 1 2 3)
1
(2 3)
=> "(2 3)"
(list is a function from the standard library that constructs a list from all of its arguments)
Macros work the same way as function, except that the arguments to macros are not evaluated.
Macros use the following syntax:
(defmacro (name arg...) body...)
For example, consider the following macro:
(defmacro (ignore x)
(cons 'quote (cons x nil)))
If we then evaluate the expression
user> (ignore foo)
=> foo
where foo is a (potentially unbound) symbol, the body of ignore
will be evaluated with the argument x
bound to the unevaluated symbol foo
.
The result of this is:
(quote . (foo . nil))
which is equivalent to:
(quote foo)
or
'foo
Finally, evaluating this value will give us the result of evaluating the macro body:
foo
The syntax is as follows:
(if test true-expr false-expr)
If test
is not nil, the result of evaluating this expression will be false-expr
. Else, it will be true-expr
.
This is a simple program that calculates factorials in a recursive fashion:
(define (factorial x)
(if (= x 0)
1
(* x (factorial (- x 1)))))
The base case, if x=0
, will return 1
.
In all other cases, we will return fact(x - 1) * x
.
user> (factorial 10)
=> 3628800