Three months ago I had never written a single line of C. Today I have a working statically typed programming language with its own bytecode compiler, its own virtual machine, and its own hand rolled garbage collector, and not one line of it comes from a library, a framework, or someone else's runtime. Everything from the scanner that reads your source code to the memory manager that cleans up after your program runs was built by me, from nothing.
Meet Oli-Nat.
What Even Is This
Oli-Nat is a full programming language pipeline written entirely in C. Source code goes in one end, and it comes out the other as executable behavior, with every single stage of that journey hand built along the way. Here's what that pipeline actually looks like.
Source code first passes through a lazy, token at a time scanner that recognizes keywords through a trie style switch statement rather than a slow string comparison chain. From there it hits a two pass Pratt parser, where the first pass silently scans ahead to register every function and class signature in the program, so that by the time the second pass runs the actual compiler, mutual recursion and forward references just work with zero extra effort from the programmer. That second pass builds a full abstract syntax tree while simultaneously running a static type checker that rejects invalid programs before they ever get the chance to run. Once a program passes that gauntlet, it gets compiled into custom bytecode and handed off to a stack based virtual machine that executes it instruction by instruction, closures, classes, inheritance, and all.
And underneath all of that sits a tri color mark and sweep garbage collector that I designed and implemented myself, tracing live objects from the value stack, the call frame stack, open upvalues, and the global variable table, so memory gets reclaimed automatically without ever leaking or crashing your program out from under you.
Why Would Anyone Do This to Themselves
Because reading about how compilers work and actually building one are two completely different experiences. You can read a hundred articles explaining what a garbage collector does, and none of them will teach you what I learned the first time my own GC collected an object that was still technically reachable and I had to figure out exactly which root I forgot to trace. That kind of lesson only comes from building the real thing and watching it break in your face.
This started as a toy project, something small just to prove to myself I could get a basic interpreter working at all. It did not stay small for long.
A Taste of the Syntax
pullf io
class Player
{
make int health = 100;
make string name = "hero";
make empty takeDamage(int amount)
{
health = health - amount;
}
}
make Player p = Player();
p.health = p.health - 10;
println(p.health);
Static types, classes with real inheritance, closures with upvalue capture, compound assignment operators, arrays, and a small standard library brought in through pullf imports for io, math, time, file access, and string manipulation. All of it running on a virtual machine I wrote from the ground up.
The Part That Actually Kept Me Up At Night
Skipping runtime type checks entirely was the single riskiest design decision in the whole project. Since the static type checker is supposed to catch every invalid type combination before a program ever runs, the virtual machine itself does zero runtime type guarding. The IS_INT and IS_STRING checks scattered through the VM exist purely to pick a dispatch path, not to catch a mistake the checker let through. That means the runtime stays lean and fast, but it also means the entire safety of the language rests on the checker being airtight, with absolutely no safety net underneath it. High risk, high reward, and still an open question I would love feedback on.
See It For Yourself
The full source, the opcode reference, the architecture writeup, and the design decision notes are all sitting in the repository right now: https://github.com/NateTheGrappler/OliNat-Programming-Language
It builds clean on both Linux and Windows, verified by a continuous integration pipeline running on every single push, because a project that only claims to be cross platform is worth a lot less than one that proves it on every commit.
What's Next
Native support for dynamic arrays and hashmaps, the ability to link programs across multiple source files, and possibly a graphics library down the line. This project is not finished, and it is not supposed to be. It is going to keep growing as long as I keep learning from it.
If you have ever wanted to understand what actually happens between typing source code and watching a program run, or if you have opinions on type systems, garbage collection, or bytecode VM design, I would genuinely love to hear from you in the comments.
Top comments (0)