automata_like_programming/simple_impl/
simple_state.rs

1use std::{cell::RefCell, marker::PhantomData, rc::Rc};
2
3use crate::automaton_state::{convert_to_dyn_reference, AutomatonState, SharedAutomatonState};
4
5/// Represents data, that can provide a key which will be used while searching for next state. Usually will use iterator
6/// based on a sequence.
7pub trait KeyProvidingData<K> {
8    fn next_key(&mut self) -> Option<K>;
9}
10
11///
12/// Connection representing edge between two nodes (or one node with itself) in a graph structure. Matcher is used to
13/// find the next state. Based on the key provided by the data. Each connection has a specified function which will be 
14/// executed while changing to matched next state.
15/// 
16/// * `matcher` - Defines whether this connection should be chosen for a specified key. It's up to the user to ensure
17/// that connections don't have intersecting matchers. The first connection matched for a key will always be used.
18/// * `exec_function` - Operation that will be executing while changing state.
19/// * `connected_state` - State that will be returned if this connection is matched. Can be the same state that this
20/// connection will be assigned to.
21pub struct SimpleInterStateConnection<'a, K, Id, D, E> where Id: Copy + 'a, K: 'a, D: 'a, E: 'a {
22    matcher: Box<dyn Fn(&K) -> bool + 'a>,
23    exec_function: Box<dyn Fn(&mut D, &K) -> Result<(), E> + 'a>,
24    connected_state: SharedAutomatonState<'a, Id, D, E>,
25}
26
27impl <'a, K, Id, D, E> SimpleInterStateConnection<'a, K, Id, D, E> where Id: Copy {
28    /// Creates new connection with specified matcher and a procedure that will be executed when this connection is matched.
29    pub fn new<M: Fn(&K) -> bool + 'a, FExec: Fn(&mut D, &K) -> Result<(), E> + 'a, S: AutomatonState<'a, Id, D, E> + 'a>(matcher: M, exec_function: FExec, next_state: &Rc<RefCell<S>>) -> Self {
30        Self { matcher: Box::new(matcher), exec_function: Box::new(exec_function), connected_state: convert_to_dyn_reference(Rc::clone(next_state)) }
31    }
32
33    /// Creates new connection with specified matcher. Does nothing when matched (designed to be used with intermediate states).
34    pub fn new_no_action<M: Fn(&K) -> bool + 'a, S: AutomatonState<'a, Id, D, E> + 'a>(matcher: M, next_state: &Rc<RefCell<S>>) -> Self {
35        Self::new(matcher, Self::do_nothing, next_state)
36    }
37
38    /// Does nothing
39    fn do_nothing(_:&mut D, _:&K) -> Result<(), E> {
40        Result::Ok(())
41    }
42}
43
44/// AutomatonState implementating struct which simplifies state definition by managing list of defined connections. 
45/// Depends on data for providing next key. This key is then used to match a connection from the defined list.
46/// Each state has an assigned identifier which is used to inform which state did the automaton stop on.
47/// Identifier is copied to the result meaning it has to implement the *Copy* trait.
48pub struct SimpleStateImplementation<'a, K, Id, D, E> where D: KeyProvidingData<K>, Id: Copy{
49    _phantom: PhantomData<D>,
50    id: Id,
51    next_states: Vec<SimpleInterStateConnection<'a, K, Id, D, E>>,
52}
53
54impl <'a, K, Id, D, E> SimpleStateImplementation<'a, K, Id, D, E> where D: KeyProvidingData<K>, Id: Copy {
55    /// Creates new simple state with provided identifier.
56    /// 
57    /// * `id` - Identifier of this state which will be copied into result when automaton stops on this state.
58    pub fn new(id: Id) -> Self {
59        Self { _phantom: PhantomData{}, next_states: Vec::new(), id}
60    }
61
62    /// Adds connection to possible next states of current state.
63    pub fn register_connection(&mut self, connection: SimpleInterStateConnection<'a, K, Id, D, E>) -> () 
64    {
65        self.next_states.push(connection);
66    }
67}
68
69impl<'a, K, Id, D, E> AutomatonState<'a, Id, D, E> for SimpleStateImplementation<'a, K, Id, D, E> where D: KeyProvidingData<K>, Id: Copy {
70    /// Returns owned copy of identifier of this state.
71    fn get_id_owned(&self) -> Id {
72        self.id
73    }
74
75    /// Returns identifier of this state.
76    fn get_id(&self) -> &Id {
77        &self.id
78    }
79
80    /// Finds connection by popping key from key iterator. Executes assigned function and returns next state if everything goes
81    /// alright. 
82    fn execute_next_connection(&self, data: &mut D) -> Result<crate::automaton::NextState<'a, Id, D, E>, E> {
83        let next_key = data.next_key();
84        if let Option::Some(k) = next_key {
85            for c in &self.next_states {
86                if (c.matcher)(&k) {
87                    (c.exec_function)(data, &k)?;
88                    return Result::Ok(crate::automaton::NextState::Continue(Rc::clone(&c.connected_state)));
89                }
90            }
91            Result::Ok(crate::automaton::NextState::NotFound)
92        } else {
93            Result::Ok(crate::automaton::NextState::ProcessEnded)
94        }
95    }
96}
97
98#[cfg(test)]
99mod test {
100    use super::KeyProvidingData;
101
102    struct TestData {
103        buffer: String,
104        end: u8,
105        current: u8,
106    }
107
108    impl TestData {
109        pub fn new(start: u8, end: u8) -> Self {
110            Self { buffer: String::new(), end, current: start }
111        }
112
113        pub fn append_text(&mut self, text: &str) -> () {
114            self.buffer.push_str(text);
115        }
116
117        pub fn data(&self) -> &String {
118            &self.buffer
119        }
120    }
121
122    impl KeyProvidingData<u8> for TestData {
123        fn next_key(&mut self) -> Option<u8> {
124            if self.current >= self.end {
125                return Option::None
126            }
127            let res = Option::Some(self.current);
128            self.current += 1;
129            return res;
130        }
131    }
132
133    mod automaton_test {
134        use crate::{automaton::{Automaton, AutomatonResult}, automaton_state::new_shared_concrete_state, simple_impl::simple_state::{test::TestData, SimpleInterStateConnection, SimpleStateImplementation}};
135
136        #[test]
137        fn automaton_with_simple_states_works() -> () {
138            let mut data = TestData::new(1, 4);
139            let mut automaton = Automaton::new(|| {
140                let world_state = new_shared_concrete_state(SimpleStateImplementation::new(3));
141                let simple_state = new_shared_concrete_state(SimpleStateImplementation::new(2));
142                simple_state.borrow_mut().register_connection(SimpleInterStateConnection::new(|k| k == &2, |d: &mut TestData, _| {
143                    d.append_text(" simple ");
144                    let res: Result<(), String> = Result::Ok(());
145                    res
146                }, &world_state));
147                let hello_state = new_shared_concrete_state(SimpleStateImplementation::new(1));
148                hello_state.borrow_mut().register_connection(SimpleInterStateConnection::new(|k| k == &1, |d: &mut TestData, _| {
149                    d.append_text("Hello");
150                    Result::Ok(())
151                }, &simple_state));
152                world_state.borrow_mut().register_connection(SimpleInterStateConnection::new(|k| k == &3, |d: &mut TestData, _| {
153                    d.append_text("world!");
154                    Result::Ok(())
155                }, &hello_state));
156                hello_state
157            });
158            let run_result = automaton.run(&mut data);
159            assert_eq!(data.data(), "Hello simple world!");
160            assert!(matches!(run_result, AutomatonResult::EmptyIter(1)));
161        }
162
163        // TBF I don't know if this situation should be Ok or Err
164        #[test]
165        fn automaton_with_simple_states_works_no_next_state_found() -> () {
166            let mut data = TestData::new(2, 3);
167            let mut automaton = Automaton::new(|| {
168                new_shared_concrete_state(SimpleStateImplementation::new(1))
169            });
170            let run_result: AutomatonResult<u32, String> = automaton.run(&mut data);
171            assert_eq!(data.data(), "");
172            assert!(matches!(run_result, AutomatonResult::CouldNotFindNextState(1)));
173        }
174    }
175}