How it works.

Four stages, one pipeline. Give racklr a grammar and it hands back a front end. Each stage is one section below.

01 Grammar a description of the language, written in ANTLR4
02 Generated parser a lexer and parser built from that description
03 Shared tree source lowered to one intermediate representation
04 Target code text emitted back out in another language

The core idea

A compiler is a shape, not a language.

Every compiler does the same four things: read source, understand it, transform it, and print it back out. racklr keeps reading and writing language-specific, generates the parser from a grammar, and shares the tree in the middle, along with everything that operates on it.

The consequence is that adding a language stops being a big project. You write down what the language looks like, you describe how its ideas map to the shared tree, and you describe how to print the tree back out. The rest of the pipeline already exists.

Generation

Write the spec, get the parser.

You do not hand-write a lexer or a parser. You write a grammar, which is a description of what the language looks like, and racklr generates the parser from it.

The generated parser does what a hand-written one would: it turns source text into a tree, and it handles the fiddly parts for you. Modes for lexing, recursive structures that would trip up a naive parser, and indentation-sensitive languages with the right lexer support.

// Lisp.g4
grammar Lisp;

form   : list_ | vector | atom ;
list_  : '(' form* ')' ;
vector : '[' form* ']' ;
atom   : SYMBOL | NUMBER ;

// the lexer rules
SYMBOL : [a-zA-Z+*/=<>!?-]+ ;
NUMBER : [0-9]+ ;
WS     : [ \t\r\n]+ -> skip ;

The shared tree

One tree, many languages.

The parser hands you a tree in the shape of the source language. racklr lowers that into a single shared tree, the universal intermediate representation, and every language agrees on what lives in it.

The shared tree has a small set of shapes that cover what programs actually do. A function, a call, a branch, a loop, an assignment. A Clojure function and a Python function are different on the surface and identical underneath. Once something is in the shared tree, every language that can read it becomes a target.

;; Clojure
(defn greet [name] (str "hello " name))

;; Python
def greet(name):
    return "hello " + name

;; both become the same shape
uir-fn(name=greet, params=[name],
  body=call(+, "hello ", name))

Adding a language

Three small pieces.

Everything above is shared. A new language only brings its own three pieces.

A grammar

The description of the language, which generates its parser.

A lowering pass

How the source tree maps onto the shared tree.

An emitter

How the shared tree prints back out as text.

Once those three exist, the language reaches every other language through the shared tree. That is the whole bet: do the transformation once, on the shared tree, and let every language meet there.

The contract

Side effects get a type system.

An effect row says what a function is allowed to do. It names the capabilities the function may use, and it says how much and within what scope.

;; this function may call the LLM, at most 5 times
^{:effects {:llm {:max 5}}}
(defn summarize [q] (llm/query q))

Inference walks the call graph and composes those rows. A function that calls something with effects inherits them, and the join has to stay inside the caller's own row. If a call oversteps, inference rejects it at compile time. Where the call graph is not statically resolvable, racklr emits a generated guard wrapper that validates the row before the function runs. The contract is checked at the boundary, not guessed at in review.

That places effect rows between the two familiar extremes. Formal verification proves properties outright, at a cost that keeps it rare. Ad hoc specs are cheap but drift from the code. An effect row sits in the middle: it does not try to read the code, it states what the code may do, and it enforces that at the seam.

Ready to try it? The front page has the install and run commands. Back to the landing page.