build-a-regex-engine / lesson-23.md
Lesson 23 · The Thompson NFA

Following the splits

Before the machine can step on a character, it has to know every state it could currently be in - which means following all the Split forks. That set-gathering is today's whole job.

The goal

Write addState, which follows Split states to collect the reachable Char and Match states into a set.

Start here - the target
TO DO
Scenario: Following splits collects the reachable consuming states
Giventhe compiled NFA for "a|b"
Whenthe start state is expanded with addState
Thenthe resulting set contains the Char 'a' state and the Char 'b' state
Andthe set does not contain the Split state itself, since a Split consumes no input
Background

A Split consumes no input, so the machine passes straight through it - possibly through several in a row. Before you can match the next character, you need the full set of consuming states (the Char and Match states) reachable from where you are by following Split arrows only. That’s addState: given a state, if it’s a Split, recurse into both of its arrows; otherwise add it to the set. This is the NFA’s epsilon-closure - the states reachable without eating a character.

For the a|b NFA, expanding the start Split yields exactly the a and b Char states, and not the Split itself. One practical caution: loops built by star and plus mean a naive recursion could revisit a Split forever. The standard guard is to stamp each state with the current step number as you add it, and skip any state already stamped this step. With the closure in hand, tomorrow’s simulator is a short loop.

Make it work
func addState(list *[]*State, s *State) {
if s == nil { return }
if s.Kind == Split {
addState(list, s.Out) // follow BOTH arrows
addState(list, s.Out1)
return
}
*list = append(*list, s) // Char or Match: a real stop
}
CheckpointDONE
addState follows all Split forks to collect the states the machine can currently be in. Commit and stop here.