lang

a compiler-compiler for syntax you write

Compiler-compiler is literal: write a reader — one function from source text to AST — and lang wraps it around a shared compiler kernel. You provide syntax and lowering. lang provides the type system and native/wasm backends. The result is a compiler for your language.

C, Lisp, flow, Forth, and minipy ship as source you can steal, not a closed language list. The polyglot demo shows reader boundaries disappear in one wasm module — no VM, FFI, or glue.

write a reader in your browser →

the whole interface

These are the last four lines of the C reader. This is all the kernel sees:

reader c(text *u8) *u8 {
    var t *Tokenizer = tok_new(text);
    var prog *PNode = parse_c_program(t);
    return emit_program(prog);
}

When lang sees #c{ ... }, it runs this reader at compile time and splices in the AST. After that, C is just code. The kernel never needs to know C exists.

start tiny. then steal.

The reader guide starts with 20 lines. The small calculator adds a real expression grammar. Then steal the closest shipped example.

#tiny{} — starter · 20 lines

answer 42 becomes a native program. Copy this first.

#calc{} — precedence · 80 lines

A tiny recursive-descent parser for arithmetic. Grow into this second.

#c{} — imperative · 840 lines

A substantial C subset. Start here for a conventional grammar and desugaring pass.

#minilisp{} — functional · 350 lines + a 200-line runtime

Closures, lists, quote, and a boxed runtime. Start here when your language needs its own value model.

#flow{} — coroutines · 380 lines

Generators lower to algebraic effects. Start here for a focused DSL. Native-only: core wasm has no stack switching.

#forth{} — stack · 680 lines

The reader turns a compile-time data stack into expression trees. Start here when your syntax looks nothing like the AST.

#minipy{} — layout · 950 lines

Its reader reconstructs INDENT/DEDENT from token offsets. Start here for layout or custom lexing.

reader in. compiler out.

Reuse lang’s tokenizer, #parser{}, and AST builders, or replace any of them. Then wrap your reader around the kernel:

$ ./out/lang compiler mine my_reader.lang -o mine

mine is now a standalone compiler for whole files in your syntax. lang itself is built the same way: kernel + lang reader, verified to a fixed point.