Skip to content

aalpar/wile

Repository files navigation

Wile

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.

Overview

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.

Background

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.

Why Another Scheme Implementation?

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.

Use of AI

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.

Features

  • 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-rules with 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, or defined using define-syntax

Build

# Build everything
make

# Build from go directory
cd go && make build

# Run tests
cd go && make test

Usage

# Start REPL
./go/cmd/scheme

# Run a Scheme file
./go/cmd/scheme -file example.scm

Example

;; 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))
;; => 3

Architecture

Source → Tokenizer → Parser → Expander → Compiler → VM
  1. Tokenizer - Lexical analysis
  2. Parser - Builds syntax tree with source information
  3. Expander - Macro expansion using syntax-rules transformers
  4. Compiler - Generates bytecode operations
  5. VM - Executes bytecode with stack-based evaluation

Key Components

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

Hygiene Model

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 tmp

Documentation

  • INTRO.md - Development guide and architecture overview
  • PRIMITIVES.md - Complete reference of supported types and primitives
  • go/DESIGN.md - Detailed macro system design
  • BIBLIOGRAPHY.md - Academic references
  • go/TODO.md - Implementation status and pending tasks

References

License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

About

Embeddable R7RS compliant Scheme implementation in Golang

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •  

Languages