Searching with Regular Expressions
Regular expressions are designed so they can be compiled into state machines, which supports efficient execution.
Finding a Shape of Text in a Large Document
Suppose you want a program to find every place a particular shape of text appears inside a larger document. The shape might be a sequence of digits followed by a dot, or a word that starts with a capital letter. A regular expression is the compact notation that describes such a shape. Writing the notation is the easy part. The harder question is how a program can check text against that description quickly, no matter how long the text is.
The source gives a direct answer to that harder question. The syntax and meaning of regular expressions were designed so that each expression can be converted into a state machine, and that state machine can then be run efficiently against input. This article follows that idea through three questions: what the conversion produces, when the conversion happens, and which flavor of notation the source's library accepts.
What a State Machine Is
A state machine is a structure that is always in exactly one of a set of states. It reads input one piece at a time, and each piece moves it from its current state to another state according to fixed rules. Some states are marked as accepting. If the machine has consumed the input and ends in an accepting state, the input matches.
The key phrase is exactly one state. At every moment the machine occupies a single state, and the next state is determined entirely by the current state and the next character. There is no lookahead into the whole input and no backtracking described in the source. The machine simply reads, moves, and at the end checks whether it is sitting in an accepting state.
Tracing a Pattern Through a Machine
The source illustrates the general control flow with the pattern ab. The machine starts in an initial state. For each character, it follows the transition that matches that character. If no transition applies, the machine cannot continue along that path. If the input ends while the machine sits in an accepting state, the input matches. The following illustration is a generated teaching example that shows one possible machine for the pattern ab. It is not a claim about the exact internal layout any particular library builds.
What do you think happens?
The input is ab. Starting in State 0, what state does the machine occupy after reading both characters, and does the input match?
Reveal answer
It ends in State 2, which is accepting, so it matches
Reading a moves the machine from State 0 to State 1. Reading b moves it from State 1 to State 2. The input has been fully consumed and the machine is in an accepting state, so the input matches. This follows the source's rule that a matching input ends in an accepting state.
Why Compiling Makes Matching Efficient
The important move is a change of representation. The regular expression is a description written as text. The state machine is a set of states and transitions that a program can follow step by step. The source says the regular expression syntax and semantics were designed so this change is possible, and that the resulting machine supports efficient execution.
In other words, the matching work is done by walking a machine, not by re-reading the description of the pattern each time a character is examined. Once the pattern has become a machine, each input character causes one transition, and the machine's current state carries all the information needed to decide what comes next. The source describes this efficiency qualitatively; it does not give timing figures, so the benefit to learn here is the structure of the work, not a measured speed.
When the Compilation Step Happens
The source is specific about timing. The regular expression type performs the compilation at run time. The conversion from pattern to state machine is carried out while the program is running, not as a separate step performed ahead of time on the pattern text. The compiled form therefore belongs to the running program and is produced from the pattern it is given.
The timeline below orders these steps. The source places the conversion inside the running program, and then places the search, which runs the machine over the input from left to right, after the machine exists. The source does not go further than this, so the timeline makes no claim about caching, reuse across calls, or the details of the conversion algorithm.
Tracing a Failed Match
A match can also fail. Using the same generated machine for ab, consider the input ax. The machine starts in State 0 and reads a, moving to State 1. In State 1, the only transition in this machine is for b. The character x has no transition that applies, so the machine cannot continue along that path. The input does not match.
Running the machine on the input ax
Using the generated machine for ab, determine whether the input ax matches.
- 1Start
The machine begins in State 0, the initial state.
- 2Read a
A transition exists from State 0 on a, so the machine moves to State 1.
- 3Read x
State 1 has no transition on x. The machine cannot continue along this path.
- 4Decide
The machine did not consume the input to an accepting state, so the input does not match.
No match. The machine stopped without reaching an accepting state.
Mistakes Beginners Make with Matching
Common mistake Thinking a match is found just because the machine read every character
What it looks likeReading the input ax with the machine for ab reads both characters without error at the first step only if a transition exists
Why it failsThe source says the input matches only if the machine ends in an accepting state after consuming the input. Reading every character is not enough.
Do this insteadCheck the final state. Consuming the input and ending in an accepting state are both required.
Common mistake Assuming the pattern is interpreted from scratch for every character
Why it failsThe source describes matching as walking a machine built from the pattern, not as re-reading the description of the pattern each time a character is examined.
Do this insteadThink of the pattern as being converted once into a machine, then the machine being walked over the input.
Common mistake Assuming the compilation happens ahead of time, before the program runs
Why it failsThe source states that the regular expression type performs the compilation at run time, while the program is running.
Do this insteadPlace the compilation inside the running program, not as a separate pre-run build step.
Common mistake Assuming every regex tool reads the same notation
Why it failsThe source notes that the library can recognize several variants, so the same text can mean different things depending on the variant selected.
Do this insteadCheck which variant the library expects before moving a pattern from one tool to another.
Notation Variants and the Default Used Here
Regular expression notation is not one single standard that every tool reads in exactly the same way. The source notes that the regular expression library can recognize several variants of the notation. A pattern therefore depends on which variant the library is set to read, even though the underlying idea of describing a text shape stays the same.
The source names its default. It uses a variant of the ECMA standard, the standard used for ECMAScript, which is more commonly known as JavaScript. When the source writes its patterns, this is the notation it is using. If you carry a pattern from one tool to another, check which variant each one expects before assuming the same text means the same thing.
Practice Tracing a Machine
Using the generated machine for ab from this article (State 0 to State 1 on a, State 1 to State 2 on b, with State 2 accepting), trace the input a. Write down the state the machine ends in and whether the input matches.
Hints
- Start in State 0 and read the single character a.
- Ask whether the machine ends in the accepting state after consuming all the input.
Summary of the Matching Process
- 1A regular expression is a compact notation for a shape of text, and its syntax and meaning were designed so it can be converted into a state machine.
- 2A state machine is always in exactly one state, reads one character at a time, and matches when the input is consumed and the machine ends in an accepting state.
- 3The regular expression type compiles the pattern at run time, while the program is running, and matching then walks the resulting machine.
- 4Notation has variants, and the source's default is an ECMA-based variant used for JavaScript, so check the variant before reusing a pattern elsewhere.
At a glance
- A regular expression is a compact notation for a shape of text, designed so that it can be converted into a state machine.
- A state machine is always in exactly one state, reads input one character at a time, and matches when the input ends in an accepting state.
- The regular expression type compiles the pattern at run time, and matching then runs the resulting machine over the input.
- The source's default notation is a variant of the ECMA standard used for JavaScript, and other tools may read the notation differently.