lali (Lali Another Lisp Implementation)

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.

Language features

Numeric operations

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 operation

(random [number]) takes no or one number argument, returns a random number below number until 0.

(random)                     ; +747528572
(random +2)                  ; returns +0 or +1

Processing operations

(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 operations

(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)

Predicates

(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

Logical operations

(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

Conditionals

(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

Grouping functions

(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

Defining variables

(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

Defining functions

(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

Defining macros

(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.

Input operation

(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 operation

(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

Output operations

(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 operation

(time) gives a timestamp in (sec minute hour day month year dow dst utcoff).

(time)                       ; (+34 +22 +17 +29 +5 +2025 +4 t +3600)

Copyright

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".

Contributions

If you want to contribute, please send me (git) patches over email. My address is at the source's copyright notices. Thank you!