T27.AI

Blog

A terminal for t27's seven backends, inside an X post

2026-10-04 · 6 min read

[one spec, hello_world; only JavaScript runs in the browser; the native results are one macOS machine on one day; nothing posted on X] The t27 player now has a terminal: the compiler, as WebAssembly in the page, prints what each of its seven backends emits and runs the spec's tests. Each backend has its own share page. Every word the terminal says lives in the spec. Run natively, hello_world as committed builds in two of the seven languages, and the post says why.

Three-panel engraved illustration for: A terminal for t27's seven backends, inside an X post
View the complete triptych at full size
#t27#Compiler#Terminal

The t27 player, the page that compiles a spec inside an X post, now has a terminal. Type t27c gen rust and the t27 compiler, running as WebAssembly in the page, prints the Rust it emits for the spec in the editor. t27c test runs the spec's own tests. Each of the compiler's seven backends has its own share page, so a link opens the terminal already typing the command for that language. Nothing is sent anywhere: every line is printed by the compiler in the page, or by the JavaScript it emitted.

t27 play · the terminal's own gates

The generator rebuilds the player from its spec and checks the spec's 14 tests (43 asserts), then 21 tests run every terminal command on hello_world: 21 pass, 0 fail. 71.3 s real, about 13 s shown; the prompt and the typing are staged, every byte printed is real. Open the recording on its own page.

Seven pages, one per backend

Each page lives at t27.ai/play/hello-world/<backend>/ and has its own preview card, so a post shows which language the link opens. Opened with autoplay, the terminal types t27c gen <backend> at 45 ms a character and runs it. The longest of the seven, t27c gen verilog_hir, is 20 characters, or 0.9 s of typing. The generator refuses a demo longer than 32 characters and checks that 32 characters at that pace stay inside the ten seconds X allows anything in a player card to start by itself (AUTOPLAY_LIMIT_MS in the spec).

BackendPageFileWhat run does
Zig/zig/hello_world.zigprints zig test hello_world.zig
C/c/hello_world.cprints cc -std=c11 -c hello_world.c
Rust/rust/hello_world.rsprints rustc --crate-type lib --edition 2021 hello_world.rs
Verilog/verilog/HelloWorld.vprints iverilog -g2012 -o /dev/null HelloWorld.v
Verilog (HIR)/verilog-hir/HelloWorld.hir.vprints iverilog -g2012 -o /dev/null HelloWorld.hir.v
JavaScript/js/hello_world.jsimports it into the page and prints its 7 exports; calls nothing
TypeScript/ts/hello_world.tsprints tsc --noEmit --strict hello_world.ts

Only JavaScript runs in a browser. For the other six backends, run says the page has no such toolchain, prints save <file> and the command for a machine that has one, and claims no result. The terminal also takes t27c gen-rust, the real t27c's spelling. gcc, cargo or yosys gets an answer saying the tool is not in the browser, instead of "command not found".

Every word is in the spec

Everything the terminal says or knows is a constant in specs/x/player.t27:

The JavaScript that runs it, public/play/shell.js, holds no English of its own: it fills {names} into the spec's templates and lays out columns. The generator enforces this in both directions. It refuses:

pub const BACKENDS : [7]str = ["zig", "c", "rust", "verilog", "verilog_hir", "js", "ts"];
; The backends whose output this page runs itself.
pub const RUNS_IN_BROWSER : [1]str = ["js"];
pub const SAY_WARN_NOT_HERE : str = "{lang} does not run in a browser: this page has no {tool}. On a machine that has it:";

Why the logic is still JavaScript

The words are t27, and the behaviour is not yet: dispatching a line, filling a template, choosing a file name. That part is 361 hand-written lines, for a measured reason. gen-js lowers declarations only, by design. The issue that created it, t27#4471, says: "The emitted module is data. Nothing in it may run." That rule is right for a spec that describes a server. For a spec that is a terminal, it means the behaviour has to live beside the spec. t27#6101 asks for an opt-in lowering of function bodies that leaves today's output byte for byte unchanged. Once it exists, the dispatch and the templates move into player.t27, and shell.js shrinks to DOM glue.

We ran the six commands ourselves

A terminal that hands out a file and a command should know what the command does. We ran each one on the files the terminal gives for hello_world.t27, as committed, on one macOS machine:

BackendCommandExitFirst error
Zigzig test1hello_world.zig:33:23: error: use of undeclared identifier 'cast'
Ccc -std=c11 -c1hello_world.c:57:22: error: call to undeclared function 'cast'
Rustrustc --crate-type lib1error[E0425]: cannot find function cast in this scope
Verilogiverilog -g20122HelloWorld.v:79: error: No function named cast found in this context
Verilog (HIR)iverilog -g20122HelloWorld.hir.v:15: syntax error (the port config is not escaped)
JavaScriptnode0none
TypeScripttsc --noEmit --strict0none

Line 47 of the spec computes an area with cast(config.width) * cast(config.height), and nothing declares cast. The typechecker accepts the call. The page's own test evaluator treats it as a conversion, so in the browser t27c test reports 4 pass, 0 fail, 0 skip. Zig, C and Rust refuse the same line. JavaScript and TypeScript pass only because they emit declarations, not the function. With the line rewritten as (config.width as u16) * (config.height as u16), six of the seven pass: zig test reports "All 2 tests passed", and the HIR Verilog still fails on its config port. Both are filed, as t27#6100 and t27#5966. The terminal does not hide this: it prints the command and never claims the result.

A page git would have dropped

The repository ignores every directory called zig/, for downloaded Zig toolchains. That rule also matched public/play/hello-world/zig/. The build would have passed, the page would have existed on the machine that made it, and the published site would have answered 404 for the Zig link. One negation line in .gitignore fixes it. A test now asks git check-ignore about every backend page, with a control path under the same rule that must still be ignored.

Before it goes on X

X's player cards may not collect data entry. The terminal takes typed commands, though nothing leaves the page. Whether a terminal page is posted as a player card or only as a link is the owner's decision. Nothing has been posted.

Try it

What this does not settle

Receipts

Work with me

Need an FPGA/RTL problem taken to measured hardware?

I work contract and part-time on hardware-AI, FPGA/RTL and ML systems — from specification and open toolchains to reproducible measurements.