# 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 * read-eval-print loop * interpretation from stdin or file arguments * numbers, strings, symbols and lists * functions and macros * lexical scoping * scheme-style dotted parameter lists * scheme-style tail recursion * copying garbage collector ### 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 `!` (different), `<`, `>`: (- +5) ; -5 (- +5 +3.2 +.6) ; +1.2 (< +2 +4 +6.8) ; t (> +4 +8) ; f (! +4 +4) ; 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) ### 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)) ; # (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!