1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
use std::collections::HashMap;
use std::hash::Hash;
use std::fmt::Debug;
use crate::types::{Transition, Effector};

/// Finite state machine with side effects (Mealy automata)
pub struct FSM<State, Effect>
    where State: Eq + PartialEq + Copy + Hash,
          Effect: Eq + PartialEq + Copy,
{
    /// State at beginning of running through stream
    initial_state: State,
    /// Transition graph that connects every state of FSM
    /// to some next states by transitions
    transition_table: HashMap<State, Vec<Transition<State, Effect>>>,
}

/// Error that occurs during initialization or running with FSM
#[derive(Copy, Clone, Debug)]
pub enum FSMError<'a, State> 
    where State: Eq + PartialEq + Copy + Debug
{
    StateDoesNotExist(State),
    NoValidTransition {
        from: State,
        string: &'a String,
        index: usize,
        character: char
    }
}

impl<State, Effect> FSM<State, Effect> 
    where State: Eq + PartialEq + Copy + Hash + Debug,
          Effect: Eq + PartialEq + Copy,
{
    /// Creates new instance of FSM
    /// - initial_state: starting state,
    /// - transition_table: transition graph
    pub fn new<'a>(
        initial_state: State, 
        transition_table: HashMap<State, Vec<Transition<State, Effect>>>
    ) -> Result<Self, FSMError<'a, State>> {
        if !transition_table.contains_key(&initial_state) {
            Err(FSMError::StateDoesNotExist(initial_state))
        } else {
            Ok(Self {
                initial_state,
                transition_table
            })
        }
    }

    /// Runs some string through FSM to validate it (and apply some effects)
    /// - string: runnable string,
    /// - effector: module that mutates some data by effects
    pub fn proceed<'a>(
        &self, 
        string: &'a String,
        mut effector: Option<&'a mut dyn Effector<Effect>>
    ) -> Result<(), FSMError<'a, State>> 
    {
        let mut curr_state = self.initial_state;
        let mut char_id: usize = 0;

        for ch in string.chars() {
            match self.transition_table.get(&curr_state) {
                Some(transitions) => {
                    let mut accepted = false;
                    
                    for transition in transitions.iter() {
                        match transition.transit(ch) {
                            (Some(new_state), effect) => {
                                curr_state = new_state;
                                accepted = true;
                                
                                if let (Some(effector), Some(effect)) = 
                                    (effector.as_mut(), effect) 
                                {
                                    effector.dispatch(effect);    
                                }

                                break;
                            },
                            _ => {}
                        }
                    }

                    if !accepted {
                        return Err(FSMError::NoValidTransition {
                            from: curr_state,
                            string,
                            index: char_id,
                            character: ch
                        });
                    }
                },
                None => {
                    return Err(FSMError::StateDoesNotExist(curr_state));
                }
            }

            char_id += 1;
        }

        Ok(())
    }
}