rust_sfsm_macros/
lib.rs

1use darling::FromMeta;
2use proc_macro::TokenStream;
3use quote::quote;
4use syn::{
5    Field, FieldMutability, Fields, FieldsNamed, Ident, Item, ItemStruct, Path, Type, TypePath,
6    Visibility, parse, parse_macro_input, token::Colon,
7};
8
9#[derive(Debug, FromMeta)]
10#[darling(derive_syn_parse)]
11struct Args {
12    states: Path,
13    context: Path,
14}
15
16/// # Rust-SFSM Attribute Macro.
17///
18/// SFSM stands for Static Finite State Machine.
19///
20/// This macro must be used on `struct`'s and implements
21/// the boilerplate for any type that has a state-like behavior.
22///
23/// ## Example
24///
25/// ```rust
26/// use rust_sfsm::{StateBehavior, StateMachine, rust_sfsm};
27///
28/// /// List of protocol states.
29/// #[derive(Clone, Copy, Default, PartialEq)]
30/// enum States {
31///     #[default]
32///     Init,
33///     Opened,
34///     Closed,
35///     Locked,
36/// }
37///
38/// /// List of protocol events.
39/// enum Events {
40///     Create,
41///     Open,
42///     Close,
43///     Lock,
44///     Unlock,
45/// }
46///
47/// /// Protocol state machine context (data shared between states).
48/// #[derive(Default)]
49/// struct Context {
50///     lock_counter: u16,
51/// }
52///
53/// impl StateBehavior for States {
54///     type State = Self;
55///     type Event<'a> = Events;
56///     type Context = Context;
57///
58///     fn enter(&self, _context: &mut Self::Context) {
59///         if self == &States::Locked {
60///             _context.lock_counter += 1
61///         }
62///     }
63///
64///     fn handle_event(
65///         &self,
66///         event: &Self::Event<'_>,
67///         _context: &mut Self::Context,
68///     ) -> Option<Self::State> {
69///         match (self, event) {
70///             (&States::Init, &Events::Create) => Some(States::Opened),
71///             (&States::Opened, &Events::Close) => Some(States::Closed),
72///             (&States::Closed, &Events::Open) => Some(States::Opened),
73///             (&States::Closed, &Events::Lock) => Some(States::Locked),
74///             (&States::Locked, &Events::Unlock) => Some(States::Closed),
75///             _ => None,
76///         }
77///     }
78/// }
79///
80/// #[rust_sfsm(states = States, context = Context)]
81/// struct Protocol {}
82///
83/// impl Protocol {
84///     fn new() -> Self {
85///         Self {
86///             current_state: Default::default(),
87///             context: Default::default(),
88///         }
89///     }
90/// }
91/// ```
92///
93/// ## Macro Expansion
94///
95/// The `rust_sfsm` macro expands to this:
96///
97/// ```rust
98/// struct Protocol {
99///     current_state: States,
100///     context: Context,
101/// }
102///
103/// impl ::rust_sfsm::StateMachine<States> for Protocol {
104///     fn current_state(&self) -> <States as ::rust_sfsm::StateBehavior>::State {
105///         self.current_state
106///     }
107///
108///     fn handle_event(
109///         &mut self,
110///         event: &<States as ::rust_sfsm::StateBehavior>::Event<'_>,
111///     ) {
112///         if let Some(next_state) = self
113///             .current_state
114///             .handle_event(event, &mut self.context)
115///         {
116///             self.transit(next_state)
117///         }
118///     }
119///
120///     fn transit(&mut self, new_state: <States as ::rust_sfsm::StateBehavior>::State) {
121///         self.current_state.exit(&mut self.context);
122///         self.current_state = new_state;
123///         self.current_state.enter(&mut self.context);
124///     }
125///
126///     fn force_state(&mut self, new_state: <States as ::rust_sfsm::StateBehavior>::State) {
127///         self.current_state = new_state;
128///     }
129/// }
130/// ```
131#[proc_macro_attribute]
132pub fn rust_sfsm(args: TokenStream, input: TokenStream) -> TokenStream {
133    let args: Args = match parse(args) {
134        Ok(args) => args,
135        Err(e) => {
136            return e.into_compile_error().into();
137        }
138    };
139
140    let input = parse_macro_input!(input as Item);
141
142    let output = match input {
143        Item::Struct(mut item_struct) => {
144            // add fields
145            add_fields(&mut item_struct, &args);
146
147            // add state machine impl
148            let struct_ident = &item_struct.ident;
149            let trait_impl = generate_state_machine_impl(struct_ident, &args);
150
151            quote! {
152                #item_struct
153                #trait_impl
154            }
155        }
156
157        _ => {
158            return syn::Error::new_spanned(input, "rust_sfsm macro can only be applied to struct")
159                .into_compile_error()
160                .into();
161        }
162    };
163
164    output.into()
165}
166
167fn add_fields(item_struct: &mut ItemStruct, args: &Args) {
168    if let Fields::Named(FieldsNamed { named, .. }) = &mut item_struct.fields {
169        let current_state_field = Field {
170            attrs: Vec::new(),
171            vis: Visibility::Inherited,
172            mutability: FieldMutability::None,
173            ident: Some(Ident::new("current_state", proc_macro2::Span::call_site())),
174            colon_token: Some(Colon::default()),
175            ty: Type::Path(TypePath {
176                qself: None,
177                path: args.states.clone(),
178            }),
179        };
180
181        let context_field = syn::Field {
182            attrs: Vec::new(),
183            vis: Visibility::Inherited,
184            mutability: syn::FieldMutability::None,
185            ident: Some(syn::Ident::new("context", proc_macro2::Span::call_site())),
186            colon_token: Some(syn::token::Colon::default()),
187            ty: syn::Type::Path(syn::TypePath {
188                qself: None,
189                path: args.context.clone(),
190            }),
191        };
192
193        named.push(current_state_field);
194        named.push(context_field);
195    }
196}
197
198fn generate_state_machine_impl(struct_ident: &Ident, args: &Args) -> proc_macro2::TokenStream {
199    let states_type = &args.states;
200
201    quote! {
202        impl ::rust_sfsm::StateMachine<#states_type> for #struct_ident
203        {
204            fn current_state(&self) -> <#states_type as ::rust_sfsm::StateBehavior>::State {
205                self.current_state
206            }
207
208            fn handle_event(&mut self, event: &<#states_type as ::rust_sfsm::StateBehavior>::Event<'_>) {
209                if let Some(next_state) = self.current_state.handle_event(event, &mut self.context) {
210                    self.transit(next_state)
211                }
212            }
213
214            fn transit(&mut self, new_state: <#states_type as ::rust_sfsm::StateBehavior>::State) {
215                self.current_state.exit(&mut self.context);
216                self.current_state = new_state;
217                self.current_state.enter(&mut self.context);
218            }
219
220            fn force_state(&mut self, new_state: <#states_type as ::rust_sfsm::StateBehavior>::State) {
221                self.current_state = new_state;
222            }
223        }
224    }
225}