automaton

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

automaton.rs (4957B)


      1 use petgraph::{graph::NodeIndex, Directed, Graph};
      2 use std::collections::BTreeMap;
      3 use std::error::Error;
      4 use std::fmt::Display;
      5 use std::panic::Location;
      6 
      7 pub mod web_automaton;
      8 
      9 pub mod dfa;
     10 pub mod enfa;
     11 pub mod graph_enfa;
     12 pub mod nfa;
     13 pub mod regex;
     14 
     15 #[derive(Debug)]
     16 pub enum AutomatonError {
     17     Tokenise(String),
     18     Parse(String),
     19     Convertion(String),
     20     Builder(String),
     21     Dot(String),
     22 }
     23 
     24 impl AutomatonError {
     25     pub fn cause(&self) -> String {
     26         match self {
     27             Self::Tokenise(s) => s.to_owned(),
     28             Self::Parse(s) => s.to_owned(),
     29             Self::Convertion(s) => s.to_owned(),
     30             Self::Builder(s) => s.to_owned(),
     31             Self::Dot(s) => s.to_owned(),
     32         }
     33     }
     34 
     35     #[track_caller]
     36     fn error_str(err_type: &str, reason: &str, loc: Option<&Location>) -> String {
     37         let location = loc.unwrap_or(Location::caller());
     38         format!("[{}::{} ERROR] {}", location, err_type, reason)
     39     }
     40 
     41     #[track_caller]
     42     fn tokenise_error(reason: &str) -> Self {
     43         let location = Location::caller();
     44         AutomatonError::Tokenise(AutomatonError::error_str(
     45             "Tokenisation",
     46             reason,
     47             Some(location),
     48         ))
     49     }
     50 
     51     #[track_caller]
     52     fn parse_error(reason: &str) -> Self {
     53         let location = Location::caller();
     54         AutomatonError::Parse(AutomatonError::error_str("Parsing", reason, Some(location)))
     55     }
     56 
     57     #[track_caller]
     58     fn conversion_error(reason: &str) -> Self {
     59         let location = Location::caller();
     60         AutomatonError::Convertion(AutomatonError::error_str(
     61             "Conversion",
     62             reason,
     63             Some(location),
     64         ))
     65     }
     66 
     67     #[track_caller]
     68     fn builder_error(reason: &str) -> Self {
     69         let location = Location::caller();
     70         AutomatonError::Builder(AutomatonError::error_str("Builder", reason, Some(location)))
     71     }
     72 
     73     #[track_caller]
     74     fn dot_error(reason: &str) -> Self {
     75         let location = Location::caller();
     76         AutomatonError::Dot(AutomatonError::error_str("Dot", reason, Some(location)))
     77     }
     78 }
     79 
     80 impl Error for AutomatonError {}
     81 
     82 impl Display for AutomatonError {
     83     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
     84         write!(f, "{}", self.cause())
     85     }
     86 }
     87 
     88 impl From<serde_json::Error> for AutomatonError {
     89     fn from(err: serde_json::Error) -> Self {
     90         Self::parse_error(&err.to_string())
     91     }
     92 }
     93 
     94 pub trait Automaton: Display {
     95     fn accepts(&self, input: &str) -> bool;
     96 }
     97 
     98 pub trait FiniteStateAutomaton: Automaton {
     99     fn as_dfa(&self) -> Result<dfa::DFA, AutomatonError>;
    100     fn from_dfa(dfa: dfa::DFA) -> Result<Self, AutomatonError>
    101     where
    102         Self: Sized;
    103     fn convert(automaton: Box<dyn FiniteStateAutomaton>) -> Result<Self, AutomatonError>
    104     where
    105         Self: Sized,
    106     {
    107         Self::from_dfa(automaton.as_dfa()?)
    108     }
    109 }
    110 
    111 pub trait Encodable<T>
    112 where
    113     Self: Sized,
    114 {
    115     fn parse(encoding: T) -> Result<Self, AutomatonError>;
    116 }
    117 
    118 pub struct Dot<V>
    119 where
    120     V: Display,
    121 {
    122     graph: Graph<u64, V, Directed>,
    123     initial_state: (u64, NodeIndex),
    124     final_states: BTreeMap<u64, NodeIndex>,
    125 }
    126 
    127 impl<V> Dot<V>
    128 where
    129     V: Display,
    130 {
    131     pub fn new(
    132         graph: Graph<u64, V, Directed>,
    133         initial_state: (u64, NodeIndex),
    134         final_states: BTreeMap<u64, NodeIndex>,
    135     ) -> Dot<V> {
    136         Dot {
    137             graph,
    138             initial_state,
    139             final_states,
    140         }
    141     }
    142 }
    143 
    144 impl<V> Display for Dot<V>
    145 where
    146     V: Display,
    147 {
    148     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    149         writeln!(f, "digraph {{")?;
    150         writeln!(f, "    entrypoint [ label = \"\", shape = none ]")?;
    151 
    152         for (id, label) in self
    153             .graph
    154             .node_indices()
    155             .filter_map(|id| Some((id, *self.graph.node_weight(id)?)))
    156         {
    157             let shape = if self.final_states.contains_key(&label) {
    158                 "doublecircle"
    159             } else {
    160                 "circle"
    161             };
    162             writeln!(
    163                 f,
    164                 "    {} [ label = \"{}\", shape = {} ]",
    165                 id.index(),
    166                 label,
    167                 shape
    168             )?;
    169         }
    170         writeln!(f, "    entrypoint -> {}", self.initial_state.1.index())?;
    171         for (from, to, transition) in self.graph.edge_indices().filter_map(|ex| {
    172             let (from_ix, to_ix) = self.graph.edge_endpoints(ex)?;
    173             let label = self.graph.edge_weight(ex)?.to_string();
    174             Some((from_ix, to_ix, label))
    175         }) {
    176             writeln!(
    177                 f,
    178                 "    {} -> {} [ label = \"{}\" ]",
    179                 from.index(),
    180                 to.index(),
    181                 transition
    182             )?;
    183         }
    184         writeln!(f, "}}")
    185     }
    186 }
    187 
    188 #[cfg(test)]
    189 mod tests {
    190     mod testlib {
    191         pub mod util;
    192     }
    193 
    194     mod dfa_tests;
    195     mod enfa_tests;
    196     mod nfa_tests;
    197     mod regex_tests;
    198 }