odem_rs_core/fsm.rs
1//! A module providing infrastructure for building safe and controlled type
2//! state machines in ODEM-rs.
3//!
4//! This module defines a set of traits, types, and macros that facilitate the
5//! creation and management of state machines with enforced state transitions.
6//! It ensures that state changes are tracked and validated at compile time.
7//!
8//! # Overview
9//!
10//! The key components provided by this module are:
11//!
12//! - **Macros**:
13//! - [`fsm`]: A macro to simplify the generation of state machines,
14//! allowing for concise definitions of states and transitions.
15//!
16//! - **Types**:
17//! - [`Ephemeral`]: A token type used to enforce one-time usage, ensuring
18//! that the number of token witnesses is always known.
19//! - [`StateMachine`]: A wrapper type for custom state machines generated
20//! through the `fsm!` macro. It maintains the state and controls
21//! transitions.
22//! - [`TransitionError`]: An error type indicating issues that may occur
23//! during state transitions, such as invalidation or borrowing conflicts.
24//!
25//! - **Traits**:
26//! - [`Stateful`]: Marks a type that supports methods using `Ephemeral`
27//! tokens to track state changes. It provides hooks for tracking the
28//! number of tokens *witnessing* the current state.
29//! - [`Rebrand`]: Defines a generalized associated type (GAT) that
30//! corresponds with a rebranded version of that type.
31//! - [`Brand`]: Extends `Stateful` types with a method to create an
32//! `Ephemeral` token, allowing for state tracking through branding.
33//! - [`Debrand`]: Allows branded instances to temporarily escape state
34//! tracking to execute closures that may rebrand the instance.
35//! - [`Transition`]: Defines safe state transitions between states
36//! `U` and `V`, given a token witness to the current state.
37//!
38//! # Usage
39//!
40//! This module is intended for defining state machines where state transitions
41//! need to be controlled and validated. By using the provided traits and types,
42//! you can enforce that certain methods are only callable in specific states
43//! and that transitions between states are valid. This leads to safer and
44//! hopefully more maintainable code.
45//!
46//! The `fsm` macro can be used to generate a custom state machine with defined
47//! states and transitions, reducing boilerplate and potential for errors.
48//!
49//! # Example
50//!
51//! ```
52//! # use odem_rs_core::fsm::*;
53//! # use core::clone::Clone;
54//! // Define a state machine with three states: `Idle`, `Exec`, and `Done`.
55//! fsm! {
56//! pub enum States<C: Clone> {
57//! Idle(C) -> { Exec },
58//! Exec -> { Idle, Done },
59//! Done -> {}
60//! }
61//! }
62//!
63//! fn main() {
64//! use States::*;
65//! let context = "MyContext".to_string();
66//! let machine = StateMachine::new(Idle(context.clone()));
67//!
68//! // Transition from Initialized to Running
69//! machine.brand(|sm, once| {
70//! // Assert the idle state
71//! let idle = sm.token(once).into_idle().unwrap();
72//! // Transition into the running state
73//! let exec: token::Exec<'_> = sm.transition(idle, ());
74//! // Do something with the running state
75//! let done: token::Done<'_> = sm.transition(exec, ());
76//! });
77//! }
78//! ```
79//!
80//! # Safety
81//!
82//! The use of [`Ephemeral`] tokens and the `brand` and `debrand` methods enforce at
83//! compile time that state transitions are valid and that state changes do not
84//! occur while other parts of the program hold a token witness. This helps
85//! prevent bugs related to invalid state transitions and ensures that the state
86//! machine remains in a valid state throughout its lifecycle.
87//!
88//! Specifically, it is made impossible to use token reflecting the state of one
89//! state machine to affect transitions in another one.
90//!
91//! For example, this machine attempts to use a token made for another state
92//! machine, leading to a compiler error:
93//!
94//! ```compile_fail,E0521
95//! # use odem_rs_core::fsm::*;
96//! // Define a state machine with three states: `Idle`, `Exec`, and `Done`.
97//! fsm! {
98//! pub enum States {
99//! Idle -> { Exec },
100//! Exec -> { Idle, Done },
101//! Done -> {}
102//! }
103//! }
104//!
105//! # fn main() {
106//! let m1 = StateMachine::new(States::Idle);
107//! let m2 = StateMachine::new(States::Exec);
108//!
109//! m1.brand(|_, once| {
110//! // Use the wrong brand of Ephemeral token; should (and does) not compile!
111//! let exec = m2.token(once).into_exec().unwrap();
112//! let _: token::Done<'_> = m1.transition(exec, ());
113//! });
114//! # }
115//! ```
116//!
117//! # Notes
118//!
119//! While the module provides mechanisms to prevent multiple tokens from
120//! invalidating each other, it is possible to create multiple tokens via
121//! repeated calls to `brand`, which would lead to the state being frozen in a
122//! read-only mode. Care should be taken to manage tokens appropriately and
123//! ensure that branding scopes are correctly entered and exited.
124
125use core::{
126 cell::{BorrowMutError, Cell, Ref, RefCell},
127 marker::PhantomData,
128 ops::Deref,
129 pin::Pin,
130};
131
132/* *********************************************************** Exposed Traits */
133
134/// Trait implemented by the [`fsm`]-macro.
135pub trait FSM {
136 /// Type of generically branded token.
137 type Token<'b>;
138
139 /// Type of the stateless enumeration of all possible states.
140 type Erased;
141
142 /// Private trait function to convert a branded [`Ephemeral`] into an equally
143 /// branded machine-specific token type.
144 ///
145 /// The function argument of type [`Private`] is meant to prevent users from
146 /// calling this method directly rather than calling the methods on the
147 /// complex state machines, like [`StateMachine`].
148 ///
149 /// Calling the method directly has the potential to invalidate the branding
150 /// since `'brand` is chosen by the caller and therefore could be applied
151 /// to `Ephemeral` token from other instances.
152 fn token<'b>(this: &Self, once: Ephemeral<'b>, _: Private) -> Self::Token<'b>;
153
154 /// Erases the configuration-specific payloads from the enumeration,
155 /// returning a type-erased version.
156 fn erased(&self) -> Self::Erased;
157
158 /// Returns the name of the current state as a string.
159 fn label(&self) -> &'static str;
160}
161
162/// Marks a type for supporting extra methods using [`Ephemeral`]-tokens to keep
163/// track of state changes.
164pub trait Stateful {
165 /// Associated marker type for the branding.
166 type Brand; // = `&'brand ()`
167
168 /// Method hook used to register the creation of another [`Ephemeral`]-token
169 /// for this instance.
170 ///
171 /// # Safety
172 /// Calling this method increases the count of `Ephemeral`-token emitted
173 /// by the object. Only call this method manually if one of these token
174 /// flows into an object that will release it later in order to freeze the
175 /// state.
176 unsafe fn enter(&self);
177
178 /// Method hook used to register the destruction of a [`Ephemeral`]-token
179 /// for this instance.
180 ///
181 /// # Safety
182 /// Calling this method reduces the count of `Ephemeral`-token which may
183 /// enable transitions to occur. Only call this method manually when
184 /// releasing a token from an object in order to unfreeze the state.
185 unsafe fn leave(&self);
186}
187
188/// Extension trait to keep track of a [`Stateful`] object's current branding.
189pub trait Rebrand<'brand>: Stateful<Brand = &'brand ()> {
190 /// Meta function that computes the rebranded version of `Self` and forces
191 /// the type to correspond with the `Self` type for one of the brandings.
192 ///
193 /// This prevents the trait from being implemented like this:
194 /// ```compile_fail,E0271
195 /// # use odem_rs_core::fsm::*;
196 /// # use core::marker::PhantomData;
197 /// struct S<'b>(PhantomData<fn(&'b ()) -> &'b ()>);
198 ///
199 /// impl<'b> Stateful for S<'b> {
200 /// type Brand = &'b ();
201 /// unsafe fn enter(&self) {}
202 /// unsafe fn leave(&self) {}
203 /// }
204 ///
205 /// impl<'b> Rebrand<'b> for S<'b> {
206 /// type Kind<'a> = S<'a>; // legal, since Kind<'b> == S<'b>
207 /// }
208 ///
209 /// struct T<'b>(PhantomData<fn(&'b ()) -> &'b ()>);
210 ///
211 /// impl<'b> Stateful for T<'b> {
212 /// type Brand = &'b ();
213 /// unsafe fn enter(&self) {}
214 /// unsafe fn leave(&self) {}
215 /// }
216 ///
217 /// impl<'b> Rebrand<'b> for T<'b> {
218 /// type Kind<'a> = S<'a>; // illegal, since ∄'a: Kind<'a> == T<'b>
219 /// }
220 /// ```
221 ///
222 type Kind<'a>: Rebrand<'a, Kind<'brand> = Self>;
223
224 /// "Converts" a reference to `Self` into a reference of `Self::Kind`, which
225 /// is constrained to be a reference to `Self` itself, making this a no-op.
226 ///
227 /// The method is required to support branding.
228 fn identity_ref(&self) -> &Self::Kind<'brand> {
229 // SAFETY: since Self::Kind<'brand> == Self, this is a no-op
230 unsafe { &*(self as *const Self as *const Self::Kind<'brand>) }
231 }
232
233 /// "Converts" a mutable reference to `Self` into a mutable reference of
234 /// `Self::Kind`, which is constrained to be a mutable reference to `Self`
235 /// itself, making this a no-op.
236 ///
237 /// The method is required to support branding.
238 fn identity_mut(&mut self) -> &mut Self::Kind<'brand> {
239 // SAFETY: since Self::Kind<'brand> == Self, this is a no-op
240 unsafe { &mut *(self as *mut Self as *mut Self::Kind<'brand>) }
241 }
242}
243
244/// Extends [`Stateful`] types by a method to create a [`Ephemeral`]-token that
245/// helps to track state changes.
246pub trait Brand<F, R> {
247 /// Provides an [`Ephemeral`]-token to a closure for [`Stateful`] objects,
248 /// unlocking the brand-specific methods.
249 fn brand(self, f: F) -> R;
250}
251
252/// Extends [`Stateful`] types by a method to temporarily escape the tracking of
253/// state changes. This is necessary when state transitions may occur as a
254/// side effect of a function call, since those are blocked as long as token
255/// witnesses to other states exist.
256pub trait Debrand<'b, F, R> {
257 /// This method allows branded instances to temporarily escape their
258 /// branding in order to execute a caller-supplied closure that may rebrand
259 /// the instance.
260 ///
261 /// Not using this method and branding the same instance twice freezes
262 /// the state in read-only mode, preventing transitions in order to
263 /// ensure that the state both token are witnessing doesn't change.
264 fn debrand(self, once: impl Into<Ephemeral<'b>>, f: F) -> (Ephemeral<'b>, R);
265}
266
267/// Trait allowing safe state-transitions given a token witness that is
268/// testifying to the current state and returning another token after the
269/// transition occurred.
270///
271/// Only potentially correct transitions are implemented, allowing a static
272/// compile-time check that all transitions follow a predefined flow-chart.
273pub trait Transition<'b, U, V> {
274 /// Associated type specifying the payload of the destination state `V`.
275 type Data;
276
277 /// Performs the transition between `U` and `V` given an `U`-specific token
278 /// and returning a `V`-specific token.
279 ///
280 /// The method is not directly callable for the user in order to ensure that
281 /// the invariants regarding the number of token witnesses are kept. The
282 /// mechanism is enforced by the last [`Private`] argument to the function
283 /// that cannot be constructed outside of this module.
284 fn transition(this: &mut Self, curr: U, data: Self::Data, _: Private) -> V;
285}
286
287/* *************************************************************** Token Type */
288
289/// Type for a one-time-only usable token.
290///
291/// Token of this type are used as a seed for the [`brand`] method and are meant
292/// to ensure that other kinds of Token may only be created once. This is the
293/// reason why the type constructor is deliberately kept private. The *only* way
294/// to create this token is to call the `brand` method.
295///
296/// This is important because otherwise it would be possible to create two Token
297/// from the same instance and use one of them to invalidate the other one. This
298/// type of bug is prevented by the `brand`-method tracking the number of
299/// `Ephemeral` tokens released for each object in combination of transitions
300/// blocking changes to the state if more than one Token exists.
301///
302/// [`brand`]: Brand::brand
303#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
304pub struct Ephemeral<'brand>(PhantomData<fn(&'brand ()) -> &'brand ()>);
305
306/* ****************************************** Marker Type for Private Details */
307
308/// Marker type to enable generating private functions in a public trait
309/// interface.
310pub struct Private(());
311
312/* ************************************************************ Complex State */
313
314/// Wrapper type for custom state machines generated through the
315/// [`fsm`] macro.
316pub struct StateMachine<'b, T: ?Sized> {
317 /// Invariant lifetime for branding.
318 _mark: PhantomData<fn(&'b ()) -> &'b ()>,
319
320 /// The number of open scopes/[`Ephemeral`] token emitted.
321 ///
322 /// Freezes state transitions if larger than 1.
323 token: Cell<u32>,
324
325 /// Mutable access to the inner state generated by the macro.
326 state: RefCell<T>,
327}
328
329impl<T> StateMachine<'static, T> {
330 /// Creates a new state machine using the state `T`.
331 pub const fn new(inner: T) -> Self {
332 Self {
333 _mark: PhantomData,
334 token: Cell::new(0),
335 state: RefCell::new(inner),
336 }
337 }
338}
339
340impl<'b, T: FSM> StateMachine<'b, T> {
341 /// Returns an enumeration containing token witnesses for the current state.
342 pub fn token(&self, once: Ephemeral<'b>) -> T::Token<'b> {
343 T::token(&*self.borrow(), once, Private(()))
344 }
345
346 /// Returns a type-erased version of the current state, i.e. an enumeration
347 /// of just the states, without additional payload.
348 pub fn erased(&self) -> T::Erased {
349 T::erased(&*self.borrow())
350 }
351
352 /// Returns the name of the current state as a string.
353 pub fn label(&self) -> &'static str {
354 T::label(&*self.borrow())
355 }
356
357 /// Performs a state transition given the current state and the payload of
358 /// the follow-up state, returning a token witness for the next state.
359 ///
360 /// This method will panic if the transition would invalidate other token
361 /// witnesses. See [`Self::try_transition`] for a non-panicking variant.
362 pub fn transition<U, V>(&self, curr: U, data: T::Data) -> V
363 where
364 T: Transition<'b, U, V>,
365 {
366 match self.try_transition(curr, data) {
367 Ok(v) => v,
368 Err(err) => panic!(
369 "cannot transition from '{}' to '{}': {err}",
370 self.label(),
371 core::any::type_name::<V>(),
372 ),
373 }
374 }
375
376 /// Attempts to perform a state transition given the current state and the
377 /// payload of the follow-up state, returning a token witness for the next
378 /// state if successful and the previous token witness on error.
379 ///
380 /// It is not possible to transition if other token witnesses for the same
381 /// state machine would be invalidated by the transition. If you simply
382 /// want to update the data associated with a state without changing it,
383 /// consider using [`Self::update`] which doesn't have this restriction.
384 pub fn try_transition<U, V>(&self, curr: U, data: T::Data) -> Result<V, TransitionError<U>>
385 where
386 T: Transition<'b, U, V>,
387 {
388 match self.token.get() {
389 0 => unreachable!("transitioning without tokens should be impossible"),
390 1 => match self.state.try_borrow_mut() {
391 Ok(mut state) => Ok(T::transition(&mut *state, curr, data, Private(()))),
392 Err(err) => Err(TransitionError::Borrow(curr, err)),
393 },
394 n => Err(TransitionError::Invalidation(curr, n)),
395 }
396 }
397
398 /// Updates the internal data of a state without changing it.
399 ///
400 /// This is allowed, even if other token witnesses exist as it cannot
401 /// invalidate them and will only panic if the state cannot be modified due
402 /// to being borrowed already.
403 pub fn update<U>(&self, curr: U, data: T::Data) -> U
404 where
405 T: Transition<'b, U, U>,
406 {
407 T::transition(&mut *self.state.borrow_mut(), curr, data, Private(()))
408 }
409
410 /// Borrows the inner state.
411 #[track_caller]
412 pub fn borrow(&self) -> Ref<'_, T> {
413 self.state.borrow()
414 }
415}
416
417impl<T: Default> Default for StateMachine<'static, T> {
418 fn default() -> Self {
419 Self::new(T::default())
420 }
421}
422
423impl<'b, T> Stateful for StateMachine<'b, T> {
424 type Brand = &'b ();
425
426 #[inline]
427 unsafe fn enter(&self) {
428 self.token.set(self.token.get() + 1);
429 }
430
431 #[inline]
432 unsafe fn leave(&self) {
433 self.token.set(self.token.get() - 1);
434 }
435}
436
437impl<'b, T> Rebrand<'b> for StateMachine<'b, T> {
438 type Kind<'a> = StateMachine<'a, T>;
439}
440
441/// Enumeration of errors that may happen during state transitions.
442#[derive(thiserror::Error)]
443pub enum TransitionError<T> {
444 /// Signals that a transition failed because other token witnesses would
445 /// have been invalidated.
446 #[error("cannot transition while {1} references exist")]
447 Invalidation(T, u32),
448 /// Signals that a transition failed because the state was already borrowed
449 /// and thus could not be overwritten.
450 #[error("cannot transition while state is borrowed")]
451 Borrow(T, BorrowMutError),
452}
453
454/* ************************************************************************** */
455
456/// Scope-guard that ensures the correct counting of branding scopes even
457/// in case of a panic.
458struct Guard<T: Stateful, const E: bool = true>(T);
459
460impl<T: Stateful> Guard<T, true> {
461 /// Creates a new scope guard, entering the branding scope.
462 fn enter(value: T) -> Self {
463 unsafe {
464 value.enter();
465 }
466 Self(value)
467 }
468}
469
470impl<T: Stateful> Guard<T, false> {
471 /// Creates a new scope guard, leaving the branding scope.
472 fn leave(value: T) -> Self {
473 unsafe {
474 value.leave();
475 }
476 Self(value)
477 }
478}
479
480impl<T: Stateful, const E: bool> Drop for Guard<T, E> {
481 /// Drops the scope guard, exiting or entering (depending on `E`) the scope.
482 fn drop(&mut self) {
483 if E {
484 unsafe {
485 self.0.leave();
486 }
487 } else {
488 unsafe {
489 self.0.enter();
490 }
491 }
492 }
493}
494
495/* **************************************************** Trait Implementations */
496
497// implement the Stateful trait for all references to Stateful objects
498impl<T: Deref<Target: Stateful>> Stateful for T {
499 type Brand = <T::Target as Stateful>::Brand;
500
501 unsafe fn enter(&self) {
502 unsafe {
503 (**self).enter();
504 }
505 }
506
507 unsafe fn leave(&self) {
508 unsafe {
509 (**self).leave();
510 }
511 }
512}
513
514// Implement the Brand method for different kinds of references to Brandable types
515impl<'b, T, F, R> Brand<F, R> for &T
516where
517 T: Rebrand<'b>,
518 F: for<'a> FnOnce(&T::Kind<'a>, Ephemeral<'a>) -> R,
519{
520 fn brand(self, f: F) -> R {
521 f(Guard::enter(self.identity_ref()).0, Ephemeral(PhantomData))
522 }
523}
524
525impl<'b, T, F, R> Brand<F, R> for &mut T
526where
527 T: Rebrand<'b>,
528 F: for<'a> FnOnce(&mut T::Kind<'a>, Ephemeral<'a>) -> R,
529{
530 fn brand(self, f: F) -> R {
531 f(Guard::enter(self.identity_mut()).0, Ephemeral(PhantomData))
532 }
533}
534
535impl<'b, T, F, R> Brand<F, R> for Pin<&T>
536where
537 T: Rebrand<'b>,
538 F: for<'a> FnOnce(Pin<&T::Kind<'a>>, Ephemeral<'a>) -> R,
539{
540 fn brand(self, f: F) -> R {
541 // SAFETY: T: Brandable<'brand> implies T::Kind<'brand> = Self
542 // therefore the cast below is safe
543 let this = unsafe { Pin::map_unchecked(self, T::identity_ref) };
544
545 f(Guard::enter(this).0.as_ref(), Ephemeral(PhantomData))
546 }
547}
548
549impl<'b, T, F, R> Brand<F, R> for Pin<&mut T>
550where
551 T: Rebrand<'b>,
552 F: for<'a> FnOnce(Pin<&mut T::Kind<'a>>, Ephemeral<'a>) -> R,
553{
554 fn brand(self, f: F) -> R {
555 // SAFETY: T: Brandable<'brand> implies T::Kind<'brand> = Self
556 // therefore the cast below is safe
557 let this = unsafe { Pin::map_unchecked_mut(self, T::identity_mut) };
558
559 f(Guard::enter(this).0.as_mut(), Ephemeral(PhantomData))
560 }
561}
562
563impl<'b, T, F, R> Brand<F, R> for crate::ptr::Irc<T>
564where
565 T: crate::ptr::IntrusivelyCounted + Rebrand<'b, Kind<'b>: crate::ptr::IntrusivelyCounted>,
566 F: for<'a> FnOnce(&crate::ptr::Irc<T::Kind<'a>>, Ephemeral<'a>) -> R,
567{
568 fn brand(self, f: F) -> R {
569 // SAFETY: T: Brandable<'brand> implies T::Kind<'brand> = Self
570 // therefore the cast below is safe
571 let this = crate::ptr::Irc::map(self, T::identity_ref);
572
573 f(&Guard::enter(this).0, Ephemeral(PhantomData))
574 }
575}
576
577// Implement the Debrand method for different kinds of references to Brandable types
578impl<'b, T, F, R> Debrand<'b, F, R> for &T
579where
580 T: Rebrand<'b>,
581 F: FnOnce(&T) -> R,
582{
583 fn debrand(self, once: impl Into<Ephemeral<'b>>, f: F) -> (Ephemeral<'b>, R) {
584 // can't risk having drop not called, so it's not passed down
585 let guard = Guard::leave(self);
586 (once.into(), f(guard.0))
587 }
588}
589
590impl<'b, T, F, R> Debrand<'b, F, R> for Pin<&T>
591where
592 T: Rebrand<'b>,
593 F: FnOnce(Pin<&T>) -> R,
594{
595 fn debrand(self, once: impl Into<Ephemeral<'b>>, f: F) -> (Ephemeral<'b>, R) {
596 // can't risk having drop not called, so it's not passed down
597 let guard = Guard::leave(self);
598 (once.into(), f(guard.0))
599 }
600}
601
602impl<'b, T, F, R> Debrand<'b, F, R> for &mut T
603where
604 T: Rebrand<'b>,
605 F: FnOnce(&mut T) -> R,
606{
607 fn debrand(self, once: impl Into<Ephemeral<'b>>, f: F) -> (Ephemeral<'b>, R) {
608 // can't risk having drop not called, so it's not passed down
609 let guard = Guard::leave(self);
610 (once.into(), f(guard.0))
611 }
612}
613
614impl<'b, T, F, R> Debrand<'b, F, R> for Pin<&mut T>
615where
616 T: Rebrand<'b>,
617 F: FnOnce(Pin<&mut T>) -> R,
618{
619 fn debrand(self, once: impl Into<Ephemeral<'b>>, f: F) -> (Ephemeral<'b>, R) {
620 // can't risk having drop not called, so it's not passed down
621 let mut guard = Guard::leave(self);
622 (once.into(), f(guard.0.as_mut()))
623 }
624}
625
626/* ********************************************************* Continuation Type States */
627
628/// A macro to automate the generation of type state machines.
629///
630/// The macro takes a definition of states and their possible transitions. It
631/// generates:
632/// - An enum representing all states.
633/// - An equivalent “erased” enum without payloads.
634/// - A set of token types for each state (and possibly for groups of states,
635/// if groups are defined).
636/// - Implementations of [`Transition`] for each defined state transition,
637/// ensuring at compile time that only valid transitions are allowed.
638///
639/// The macro also generates convenience methods to check the current state and
640/// to convert between token types. It can only be called from the module scope,
641/// not from the function scope, since it creates multiple modules.
642///
643/// # Example Usage
644///
645/// Here is a simple invocation of the macro for a finite state machine with
646/// three states:
647///
648/// ```
649/// # mod test {
650/// # use odem_rs_core::fsm::*;
651/// // Define a state machine with three states: `Idle`, `Exec`, and `Done`.
652/// fsm! {
653/// pub enum States {
654/// Idle -> { Exec },
655/// Exec -> { Idle, Done },
656/// Done -> {}
657/// }
658/// }
659/// # }
660/// ```
661///
662/// The macro also supports a single generic argument with a single trait bound.
663///
664/// ```
665/// # mod test {
666/// # use odem_rs_core::fsm::*;
667/// # use core::clone::Clone;
668/// fsm! {
669/// pub enum States<C: Clone> {
670/// Idle(C) -> { Exec },
671/// Exec -> { Idle, Done },
672/// Done -> {}
673/// }
674/// }
675/// # }
676/// ```
677///
678/// There is an implicit trait bound of `?Sized` but it can be overridden by
679/// choosing a trait bound that implies `Sized`. For technical reasons, at most
680/// one generic argument with one trait bound is supported at the moment.
681///
682/// The macro also allows defining meta states that are collections of existing
683/// states:
684///
685/// ```
686/// # mod test {
687/// # use odem_rs_core::fsm::*;
688/// fsm! {
689/// pub enum States {
690/// Idle -> { Exec },
691/// Exec -> { Idle, Done },
692/// Done -> {}
693/// }
694///
695/// pub Any = { Idle, Exec, Done };
696/// pub Live: Any = { Idle, Exec };
697/// }
698/// # }
699/// ```
700///
701/// The `Live` meta state is marked as a subset of the `Any` meta state,
702/// allowing it to convert into that state.
703#[doc(hidden)]
704#[macro_export]
705macro_rules! __fsm {
706 (
707 $(#[$DOC:meta])*
708 $V:vis enum $E:ident $(<$C:ident: $W:ty>)? {
709 $($(#[$SDOC:meta])* $S:ident $(($T:ty))? -> {$($D:ident),* $(,)?}),* $(,)?
710 }
711
712 $(
713 $(#[$MDOC:meta])*
714 $MV:vis $MS:ident $(: $MB:ident)? = {$($MT:ident),* $(,)?};
715 )*
716 ) => {paste::paste! {
717 // generate the main enumeration
718 #[cfg_attr(doc, aquamarine::aquamarine)]
719 $(#[$DOC])*
720 #[doc = "```mermaid"]
721 #[doc = "---"]
722 #[doc = "title: " $E " Transition Diagram"]
723 #[doc = "---"]
724 #[doc = "flowchart LR"]
725 #[doc = $($("\t" $S "{{" $S "}}-->" $D "{{" $D "}}\n")*)*]
726 #[doc = "```"]
727 $V enum $E $(<$C: ?Sized + $W>)* {
728 $(
729 $(#[$SDOC])*
730 $S $(($T))*
731 ),*
732 }
733
734 impl<$($C: ?Sized + $W)*> $crate::fsm::FSM for $E $(<$C>)* {
735 type Token<'brand> = token::$E<'brand>;
736 type Erased = erased::$E;
737
738 fn token<'brand>(this: &Self, once: Ephemeral<'brand>, _: Private) -> Self::Token<'brand> {
739 match this {$(
740 Self::$S {..} => token::$E::$S(token::$S(once))
741 ),*}
742 }
743
744 fn erased(&self) -> Self::Erased {
745 match self {$(
746 Self::$S {..} => erased::$E::$S
747 ),*}
748 }
749
750 fn label(&self) -> &'static str {
751 match self {$(
752 Self::$S {..} => stringify!($S)
753 ),*}
754 }
755 }
756
757 #[allow(dead_code)]
758 impl<$($C: ?Sized + $W)*> $E $(<$C>)* {
759 $(
760 #[doc = "Returns `true` if the current state is "]
761 #[doc = "[`Self::" $S "`] and `false` otherwise."]
762 pub const fn [<is_ $S:lower>](&self) -> bool {
763 matches!(self, Self::$S {..})
764 }
765 )*
766 $(
767 #[doc = "Returns `true` if the current state is "]
768 #[doc = $("[`Self::" $MT "`]")" or "* " and `false` otherwise."]
769 pub const fn [<is_ $MS:lower>](&self) -> bool {
770 $(self.[<is_ $MT:lower>]())|*
771 }
772 )*
773 }
774
775 #[doc = "A module containing a configuration- and payload-erased "]
776 #[doc = "version of [`" $E "`]."]
777 $V mod erased {
778 // import the surrounding scope for the documentation links
779 #[allow(unused_imports)]
780 use super::*;
781
782 #[cfg_attr(doc, aquamarine::aquamarine)]
783 $(#[$DOC])*
784 #[doc = "```mermaid"]
785 #[doc = "---"]
786 #[doc = "title: " $E " Transition Diagram"]
787 #[doc = "---"]
788 #[doc = "flowchart LR"]
789 #[doc = $($("\t" $S "{{" $S "}}-->" $D "{{" $D "}}\n")*)*]
790 #[doc = "```"]
791 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
792 pub enum $E {$(
793 $(#[$SDOC])*
794 $S
795 ),*}
796
797 #[allow(dead_code)]
798 impl $E {
799 /// Returns the label of the current state as a static string.
800 pub const fn label(&self) -> &'static str {
801 match self {$(
802 Self::$S => stringify!($S)
803 ),*}
804 }
805
806 $(
807 #[doc = "Returns `true` if the current state is [`Self::" $S "`] and `false` otherwise."]
808 pub const fn [<is_ $S:lower>](&self) -> bool {
809 matches!(self, Self::$S)
810 }
811 )*
812
813 $(
814 #[doc = "Returns `true` if the current state is "]
815 #[doc = $("[`Self::" $MT "`]")" or "* " and `false` otherwise."]
816 pub const fn [<is_ $MS:lower>](&self) -> bool {
817 match self {
818 $(Self::$MT)|* => true,
819 _ => false
820 }
821 }
822 )*
823 }
824 }
825
826 #[doc = "A module containing weightless, branded 👻 token types that "]
827 #[doc = "are used as testimony that equally branded [`" $E "`] are in "]
828 #[doc = "the corresponding state (`" $($S)"` or `"* "`)."]
829 $V mod token {
830 use $crate::fsm::{Ephemeral, Transition};
831 use super::*;
832 use core::fmt;
833
834 #[doc = "An enumeration of token-witnesses for the states "]
835 #[doc = $("[`" $S "`](super::" $E "::" $S ")")", "* " and the meta-states "]
836 #[doc = $("[`" $MS "`]")", "* "."]
837 #[derive(Debug, Eq, PartialEq, Hash)]
838 pub enum $E<'brand> {$(
839 #[doc = "[`" $S "`] state containing the token as a witness."]
840 #[doc = ""]
841 #[doc = "[`" $S "`]: super::" $E "::" $S]
842 $S($S<'brand>)
843 ),*}
844
845 #[allow(dead_code)]
846 impl<'brand> $E<'brand> {
847 /// Strips the token information from the enumeration, yielding
848 /// a type-erased variant of it.
849 pub const fn erased(&self) -> erased::$E {
850 match self {$(
851 Self::$S(_) => erased::$E::$S
852 ),*}
853 }
854
855 $(
856 #[doc = "Attempts to convert the token referencing the [`" $E "`] "]
857 #[doc = "into a token referencing the more specialized [`" $S "`]."]
858 #[doc = ""]
859 #[doc = "[`" $E "`]: super::" $E]
860 #[doc = "[`" $S "`]: super::" $E "::" $S]
861 pub const fn [<as_ $S:lower>](&self) -> Option<&$S<'brand>> {
862 match self {
863 Self::$S(token) => Some(token),
864 _ => None
865 }
866 }
867
868 #[doc = "Attempts to convert the token for [`" $E "`] "]
869 #[doc = "into a token for the more specialized [`" $S "`]."]
870 #[doc = ""]
871 #[doc = "[`" $E "`]: super::" $E]
872 #[doc = "[`" $S "`]: super::" $E "::" $S]
873 pub const fn [<into_ $S:lower>](self) -> Result<$S<'brand>, error::[<Not $S>]> {
874 match self {
875 Self::$S(token) => Ok(token),
876 _ => Err(error::[<Not $S>](self.erased()))
877 }
878 }
879 )*
880
881 $(
882 #[doc = "Attempts to convert the token referencing the "]
883 #[doc = "[`" $E "`] into a token referencing state "]
884 #[doc = $($MT)" or "* "."]
885 #[doc = ""]
886 #[doc = "[`" $E "`]: super::" $E]
887 pub const fn [<as_ $MS:lower>](&self) -> Option<&$MS<'brand>> {
888 match self {
889 $(Self::$MT(token) => Some(unsafe { &*(token as *const $MT<'brand> as *const $MS<'brand>) }),)*
890 _ => None
891 }
892 }
893
894 #[doc = "Attempts to convert the token for [`" $E "`] "]
895 #[doc = "into a token for the more specialized "$($MT)" or "* "."]
896 #[doc = ""]
897 #[doc = "[`" $E "`]: super::" $E]
898 pub const fn [<into_ $MS:lower>](self) -> Result<$MS<'brand>, error::[<Not $MS>]> {
899 match self {
900 $(Self::$MT(token) => Ok($MS(token.0)),)*
901 _ => Err(error::[<Not $MS>](self.erased()))
902 }
903 }
904 )*
905 }
906
907 impl<'brand> From<$E<'brand>> for Ephemeral<'brand> {
908 fn from(value: $E<'brand>) -> Self {
909 match value {
910 $($E::$S(token) => token.0),*
911 }
912 }
913 }
914
915 $(
916 #[doc = "Token testifying that the equally branded "]
917 #[doc = "[`" $E "`] is [`" $S "`]."]
918 #[doc = ""]
919 #[doc = "[`" $S "`]: super::" $E "::" $S]
920 #[derive(PartialOrd, PartialEq, Ord, Eq, Hash)]
921 #[repr(transparent)]
922 $V struct $S<'brand>(pub(super) Ephemeral<'brand>);
923
924 impl<'brand> $S<'brand> {
925 /// Creates a new token of this type with an invariant lifetime `'brand`.
926 ///
927 /// # Safety
928 /// Since tokens of this type are used to provide security guarantees,
929 /// creating new ones should only be done if it is absolutely certain that
930 /// the property symbolized by the token is actually upheld by the equally
931 /// branded object it applies to.
932 pub const unsafe fn new(once: Ephemeral<'brand>) -> Self {
933 Self(once)
934 }
935 }
936
937 impl<'brand> From<$S<'brand>> for Ephemeral<'brand> {
938 fn from(value: $S<'brand>) -> Self {
939 value.0
940 }
941 }
942
943 impl fmt::Debug for $S<'_> {
944 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
945 write!(f, concat!(stringify!($E), "(", stringify!($S), ")"))
946 }
947 }
948 )*
949
950 $crate::fsm::fsm! {
951 @gen_transitions ($E) ($($W)*) ($($S $(($T))* => $($D)*),*)
952 }
953
954 $(
955 $(#[$MDOC])*
956 #[repr(transparent)]
957 $MV struct $MS<'brand>(Ephemeral<'brand>);
958
959 #[allow(dead_code)]
960 impl<'brand> $MS<'brand> {
961 /// Creates a new token of this type with an invariant lifetime `'brand`.
962 ///
963 /// # Safety
964 /// Since tokens of this type are used to provide security guarantees,
965 /// creating new ones should only be done if it is absolutely certain that
966 /// the property symbolized by the token is actually upheld by the equally
967 /// branded object it applies to.
968 pub const unsafe fn new(once: Ephemeral<'brand>) -> Self {
969 Self(once)
970 }
971
972 $(
973 #[doc = "Re-casts a reference to the specialized "]
974 #[doc = "[`" $MS "`]-token into a token of the generalized "]
975 #[doc = "[`" $MB "`] kind."]
976 pub const fn [<as_ $MB:lower>](&self) -> &$MB<'brand> {
977 unsafe { &*(self as *const $MS<'brand> as *const $MB<'brand>) }
978 }
979
980 #[doc = "Converts the specialized [`" $MS "`]-token into "]
981 #[doc = "a token of the generalized [`" $MB "`] kind."]
982 pub const fn [<into_ $MB:lower>](self) -> $MB<'brand> {
983 $MB(self.0)
984 }
985 )*
986 }
987
988 impl<'brand> From<$MS<'brand>> for Ephemeral<'brand> {
989 fn from(value: $MS<'brand>) -> Self {
990 value.0
991 }
992 }
993
994 impl fmt::Debug for $MS<'_> {
995 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
996 write!(f, concat!("Token(", stringify!($MS), ")"))
997 }
998 }
999
1000 $(
1001 #[allow(dead_code)]
1002 impl<'brand> $MT<'brand> {
1003 #[doc = "Re-casts a reference to the specialized "]
1004 #[doc = "[`" $MT "`]-token into a token of the generalized "]
1005 #[doc = "[`" $MS "`] kind."]
1006 pub const fn [<as_ $MS:lower>](&self) -> &$MS<'brand> {
1007 unsafe { &*(self as *const $MT<'brand> as *const $MS<'brand>) }
1008 }
1009
1010 #[doc = "Converts the specialized [`" $MT "`]-token into "]
1011 #[doc = "a token of the generalized [`" $MS "`] kind."]
1012 pub const fn [<into_ $MS:lower>](self) -> $MS<'brand> {
1013 $MS(self.0)
1014 }
1015 }
1016
1017 impl<'brand> From<$MT<'brand>> for $MS<'brand> {
1018 fn from(value: $MT<'brand>) -> Self { value.[<into_ $MS:lower>]() }
1019 }
1020 )*
1021 )*
1022 }
1023
1024 /// A module for error types that may occur during conversion.
1025 $V mod error {
1026 use core::fmt;
1027
1028 $(
1029 #[doc = "Error type indicating that the current [`" $E "`] was not [`" $S "`]."]
1030 #[doc = ""]
1031 #[doc = "[`" $S "`]: super::" $E "::" $S]
1032 #[doc = "[`" $E "`]: super::" $E]
1033 #[derive(Copy, Clone, Debug)]
1034 pub struct [<Not $S>](pub super::erased::$E);
1035
1036 impl fmt::Display for [<Not $S>] {
1037 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1038 write!(
1039 f,
1040 concat!("<", stringify!($S), "> expected but <{}> found instead"),
1041 self.0.label()
1042 )
1043 }
1044 }
1045
1046 impl core::error::Error for [<Not $S>] {}
1047 )*
1048
1049 $(
1050 #[doc = "Error type indicating that the current [`" $E "`] was "]
1051 #[doc = "none of " $("[`" $MT "`](super::" $E "::" $MT ")")" or "* "."]
1052 #[doc = ""]
1053 #[doc = "[`" $E "`]: super::" $E]
1054 #[derive(Copy, Clone, Debug)]
1055 pub struct [<Not $MS>](pub super::erased::$E);
1056
1057 impl fmt::Display for [<Not $MS>] {
1058 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1059 write!(
1060 f,
1061 concat!("either " $(, "'", stringify!($MT), "'",)" or "* " expected but state '{}' found instead"),
1062 self.0.label()
1063 )
1064 }
1065 }
1066
1067 impl core::error::Error for [<Not $MS>] {}
1068 )*
1069 }
1070 }};
1071
1072 // transitions with a generic argument
1073 (@gen_transitions ($E:ident) ($W:ty) ($($S:ident $(($T:ty))? => $($D:ident)*),*)) => {paste::paste! {$(
1074 impl<'b, C: ?Sized + $W> Transition<'b,$S<'b>, $S<'b>> for super::$E<C> {
1075 #[allow(unused_parens)]
1076 type Data = ($($T)*);
1077
1078 #[doc = "Updates the state of [`" $S "`],"]
1079 #[doc = "requiring a " $S "-token and returning it."]
1080 fn transition(this: &mut Self, token: $S<'b>, _data: Self::Data, _: $crate::fsm::Private) -> $S<'b> {
1081 *this = super::$E::$S $(({
1082 let _: $T;
1083 _data
1084 }))?;
1085 token
1086 }
1087 }
1088
1089 $(
1090 impl<'b, C: ?Sized + $W> Transition<'b,$S<'b>, $D<'b>> for super::$E<C> {
1091 type Data = <Self as Transition<'b,$D<'b>,$D<'b>>>::Data;
1092
1093 #[doc = "Performs the transition between [`" $S "`] and [`" $D "`],"]
1094 #[doc = "requiring a " $S "-token and returning the resulting "]
1095 #[doc = $D "-token."]
1096 fn transition(this: &mut Self, token: $S<'b>, data: Self::Data, p: $crate::fsm::Private) -> $D<'b> {
1097 #[cfg(feature = "tracing")]
1098 ::tracing::trace!(from = %stringify!($S), into = %stringify!($D), "transition");
1099 Self::transition(this, $D(token.0), data, p)
1100 }
1101 }
1102 )*
1103 )*}};
1104
1105 // transitions without a generic argument
1106 (@gen_transitions ($E:ident) () ($($S:ident $(($T:ty))? => $($D:ident)*),*)) => {paste::paste! {$(
1107 impl<'b> Transition<'b, $S<'b>, $S<'b>> for super::$E {
1108 #[allow(unused_parens)]
1109 type Data = ($($T)*);
1110
1111 #[doc = "Updates the state of [`" $S "`],"]
1112 #[doc = "requiring a " $S "-token and returning it."]
1113 fn transition(this: &mut Self, token: $S<'b>, _data: Self::Data, _: $crate::fsm::Private) -> $S<'b> {
1114 *this = super::$E::$S $(({
1115 let _: $T;
1116 _data
1117 }))?;
1118 token
1119 }
1120 }
1121
1122 $(
1123 impl<'b> Transition<'b, $S<'b>, $D<'b>> for super::$E {
1124 type Data = <Self as Transition<'b,$D<'b>,$D<'b>>>::Data;
1125
1126 #[doc = "Performs the transition between [`" $S "`] and [`" $D "`],"]
1127 #[doc = "requiring a " $S "-token and returning the resulting "]
1128 #[doc = $D "-token."]
1129 fn transition(this: &mut Self, token: $S<'b>, data: Self::Data, p: $crate::fsm::Private) -> $D<'b> {
1130 #[cfg(feature = "tracing")]
1131 ::tracing::trace!(from = %stringify!($S), into = %stringify!($D), "transition");
1132 Self::transition(this, $D(token.0), data, p)
1133 }
1134 }
1135 )*
1136 )*}};
1137}
1138
1139// Export the macro from within this module.
1140#[doc(inline)]
1141pub use __fsm as fsm;
1142
1143#[cfg(test)]
1144mod tests {
1145 use super::*;
1146
1147 // Define a state machine with three states: `Idle`, `Exec`, and `Done`.
1148 fsm! {
1149 #[allow(dead_code)]
1150 pub enum States {
1151 Idle -> { Busy },
1152 Busy -> { Idle, Done },
1153 Done(bool) -> {}
1154 }
1155 }
1156
1157 #[test]
1158 fn simple_sm() {
1159 let m1 = StateMachine::new(States::Idle);
1160
1161 let _ = m1.brand(|sm, once| {
1162 // Assert the idle state
1163 let idle = sm.token(once).into_idle().unwrap();
1164 // Transition into the running state
1165 let busy: token::Busy<'_> = sm.transition(idle, ());
1166 // Do something with the running state
1167 let _: token::Done<'_> = sm.transition(busy, true);
1168
1169 m1.borrow()
1170 });
1171 }
1172}