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/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/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 807b675..3e5981d 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -539,8 +539,8 @@ 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 quote! { /// This trait outlines the guards and actions that need to be implemented for the state @@ -575,7 +575,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 @@ -588,7 +588,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..18c69d4 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>; @@ -46,8 +46,8 @@ 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, pub custom_error: bool, pub states: HashMap, @@ -242,8 +242,8 @@ 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, custom_error: sm.custom_error, states, diff --git a/macros/src/parser/state_machine.rs b/macros/src/parser/state_machine.rs index 2b011d0..48a5f01 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, parse, spanned::Spanned, token, Attribute, Ident, Token, Type}; #[derive(Debug)] pub struct StateMachine { @@ -7,8 +7,8 @@ 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, } impl StateMachine { @@ -18,8 +18,8 @@ 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(), } } @@ -106,38 +106,17 @@ impl parse::Parse for StateMachine { input.parse::()?; statemachine.name = Some(input.parse::()?); } - "derive_states" => { + + "states_attr" => { 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; - }; - } - } + statemachine.states_attr = Attribute::parse_outer(input)?; } - "derive_events" => { + + "events_attr" => { 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; - }; - } + statemachine.events_attr = Attribute::parse_outer(input)?; } + keyword => { return Err(parse::Error::new( input.span(), @@ -146,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 4025e3e..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 { @@ -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)] + use serde::Serialize; + statemachine! { + transitions: { + *State1 + tostate2 = State2, + State2 + tostate3 / increment_count = State3 + }, + states_attr: #[derive(Debug, Clone, Copy, Serialize)] #[non_exhaustive] #[repr(u8)] #[serde(tag="type")], + 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); +}