GENO

Blog/Tooling

No. 006

The playground runs the real compiler

David IachOctober 14, 20262 min read

Abstract

Most language playgrounds send your code to a server. Geno's runs the actual CLI inside your browser tab, with Python compiled to WebAssembly and a 600 KB bundle of the toolchain.

The playground on this site does not imitate Geno. It loads CPython compiled to WebAssembly, unpacks the same geno package you get from pip install, and runs the real CLI against your code. Nothing leaves your browser.

Why not a server?

A hosted sandbox is the usual answer, and Geno has one (geno serve). For a playground it brings costs we did not want: a service to operate, rate limits to tune, and a privacy question for every keystroke. It also tends to drift from the release people actually install.

Geno's toolchain is plain Python with no compiled extensions. That makes it a good fit for Pyodide, which runs CPython in WebAssembly. The whole geno package, minus its tests, compresses to about 600 KB.

How it works

  1. On your first run, a Web Worker loads Pyodide (about 13 MB, cached by your browser after that).
  2. The worker fetches the Geno bundle and adds it to Python's import path.
  3. Each button writes your editor contents to Main.geno in an in-memory file system and calls the CLI's own main() with the same arguments you would type.
$ geno test -v Main.geno
  FAIL  Main.geno (3/4) 28ms
        x score_label: expected "passing", got "needs work"

3 passed, 1 failed, in 0.03s

That output is not formatted by the website. It is what geno test prints.

Where the browser differs

Run uses the direct interpreter. The default geno run lane starts a sandboxed child process, and a browser tab cannot spawn processes. The playground uses geno run --unsafe --cap …, which applies exactly the capabilities you tick. With none ticked it switches to the fail-closed --json lane, which grants nothing.

Files are imaginary. Grant fs and your program can read and write, but only inside the worker's in-memory file system.

Runaway programs are stopped twice. Geno's own step and time limits apply as they do on the command line. If something still runs too long, the page discards the worker and starts a fresh one.

The open-blocks indicator

Under the editor you will see which blocks are open at your cursor, such as if › for › func. Once the runtime has loaded, that comes from geno.constraints, the module behind geno constrain. It is the same information a model gets when Geno is used for constrained decoding: at every point in a program, what still needs closing.

Sharing

The share button puts your program in the page address after a #, so the code is never sent to a server. Paste the link and the playground opens with your program in it.