tiny-lisp is a small implementation of LISP, written in standard C11. It is intended to show how features such as macros, tail recursion and a copying garbage collector can be implemented in C.
- read-eval-print loop
- numbers, strings, symbols and lists
- functions and macros
- lexical scoping
- scheme-style dotted parameter lists
- scheme-style tail recursion
- copying garbage collector
tiny-lisp supports the four arithmetic operations +, -, *, / as well as
the relational operations =, <, <=, >, >=:
(- 5) ; -5
(- 5 3.2 .6) ; 1.2
(< 2 4 6.8) ; t
(> 4 8) ; nil
(list ...) returns a list containing the supplied arguments:
(list) ; nil
(list 1 2 3) ; (1 2 3)
(list 1 2 (+ 1 2)) ; (1 2 3)
(cons x y) creates a new cons cell, the car of which is x and the cdr
of which is y:
(cons 1 2) ; (1 . 2)
(cons 1 '(2)) ; (1 2)
(car x), (cdr x) return the car and cdr of a list respectively:
(car '(1 2 3)) ; 1
(cdr '(1 2 3)) ; (2 3)
(eq x y) returns t if x and y refer to the same object, or if they are
numbers with the same value, or if they are string objects with identical
content:
(eq 1 1) ; t
(eq 1 2) ; nil
(eq 'a 'a) ; t
(eq 'a 'b) ; nil
(eq "hello" "hello") ; t
(eq "hello" "world") ; nil
(eq '(1 2) '(1 2)) ; nil
(equal x y) returns t if x and y are objects of identical structure and
each of their elements satisfy the eq predicate, otherwise nil:
(equal '(1 2) '(1)) ; nil
(equal '(1 2) '(1 2)) ; t
(null x) returns t if x is nil, otherwise nil:
(null nil) ; t
(null 1) ; nil
(null "hello") ; nil
(null '(1 2)) ; nil
(atom x) returns t if x is not a cons cell, otherwise nil:
(atom nil) ; t
(atom 1) ; t
(atom "hello") ; t
(atom '(1 2)) ; nil
(consp x) returns t if x is a cons cell, otherwise nil:
(consp nil) ; nil
(consp 1) ; nil
(consp "hello") ; nil
(consp '(1 2)) ; t
(listp x) returns t if x is a cons cell or nil, otherwise nil:
(listp nil) ; t
(listp 1) ; nil
(listp "hello") ; nil
(listp '(1 2)) ; t
(zerop x) returns t if x is the number zero, otherwise nil:
(zerop 0) ; t
(zerop 1) ; nil
(zerop "hello") ; error: "hello" is not a number
(not x) returns t if x is nil, otherwise nil (not is therefore
identical to null, but is usually used in conjunction with boolean logic).
(and ...) evaluates its arguments one at a time from left to right. As soon
as any argument evaluates to nil, and returns nil without evaluating the
remaining arguments. Otherwise, it returns the value produced by evaluating
its last argument. If no arguments are supplied, and returns t:
(and) ; t
(and 1 2 3) ; 3
(and 1 nil 3) ; nil
(or ...) evaluates its arguments one at a time from left to right. As soon
as any argument does not evaluate to nil, or returns its value without
evaluating the remaining arguments. Otherwise, it returns nil:
(or) ; nil
(or 1 2 3) ; 1
(or nil nil 3) ; 3
(princ x) prints x to the standard output:
(princ "Hello!\n") ; prints Hello! followed by a newline
(print x) is similar to princ but its output is preceded by a newline and
followed by a space. In addition, print differs in its handling of strings
which it outputs exactly the way they were entered, including reproducing
escape sequences:
(print "\"a\\b\"\n") ; prints "\"a\\b\"\n"
(if test then [else]) returns the result of evaluating then if test
does not evaluate to nil, otherwise returns the result of evaluating else
or nil:
(if 1 2 3) ; 2
(if nil 2 3) ; 3
(if nil 2) ; nil
(cond ...) takes zero or more clauses, each of the form (test [expr...]).
cond returns the result of evaluating expr... of the first clause for which
test does not evaluate to nil without evaluating the remaining clauses. If
a clause does not supply expr..., the result of evaluating test is returned
instead. If every test evaluates to nil, or no clauses are given, cond
returns nil:
(cond) ; nil
(cond (nil "hello")
(t "world")) ; "world"
(cond (nil "hello")
("world")) ; "world"
(setq name expr ...) binds the result of evaluating expr to the symbol
name. More than one name-expr pair can be given. setq returns the value
bound to the last symbol:
(setq a 1
b 2) ; 2
a ; 1
b ; 2
(lambda params expr...) creates an anonymous function with parameters
params and body expr.... params can be nil, a symbol, or a list of
symbols. If params is a symbol or a dotted list of symbols, the function will
accept a variable number of arguments:
((lambda nil
"hello")) ; "hello"
((lambda (a b)
(+ a b)) 1 2) ; 3
((lambda (a b . c)
c) 1 2 3 4 5) ; (3 4 5)
((lambda args
args) 1 2 3 4 5) ; (1 2 3 4 5)
(defun name params expr...) creates a function and binds it to the symbol
name:
(defun plus1 (x)
(+ x 1)) ; #<Lambda (x)>
(plus1 2) ; 3
(macro params expr...) creates an anonymous macro with parameters params
and body expr....
(defmacro name params expr...) creates a macro and binds it to the symbol
name.
Macros are different from functions in that they do not evaluate their arguments when called. Instead, we can think of them as taking expressions as input and returning a new expression as output.
Imagine we want to implement (when test expr...):
(setq x '(1 2 3)) ; (1 2 3)
(when (consp x)
(car x)) ; 1
(setq x "hello")
(when (consp x)
(car x)) ; nil
when, if implemented as a function, would not work correctly, since expr...
would be evaluated as soon as when is called:
(setq x "hello")
(when (consp x)
(car x)) ; error: "hello" is not a list
However, we can implement when as a macro that wraps expr... in a call to
if:
(defmacro when (test . expr)
(list 'if test (cons 'progn expr)))
(when (consp x) (car x)) would then produce the expression
(if (consp x) (progn (car x))) which yields the expected behaviour.