A R7RS Scheme interpreter/compiler in Go with hygienic macros.
The name is a play on "scheme" (as in "wiles" - cunning stratagems) and a nod to Wile E. Coyote, the cartoon schemer.
Wile compiles Scheme source code to bytecode and executes it on a stack-based virtual machine. It implements R7RS-style syntax-rules macros with a "sets of scopes" hygiene model.
Wile was originally a Lisp interpreter (compiler and VM) used for scripting block-based storage systems (databases, pipelines, search, etc.). It's been recently expanded to the Scheme R7RS standard in the hopes that it will be of use to someone who wants to use Lisp/Scheme in Go.
The world isn't really in need of another Scheme implementation - there are plenty out there. The primary use of this implementation is for embedding into Go. Go was seen as a good candidate for embedding Scheme because it's already got garbage collection (saving the need to implement garbage collection for Scheme), and Go's use in many server-side applications - such as web-servers and database servers.
Anthropic's Claude Code was used to help document, fill out the primitive library, and diagnose bugs. The CLAUDE.md file is committed to help others get started.
- Bytecode compilation - Scheme code compiles to an efficient bytecode representation
- Stack-based VM - Execution uses a stack machine with proper tail-call optimization
- Hygienic macros -
syntax-ruleswith the "sets of scopes" model (Flatt 2016) - First-class syntax objects - Source location and scope information preserved through compilation
- Derived expressions as macros -
let,cond,and,ordefined usingdefine-syntax
# Build everything
make
# Build from go directory
cd go && make build
# Run tests
cd go && make test# Start REPL
./go/cmd/scheme
# Run a Scheme file
./go/cmd/scheme -file example.scm;; Define a macro
(define-syntax let1
(syntax-rules ()
((let1 ((name val) ...) body)
((lambda (name ...) body) val ...))))
;; Use the macro
(let1 ((x 1) (y 2))
(+ x y))
;; => 3Source → Tokenizer → Parser → Expander → Compiler → VM
- Tokenizer - Lexical analysis
- Parser - Builds syntax tree with source information
- Expander - Macro expansion using
syntax-rulestransformers - Compiler - Generates bytecode operations
- VM - Executes bytecode with stack-based evaluation
| Package | Purpose |
|---|---|
machine/ |
Virtual machine, compiler, macro expander |
environment/ |
Variable binding and scope management |
syntax/ |
First-class syntax objects with hygiene |
values/ |
Scheme value types (numbers, pairs, etc.) |
match/ |
Pattern matching engine for macros |
parser/ |
Scheme parser |
tokenizer/ |
Lexer |
Wile uses the "sets of scopes" approach from Flatt's 2016 paper. Each identifier carries a set of scopes, and variable resolution checks that the binding's scopes are a subset of the use site's scopes:
bindingScopes ⊆ useScopes
This prevents unintended variable capture in macros:
(define-syntax swap!
(syntax-rules ()
((swap! x y)
(let ((tmp x)) ; tmp gets macro's scope
(set! x y)
(set! y tmp)))))
(let ((tmp 5) (a 1) (b 2)) ; this tmp has different scope
(swap! a b)
tmp) ; => 5, not captured by macro's tmpINTRO.md- Development guide and architecture overviewPRIMITIVES.md- Complete reference of supported types and primitivesgo/DESIGN.md- Detailed macro system designBIBLIOGRAPHY.md- Academic referencesgo/TODO.md- Implementation status and pending tasks
- Binding as Sets of Scopes - Flatt (2016)
- R7RS Scheme - Language specification
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.