There's a Koru Playground Now. Please Break It.

The real compiler runs in your browser via WebAssembly. The JavaScript emitter is a toddler. Both of those are on purpose.

· 6 min read

This is experimental, and we mean it more than usual. The compiler running in your browser is the real one, cross-compiled to WebAssembly — but the JavaScript emitter it drives is a few days old and understands only a slice of Koru. Lots of perfectly valid programs will fail to compile, or compile to wrong JavaScript. That is not a disclaimer we’re hiding behind. It’s the point of shipping it now.

A couple of weeks ago we argued that the web is slow because of dispatch, not compute, and that the way out isn’t to make JavaScript faster — it’s to treat JavaScript as a backend, the way Koru already treats Zig, and resolve the dispatch at compile time. Then we showed the compiler actually emitting JavaScript for a hello-world, evaluating Koru’s compile-time templates on the way down.

That was all command-line. You had to trust us. So:

Go try it

There’s a playground now. You write Koru in the left pane; the right pane shows the JavaScript the compiler emitted and the output of running it. It recompiles as you type, in single-digit milliseconds, and the output runs in your browser. No account, no server round-trip, nothing installed.

The interesting part is what’s not happening. There is no transpiler, no JavaScript reimplementation of Koru, no hand-written interpreter pretending to be the language. The thing compiling your code is the actual Koru compiler — the same parser, the same checkers, the same emitter that runs on the command line — cross-compiled to wasm32 and handed your source. When the playground gets a construct wrong, it gets it wrong the same way the real compiler does. That property is worth more to us than a playground that papers over the gaps.

The emitter is a toddler

Here is the honest shape of it. The JavaScript backend can do:

  • const { … } blocks — declared once, lowered to module-scope JS
  • print.blk { … } with {{ interpolation }}
  • if (…) | then |> … | else |> …
  • for(0..3) ! each i |> … loops — they lower to a real JS for and run (the shape-checking around the binding is looser than it should be — it’ll take a binding where it ought to insist on a discard — but the loop works)
  • plain event invocations and flows

And it falls flat on plenty:

  • chaining two block-printers (… } |> print.blk { …) only expands the first
  • print.ln(…) as an if-branch body breaks on the JavaScript target
  • and a long tail we haven’t catalogued, because you’ll find it faster than we will

We could have waited until the emitter was respectable. We didn’t, because a playground that only runs the demos that already work teaches us nothing. A playground that runs whatever you type is a dragnet.

Why “please break it” is not a figure of speech

The week before this post, almost every real bug we fixed in the JavaScript path was found by someone typing a normal-looking program into the playground and watching it fall over. A const that didn’t make it into scope. A string with a \n in it that the parser mistook for host code. print.ln in a branch that crashed the backend on the JavaScript target while compiling fine on Zig. None of these were caught by the test suite — the suite was green the whole time — because no test had ever tried them. The playground tried them, because a person did.

Every one of those became a pinned regression test the moment it was found. That loop — type something ordinary, watch it break, pin the break, fix it — is the actual product here. The emitter being a toddler isn’t a weakness in that loop; it’s the fuel.

So: open the playground, write something you’d expect to work, and when it doesn’t, tell us. There’s a feedback button right there. The more ordinary the program you break, the more useful the report.

We’ll grow the emitter up in public.