Agile Software Engineering
This podcast explores how craftsmanship, architecture, engineering rigor, and organizational practices come together in modern R&D environments. Each edition refines and deepens my earlier reflections, building a coherent and evolving body of knowledge around Agile Software Engineering
Agile Software Engineering
YACC? What Is That? Why Compiler Thinking Still Matters (Compiler Theory part 2)
Use Left/Right to seek, Home/End to jump to start or end. Hold shift to jump forward or backward.
In this episode of The Agile Software Engineering Deep Dive, Alessandro Guida continues the two-part journey into compiler theory.
Part 1 focused on BNF, syntax, and grammar. Part 2 moves into compiler thinking itself: LEX, YACC, compilers, interpreters, syntax trees, the Dragon Book, and the evolution from classic compiler pipelines to modern development ecosystems.
For many software engineers who studied computer science in earlier decades, compiler theory was part of the foundation. It taught us how programming languages are defined, parsed, checked, transformed, optimized, and executed.
Today, most developers are not writing compilers by hand. But the ideas behind compilers are still everywhere: in build systems, query engines, static analyzers, IDEs, code generators, rule engines, security scanners, infrastructure-as-code tools, and AI coding assistants.
This episode explores why compiler thinking still matters when we design APIs, configuration formats, workflow definitions, domain-specific languages, rule systems, and AI-assisted development tools.
It also revisits the classic distinction between compilers and interpreters, explains how modern compilers have evolved, and reflects on the connection between programming languages and AI prompts.
This is not a nostalgic look back at old tools.
It is a reminder that when human intent must become reliable machine action, the old lessons of compiler theory are still surprisingly modern.
This Podcast is an audio version of the written Agile Software Engineering newsletter. If you want to go deeper, don't forget to subscribe the newsletter too.
Welcome to the Agile Software Engineering Deep Dive, the podcast where we unpack the ideas shaping modern software engineering. My name is Alessandra Guida, and I've spent most of my career building and leading software engineering teams across several industries. And today we continue our short journey into compiler theory. In the previous episode, we looked at Bacchus NAR form, or BNF, and why grammar still matters when modern applications become programmable. Today we move from grammar to compiler thinking itself. Lex. The Dragon Book. For many of us who studied computer science some decades ago, compiler theory was part of the foundation. It was one of those subjects that taught us not only how to write programs, but how programming languages themselves were defined, parsed, checked, transformed, and executed. Today, most developers are not writing parsers by hand. They are building APIs, cloud services, integrations, pipelines, configuration systems, rule engines, workflow platforms, developer tools, and AI-assisted applications. But the old ideas have not disappeared. They are hiding everywhere. Every time we translate human intent into structured machine action, we are touching the same fundamental problem compiler theory was built to address. How do we take something written by a human, understand its structure, validate its meaning, and execute it safely and predictably? This episode is part two of a short two-part journey into compiler theory. Part one was about BNF, syntax, and grammar. Part two is about compiler thinking more broadly. Lex, Yak, compilers, interpreters, modern compiler evolution, AI prompts, and why this almost forgotten discipline still matters. This is not nostalgia. It is a reminder that some of the old computer science foundations are still alive inside the tools, languages, platforms, and intelligent systems we build today. Let's dive in.
SPEAKER_01Oh, yeah, definitely. Far too many times.
SPEAKER_02Right. And then only to realize the entire outage was caused by, you know, a single missing comma in a JSON configuration file.
SPEAKER_01It is the absolute worst feeling in the world.
SPEAKER_02It really is. But that specific moment of rage, when you finally realize the machine doesn't actually understand your intent, but only your syntax, that is the exact reason compiler theory from the 1980s is still entirely relevant to the code we write today.
SPEAKER_01Yeah, we layer so many uh complex abstractions over our hardware now that it's just so easy to forget there's this cavernous mechanical gap between what we want a program to do and a processor actually flipping bits.
SPEAKER_02Yeah, the illusion of seamless communication just shatters.
SPEAKER_01Exactly. When a system crashes over a missing comma, you're suddenly reminded that machines don't speak human context at all. They only execute strict mathematical rules.
SPEAKER_02Which brings us to today. Welcome to today's deep dive. We are unpacking issue number 39 of an agile software engineering newsletter, and it's titled YACC. What is that? Why compiler thinking still matters?
SPEAKER_01A really fascinating read.
SPEAKER_02It is. And our mission today's is to figure out why understanding how programming languages are actually built is, well, increasingly being called a lost foundation of software engineering.
SPEAKER_01And just to set the stage for you, we're specifically looking at part two of a broader series on compiler theory.
SPEAKER_02Right, which brings up a quick but important piece of housekeeping for you listening right now. Since this is part two, please remember to read and subscribe to the full article.
SPEAKER_01The link is right there in the show notes.
SPEAKER_02Yep. You are definitely going to want to see the original illustrations and dig into the in-depth content provided there. And, you know, while this deep drive is completely free, it is a massive help to us if you press like on this deep dive and subscribe to receive all future issues.
SPEAKER_01It really does help. So to understand why modern developers should care about uh parsing and syntax trees, we kind of have to travel back to the 1980s and 90s.
SPEAKER_02Okay, let's go back.
SPEAKER_01During that era, compiler theory wasn't some niche optional elective for computer science students. It was the engine room of the whole discipline. If you were studying computer science, you were basically guaranteed to encounter a specific textbook, formerly titled Compilers Principles, Techniques, and Tools.
SPEAKER_02Which is universally known as the Dragon Book.
SPEAKER_01Yes, the famous Dragon Book, written by Aho, Sethy, Ullman, and later Lam. The Dragon Book was practically a rite of passage.
SPEAKER_02Because it didn't just teach you the syntax of a language.
SPEAKER_01Exactly. It taught you how to architect language itself. It detailed how to structure rules, how to assign meaning to completely arbitrary text, and importantly, how to safely execute it.
SPEAKER_02It kind of feels like the difference between, say, writing a script that consumes a third-party API versus actually architecting the entire underlying network protocol yourself.
SPEAKER_01Aaron Powell That's a really good analogy.
SPEAKER_02Like when you consume the API, you're just living within someone else's constraints. But when you build the protocol, you own the domain.
SPEAKER_01Right. And the pioneers of computing, they had no choice but to build those protocols from scratch. I mean, in the 1950s, programmers were working directly with assembly or raw machine code.
SPEAKER_02Which sounds exhausting.
SPEAKER_01Oh, incredibly. So Grace Hopper's A0 system was a massive leap. It proved a machine could take human-oriented instructions, basically pulling predefined subroutines, and then translate them into machine code.
SPEAKER_02It essentially demonstrated that software could build software.
SPEAKER_01Exactly. Then John Bacchus and his team at IBM shipped Fortran, which proved that a high-level language could generate machine code optimized enough for serious scientific computing. But the real philosophical shift, at least according to the source we're looking at, was Algeola.
SPEAKER_02Algeol. Okay, why Algeol?
SPEAKER_01Algeol changed the landscape because it established that a programming language shouldn't just be this uh ad hoc collection of features built by trial and error. It demonstrated that a language must be mathematically precisely described. And this shift in thinking birthed a very popular half-serious saying in the 1980s computer science world. Which was don't solve a problem. Build a compiler to solve the problem.
SPEAKER_02Okay, wait, I have to challenge that.
SPEAKER_01Go for it.
SPEAKER_02Because if I'm leading a modern agile team today, and I tell my engineers, hey, instead of writing a quick Python script to parse some user data, we're gonna build a custom compiler. I mean, I'm getting fired.
SPEAKER_01Oh, absolutely.
SPEAKER_02That just sounds like a recipe for massive over-engineering. Aaron Powell Yeah.
SPEAKER_01If you take it literally and build a bespoke, Turing complete language for a basic data pipeline, yes, you will definitely get fired. But the underlying philosophy is more about creating highly specific, constrained environments.
SPEAKER_02Aaron Powell Okay, constrained in what way?
SPEAKER_01Aaron Powell Well, if you can define the exact rules of a domain, if you can parse it and execute it predictably, you eliminate entire classes of bugs. You control the environment rather than just reacting to edge cases all the time.
SPEAKER_02I see. But to control that environment, you need a way to actually enforce the rules. Algeol laid the philosophical groundwork that code should be precise, but you know, philosophy doesn't compile. We needed a mathematical framework to actually enforce that precision. Which brings us to BNF.
SPEAKER_01Bacchus now form, exactly. BNF applied the concept of context-free grammars to software code. It gave engineers a formal mathematical way to describe syntax.
SPEAKER_02So it turned language design into a strict discipline.
SPEAKER_01Yes. Before BNF, you might have a prose-based explanation where someone says normally a function looks like this. But with BNF, you could mathematically define every single valid structural combination in a language.
SPEAKER_02But again, having a mathematical blueprint doesn't magically execute the code. You still have to build a tool that takes a raw text file from a developer and automates that math into a working parser.
SPEAKER_01Right, which is exactly where LEX and YACC entered the picture.
SPEAKER_02And yes, for anyone wondering, YACC literally stands for yet another compiler compiler, which is just peak dry engineering humor.
SPEAKER_01Aaron Powell It's very self-deprecating. But the architecture LEX and YACC established was profound because it enforced a really strict separation of concerns. Let's look at the math example from the text.
SPEAKER_02Okay.
SPEAKER_01The expression is three plus four times five.
SPEAKER_02Three plus four times five. Got it.
SPEAKER_01To a human, that's basic math. But to a machine, that isn't math at all. It's just a raw stream of individual text characters.
SPEAKER_02Right. The machine just sees the character three, a space, a plus symbol, a space, a four. It has no concept of numbers or operations yet.
SPEAKER_01Exactly. So first you feed that character stream into LEX. LEX performs the lexical analysis. It uses a state machine to scan all those characters and create them into logical tokens.
SPEAKER_02Okay, so it drops the white space.
SPEAKER_01Yep, drops the white space and outputs a clean token stream. So it says number plus number times number.
SPEAKER_02Okay, I have an analogy for this. So LEX is kind of like a person identifying the nouns and verbs in a sentence.
SPEAKER_01I like that.
SPEAKER_02It categorizes the raw data, but it's structurally blind, right? It has no idea what the relationships between those tokens actually are. It doesn't know about the order of operations.
SPEAKER_01Exactly. And that is where YACC comes in. That structural validation is handled by YACC. Yeah. YACC is the parser. So it takes that flat stream of tokens from LEX and applies those strict BNF grammar rules we talked about to build an abstract syntax tree, or AST.
SPEAKER_02So YACC is basically the strict grammar teacher, ensuring the sentence actually makes logical sense.
SPEAKER_01Spot on. The AST is where the actual logic takes shape. The parser sees the tokens, knows that multiplication has a higher precedence than addition, and constructs this hierarchical tree.
SPEAKER_02Which guarantees the machine will evaluate the four times five before it adds the three.
SPEAKER_01Right. It completely prevents the machine from arbitrarily calculating three plus four first and then multiplying by five. Syntax defines structure, and structure affects meaning.
SPEAKER_02And separating those two tasks, having LEX handle the messy character scanning and YECC handle the complex hierarchical logic, that must have made things a lot easier to build.
SPEAKER_01It allowed developers to build incredibly robust systems. You didn't have your parsing logic all tangled up with your tokenization logic. Languages became much easier to extend and maintain.
SPEAKER_02Okay, so once that abstract syntax tree is built, the machine finally understands the developer's intent. Now it just has to, well, execute it.
SPEAKER_01Right, the final step.
SPEAKER_02And historically, computer science textbooks drew a very hard, clean line right here. They divided execution into two neat boxes compilers and interpreters.
SPEAKER_01Ah, yes. The classic textbook dichotomy.
SPEAKER_02How does that textbook definition usually go?
SPEAKER_01Well, normally it says a compiler takes your high-level C code, translates the entire program into native machine code ahead of time, and then you just run that compiled binary.
SPEAKER_02And an interpreter.
SPEAKER_01An interpreter, conversely, reads the source code and executes it directly on the fly, line by line.
SPEAKER_02It's a really nice, clean mental model, but the source points out that this binary is just completely outdated when you look at modern software architecture. The modern reality is incredibly messy.
SPEAKER_01It's extremely hybridized now. The traditional boundaries have almost entirely dissolved.
SPEAKER_02Give me an example.
SPEAKER_01Take C sharp. It isn't typically compiled straight down to hardware-specific machine code right away. It's compiled into an intermediate language. Then a runtime environment takes that intermediate code and uses a just-in-time compiler, a JIT, to translate it into native machine code directly on the target machine, right as the program executes.
SPEAKER_02Okay, what about Python? Because everyone refers to Python as an interpreted language.
SPEAKER_01They do, but Python source code is actually compiled into bytecode first. Then that bytecode is executed by the Python virtual machine.
SPEAKER_02Wow, okay. And then there's JavaScript.
SPEAKER_01Oh, JavaScript engines like V8 take dynamic execution to an absolute extreme. They don't just interpret code, they actively profile it while it runs. They dynamically parse, optimize, compile, and even de-optimize code on the fly.
SPEAKER_02Wait, de-optimize. Let's dig into that for a second. Why would a JavaScript engine go through the trouble of optimizing code only to intentionally throw that optimization away and recompile it?
SPEAKER_01It all comes down to dynamic typing. Let's say you write a JavaScript function that adds two variables together. The engine observes your code running and notices you're constantly passing integers into that function.
SPEAKER_02Okay, makes sense.
SPEAKER_01So behind the scenes, the JIT compiler generates highly optimized machine code specifically for integer addition. It runs incredibly fast. But JavaScript allows you to change types at runtime.
SPEAKER_02Ah, I see where this is going.
SPEAKER_01Right. The very moment you pass a string into that same function, the engine realizes its strict integer assumption is suddenly invalid. So it has to immediately throw away that optimized machine code, de-optimize back to a safer bytecode, and figure out how to concatenate strings instead.
SPEAKER_02That is a massive amount of hidden machinery just to run a basic web script.
SPEAKER_01It really is.
SPEAKER_02Which makes me wonder if everything from Java to JavaScript is relying on these complex pipelines of intermediate representation and JIT compilation, shouldn't we just throw out the terms compiler and interpreter entirely when we teach new developers?
SPEAKER_01I wouldn't retire them completely. No, because the architectural trade-offs between ahead-of-time compilation and on-the-fly execution still dictate how we build systems. Like if you're building a web browser, you absolutely need a JIT compiler because you are downloading and running unknown code dynamically.
SPEAKER_02That makes sense.
SPEAKER_01But if you're building embedded software for, say, a pacemaker, you want strict ahead-of-time compilation with completely predictable memory and CPU profiles. You don't want dynamic de-optimization in a pacemaker.
SPEAKER_02Yeah, definitely not.
SPEAKER_01So the foundational models from the Dragon Book haven't disappeared at all. Modern compilers are just doing significantly more heavy lifting. And crucially, they've moved much closer to the developer workflow.
SPEAKER_02You're talking about the tools we use while writing code rather than just the build steps at the end, right?
SPEAKER_01Exactly. Look at language server protocols and modern IDEs. When you get a really fast autocomplete or real-time squiggly red lines highlighting a syntax error before you even hit save, that is literally the compiler running continuously in the background.
SPEAKER_02It's performing live static analysis.
SPEAKER_01Yes. Tools like the LLVM infrastructure have completely revolutionized this space.
SPEAKER_02LLVM is fascinating because it heavily leverages that abstract syntax tree concept we talked about earlier with YACC. It uses an intermediate representation or IR to separate the front-end parser from the back-end optimizer.
SPEAKER_01It's brilliant because LLVM uses a standardized intermediate representation. You can have a front-end parser for C Ruck, another completely different front-end for Rust, and another for Swift.
SPEAKER_02But they all speak the same language in the middle.
SPEAKER_01Right. They all parse their respective syntax down into the exact same LLVM IR. Then the LLVM back end takes that IR and optimizes it for different hardware architectures. It means entirely different programming languages get to share the exact same optimization logic.
SPEAKER_02Which is just incredible modular architecture. But I want to pivot here and bring this directly back to you, the listener.
SPEAKER_00Good idea.
SPEAKER_02Because I can easily imagine a developer thinking look, I write internal web dashboards, I configure cloud infrastructure, I am never going to write a custom front end for LLVM. Why do I need to care about BNF or lexical analysis?
SPEAKER_01You need to care because compiler theory teaches you how to separate form from meaning.
SPEAKER_02Every time you define an API schema or structure a complex configuration format, you are essentially designing a language. You're creating a structured protocol for expressing intent to a machine.
SPEAKER_01Yes. And when you build those protocols without applying compiler thinking, you end up creating what the source calls accidental languages.
SPEAKER_02Oh man, this is a wildly relatable pain point. We've all seen this.
SPEAKER_01No, absolutely.
SPEAKER_02Someone on a team creates a simple YAML configuration file for a deployment pipeline. Just a few keys and values, right? Like court, 8080, no big deal.
SPEAKER_01Mamus last words.
SPEAKER_02Right? Because three years later, another developer adds an IFLS condition. Then someone else adds a regex parsing rule to a string field, and suddenly your simple config file has morphed into this monstrous, undocumented programming language that breaks if you look at it wrong.
SPEAKER_01It's like building a house by nailing random boards together without a blueprint. You eventually manage to build a second floor, but there are absolutely no load-bearing walls. And when a wall inevitably collapses, you have no structural logic to tell you which nail failed. Accidental languages are notoriously fragile because they grow without a formally defined grammar.
SPEAKER_02Because there was never a distinct parsing phase designed up front, you get terrible error handling. You miss a single indentation space, and the whole deployment pipeline crashes with some generic stack trace instead of telling you exactly which node in the syntax tree failed.
SPEAKER_01Exactly. Applying compiler thinking means recognizing the moment you cross the line from just writing application logic to actually designing a system of expression. It means taking a step back and formally defining your syntax, your validation rules, and your execution constraints.
SPEAKER_02And the ultimate stress test for this discipline is happening right now with artificial intelligence.
SPEAKER_01It really is.
SPEAKER_02We are currently experiencing this massive push away from strict syntax, attempting to replace it with natural language prompts. People are just talking to the machine. Yeah. Does AI make all of this formal language stuff obsolete?
SPEAKER_01Aaron Powell Well, natural language prompts are the dream we've chased since the days of assembly code. Natural language is incredibly expressive and flexible.
SPEAKER_02But it is inherently ambiguous. I mean, humans rely heavily on shared context to resolve ambiguity. Machines do not have lived experience to draw context from.
SPEAKER_01And that is exactly why the text argues that AI doesn't make formal compiler rules obsolete, it actually makes them vastly more critical.
SPEAKER_02Real. More critical.
SPEAKER_01Yes. Natural language is phenomenal for describing intent, but it is a terrible mechanism for guaranteeing execution. Computers still require precise instructions to execute tasks safely.
SPEAKER_02Right. It's the hallucination problem. An AI model might generate a block of code that looks syntactically correct to a human, but structurally violates the rules of the environment. If you don't have a strict compiler to act as the deterministic referee, you really can't trust the output.
SPEAKER_01We need compiler theory to act as the verification layer. The future of development isn't a choice between AI prompts and programming languages. It's a synthesis.
SPEAKER_02A synthesis, I like that.
SPEAKER_01We will absolutely use natural language and AI models to explore complex domains, explain legacy systems, and generate boilerplate code. But we fundamentally require formal, strictly defined languages to constrain that generation, verify the structural logic, and safely execute the code.
SPEAKER_02Without the math of a syntax tree to validate what the AI just built, you are just inviting chaos into your production environment.
SPEAKER_01Precisely.
SPEAKER_02They are the active beating heart of our infrastructure. They dictate how our build systems work, how our query engines parse data, how our IDEs catch errors, and how we will safely integrate AI coding assistance into our workflows. Translating human intent into predictable machine action will always require rigorous structure.
SPEAKER_01And if you're looking to upgrade your mental models around this, the source lists several foundational books for those who want to dive deeper.
SPEAKER_02Oh, awesome. What are the recommendations?
SPEAKER_01Well, obviously there's the classic compilers, principles, techniques, and tools, the Dragon Book. But for a more modern, implementation-oriented approach, they also recommend engineering a compiler and modern compiler implementation.
SPEAKER_02Any practical guides?
SPEAKER_01Yes. There's the classic Unix guide, Lex and Yak, and a truly excellent contemporary book called Crafting Interpreters.
SPEAKER_02Ah. Crafting interpreters is a fantastic recommendation. It walks you through building a language step by step, showing you the architecture without drowning you in heavy academic proofs right out of the gate.
SPEAKER_01It bridges the gap between theory and immediate practical application perfectly.
SPEAKER_02Remember, you can find the links to all of these book recommendations, along with the full deep dive article right in the show notes. Please make sure you click through to read and subscribe to the full article. The original illustrations and the complete depth of the source material are absolutely worth your time.
SPEAKER_01Highly recommend checking it out.
SPEAKER_02And again, while we love making this deep dive completely free for you, it is a tremendous help to us if you press like right now and subscribe to receive all of our future issues.
SPEAKER_01Your support directly allows us to keep analyzing these engineering foundations.
SPEAKER_02We started today by talking about how easily a single missing comma can completely shatter the illusion that computers understand what we want. We rely entirely on the hidden structures built by early compiler pioneers to bridge the gap between our messy human intent and the strict reality of the processor.
SPEAKER_01It's all about structure.
SPEAKER_02So we want to leave you with one final thought to ponder on your own as you go back to your code bases today. Think about the project you are working on right now. What accidental language are you currently forcing your users or your own team to speak without even realizing it? And how could introducing just a little bit of strict compiler thinking finally fix it?
SPEAKER_00Thank you for listening to the Agile Engineering Deep Dive Podcast. If you found this episode valuable, feel free to share it with someone who might benefit. A colleague, your team, or your network. You can access all episodes by subscribing to the podcast and find their written counterparts in the Agile Software Engineering newsletter on LinkedIn. And if you have thoughts, ideas, or stories from your own engineering journey, I'd love to hear from you. Your input helps shape what we explore next. Thanks again for tuning in, and see you in the next episode.
Podcasts we love
Check out these other fine podcasts recommended by us, not an algorithm.
Darknet Diaries
Jack Rhysider