f.Fabiansoftware developer
← All projects
2025 · Diploma thesis · HTL Leonding

Kipper

My diploma thesis: a programming language for the web and Node.js. I got to teach it object-oriented programming: classes, interfaces, and type checks that still work while the program is running.

TypeScriptCompilerANTLRLanguage designOOP
TECH STACK
  • TypeScript
  • Node.js
  • ANTLR
  • JavaScript
  • Git

About the project

Kipper is a type-safe programming language that compiles to JavaScript and TypeScript, so it runs in the browser and on Node.js. Luna Klatzer started it, and for our diploma thesis at HTL Leonding, Luna, Lorenz Holzbauer and I got to work on it together.

Building a programming language sounds a bit intimidating. It is. It’s also one of the most fun things I’ve ever worked on.

My job was making Kipper object-oriented. Before version 0.12 there were no classes or interfaces. After it, you could write the program in the first screenshot, and it actually ran:

  • Classes with properties, methods and constructors, plus new and this.
  • Interfaces that describe what an object should look like, methods included.
  • instanceof to check whether a value came from a certain class.
  • matches, my favourite. It checks while the program is running whether a value fits an interface, which is handy for data you only get at runtime, like JSON from an API. You just write matches and the compiler generates the checks for you.

Every one of these had to make it through the whole compiler: the ANTLR grammar, the syntax tree, the type checker (with error messages that hopefully make sense), and the code generators for JavaScript and TypeScript. One new keyword means touching all of them. Seeing the first class compile and print the right thing in the playground was a very good day.

Want to try it? The playground link is below. Go break something. ._.

Case study

The problem

Kipper had no object-oriented features before version 0.12: no classes, no interfaces. And like every language that compiles to JavaScript, its types vanish once the program runs, so there was no way to check whether data from outside, like JSON from an API, really has the shape the code expects.

Decisions I made

  • Build every feature through the whole compiler instead of as a shortcut in one place: grammar, syntax tree, type checker and both code generators, so classes and interfaces behave the same everywhere.
  • Make matches a compile-time feature: the compiler reads the interface and generates the runtime checks, so nobody has to write validation code by hand.
  • Extend the existing ANTLR-based compiler and follow its conventions instead of rewriting parts of it the way I would have liked.

Trade-offs

  • Two targets, JavaScript and TypeScript, mean every new keyword has to be generated twice and tested twice.
  • Runtime checks from matches are real code in the output, so they cost a little size and speed. They only appear where matches is actually used.
  • Working in a codebase that existed before us is slower than starting fresh, but the language stayed consistent and the reviews kept it that way.

What I learned

  • A compiler is a lot of moving parts that all have to agree with each other. Add one keyword and you find out exactly how many.
  • JavaScript forgets all your types once the program runs. If you want type checks there, you have to build them yourself, and that turned out to be a really fun puzzle.
  • How to work as a team on a big codebase that existed before you: reading other people’s code, reviews, issues, and keeping the changelog honest.