The project
· a new career in a new town
How to verify that the compiler compiles itself, how language changes are reviewed and the twenty principles printed by heroes this.
How to verify that it compiles itself
The Heroes compiler is written in Heroes. Compiling it requires nested data, name tables, recursion, error handling and programs spread across files. It is a practical test of how the language handles a complex program.
The repository, the project’s archive, also contains the compiler as one generated C file. You can build it on a computer without Heroes installed. Then use the resulting compiler to translate its own source into C and compare the two files. Do it on a release, the version the project has published:
$ git checkout v0.5.0
$ clang -I runtime seed/heroes.c runtime/runtime.c -o heroes
$ ./heroes build selfhost/main.hero --emit-c -o out.c
$ cmp out.c seed/heroes.c
(cmp prints nothing: not one byte differs)
seed/heroes.c the C the repository ships: the way in
out.c what the compiler writes when it compiles itself
over twenty megabytes of C, and more than 250,000 lines of it
If the files are identical, cmp prints nothing. The comparison verifies that the compiler reproduces the C it was built from. The third command takes under two minutes on a MacBook Air. Anyone can repeat the test: the repository is open and the four commands are the whole of it.
Between two releases the generated C file is an earlier day’s compiler. It builds the current one from the Heroes source, and that one reproduces itself one step later, which the project checks on every change. heroes --version says which one you hold: its second line is a fingerprint of the source it was built from.
- 680+modules of Heroes
- 179klines, and growing
- 0TODO, FIXME or HACK in the compiler
- 220+design sittings on the record
Heroes implements every part of the compiler: reading the words and structure of the code, connecting names to declarations, checking types, creating and checking an intermediate representation of the program, managing memory, generating C, checking external libraries, reporting errors, formatting code and handling commands.
Errors found by compiling its own source
The new compiler initially rejected its own source, incorrectly reporting a structure that contained itself. Investigating this found defects in both the new compiler and its predecessor. One involved the underline in an error message: it covered the annotation that the test used to check its position.
Compiling the compiler’s source tests many language forms in one program. Several defects had the same cause: a loop jump inside a match branch skipped the instruction that updated a manually maintained index.
The final difference between the two files was the order of the lines. Some lists were already ordered in the previous compiler; the new one needed explicit sorting, as notes in the source had warned. Maps do not guarantee an order. Adding the missing sorts made the files identical.
How language changes are reviewed · five years
Changes to syntax, language rules, error messages or compiler structure are reviewed by a panel of five seats. Each seat receives different material, and four can veto a proposal. Internal compiler or runtime changes, which affect the code that supports execution, go to two of the five if they leave syntax, error messages and the specification unchanged.
- The readerReceives the specification and sample programs, without the design document or compiler source. Tests whether the language can be understood. veto
- The compiler engineerExamines the source and assesses how much code the change requires to write and maintain. veto
- The C pragmatistCompiles the C code needed to use a library from Heroes, or documents why compilation fails. veto
- The spec wardenCompares token counts before and after the change and checks that the specification stays within its limit. Tokens are the pieces of text a model reads. veto
- The historianHas to search the web and cite dates and sources: unsourced precedent is inadmissible. It has also read the author’s history of programming languages, and uses it to know where to look rather than as the source, because a book by this project’s author cannot be evidence for this project. Advises without veto power.
The panel chooses the solution it considers strongest and most complete, then submits it to the author for approval. Work continues while confirmation is pending. If there is a more cautious alternative, it is recorded so the author can consider it. After the five verdicts a sixth reader, the completeness critic, asks what nobody listed, and more than once the solution adopted was a route no seat had proposed.
Each review is recorded in a repository file, including the material examined, the verdicts and any vetoes. There are more than a hundred and fifty reviews. Every language change requires this review process. What the panel refused, and who said no, has a page of its own: what Heroes refused →
Twenty lines
heroes this prints these principles, which are included in the compiler. They are reproduced here as the command prints them.
The Zen of Heroes
1. This language begins with a tribute to David Bowie.
2. It is made for language models; people can read it too.
3. A model may have read everything; each page must still explain itself.
4. What the eye can see is all there is: sound and vision.
5. There is one way to say each thing; every program sings in the same voice.
6. That way may not be obvious at first; the Starman already knows it.
7. The semantics are familiar; the syntax is new.
8. Turn and face the strange: every strange choice here is deliberate.
9. Nothing changes behind your back; every change signs its name.
10. Every plausible mistake must become a compile error before the program runs.
11. Unless you mark the gap with ???: an honest hole beats a confident guess.
12. A compile-time error is an answer; a runtime error is an ambush.
13. An error should tell you how to fix the program. Anything less is a complaint.
14. Repeating a word is cheap; forgetting one can cost the whole program.
15. The specification stays small under pressure; every word must earn its place.
16. Crashes and leaks are bugs. Ashes to ashes: what Heroes takes, it returns.
17. When pretty code and safe code disagree, safe code wins.
18. There is no standard library; C is the man who sold the world.
19. Never crashing in the same car: what C might keep, Heroes assumes it keeps.
20. We can be heroes, just for one day.
Line 10 states the thesis, line 18 the initial constraint, line 19 what that constraint costs at the border with C, and line 17 the priority when two principles conflict. Why the language is built this way →
What is built, and what is next
The chain is one table in the repository, a row per milestone, closed first and scheduled after. This page reads it when the site is built: 71 milestones closed of 92, and the open one is M-panic-location.
What comes next
| # | milestone | what it delivers |
|---|---|---|
| 72 | M-panic-location | an abort a program raises names its file, line, column and function, an assert on any value shows both sides, and no program's text forges a report |
| 73 | M-generated-programs | programs nobody wrote: a generator that composes valid Heroes and knows the answer before the compiler is asked |
| 74 | M-compatibility-promise | the paragraph 1.0.0 rests on, and the suite that makes a broken promise red |
| 75 | M-latency-ceiling | a latency the compiler may not lose without a decision: heroes check and clang on the seed timed at every close, the incremental frontend its return condition |
| 76 | M-core-packages | small packages that compose, organised as Go's tree, in Heroes or over C |
| 77 | M-package-manager | heroes add/heroes fetch; bindings instead of a standard library |
| 78 | M-web-framework | composes the core packages, Go/Echo style: explicit routes, records, no magic |
| 79 | M-doc-generator | heroes doc, the one direction Part 6's literate-source row promises |
| 80 | M-compared-languages | the same tasks written by a model in Heroes, Go, Rust, C and Zig, scored on hidden tests, turns and silent failures, the prediction registered before the first run |
| 81 | M-deployable-binary | what the machine that RUNS a Heroes program needs, on the three platforms, and which -O a shipped artifact carries |
| 82 | M-standalone-compiler | heroes needing nothing else installed: a C compiler and a linker inside it, and what each platform's C library needs beside it |
| 83 | M-install-channels | a Homebrew tap, winget, a Nix flake, a Docker image, all built from the seed and pinned to a v* release tag; the version scheme itself was decided ahead, 2026-09-07 |
| 84 | M-online-compiler | the compiler reached without installing anything: the site's visitor writes Heroes and gets its answer |
| 85 | M-lsp-server | heroes lsp |
| 86 | M-vscode-extension | the extension, complete |
| 87 | M-publication-gate | the last gate before anything goes outward |
| 88 | M-launch-posts | the first posts, each with the numbers its command produced, after the first hour a stranger spends was tried |
| 89 | M-microcontroller-verdict | the ruling on a Heroes program running on a microcontroller under an RTOS, RISC-V first; the two facts a 32-bit build refuses today are its brief |
| 90 | M-qbe-backend | Part 7 item 15 — the proof that the IR is not C in disguise |
| 91 | M-journey-book | the journey — how this language came to be |
| 92 | M-guide-book | the guide, as a book you would find in a shop |
The 71 closed milestones, the most recent first
| # | milestone | closed | what it delivered |
|---|---|---|---|
| 71 | M-rechecked-issues | 2026-10-25 | every open issue re-checked against the tree: closed with the commit that settled it, closed with the reason it no longer applies, repaired, or re-homed on a row still to come |
| 70 | M-thesis-harness | 2026-10-24 | Part 11's metrics 2 and 4 run for the first time, as a Heroes program: forty held-out tasks sealed and a prediction pushed before the run, the primary inconclusive at +0.0 points with no discordant pair, the strict arm paying at turn 1 and level by turn 3 |
| 69 | M-witnessed-seed | 2026-10-18 | the seed witnessed by a second implementation: the archived Rust bootstrap rebuilt and agreeing with the seed at its tag, a gcc leg beside clang, every seed since the archive rebuilt from the one before it, 39 of 39, and every refresh since naming its route for the CI; then the bootstrap out of the tree, kept at its tag |
| 68 | M-readonly-lend | 2026-10-18 | a function reads the caller's place without claiming to change it: lent on a parameter, C's const T * bound read-only, and beside it an @ of the same place and a read-only lend past a variadic prototype refused |
| 67 | M-bounded-seed | 2026-10-17 | the seed bounded in shape and in rhythm: its re-anchoring #lines joined to their anchors, refreshed only when the two-stage build needs it and at each release, every program carrying the key of what it was compiled from, and kept in the history at every tag, the history rewritten once and announced |
| 66 | M-buildable-structs | 2026-10-17 | a C struct with long arrays is built by naming the fields that matter and ending with rest: zero. |
| 65 | M-typed-inspection | 2026-10-13 | a stopped program shows Heroes values: the frame lists the author's bindings alone, the emitter declaring what it invented nodebug, and one fixed formatter, editors/lldb/heroes_lldb.py, which the editor loads, shows a record, a str, a [T], a {K: V}, a T?'s live arm and a variant's case in Heroes' syntax; the names keep their C prefix, refused in the emitted C by panel 213's two vetoes and kept by the author's answer of 2026-10-14 on its issue 2037 |
| 64 | M-issue-files | 2026-10-12 | every item this project tracks is one GitHub issue: one file each of issues/ first, a card on each, then 2,029 issues imported in the order they were born with their own days, then GitHub alone and the folder out of the tree. |
| 63 | M-inferred-cell | 2026-10-10 | a mutable cell declared by its own symbol, @=, and typed by its value as a binding is; a cell nothing re-binds refused with a certain fix; the tree rewritten through the compiler's own fix. |
| 62 | M-agreed-retention | 2026-09-28 | one C function, one story about who frees what it is handed: two modules may declare it with contradictory retention marks and the compiler takes both; one declaration cannot carry a contract chosen per call, which is sqlite3_bind_text's three modes; and nothing says a pointer C made is C's to free once. |
| 61 | M-declared-extents | 2026-09-21 | how far C may read through a lent address, and what becomes of the pointer afterwards: counted_by declares the extent and the emitter checks it against the field's own sizeof in C; then the author flipped the default, so a C parameter is taken to KEEP what it is handed and a lend reaches only one declared lent; and where no word can reach — C frees the bytes from a callback, or from a later call with no pointer at all — the runtime names the leases that were live. |
| 60 | M-readable-bytes | 2026-09-18 | a C byte buffer becomes text a program can print: the inbound direction str has never had |
| 59 | M-arm-platform | 2026-09-18 | the fourth real machine: arm64 Linux, where a container runs and where char is unsigned |
| 58 | M-check-completeness | 2026-09-17 | ten defects the hunt turned up, closed — and three were not what their own entry said |
| 57 | M-marked-acquisition | 2026-09-14 | the mark goes where the obligation is CREATED — on the acquiring call — so a C handle nobody releases stops being silent |
| 56 | M-cleanup-verdict | 2026-09-14 | the ruling on a scope-bound release for a C handle: a releaser keyed on the TYPE refused to Part 6, because ownership is a property of the CALL |
| 55 | M-handle-verdict | 2026-09-14 | the ruling on telling one C handle from another, and three defects that were one question |
| 54 | M-deferral-ledger | 2026-09-13 | every Part 7 item with no milestone gets a dated verdict or a return condition, and Part 8's homeless warts with it |
| 53 | M-declared-thresholds | 2026-09-12 | every ceiling and floor says which way it may move, and something notices when it should have moved and did not |
| 52 | M-reflection-verdict | 2026-09-12 | reflection refused at run time and as a general tag; a field's name becomes a name the checker resolves |
| 51 | M-stated-grammar | 2026-09-12 | every syntactic form stated once, in Wirth's notation, inside the specification beside the prose that governs it, and heroes grammar for the half the compiler can print from its own tables |
| 50 | M-rotated-records | 2026-09-12 | the records become directories, one entry per file and one milestone per file, and the file that held each stays behind as a map so every citation still resolves |
| 49 | M-named-callbacks | 2026-09-11 | the role inversion through a generic callback, closed at 7 of 7 against a cheaper reading's 2 of 7: fold's type names its accumulator and its element, and a function handed to it names them the same way — a naming mandate the author chose over the sitting's own recommendation, with both numbers in front of them |
| 48 | M-labelled-types | 2026-09-11 | defect 026 repaired: a function type names the parameters that can be confused, the names are part of its identity, and the classic inversion through a function value goes from 17% to 100% — at 432 lines against a condition of about 150, which is the sitting's own headline |
| 47 | M-positional-values | 2026-09-11 | defect 026 narrowed: a label at a call through a function value is refused, the note stops prescribing the hole in silence, and spec § 3 says the call is positional — a sentence vetoed as false at the ballot that the refusal made true |
| 46 | M-labelled-builtins | 2026-09-11 | defect 025 repaired: the same-typed-argument rule reaches the two built-ins it did not, both halves of it, 1022 call sites relabelled, and the classic argument inversion drops from 367 survivors to 75 with the swapped fail( at zero |
| 45 | M-anchored-spec | 2026-09-11 | the specification takes the shape of a Report: thirteen numbered sections in reference order, one home per rule, the numbers as the citation anchors, two checks that keep the form, 81 citations converted, and the site's page copying the text in one click |
| 44 | M-interpolated-strings | 2026-09-09 | f"line {n}: {word}": one token kind, one AST node, three helper modules, four decided ceilings moved by their measured lines, the spec at +138 real with R6's two sentences, and heroes mutate reaching the holes |
| 43 | M-interpolation-verdict | 2026-09-09 | the ruling on design.md Part 7 item 7: string interpolation ENTERS as f"line {n}: {word}", three counts and one sitting, the spelling the author's; closed with the verdict and not the form, as it was named to |
| 42 | M-held-bytes | 2026-09-09 | §4.19's FOURTH case: x: cstr @ s.lease() is a COPY C may read until end_lease(@x). |
| 41 | M-cstr-lifetime | 2026-09-09 | a cstr may not outlive the str it was lent from: three positional clauses, 0 of 638 files moved. |
| 40 | M-closures-verdict | 2026-09-08 | the ruling on Part 7 items 1 and 12: both refused, closures to Part 6 and inline blocks left unplaced |
| 39 | M-discard-refusal | 2026-09-08 | _ = on a fallible value becomes a compile error, in two positions and not three |
| 38 | M-open-repository | 2026-09-08 | the repository opens, and every page that says it is shut stops saying so |
| 37 | M-declared-freer | 2026-09-07 | owned <C function>: the string C hands you is freed by the name its own declaration gives |
| 36 | M-thread-stacks | 2026-09-06 | the guard speaks on every thread, and a worker's floor is the thread that ran main |
| 35 | M-isolated-threads | 2026-09-06 | Part 7.13 concurrency: three of four measured corruption classes closed, and the door was in the checker |
| 34 | M-c-callbacks | 2026-09-05 | a Heroes function reaches a C callback parameter, and a foreign thread is refused by name rather than left to corrupt |
| 33 | M-corpus-depth | 2026-09-04 | the rung between a program and the compiler: nine programs, and half of every frame |
| 32 | M-robustness-guards | 2026-09-03 | the guards that shut the holes §1.12 named: @ on an immutable, the stack, the C pointer verdict, the harness scratch |
| 31 | M-documentation-site | 2026-09-02 | the site, anchored to programs that run |
| 30 | M-corpus-coverage | 2026-09-02 | every language form has a program that runs it |
| 29 | M-selfhost-nesting | 2026-09-02 | the compiler's own modules move into ten directories |
| 28 | M-package-layout | 2026-09-02 | use paths, the qualifier, as, and where a program's files live |
| 27 | M-argv-execution | 2026-08-31 | the compiler runs programs by argument list, and the shell stops being the boundary |
| 26 | M-separate-compilation | 2026-08-26 | one .c per module, prototypes across TUs, the cache |
| 25 | M-bootstrap-archive | 2026-08-19 | the third language dies |
| 24 | M-harness-port | 2026-08-18 | the net in Heroes |
| 23 | M-selfhost-fixpoint | 2026-08-18 | the fixpoint and the seed |
| 22 | M-selfhost-port | 2026-08-17 | the compiler twice over, and one hash |
| 21 | M-selfhost-probe | 2026-08-15 | the lexer ported, to measure what was missing |
| 20 | M-complete-structs | 2026-08-15 | every field form a C header can write |
| 19 | M-struct-passing | 2026-08-15 | a struct crosses by value |
| 18 | M-binding-fidelity | 2026-08-14 | what an extern accepts |
| 17 | M-program-corpus | 2026-08-13 | nine programs, six compiler defects, a CI |
| 16 | M-sized-integers | 2026-08-12 | eight widths; int stops being a word |
| 15 | M-literal-bases | 2026-08-12 | 0x 0o 0b, the _ separator |
| 14 | M-header-constants | 2026-08-12 | the number leaves the file |
| 13 | M-ffi-ladder | 2026-08-12 | Heroes calls C; SQLite with no shim |
| 12 | M-module-namespace | 2026-08-12 | a program is many files |
| 11 | M-generics-library | 2026-08-11 | sugar, heroes test, generics, the library |
| 10 | M-optional-map | 2026-08-10 | T? and {K: V} — the last two types |
| 9 | M-value-aggregates | 2026-08-10 | records and variants by value, copy-on-write |
| 8 | M-strings-ownership | 2026-08-05 | str in C, and the ownership pass |
| 7 | M-scalars-run | 2026-08-04 | the first native binary |
| 6 | M-ir-lowering | 2026-08-04 | desugaring, the three-address IR with basic blocks |
| 5 | M-typed-frontend | 2026-08-04 | the frontend complete: names, types, errors as a product |
| 4 | M-name-resolution | 2026-08-04 | scopes, no shadowing, order-free top level |
| 3 | M-syntax-tree | 2026-08-04 | parser, AST, the canonical formatter |
| 2 | M-token-stream | 2026-08-04 | the lexer, and --dump-tokens |
| 1 | M-day-zero | 2026-08-03 | git, workspace, four hand-written C targets |
Next: four commands build it yourself, or take the whole language in one file.