https://github.com/daneelsan/tinylisp
A web-based Lisp interpreter with a "terminal" interface
https://github.com/daneelsan/tinylisp
html javascript lisp wasm zig
Last synced: 8 months ago
JSON representation
A web-based Lisp interpreter with a "terminal" interface
- Host: GitHub
- URL: https://github.com/daneelsan/tinylisp
- Owner: daneelsan
- License: mit
- Created: 2023-01-10T15:35:42.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2025-03-21T14:53:58.000Z (over 1 year ago)
- Last Synced: 2025-08-17T21:35:10.846Z (10 months ago)
- Topics: html, javascript, lisp, wasm, zig
- Language: Zig
- Homepage: https://daneelsan.github.io/tinylisp/
- Size: 794 KB
- Stars: 3
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# tinylisp
Play with tinylisp at: https://daneelsan.github.io/tinylisp/
tinylisp is a minimal Lisp interpreter implemented in Zig, using NaN boxing for efficient memory representation. It supports core Lisp features like atoms, lists, conditionals, arithmetic, and closures, along with an interactive REPL and debugging tools for inspecting the heap, stack, and environment.

Read my thoughts at https://daneelsan.github.io/2025/03/20/tinylisp.html
## Documentation
### Atoms
`#t` (True):
```lisp
λ #t
#t
```
`ERR` (Error):
```lisp
λ (cdr 42)
ERR
```
### Arithmetic
`+` (Addition):
```lisp
λ (+ 1 2 3 4)
10
```
`-` (Subtraction):
```lisp
λ (- 10 2 3)
5
```
`*` (Multiplication):
```lisp
λ (* 2 3 4)
24
```
`/` (Division):
```lisp
λ (/ 3.4 4)
0.85
```
### Structural
`car`:
```lisp
λ (car '(1 2 3))
1
```
`cdr`:
```lisp
λ (cdr '(1 2 3))
(2 3)
```
`cons`:
```lisp
λ (cons 1 2)
(1 . 2)
```
```lisp
λ (cons 1 '(2 3))
(1 2 3)
```
### Quoting and Evaluation
`'` (Quoting):
```lisp
λ '(+ 1 2 3)
(+ 1 2 3)
```
`eval` (Evaluation):
```lisp
λ (eval '(+ 1 2 3))
6
```
### Conditionals and Logic
`if`:
```lisp
λ (if (< 1 2) 'true 'false)
true
```
`and`:
```lisp
λ (and (< 1 2) (< 2 3))
#t
```
```lisp
λ (and (< 2 1) (< 2 3))
()
```
`or`:
```lisp
λ (or (< 1 2) (< 3 2))
#t
```
`not`:
```lisp
λ (not (< 1 2))
()
```
`=` (Equality):
```lisp
λ (= 1.0 1)
#t
```
```lisp
λ (= 1.1 1)
()
```
### Lambdas and Closures
`lambda`:
```lisp
λ ((lambda (x) (* x x)) 5)
25
```
`define`:
```lisp
λ (define square (lambda (x) (* x x)))
square
```
```lisp
λ (square 5)
25
```
```lisp
λ (define x 42)
x
```
```lisp
λ (+ x x)
84
```
### Debugging and Introspection
`echo`:
```lisp
λ (+ (echo (* 3 4) 5))
>> 12
12
```
`echo-eval`:
```lisp
λ (echo-eval (+ (* 3 4) 5))
>> ((+ (* 3 4) 5))
<< 17
17
```
`print-env`:
```lisp
λ (print-env)
(
(echo-eval . «echo-eval»)
(echo . «echo»)
(print-env . «print-env»)
(print-stack . «print-stack»)
(print-heap . «print-heap»)
(define . «define»)
(lambda . «lambda»)
(if . «if»)
(and . «and»)
(or . «or»)
(not . «not»)
(= . «=»)
(> . «>»)
(< . «<»)
(/ . «/»)
(* . «*»)
(- . «-»)
(+ . «+»)
(int . «int»)
(cdr . «cdr»)
(car . «car»)
(cons . «cons»)
(quote . «quote»)
(eval . «eval»)
(#t . #t)
)
()
```
`print-heap`:
```lisp
λ (print-heap)
------------------- HEAP -------------------
| # | address | symbol |
|-----|----------|-------------------------|
| 0 | 0x0000 | ERR |
| 1 | 0x0004 | #t |
| 2 | 0x0007 | eval |
| 3 | 0x000C | quote |
| 4 | 0x0012 | cons |
| 5 | 0x0017 | car |
| 6 | 0x001B | cdr |
| 7 | 0x001F | int |
| 8 | 0x0023 | + |
| 9 | 0x0025 | - |
| 10 | 0x0027 | * |
| 11 | 0x0029 | / |
| 12 | 0x002B | < |
| 13 | 0x002D | > |
| 14 | 0x002F | = |
| 15 | 0x0031 | not |
| 16 | 0x0035 | or |
| 17 | 0x0038 | and |
| 18 | 0x003C | if |
| 19 | 0x003F | lambda |
| 20 | 0x0046 | define |
| 21 | 0x004D | print-heap |
| 22 | 0x0058 | print-stack |
| 23 | 0x0064 | print-env |
| 24 | 0x006E | echo |
| 25 | 0x0073 | echo-evaluation |
| ... |
--------------------------------------------
()
```
`print-stack`:
```lisp
λ (print-stack)
------------- STACK ------------
| pointer | tag | ordinal | Expr
|----------|--------|----------|--------------
| 1024 | ATOM | 0x0004 | #t
| 1023 | ATOM | 0x0004 | #t
| 1022 | CONS | 1022 |
| 1021 | NIL | 0 | ()
| 1020 | ATOM | 0x0007 | eval
| 1019 | PRIM | 0 | «eval»
| 1018 | CONS | 1018 |
| 1017 | CONS | 1020 |
| 1016 | ATOM | 0x000C | quote
| 1015 | PRIM | 1 | «quote»
| 1014 | CONS | 1014 |
| 1013 | CONS | 1016 |
| 1012 | ATOM | 0x0012 | cons
| 1011 | PRIM | 2 | «cons»
| 1010 | CONS | 1010 |
| 1009 | CONS | 1012 |
| 1008 | ATOM | 0x0017 | car
| 1007 | PRIM | 3 | «car»
| 1006 | CONS | 1006 |
| 1005 | CONS | 1008 |
| 1004 | ATOM | 0x001B | cdr
| 1003 | PRIM | 4 | «cdr»
| 1002 | CONS | 1002 |
| 1001 | CONS | 1004 |
| 1000 | ATOM | 0x001F | int
| 999 | PRIM | 5 | «int»
| 998 | CONS | 998 |
| 997 | CONS | 1000 |
| 996 | ATOM | 0x0023 | +
| 995 | PRIM | 6 | «+»
| 994 | CONS | 994 |
| 993 | CONS | 996 |
| 992 | ATOM | 0x0025 | -
| 991 | PRIM | 7 | «-»
| 990 | CONS | 990 |
| 989 | CONS | 992 |
| 988 | ATOM | 0x0027 | *
| 987 | PRIM | 8 | «*»
| 986 | CONS | 986 |
| 985 | CONS | 988 |
| 984 | ATOM | 0x0029 | /
| 983 | PRIM | 9 | «/»
| 982 | CONS | 982 |
| 981 | CONS | 984 |
| 980 | ATOM | 0x002B | <
| 979 | PRIM | 10 | «<»
| 978 | CONS | 978 |
| 977 | CONS | 980 |
| 976 | ATOM | 0x002D | >
| 975 | PRIM | 11 | «>»
| 974 | CONS | 974 |
| 973 | CONS | 976 |
| 972 | ATOM | 0x002F | =
| 971 | PRIM | 12 | «=»
| 970 | CONS | 970 |
| 969 | CONS | 972 |
| 968 | ATOM | 0x0031 | not
| 967 | PRIM | 13 | «not»
| 966 | CONS | 966 |
| 965 | CONS | 968 |
| 964 | ATOM | 0x0035 | or
| 963 | PRIM | 14 | «or»
| 962 | CONS | 962 |
| 961 | CONS | 964 |
| 960 | ATOM | 0x0038 | and
| 959 | PRIM | 15 | «and»
| 958 | CONS | 958 |
| 957 | CONS | 960 |
| 956 | ATOM | 0x003C | if
| 955 | PRIM | 16 | «if»
| 954 | CONS | 954 |
| 953 | CONS | 956 |
| 952 | ATOM | 0x003F | lambda
| 951 | PRIM | 17 | «lambda»
| 950 | CONS | 950 |
| 949 | CONS | 952 |
| 948 | ATOM | 0x0046 | define
| 947 | PRIM | 18 | «define»
| 946 | CONS | 946 |
| 945 | CONS | 948 |
| 944 | ATOM | 0x004D | print-heap
| 943 | PRIM | 19 | «print-heap»
| 942 | CONS | 942 |
| 941 | CONS | 944 |
| 940 | ATOM | 0x0058 | print-stack
| 939 | PRIM | 20 | «print-stack»
| 938 | CONS | 938 |
| 937 | CONS | 940 |
| 936 | ATOM | 0x0064 | print-env
| 935 | PRIM | 21 | «print-env»
| 934 | CONS | 934 |
| 933 | CONS | 936 |
| 932 | ATOM | 0x006E | echo
| 931 | PRIM | 22 | «echo»
| 930 | CONS | 930 |
| 929 | CONS | 932 |
| 928 | ATOM | 0x0073 | echo-eval
| 927 | PRIM | 23 | «echo-eval»
| 926 | CONS | 926 |
| 925 | CONS | 928 |
| 924 | ATOM | 0x0058 | print-stack
| 923 | NIL | 0 | ()
| ... |
|------------------------------|
()
```
## Build
Compiled using zig version:
```shell
$ zig version
0.14.0
```
Build the wasm executable using `zig build`:
```shell
$ zig build
$ ls zig-out/bin
tinylisp.wasm
```
Additionally, build the local executable using `zig build local`:
```shell
$ zig build local
$ ls zig-out/bin
tinylisp tinylisp.wasm
```
Or run the local executable directly using `zig build run`:
```shell
$ zig build run
λ
```
## TODO:
### General
- [X] Compile to .wasm and add a javascript REPL
- [X] Write a blogpost
- [ ] Expand documentation with more examples
### Zig
- [ ] Add tests
- [ ] Improve parsing to handle multiple expressions separed by new lines
### Terminal
- [X] `Ctrl+c` to delete the current line
- [ ] Multiline inputs don't work well with the history explorer (`Up Arrow`)
- [ ] Syntax highlighting
- [ ] Drop files
- [ ] Click to copy input/output
## Resources
- [Lisp in 99 lines of C and how to write one yourself - Robert-van-Engelen](https://github.com/Robert-van-Engelen/tinylisp#lisp-in-99-lines-of-c-and-how-to-write-one-yourself)
- [Passing strings to and from WebAssembly using C](https://log.schemescape.com/posts/webassembly/passing-strings-to-c.html)
- [TINYLISP: A Minimal Lisp Interpreter in Zig and WebAssembly](https://daneelsan.github.io/2025/03/20/tinylisp.html)