Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 8

Regular Expressions and Finite Automata in Practical Applications

Recommended resources

Introduction

Welcome to the final lesson of our first module. So far, we have built a solid theoretical foundation for computation with finite memory. We began by formally defining Deterministic and Nondeterministic Finite Automata (DFAs and NFAs), then introduced regular expressions as a compact language for describing patterns. We proved the fundamental equivalence between these models and explored their limitations with the pumping lemma. In our last lesson, we saw how to construct complex regular languages from simpler ones using closure properties.

Today, we will connect this entire body of theory to practice. You'll learn how these abstract concepts are not just mathematical artifacts but the core engine behind essential, everyday computing tasks. We will explore three major applications, answering the question: "Where are finite automata and regular expressions actually used?" This lesson will focus on explaining their roles in lexical analysis, text search, and input validation, directly addressing the final learning outcome for this module.


1. Lexical Analysis: The First Step of a Compiler

When a compiler processes a program you've written, its very first task is to read the raw text of your source code—a long stream of individual characters—and break it down into a sequence of meaningful words or symbols called tokens. This process is called lexical analysis, and the part of the compiler that does it is the lexical analyzer, or lexer.

Think about the code if (x > 10). The lexer's job is to convert that string of characters into a stream of tokens like: IF_KEYWORD, OPEN_PAREN, IDENTIFIER(x), GREATER_THAN_OP, NUMBER(10), CLOSE_PAREN.

How does it recognize these components? Each type of token is defined by a pattern, and these patterns are specified using regular expressions.

  • Token: A category or type of a lexical unit, like IDENTIFIER or NUMBER.
  • Lexeme: The actual character sequence in the code that matches a pattern, such as x or 10.
  • Pattern: A rule, expressed as a regular expression, that describes the set of possible lexemes for a token.

For example:

  • NUMBER: [0-9]+
  • IDENTIFIER: [a-zA-Z_][a-zA-Z0-9_]*
  • IF_KEYWORD: if

To build the lexer, we leverage the theoretical pipeline we've spent this module developing.

Caption: This diagram shows the standard process for building a lexical analyzer. Patterns for tokens are written as regular expressions, which are then combined and converted into an NFA, and finally into an efficient, table-driven DFA.

The process is as follows:

  1. Union of Patterns: The lexer needs to recognize all possible tokens simultaneously. This is achieved by taking the regular expressions for every token type and combining them into a single, large regular expression using the union operator (|). This is a direct application of the closure property under union we discussed in the last lesson.
  2. Conversion to NFA: This large regular expression is converted into an NFA (typically using Thompson's construction, which you've seen previously).
  3. Conversion to DFA: The NFA is then converted into an equivalent, highly efficient DFA using the subset construction algorithm. This DFA is the heart of the lexical analyzer. It reads the source code one character at a time and, by tracking its state, can identify the longest possible lexeme that matches a token pattern.

To see this process in action, watch the following video. It demonstrates how individual automata for different tokens are designed and then combined into a single machine.

Introduction to Lexical Analyzer

This video from Neso Academy provides a clear, step-by-step explanation of how a lexical analyzer works, from recognizing individual tokens to combining them into a single automaton.

Please watch the first two main segments: Introduction: This part defines the role of the lexical analyzer and the distinction between lexemes and tokens. Combining Automata: Pay close attention to how separate finite state machines for an if keyword, identifiers, and integers are first conceptualized and then combined into a single NFA, which is then shown as an equivalent DFA. This is the core implementation strategy.

Tools like Lex or Flex are lexical analyzer generators. They automate this entire process. A developer provides a file with a list of regular expressions and corresponding actions, and the tool generates the C code for a highly optimized DFA-based lexer.

[PDF] Lexical Analysis

These slides from a compiler course at FSU provide a concise look at the Lex tool, showing how it uses regular expressions in practice.

Please read the sections titled "Lex - A Lexical Analyzer Generator" and "LEX - A Lexical Analyzer Generator (cont.)". Focus on the structure of a Lex program (definitions, rules, subroutines) and the examples of rules. You'll see how a regular expression is directly mapped to a piece of C code to be executed when that pattern is found. Also, note the final section, "Implementation Details," which explicitly lists the NFA construction, DFA conversion, and minimization steps we've studied.


2. Text Search: The Power of grep and "Find"

Another classic application of this theory is in text searching. When you use a command-line tool like grep or the "Find" functionality (Ctrl+F) in a text editor, you are often using a regular expression engine.

The task is to find all occurrences of a pattern (described by a regular expression) within a larger text. The core of the implementation again relies on converting the user's regular expression into a finite automaton that processes the text. However, there is a crucial engineering trade-off in how this is done.

Strategy 1: Pre-computation into a DFA

  • Method: Convert the regular expression into an NFA, and then into a DFA.
  • Search: Run the text through the DFA. The search is extremely fast, taking time proportional to the length of the text, .
  • Trade-off: The conversion from an NFA to a DFA can, in the worst case, be exponential in time and space relative to the size of the regular expression. For a one-off search of a complex pattern, this "pre-computation" cost might be too high. The classic KMP string-searching algorithm can be understood as a clever way to build and run a DFA for a simple string pattern without explicitly storing the entire transition table.

Strategy 2: Direct NFA Simulation

  • Method: Convert the regular expression into an NFA and simulate the NFA's behavior directly as you read the text.
  • Search: A naive simulation is slow (, where is the number of states in the NFA). However, clever algorithms can simulate the NFA very efficiently. A common technique is bit-parallelism, where the set of active NFA states is stored in a single machine word (e.g., a 64-bit integer). Transitions for all states can then be computed simultaneously using fast bitwise operations (like shifts and ANDs).
  • Trade-off: This avoids the potentially large pre-computation cost of building a DFA. For patterns that produce an NFA with fewer states than the number of bits in a machine word, this method is often as fast as or faster than the DFA-based approach in practice.

The following video discusses the historical context of these trade-offs, pioneered by Ken Thompson, one of the creators of Unix.

Regular Expressions - Computerphile

This Computerphile video discusses the practical implementation of regular expressions for text editing, highlighting the trade-off between deterministic and non-deterministic approaches.

Please watch the segment from Ken Thompson's implementation. The speaker explains the two fundamental choices: Spend time pre-processing to convert the NFA to a DFA for a very fast search (Brian Kernighan's approach for the pic language compiler). Simulate the NFA on-the-fly to avoid pre-processing overhead, which is better for interactive, one-off searches (Ken Thompson's approach for the ed editor). This latter approach is an early example of what we now call just-in-time (JIT) compilation.

For a deeper look into the automata used for text search, the following paper provides excellent illustrations and explanations.

[PDF] Text Searching: Theory and Practice 1 Introduction - DCC UChile

This academic paper surveys algorithms for text searching, starting with the foundational automata-based methods.

Please read the introduction to Section 3.1, "Automata". It shows the NFA and corresponding DFA for searching the string "abracadabra". Notice how the simple, regular NFA becomes a more complex DFA with "backward" arrows. Then, read Section 3.2, "Simulating Nondeterminism", which explains the core idea behind the Shift-And/Shift-Or algorithms that use bit-parallelism to simulate NFAs efficiently.


3. Input Validation

A third, ubiquitous application of regular expressions is input validation. This involves checking whether a piece of user-provided data, like an email address, phone number, or password, conforms to a specific format.

For instance, a simple (though not fully comprehensive) regular expression to validate an email address might be:
[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}

Let's break this down:

  • [a-zA-Z0-9._%+-]+ : One or more characters that are letters, numbers, or one of ._%+-. This represents the local part of the email address.
  • @: The literal "@" symbol.
  • [a-zA-Z0-9.-]+: One or more characters for the domain name.
  • \.: A literal dot (. is a special character in regex, so it must be escaped).
  • [a-zA-Z]{2,}: The top-level domain, consisting of two or more letters.

When you submit a form on a website, the backend (and often the frontend via JavaScript) uses a regex engine to test your input against patterns like this. The process is identical to text search: the regex is compiled into an automaton, and the input string is checked to see if it is accepted by that automaton. The main difference is that for validation, the entire string must match the pattern, not just a substring within it.

This same principle is used to enforce password complexity rules, format phone numbers, parse URLs, and countless other data validation tasks that are fundamental to robust software.


Conclusion

In this lesson, we have seen the practical payoff of the theory developed throughout this module. The abstract machinery of finite automata and the concise notation of regular expressions are the direct foundation for solving real-world problems in pattern matching.

Key Takeaways:

  • Lexical Analysis: Compilers use a DFA—built from the union of regular expressions for all token types—to transform raw source code into a stream of tokens.
  • Text Search: Tools like grep compile a user's regular expression into an automaton. They face a design choice between costly pre-computation into a fast DFA or efficient on-the-fly simulation of an NFA.
  • Input Validation: Software validates that data like emails or passwords match a required format by testing the input string against a corresponding regular expression.

Across all these applications, the core idea is the same: use regular expressions as a high-level specification for patterns, and use the equivalence with finite automata to build an efficient implementation that recognizes those patterns.

This concludes our exploration of regular languages and finite memory. We have seen what they can do and what they cannot (via the pumping lemma). The next natural question is: what lies beyond? What kinds of languages, like those with nested structures, require more powerful models of computation? In the next module, we will answer this by adding a simple, infinite memory structure—a stack—to our machines, leading us into the world of Context-Free Languages and Pushdown Automata.