A transpiler toolkit with a trust layer

Grammar in,
compiler out.

Write a grammar and racklr generates the front end. Annotate what your code may do and it checks the contract when it compiles, with a runtime guard as backstop. One shared tree, and a boundary you can read.

For people writing compilers, and for teams running generated code.

Python in
def greet(name):
    return "hello " + name
↓
Shared tree (UIR)
uir-fn(name=greet, params=[name],
  body=call(+, "hello ", name))
↓
JavaScript out
function greet(name) {
  return "hello " + name;
}

Python in, JavaScript out. The same uir-fn sits in the middle.

From framework to static files

Next.js / React in
export default function Counter() {
  const [n, setN] = useState(0);
  useEffect(() => {
    document.title = "count " + n;
  }, [n]);
  return <button onClick={() => setN(n + 1)}>{n}</button>;
}
↓
Shared tree (UIR)
uir-fn(Counter, [])
  state: n <- useState(0)
  effect: set document.title
  body: uir-element(button, { onClick }, n)
↓
Vanilla HTML + JS out
<button id="count">0</button>
<script>
  let n = 0;
  const btn = document.getElementById("count");
  btn.onclick = () => {
    n += 1;
    btn.textContent = n;
    document.title = "count " + n;
  };
</script>

Hooks, state, and effects become plain DOM listeners and variables.

Effect rows

Side effects, type-checked.

Generated code is a black box: you can read what it says, but not what it will do. An effect row puts a readable boundary around it. Annotate a function with the capabilities it may use, and the compiler checks that its calls stay inside the row at compile time, with a runtime guard as a backstop.

;; this function may call the LLM, at most 5 times
^{:effects {:llm {:max 5}}}
(defn summarize [q] (llm/query q))
;; declared with no effects, then it calls the LLM
^{:effects {}}
(defn summarize [q] (llm/query q))
;; error: llm/query exceeds the declared effect row
Formal verification

Proves properties outright, and often costs more than teams can justify.

Ad hoc specs

Cheap and everywhere, but they stop at the surface and drift from the code.

Effect rows

The middle ground. A checked boundary: what it may do, how often, in what scope.

The box stays dark. The boundary is what you read and verify.

The foundation

One tree, every language.

Every front end lowers to the same 40+ node types. A Clojure function and a Python function are different on the surface and identical underneath. Adding a language is two small passes, not a rewrite.

Grammar → parser

A working lexer and parser module, generated straight from the spec.

40+ nodes

The shared intermediate representation every language meets in the middle.

6 front ends

Parsing, lowering, and emitting through UIR today.

Who it's for

Two jobs, one tool.

If you build compilers, a grammar becomes a working front end instead of a hand-written parser. If you run generated code, an effect row becomes a readable boundary on what that code may do. The two meet on the shared tree.

What's next

Six front ends today. More when you join in.

Six languages parse, lower, and emit through UIR today: JSON, Python, JavaScript, TypeScript, TSX / React, and Clojure. Early days. The foundation and the effect layer are the pieces that exist now; the rest is open ground.

If one of these is close to your heart, or your favorite language is missing, the door is open. A front end is a grammar, a lowering pass, and an emitter. Everything else is already shared. Contribute on GitHub.

Try it

A library, not a service.

It's Racket. Install it, then run the suites or wire a grammar through the pipeline yourself.

git clone https://github.com/racklr-dev/racklr
cd racklr
raco pkg install --link .      # link the package
raco test racklr-test/*.rkt   # run the suites

Why Racket? Macros and a parser ecosystem made it feasible to treat a grammar as data and generate a parser from it. That generation is the lever the rest of the toolchain rests on.