automaton

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

dfa.rs (10240B)


      1 use core::fmt::Display;
      2 use petgraph::{Directed, Graph};
      3 use std::{
      4     collections::{BTreeMap, HashMap, HashSet},
      5     fmt,
      6 };
      7 
      8 use crate::{Automaton, AutomatonError, Dot, Encodable, FiniteStateAutomaton};
      9 use serde_json::{from_str, Value};
     10 
     11 #[derive(Hash, Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
     12 pub enum Transition {
     13     Char(char),
     14     Wildcard,
     15 }
     16 
     17 impl Display for Transition {
     18     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
     19         match self {
     20             Transition::Char(c) => write!(f, "'{}'", c),
     21             Transition::Wildcard => write!(f, "•"),
     22         }
     23     }
     24 }
     25 
     26 #[derive(Clone, Debug, PartialEq)]
     27 pub struct DFA {
     28     pub alphabet: HashSet<char>,
     29     pub states: HashSet<u64>,
     30     pub initial_state: u64,
     31     pub final_states: HashSet<u64>,
     32     pub transition_table: HashMap<(u64, Transition), u64>,
     33 }
     34 
     35 impl Automaton for DFA {
     36     fn accepts(&self, input: &str) -> bool {
     37         let mut current_state: u64 = self.initial_state;
     38         for c in input.chars() {
     39             match self
     40                 .transition_table
     41                 .get(&(current_state, Transition::Char(c)))
     42                 .or(self
     43                     .transition_table
     44                     .get(&(current_state, Transition::Wildcard)))
     45             {
     46                 Some(state) => current_state = state.to_owned(),
     47                 None => {
     48                     eprintln!("The DFA's transition table is incomplete or malformed!");
     49                     return false;
     50                 }
     51             }
     52         }
     53         self.final_states.contains(&current_state)
     54     }
     55 }
     56 
     57 impl FiniteStateAutomaton for DFA {
     58     fn as_dfa(&self) -> Result<DFA, AutomatonError> {
     59         Ok(self.clone())
     60     }
     61 
     62     fn from_dfa(dfa: self::DFA) -> Result<Self, AutomatonError>
     63     where
     64         Self: Sized,
     65     {
     66         Ok(dfa)
     67     }
     68 
     69     fn convert(automaton: Box<dyn FiniteStateAutomaton>) -> Result<Self, AutomatonError>
     70     where
     71         Self: Sized,
     72     {
     73         automaton.as_dfa()
     74     }
     75 }
     76 
     77 impl Display for DFA {
     78     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
     79         let mut transition_vec: Vec<Transition> =
     80             self.alphabet.iter().map(|&c| Transition::Char(c)).collect();
     81         let mut states_vec: Vec<u64> = self.states.iter().copied().collect();
     82         let mut final_states_vec: Vec<u64> = self.final_states.iter().copied().collect();
     83 
     84         transition_vec.push(Transition::Wildcard);
     85         transition_vec.sort();
     86         states_vec.sort();
     87         final_states_vec.sort();
     88 
     89         let transition_str = format!("{:?}", transition_vec);
     90         let states_str = format!("{:?}", states_vec);
     91         let initial_state_str = self.initial_state.to_string();
     92         let final_states_str = format!("{:?}", final_states_vec);
     93 
     94         let transition_tbl_hdr = transition_vec
     95             .iter()
     96             .fold("\t|".to_owned(), |acc, &t| format!("{}\t{}\t|", acc, t));
     97 
     98         let mut transition_map: HashMap<u64, Vec<Option<u64>>> = HashMap::new();
     99         for &state in states_vec.iter() {
    100             let input_dests = transition_vec
    101                 .iter()
    102                 .map(|&transition| {
    103                     self.transition_table
    104                         .get(&(state, transition))
    105                         .and_then(|v| Some(v.to_owned()))
    106                 })
    107                 .collect();
    108             transition_map.insert(state, input_dests);
    109         }
    110 
    111         let transition_table_str =
    112             transition_map
    113                 .iter()
    114                 .fold(transition_tbl_hdr, |acc, (from, dest_groups)| {
    115                     let line_str = dest_groups.into_iter().fold("".to_owned(), |acc, dests| {
    116                         let dest_str = match dests {
    117                             None => "∅".to_owned(),
    118                             Some(v) => v.to_string(),
    119                         };
    120                         format!("{}{}\t|\t", acc, dest_str)
    121                     });
    122                     format!("{}\n{}\t|\t{}", acc, from, line_str)
    123                 });
    124         writeln!(f, "Alphabet: {}", transition_str)?;
    125         writeln!(f, "States: {}", states_str)?;
    126         writeln!(f, "Initial State: {}", initial_state_str)?;
    127         writeln!(f, "Final States: {}", final_states_str)?;
    128         writeln!(f, "Transition Table:\n{}", transition_table_str)?;
    129         let graphviz_repr: Result<Dot<Transition>, AutomatonError> = self.try_into();
    130         match graphviz_repr {
    131             Ok(s) => writeln!(f, "Graphviz Representation:\n{}", s),
    132             Err(e) => writeln!(f, "Error getting Graphviz representation: {}", e),
    133         }
    134     }
    135 }
    136 
    137 // This is the impl that should be used when reading DFA files
    138 impl Encodable<&str> for DFA {
    139     fn parse(json_str: &str) -> Result<DFA, AutomatonError> {
    140         DFA::parse(from_str::<Value>(json_str)?)
    141     }
    142 }
    143 
    144 impl Encodable<Value> for DFA {
    145     fn parse(json: Value) -> Result<DFA, AutomatonError> {
    146         let alphabet_vec: Vec<char> = json["alphabet"]
    147             .as_array()
    148             .ok_or(AutomatonError::parse_error(
    149                 "Could not get the alphabet from the JSON",
    150             ))?
    151             .iter()
    152             .map(|v| v.to_string().chars().next())
    153             .collect::<Option<Vec<char>>>()
    154             .ok_or(AutomatonError::parse_error(
    155                 "Could not convert the alphabet to UTF-8 characters",
    156             ))?;
    157 
    158         let alphabet: HashSet<char> = alphabet_vec.iter().map(char::to_owned).collect();
    159 
    160         let states: HashSet<u64> = json["states"]
    161             .as_array()
    162             .ok_or(AutomatonError::parse_error(
    163                 "Could not get the states from the JSON",
    164             ))?
    165             .iter()
    166             .map(|v| v.as_u64())
    167             .collect::<Option<HashSet<u64>>>()
    168             .ok_or(AutomatonError::parse_error(
    169                 "Could not convert the states to u64",
    170             ))?;
    171 
    172         let initial_state: u64 =
    173             json["initial_state"]
    174                 .as_u64()
    175                 .ok_or(AutomatonError::parse_error(
    176                     "Initial state incorrectly formatted",
    177                 ))?;
    178 
    179         let final_states: HashSet<u64> = json["final_states"]
    180             .as_array()
    181             .ok_or(AutomatonError::parse_error(
    182                 "Could not get the final states from the JSON",
    183             ))?
    184             .iter()
    185             .map(|v| v.as_u64())
    186             .collect::<Option<_>>()
    187             .ok_or(AutomatonError::parse_error(
    188                 "Could not convert the final states to u64",
    189             ))?;
    190 
    191         let mut transition_table: HashMap<(u64, Transition), u64> = HashMap::new();
    192         for row in json["transition_table"]
    193             .as_array()
    194             .ok_or(AutomatonError::parse_error(
    195                 "Unable to parse rows  in transition table",
    196             ))?
    197             .iter()
    198         {
    199             let transitions = row
    200                 .as_array()
    201                 .ok_or(AutomatonError::parse_error(
    202                     "Unable to get a row in the transition table",
    203                 ))?
    204                 .to_owned();
    205 
    206             let from: u64 = transitions
    207                 .get(0)
    208                 .ok_or(AutomatonError::parse_error(
    209                     "Unable to get the \"from\" state of a row",
    210                 ))?
    211                 .as_u64()
    212                 .ok_or(AutomatonError::parse_error(
    213                     "Unable to parse the  \"from\" state of a row as a u64",
    214                 ))?;
    215 
    216             //let dests: Vec<(char, u64)> = transitions[1..]
    217             let dests: Vec<(char, u64)> = alphabet_vec
    218                 .iter()
    219                 .zip(transitions.iter().skip(1).map(Value::as_u64))
    220                 .map(|(input, dest)| dest.and_then(|v| Some((*input, v))))
    221                 .collect::<Option<_>>()
    222                 .ok_or(AutomatonError::parse_error(
    223                     "Unable to parse the transition destinations in the table",
    224                 ))?;
    225             for (input, dest) in dests.iter() {
    226                 transition_table.insert((from, Transition::Char(*input)), *dest);
    227             }
    228         }
    229 
    230         Ok(DFA {
    231             alphabet,
    232             states,
    233             final_states,
    234             transition_table,
    235             initial_state,
    236         })
    237     }
    238 }
    239 
    240 impl TryFrom<&DFA> for Dot<Transition> {
    241     type Error = AutomatonError;
    242     fn try_from(dfa: &DFA) -> Result<Self, Self::Error> {
    243         let mut graph: Graph<u64, Transition, Directed> = Graph::new();
    244 
    245         let states: HashMap<u64, _> = dfa.states.iter().map(|&s| (s, graph.add_node(s))).collect();
    246 
    247         let mut alphabet: HashSet<Transition> =
    248             dfa.alphabet.iter().map(|&c| Transition::Char(c)).collect();
    249         alphabet.insert(Transition::Wildcard);
    250 
    251         for transition in alphabet {
    252             for (from, from_id, dest) in states.iter().filter_map(|(&from, &from_id)| {
    253                 dfa.transition_table
    254                     .get(&(from, transition))
    255                     .map(|dest| (from, from_id, dest))
    256             }) {
    257                 let dest_id = *states.get(dest).ok_or(AutomatonError::dot_error(
    258                     "The destination of a transition does not exist in the dfa states!",
    259                 ))?; // Maybe not really necessary...
    260                 if transition == Transition::Wildcard
    261                     || !dfa
    262                         .transition_table
    263                         .get(&(from, Transition::Wildcard))
    264                         .is_some_and(|wildcard_dest| dest == wildcard_dest)
    265                 {
    266                     graph.add_edge(from_id, dest_id, transition);
    267                 }
    268             }
    269         }
    270 
    271         let initial_state_id = *states
    272             .get(&dfa.initial_state)
    273             .ok_or(AutomatonError::dot_error(
    274                 "Unable to get initial state graph index!",
    275             ))?;
    276 
    277         let final_states = dfa
    278             .final_states
    279             .iter()
    280             .map(|s| states.get(s).map(|&ix| (*s, ix)))
    281             .collect::<Option<BTreeMap<u64, _>>>()
    282             .ok_or(AutomatonError::dot_error(
    283                 "Unable to get the graph index of a final state!",
    284             ))?;
    285 
    286         Ok(Self::new(
    287             graph,
    288             (dfa.initial_state, initial_state_id),
    289             final_states,
    290         ))
    291     }
    292 }