diff --git a/finny/src/fsm/fsm_factory.rs b/finny/src/fsm/fsm_factory.rs index df889bb..4382926 100644 --- a/finny/src/fsm/fsm_factory.rs +++ b/finny/src/fsm/fsm_factory.rs @@ -1,4 +1,4 @@ -use crate::{FsmBackend, FsmBackendImpl, FsmEventQueue, FsmFrontend, FsmResult, FsmTimers, FsmTimersNull, Inspect, InspectNull}; +use crate::{FsmBackend, FsmBackendImpl, FsmEventQueue, FsmFrontend, FsmResult, FsmTimers, FsmTimersNull, Inspect}; #[cfg(feature="std")] use crate::{FsmEventQueueVec, timers::std::TimersStd}; @@ -26,7 +26,9 @@ pub trait FsmFactory { /// Build a new frontend for the FSM with a `FsmEventQueueVec` queue, `TimersStd` for timers and no logging. #[cfg(feature="std")] - fn new(context: ::Context) -> FsmResult, InspectNull, TimersStd>> { + fn new(context: ::Context) -> FsmResult, crate::inspect::null::InspectNull, TimersStd>> { + use crate::inspect::null::InspectNull; + let frontend = FsmFrontend { queue: FsmEventQueueVec::new(), backend: FsmBackendImpl::new(context)?, diff --git a/finny/src/fsm/fsm_impl.rs b/finny/src/fsm/fsm_impl.rs index 272190b..e89830f 100644 --- a/finny/src/fsm/fsm_impl.rs +++ b/finny/src/fsm/fsm_impl.rs @@ -55,6 +55,10 @@ impl DerefMut for FsmBackendImpl { } } +pub trait FsmBackendResetSubmachine { + fn reset(backend: &mut FsmBackendImpl, inspect_event_ctx: &mut I) where I: Inspect; +} + /// The frontend of a state machine which also includes environmental services like queues /// and inspection. The usual way to use the FSM. diff --git a/finny/src/fsm/inspect/mod.rs b/finny/src/fsm/inspect/mod.rs new file mode 100644 index 0000000..308b2b7 --- /dev/null +++ b/finny/src/fsm/inspect/mod.rs @@ -0,0 +1,32 @@ +use core::fmt::Debug; +use core::any::Any; + +use crate::{FsmBackend, FsmBackendImpl, FsmEvent, FsmStates}; + +#[derive(Debug, Clone)] +pub enum InspectFsmEvent where S: Debug + Clone { + StateEnter(S), + StateExit(S) +} + +pub trait Inspect: InspectEvent { + + fn new_event(&self, event: &FsmEvent<::Events, ::Timers>, fsm: &FsmBackendImpl) -> Self; + fn event_done(self, fsm: &FsmBackendImpl); + + fn for_transition(&self) -> Self; + fn for_sub_machine(&self) -> Self; + fn for_timer(&self, timer_id: ::Timers) -> Self where F: FsmBackend; + + fn on_guard(&self, guard_result: bool); + fn on_state_enter(&self); + fn on_state_exit(&self); + fn on_action(&self); + + fn on_error(&self, msg: &str, error: &E) where E: core::fmt::Debug; + fn info(&self, msg: &str); +} + +pub trait InspectEvent { + fn on_event(&self, event: &InspectFsmEvent); +} \ No newline at end of file diff --git a/finny/src/fsm/mod.rs b/finny/src/fsm/mod.rs index 0583137..25468df 100644 --- a/finny/src/fsm/mod.rs +++ b/finny/src/fsm/mod.rs @@ -8,9 +8,9 @@ mod queue; mod states; mod transitions; mod tests_fsm; -mod inspect; mod dispatch; mod timers; +mod inspect; pub use self::events::*; pub use self::fsm_factory::*; @@ -39,7 +39,7 @@ pub type FsmDispatchResult = FsmResult<()>; /// Finite State Machine backend. Handles the dispatching, the types are /// defined by the code generator. -pub trait FsmBackend where Self: Sized { +pub trait FsmBackend where Self: Sized + Debug { /// The machine's context that is shared between its constructors and actions. type Context; /// The type that holds the states of the machine. diff --git a/finny/src/fsm/queue.rs b/finny/src/fsm/queue.rs index 145593a..32f39f0 100644 --- a/finny/src/fsm/queue.rs +++ b/finny/src/fsm/queue.rs @@ -53,6 +53,74 @@ mod queue_vec { #[cfg(feature = "std")] pub use self::queue_vec::*; +#[cfg(feature = "std")] +mod queue_vec_shared { + use std::sync::{Arc, Mutex}; + + use crate::FsmError; + + use super::*; + + /// An unbound event queue that uses `VecDeque`. + pub struct FsmEventQueueVecShared { + inner: Inner + } + + impl Clone for FsmEventQueueVecShared where F: FsmBackend { + fn clone(&self) -> Self { + Self { inner: Inner { queue: self.inner.queue.clone() } } + } + } + + struct Inner { + queue: Arc::Events>>> + } + + impl FsmEventQueueVecShared { + pub fn new() -> Self { + let q = VecDeque::new(); + let inner = Inner { + queue: Arc::new(Mutex::new(q)) + }; + FsmEventQueueVecShared { + inner + } + } + } + + impl FsmEventQueue for FsmEventQueueVecShared { + fn dequeue(&mut self) -> Option<::Events> { + if let Ok(mut q) = self.inner.queue.lock() { + q.pop_front() + } else { + None + } + } + + fn len(&self) -> usize { + if let Ok(q) = self.inner.queue.lock() { + q.len() + } else { + 0 + } + } + } + + impl FsmEventQueueSender for FsmEventQueueVecShared { + fn enqueue::Events>>(&mut self, event: E) -> FsmResult<()> { + if let Ok(mut q) = self.inner.queue.lock() { + q.push_back(event.into()); + Ok(()) + } else { + Err(FsmError::QueueOverCapacity) + } + } + } +} + +#[cfg(feature = "std")] +pub use self::queue_vec_shared::*; + mod queue_array { use arraydeque::{Array, ArrayDeque}; @@ -169,6 +237,7 @@ impl<'a, Q, F, FSub> FsmEventQueueSender for FsmEventQueueSub<'a, Q, F, FS } + #[cfg(test)] use super::tests_fsm::TestFsm; @@ -185,6 +254,12 @@ fn test_array() { test_queue(queue); } +#[test] +fn test_dequeue_vec_shared() { + let queue = FsmEventQueueVecShared::::new(); + test_queue(queue); +} + #[cfg(test)] fn test_queue>(mut queue: Q) { use super::tests_fsm::{Events, EventA}; diff --git a/finny/src/fsm/states.rs b/finny/src/fsm/states.rs index c2f8ea0..79121cf 100644 --- a/finny/src/fsm/states.rs +++ b/finny/src/fsm/states.rs @@ -5,9 +5,9 @@ use crate::FsmResult; /// The implementation should hold all of the FSM's states as fields. pub trait FsmStates: FsmStateFactory where TFsm: FsmBackend { /// The enum type for all states that's used as the "current state" field in the FSM's backend. - type StateKind: Clone + Copy + Debug + PartialEq; + type StateKind: Clone + Copy + Debug + PartialEq + 'static; /// An array of current states for the machine, one for each region. - type CurrentState: Clone + Copy + Debug + Default + AsRef<[FsmCurrentState]> + AsMut<[FsmCurrentState]>; + type CurrentState: Clone + Copy + Debug + Default + AsRef<[FsmCurrentState]> + AsMut<[FsmCurrentState]> + 'static; } /// The current state of the FSM. @@ -49,6 +49,7 @@ pub trait FsmStateFactory where Self: Sized, TFsm: FsmBackend { fn new_state(context: &::Context) -> FsmResult; } +/// The implementation of a simple state factory, where the state supports Default. impl FsmStateFactory for TState where TState: Default, TFsm: FsmBackend { fn new_state(_context: &::Context) -> FsmResult { Ok(Default::default()) diff --git a/finny/src/fsm/tests_fsm.rs b/finny/src/fsm/tests_fsm.rs index 9150eb4..70b166e 100644 --- a/finny/src/fsm/tests_fsm.rs +++ b/finny/src/fsm/tests_fsm.rs @@ -8,6 +8,7 @@ pub struct StateA; #[derive(Copy, Clone, Debug, PartialEq)] pub struct EventA { pub n: usize } +#[derive(Debug)] pub struct TestFsm; #[derive(Default)] diff --git a/finny/src/fsm/transitions.rs b/finny/src/fsm/transitions.rs index c8df0e1..4ab6a43 100644 --- a/finny/src/fsm/transitions.rs +++ b/finny/src/fsm/transitions.rs @@ -3,8 +3,10 @@ use crate::{FsmBackendImpl, FsmDispatchResult, FsmEventQueueSub, FsmTimers, FsmTimersSub, lib::*}; use crate::{DispatchContext, EventContext, FsmBackend, FsmCurrentState, FsmEvent, FsmEventQueue, FsmRegionId, FsmStateTransitionAsMut, FsmStates, Inspect}; +use super::inspect::InspectFsmEvent; + /// A state's entry and exit actions. -pub trait FsmState { +pub trait FsmState where Self: Sized { /// Action that is executed whenever this state is being entered. fn on_entry<'a, Q: FsmEventQueue>(&mut self, context: &mut EventContext<'a, F, Q>); /// Action that is executed whenever this state is being exited. @@ -19,6 +21,15 @@ pub trait FsmState { queue: context.queue }; + // inspection + { + context.inspect.on_state_enter::(); + + let kind = ::fsm_state(); + let ev = InspectFsmEvent::StateEnter(kind); + context.inspect.on_event(&ev); + } + let state: &mut Self = context.backend.states.as_mut(); state.on_entry(&mut event_context); } @@ -34,6 +45,15 @@ pub trait FsmState { let state: &mut Self = context.backend.states.as_mut(); state.on_exit(&mut event_context); + + // inspection + { + context.inspect.on_state_exit::(); + + let kind = ::fsm_state(); + let ev = InspectFsmEvent::StateExit(kind); + context.inspect.on_event(&ev); + } } fn fsm_state() -> <::States as FsmStates>::StateKind; diff --git a/finny/src/inspect/chain.rs b/finny/src/inspect/chain.rs new file mode 100644 index 0000000..42bb446 --- /dev/null +++ b/finny/src/inspect/chain.rs @@ -0,0 +1,118 @@ +use crate::{FsmBackend, FsmBackendImpl, FsmEvent, Inspect, InspectEvent, InspectFsmEvent}; +use core::any::Any; +use core::fmt::Debug; +use super::{null::InspectNull}; + + +pub struct InspectChain +where A: Inspect, B: Inspect +{ +pub a: A, +pub b: B +} + +impl InspectChain +where A: Inspect, B: Inspect +{ +pub fn new_pair(inspect_a: A, inspect_b: B) -> Self { + InspectChain { + a: inspect_a, + b: inspect_b + } +} + +pub fn add_inspect(self, inspect: C) -> InspectChain, C> { + InspectChain { + a: self, + b: inspect + } +} +} + +impl InspectChain +where A: Inspect +{ +pub fn new_chain(inspect: A) -> Self { + InspectChain { + a: inspect, + b: InspectNull::new() + } +} +} + + +impl Inspect for InspectChain + where A: Inspect, B: Inspect +{ + fn new_event(&self, event: &FsmEvent<::Events, ::Timers>, fsm: &FsmBackendImpl) -> Self { + Self { + a: self.a.new_event(event, fsm), + b: self.b.new_event(event, fsm) + } + } + + fn event_done(self, fsm: &FsmBackendImpl) { + self.a.event_done(fsm); + self.b.event_done(fsm); + } + + fn for_transition(&self) -> Self { + Self { + a: self.a.for_transition::(), + b: self.b.for_transition::() + } + } + + fn for_sub_machine(&self) -> Self { + Self { + a: self.a.for_sub_machine::(), + b: self.b.for_sub_machine::() + } + } + + fn for_timer(&self, timer_id: ::Timers) -> Self where F: FsmBackend { + Self { + a: self.a.for_timer::(timer_id.clone()), + b: self.b.for_timer::(timer_id) + } + } + + fn on_guard(&self, guard_result: bool) { + self.a.on_guard::(guard_result); + self.b.on_guard::(guard_result); + } + + fn on_state_enter(&self) { + self.a.on_state_enter::(); + self.b.on_state_enter::(); + } + + fn on_state_exit(&self) { + self.a.on_state_exit::(); + self.b.on_state_exit::(); + } + + fn on_action(&self) { + self.a.on_action::(); + self.b.on_action::(); + } + + fn on_error(&self, msg: &str, error: &E) where E: core::fmt::Debug { + self.a.on_error(msg, error); + self.b.on_error(msg, error); + } + + fn info(&self, msg: &str) { + self.a.info(msg); + self.b.info(msg); + } +} + +impl InspectEvent for InspectChain + where A: Inspect, B: Inspect +{ + fn on_event(&self, event: &InspectFsmEvent) { + self.a.on_event(event); + self.b.on_event(event); + } +} \ No newline at end of file diff --git a/finny/src/inspect/events.rs b/finny/src/inspect/events.rs new file mode 100644 index 0000000..0b8c783 --- /dev/null +++ b/finny/src/inspect/events.rs @@ -0,0 +1,76 @@ +use crate::{FsmBackend, FsmBackendImpl, FsmEvent, Inspect, InspectEvent, InspectFsmEvent}; +use core::any::Any; +use core::fmt::Debug; + +#[derive(Clone)] +pub struct EventInspector + where T: InspectEvent + Clone +{ + event_handler: T +} + +impl EventInspector + where T: InspectEvent + Clone +{ + pub fn new(inspect: T) -> Self { + Self { + event_handler: inspect + } + } +} + +impl Inspect for EventInspector + where TI: InspectEvent + Clone +{ + fn new_event(&self, _event: &FsmEvent<::Events, ::Timers>, _fsm: &FsmBackendImpl) -> Self { + self.clone() + } + + fn for_transition(&self) -> Self { + self.clone() + } + + fn for_sub_machine(&self) -> Self { + self.clone() + } + + fn for_timer(&self, _timer_id: ::Timers) -> Self where F: FsmBackend { + self.clone() + } + + fn on_guard(&self, _guard_result: bool) { + + } + + fn on_state_enter(&self) { + + } + + fn on_state_exit(&self) { + + } + + fn on_action(&self) { + + } + + fn event_done(self, fsm: &FsmBackendImpl) { + + } + + fn on_error(&self, msg: &str, error: &E) where E: core::fmt::Debug { + + } + + fn info(&self, msg: &str) { + + } +} + +impl InspectEvent for EventInspector + where TI: InspectEvent + Clone +{ + fn on_event(&self, event: &InspectFsmEvent) { + self.event_handler.on_event(event) + } +} \ No newline at end of file diff --git a/finny/src/inspect/mod.rs b/finny/src/inspect/mod.rs index f1e5914..d7893aa 100644 --- a/finny/src/inspect/mod.rs +++ b/finny/src/inspect/mod.rs @@ -1,2 +1,7 @@ +pub mod null; +pub mod chain; +pub mod events; + + #[cfg(feature="inspect_slog")] pub mod slog; \ No newline at end of file diff --git a/finny/src/fsm/inspect.rs b/finny/src/inspect/null.rs similarity index 55% rename from finny/src/fsm/inspect.rs rename to finny/src/inspect/null.rs index 37ac64c..04c3b3b 100644 --- a/finny/src/fsm/inspect.rs +++ b/finny/src/inspect/null.rs @@ -1,22 +1,6 @@ -use crate::{FsmBackendImpl, lib::*}; -use crate::{FsmBackend, FsmEvent}; -pub trait Inspect { - - fn new_event(&self, event: &FsmEvent<::Events, ::Timers>, fsm: &FsmBackendImpl) -> Self; - fn event_done(self, fsm: &FsmBackendImpl); - - fn for_transition(&self) -> Self; - fn for_sub_machine(&self) -> Self; - fn for_timer(&self, timer_id: ::Timers) -> Self where F: FsmBackend; - - fn on_guard(&self, guard_result: bool); - fn on_state_enter(&self); - fn on_state_exit(&self); - fn on_action(&self); - - fn on_error(&self, msg: &str, error: &E) where E: Debug; - fn info(&self, msg: &str); -} +use crate::{FsmBackend, FsmBackendImpl, FsmEvent, Inspect, InspectEvent, InspectFsmEvent}; +use core::fmt::Debug; +use core::any::Any; #[derive(Default)] pub struct InspectNull; @@ -64,11 +48,17 @@ impl Inspect for InspectNull { } - fn on_error(&self, msg: &str, error: &E) where E: Debug { + fn on_error(&self, msg: &str, error: &E) where E: core::fmt::Debug { } fn info(&self, msg: &str) { + } +} + +impl InspectEvent for InspectNull { + fn on_event(&self, event: &InspectFsmEvent) { + } } \ No newline at end of file diff --git a/finny/src/inspect/slog.rs b/finny/src/inspect/slog.rs index beae764..cc04cba 100644 --- a/finny/src/inspect/slog.rs +++ b/finny/src/inspect/slog.rs @@ -1,7 +1,9 @@ use slog::{info, o, error}; -use crate::{FsmBackend, FsmBackendImpl, FsmEvent, Inspect}; +use crate::{FsmBackend, FsmBackendImpl, FsmEvent, Inspect, InspectEvent, InspectFsmEvent}; use crate::lib::*; use AsRef; +use core::fmt::Debug; +use core::any::Any; pub struct InspectSlog { pub logger: slog::Logger @@ -90,4 +92,11 @@ impl Inspect for InspectSlog fn info(&self, msg: &str) { info!(self.logger, "{}", msg); } +} + +impl InspectEvent for InspectSlog +{ + fn on_event(&self, event: &InspectFsmEvent) { + info!(self.logger, "Inspection event {:?}", event); + } } \ No newline at end of file diff --git a/finny_derive/src/codegen.rs b/finny_derive/src/codegen.rs index 360b8fa..f943cc0 100644 --- a/finny_derive/src/codegen.rs +++ b/finny_derive/src/codegen.rs @@ -2,7 +2,7 @@ use std::collections::HashSet; use proc_macro2::{Span, TokenStream}; use quote::{TokenStreamExt, quote}; -use crate::{codegen_meta::generate_fsm_meta, fsm::FsmTypes, parse::{FsmState, FsmStateAction, FsmStateKind}, utils::{remap_closure_inputs, to_field_name}}; +use crate::{codegen_meta::generate_fsm_meta, fsm::FsmTypes, parse::{FsmState, FsmStateAction, FsmStateKind}, utils::{remap_closure_inputs, to_field_name, tokens_to_string}}; use crate::{parse::{FsmFnInput, FsmStateTransition, FsmTransitionState, FsmTransitionType}, utils::ty_append}; @@ -30,9 +30,10 @@ pub fn generate_fsm_code(fsm: &FsmFnInput, _attr: TokenStream, _input: TokenStre let mut state_variants = TokenStream::new(); let mut state_accessors = TokenStream::new(); + for (i, (_, state)) in fsm.fsm.states.iter().enumerate() { let name = &state.state_storage_field; - let state_ty = FsmTypes::new(&state.ty,&fsm.base.fsm_generics); + let state_ty = FsmTypes::new(&state.ty, &fsm.base.fsm_generics); let ty = state_ty.get_fsm_ty(); let ty_name = state_ty.get_fsm_no_generics_ty(); @@ -157,6 +158,7 @@ pub fn generate_fsm_code(fsm: &FsmFnInput, _attr: TokenStream, _input: TokenStre } quote! { + /// States storage struct for the state machine. pub struct #states_store_ty #fsm_generics_type #fsm_generics_where { #code_fields _fsm: core::marker::PhantomData< #fsm_ty #fsm_generics_type > @@ -202,13 +204,16 @@ pub fn generate_fsm_code(fsm: &FsmFnInput, _attr: TokenStream, _input: TokenStre let mut variants = TokenStream::new(); let mut as_ref_str = TokenStream::new(); + let mut i = 0; for (ty, _ev) in fsm.fsm.events.iter() { let ty_str = crate::utils::tokens_to_string(ty); variants.append_all(quote! { #ty ( #ty ), }); as_ref_str.append_all(quote! { #event_enum_ty:: #ty(_) => #ty_str, }); + i += 1; } + for (_sub, state) in submachines { let sub_fsm = FsmTypes::new(&state.ty, &fsm.base.fsm_generics); let sub_fsm_event_ty = sub_fsm.get_fsm_events_ty(); @@ -222,6 +227,7 @@ pub fn generate_fsm_code(fsm: &FsmFnInput, _attr: TokenStream, _input: TokenStre as_ref_str.append_all(quote! { #event_enum_ty :: #sub_fsm_ty(_) => #sub_fsm_event_ty_str , }); + i += 1; } let mut derives = TokenStream::new(); @@ -230,6 +236,21 @@ pub fn generate_fsm_code(fsm: &FsmFnInput, _attr: TokenStream, _input: TokenStre #[derive(Debug)] }); } + + let as_ref_str = match i { + 0 => { + quote! { + stringify!(#event_enum_ty) + } + }, + _ => { + quote! { + match self { + #as_ref_str + } + } + } + }; let evs = quote! { #[derive(finny::bundled::derive_more::From)] @@ -241,9 +262,7 @@ pub fn generate_fsm_code(fsm: &FsmFnInput, _attr: TokenStream, _input: TokenStre impl core::convert::AsRef for #event_enum_ty { fn as_ref(&self) -> &'static str { - match self { - #as_ref_str - } + #as_ref_str } } }; @@ -258,9 +277,10 @@ pub fn generate_fsm_code(fsm: &FsmFnInput, _attr: TokenStream, _input: TokenStre for transition in ®ion.transitions { let ty = &transition.transition_ty; - let mut q = quote! { - pub struct #ty; - }; + + let mut transition_doc = String::new(); + + let mut q = TokenStream::new(); match &transition.ty { // internal or self transtion (only the current state) @@ -271,6 +291,12 @@ pub fn generate_fsm_code(fsm: &FsmFnInput, _attr: TokenStream, _input: TokenStre let is_self_transition = if let FsmTransitionType::SelfTransition(_) = &transition.ty { true } else { false }; + transition_doc.push_str(&format!(" {} transition within state [{}], responds to the event [{}].", + if is_self_transition { "A self" } else {"An internal"}, + tokens_to_string(&state.ty), + tokens_to_string(event_ty) + )); + if let Some(ref guard) = s.action.guard { let remap = remap_closure_inputs(&guard.inputs, vec![ quote! { event }, quote! { context }, quote! { states } @@ -278,6 +304,8 @@ pub fn generate_fsm_code(fsm: &FsmFnInput, _attr: TokenStream, _input: TokenStre let body = &guard.body; + transition_doc.push_str(" Guarded."); + let g = quote! { impl #fsm_generics_impl finny::FsmTransitionGuard<#fsm_ty #fsm_generics_type, #event_ty> for #ty #fsm_generics_where { fn guard<'fsm_event, Q>(event: & #event_ty, context: &finny::EventContext<'fsm_event, #fsm_ty #fsm_generics_type, Q>, states: & #states_store_ty #fsm_generics_type ) -> bool @@ -298,6 +326,8 @@ pub fn generate_fsm_code(fsm: &FsmFnInput, _attr: TokenStream, _input: TokenStre quote! { event }, quote! { context }, quote! { state } ].as_slice())?; + transition_doc.push_str(" Executes an action."); + let body = &action.body; quote! { @@ -328,6 +358,8 @@ pub fn generate_fsm_code(fsm: &FsmFnInput, _attr: TokenStream, _input: TokenStre FsmTransitionType::StateTransition(s @ FsmStateTransition { state_from: FsmTransitionState::None, .. }) => { let initial_state_ty = &s.state_to.get_fsm_state()?.ty; + transition_doc.push_str(" Start transition."); + q.append_all(quote! { impl #fsm_generics_impl finny::FsmTransitionFsmStart<#fsm_ty #fsm_generics_type, #initial_state_ty > for #ty #fsm_generics_where { @@ -339,9 +371,21 @@ pub fn generate_fsm_code(fsm: &FsmFnInput, _attr: TokenStream, _input: TokenStre // normal state transition FsmTransitionType::StateTransition(s) => { + let event_ty = &s.event.get_event()?.ty; + let state_from = s.state_from.get_fsm_state()?; + let state_to = s.state_to.get_fsm_state()?; + + transition_doc.push_str(&format!(" Transition, from state [{}] to state [{}] upon the event [{}].", + tokens_to_string(&state_from.ty), + tokens_to_string(&state_to.ty), + tokens_to_string(&event_ty) + )); + if let Some(ref guard) = s.action.guard { let event_ty = &s.event.get_event()?.ty; + transition_doc.push_str(" Guarded."); + let remap = remap_closure_inputs(&guard.inputs, vec![ quote! { event }, quote! { context }, quote! { states } ].as_slice())?; @@ -364,6 +408,8 @@ pub fn generate_fsm_code(fsm: &FsmFnInput, _attr: TokenStream, _input: TokenStre } let action_body = if let Some(ref action) = s.action.action { + transition_doc.push_str(" Executes an action."); + let remap = remap_closure_inputs(&action.inputs, vec![ quote! { event }, quote! { context }, quote! { from }, quote! { to } ].as_slice())?; @@ -377,10 +423,6 @@ pub fn generate_fsm_code(fsm: &FsmFnInput, _attr: TokenStream, _input: TokenStre } else { TokenStream::new() }; - - let event_ty = &s.event.get_event()?.ty; - let state_from = s.state_from.get_fsm_state()?; - let state_to = s.state_to.get_fsm_state()?; let state_from_ty = &state_from.ty; let state_to_ty = &state_to.ty; @@ -398,6 +440,13 @@ pub fn generate_fsm_code(fsm: &FsmFnInput, _attr: TokenStream, _input: TokenStre q.append_all(a); } } + + transition_doc.push_str(&format!(" Part of [{}].", tokens_to_string(fsm_ty))); + + q.append_all(quote! { + #[doc = #transition_doc ] + pub struct #ty; + }); t.append_all(q); } @@ -473,7 +522,16 @@ pub fn generate_fsm_code(fsm: &FsmFnInput, _attr: TokenStream, _input: TokenStre let fsm_sub_entry = match &transition.ty { FsmTransitionType::StateTransition(FsmStateTransition {state_to: FsmTransitionState::State(s @ FsmState { kind: FsmStateKind::SubMachine(_), .. }), .. }) => { + + let sub_ty = &s.ty; + quote! { + + // reset + { + use finny::FsmBackendResetSubmachine; + >::reset(ctx.backend, &mut inspect_event_ctx); + } { <#transition_ty>::execute_on_sub_entry(&mut ctx, #region_id, &mut inspect_event_ctx); } @@ -681,6 +739,14 @@ pub fn generate_fsm_code(fsm: &FsmFnInput, _attr: TokenStream, _input: TokenStre result } } + + impl #fsm_generics_impl core::fmt::Debug for #fsm_ty #fsm_generics_type + #fsm_generics_where + { + fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error > { + Ok(()) + } + } } }; @@ -739,6 +805,7 @@ pub fn generate_fsm_code(fsm: &FsmFnInput, _attr: TokenStream, _input: TokenStre quote! { + /// A Finny Finite State Machine. pub struct #fsm_ty #fsm_generics_type #fsm_generics_where { backend: finny::FsmBackendImpl<#fsm_ty #fsm_generics_type > } @@ -812,8 +879,12 @@ pub fn generate_fsm_code(fsm: &FsmFnInput, _attr: TokenStream, _input: TokenStre let trigger = remap_closure_inputs(&timer.trigger.inputs, &[quote! { ctx }, quote! { state }])?; let trigger_body = &timer.trigger.body; + let timer_doc = format!("A timer in the state [{}] of FSM [{}].", tokens_to_string(state_ty), tokens_to_string(fsm_ty)); + code.append_all(quote! { + #[doc = #timer_doc ] + pub struct #timer_ty #fsm_generics_type #fsm_generics_where { instance: Option > } @@ -1045,6 +1116,47 @@ pub fn generate_fsm_code(fsm: &FsmFnInput, _attr: TokenStream, _input: TokenStre code }; + // submachine restart + + let sub_restart = { + + let subs: Vec<_> = fsm.fsm.states.iter().filter_map(|(ty, state)| match state.kind { + FsmStateKind::SubMachine(ref sub) => Some((ty, state, sub.clone())), + _ => None + }).collect(); + + + if subs.len() == 0 { + TokenStream::new() + } else { + + let mut q = TokenStream::new(); + + for (sub_ty, state, sub) in subs { + + q.append_all(quote! { + + impl #fsm_generics_impl finny::FsmBackendResetSubmachine< #fsm_ty #fsm_generics_type , #sub_ty > for #fsm_ty #fsm_generics_type + #fsm_generics_where + { + + fn reset(backend: &mut finny::FsmBackendImpl< #fsm_ty #fsm_generics_type >, inspect_event_ctx: &mut I) + where I: finny::Inspect + { + let sub_fsm: &mut #sub_ty = backend.states.as_mut(); + sub_fsm.backend.current_states = Default::default(); + inspect_event_ctx.info("Setting the state of the submachine to Start."); + } + } + + }); + + } + + q + } + }; + let fsm_meta = generate_fsm_meta(&fsm); let mut q = quote! { @@ -1062,6 +1174,8 @@ pub fn generate_fsm_code(fsm: &FsmFnInput, _attr: TokenStream, _input: TokenStre #timers + #sub_restart + #fsm_meta }; diff --git a/finny_derive/src/codegen_meta.rs b/finny_derive/src/codegen_meta.rs index 1106e65..bd05ca8 100644 --- a/finny_derive/src/codegen_meta.rs +++ b/finny_derive/src/codegen_meta.rs @@ -2,21 +2,23 @@ use std::collections::HashMap; use proc_macro2::TokenStream; -use crate::{ - meta::{ +use crate::{meta::{ FinnyEvent, FinnyFsm, FinnyRegion, FinnyState, FinnyStateKind, FinnyTimer, FinnyTransition, FinnyTransitionKind, FinnyTransitionNormal, - }, - parse::{FsmFnInput, FsmTransitionState}, - utils::tokens_to_string, -}; + }, parse::{FsmFnInput, FsmState, FsmStateKind, FsmTransitionState}, utils::{strip_generics, tokens_to_string}}; use quote::quote; +fn ty_to_string(ty: &syn::Type) -> String { + let ty = ty.clone(); + let ty = strip_generics(ty); + tokens_to_string(&ty) +} + fn to_info_state(s: &FsmTransitionState, fsm: &FsmFnInput) -> FinnyStateKind { match s { FsmTransitionState::None => FinnyStateKind::Stopped, - FsmTransitionState::State(s) => FinnyStateKind::State(FinnyState { - state_id: tokens_to_string(&s.ty), + FsmTransitionState::State(s @ FsmState { kind: FsmStateKind::Normal, .. }) => FinnyStateKind::State(FinnyState { + state_id: ty_to_string(&s.ty), timers: s .timers .iter() @@ -25,6 +27,7 @@ fn to_info_state(s: &FsmTransitionState, fsm: &FsmFnInput) -> FinnyStateKind { }) .collect(), }), + FsmTransitionState::State(s @ FsmState { kind: FsmStateKind::SubMachine(_), .. }) => FinnyStateKind::SubMachine(ty_to_string(&s.ty)) } } @@ -119,33 +122,68 @@ pub fn generate_fsm_meta(fsm: &FsmFnInput) -> TokenStream { //let json = serde_json::to_string_pretty(&info).expect("Failed to serialize the FSM info JSON!"); let fsm_ty = &fsm.base.fsm_ty; + let fsm_ty_name = tokens_to_string(&strip_generics(fsm_ty.clone())); let fsm_info_ty = &fsm.base.fsm_info_ty; - let fsm_ty_name_str = crate::utils::to_snake_case(&tokens_to_string(&fsm_ty)); + let fsm_ty_name_snake = crate::utils::to_snake_case(&tokens_to_string(&fsm_ty)); let (fsm_generics_impl, fsm_generics_type, fsm_generics_where) = fsm.base.fsm_generics.split_for_impl(); - let mut plant_uml_test_build = TokenStream::new(); - - #[cfg(feature="generate_plantuml")] - { - let plant_uml = crate::meta::plantuml::to_plant_uml(&info); - - let test_fn_name = crate::utils::to_field_name(&crate::utils::ty_append(&fsm_ty, "PlantUML")); - - plant_uml_test_build = quote! { - #[test] - #[cfg(test)] - fn #test_fn_name () { - use std::io::prelude::*; - use std::fs; - - let contents = #plant_uml; - - let mut f = fs::File::create(&format!("{}.plantuml", #fsm_ty_name_str )).unwrap(); - f.write_all(contents.as_bytes()).unwrap(); + let plant_uml_test_build = { + #[cfg(not(feature="generate_plantuml"))] + { TokenStream::new() } + #[cfg(feature="generate_plantuml")] + { + let (plant_uml_str, additional) = crate::meta::plantuml::to_plant_uml(&info).expect("PlantUML syntax generation error!"); + + let test_fn_name = crate::utils::to_field_name(&crate::utils::ty_append(&fsm_ty, "_plantuml")); + + quote! { + #[test] + #[cfg(test)] + fn #test_fn_name () { + use std::io::prelude::*; + use std::fs; + + let contents = < #fsm_info_ty > :: plantuml(); + + let mut f = fs::File::create(&format!("{}.plantuml", #fsm_ty_name_snake )).unwrap(); + f.write_all(contents.as_bytes()).unwrap(); + } + + #[derive(Default)] + pub struct #fsm_info_ty; + + impl #fsm_info_ty { + pub fn plantuml_inner() -> String { + use std::fmt::Write; + + let mut output = ( #plant_uml_str ).to_string(); + + #additional + + output + } + + pub fn plantuml() -> String { + use std::fmt::Write; + + let mut output = String::new(); + + writeln!(&mut output, "@startuml {}", #fsm_ty_name ); + + writeln!(&mut output, "{}", Self::plantuml_inner()); + + writeln!(&mut output, "@enduml"); + + output + } + } } - }; - } + } + }; + + + diff --git a/finny_derive/src/meta/mod.rs b/finny_derive/src/meta/mod.rs index cc552b5..b04f75c 100644 --- a/finny_derive/src/meta/mod.rs +++ b/finny_derive/src/meta/mod.rs @@ -25,19 +25,19 @@ pub struct FinnyRegion { pub enum FinnyStateKind { Stopped, State(FinnyState), - //SubMachine(String) + SubMachine(String) } impl FinnyStateKind { pub fn get_state_id(&self) -> String { match self { FinnyStateKind::Stopped => "Stopped".into(), - FinnyStateKind::State(s) => s.state_id.clone() + FinnyStateKind::State(s) => s.state_id.clone(), + FinnyStateKind::SubMachine(id) => id.clone() } } } - #[derive(Serialize, Deserialize, Clone, Debug)] pub struct FinnyState { pub state_id: String, diff --git a/finny_derive/src/meta/plantuml.rs b/finny_derive/src/meta/plantuml.rs index 63a0c83..aed2f66 100644 --- a/finny_derive/src/meta/plantuml.rs +++ b/finny_derive/src/meta/plantuml.rs @@ -1,11 +1,14 @@ +use proc_macro2:: TokenStream; +use quote::{quote, TokenStreamExt}; +use syn::{PathSegment, TypePath, parse::Parse}; + use super::FinnyFsm; use std::fmt::Write; -pub fn to_plant_uml(fsm: &FinnyFsm) -> String { +pub fn to_plant_uml(fsm: &FinnyFsm) -> Result<(String, TokenStream), std::fmt::Error> { let mut output = String::new(); - - output.push_str("@startuml\n"); + let mut subs = TokenStream::new(); for region in fsm.regions.values() { for state in region.states.values() { @@ -15,10 +18,24 @@ pub fn to_plant_uml(fsm: &FinnyFsm) -> String { } super::FinnyStateKind::State(state) => { - writeln!(&mut output, "state {} {{", state.state_id); + writeln!(&mut output, "state {} {{", state.state_id)?; + writeln!(&mut output, "}}")?; + + for timer in &state.timers { + writeln!(&mut output, "state {} : Timer {}", state.state_id, timer.timer_id)?; + } + }, + super::FinnyStateKind::SubMachine(sub_id) => { + let p = syn::parse_str::(&format!("{}Info", sub_id)).unwrap(); + + + subs.append_all(quote! { + writeln!(&mut output, "state {} {{", #sub_id); + writeln!(&mut output, "{}", < #p > :: plantuml_inner() ); + writeln!(&mut output, "}}"); + }); - writeln!(&mut output, "}}"); } } } @@ -33,10 +50,12 @@ pub fn to_plant_uml(fsm: &FinnyFsm) -> String { match &transition.transition { super::FinnyTransitionKind::SelfTransition { state_id } => { - writeln!(&mut output, "{state} --> {state} : {event} (Self)", state = state_id, event = event); + writeln!(&mut output, "{state} --> {state} : {event} (Self)", state = state_id, event = event)?; + writeln!(&mut output, "note on link: {}", transition.transition_id)?; } super::FinnyTransitionKind::InternalTransition { state_id } => { - writeln!(&mut output, "{state} --> {state} : {event} (Internal)", state = state_id, event = event); + writeln!(&mut output, "{state} --> {state} : {event} (Internal)", state = state_id, event = event)?; + writeln!(&mut output, "note on link: {}", transition.transition_id)?; } super::FinnyTransitionKind::NormalTransition(t) => { let state_from = match t.from_state.as_str() { @@ -44,13 +63,12 @@ pub fn to_plant_uml(fsm: &FinnyFsm) -> String { _ => &t.from_state }; - writeln!(&mut output, "{state_from} --> {state_to} : {event}", state_from = state_from, state_to = t.to_state, event = event); + writeln!(&mut output, "{state_from} --> {state_to} : {event}", state_from = state_from, state_to = t.to_state, event = event)?; + writeln!(&mut output, "note on link: {}", transition.transition_id)?; } } } } - output.push_str("@enduml\n"); - - output + Ok((output, subs)) } \ No newline at end of file diff --git a/finny_nostd_tests/src/main.rs b/finny_nostd_tests/src/main.rs index 04591ec..10f5b72 100644 --- a/finny_nostd_tests/src/main.rs +++ b/finny_nostd_tests/src/main.rs @@ -1,7 +1,7 @@ #![no_std] #![no_main] -use finny::{finny_fsm, FsmFactory, FsmEventQueueArray, InspectNull, FsmTimersNull}; +use finny::{finny_fsm, FsmFactory, FsmEventQueueArray, inspect::null::InspectNull, FsmTimersNull}; use finny::decl::{FsmBuilder, BuiltFsm}; use heapless::consts::*; diff --git a/finny_tests/tests/fsm_minimal.rs b/finny_tests/tests/fsm_minimal.rs new file mode 100644 index 0000000..517774d --- /dev/null +++ b/finny_tests/tests/fsm_minimal.rs @@ -0,0 +1,13 @@ +use finny::{FsmFactory, FsmResult, decl::{BuiltFsm, FsmBuilder}, finny_fsm}; + +#[derive(Default, Debug)] +struct State { + +} + +#[finny_fsm] +fn build_fsm(mut fsm: FsmBuilder) -> BuiltFsm { + fsm.initial_state::(); + fsm.state::(); + fsm.build() +}