Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add custom attributes for States and Events enum #88

Merged
merged 4 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -370,18 +370,18 @@ 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
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,
}
Expand Down Expand Up @@ -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

Expand Down
10 changes: 5 additions & 5 deletions docs/dsl.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions examples/dominos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Events>) + ToD2 / to_d3 = D2,
Expand Down
2 changes: 1 addition & 1 deletion examples/named_ex1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use smlang::statemachine;

statemachine! {
name: Linear,
derive_states: [Debug],
states_attr: #[derive(Debug)],
transitions: {
*State1 + Event1 = State2,
State2 + Event2 = State3,
Expand Down
4 changes: 2 additions & 2 deletions examples/state_machine_logger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 4 additions & 4 deletions macros/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
10 changes: 5 additions & 5 deletions macros/src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, HashMap<String, EventMapping>>;

Expand Down Expand Up @@ -46,8 +46,8 @@ impl fmt::Display for AsyncIdent {
#[derive(Debug)]
pub struct ParsedStateMachine {
pub name: Option<Ident>,
pub derive_states: Vec<Ident>,
pub derive_events: Vec<Ident>,
pub states_attr: Vec<Attribute>,
pub events_attr: Vec<Attribute>,
pub temporary_context_type: Option<Type>,
pub custom_error: bool,
pub states: HashMap<String, Ident>,
Expand Down Expand Up @@ -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,
Expand Down
49 changes: 14 additions & 35 deletions macros/src/parser/state_machine.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
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 {
pub temporary_context_type: Option<Type>,
pub custom_error: bool,
pub transitions: Vec<StateTransition>,
pub name: Option<Ident>,
pub derive_states: Vec<Ident>,
pub derive_events: Vec<Ident>,
pub states_attr: Vec<Attribute>,
pub events_attr: Vec<Attribute>,
}

impl StateMachine {
Expand All @@ -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(),
}
}

Expand Down Expand Up @@ -106,38 +106,17 @@ impl parse::Parse for StateMachine {
input.parse::<Token![:]>()?;
statemachine.name = Some(input.parse::<Ident>()?);
}
"derive_states" => {

"states_attr" => {
input.parse::<Token![:]>()?;
if input.peek(token::Bracket) {
let content;
bracketed!(content in input);
loop {
if content.is_empty() {
break;
};
let trait_ = content.parse::<Ident>()?;
statemachine.derive_states.push(trait_);
if content.parse::<Token![,]>().is_err() {
break;
};
}
}
statemachine.states_attr = Attribute::parse_outer(input)?;
}
"derive_events" => {

"events_attr" => {
input.parse::<Token![:]>()?;
let content;
bracketed!(content in input);
loop {
if content.is_empty() {
break;
};
let trait_ = content.parse::<Ident>()?;
statemachine.derive_events.push(trait_);
if content.parse::<Token![,]>().is_err() {
break;
};
}
statemachine.events_attr = Attribute::parse_outer(input)?;
}

keyword => {
return Err(parse::Error::new(
input.span(),
Expand All @@ -146,8 +125,8 @@ impl parse::Parse for StateMachine {
\"transitions\", \
\"temporary_context\", \
\"custom_error\", \
\"derive_states\", \
\"derive_events\"
\"states_attr\", \
\"events_attr\"
]",
keyword
),
Expand Down
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
//! }
Expand Down
44 changes: 37 additions & 7 deletions tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand All @@ -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,
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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 {
Expand All @@ -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);
}