I was trying to make a game. A real one, not a tutorial clone. It had a physics simulation, a basic entity system, and SDL2 for rendering. I had been working on it for a few weeks and the game loop was getting slower every time I added something new.
I ran a profiler on it. The bottleneck wasn't my algorithm or my data structures. It was just Python being Python. Every attribute access, every method call, every loop iteration was carrying the overhead of a dynamic language runtime that has no idea what type anything is until it looks.
I looked at the options. C is fast but the syntax genuinely hurt to read after writing Python for years. C++ was even faster but the build system alone felt like a full-time job. Rust was intellectually fascinating but the borrow checker would have slowed me down to a crawl while I was still learning. Go was readable but not designed for game dev at all.
I had one of those moments where the obvious question appears and you realize you've been avoiding it: why doesn't something like this exist already? A language that looks like Python but compiles to native code? Statically typed, so the compiler knows what everything is. Fast to write. Fast to run.
I looked around for a while. Mojo was close but wasn't ready and was more focused on ML workloads. Cython was a hybrid approach that still fundamentally ran on CPython. Nothing was exactly what I wanted.
The Idea
I started sketching what I wanted the language to look like. Indentation-based, because I genuinely think it produces
cleaner code. Explicit types on functions and variable declarations. The fn keyword instead of
def to signal "this is compiled." Structs with methods.
Built-in SDL2 support so you can open a window without importing anything.
I had never written a compiler before. I had heard the word "parser" but couldn't have explained what a grammar was. I had no formal education in computer science, just a few years of self-teaching from documentation and tutorials.
I told myself I would build it over a weekend. That was wrong. But I started anyway.
The First Three Weeks
The first week was entirely reading and research. I found "Crafting Interpreters" by Robert Nystrom, which is one of the best technical books I've ever read. The first few chapters teach you to write a tree-walk interpreter in Java or C. I adapted the concepts to Python and started building.
The lexer came first. You take a string of source code and split it into tokens: keywords, identifiers, operators, string literals, numbers. Writing the lexer felt almost trivial. A few hundred lines of Python and I could tokenize a file. I ran it on my test programs and watched the tokens print out. That felt good.
The parser was harder. A recursive descent parser reads tokens and builds a tree. Each grammar rule becomes a function. You call the function for a statement and it calls the function for an expression and so on until you hit a leaf node. I wrote it, hit infinite recursion bugs, rewrote it, hit precedence bugs, fixed those. After about two weeks I had a parser that could handle most of the language.
Then I discovered LLVM and everything changed. Instead of writing my own code generator from scratch to produce assembly, I could emit LLVM Intermediate Representation using a Python library called llvmlite, and LLVM would do the actual native code generation. That meant the optimizer, the register allocator, the architecture support: all of that came for free. I just had to learn how to emit IR.
The Day Hello World First Compiled
I'll remember this for a while. It was late, past midnight. I had been debugging a segfault in the generated IR for about an hour. The bug turned out to be that I was passing the wrong calling convention to the LLVM function builder. I fixed it, ran my compiler on the world's simplest Vexel program, linked the output, typed the executable name.
# hello.vx fn main(): print("Hello, World!")
The terminal printed "Hello, World!" A compiled Vexel program had run. I sat there for a while. I was not expecting how good it would feel.
Everything After That
Once Hello World compiled, the rest was a matter of adding features one by one. Structs took a few days.
Enums took a week. The garbage collector took two weeks and broke four times before it worked.
Interfaces and the impl X for Y syntax took three days.
SDL2 support was the most fun to build because once it worked you could actually see a window appear.
I built it in the evenings and on weekends, after school. There were a few weeks where I barely touched it because I had other things going on. There were other weeks where I would work on it for five hours in a row because a feature was almost done and I couldn't stop.
I turned 17 in the middle of building the type checker. The analyzer pass that walks the AST and verifies that every operation is type-correct was probably the most technically demanding part of the whole compiler. You have to track every variable, every function signature, every struct field. You have to propagate types correctly through nested expressions. You have to give clear, accurate error messages.
I got it working. Not perfectly, not elegantly, but correctly.
Why I'm Sharing This
I'm not posting this to impress anyone. I'm posting it because when I started I genuinely thought "building a compiler" was something that required a CS degree and years of experience. It turns out that is not true. It requires time, stubbornness, and the willingness to read documentation and be wrong about things repeatedly.
If you're thinking about building something ambitious, the best advice I can give you is to just start it badly. Write the worst possible version of the first component. You will fix it. The first version of my lexer was terrible. I rewrote it. The first version of my parser had fundamental structural flaws. I rewrote it. The important thing was that they existed, so I had something to improve.
Vexel v0.1 is out. It compiles real programs. It's not done. I'm going to keep building it.