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.
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 JSprint.blk { … }with{{ interpolation }}if (…) | then |> … | else |> …for(0..3) ! each i |> …loops — they lower to a real JSforand 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 anif-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.