terminals-core 0.1.0

Core runtime primitives for Terminals OS: phase dynamics, AXON wire protocol, substrate engine, and sematonic types
Documentation
/// Generic state machine trait.
/// S = state type (typically an enum), E = event type (typically an enum).
pub trait StateMachine {
    type State: Clone + PartialEq + std::fmt::Debug;
    type Event: Clone + std::fmt::Debug;

    /// Current state
    fn state(&self) -> &Self::State;

    /// Process an event, potentially transitioning to a new state.
    /// Returns the new state (or current if no transition).
    fn transition(&mut self, event: &Self::Event) -> &Self::State;

    /// Check if a transition is valid without executing it
    fn can_transition(&self, event: &Self::Event) -> bool;
}

/// Transition function type alias for FSM.
type TransitionFn<S, E> = Box<dyn Fn(&S, &E) -> Option<S>>;

/// A concrete FSM implementation with transition history and callbacks.
pub struct BasicFSM<S, E> {
    current: S,
    history: Vec<(S, E, S)>, // (from, event, to) trace
    transition_fn: TransitionFn<S, E>,
    max_history: usize,
}

impl<S: Clone + PartialEq + std::fmt::Debug, E: Clone + std::fmt::Debug> BasicFSM<S, E> {
    pub fn new(initial: S, transition_fn: impl Fn(&S, &E) -> Option<S> + 'static) -> Self {
        Self {
            current: initial,
            history: Vec::new(),
            transition_fn: Box::new(transition_fn),
            max_history: 100,
        }
    }

    pub fn with_max_history(mut self, max: usize) -> Self {
        self.max_history = max;
        self
    }

    pub fn history(&self) -> &[(S, E, S)] {
        &self.history
    }

    /// Checkpoint: snapshot of current state (can be used for rollback)
    pub fn checkpoint(&self) -> S {
        self.current.clone()
    }

    /// Restore to a previous checkpoint
    pub fn restore(&mut self, state: S) {
        self.current = state;
    }
}

impl<S: Clone + PartialEq + std::fmt::Debug, E: Clone + std::fmt::Debug> StateMachine
    for BasicFSM<S, E>
{
    type State = S;
    type Event = E;

    fn state(&self) -> &S {
        &self.current
    }

    fn transition(&mut self, event: &E) -> &S {
        if let Some(next) = (self.transition_fn)(&self.current, event) {
            let from = self.current.clone();
            self.current = next;
            let to = self.current.clone();
            self.history.push((from, event.clone(), to));
            if self.history.len() > self.max_history {
                self.history.remove(0);
            }
        }
        &self.current
    }

    fn can_transition(&self, event: &E) -> bool {
        (self.transition_fn)(&self.current, event).is_some()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[derive(Debug, Clone, PartialEq)]
    enum Light {
        Red,
        Yellow,
        Green,
    }

    #[derive(Debug, Clone)]
    enum LightEvent {
        Timer,
        Emergency,
    }

    fn traffic_light() -> BasicFSM<Light, LightEvent> {
        BasicFSM::new(Light::Red, |state, event| {
            #[allow(unreachable_patterns)]
            match (state, event) {
                (Light::Red, LightEvent::Timer) => Some(Light::Green),
                (Light::Green, LightEvent::Timer) => Some(Light::Yellow),
                (Light::Yellow, LightEvent::Timer) => Some(Light::Red),
                (_, LightEvent::Emergency) => Some(Light::Red),
                _ => None,
            }
        })
    }

    #[test]
    fn test_initial_state() {
        let fsm = traffic_light();
        assert_eq!(fsm.state(), &Light::Red);
    }

    #[test]
    fn test_valid_transition() {
        let mut fsm = traffic_light();
        fsm.transition(&LightEvent::Timer);
        assert_eq!(fsm.state(), &Light::Green);
    }

    #[test]
    fn test_full_cycle() {
        let mut fsm = traffic_light();
        fsm.transition(&LightEvent::Timer); // Red → Green
        fsm.transition(&LightEvent::Timer); // Green → Yellow
        fsm.transition(&LightEvent::Timer); // Yellow → Red
        assert_eq!(fsm.state(), &Light::Red);
    }

    #[test]
    fn test_emergency_from_any_state() {
        let mut fsm = traffic_light();
        fsm.transition(&LightEvent::Timer); // → Green
        fsm.transition(&LightEvent::Emergency); // → Red
        assert_eq!(fsm.state(), &Light::Red);
    }

    #[test]
    fn test_can_transition() {
        let fsm = traffic_light();
        assert!(fsm.can_transition(&LightEvent::Timer));
    }

    #[test]
    fn test_history_tracking() {
        let mut fsm = traffic_light();
        fsm.transition(&LightEvent::Timer);
        fsm.transition(&LightEvent::Timer);
        assert_eq!(fsm.history().len(), 2);
    }

    #[test]
    fn test_checkpoint_restore() {
        let mut fsm = traffic_light();
        fsm.transition(&LightEvent::Timer); // → Green
        let cp = fsm.checkpoint();
        fsm.transition(&LightEvent::Timer); // → Yellow
        assert_eq!(fsm.state(), &Light::Yellow);
        fsm.restore(cp);
        assert_eq!(fsm.state(), &Light::Green);
    }

    #[test]
    fn test_history_max_limit() {
        let mut fsm = traffic_light().with_max_history(2);
        fsm.transition(&LightEvent::Timer);
        fsm.transition(&LightEvent::Timer);
        fsm.transition(&LightEvent::Timer);
        assert_eq!(fsm.history().len(), 2); // oldest entry evicted
    }
}