Vibe

A programming language where humans and AI reason together about programs.

R7RS Scheme · LLVM bitcode · fully self-hosted

The Problem with “Vibe Coding”

The problem with vibe coding isn’t the vibe — it’s the code.

When you ask a model to generate code in a language that resists clean abstraction, you get code that resists understanding. Not because the model failed, but because the language offers no tools for transparency. The generated code is opaque by nature: you can read it, but you cannot reason about it without re-deriving every implicit assumption the language makes.

Scheme is different. Its macro system makes program structure explicit — any expression can be mechanically expanded to a small set of well-understood primitives. There is no hidden compiler magic, no implicit transformation you have to take on faith. Vibe coding with Scheme is a more powerful experience because the language itself is designed for the kind of transparency that human–AI collaboration demands.

Two Goals

R7RS Small Scheme via macro expansion. Every language feature that can be a macro must be a macro. Only an irreducible set of primitive forms (define, lambda, if, set!, quote, define-syntax) are implemented natively. Everything else expands to those primitives through well-defined, inspectable transformations.

Binding registry with human-language descriptions. Every binding in a Vibe program — every function, variable, macro, and type — is paired with a plain-language description of what it does. An LLM introspects a program by combining macro expansion (to see structure) with registry lookup (to see meaning). No “trust the code” — understand the code.

Together, these goals close the loop: macros make programs structurally transparent; the registry makes them semantically transparent. Structure all the way down, meaning all the way up.

How It Works

  User Programs
  ─────────────────────────────────────
  Standard Library (R7RS 6.x)
  ─────────────────────────────────────
  Derived Forms via Macros
  let, cond, and, or, begin, do, ...
  ─────────────────────────────────────
  Primitive Forms
  define, lambda, if, set!, quote
  ─────────────────────────────────────
  Kernel DSL (llvm:*)
  llvm:define-function, llvm:call, ...
  ─────────────────────────────────────
  LLVM Bitcode
  ─────────────────────────────────────
  Native Machine Code

Each layer is built entirely from the layer below it. The kernel DSL is the trusted core — the only code that must be understood by reading its implementation rather than its expansion. Everything above is transparent by construction.

Current Status

Self-hosted compiler Done Macro system (unhygienic) Partial Kernel rewrite using macros Partial R7RS primitive forms — Milestone 1 Done NaN-boxed runtime Done Optimizing pipeline Done Closures & first-class procedures — Milestone 2 Partial Macro system (hygienic) Planned R7RS derived forms Not started Standard library Not started Binding registry Not started

Scheme today: define (variables and procedures, with docstrings, mutual recursion and forward references), if, set!, quote (interned symbols, cached list literals), and calls to runtime builtins — enough to compile (define (fib n) …) (exit (fib 10)). These are a lowering pass, not a second language tier: Scheme S-expressions are rewritten into llvm:* S-expressions in the same representation, and --emit-lowered prints exactly what the code generator consumes. There are no local variables yet — let and friends reduce to lambda, which is Milestone 2.

Performance: the compiler links the runtime's IR into your module and runs the LLVM pass pipeline (-O2 by default), so the explicit boxing that makes lowered output readable costs nothing in the final binary. Naive fib(36) runs in 0.03s against C -O2's 0.02s on the same machine — down from 0.39s before the pipeline existed. That comparison flatters Vibe while Milestone 1 arithmetic skips type checks; a real numeric tower will reopen part of the gap.

Macro expander today: syntax-rules with optional literals, multiple clauses, top-level install-after-expand (e.g. prelude define-vibe-syntax), nested list subpatterns, and final ... (ellipsis) in patterns and templates. The kernel prepends shared helpers in macros.vibe: AST access (vibe:ast-ref / vibe:ast-addr), list construction (vibe:ast-list), null/empty predicates, vibe:node-kind? (including quote wrappers), lexer token and character literals (vibe:token-lit, vibe:char-eq?, vibe:char-digit?), and C-style i32 truthiness helpers (vibe:i32-one?, etc.). Large codegen dispatch is still mostly explicit; further line-count wins now need language features rather than macros — the kernel's 247 hand-encoded byte-array constants, for instance, were removed by teaching llvm:define-constant to take a string literal, something syntax-rules cannot do. Bootstrap seeds are versioned on GitHub Releases; the default seed matches the expander capabilities needed for current main. Full hygiene and non-final ellipsis patterns are still planned. Hygiene is scheduled before the derived-form layer, since let, do and cond's => are exactly the binding macros it protects (see design docs).

Resources