An experiment by Krishna Paul, with the language designed and built by Claude
A programming language designed by a language model, for language models.
Candor's users are models. Its rules are designed so a model writes correct code from a single spec, so the compiler can tell it exactly what to fix, and so the human reviewing the result can trust what they read.
A taste
type Shape = enum
Circle(radius: Float)
Rect(w: Float, h: Float)
end type Shape
# No `effects` clause: this function is provably pure.
fn area(s: Shape) -> Float
ensures result >= 0.0
example area(Rect(2.0, 3.0)) == 6.0
match s
case Circle(r)
return 3.141592653589793 * r * r
case Rect(w, h)
return w * h
end match
end fn area
fn convert(amount: Float) -> Float
let rate: Float = ?exchange_rate # a typed hole: honest "I don't know"
return amount * rate
end fn convert
fn main()
effects io # printing must be declared
print(show(area(Circle(1.0))))
end fn main
Designed around failure modes
| How LLM-written code goes wrong | What Candor does about it |
|---|---|
| Hidden side effects far from the call site | effects io, fs, time in the signature, checked through every call. No clause means the function is pure. |
| Making up a plausible API when unsure | Typed holes (?exchange_rate) type-check, list what's in scope, trap if reached, and block build. |
| Code that looks right but isn't | requires / ensures contracts checked at runtime, plus example clauses run by candor test. |
| Losing track of nesting | Labeled ends: end fn area, end if. A mismatch points at the line that opened the block. |
| Silent coercion, truthiness, overflow | No implicit conversions. Overflow, out-of-bounds indexes and division by zero trap with the function, file and line. |
| "Where does this name come from?" | use "lexer.cnd". A file can name only what it, or a file it directly uses, declares. The error says which use to add. |
| Aliasing and mutation at a distance | Value semantics. A callee can change your variable only through inout, and the call site has to say inout too. |
| Off-by-one loops | Explicit ranges: 0 ..< n (exclusive) vs 1 ..= n (inclusive). |
| Mixing up ids and plain numbers | type UserId = distinct Int. Passing an OrderId where a UserId belongs is a compile error, and the hint gives the fix. |
| Name confusion | No shadowing, globally unique enum variants, "did you mean" suggestions, --json diagnostics. |
Quick start
git clone https://github.com/krishnapaul242/candor && cd candor
node src/cli.js run examples/tour.cnd # instant: compiles in memory to JS
node src/cli.js run examples/tour.cnd --native # clang -O2, then run
node src/cli.js test examples/tour.cnd # run every `example` clause
node src/cli.js check examples/tour.cnd --json # machine-readable diagnostics
node src/cli.js build examples/tour.cnd -o tour # native binary
selfhost/test.sh # bootstrap the self-hosted compiler
What's in the box
Reference compiler
About 2,000 lines of dependency-free JavaScript: lexer, parser, checker, and two backends held to byte-identical behavior.
Self-hosted compiler
candorc, about 4,200 lines of Candor in six modules, with every diagnostic word for word. It bootstraps to a fixed point.
Differential tests
Every program runs through every compiler path, and stdout, stderr and exit codes must match exactly.
Field notes
An honest record of what hurt while writing a compiler in the language, which drives the roadmap.
Status: experimental (v0.4). Native builds never free memory, and there are no generics yet. See the roadmap for what comes next and why.