30 September 2024
Rost: a typed programming language built in Rust
Rost: a typed programming language built in Rust
Rost is a statically typed programming language I've built in Rust. It has generics, type checking, error messages that point to the exact code, and a language server for VS Code. The compiler outputs Python or WebAssembly, and you can try it in your browser. The name is Swedish for rust.
I've made a lot of programming languages from scratch, and Rost is the latest one.
Where it started
My oldest GitHub repositories are small C++ games from 9th grade, like Snake and Pong. In 2015, I started building Third, a 2D platformer engine in C++ and SDL with a built-in map editor.
In October 2015, after a few experiments with Lua bindings, I added Lua scripting to Third, so things like movement, projectiles, chat, and spells could be written as Lua scripts. That's when I got interested in making languages myself. Six days later, I started DevilScript, my first language on GitHub:
a = 5
b = 10
c = a + b
ptc! This is a line that is printed
More followed:
- StBernandScript (2016, C++): variables, strings, constants, and arithmetic with parentheses.
- Rust_interpreter (2018, Rust): an expression-based language compiled to bytecode for a small virtual machine.
- rust-lang-2 (2020, Rust): functions as values and lists, compiled to instructions for a virtual machine.
Rost
I started Rost in August 2022. The first version compiled straight to x86-64 assembly for NASM. Within ten days it had arithmetic, type inference, if statements, while loops, and colored error messages, and over the next year it got functions, pointers, structs, and arrays.
In September 2024, I rewrote the compiler around an intermediate representation, added generics, and added two new backends: one that generates Python and one that generates WebAssembly. Here's a generic function, compiled with the Python backend:
__builtin fn printf(format: str, ...args: any);
fn add_two_things<T>(format: str, a: T, b: T) {
let c: T = a + b;
printf(format, a, b, c);
}
fn main() {
add_two_things("%i + %i = %i\n", 1, 5);
add_two_things("%s + %s = %s\n", "abc", "def");
}
1 + 5 = 6
abc + def = abcdef
__builtin declares a function that the backend provides. In the Python backend, printf uses Python's string formatting.
The rewrite doesn't support everything the first version did yet: loops, if statements, and structs are still on the roadmap.
Error messages and editor support
Rost points to every piece of code involved in an error. Calling a function with the wrong type gives:
--> input.ro:4:10, input.ro:1:12 => CompilerError
1 | fn my_func(in: int) {}
| ^^
| └─ Expected int in declaration
2 |
3 | fn main() {
4 | my_func("abc");
| ^^^^^
| └─ Wrong type in function call: str
5 | }
I also wrote a language server for Rost. With its VS Code extension, the same errors show up as you type, and you can hover over code to see types and jump to definitions.
From Rost to my master's thesis
Making my own languages is what led me to my master's thesis at KTH. Editor support like this makes a language much easier to use, but it takes a lot of work to build. With tools like Langium or Xtext, a language that already has a compiler typically has to implement its semantics a second time to get it.
In Semi-Automatic LSP Server Generation for Domain-Specific Languages, I proposed reusing what the compiler already knows instead: metadata attached during compilation provides language server features such as go-to definition and hover information. I demonstrated the approach in the Miking language workbench and evaluated it on two domain-specific languages, MLang and ProbTime. The results show that language support can be derived semi-automatically with minimal manual work.
Try it
The Rost demo compiles your code to WebAssembly and runs it in the browser. The source code is on GitHub.