Crate smlang

Source
Expand description

§smlang

smlang is a procedural macro library creating a state machine language DSL is to facilitate the use of state machines, as they quite fast can become overly complicated to write and get an overview of.

§Project dependent documentation

When this crate is used in a project the documentation will be auto generated in the documentation of the project, this comes from the procedural macro also generating documentation.

§Transition DSL

The state machine macro DSL is defined as follows:

use smlang::statemachine;

statemachine!{
    // [Optional] An optional prefix to name the generated state machine trait code. This
    // can be used to allow multiple state machines to exist in the same source
    // file. The generated trait and types are `<name>States`, `<name>Events`,
    // and `<name>StateMachine` respectively.
    name: Name,

    // [Optional] Can be used if a temporary context is needed within the state machine
    // API. When specified, the temporary context is provided in
    // `StateMachine::process_event()` and is exposed in guards and actions as
    // the second argument.
    temporary_context: u32,

    // [Optional] Can be optionally specified to add a new `type Error` to the
    // generated `StateMachineContext` trait to allow guards to return a custom
    // 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: [],

    transitions: {
        // * denotes the starting state
        *StartState + Event1 [ guard1] / action1 = DstState1,

        // Guards and actions can be async functions.
        SrcState2 + Event2 [ async guard2 ] / async action2 = DstState2,

        // Pattern matching can be used to support multiple states with the same
        // transition event.
        StartState | SrcState2 + Event3 [ guard3] / action3 = DstState3,

        // ..or wildcarding can be used to allow all states to share a
        // transition event.
        _ + Event4 = DstState4,

        // States can contain data
        StateWithData(u32) + Event = DstState5,
        StateWithOtherData(&'a u32) + Event = DstState5,

        // Guards can be logically combined using `!`, `||`, and `&&`.
        SrcState6 + Event6 [ async guard6 || other_guard6 ] / action6 = DstState6,
        SrcState7 + Event7 [ async guard7 && !other_guard7 ] / action7 = DstState7,
    }
    // ...
}

§Example

Below is an example of the state machine macro in use along with the code that would be generated for this sample to demonstrate how this library is used.

use smlang::statemachine;

statemachine! {
    name: Sample,
    derive_states: [Debug],
    derive_events: [Clone, Debug],
    transitions: {
        *Init + InitEvent [ guard_init ] / action_init = Ready,
    }
}

Results in the following code:

#[derive(Debug)]
enum SampleStates {
    Init,
    Ready,
}

#[derive(Clone, Debug)]
enum SampleEvents {
    InitEvent,
}

struct SampleStateMachine<C: SampleStateMachineContext> {
    // ...
}

enum SampleError {
    InvalidEvent,
    GuardFailed,
    // ...
}

impl<C: SampleStateMachineContext> SampleStateMachine<C> {
    /// Creates a state machine with the starting state
    pub fn new() -> Self { /**/ }

    /// Returns the current state
    pub fn state(&self) -> States { /**/ }

    /// Process an event
    ///
    /// # Returns
    /// `Ok(NextState)` if the transition was successful or `Err()` if the transition failed.
    /// guard failed
    pub fn process_event(&mut self, event: Events) -> Result<SampleStates, SampleError> {
    /**/
    }
}

trait SampleStateMachineContext {
    // Called to guard the transition to `Ready`. Returns an `Err` if the guard fails.
    fn guard_init(&mut self) -> Result<(), ()>;

    // Called when transitioning to `Ready`.
    fn action_init(&mut self);
}

Macros§

statemachine