This is a lisp interpreter written for a course project. It does not intend to be fast or feature rich, and the C++ code is pretty hacky, but it can probably provide some educational value.
A recent version of Visual Studio, GCC, and Clang should be found on your system, they need to support C++20.
You also need to have CMake and Conan package manager installed. You can install them by
pip install cmake
pip install conan
After installing all the tools, you can build the code with the following commands:
mkdir build
cd build
cmake ..
make
After building the project, there should be an bin/easylisp
executable under the build
(where you invoked CMake)
folder.
You can run the interpreter as a REPL:
$ easylisp
>> 1
1
>> (define square (lambda (x) (* x x)))
>> (map square (range 1 6))
(1 4 9 16 25)
>> (cons 1 (list 2 3 4))
(1 2 3 4)
>> (require list) ;; load list.easylisp
>> (cartesian-product (list 1 2 3) (list 4 5))
((1 . 4) (1 . 5) (2 . 4) (2 . 5) (3 . 4) (3 . 5))
Or you can use it to interpret a file:
$ easylisp file.easylisp
You can find some examples in the scripts
folder. Those scripts will be automatically copied into the same folder of
executable after build.
(if (> 10 20) 20 10)
let
creates local variables. The following expression evaluates to the number 15
:
(let ((x 10) (y 5)) (+ x y))
Lambda expression is the primary way to create new procedurals. The following lambda expression adds 1
to x
every
time it gets applied:
(lambda (x) (+ x 1))
The above lambda expression creates an anonymous procedural. To store it in a variable, we can combine it with the let expression:
(let ((+1 (lambda (x) (+ x 1)))) (+1 42))
The above code first creates procedural +1
, and then we apply it with 42
. As a result, we should get 43
.
Like many more modern Lisp dialects, our language has Lexical scope, which means the lambda expression can refer to variables in outer scope when it is created. For example:
(((lambda (y) (lambda (x) (+ x y))) 42) 55)
The above expression will evaluate 97
even though the inner lambda expression does not contain a parameter y. The
above code is equivalent to:
(((lambda (x) (+ x 42))) 55)
define
creates global variables. The following example evaluates to 101
.
(define x 100)
(+ x 1)
We can combine define
and lambda
to create new procedurals. For example:
(define cube (lambda (x) (* x x x)))
(cube 2) ; 8
(cube 10) ; 1000
(cube 100) ; 1000000
We can achieve recursion by using definition. For example:
(define fact (lambda (x) (if (< x 2) 1 (* x (fact (- x 1))))))
(fact 0) ; 1
(fact 1) ; 1
(fact 2) ; 2
(fact 3) ; 6
(fact 10) ; 3628800
(list 1 2 3) ;; (1 2 3)
(cons 1 (list 2 3)) ;; (1 2 3)
(cons 1 2) ;; (1 . 2) - This is a pair instead of a list
require
loads a module in the working directory. For example, (require list)
interpret list.easylisp
and adds the
results into the global environment.
boolean?
true
,false
and
,or
,not
(eq? x y)
- Referential equality comparison
- examples:
(eq? 1 2) ; false (eq? 1 1) ; true (eq? (list 1) (list 1)) ; false (let ((x (cons 1 2))) (eq? x x)) ; true
(equal? x y)
- Value equality comparison
- examples:
(equal? 1 2) ; false (equal? 1 1) ; true (equal? (list 1) (list 1)) ; true (let ((x (cons 1 2))) (eq? x x)) ; true
number?
+
,-
,*
,/
<
,<=
,>
,>=
procedural?
null
Empty list(null? v)
- Returns
true
ifv
isnull
,false
otherwise - examples:
(null? 1) ; false (null? (list)) ; true (null? null) ; true (null? (list 1)) ; false (null? (cons 1 2)) ; false
- Returns
(pair? v)
- Returns
true
ifv
is a pair,false
otherwise - examples:
(pair? 1) ; false (pair? null) ; false (pair? (list 1)) ; true (pair? (cons 1 2)) ; true
- Returns
(list? v)
- Returns
true
ifv
is a list,false
otherwise - examples:
(list? 1) ; false (list? (list)) ; true (list? (list 1)) ; true (list? (cons 1 2)) ; false
- Returns
(list ...)
- Constructs a list
- examples:
(list) ; null (list 1 2) ; (1 2)
(range lower upper)
lower: number?
upper: number?
- Constructs a list of integers from range
[lower, upper)
- examples:
(range 0 5) ; (0 1 2 3 4) (range 5 0) ; ()
(cons v1 v2)
- Constructs a pair
- examples:
(cons 1 2) ; (1 . 2) (cons 1 (list 2) ; (1 2)
(car p)
p: pair?
- Returns the first element of a pair
- examples:
(car (list 1 2)) ; 1 (car (cons 1 2)) ; 1
(cdr p)
p: pair?
- Returns the second element of a pair
- examples:
(cdr (list 1 2)) ; (2) (cdr (cons 1 2)) ; 2
(map proc l)
proc: procedural?
l: list?
- Creates a new list by applying
proc
to each element ofl
- examples:
(map (lambda (x) (+ x 1)) (list 1 2 3 4 5)) ;; (2 3 4 5 6)
(filter pred l)
pred: procedural?
l: list?
- Returns a list with the elements of
l
for whichpred
producestrue
. - examples:
(filter (lambda (x) (> x 0)) (list 1 -1 2 -2 3)) ;; (1 2 3)
(foldl proc init l)
proc: procedural?
l: list?
- Folds from left to right
- examples:
(foldl * 1 (range 1 5)) ;; 24 (foldl cons (list 42) (range 0 5)) ;; (4 3 2 1 0 42)
(foldr proc init l)
proc: procedural?
l: list?
- Folds from right to left
- examples:
(foldr * 1 (range 1 5)) ;; 24 (foldr cons (list 42) (range 0 5)) ;; (0 1 2 3 4 42)
(print v)
- Prints the value
v
and an endline
- Prints the value
Copyright 2021 Lesley Lai & Yu Li & Valyria Mcfarland
This repository is released under the MIT license. See License for more information.