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}