automaton

An automaton library & basic programs written in Rust
git clone git://git.ethandl.dev/automaton
Log | Files | Refs | README

graph_enfa.rs (14774B)


      1 use crate::{
      2     dfa,
      3     enfa::{self, ENFA},
      4     Automaton, AutomatonError, Dot, FiniteStateAutomaton,
      5 };
      6 use petgraph::{graph::NodeIndex, visit::EdgeRef, Directed, Graph};
      7 use std::{
      8     collections::{BTreeMap, HashMap, HashSet},
      9     fmt::Display,
     10 };
     11 
     12 #[derive(Hash, Debug, PartialEq, Eq, Copy, Clone)]
     13 pub enum Transition {
     14     Char(char),
     15     Wildcard,
     16     Epsilon,
     17 }
     18 
     19 impl Display for Transition {
     20     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
     21         match self {
     22             Transition::Char(c) => write!(f, "'{}'", c),
     23             Transition::Wildcard => write!(f, "•"),
     24             Transition::Epsilon => write!(f, "ε"),
     25         }
     26     }
     27 }
     28 
     29 impl From<Transition> for enfa::Transition {
     30     fn from(transition: Transition) -> Self {
     31         match transition {
     32             Transition::Char(c) => enfa::Transition::Char(c),
     33             Transition::Wildcard => enfa::Transition::Wildcard,
     34             Transition::Epsilon => enfa::Transition::Epsilon,
     35         }
     36     }
     37 }
     38 
     39 impl From<enfa::Transition> for Transition {
     40     fn from(transition: enfa::Transition) -> Self {
     41         match transition {
     42             enfa::Transition::Char(c) => Transition::Char(c),
     43             enfa::Transition::Wildcard => Transition::Wildcard,
     44             enfa::Transition::Epsilon => Transition::Epsilon,
     45         }
     46     }
     47 }
     48 
     49 impl From<dfa::Transition> for Transition {
     50     fn from(transition: dfa::Transition) -> Self {
     51         match transition {
     52             dfa::Transition::Char(c) => Transition::Char(c),
     53             dfa::Transition::Wildcard => Transition::Wildcard,
     54         }
     55     }
     56 }
     57 
     58 /// Intended as an easy way of constructing an ε-NFA in bits, adding onto it much like you'd build an ε-NFA on a whiteboard.
     59 #[derive(Clone)]
     60 pub struct GraphENFA {
     61     alphabet: HashSet<char>,
     62     states: HashSet<u64>,
     63     initial_state: u64,
     64     final_states: HashSet<u64>,
     65     graph: Graph<u64, Transition, Directed>,
     66     state_id_to_index_map: HashMap<u64, NodeIndex>,
     67     current_state_id: u64,
     68 }
     69 
     70 impl GraphENFA {
     71     /// Create a new graph ε-NFA
     72     pub fn new() -> (Self, u64) {
     73         let mut states = HashSet::new();
     74         let mut state_id_to_index_map = HashMap::new();
     75         let mut graph = Graph::new();
     76 
     77         let initial_state_id = 0;
     78 
     79         let initial_state_index = graph.add_node(initial_state_id);
     80 
     81         states.insert(initial_state_id);
     82         state_id_to_index_map.insert(initial_state_id, initial_state_index);
     83 
     84         (
     85             GraphENFA {
     86                 alphabet: HashSet::new(),
     87                 states,
     88                 initial_state: initial_state_id,
     89                 final_states: HashSet::new(),
     90                 graph,
     91                 state_id_to_index_map,
     92                 current_state_id: initial_state_id,
     93             },
     94             initial_state_id,
     95         )
     96     }
     97 
     98     /// Set the acceptance of a state, will return an error if the state is already in the desired acceptance or the state doesn't exist in the ε-NFA.
     99     pub fn set_state_acceptance(
    100         &mut self,
    101         state_id: u64,
    102         accept: bool,
    103     ) -> Result<(), AutomatonError> {
    104         if !self.states.contains(&state_id) {
    105             return Err(AutomatonError::builder_error(
    106                 "There's no state with that ID!",
    107             ));
    108         }
    109 
    110         if accept && !self.final_states.contains(&state_id) && self.final_states.insert(state_id) // adding to final states
    111             || !accept // Removing from final states
    112                 && self.final_states.contains(&state_id)
    113                 && self.final_states.remove(&state_id)
    114         {
    115             Ok(())
    116         } else {
    117             Err(AutomatonError::builder_error(
    118                 "The state was already in the desired acceptance!",
    119             ))
    120         }
    121     }
    122 
    123     /// Changes the initial state of the ε-NFA to the state with ID `state_id`, this will remove the other initial state. Returns `Ok` of the previous initial state, or an error describing the failure
    124     pub fn set_initial_state(&mut self, state_id: u64) -> Result<u64, AutomatonError> {
    125         if !self.states.contains(&state_id) {
    126             return Err(AutomatonError::builder_error(
    127                 "There is no state with the specified ID",
    128             ));
    129         }
    130 
    131         let prev_initial_state_id = self.initial_state;
    132         self.initial_state = state_id;
    133 
    134         Ok(prev_initial_state_id)
    135     }
    136 
    137     /// Inserts a new state with no transitions into the ε-NFA with the ID `state_id`
    138     fn insert_state(&mut self, state_id: u64) -> Result<(), AutomatonError> {
    139         if self.states.contains(&state_id) {
    140             return Err(AutomatonError::builder_error(
    141                 "There's already a state with that ID",
    142             ));
    143         }
    144 
    145         let node_index = self.graph.add_node(state_id);
    146         if !self
    147             .state_id_to_index_map
    148             .insert(state_id, node_index)
    149             .is_none()
    150         {
    151             return Err(AutomatonError::builder_error(
    152                 "There's already a state with that ID",
    153             ));
    154         }
    155 
    156         if !self.states.insert(state_id) {
    157             return Err(AutomatonError::builder_error(
    158                 "There's already a state with that ID",
    159             ));
    160         }
    161         Ok(())
    162     }
    163 
    164     /// Inserts a new state with no transitions into the ε-NFA with the ID of the max existing ID + 1.
    165     pub fn new_state(&mut self) -> Result<u64, AutomatonError> {
    166         self.current_state_id += 1;
    167 
    168         self.insert_state(self.current_state_id)?;
    169 
    170         Ok(self.current_state_id)
    171     }
    172 
    173     /// Add a transition from one state to another, returns an error if either of the states doesn't exist.
    174     /// An epsilon transition is done using the `None` of the `Option<char>` enum.
    175     pub fn insert_transition(
    176         &mut self,
    177         from_id: u64,
    178         to_id: u64,
    179         input: Transition,
    180     ) -> Result<(), AutomatonError> {
    181         if !self.states.contains(&from_id) || !self.states.contains(&to_id) {
    182             return Err(AutomatonError::builder_error(
    183                 "Tried adding a transition to/from a state that doesn't exist!",
    184             ));
    185         }
    186 
    187         match input {
    188             Transition::Char(c) => {
    189                 if !self.alphabet.contains(&c) {
    190                     self.alphabet.insert(c);
    191                 }
    192             }
    193             _ => (),
    194         }
    195 
    196         let from_index = self
    197             .state_id_to_index_map
    198             .get(&from_id)
    199             .ok_or(AutomatonError::builder_error(
    200                 "Couldn't get the index of the from state!",
    201             ))?
    202             .to_owned();
    203 
    204         let to_index = self
    205             .state_id_to_index_map
    206             .get(&to_id)
    207             .ok_or(AutomatonError::builder_error(
    208                 "Couldn't get the index of the to state!",
    209             ))?
    210             .to_owned();
    211 
    212         self.graph.add_edge(from_index, to_index, input);
    213 
    214         Ok(())
    215     }
    216 
    217     /// Convert into a regular ε-NFA backed by a `HashMap` instead of a `Graph`
    218     /// Whilst this can be used, the `as_dfa` method should be used if you're going to evaluate string acceptance.
    219     pub fn as_enfa(&self) -> Result<ENFA, AutomatonError> {
    220         let mut transition_table: HashMap<(u64, enfa::Transition), Vec<u64>> = HashMap::new();
    221         let mut current_states = vec![self.initial_state];
    222         let mut next_states = vec![];
    223         let mut visited = HashSet::new();
    224 
    225         while current_states.len() != 0 {
    226             for &current_state in current_states.iter() {
    227                 let current_state_index = self
    228                     .state_id_to_index_map
    229                     .get(&current_state)
    230                     .ok_or(AutomatonError::conversion_error(
    231                         "Couldn't get graph index of a state!",
    232                     ))?
    233                     .to_owned();
    234                 for edge in self.graph.edges(current_state_index) {
    235                     let mut transitions = transition_table
    236                         .get(&(current_state, Transition::into(*edge.weight())))
    237                         .map_or(vec![], |v: &Vec<u64>| v.to_owned());
    238                     let dest = self
    239                         .graph
    240                         .node_weight(edge.target())
    241                         .ok_or(AutomatonError::conversion_error(
    242                             "Error converting from graph to regular NFA!",
    243                         ))?
    244                         .to_owned();
    245                     if !transitions.contains(&dest) {
    246                         transitions.push(dest);
    247                     }
    248                     if !visited.contains(&dest) {
    249                         next_states.push(dest);
    250                     }
    251                     transition_table.insert(
    252                         (current_state, Transition::into(*edge.weight())),
    253                         transitions,
    254                     );
    255                 }
    256 
    257                 visited.insert(current_state);
    258             }
    259             current_states = next_states;
    260             next_states = vec![];
    261         }
    262 
    263         Ok(ENFA::new(
    264             self.alphabet.clone(),
    265             self.states.clone(),
    266             self.initial_state,
    267             self.final_states.clone(),
    268             transition_table,
    269         ))
    270     }
    271 }
    272 
    273 impl Automaton for GraphENFA {
    274     // This should never be used
    275     fn accepts(&self, input: &str) -> bool {
    276         let dfa = self.as_dfa().expect("Woops!");
    277         dfa.accepts(input)
    278     }
    279 }
    280 
    281 impl FiniteStateAutomaton for GraphENFA {
    282     /// Converts the `Graph` ε-NFA into a regular ε-NFA backed by a `HashMap`, then converts that to a DFA
    283     fn as_dfa(&self) -> std::result::Result<crate::dfa::DFA, AutomatonError> {
    284         let enfa = self.as_enfa()?;
    285         enfa.as_dfa()
    286     }
    287 
    288     /// This should never be used... Unless you really badly want to modify an existing DFA.
    289     fn from_dfa(dfa: crate::dfa::DFA) -> std::result::Result<Self, AutomatonError>
    290     where
    291         Self: Sized,
    292     {
    293         let mut state_id_to_index_map: HashMap<u64, NodeIndex> = HashMap::new();
    294         let mut graph = Graph::new();
    295 
    296         for ((from, input), to) in dfa.transition_table {
    297             let from_index = state_id_to_index_map
    298                 .get(&from)
    299                 .map_or_else(|| graph.add_node(from), |n| n.to_owned());
    300             state_id_to_index_map.insert(from, from_index);
    301             let to_index = state_id_to_index_map
    302                 .get(&to)
    303                 .map_or_else(|| graph.add_node(to), |n| n.to_owned());
    304             state_id_to_index_map.insert(to, to_index);
    305             graph.add_edge(from_index, to_index, Transition::from(input));
    306         }
    307 
    308         let current_state_id = *dfa
    309             .states
    310             .iter()
    311             .max()
    312             .ok_or(AutomatonError::conversion_error(
    313                 "Unable to get the current state ID",
    314             ))?;
    315 
    316         Ok(GraphENFA {
    317             alphabet: dfa.alphabet,
    318             states: dfa.states,
    319             initial_state: dfa.initial_state,
    320             final_states: dfa.final_states,
    321             graph,
    322             state_id_to_index_map,
    323             current_state_id,
    324         })
    325     }
    326 }
    327 
    328 impl Display for GraphENFA {
    329     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    330         let mut alphabet_vec: Vec<char> = self.alphabet.iter().copied().collect();
    331         let mut states_vec: Vec<u64> = self.states.iter().copied().collect();
    332         let mut final_states_vec: Vec<u64> = self.final_states.iter().copied().collect();
    333 
    334         alphabet_vec.sort();
    335         states_vec.sort();
    336         final_states_vec.sort();
    337 
    338         let alphabet_str: String =
    339             alphabet_vec
    340                 .iter()
    341                 .enumerate()
    342                 .fold("[".to_owned(), |st, (index, c)| {
    343                     let ending = if index == self.alphabet.len() - 1 {
    344                         "]"
    345                     } else {
    346                         ", "
    347                     };
    348                     format!("{}{}{}", st, c, ending)
    349                 });
    350 
    351         let states_str: String =
    352             states_vec
    353                 .iter()
    354                 .enumerate()
    355                 .fold("[".to_owned(), |st, (index, n)| {
    356                     let ending = if index == self.states.len() - 1 {
    357                         "]"
    358                     } else {
    359                         ", "
    360                     };
    361                     format!("{}{}{}", st, n, ending)
    362                 });
    363 
    364         let initial_state_str: String = self.initial_state.to_string();
    365 
    366         let final_states_str: String =
    367             final_states_vec
    368                 .iter()
    369                 .enumerate()
    370                 .fold("[".to_owned(), |st, (index, n)| {
    371                     let ending = if index == self.final_states.len() - 1 {
    372                         "]"
    373                     } else {
    374                         ", "
    375                     };
    376                     format!("{}{}{}", st, n, ending)
    377                 });
    378 
    379         writeln!(f, "Alphabet: {}", alphabet_str)?;
    380         writeln!(f, "States: {}", states_str)?;
    381         writeln!(f, "Initial State: {}", initial_state_str)?;
    382         writeln!(f, "Final States: {}", final_states_str)?;
    383         let graphviz_repr: Result<Dot<Transition>, AutomatonError> = self.try_into();
    384         match graphviz_repr {
    385             Ok(s) => writeln!(f, "Graphviz Representation:\n{}", s),
    386             Err(e) => writeln!(f, "Error getting Graphviz representation: {}", e),
    387         }
    388     }
    389 }
    390 
    391 impl From<&GraphENFA> for Graph<u64, Transition, Directed> {
    392     fn from(graph_enfa: &GraphENFA) -> Self {
    393         graph_enfa.graph.clone()
    394     }
    395 }
    396 
    397 impl TryFrom<&GraphENFA> for Dot<Transition> {
    398     type Error = AutomatonError;
    399     fn try_from(graph_enfa: &GraphENFA) -> Result<Self, Self::Error> {
    400         let states = graph_enfa
    401             .states
    402             .iter()
    403             .map(|s| graph_enfa.state_id_to_index_map.get(s).map(|&ix| (*s, ix)))
    404             .collect::<Option<BTreeMap<u64, _>>>()
    405             .ok_or(AutomatonError::dot_error(
    406                 "Unable to get the graph index of one of the states!",
    407             ))?;
    408         let initial_state_id =
    409             *states
    410                 .get(&graph_enfa.initial_state)
    411                 .ok_or(AutomatonError::dot_error(
    412                     "Unable to get the graph index of the initial state!",
    413                 ))?;
    414         let final_states = graph_enfa
    415             .final_states
    416             .iter()
    417             .map(|s| states.get(s).map(|&ix| (*s, ix)))
    418             .collect::<Option<BTreeMap<u64, _>>>()
    419             .ok_or(AutomatonError::dot_error(
    420                 "Unable to get the graph index of a final state!",
    421             ))?;
    422         Ok(Self::new(
    423             graph_enfa.into(),
    424             (graph_enfa.initial_state, initial_state_id),
    425             final_states,
    426         ))
    427     }
    428 }