AtlasLang
Build the whole thing, one stage at a time, from `print(1 + 2)` to a typed language with a bytecode VM, an SSA optimizer and a language server.
From `print(1 + 2);` to a typed language with a bytecode VM, an SSA optimizer and a register allocator — twelve representations, all of them produced by a compiler in this repository that you can type into.
Characters to tokens by maximal munch, with a half-open byte range on every token — and one hazard, `123abc`, that our lexer reports instead of silently splitting into two tokens and producing a parse error three lines away.
Recursive descent for statements and Pratt parsing for expressions, in one file, so you can read the two techniques next to each other — plus panic-mode recovery that synchronizes on `;` and statement keywords instead of stopping at the first error.
The shortest path from a parsed program to a running one is to walk the tree and evaluate as you go. It is where most languages start, it is the version whose correctness is easiest to argue, and it is the version AtlasLang deliberately did not ship — for reasons worth knowing.
An inner `let x` must not disturb an outer one. Our lowering keys storage slots by the resolved symbol rather than by the source name — because an earlier version keyed them by name, and the outer `x` was silently overwritten.
`int`, `bool`, `str`; annotations optional on `let` and inferred from the initializer, required on parameters and returns. And a definite-return analysis so conservative it rejects `while (true) { return 1; }` — which is the cleanest example of soundness without completeness you will find.
Twenty-three opcodes and an operand stack. A three-address instruction `%d = a op b` becomes "push a, push b, op", and every virtual register becomes a numbered local slot — which is the whole translation, and the whole argument for having had an IR first.
Eight transformations over SSA, run to a fixed point, each carrying its legality precondition as data rather than as a comment. Two predicates do all the safety work: a `print` is never removed, and `x / 0` is never folded.
A working compiler is the smaller half. Once people write programs in your language they need diagnostics that point at the mistake, a formatter that ends the argument, highlighting that is right about their code, and eventually a language server — because everything they use is going to want one.