Candor.

Candor compiles Candor

selfhost/candorc.cnd is a complete Candor compiler written in Candor: a lexer, a parser, a checker with every diagnostic, and a C backend. It builds itself, and the result builds itself again, identically.

The bootstrap

StageWhat happens
0The JavaScript reference compiler, the seed.
1Stage 0 compiles candorc.cnd into a native candorc1.
2candorc1 compiles candorc.cnd, producing stage2.c, which builds candorc2.
3, 4candorc2 and then candorc3 each compile candorc.cnd again.

The C from stages 2, 3 and 4 must be byte-identical. That is the fixed point: the compiler carries itself, and successive generations stop changing. A compiled compiler then builds itself in about 10 ms.

How it's checked

selfhost/test.sh          # stage 1, comparisons, bootstrap
FULL=1 selfhost/test.sh   # ...and every benchmark too
  1. Behavior: every program in tests/run, examples and bench gives identical stdout, stderr and exit code when compiled by candorc and when run by the reference compiler.
  2. Diagnostics: every file in tests/errors gets word-for-word identical errors, hints and caret positions from both compilers. One file alone produces 46 different errors.
  3. Fixed point: stages 2, 3 and 4 match byte for byte.

Every language change goes into both compilers, and the suite keeps them in agreement. So the language can now evolve using itself.

Field notes

The point of self-hosting was to learn whether the language holds up for a real program. The full notes are in selfhost/NOTES.md. The short version:

What worked

What hurt, and what changed because of it

Pain pointStatus
Enums couldn't be compared, so the AST used strings for its kindsFixed: == and show on payload-free enums. The AST now uses NodeKind.
No constants, so type ids were magic numbersFixed: top-level constants (T_INT, …).
No array popFixed: pop(xs).
No modules: six files joined with catFixed in v0.3: candorc is six real modules, and errors cite lexer.cnd:46.
No way to move a large value: loading each module copies the AST arena in and outMeasured: peak memory went from 36 to 54 MB. Addressed by v0.6 (copy-on-write)
No error propagation: a failed flag threaded everywherePlanned: generics with Result[T, E]
Type ids are plain Ints, interchangeable with node idsPlanned: distinct types
No closuresOpen question

An honest caveat. candorc was a port of a design first written in JavaScript, which is easier than designing a compiler from scratch. The clean first runs say more about the porting than about the language. The pain points are the more trustworthy signal.