lali is a small implementation of LISP, derived from tiny-lisp, written in standard C11. It has features such as macros, tail recursion and a copying garbage collector.
lali extends and improves the original LISP. It tries to stay connected to the roots, while been greatly powerful. Also, aims at bringing joy to the programmer's mind. What you write, becomes part of who you are.
For lali to recognize numbers, numbers have to be prefixed or by +
(for positive
numbers) or by -
(for negative numbers).
There is support for the five arithmetic operations +
, -
, *
, /
, %
(with
%
the operands are automatically converted to integer) as well as the relational
operations <
, >
:
(- +5) ; -5
(- +5 +3.2 +.6) ; +1.2
(< +2 +4 +6.8) ; t
(> +4 +8) ; f
(random [number])
takes no or one number argument, returns a random number below
number
until 0.
(random) ; +747528572
(random +2) ; returns +0 or +1
(quote x)
, or 'x
, returns an expression without being processed:
(quote a) ; a
'b ; b
(quote (a b c)) ; (a b c)
'(a b c) ; (a b c)
(say x)
, or \x
, returns an expression without being processed, and with the
first car
as ()
. This result is useful when processing a list and wanting to
do something at the beginning, because other cons cells should never have ()
in
car
:
(say a) ; (() . a)
\b ; (() . b)
(say (a b c)) ; (() a b c)
\(a b c) ; (() a b c)
(split x)
takes a symbol, a number, or a string, returns a list with x
splitted by each character:
(split 'cons) ; (c o n s)
(split +123) ; (+ 1 2 3)
(split "meeow") ; ("m" "e" "e" "o" "w")
(split '(what error)) ; gives an error
(split cons) ; gives an error
(join x)
takes a list, returns an symbol from joining each list atom. List atoms
cannot be of different type:
(join '(a bc d)) ; abcd
(join '(+0 +1)) ; +0+1
(join '("a " "string")) ; "a string"
(join '("what" error)) ; error: cannot join list atoms of different type
(list ...)
returns a list containing the supplied arguments:
(list) ; ()
(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 '(a b c)) ; a
(cdr '(a b c)) ; (b c)
(dif x y)
returns t
if x
and y
refers to different objects, or if they are
numbers with different values, or if they are string objects with different
contents:
(dif +1 +1) ; f
(dif +1 +2) ; t
(dif 'a 'a) ; f
(dif 'a 'b) ; t
(dif t t) ; f
(dif t f) ; t
(dif '(a b) '(a b)) ; t
(differ x y)
returns t
if x
and y
are objects of different structure and
each of their elements satisfy the dif
predicate, otherwise f
:
(differ '(a b) '(a)) ; t
(differ '(a b) '(a b)) ; f
(space x)
returns t
if x
is an empty list ()
, otherwise f
:
(space ()) ; t
(space +1) ; f
(space t) ; f
(space '(a b)) ; f
(ap x)
returns t
if x
is "an existing thing" (not an empty list ()
),
otherwise f
:
(ap ()) ; f
(ap +1) ; t
(ap t) ; t
(ap '(a b)) ; t
(atom x)
returns t
if x
is an atom (and not an empty list ()
), otherwise
f
:
(atom ()) ; f
(atom +1) ; t
(atom t) ; t
(atom '(a b)) ; f
(listp x)
returns t
if x
is either an empty list ()
, or a list with
atom(s), otherwise f
:
(listp ()) ; t
(listp +1) ; f
(listp t) ; f
(listp '(a b)) ; t
(consp x)
returns t
if x
is a list with atom(s), otherwise f
:
(consp ()) ; f
(consp +1) ; f
(consp t) ; f
(consp '(a b)) ; t
(zerop x)
returns t
if x
is the number zero, otherwise f
:
(zerop +0) ; t
(zerop +1) ; f
(zerop t) ; error: t is not a number
(not x)
returns t
if x
is f
, return n
if x
is n
, otherwise returns
f
.
(and ...)
evaluates its arguments one at a time from left to right. As soon as
any argument evaluates to f
, and
returns f
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 f +3) ; f
(or ...)
evaluates its arguments one at a time from left to right. As soon as
any argument does not evaluate to f
, or
returns its value without evaluating
the remaining arguments. Otherwise, it returns f
:
(or) ; f
(or +1 +2 +3) ; +1
(or f f +3) ; +3
(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 f
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 f
, or no clauses are given, cond
returns
n
:
(cond) ; n
(cond (f 'Hello)
(t 'World)) ; World
(cond (f 'Hello)
('World)) ; World
(cond (t 'Hello)
('World)) ; Hello
(fill ...)
is similar to cond
. But fill
differs in its evaluation of test
.
When test
does evaluate to f
returns the result of evaluating expr...
without evaluating the remaining clauses:
(fill) ; n
(fill (f 'Hello)
(t 'World)) ; Hello
(fill (f 'Hello)
('World)) ; Hello
(fill (t 'Hello)
('World)) ; World
(prog ...)
takes zero or more expressions, and evaluates each expression, one
after the other. Returns the evaluation of the last expression. Returns n
, if no
expression is given:
(prog) ; n
(prog 'Hello
(atom f)) ; t
(prog 'Hello
n
(car 'World)) ; error: World is not a list
(progs expr ...)
is similar to prog
. But progs
differs when the evaluation
of any expression is equal to the symbol of the evaluation of expr
, stopping
evaluation of further expressions and returning the symbol of the evaluation of
expr
immediately:
(progs n) ; n
(progs n
'Hello
(atom f)) ; t
(progs n
'Hello
n
(car 'World)) ; n
(set expr-var-A expr-val-A ...)
binds the result of evaluating expr-val-A
to
the symbol of expr-var-A
evaluated. More than one expr-var
-expr-val
pair can
be given. set
returns the value bound to the last symbol:
(set '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 ()
, 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 ()
'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...)
:
(set 'x '(+1 +2 +3)) ; (+1 +2 +3)
(when (consp x)
(car x)) ; +1
(set 'x 'hello)
(when (consp x)
(car x)) ; n
when
, if implemented as a function, would not work correctly, since expr...
would be evaluated as soon as when
is called:
(set '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
cond
:
(defmacro when (test . expr)
(list cond (list test (cons 'prog expr))))
(when (consp x) (car x))
would then produce the expression
(cond ((consp x) (prog (car x))))
which yields the expected behaviour.
(read)
takes zero arguments, and reads an expression from standard input:
(set 'a (read)) ; type (Hello World!) followed by an enter
a ; (Hello World!)
(eval expr)
takes one expression, and evaluates expr
:
(eval t) ; t
(prog
(set 'a 'b)
(eval a)) ; error: b has no value
(prog
(set 'a 'b)
(eval 'a)) ; b
(eval '(atom 1)) ; t
(eval '(run it)) ; error: run has no value
(prog
(set 'a 'b)
(eval '(a it))) ; error: b is not a function
(newline)
prints a newline to the standard output.
(print x)
prints x
to the standard output, exactly the way it was entered:
(print '(Hello World!)) ; prints (Hello World!) followed by a space
(princ x)
is similar to print
. But princ
differs in its handling of lists
outputting them without the initial/ending parenthesis:
(princ '(Hello World!)) ; prints Hello World! followed by a space
(time)
gives a timestamp in (sec minute hour day month year dow dst utcoff).
(time) ; (+34 +22 +17 +29 +5 +2025 +4 t +3600)
You can find the licenses of lali in the LICENSES/ directory.
lali uses year ranges in its copyright text. For example, the year range "2025-2027" means years "2025, 2026, and 2027".
If you want to contribute, please send me (git) patches over email. My address is at the source's copyright notices. Thank you!