Skip to content

Hello! 👋 My name is Nathan Weir. This is a fun personal project for using AI to build a bespoke, domain-specific programming language. It is not a serious, professional project. This site and the language itself are largely generated via Claude Code. If you find yourself programming with Weir, have fun - but use at your own risk!

Compilation Pipeline

Weir’s compilation pipeline transforms source code through several stages, from text to native machine code. The pipeline is strictly linear — each stage has clear inputs and outputs.

Tokenizes source text into a stream of tokens: parentheses, brackets, braces, atoms (identifiers, numbers, strings, keywords), and comments.

Transforms the token stream into an AST (abstract syntax tree). The parser recognizes:

  • Top-level items: defn, deftype, defstruct, defclass, instance, defmacro, extern "C", import
  • Expressions: literals, function calls, let, if, cond, match, when, unless, fn, do, set!, unsafe
  • Patterns: constructor patterns, struct destructuring, wildcards, literals
  • Type expressions: named types, function types, type applications, type variables

Expands macro invocations by rewriting S-expressions. Macros are purely syntactic — they transform S-expressions into S-expressions without type knowledge:

parse → macro expand → type check → compile

This keeps the cascade model clean: re-expansion feeds into re-typechecking, which feeds into recompilation.

Performs static type checking with:

  • Local type inference — explicit function signatures, inferred locals
  • Algebraic data type checking — constructors, exhaustive pattern matching
  • Typeclass resolution — instance lookup, constraint checking
  • Struct field validation — construction arguments, field access types
  • Unsafe enforcement — extern function calls require unsafe blocks

The type checker produces a TypeCheckResult containing resolved types for every expression, which is consumed by codegen.

For weir run and weir dev. Compiles each function to native code using Cranelift:

  1. Translate typed AST to Cranelift IR (intermediate representation)
  2. Cranelift optimizes and generates machine code
  3. Code is loaded into executable memory
  4. Functions are dispatched via an indirect function table

Cranelift prioritizes compilation speed over maximum optimization — ideal for dev-mode live reloading.

For weir build. Compiles the entire program ahead-of-time:

  1. Generate Cranelift IR for all functions
  2. Emit object file (.o)
  3. Link with the C runtime and any native libraries via cc
  4. Produce a standalone binary

For weir interp. A tree-walking interpreter that evaluates the AST directly. Slower but useful for debugging and testing.

ModeCommandSpeedUse Case
JITweir runFast compile, fast runDevelopment, testing
AOTweir buildSlow compile, fastest runRelease binaries
Interpreterweir interpNo compile, slow runDebugging, testing
Devweir devJIT + file watchingLive reloading

The compiler is implemented in Rust across several crates:

CrateRole
weir-lexerTokenizer
weir-parserS-expression parser → AST
weir-astAST types, pretty printer, TCO analysis
weir-macrosMacro expander
weir-typeckType checker
weir-codegenCranelift JIT/AOT backend, dev mode
weir-interpTree-walking interpreter
weir-runtimeGC, arenas, runtime support

Self-recursive calls in tail position are compiled as loops in the Cranelift backend and trampolined in the interpreter. This means deep recursion (1M+ depth) works without stack overflow.

A tail-position analysis pass (weir-ast/src/tco.rs) identifies which call expressions can be optimized. The codegen transforms these into jumps to a loop header block rather than actual function calls.