let elim_dead_states (nfa : nfa) : unit =
  let live_states = backward_reachable nfa nfa.f in
  let dead_lhs = create def_machine_size in
  let dead_rhs = create (def_machine_size * def_delta_size) in
  let dead_eps = create def_machine_size in
  let delta_iter q1 rhs =
    if mem live_states q1 then
      let rhs_iter q2 _ = if not (mem live_states q2) then
        add dead_rhs (q1, rhs, q2) 
      in
        Hashtbl.iter rhs_iter rhs
    else
      add dead_lhs q1
  in
  let epsilon_iter q1 rhs =
    if mem live_states q1 then 
      Hashtbl.replace nfa.epsilon q1 (cap rhs live_states)
    else
      add dead_eps q1
  in
  let do_remove_lhs q = Hashtbl.remove nfa.delta q in
  let do_remove_rhs (q1, tbl, q2) =
    if Hashtbl.length tbl = 1 then 
      Hashtbl.remove nfa.delta q1
    else
      Hashtbl.remove tbl q2
  in
    (* Because Hashtbl dislikes removing elements while iterating, we
       first figure out what to remove and then actually do the
       removal.  *)

    Hashtbl.iter delta_iter nfa.delta;
    Hashtbl.iter epsilon_iter nfa.epsilon;
    nfa.q <- live_states;
    iter do_remove_lhs dead_lhs;
    iter do_remove_rhs dead_rhs