Learning Compilers: How it made me a better Software Engineer

Today, I’m a software engineer at GEICO, with over 4 years of experience. When I look back at the subjects I studied that really helped me, I must go back to the first programming language I learnt: C, before I started my undergrad. I memorised the syntax by heart, which made me dive right into thinking in C to solve any problem (although I haven’t solved many complex coding problems like the LeetCode style). It sure did give me a head start among my peers. I used to complete all the homework assignments and explain them to my friends (which is a story for another day on how I learned quickly and remember concepts well). But what subjects did I enjoy the most? It was the Theory of Computation and Compiler Design. I felt like solving puzzles, drawing all the state machines, and writing regular expressions for the BNF/eBNF grammar. I very much enjoyed them (I still feel rusty), although they were just theoretical. Then I spent some years working as a frontend software engineer and completely became rusty in those subjects, like everyone does. In 2024, I decided to return to school to get a Master’s Degree in Computer Science at the University of Wisconsin-Milwaukee. The first semester was great, with subjects like Computer Networks (CS530) and Machine Learning (CS711), but I didn’t enjoy them much. In the second semester, I enrolled in Programming Language Concepts (CS435), which I thought would be a basic intro to programming language. Still, all my peers suggested otherwise, and even then, I dared to continue, and to my surprise, it’s the best subject I took. It’s a combination of the Theory of Computation and Functional Programming, which made me really good at concepts like recursion (the inductive step and base case), generators, pattern matching, and I learned some interesting programming languages like SML and Scala, on top of the usual ones like JavaScript and Python.
The third semester was where the magic and everything began. At UWM, once every two years, Compiler Construction and Theory (CS754, plus a Hands-on Lab) is offered, and taught by Dr John Boyland (one of my favourite professors) and Amir (the TA and Instructor of the Lab), and is one of the difficult courses. Having already taken the PLC (CS435), I was ready to build a COOL (Classroom Object-Oriented Language) compiler by hand, which gave me insight into how programming languages work and behave. The compiler coursework consisted of understanding and building the basic components of a compiler: a lexer (using yacc and antlr), a parser (using scala-bison), a semantic analyser, inheritance, code generation, and code optimisation.
The lexer and parser-related modules in the coursework helped me understand how some programming languages have specific syntax. It not only helped me with my research but also with my work (at the Division of Marketing and Communication at UWM). I recall an instance where I encountered a problem with a third-party PHP plugin on a WordPress website. I haven’t worked extensively with PHP before, but the knowledge I gained about the syntax and semantic paradigms helped me debug and fix a bug on a mature WordPress plugin with thousands of users. It gave me the confidence to approach any new programming language fearlessly.
The tree visitor style pattern introduced in the semantic analyser-related modules has given me a great understanding of tree data structures and how they could be traversed. During the type-checking phase of the coursework, the decorator design patterns used to build symbol tables (recalling the Environment, NestedEnvironment and MethodEnvironment) helped explain how decorators work. They were used as a base for Higher-Order Functions or Components in React, @Annotators in Java, and @Decorators in TypeScript. Even checking how casting and subtyping work within object-oriented languages and especially knowing how type-lub and type-leq work, helped a lot.
The implementation of inheritance, I think, makes the biggest difference in my software engineering skills. I have always hesitated to work with pure object-oriented languages like Java (and Scala), even though I used them at work, due to the boilerplate and initial setup required and tended to work more with programming languages like JavaScript and Python because they were simpler. Now with the concepts I have learnt — like casting, multiple inheritance (and how it is a pain to implement and how different programming languages implement that), vtables and the object layout, I’m more confident than before that I can easily work with Java than before.
Having learnt assembly programming for an 8086 microprocessor as a part of the Micro Processors and Micro Controllers course in my undergraduate studies, I was able to write simple programs like arithmetic operations. The code generation seemed overwhelming at the start, but the handouts and lab sessions helped me understand topics such as the MIPS assembly, the calling sequence and register allocation.
Code Optimisation is the difficult part of the entire coursework to implement, but the techniques learnt were useful in writing better code and are not entirely dependent on the compiler.
