summaryrefslogtreecommitdiff
path: root/README.md
blob: d193110100d2e7d18f0b82c4b18a91bee09c66fd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
<!--
lali (Lali Another Lisp Implementation)

Author: Daniel Cerqueira (dan.git@lispclub.com)
Maintainer: Daniel Cerqueira (dan.git@lispclub.com)
Version: 0.0
Keywords: lali, lisp, implementation, interpreter, lisp1.5,
          computer programming language
Homepage: https://gitlab.com/alexandre1985/lali
SPDX-License-Identifier: GFDL-1.3-or-later

Copyright (C) 2025 Daniel Cerqueira

This file is part of lali.

Permission is granted to copy, distribute and/or modify this document under the
terms of the GNU Free Documentation License, Version 1.3 or any later version
published by the Free Software Foundation; with no Invariant Sections, no
Front-Cover Texts, and no Back-Cover Texts.

You should have received a copy of the GNU Free Documentation License along with
this program; if not, see <https://www.gnu.org/licenses/>.


lali software is based on tiny-lisp <https://github.com/matp/tiny-lisp/> version
from 2016, written by Matthias Pirstitz, which is released to public domain under
Unlicense <https://unlicense.org/>.
-->

# 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

lali supports 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)                     ; 2.1442e+09
    (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 '(1 2 3))               ; 1
    (cdr '(1 2 3))               ; (2 3)

### 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 '(1 2) '(1 2))          ; 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 '(1 2) '(1))         ; t
    (differ '(1 2) '(1 2))       ; f

`(space x)` returns `t` if `x` is an empty list `()`, otherwise `f`:

    (space ())                   ; t
    (space 1)                    ; f
    (space t)                    ; f
    (space '(1 2))               ; 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 '(1 2))                  ; 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 '(1 2))                ; 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 '(1 2))               ; t

`(consp x)` returns `t` if `x` is a list with atom(s), otherwise `f`:

    (consp ())                   ; f
    (consp 1)                    ; f
    (consp t)                    ; f
    (consp '(1 2))               ; 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
`if`:

    (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)                       ; (23 54 17 13 5 2025 2 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!