From 2ba444b81036816cd18efc031a78fac936509052 Mon Sep 17 00:00:00 2001 From: Prabhpreet Dua <615318+prabhpreet@users.noreply.github.com> Date: Sat, 17 Aug 2024 18:06:38 +0530 Subject: [PATCH 1/4] Add custom attributes for States and Events enum Use `states_attr` and `events_attr` to add custom attributes (eg. #[allow(...)]) --- macros/src/codegen.rs | 5 +++++ macros/src/parser/mod.rs | 6 +++++- macros/src/parser/state_machine.rs | 17 ++++++++++++++++- tests/test.rs | 30 ++++++++++++++++++++++++++++++ 4 files changed, 56 insertions(+), 2 deletions(-) diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index 807b675..73ccf58 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -541,6 +541,9 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { let derive_states_list = &sm.derive_states; let derive_events_list = &sm.derive_events; + + let states_attr_list = &sm.states_attr; + let events_attr_list = &sm.events_attr; // Build the states and events output quote! { /// This trait outlines the guards and actions that need to be implemented for the state @@ -576,6 +579,7 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { /// List of auto-generated states. #[allow(missing_docs)] #[derive(#(#derive_states_list),*)] + #(#states_attr_list)* pub enum #states_type_name <#state_lifetimes> { #(#state_list),* } /// Manually define PartialEq for #states_type_name based on variant only to address issue-#21 @@ -589,6 +593,7 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { /// List of auto-generated events. #[allow(missing_docs)] #[derive(#(#derive_events_list),*)] + #(#events_attr_list)* pub enum #events_type_name <#event_lifetimes> { #(#event_list),* } /// Manually define PartialEq for #events_type_name based on variant only to address issue-#21 diff --git a/macros/src/parser/mod.rs b/macros/src/parser/mod.rs index faed8ee..1995b6d 100644 --- a/macros/src/parser/mod.rs +++ b/macros/src/parser/mod.rs @@ -16,7 +16,7 @@ use proc_macro2::{Span, TokenStream}; use crate::parser::event::Transition; use std::collections::{hash_map, HashMap}; use std::fmt; -use syn::{parse, Ident, Type}; +use syn::{parse, Attribute, Ident, Type}; use transition::StateTransition; pub type TransitionMap = HashMap>; @@ -48,6 +48,8 @@ pub struct ParsedStateMachine { pub name: Option, pub derive_states: Vec, pub derive_events: Vec, + pub states_attr: Vec, + pub events_attr: Vec, pub temporary_context_type: Option, pub custom_error: bool, pub states: HashMap, @@ -244,6 +246,8 @@ impl ParsedStateMachine { name: sm.name, derive_states: sm.derive_states, derive_events: sm.derive_events, + states_attr: sm.states_attr, + events_attr: sm.events_attr, temporary_context_type: sm.temporary_context_type, custom_error: sm.custom_error, states, diff --git a/macros/src/parser/state_machine.rs b/macros/src/parser/state_machine.rs index 2b011d0..c06c57b 100644 --- a/macros/src/parser/state_machine.rs +++ b/macros/src/parser/state_machine.rs @@ -1,5 +1,5 @@ use super::transition::{StateTransition, StateTransitions}; -use syn::{braced, bracketed, parse, spanned::Spanned, token, Ident, Token, Type}; +use syn::{braced, bracketed, parse, spanned::Spanned, token, Attribute, Ident, Token, Type}; #[derive(Debug)] pub struct StateMachine { @@ -9,6 +9,8 @@ pub struct StateMachine { pub name: Option, pub derive_states: Vec, pub derive_events: Vec, + pub states_attr: Vec, + pub events_attr: Vec, } impl StateMachine { @@ -20,6 +22,8 @@ impl StateMachine { name: None, derive_states: Vec::new(), derive_events: Vec::new(), + states_attr: Vec::new(), + events_attr: Vec::new(), } } @@ -138,6 +142,17 @@ impl parse::Parse for StateMachine { }; } } + + "states_attr" => { + input.parse::()?; + statemachine.events_attr = Attribute::parse_outer(&input)?; + } + + "events_attr" => { + input.parse::()?; + statemachine.events_attr = Attribute::parse_outer(&input)?; + } + keyword => { return Err(parse::Error::new( input.span(), diff --git a/tests/test.rs b/tests/test.rs index 4025e3e..5f97517 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -487,3 +487,33 @@ fn test_wildcard_states_and_internal_transitions() { assert!(sm.process_event(Events::Event2).is_err()); // InvalidEvent assert_eq!(States::State3, sm.state); } +#[test] +fn test_specify_attrs() { + #![deny(non_camel_case_types)] + statemachine! { + transitions: { + *State1 + tostate2 = State2, + State2 + tostate3 / increment_count = State3 + }, + derive_states: [Debug, Clone, Copy], + states_attr: #[non_exhaustive] #[repr(u8)], + events_attr: #[derive(Debug)] #[allow(non_camel_case_types)] + } + + #[derive(Debug, Default)] + pub struct Context { + count: u32, + } + + impl StateMachineContext for Context { + fn increment_count(&mut self) -> Result<(), ()> { + self.count += 1; + Ok(()) + } + } + let mut sm = StateMachine::new(Context::default()); + + assert_eq!(sm.state().clone(), States::State1); + assert_transition!(sm, Events::tostate2, States::State2, 0); + assert_transition!(sm, Events::tostate3, States::State3, 1); +} From 9728b4a554807fae32c9babea635c52509c009a3 Mon Sep 17 00:00:00 2001 From: Prabhpreet Dua <615318+prabhpreet@users.noreply.github.com> Date: Sat, 17 Aug 2024 18:56:38 +0530 Subject: [PATCH 2/4] Test example to include serde, fix --- Cargo.toml | 1 + macros/src/parser/state_machine.rs | 2 +- tests/test.rs | 5 +++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ea60ad0..fa3a5ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ smlang-macros = { path = "macros", version = "0.8.0" } [dev-dependencies] smol = "1" derive_more = "0.99.17" +serde = {version = "1",features = ["derive"]} [target.'cfg(not(target_os = "none"))'.dev-dependencies] trybuild = "1.0" diff --git a/macros/src/parser/state_machine.rs b/macros/src/parser/state_machine.rs index c06c57b..a0ddb2d 100644 --- a/macros/src/parser/state_machine.rs +++ b/macros/src/parser/state_machine.rs @@ -145,7 +145,7 @@ impl parse::Parse for StateMachine { "states_attr" => { input.parse::()?; - statemachine.events_attr = Attribute::parse_outer(&input)?; + statemachine.states_attr = Attribute::parse_outer(&input)?; } "events_attr" => { diff --git a/tests/test.rs b/tests/test.rs index 5f97517..5da7933 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -490,13 +490,14 @@ fn test_wildcard_states_and_internal_transitions() { #[test] fn test_specify_attrs() { #![deny(non_camel_case_types)] + use serde::Serialize; statemachine! { transitions: { *State1 + tostate2 = State2, State2 + tostate3 / increment_count = State3 }, - derive_states: [Debug, Clone, Copy], - states_attr: #[non_exhaustive] #[repr(u8)], + derive_states: [Debug, Clone, Copy, Serialize], + states_attr: #[non_exhaustive] #[repr(u8)] #[serde(tag="type")], events_attr: #[derive(Debug)] #[allow(non_camel_case_types)] } From 8a6b893fc55c6b94b9c877e566dbdaee89bea595 Mon Sep 17 00:00:00 2001 From: Prabhpreet Dua <615318+prabhpreet@users.noreply.github.com> Date: Mon, 19 Aug 2024 18:44:55 +0530 Subject: [PATCH 3/4] Remove `derive_states`, `derive_events` fields, use `states_attr`, `events_attr` instead --- README.md | 10 +++---- docs/dsl.md | 10 +++---- examples/dominos.rs | 4 +-- examples/named_ex1.rs | 2 +- examples/state_machine_logger.rs | 4 +-- macros/src/codegen.rs | 5 ---- macros/src/parser/mod.rs | 4 --- macros/src/parser/state_machine.rs | 42 +++--------------------------- src/lib.rs | 4 +-- tests/test.rs | 17 ++++++------ 10 files changed, 28 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index d190c24..16cfa8f 100644 --- a/README.md +++ b/README.md @@ -370,9 +370,9 @@ in `dominos`. ## Helpers -### Auto-derive certain traits for states and events +### Specify attributes for states and events -Setting `derive_events` and `derive_states` fields to an array of traits adds a derive expression to `Events` and `States` enums respectively. To derive Display, use `derive_more::Display`. +Setting `events_attr` and `states_attr` fields to a list of attributes to `Events` and `States` enums respectively. To derive Display, use `derive_more::Display`. ```rust @@ -380,8 +380,8 @@ use core::Debug; use derive_more::Display; // ... statemachine!{ - derive_states: [Debug, Display], - derive_events: [Debug, Display], + states_attr: #[derive(Debug, Display)], + events_attr: #[derive(Debug, Display)], transitions: { *State1 + Event1 = State2, } @@ -409,7 +409,7 @@ fn log_action(&self, action: &'static str) {} fn log_state_change(&self, new_state: &States) {} ``` -See `examples/state_machine_logger.rs` for an example which uses `derive_states` and `derive_events` to derive `Debug` implementations for easy logging. +See `examples/state_machine_logger.rs` for an example which uses `states_attr` and `events_attr` to derive `Debug` implementations for easy logging. ## Contributors diff --git a/docs/dsl.md b/docs/dsl.md index 09c6ef0..3642d65 100644 --- a/docs/dsl.md +++ b/docs/dsl.md @@ -23,11 +23,11 @@ statemachine!{ // error type instead of `()`. custom_error: false, - // [Optional] A list of derive names for the generated `States` and `Events` - // enumerations respectively. For example, to `#[derive(Debug)]`, these - // would both be specified as `[Debug]`. - derive_states: [], - derive_events: [], + // [Optional] A list of attributes for the generated `States` and `Events` + // enumerations respectively. For example, to `#[derive(Debug)]` and `#[repr(u8)], these + // would both be specified in a list as follows: + states_attr: #[derive(Debug)] #[repr(u8)], + events_attr: #[derive(Debug)] #[repr(u8)], transitions: { // * denotes the starting state diff --git a/examples/dominos.rs b/examples/dominos.rs index 271f0b7..c46c2ad 100644 --- a/examples/dominos.rs +++ b/examples/dominos.rs @@ -5,8 +5,8 @@ use smlang::statemachine; statemachine! { - derive_states: [Debug], - derive_events: [Debug], + states_attr: #[derive(Debug)], + events_attr: #[derive(Debug)], transitions: { *D0 + ToD1 / to_d2 = D1, D1(Option) + ToD2 / to_d3 = D2, diff --git a/examples/named_ex1.rs b/examples/named_ex1.rs index 5ba3c17..2779aa0 100644 --- a/examples/named_ex1.rs +++ b/examples/named_ex1.rs @@ -9,7 +9,7 @@ use smlang::statemachine; statemachine! { name: Linear, - derive_states: [Debug], + states_attr: #[derive(Debug)], transitions: { *State1 + Event1 = State2, State2 + Event2 = State3, diff --git a/examples/state_machine_logger.rs b/examples/state_machine_logger.rs index 17e2dfd..3eeb1c8 100644 --- a/examples/state_machine_logger.rs +++ b/examples/state_machine_logger.rs @@ -16,8 +16,8 @@ pub struct MyEventData(pub u32); pub struct MyStateData(pub u32); statemachine! { - derive_states: [Debug], - derive_events: [Debug], + states_attr: #[derive(Debug)], + events_attr: #[derive(Debug)], transitions: { *State1 + Event1(MyEventData) [guard1] / action1 = State2, State2(MyStateData) + Event2 [guard2] / action2 = State3, diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index 73ccf58..3e5981d 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -539,9 +539,6 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { quote! {#error_type_name} }; - let derive_states_list = &sm.derive_states; - let derive_events_list = &sm.derive_events; - let states_attr_list = &sm.states_attr; let events_attr_list = &sm.events_attr; // Build the states and events output @@ -578,7 +575,6 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { /// List of auto-generated states. #[allow(missing_docs)] - #[derive(#(#derive_states_list),*)] #(#states_attr_list)* pub enum #states_type_name <#state_lifetimes> { #(#state_list),* } @@ -592,7 +588,6 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { /// List of auto-generated events. #[allow(missing_docs)] - #[derive(#(#derive_events_list),*)] #(#events_attr_list)* pub enum #events_type_name <#event_lifetimes> { #(#event_list),* } diff --git a/macros/src/parser/mod.rs b/macros/src/parser/mod.rs index 1995b6d..18c69d4 100644 --- a/macros/src/parser/mod.rs +++ b/macros/src/parser/mod.rs @@ -46,8 +46,6 @@ impl fmt::Display for AsyncIdent { #[derive(Debug)] pub struct ParsedStateMachine { pub name: Option, - pub derive_states: Vec, - pub derive_events: Vec, pub states_attr: Vec, pub events_attr: Vec, pub temporary_context_type: Option, @@ -244,8 +242,6 @@ impl ParsedStateMachine { Ok(ParsedStateMachine { name: sm.name, - derive_states: sm.derive_states, - derive_events: sm.derive_events, states_attr: sm.states_attr, events_attr: sm.events_attr, temporary_context_type: sm.temporary_context_type, diff --git a/macros/src/parser/state_machine.rs b/macros/src/parser/state_machine.rs index a0ddb2d..5aa6edd 100644 --- a/macros/src/parser/state_machine.rs +++ b/macros/src/parser/state_machine.rs @@ -1,5 +1,5 @@ use super::transition::{StateTransition, StateTransitions}; -use syn::{braced, bracketed, parse, spanned::Spanned, token, Attribute, Ident, Token, Type}; +use syn::{braced, parse, spanned::Spanned, token, Attribute, Ident, Token, Type}; #[derive(Debug)] pub struct StateMachine { @@ -7,8 +7,6 @@ pub struct StateMachine { pub custom_error: bool, pub transitions: Vec, pub name: Option, - pub derive_states: Vec, - pub derive_events: Vec, pub states_attr: Vec, pub events_attr: Vec, } @@ -20,8 +18,6 @@ impl StateMachine { custom_error: false, transitions: Vec::new(), name: None, - derive_states: Vec::new(), - derive_events: Vec::new(), states_attr: Vec::new(), events_attr: Vec::new(), } @@ -110,38 +106,6 @@ impl parse::Parse for StateMachine { input.parse::()?; statemachine.name = Some(input.parse::()?); } - "derive_states" => { - input.parse::()?; - if input.peek(token::Bracket) { - let content; - bracketed!(content in input); - loop { - if content.is_empty() { - break; - }; - let trait_ = content.parse::()?; - statemachine.derive_states.push(trait_); - if content.parse::().is_err() { - break; - }; - } - } - } - "derive_events" => { - input.parse::()?; - let content; - bracketed!(content in input); - loop { - if content.is_empty() { - break; - }; - let trait_ = content.parse::()?; - statemachine.derive_events.push(trait_); - if content.parse::().is_err() { - break; - }; - } - } "states_attr" => { input.parse::()?; @@ -161,8 +125,8 @@ impl parse::Parse for StateMachine { \"transitions\", \ \"temporary_context\", \ \"custom_error\", \ - \"derive_states\", \ - \"derive_events\" + \"states_attr\", \ + \"events_attr\" ]", keyword ), diff --git a/src/lib.rs b/src/lib.rs index 0ff7635..e28fb9b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,8 +22,8 @@ //! //! statemachine! { //! name: Sample, -//! derive_states: [Debug], -//! derive_events: [Clone, Debug], +//! states_attr: #[derive(Debug)], +//! events_attr: #[derive(Clone, Debug)], //! transitions: { //! *Init + InitEvent [ guard_init ] / action_init = Ready, //! } diff --git a/tests/test.rs b/tests/test.rs index 5da7933..2149ead 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -121,8 +121,8 @@ fn multiple_lifetimes() { #[test] fn derive_display_events_states() { statemachine! { - derive_events: [Debug,Display], - derive_states: [Debug,Display], + events_attr: #[derive(Debug,Display)], + states_attr: #[derive(Debug,Display)], transitions: { *Init + Event = End, } @@ -145,8 +145,8 @@ fn derive_display_events_states() { fn named_derive_display_events_states() { statemachine! { name: SM, - derive_events: [Debug,Display], - derive_states: [Debug,Display], + events_attr: #[derive(Debug,Display)], + states_attr: #[derive(Debug,Display)], transitions: { *Init + Event = End, } @@ -205,7 +205,7 @@ fn guard_expressions() { pub struct Entry(pub u32); statemachine! { - derive_states: [Display, Debug], + states_attr: #[derive(Display, Debug)], transitions: { *Init + Login(&'a Entry) [valid_entry] / attempt = LoggedIn, Init + Login(&'a Entry) [!valid_entry && !too_many_attempts] / attempt = Init, @@ -380,7 +380,7 @@ fn test_internal_transition_with_data() { // State4(State3Data) + Event3 / action_3 = State4(State3Data), _ + Event3 / action_3 = _, }, - derive_states: [Debug, Clone, Copy, Eq ] + states_attr: #[derive(Debug, Clone, Copy, Eq)] } /// Context #[derive(Default, Debug, PartialEq, Eq)] @@ -463,7 +463,7 @@ fn test_wildcard_states_and_internal_transitions() { _ + Event1 / increment_count, // Internal transition (implicit: omitting target state) _ + Event3 / increment_count = _ , // Internal transition (explicit: using _ as target state) }, - derive_states: [Debug, Clone, Copy] + states_attr: #[derive(Debug, Clone, Copy)] } #[derive(Debug)] pub struct Context { @@ -496,8 +496,7 @@ fn test_specify_attrs() { *State1 + tostate2 = State2, State2 + tostate3 / increment_count = State3 }, - derive_states: [Debug, Clone, Copy, Serialize], - states_attr: #[non_exhaustive] #[repr(u8)] #[serde(tag="type")], + states_attr: #[derive(Debug, Clone, Copy, Serialize)] #[non_exhaustive] #[repr(u8)] #[serde(tag="type")], events_attr: #[derive(Debug)] #[allow(non_camel_case_types)] } From 398625137164e80b3d26ff97e4aa2b031e99c984 Mon Sep 17 00:00:00 2001 From: Prabhpreet Dua <615318+prabhpreet@users.noreply.github.com> Date: Mon, 19 Aug 2024 20:08:33 +0530 Subject: [PATCH 4/4] Fix clippy warning, add changelog --- CHANGELOG.md | 8 +++++++- macros/src/parser/state_machine.rs | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3bad72..14bba70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] -* None yet! +### Added + +- Add support for defining States and Events attributes using `states_attr` and `events_attr` fields + +### Changed + +- [breaking] Remove `derive_states` and `derive_events` fields in lieu of `states_attr` and `events_attr` to define attributes generically ## [v0.8.0] - 2024-08-07 diff --git a/macros/src/parser/state_machine.rs b/macros/src/parser/state_machine.rs index 5aa6edd..48a5f01 100644 --- a/macros/src/parser/state_machine.rs +++ b/macros/src/parser/state_machine.rs @@ -109,12 +109,12 @@ impl parse::Parse for StateMachine { "states_attr" => { input.parse::()?; - statemachine.states_attr = Attribute::parse_outer(&input)?; + statemachine.states_attr = Attribute::parse_outer(input)?; } "events_attr" => { input.parse::()?; - statemachine.events_attr = Attribute::parse_outer(&input)?; + statemachine.events_attr = Attribute::parse_outer(input)?; } keyword => {