Skip to main content

magicstatemachines/macros/
state_machine_definition.rs

1/// Defines the state-machine contract for a stand-in type.
2///
3/// Use this macro in the crate that owns the state marker types and the
4/// stand-in ZST. The generated code contains only contract information:
5/// [`Initial`] implementations, [`Transition`] implementations, and optional
6/// state-union declarations. It deliberately contains no runtime type and no
7/// transition bodies. Think of this as the public state-machine interface: it
8/// says which states exist, which states may be constructed initially, and
9/// which edges are legal.
10///
11/// That separation is the main enforcement mechanism. A downstream crate can
12/// implement behavior for its runtime type, but it cannot add new legal
13/// transitions unless it owns the stand-in or the state markers. In the normal
14/// split-crate layout, the definition crate owns both, so Rust's orphan rules
15/// make the transition graph a hard public contract.
16///
17/// The transition argument list declares the signature required by
18/// [`transition!`](macro@crate::transition). Argument names are documentation
19/// for the contract; only their types participate in the generated
20/// [`Transition::F`](crate::Transition::F) signature. For example,
21/// `transition Connected => Authenticated(user: String);` emits an impl whose
22/// signature is equivalent to:
23///
24/// ```ignore
25/// impl magicstatemachines::Transition<Connected, Authenticated> for ConnectionStandin {
26///     type F = fn(String);
27/// }
28/// ```
29///
30/// It does not say what happens to the runtime data. The implementation crate
31/// supplies that effect later with [`StateMachineImpl!`](macro@crate::StateMachineImpl).
32///
33/// ```ignore
34/// use magicstatemachines::{StateMachineDefinition, States};
35///
36/// pub struct ConnectionStandin;
37///
38/// pub mod states {
39///     use magicstatemachines::States;
40///
41///     States! {
42///         Disconnected;
43///         Reconnecting;
44///         Connected;
45///         Authenticated;
46///         Failed;
47///     }
48/// }
49///
50/// use states::*;
51///
52/// StateMachineDefinition! {
53///     for ConnectionStandin;
54///
55///     Initial: Disconnected | Reconnecting;
56///
57///     transition Disconnected => Connected | Failed();
58///     transition Connected => Authenticated(user: String);
59///     transition Connected | Authenticated => Disconnected();
60///     transition Authenticated => Connected();
61///
62///     union All: Disconnected | Connected | Authenticated;
63///     union Online: All, Connected | Authenticated;
64/// }
65/// ```
66///
67/// `|` on the left or right of a transition expands to every pair. For
68/// example, `transition Authenticated => Connected | Failed();` declares both
69/// `Authenticated -> Connected` and `Authenticated -> Failed` with the same
70/// empty signature. `transition Connected | Authenticated => Disconnected();`
71/// declares two incoming edges into `Disconnected`. This is only a declaration;
72/// whether the implementation shares a body is decided later by
73/// [`StateMachineImpl!`](macro@crate::StateMachineImpl).
74///
75/// Union declarations are forwarded to [`StateUnion!`](macro@crate::StateUnion).
76/// They can be written here for convenience, but they are still independent of
77/// the stand-in and may also be written separately. A union such as
78/// `union Online: Connected | Authenticated;` does not add transitions by
79/// itself; it only gives APIs a way to name "any online state".
80///
81/// A transition declaration must end in `;`. Bodies are rejected on purpose:
82///
83/// ```compile_fail
84/// use magicstatemachines::{StateMachineDefinition, States};
85///
86/// pub struct Standin;
87/// States! { A; B; }
88///
89/// StateMachineDefinition! {
90///     for Standin;
91///
92///     Initial: A;
93///
94///     transition A => B() {
95///         // Effects belong in `StateMachineImpl!`, not in the definition.
96///     }
97/// }
98/// ```
99///
100/// [`Initial`]: crate::Initial
101/// [`Transition`]: crate::Transition
102#[macro_export]
103macro_rules! StateMachineDefinition {
104    (
105        for $standin:ty;
106        Initial: $first_initial:ident $(| $initial:ident)*;
107        $($transitions:tt)*
108    ) => {
109        $crate::__StateMachineDefinition!(@initial_impls $standin; $first_initial $(| $initial)*);
110
111        $crate::__StateMachineDefinition!(
112            @parse $standin;
113            $($transitions)*
114        );
115    };
116}
117
118#[doc(hidden)]
119#[macro_export]
120macro_rules! __StateMachineDefinition {
121    (@initial_impls $standin:ty; $first_initial:ident $(| $initial:ident)*) => {
122        impl $crate::Initial<$first_initial> for $standin {}
123        $(
124            impl $crate::Initial<$initial> for $standin {}
125        )*
126    };
127    (@parse $standin:ty;) => {};
128    (
129        @parse $standin:ty;
130        union $name:ident:
131        $first:ident $(| $state:ident)*;
132        $($rest:tt)*
133    ) => {
134        $crate::StateUnion!($name: $first $(| $state)*);
135        $crate::__StateMachineDefinition!(@parse $standin; $($rest)*);
136    };
137    (
138        @parse $standin:ty;
139        union $name:ident:
140        $first_super:ident $(+ $supertrait:ident)*,
141        $first:ident $(| $state:ident)*;
142        $($rest:tt)*
143    ) => {
144        $crate::StateUnion!($name: $first_super $(+ $supertrait)*, $first $(| $state)*);
145        $crate::__StateMachineDefinition!(@parse $standin; $($rest)*);
146    };
147    (
148        @parse $standin:ty;
149        transition $first_from:ident $(| $from:ident)* => $first_to:ident $(| $to:ident)*
150            ($($arg:ident : $arg_ty:ty),* $(,)?);
151        $($rest:tt)*
152    ) => {
153        $crate::__StateMachineDefinition!(
154            @transition_impls $standin; [$first_from $(| $from)*] => [$first_to $(| $to)*]
155            ($($arg : $arg_ty),*)
156        );
157        $crate::__StateMachineDefinition!(@parse $standin; $($rest)*);
158    };
159    (
160        @parse $standin:ty;
161        transition $first_from:ident $(| $from:ident)* => $first_to:ident $(| $to:ident)*
162            ($($arg:ident : $arg_ty:ty),* $(,)?),
163        $($rest:tt)*
164    ) => {
165        ::core::compile_error!(
166            "state-machine definition transitions are declarations and must end with `;`"
167        );
168    };
169    (
170        @parse $standin:ty;
171        transition $first_from:ident $(| $from:ident)* => $first_to:ident $(| $to:ident)*
172            ($($arg:ident : $arg_ty:ty),* $(,)?) { $($body:tt)* }
173        $($rest:tt)*
174    ) => {
175        ::core::compile_error!(
176            "state-machine definition transitions cannot contain implementation bodies"
177        );
178    };
179    (
180        @parse $standin:ty;
181        transition $first_from:ident $(| $from:ident)* => $first_to:ident $(| $to:ident)*
182            ($($arg:ident : $arg_ty:ty),* $(,)?) { $($body:tt)* },
183        $($rest:tt)*
184    ) => {
185        ::core::compile_error!(
186            "state-machine definition transitions cannot contain implementation bodies"
187        );
188    };
189    (
190        @transition_impls $standin:ty; [$first_from:ident $(| $from:ident)*] => [$first_to:ident $(| $to:ident)*]
191        $args:tt
192    ) => {
193        $crate::__StateMachineDefinition!(
194            @transition_impls_for_from $standin; $first_from => [$first_to $(| $to)*] $args
195        );
196        $crate::__StateMachineDefinition!(
197            @transition_impls_for_froms $standin; [$($from)|*] => [$first_to $(| $to)*] $args
198        );
199    };
200    (@transition_impls_for_froms $standin:ty; [] => $targets:tt $args:tt) => {};
201    (
202        @transition_impls_for_froms $standin:ty; [$first_from:ident $(| $from:ident)*] => $targets:tt $args:tt
203    ) => {
204        $crate::__StateMachineDefinition!(
205            @transition_impls_for_from $standin; $first_from => $targets $args
206        );
207        $crate::__StateMachineDefinition!(
208            @transition_impls_for_froms $standin; [$($from)|*] => $targets $args
209        );
210    };
211    (
212        @transition_impls_for_from $standin:ty; $from:ident => [$first_to:ident $(| $to:ident)*]
213        $args:tt
214    ) => {
215        $crate::__StateMachineDefinition!(@transition_impl $standin; $from => $first_to $args);
216        $(
217            $crate::__StateMachineDefinition!(@transition_impl $standin; $from => $to $args);
218        )*
219    };
220    (@transition_impl $standin:ty; $from:ident => $to:ident ()) => {
221        impl $crate::Transition<$from, $to> for $standin {}
222    };
223    (
224        @transition_impl $standin:ty; $from:ident => $to:ident
225        ($first_arg:ident : $first_arg_ty:ty $(, $arg:ident : $arg_ty:ty)*)
226    ) => {
227        impl $crate::Transition<$from, $to> for $standin {
228            type F = fn($first_arg_ty $(, $arg_ty)*);
229        }
230    };
231}