Puffin: Scheme-like language with self-hosting compiler and gradual types Kristopher Micinski and Claude Code have created Puffin, a minimal Scheme/ML-like functional programming language with a self-hosting compiler, gradual types, and three backends (arm64, x86-64, bytecode). The compiler, puffincc, is written in Puffin itself and can bootstrap from a bytecode seed using only a C toolchain, with a goal to replace Racket for day-to-day compiler and interpreter development. The language includes match patterns, quasiquoting, a gradual type system, a typed FFI, modules, and a standard library, though the author notes serious gaps in gradual typing and module design. Puffin is a minimal Scheme/ML-like functional programming language written by Claude Code, directed by and extending a compiler written by Kristopher Micinski. The original compiler / language is detailed here: https://kmicinski.com/functional-programming/2025/11/23/build-a-language/ https://kmicinski.com/functional-programming/2025/11/23/build-a-language/ I used Claude Code to build Puffin by extending that teaching-focused language from Jeremy Siek's compiler book with several quality-of-life features: - Match patterns and quasiquoting - A gradual type system using consistency-based typing - A simple standard library consisting of sets, hashes dictionaries , and several other facilities - A simple module system inspired by SML - A typed FFI The project is, in some ways, an adventure in Slop. My ultimate goal is to use this to replace Racket for my day-to-day brainstorming in compiler / interpreters / etc. Claude Fable built the code, I read all of the code and understand the compiler's structure--but there are still some serious gaps which have not been fully thought-through: gradual typing, modules, these are places which are seriously prototypical so far. I have written again, using Claude Code some large ports of Racket applications into Puffin, and a translation of a large ~10kloc Racket application did demonstrate a serious Puffin compiler bug--so there are surely both a genuinely bad, unsound design choices which must be rooted out and b bugs in the implementation. Here's a "hello world" example: define-type Expr Num Int Add Expr Expr Mul Expr Expr define eval-expr e : Expr : Int match e Num n n Add a b + eval-expr a eval-expr b Mul a b eval-expr a eval-expr b println eval-expr Add Mul Num 3 Num 4 Num 1 ;; 13 The compiler is called puffincc : it is self-hosting, written in Puffin see puffincc-src/ , and includes with three backends: a arm64, b x86-64, and c bytecode. It is the single source of ground truth for the language: the browser playground web/ runs puffincc itself , compiled to bytecode, on a WebAssembly build of the bytecode VM src/vm/ .Some highlights coming from Racket: Self-hosting, Racket-free : bin/bootstrap builds the compiler from the committed bytecode seed with nothing but a minimal C toolchain why not Rust ? , and should yield a stage-2/3 byte-identical fixpoint every time. Gradual types are described in docs/TYPES.md : ADTs, annotations where you want them, where you don't; transient casts with blame at declared boundaries; exhaustiveness warnings. A typed FFI docs/FFI.md : dlopen 'd C imports declared with ordinary : name τ types; the declaration generates the marshaling, and every crossing is checked with blame naming the import. As an example, examples/z3/ binds the Z3 SMT solver as a Puffin API. Modules + separate compilation described in docs/MODULES.md , a native REPL, a bytecode VM, and the in-browser playground with the real compiler and REPL. Tested by construction : a 309-check golden corpus and a differential error corpus run every program down every route — diagnostics byte-identical, rejection behavior corpus-tested like success. Quick start quick-start bin/bootstrap Racket-free bootstrap: cc-only, from the committed seed boot/puffincc.pbc, with a stage-2/3 fixpoint proof build/puffincc prog.puf -o prog puffincc compiles + links natively build/puffincc -t bytecode prog.puf -o prog.pbc ... or to bytecode bin/puffin-vm prog.pbc run bytecode on the native VM tools/test-corpus.sh the golden corpus, Racket-free gen mode WRITES the goldens tools/test-errors.sh the differential ERROR corpus src/errors-corpus : must-fail programs x every route tools/test-examples.sh the examples/ programs vs their goldens, native + VM z3 examples skip when libz3 is absent tools/gen-web-vm.sh browser engine artifacts self-hosted cd web && npm run dev the playground: puffincc in wasm bin/refresh-boot refresh the seed at release points Multi-file programs use the module system require "lib.puf" / provide ... ; all backends native, the bytecode VM, the web playground resolve via puffincc's resolver. Documentation documentation There is a tutorial here: docs/tutorial.html docs/tutorial.html , Puffin for Racketeers . We me and mister Claude also wrote reference docs: docs/LANGUAGE.md docs/LANGUAGE.html — the language, day to day docs/TYPES.md docs/TYPES.html — the gradual type system docs/MODULES.md docs/MODULES.html — modules and separate compilation docs/FFI.md docs/FFI.html — the FFI, with runnable examples/ examples/index.html including the Z3 binding and a solve-and-prove-unique Sudoku docs/STDLIB.md docs/stdlib-overview.html — the standard library docs/stdlib.html is the generated reference; its examples are executed and asserted at generation time docs/OPTIMIZER.md docs/OPTIMIZER.html — -O 0|1|2 docs/WASM-VM.md docs/WASM-VM.html + docs/BYTECODE.md docs/BYTECODE.html — the VM and the browser architecture docs/BOOTSTRAP.md docs/BOOTSTRAP.html — how the Racket-free bootstrap works docs/DELTA.md docs/DELTA.html — the project's history: "what's the delta from the course compiler?" The Racket oracle the-racket-oracle The Racket implementation in src/ is the optional consistency oracle : it cross-checks puffincc per-pass src/diff-ir.rkt , and its reference interpreter re-derives the goldens as a differential test: the two implementations must agree byte-for-byte. bin/build-puffincc hosted stage-1 bootstrap alternative bin/puffin the Racket-hosted CLI/REPL racket src/test.rkt -m all the corpus through the oracle routes racket src/diff-ir.rkt