What if “wenn” worked just as well as “if”? My students and I built a compiler that turns German into .NET IL

Every developer in Germany thinks in German and codes in English. We say "solange der Kunde aktiv ist…" in the meeting, then type while (customer.IsActive) in the IDE. We've done it so long we don't even notice the translation anymore.

So in my Software Engineering course at Frankfurt University of Applied Sciences, I gave my students a slightly crazy challenge: what would it take to write code in German and have it run natively on .NET? Not transpiled to C#, and not interpreted. Real Intermediate Language, running on the same runtime as every enterprise app we ship.

The answer: about 400 lines of C#, a lot of whiteboard discussions, and a room full of students who suddenly realised that compilers aren't magic and the AI is a friend.

Meet Deutsch.NET#

zahl fakultät = 1;
zahl i = 1;

solange i <= 10 {
    fakultät = fakultät * i;
    i = i + 1;
}

wenn fakultät > 1000000 {
    schreibe "Das ist groß!";
}

That's a real program. zahl, solange, wenn, sonst and schreibe are keywords, umlauts are valid in variable names (yes, größe works), and the output is a genuine .NET assembly. You can run it with dotnet programm.dll or open it in ILSpy and read the IL.

How it works (it's less magic than you think - alle kochen mit Wasser)

Every compiler, from Roslyn to GCC, follows the same basic pipeline. Together we implemented each stage in its simplest honest form.

1. Lexer. It turns raw text into tokens. It recognizes solange as a keyword, fakultät as a name and <= as an operator.

2. Parser. A recursive-descent parser turns tokens into an abstract syntax tree. Operator precedence (oder < und < comparison < + < *) is simply the order in which the functions call each other.

3. Code generation. The tree is walked and each node is emitted as IL through System.Reflection.Emit. A solange loop becomes a label, the condition, a brfalse to the end, the body, and a br back to the start. Even <= has no direct IL instruction; it becomes cgt followed by "compare with zero". That's the kind of detail you only learn by building it yourself.

4. Assembly. The new PersistedAssemblyBuilder in .NET 9 writes the result to disk as a real executable. For years this was only possible on .NET Framework; now it's back, and it's a game changer for anyone building languages or DSLs on .NET.

Why build a compiler in a Software Engineering course?

Because it's the perfect software engineering project in miniature.

Architecture becomes tangible. Lexer, parser and code generator are clean, separate layers with clear contracts between them. Students experience firsthand why separation of concerns isn't just a slide in a lecture.

Testing gets real. Every keyword, every operator and every edge case (what happens with zahl x = x;?) needs a test. Good error messages in German ("Zeile 7: ';' erwartet") turned out to be just as much work as the happy path, exactly like in real products.

The fundamentals stick. When you've emitted ldloc and brfalse yourself, you understand async state machines, LINQ and performance profiles on a completely different level. Abstractions stop being mysterious once you've built one.

And it's fun. Seeing your own German program print "Das ist groß!" from an assembly you generated byte by byte is a moment students don't forget.

Why this matters beyond the classroom

Domain-specific languages are underrated. Imagine business rules, pricing logic or configuration written in the language the domain experts speak. A DSL compiled to IL runs at full .NET speed and remains strictly deterministic.

And this is where it gets interesting for AI. Everyone is talking about natural language as the new programming interface. LLMs are brilliant at turning intent into code, but they're probabilistic. Compilers are deterministic. The real power lies in combining both: an agent translates human intent into a precise, constrained DSL, and a compiler guarantees what actually runs. Natural language for expression, formal languages for trust.

The next generation of engineers needs to understand both worlds. That's exactly what we try to teach.

What's next

Deutsch# is intentionally tiny: integers, strings, branches and loops. The roadmap is already on the whiteboard. That means nested scopes, a real type checker, functions (funktion … gib zurück …), and calling into the .NET base class library. Plenty of material for the next student projects.

We also used Claude and ChatGPT as an AI pair programmer along the way, which is a story in itself. It sped things up a lot, but the real understanding came from the students reading and questioning every line it suggested.

Would you write production code in your native language, or is English simply the lingua franca of code forever? And should we put Deutsch.NET# on GitHub? Let us know in the comments. ?

Huge thanks to my students for their curiosity and energy. You made this one a blast. ?

dotnet #csharp #compilers #softwareengineering #teaching #FrankfurtUAS #AI #DSL #MicrosoftMVP

Originally published on LinkedIn.