1use core::fmt;
5
6#[cfg(test)]
7mod tests;
8
9pub type Result<T> = core::result::Result<(), Error<T>>;
10
11#[cfg(feature = "state-tracing")]
12#[doc(hidden)]
13pub use tracing::debug as _debug;
14
15#[cfg(not(feature = "state-tracing"))]
16#[doc(hidden)]
17pub use crate::__tracing_noop__ as _debug;
18
19#[macro_export]
20#[doc(hidden)]
21macro_rules! __state_transition__ {
22 ($state:ident, $valid:pat => $target:expr) => {
23 $crate::state::transition!(@build [], _, $state, [$valid => $target])
24 };
25 (@build [$($targets:expr),*], $event:ident, $state:ident, [$valid:pat => $target:expr] $($remaining:tt)*) => {{
26 if matches!($state, $valid) {
28 let __event__ = stringify!($event);
29 if __event__.is_empty() || __event__ == "_" {
30 $crate::state::_debug!(prev = ?$state, next = ?$target, location = %core::panic::Location::caller());
31 } else {
32 $crate::state::_debug!(event = %__event__, prev = ?$state, next = ?$target, location = %core::panic::Location::caller());
33 }
34
35 *$state = $target;
36 Ok(())
37 } else {
38 $crate::state::transition!(
39 @build [$($targets,)* $target],
40 $event,
41 $state,
42 $($remaining)*
43 )
44 }
45 }};
46 (@build [$($targets:expr),*], $event:ident, $state:ident $(,)?) => {{
47 let targets = [$($targets),*];
48
49 if targets.len() == 1 && targets[0].eq($state) {
51 let current = targets[0].clone();
52 Err($crate::state::Error::NoOp { current })
53 } else {
54 Err($crate::state::Error::InvalidTransition {
56 current: $state.clone(),
57 event: stringify!($event),
58 })
59 }
60 }};
61}
62
63pub use crate::__state_transition__ as transition;
64
65#[macro_export]
66#[doc(hidden)]
67macro_rules! __state_event__ {
68 (
69 $(#[doc = $doc:literal])*
70 $event:ident (
71 $(
72 $($valid:ident)|* => $target:ident
73 ),*
74 $(,)?
75 )
76 ) => {
77 $(
78 #[doc = $doc]
79 )*
80 #[inline]
81 #[track_caller]
82 pub fn $event(&mut self) -> $crate::state::Result<Self> {
83 $crate::state::transition!(
84 @build [],
85 $event,
86 self,
87 $(
88 [$(Self::$valid)|* => Self::$target]
89 )*
90 )
91 }
92 };
93 ($(
94 $(#[doc = $doc:literal])*
95 $event:ident (
96 $(
97 $($valid:ident)|* => $target:ident
98 ),*
99 $(,)?
100 );
101 )*) => {
102 $(
103 $crate::state::event!(
104 $(#[doc = $doc])*
105 $event($($($valid)|* => $target),*)
106 );
107 )*
108
109 #[cfg(test)]
110 pub fn test_transitions() -> impl ::core::fmt::Debug {
111 use $crate::state::Error;
112 use ::core::{fmt, result::Result};
113
114 let mut all_states = [
115 $($(
117 $(
118 (stringify!($valid), Self::$valid),
119 )*
120 (stringify!($target), Self::$target),
121 )*)*
122 ];
123
124 all_states.sort_unstable_by_key(|v| v.0);
125 let (sorted, _) = $crate::slice::partition_dedup(&mut all_states);
126
127 const EVENT_LEN: usize = {
128 let mut len = 0;
129 $({
130 let _ = stringify!($event);
131 len += 1;
132 })*
133 len
134 };
135
136 let apply = |state: &Self| {
137 [$({
138 let mut state = state.clone();
139 let result = state.$event().map(|_| state);
140 (stringify!($event), result)
141 }),*]
142 };
143
144 struct Transitions<const L: usize, T, A> {
145 states: [(&'static str, T); L],
146 count: usize,
147 apply: A,
148 }
149
150 impl<const L: usize, T, A> fmt::Debug for Transitions<L, T, A>
151 where
152 T: fmt::Debug,
153 A: Fn(&T) -> [(&'static str, Result<T, Error<T>>); EVENT_LEN],
154 {
155 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
156 let mut m = f.debug_map();
157
158 for (name, state) in self.states.iter().take(self.count) {
159 let events = (self.apply)(state);
160 m.entry(&format_args!("{name}"), &Entry(events));
161 }
162
163 m.finish()
164 }
165 }
166
167 struct Entry<T>([(&'static str, Result<T, Error<T>>); EVENT_LEN]);
168
169 impl<T> fmt::Debug for Entry<T>
170 where
171 T: fmt::Debug
172 {
173 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
174 let mut m = f.debug_map();
175
176 for (event, outcome) in self.0.iter() {
177 m.entry(&format_args!("{event}"), outcome);
178 }
179
180 m.finish()
181 }
182 }
183
184 let count = sorted.len();
185 Transitions {
186 states: all_states,
187 count,
188 apply,
189 }
190 }
191
192 pub fn dot() -> impl ::core::fmt::Display {
194 struct Dot(&'static str);
195
196 impl ::core::fmt::Display for Dot {
197 fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
198 writeln!(f, "digraph {{")?;
199 writeln!(f, " label = {:?};", self.0)?;
200
201 let mut all_states = [
202 $($(
204 $(
205 stringify!($valid),
206 )*
207 stringify!($target),
208 )*)*
209 ];
210
211 all_states.sort_unstable();
212 let (all_states, _) = $crate::slice::partition_dedup(&mut all_states);
213
214 for state in all_states {
215 writeln!(f, " {state};")?;
216 }
217
218 $($(
219 $(
220 writeln!(
221 f,
222 " {} -> {} [label = {:?}];",
223 stringify!($valid),
224 stringify!($target),
225 stringify!($event),
226 )?;
227 )*
228 )*)*
229
230 writeln!(f, "}}")?;
231 Ok(())
232 }
233 }
234
235 Dot(::core::any::type_name::<Self>())
236 }
237 }
238}
239
240pub use crate::__state_event__ as event;
241
242#[macro_export]
243#[doc(hidden)]
244macro_rules! __state_is__ {
245 ($(#[doc = $doc:literal])* $function:ident, $($state:ident)|+) => {
246 $(
247 #[doc = $doc]
248 )*
249 #[inline]
250 pub fn $function(&self) -> bool {
251 matches!(self, $(Self::$state)|*)
252 }
253 };
254}
255
256pub use crate::__state_is__ as is;
257
258#[derive(Clone, Copy, Debug, PartialEq, Eq)]
259pub enum Error<T> {
260 NoOp { current: T },
261 InvalidTransition { current: T, event: &'static str },
262}
263
264impl<T: fmt::Debug> fmt::Display for Error<T> {
265 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266 match self {
267 Self::NoOp { current } => {
268 write!(f, "state is already set to {current:?}")
269 }
270 Self::InvalidTransition { current, event } => {
271 write!(f, "invalid event {event:?} for state {current:?}")
272 }
273 }
274 }
275}
276
277#[cfg(feature = "std")]
278impl<T: fmt::Debug> std::error::Error for Error<T> {}