No description
Find a file
2025-08-15 15:54:46 +08:00
.vscode New odin version, now at 600 lines 2025-08-07 19:40:23 +08:00
paper Komplott, a single-file scheme/lisp with copying GC in 540 lines 2018-10-14 09:28:30 +02:00
tests GC 2025-08-05 14:30:36 +08:00
.gitignore remove justfile and rename odin version 2025-08-05 14:37:02 +08:00
.gitmodules Clean up and compress the odin implementation (now at 662 lines) - also removed the back submodule 2025-08-05 18:04:25 +08:00
komplodin.odin Some small cleanups 2025-08-15 15:54:46 +08:00
komplott.c New odin version, now at 600 lines 2025-08-07 19:40:23 +08:00
LICENSE Add MIT License 2018-10-14 09:35:42 +02:00
Makefile Clean up and compress the odin implementation (now at 662 lines) - also removed the back submodule 2025-08-05 18:04:25 +08:00
ols.json use vscode for debugging 2025-08-05 14:30:36 +08:00
README.md Update README 2025-08-07 19:47:35 +08:00

komplott / komplodin

A tribute to:

Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I

(as found in paper/recursive.pdf)

A micro-subset of scheme / the original LISP in a single C file: komplott.c

! New in 2025!

The LISP interpreter translated to Odin in komplodin.odin. More lines of code, but I am less familiar with the language and am translating directly from C, so there are probably ways to make it a cleaner solution.

When I posted this to lobste.rs, gingerBill (creator of Odin) was kind enough to make a more direct translation of the C code into Odin, which can be viewed in this gist: komplott.odin.

Since the lobste.rs posting, I have tweaked the Odin version a bit more, and so it differs from the C version quite a bit in the implementation details. I've tried to keep the output and functionality of the two programs the same though.

Features

  • Single file implementation.

  • Less than 500 lines of code (~600 lines for the Odin version)

  • Scheme-compliant enough for the test programs to be executable by GNU Guile (not sure if this is true anymore)

  • Copying semi-space garbage collector based on Cheney's Algorithm.

    For more details on how it works, Andy Wingo has a great post about this kind of garbage collector on his blog (wingolog).

  • Limited tail call optimization (not true TCO; see tests/true-tco.scm).

  • Near-zero error handling.

  • Zero thread safety or security.

Also includes:

lisp15.scm

An implementation of the core of LISP 1.5 from 1962

Instructions

  • To build the komplott executable, run make komplott. The only dependency aside from make is gcc.

  • To build the Odin version (komplodin), run make komplodin. This depends on the Odin compiler.

  • To run the LISP 1.5 interpreter and a couple of test cases, run make test.

LISP 1.5

The version presented in the README is slightly tweaked from the one that can be found in tests/lisp15.scm in order to more closely resemble early LISP rather than scheme: #t and #f are written as t and nil.


(define pairlis (lambda (x y a)
                  (cond ((null? x) a)
                        (t (cons (cons (car x) (car y))
                                 (pairlis (cdr x) (cdr y) a))))))

(define assoc (lambda (x a)
                (cond ((equal? (caar a) x) (car a))
                      (t (assoc x (cdr a))))))

(define atom? (lambda (x)
                (cond
                 ((null? x) t)
                 ((atom? x) t)
                 (t nil))))

(define evcon (lambda (c a)
                (cond
                 ((eval (caar c) a) (eval (cadar c) a))
                 (t (evcon (cdr c) a)))))

(define evlis (lambda (m a)
                (cond
                 ((null? m) nil)
                 (t (cons (eval (car m) a)
                             (evlis (cdr m) a))))))

(define apply (lambda (fun x a)
                (cond
                 ((atom? fun)
                  (cond
                   ((equal? fun (quote CAR)) (caar x))
                   ((equal? fun (quote CDR)) (cdar x))
                   ((equal? fun (quote CONS)) (cons (car x) (cadr x)))
                   ((equal? fun (quote ATOM)) (atom? (car x)))
                   ((equal? fun (quote EQ)) (equal? (car x) (cadr x)))
                   (t (apply (eval fun a) x a))))

                 ((equal? (car fun) (quote LAMBDA))
                  (eval (caddr fun) (pairlis (cadr fun) x a)))

                 ((equal? (car fun) (quote LABEL))
                  (apply
                   (caddr fun)
                   x
                   (cons
                    (cons (cadr fun) (caddr fun))
                    a))))))

(define eval (lambda (e a)
               (cond
                ((atom? e) (cdr (assoc e a)))
                ((atom? (car e))
                 (cond
                  ((equal? (car e) (quote QUOTE)) (cadr e))
                  ((equal? (car e) (quote COND)) (evcon (cdr e) a))
                  (t (apply (car e) (evlis (cdr e) a) a))))
                (t (apply (car e) (evlis (cdr e) a) a)))))

(define evalquote (lambda (fn x) (apply fn x (quote ()))))

Here is an example of actual LISP 1.5 code:

((LABEL MAPCAR
        (LAMBDA (FN SEQ)
                (COND
                  ((EQ NIL SEQ) NIL)
                  (T (CONS (FN (CAR SEQ))
                           (MAPCAR FN (CDR SEQ)))))))
 DUP LST)

; where
; DUP -> (LAMBDA (X) (CONS X X))
; LST -> (A B C)

To prevent reading from continuing indefinitely, each packet should end with STOP followed by a large number of right parentheses. An unpaired right parenthesis will cause a read error and terminate reading.

STOP )))))))))))))))))