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}