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}