Skeme is a lightweight Scheme-like language interpreter written in portable C. It features a single-pass compiler that emits bytecode for a stack-based virtual machine with tail-call optimization, mark-sweep garbage collection, closures, and a REPL.
General curiosity about garbage collection methods was the primary motivation for this project. Since LISP was the first language to feature garbage collection, implementing a popular LISP dialect seemed like a good test case.
Not having any prior experience implementing garbage collectors, or with closures or functional programming, I found Crafting Interpreters by Robert Nystrom very helpful.
This project uses many of the parsing methods I learned from Jack Crenshaw a long time ago. And while I don't explicitly use my ParserKit library for this project, the same recursive descent primitives are here. The project does use my hash library and test framework.
- Scheme R5RS-ish — lists, lambdas, closures, tail-call optimization,
cond/case - Bytecode VM — compiled to bytecode (using computed gotos on GCC/Clang, and function pointer tables on MSVC)
- Garbage collection — tri-color mark-sweep GC with automatic thresholds
- First-class functions — lambdas, closures with captured upvalues, higher-order functions
- Numeric types — integers and floats with tagged pointer representation
- String interning — strings are interned to avoid duplicates and for fast equality checks
- REPL — interactive read-eval-print loop with terminal colored output
- Extensible — register custom C types and functions (see Extending Skeme)
- Cross-platform — builds on Windows (MSVC), Linux (GCC/Clang), and macOS
git clone https://github.com/mseminatore/skeme.git
cd skeme
git submodule update --init --recursivecmake -S . -B build
cmake --build build --config ReleaseThe binaries are placed in build/Release/ (MSVC) or build/ (Make-based
generators).
makeskeme [options] [inputfile]
| Flag | Description |
|---|---|
-i |
Start interactive REPL |
-v |
Enable verbose output |
-d |
Enable parser debug output |
-p |
Enable parser logging |
-profile |
Print VM profiling report on exit |
-h |
Display usage help |
skeme myprogram.scmskeme -iOr simply run skeme with no arguments to enter the REPL. Use (exit) to quit.
Use load at the top of your script to pull in additional definitions:
(load "stdlib.scm")The file stdlib.scm provides common utility functions on top of the native builtins:
| Function | Description |
|---|---|
apply |
Apply a function to each element of a list |
filter |
Return elements satisfying a predicate |
append |
Concatenate two lists |
cadr |
(car (cdr x)) |
cddr |
(cdr (cdr x)) |
even? |
Test if a number is even |
odd? |
Test if a number is odd |
min |
Return the smaller of two numbers |
max |
Return the larger of two numbers |
assert |
Assert a condition or raise an error |
list-ref |
Return the nth element of a list |
list-tail |
Return sublist after dropping n elements |
member |
Find first element equal? to a value |
memq |
Find first element eq? to a value |
assoc |
Look up key in association list (equal?) |
assq |
Look up key in association list (eq?) |
for-each |
Apply function to each element (side effects) |
gcd |
Greatest common divisor |
lcm |
Least common multiple |
The following functions are implemented natively in C:
Arithmetic: +, -, *, /, =, <, >, <=, >=, modulo,
abs, quotient, remainder, expt, sqrt, floor, ceiling,
truncate, round
List operations: cons, car, cdr, list, length, reverse,
set-car!, set-cdr!, map
Type predicates: null?, pair?, list?, number?, integer?,
float?, boolean?, char?, string?, atom?, zero?, positive?,
negative?, procedure?
Equality: eq?, eqv?, equal?
Boolean: not
Strings: string-append, string-length, string-ref, substring,
string-copy, string->number, number->string, string->float,
float->string
Characters: char->integer, integer->char
I/O: display, write, newline, read, read-line, read-char,
write-char, write-line, printf, open-input-file, open-output-file,
close-file, delete-file
Control: eval, error, exit
Introspection: typeof, disasm, gc, gc-stats, mem-allocated,
getenv, command-line, pwd
Skeme supports registering custom C types and native functions. You can add new data types (with GC integration, printing, and equality) and expose C functions to Scheme code.
See EXTENDING.md for the full API reference and examples.
skeme -v test.scmRuns the built-in test suite using the skunit.scm test framework.
# CMake
ctest --test-dir build -C Release
# Make
make unit-test# Make
make all-testsMIT — Copyright (c) 2025 Mark Seminatore