Find a file
russell@unturf.com a7ca8e9f89 Add bytecode compiler and stack-based VM with 3-25x speedup
Stack-based VM with 17 opcodes, TCO via in-place frame reset,
compile-time macro expansion, and seamless interop between
compiled and interpreted code. 514 tests green.
2026-04-13 14:39:15 -04:00
.gitignore Initial implementation of uncommonlisp 2026-04-13 11:01:04 -04:00
bench.py Add bytecode compiler and stack-based VM with 3-25x speedup 2026-04-13 14:39:15 -04:00
CLAUDE.md Initial implementation of uncommonlisp 2026-04-13 11:01:04 -04:00
Makefile Initial implementation of uncommonlisp 2026-04-13 11:01:04 -04:00
README.md Add string ports, error objects, rationals, modules, record inheritance, pretty-print 2026-04-13 12:27:06 -04:00
stdlib.lsp Add parameterize fix, let-values, case=>, arithmetic, FS/system builtins, tracing, SRFI-64 2026-04-13 13:32:11 -04:00
tests.py Add bytecode compiler and stack-based VM with 3-25x speedup 2026-04-13 14:39:15 -04:00
uncommonlisp.py Add bytecode compiler and stack-based VM with 3-25x speedup 2026-04-13 14:39:15 -04:00

uncommonlisp

A Scheme-like Lisp interpreter in one Python file.

λ> (define (fib n)
     (let loop ((a 0) (b 1) (i 0))
       (if (= i n) a (loop b (+ a b) (+ i 1)))))
λ> (map fib (iota 10))
(0 1 1 2 3 5 8 13 21 34)

Usage

python3 uncommonlisp.py              # interactive REPL
python3 uncommonlisp.py script.lsp   # run a file
python3 uncommonlisp.py -e '(+ 1 2)' # eval an expression

What's implemented

Core language

  • Full lexical scoping and closures
  • Tail-call optimization (TCO) via explicit loop — deep recursion never blows the stack
  • Hygienic macros via syntax-rules with ellipsis (...) support
  • define-macro / defmacro for procedural macro transformers
  • call/cc (escape continuations)
  • values / call-with-values
  • dynamic-wind, guard, with-exception-handler
  • quasiquote / unquote / unquote-splicing with proper nesting
  • R7RS internal defines → letrec* body semantics (mutual recursion in bodies)
  • R7RS error objects — error-object? error-object-message error-object-irritants
  • Exact rational arithmetic via Fraction(/ 1 3)1/3, (+ 1/4 3/4)1
  • String ports — open-input-string open-output-string get-output-string read on ports
  • Module system — module / import with explicit export lists
  • define-record-type with (inherit parent) for single-inheritance

Special forms define set! lambda λ if cond case and or when unless begin let let* letrec letrec* named-let do quasiquote define-macro define-syntax syntax-rules let-syntax letrec-syntax apply eval values call/cc dynamic-wind guard parameterize load error module import define-record-type

Built-ins

  • Arithmetic: + - * / quotient remainder modulo expt sqrt abs floor ceiling round truncate min max gcd lcm log exp sin cos tan atan numerator denominator and more
  • Rationals: (/ 1 3)1/3, literal 1/3 syntax, exact arithmetic, exact / inexact conversion
  • Comparison: = < > <= >= zero? positive? negative? odd? even?
  • Booleans: not boolean? boolean=?
  • Equality: eq? eqv? equal?
  • Pairs & lists: cons car cdr set-car! set-cdr! list list* length append reverse list-ref list-tail memq memv member assq assv assoc iota map for-each filter fold-left fold-right reduce any every count flat-map sort sort-by partition find take drop take-while drop-while take-right drop-right zip flatten concatenate list-tabulate unfold and more
  • SRFI-1: last firstfifth delete lset-union lset-intersection lset-difference proper-list? dotted-list?
  • Strings: string-length string-ref substring string-append string-upcase string-downcase string->list list->string string->symbol symbol->string string->number (with #x/0x/#b/#o prefix detection) number->string string-contains string-split string-join string-trim string-replace format and more
  • Characters: char->integer integer->char char-alphabetic? char-numeric? char-upcase char-downcase
  • Vectors: make-vector vector vector-ref vector-set! vector->list list->vector
  • Hash tables: make-hash-table hash-table-set! hash-table-ref hash-table-ref/default hash-table-delete! hash-table-exists? hash-table-keys hash-table-values hash-table->alist hash-table-walk and more
  • Type predicates: number? integer? real? rational? string? symbol? pair? null? list? char? vector? boolean? procedure? exact? inexact?
  • I/O: display write newline read read-char peek-char read-line load with-output-to-string open-input-string open-output-string get-output-string
  • Error objects: error-object? error-object-message error-object-irritants
  • Pretty-print: pp / pretty-print — indented output respecting line width
  • Python interop: py-eval py-exec py-import py-call py-attr

Prelude (loaded automatically) when unless case while for define-record-type (with inheritance) 1+ 1- add1 sub1 square cube compose atom? range flatten string-map string-for-each call-with-string-output-port

Standard library (stdlib.lsp, load explicitly) Syntax-rules versions of let/and/or/cond/case/do, fluid-let, receive (SRFI-8), begin0, while/until, dotimes/dolist, push!/pop!, and-let* (SRFI-2), string utilities, list utilities (sum product maximum minimum average enumerate transpose chunks interleave), numeric utilities (factorial fib prime? primes-up-to clamp), alist/hash utilities, tree utilities, simple object system, coroutines via call/cc

Examples

; Closures
(define (make-counter)
  (let ((n 0))
    (lambda () (set! n (+ n 1)) n)))

(define c (make-counter))
(c) ; => 1
(c) ; => 2

; Hygienic macro (syntax-rules)
(define-syntax my-or
  (syntax-rules ()
    ((my-or) #f)
    ((my-or e) e)
    ((my-or e1 e2 ...)
     (let ((t e1))
       (if t t (my-or e2 ...))))))

; define-record-type
(define-record-type point
  (make-point x y)
  point?
  (x point-x)
  (y point-y set-point-y!))

(define p (make-point 3 4))
(point-x p)  ; => 3

; Named let (looping)
(let loop ((i 0) (acc '()))
  (if (= i 5)
      (reverse acc)
      (loop (+ i 1) (cons (* i i) acc))))
; => (0 1 4 9 16)

; Hash tables
(define freq
  (let ((h (make-hash-table)))
    (for-each (lambda (x)
      (hash-table-set! h x (+ 1 (hash-table-ref/default h x 0))))
      '(a b a c b a))
    h))
(hash-table-ref freq 'a)  ; => 3

; Tail calls — no stack overflow even at depth 1,000,000
(define (count-down n)
  (if (= n 0) 'done (count-down (- n 1))))
(count-down 1000000)  ; => done

; Python interop
(define re (py-import "re"))
(py-call (py-attr re 'findall) "[0-9]+" "abc123def456")
; => ["123", "456"]

Running tests

make test          # run 455 tests
make test-verbose  # verbose output

Running benchmarks

python3 bench.py     # compare against CPython baseline
python3 bench.py -v  # show result values too

File layout

uncommonlisp.py   interpreter (self-contained, one file)
stdlib.lsp        extended standard library (load manually)
tests.py          test suite (396 tests)
bench.py          benchmarks vs CPython
Makefile          make test / make repl