automata_like_programming/
automaton.rs

1use std::{marker::PhantomData, rc::Rc};
2
3use crate::automaton_state::SharedAutomatonState;
4
5/// Result of an attempt of determining next target state.
6pub enum NextState<'a, Id, D, E> {
7    /// Automaton should take provided state for the next iteration.
8    Continue(SharedAutomatonState<'a, Id, D, E>),
9    /// The input data has ended so there is no way for matching next state.
10    ProcessEnded,
11    /// There are no possible target states for received input data.
12    NotFound,
13}
14
15/// Finite-state automaton that crawls around a specified graph until no more state changes can be done.
16pub struct Automaton<'a, Id, D, E> {
17    start_state: SharedAutomatonState<'a, Id, D, E>,
18    _data_phantom: PhantomData<D>,
19    _error_phantom: PhantomData<E>,
20}
21
22/// Provides information on why automaton has stopped executing.
23pub enum AutomatonResult<Id, E> {
24    // Ok, // Not needed - should end because no more keys, no state could be found or state forces the end of process (no default ending).
25    /// Automaton execution ended because no more keys could be extracted. Contains identifier of current state in automaton execution - no more
26    /// keys could be extracted after reaching this state.
27    EmptyIter(
28        Id
29    ),
30    /// No connection could be matched for a key. Contains identifier of current state in automaton execution - no connections could be found on this state for given key.
31    CouldNotFindNextState(
32        Id
33    ),
34    /// An error occured while executing function assigned to connection. Contains error generated while changing state.
35    Error(
36        E
37    )
38}
39
40impl <Id, E> AutomatonResult<Id, E> {
41    pub fn is_empty_iter(&self) -> bool {
42        return matches!(self, AutomatonResult::EmptyIter(_))
43    }
44
45    pub fn is_could_not_find_next_state(&self) -> bool {
46        return matches!(self, AutomatonResult::CouldNotFindNextState(_))
47    }
48
49    pub fn is_error(&self) -> bool {
50        return matches!(self, AutomatonResult::Error(_))
51    }
52
53    pub fn expect_empty_iter(self) -> Result<Id, AutomatonResult<Id, E>> {
54        if let AutomatonResult::EmptyIter(id) = self {
55            Result::Ok(id)
56        } else {
57            Result::Err(self)
58        }
59    }
60
61    pub fn expect_could_not_find_next_state(self) -> Result<Id, AutomatonResult<Id, E>> {
62        if let AutomatonResult::CouldNotFindNextState(id) = self {
63            Result::Ok(id)
64        } else {
65            Result::Err(self)
66        }
67    }
68
69    pub fn expect_error(&self) -> Result<&E, &AutomatonResult<Id, E>> {
70        if let AutomatonResult::Error(err) = self {
71            Result::Ok(err)
72        } else {
73            Result::Err(self)
74        }
75    }
76}
77
78impl <'a, Id, D, E> Automaton<'a, Id, D, E> {
79    /// Creates new automaton with graph initiated by specified function.
80    pub fn new_init_fn<FInit: Fn() -> SharedAutomatonState<'a, Id, D, E>>(f_state_graph_init: FInit) -> Self {
81        Self::new(f_state_graph_init())
82    }
83
84    /// Creates new automaton with graph starting at specified state.
85    pub fn new(graph_root_state: SharedAutomatonState<'a, Id, D, E>) -> Self {
86        Self {start_state: graph_root_state, _data_phantom: PhantomData{}, _error_phantom: PhantomData{}}
87    }
88
89    /// Starts automaton with given data.
90    pub fn run(&mut self, data: &mut D) -> AutomatonResult<Id, E> {
91        let mut current_state = Rc::clone(&self.start_state);
92        loop {
93            let connection_execute_result = current_state.borrow().execute_next_connection(data);
94            match connection_execute_result {
95                Err(err) => {
96                    return AutomatonResult::Error(err);
97                },
98                Ok(next_state_result) => {
99                    match next_state_result {
100                        NextState::Continue(next_state) => current_state = next_state,
101                        NextState::NotFound => return AutomatonResult::CouldNotFindNextState(current_state.borrow().get_id_owned()),
102                        NextState::ProcessEnded => return AutomatonResult::EmptyIter(current_state.borrow().get_id_owned()),
103                    };
104                },
105            };
106        };
107    }
108}
109
110#[cfg(test)]
111pub mod test {
112    use std::rc::Rc;
113
114    use crate::{automaton::AutomatonResult, automaton_state::{new_shared_automaton_state, AutomatonState, SharedAutomatonState}};
115
116    use super::{Automaton, NextState};
117
118    pub struct TestNodeHello<'a> {
119        next_state: Option<SharedAutomatonState<'a, u8, String, String>>
120    }
121
122    impl<'a> TestNodeHello <'a> {
123        pub fn new(next_state: Option<SharedAutomatonState<'a, u8, String, String>>) -> Self {
124            Self { next_state }
125        }
126    }
127
128    impl <'a> AutomatonState<'a, u8, String, String> for TestNodeHello<'a> {
129        fn get_id_owned(&self) -> u8 {
130            1
131        }
132        
133        fn get_id(&self) -> &u8 {
134            &1
135        }
136        
137        fn execute_next_connection(&self, data: &mut String) -> Result<NextState<'a, u8, String, String>, String> {
138            data.push_str("Hello");
139            if let Option::Some(nxt_state) = &self.next_state {
140                Result::Ok(NextState::Continue(Rc::clone(nxt_state)))
141            } else {
142                Result::Ok(NextState::NotFound)
143            }
144        }
145    }
146
147    pub struct TestNodeWorld {
148    }
149
150    impl TestNodeWorld {
151        pub fn new() -> Self {
152            Self {  }
153        }
154    }
155
156    impl <'a> AutomatonState<'a, u8, String, String> for TestNodeWorld {
157        fn get_id_owned(&self) -> u8 {
158            2
159        }
160        
161        fn get_id(&self) -> &u8 {
162            &2
163        }
164        
165        fn execute_next_connection(&self, data: &mut String) -> Result<NextState<'a, u8, String, String>, String> {
166            data.push_str(" world");
167            Result::Ok(NextState::ProcessEnded)
168        }
169    }
170
171    #[test]
172    fn automaton_2_nodes_works() -> () {
173        let mut data = String::with_capacity(11);
174        let mut automaton = Automaton::new({
175            let world_state: SharedAutomatonState<u8, String, _> = new_shared_automaton_state(TestNodeWorld::new());
176            let hello_state: SharedAutomatonState<u8, String, _> = new_shared_automaton_state(TestNodeHello::new(Option::Some(Rc::clone(&world_state))));
177            hello_state
178        });
179        let run_res = automaton.run(&mut data);
180        assert!(matches!(run_res, AutomatonResult::EmptyIter(2)));
181        assert_eq!(data, "Hello world");
182    }
183
184    #[test]
185    fn automaton_result_is_empty_iter() -> () {
186        assert!(AutomatonResult::<u8, String>::EmptyIter(1).is_empty_iter());
187        assert!(!AutomatonResult::<u8, String>::CouldNotFindNextState(1).is_empty_iter());
188        assert!(!AutomatonResult::<u8, String>::Error(String::from("Test error")).is_empty_iter());
189    }
190
191    #[test]
192    fn automaton_result_is_could_not_find_next_state() -> () {
193        assert!(!AutomatonResult::<u8, String>::EmptyIter(1).is_could_not_find_next_state());
194        assert!(AutomatonResult::<u8, String>::CouldNotFindNextState(1).is_could_not_find_next_state());
195        assert!(!AutomatonResult::<u8, String>::Error(String::from("Test error")).is_could_not_find_next_state());
196    }
197
198    #[test]
199    fn automaton_result_is_error() -> () {
200        assert!(!AutomatonResult::<u8, String>::EmptyIter(1).is_error());
201        assert!(!AutomatonResult::<u8, String>::CouldNotFindNextState(1).is_error());
202        assert!(AutomatonResult::<u8, String>::Error(String::from("Test error")).is_error());
203    }
204}