rust_sfsm/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2
3/// Trait for the state behavior.
4pub trait StateBehavior {
5    type State: Clone + Copy + PartialEq + Default;
6    type Event: Clone + Copy + PartialEq;
7    type Context: Default;
8
9    /// Handle an event and return the next state (if a transition occurs).
10    fn handle(&self, event: &Self::Event, _context: &mut Self::Context) -> Option<Self::State>;
11
12    /// State entry.
13    fn enter(&self, _context: &mut Self::Context) {}
14
15    /// State exit.
16    fn exit(&self, _context: &mut Self::Context) {}
17}
18
19/// # Rust Static FSM
20///
21/// A full static Rust finite state machine macro library.
22///
23/// ## Usage
24///
25/// The `rust_sfsm` macro takes as input the state machine's name, the states enum,
26/// the events enum and the context struct.
27///
28/// The state machine's name can be just an ident if no other member is necessary
29/// to the struct:
30///
31/// ```rust,ignore
32/// use rust_sfsm::{rust_sfsm, StateBehavior};
33///
34/// #[derive(Clone, Copy, PartialEq)]
35/// enum States {
36///     (...)
37/// }
38///
39/// #[derive(Clone, Copy, PartialEq)]
40/// enum Events {
41///     (...)
42/// }
43///
44/// #[derive(Default)]
45/// struct Context {
46///     (...)
47/// }
48///
49/// rust_sfsm!(FooName, States, Events, Context);
50/// ```
51///
52/// The state machine's name can also be a struct with default values if data
53/// other than the cotext is desired:
54///
55/// ```rust,ignore
56/// rust_sfsm!(
57///     FooName {
58///         foo_data: u16 = 0,
59///         boo_data: boo = false,
60///     },
61///     States,
62///     Events,
63///     Context
64/// );
65/// ```
66#[macro_export]
67macro_rules! rust_sfsm {
68    // Case 1: With additional members for the state machine struct
69    (
70        $state_machine_name:ident {
71            $($member_field:ident: $member_field_type:ty = $member_default:expr),* $(,)?
72        },
73        $state_type:ident,
74        $event_type:ident,
75        $context_type:ident
76    ) => {
77        rust_sfsm!(@generate $state_machine_name, $state_type, $event_type, $context_type,
78            members { $($member_field: $member_field_type = $member_default),* }
79        );
80    };
81
82    // Case 2: Without additional members for the state machine struct
83    (
84        $state_machine_name:ident,
85        $state_type:ident,
86        $event_type:ident,
87        $context_type:ident
88    ) => {
89        rust_sfsm!(@generate $state_machine_name, $state_type, $event_type, $context_type,
90            members { }
91        );
92    };
93
94    // Internal implementation for generating the state machine
95    (
96        @generate $state_machine_name:ident, $state_type:ident, $event_type:ident, $context_type:ident,
97        members { $($member_field:ident: $member_field_type:ty = $member_default:expr),* }
98    ) => {
99        /// State machine struct.
100        pub struct $state_machine_name {
101            current_state: $state_type,
102            context: $context_type,
103            $(
104                $member_field: $member_field_type,
105            )*
106        }
107
108        impl $state_machine_name {
109            /// Create a new state machine.
110            pub fn new() -> Self {
111                Self {
112                    current_state: Default::default(),
113                    context: Default::default(),
114                    $(
115                        $member_field: $member_default,
116                    )*
117                }
118            }
119
120            /// Transit to a new state.
121            pub fn transit(&mut self, new_state: $state_type) {
122                self.current_state.exit(&mut self.context);
123                self.current_state = new_state;
124                self.current_state.enter(&mut self.context);
125            }
126
127            /// Force transition to a new state without calls to respectives
128            /// `enter` and `exit` functions.
129            pub fn force_state(&mut self, new_state: $state_type) {
130                self.current_state = new_state;
131            }
132
133            /// Get the current state
134            pub fn current_state(&self) -> $state_type {
135                self.current_state
136            }
137
138            /// Handle event and transition if necessary.
139            fn handle(&mut self, event: $event_type) {
140                if let Some(next_state) = self.current_state.handle(&event, &mut self.context) {
141                    self.current_state.exit(&mut self.context);
142                    self.current_state = next_state;
143                    self.current_state.enter(&mut self.context);
144                }
145            }
146        }
147    };
148}