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

Release/0.7.0 #69

Merged
merged 9 commits into from
Jul 3, 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
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]

## [0.7.0] - 2024-07-03

### Added

- Add support for async guards and actions
Expand Down Expand Up @@ -172,7 +174,8 @@ a long list of states to go through.
* Support for data in states
* Change log added

[Unreleased]: https://github.com/korken89/smlang-rs/compare/v0.6.0...master
[Unreleased]: https://github.com/korken89/smlang-rs/compare/v0.7.0...master
[v0.7.0]: https://github.com/korken89/smlang-rs/compare/v0.6.0...v0.7.0
[v0.6.0]: https://github.com/korken89/smlang-rs/compare/v0.5.1...v0.6.0
[v0.5.1]: https://github.com/korken89/smlang-rs/compare/v0.5.0...v0.5.1
[v0.5.0]: https://github.com/korken89/smlang-rs/compare/v0.4.2...v0.5.0
Expand Down
5 changes: 2 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@ description = "A no-std state machine language DSL"
keywords = ["dsl", "statemachine"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/korken89/smlang-rs"
version = "0.6.0"
version = "0.7.0"
edition = "2018"
readme = "README.md"

[dependencies]
smlang-macros = { path = "macros", version = "0.6.0" }
async-trait = "0.1"
smlang-macros = { path = "macros", version = "0.7.0" }

ryan-summers marked this conversation as resolved.
Show resolved Hide resolved
[dev-dependencies]
smol = "1"
Expand Down
66 changes: 55 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@

> A state machine language DSL based on the syntax of [Boost-SML](https://boost-ext.github.io/sml/).

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

The aim of this DSL is to facilitate the use of state machines, as they quite fast can become overly complicated to write and get an overview of.
The library supports both `async` and non-`async` code.

## Transition DSL

The DSL is defined as follows:
Below is a sample of the DSL. For a full description of the `statemachine` macro, please reference
the [DSL document](docs/dsl.md).

```rust
statemachine!{
Expand All @@ -23,7 +26,9 @@ statemachine!{
}
```

Where `guard` and `action` are optional and can be left out. A `guard` is a function which returns `true` if the state transition should happen, and `false` if the transition should not happen, while `action` are functions that are run during the transition which are guaranteed to finish before entering the new state.
Where `guard` and `action` are optional and can be left out. A `guard` is a function which returns
`Ok(true)` if the state transition should happen - otherwise, the transition should not happen.
The `action` functions are run during the state machine transition.

> This implies that any state machine must be written as a list of transitions.

Expand Down Expand Up @@ -89,7 +94,9 @@ in the order they appear in the state machine definition, will be selected.
### State machine context

The state machine needs a context to be defined.
The `StateMachineContext` is generated from the `statemachine!` proc-macro and is what implements guards and actions, and data that is available in all states within the state machine and persists between state transitions:
The `StateMachineContext` is generated from the `statemachine!` proc-macro and is what implements
guards and actions, and data that is available in all states within the state machine and persists
between state transitions:

```rust
statemachine!{
Expand All @@ -112,6 +119,7 @@ fn main() {

See example `examples/context.rs` for a usage example.


### State data

Any state may have some data associated with it:
Expand Down Expand Up @@ -202,12 +210,49 @@ See example `examples/guard_action_syntax.rs` for a usage-example.

### Async Guard and Action

Guards and actions may both be optionally `async`:
```rust
use smlang::{async_trait, statemachine};

statemachine! {
transitions: {
*State1 + Event1 [guard1] / async action1 = State2,
State2 + Event2 [async guard2] / action2 = State3,
}
}


pub struct Context {
// ...
}

impl StateMachineContext for Context {
async fn action1(&mut self) -> () {
// ...
}

async fn guard2(&mut self) -> Result<(), ()> {
// ...
}

fn guard1(&mut self) -> Result<(), ()> {
// ...
}

fn action2(&mut self) -> () {
// ...
}
}
```


See example `examples/async.rs` for a usage-example.

## State Machine Examples

Here are some examples of state machines converted from UML to the State Machine Language DSL. Runnable versions of each example is available in the `examples` folder.
The `.png`s are generated with the `graphviz` feature.
Here are some examples of state machines converted from UML to the State Machine Language DSL.
Runnable versions of each example is available in the `examples` folder. The `.png`s are generated
with the `graphviz` feature.

### Linear state machine

Expand Down Expand Up @@ -315,6 +360,7 @@ List of contributors in alphabetical order:

* Emil Fresk ([@korken89](https://github.com/korken89))
* Mathias Koch ([@MathiasKoch](https://github.com/MathiasKoch))
* Ryan Summers ([@ryan-summers](https://github.com/ryan-summers))
korken89 marked this conversation as resolved.
Show resolved Hide resolved
* Donny Zimmanck ([@dzimmanck](https://github.com/dzimmanck))

---
Expand All @@ -323,10 +369,8 @@ List of contributors in alphabetical order:

Licensed under either of

- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
http://www.apache.org/licenses/LICENSE-2.0)

- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
- Apache License, Version 2.0 [LICENSE-APACHE](LICENSE-APACHE) or <http://www.apache.org/licenses/LICENSE-2.0>
- MIT license [LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>

at your option.

57 changes: 57 additions & 0 deletions docs/dsl.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
## Transition DSL

The state machine macro DSL is defined as follows:

```rust
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_guard_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,
}
// ...
}
```
3 changes: 1 addition & 2 deletions examples/async.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

#![deny(missing_docs)]

use smlang::{async_trait, statemachine};
use smlang::statemachine;

statemachine! {
transitions: {
Expand All @@ -20,7 +20,6 @@ pub struct Context {
done: bool,
}

#[async_trait]
impl StateMachineContext for Context {
fn guard3(&self) -> Result<bool, ()> {
println!("`guard3` called from async context");
Expand Down
3 changes: 1 addition & 2 deletions examples/named_async.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

#![deny(missing_docs)]

use smlang::{async_trait, statemachine};
use smlang::statemachine;

statemachine! {
name: AsyncSimple,
Expand All @@ -21,7 +21,6 @@ pub struct Context {
done: bool,
}

#[async_trait]
impl AsyncSimpleStateMachineContext for Context {
fn guard1(&self) -> Result<bool, ()> {
println!("`guard1` called from sync context");
Expand Down
2 changes: 1 addition & 1 deletion macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ description = "Procedual macros for the smlang crate"
keywords = ["dsl", "statemachine"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/korken89/smlang-rs"
version = "0.6.0"
version = "0.7.0"
edition = "2018"
readme = "../README.md"

Expand Down
7 changes: 3 additions & 4 deletions macros/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -523,10 +523,10 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream {
quote! {}
};

let (is_async, is_async_trait) = if is_async_state_machine {
(quote! { async }, quote! { #[smlang::async_trait] })
let is_async = if is_async_state_machine {
quote! { async }
} else {
(quote! {}, quote! {})
quote! {}
};

let error_type = if sm.custom_guard_error {
Expand All @@ -543,7 +543,6 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream {
quote! {
/// This trait outlines the guards and actions that need to be implemented for the state
/// machine.
#is_async_trait
pub trait #state_machine_context_type_name {
#guard_error
#guard_list
Expand Down
90 changes: 52 additions & 38 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,61 +10,75 @@
//! **documentation of the project**, this comes from the procedural macro also generating
//! documentation.
//!
//! # DSL
#![doc = include_str!("../docs/dsl.md")]
//!
//! Please consult the README for the DSL specification.
//! # Example
//!
//! # Examples
//! 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.
//!
//! Please consult the README for examples.
//! ```rust
//! use smlang::statemachine;
//!
//! # Errors
//!
//! `StateMachine::process_event` will return `Ok(NextState)` if the transition was successful,
//! or `Err(Error::GuardFailed)` if the guard failed, or `Err(Error::InvalidEvent)` if an event
//! which should not come at this stage of the state machine was processed.
//!
//! # Panics
//!
//! There are no `panic!` in this library.
//!
//! # Unsafe
//!
//! There is no use of `unsafe` in this library.
//!
//! # Auto-generated types
//!
//! ```ignore
//! // Auto generated enum of states
//! enum States { ... }
//! statemachine! {
//! name: Sample,
//! derive_states: [Debug],
//! derive_events: [Clone, Debug],
//! transitions: {
//! *Init + InitEvent [ guard_init ] / action_init = Ready,
//! }
//! }
//! ```
//!
//! Results in the following code:
//! ```ignore
//! // Auto generated enum of possible events
//! enum Events { ... }
//! ```
//! #[derive(Debug)]
//! enum SampleStates {
//! Init,
//! Ready,
//! }
//!
//! ```ignore
//! // Auto generated struct which holds the state machine implementation
//! struct StateMachine { ... }
//! ```
//! #[derive(Clone, Debug)]
//! enum SampleEvents {
//! InitEvent,
//! }
//!
//! # State machine generated API
//! struct SampleStateMachine<C: SampleStateMachineContext> {
//! // ...
//! }
//!
//! ```ignore
//! struct StateMachine {
//! enum SampleError {
//! InvalidEvent,
//! GuardFailed,
//! // ...
//! }
//!
//! impl<C: SampleStateMachineContext> SampleStateMachine<C> {
//! /// Creates a state machine with the starting state
//! pub fn new() -> Self;
//! pub fn new() -> Self { /**/ }
//!
//! /// Returns the current state
//! pub fn state(&self) -> States;
//! pub fn state(&self) -> States { /**/ }
//!
//! /// Process an event
//! pub fn process_event(&mut self, event: Events) -> Result<States, Error>;
//! ///
//! /// # 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> {
//! # Err(SampleError::InvalidEvent);
//! /**/
//! }
//! }
//!
//! 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);
//! }
//! ```

#![no_std]

pub use async_trait::async_trait;
pub use smlang_macros::statemachine;
Loading