Skip to content

Commit

Permalink
confine all generated code inside a module
Browse files Browse the repository at this point in the history
  • Loading branch information
eugene-babichenko committed May 12, 2024
1 parent a7b42b5 commit 890ff8b
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 144 deletions.
Empty file removed .github/workflows/publish.yml
Empty file.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ The format is based on [Keep a Changelog][keepachangelog], and this project
adheres to [Semantic Versioning][semver].

## [Unreleased]
### Changed
* All types generated by the `state_machine` macro are now confined in a module.
The name of the module is passed to the macro as the name of the state
machine. All generated types now have uniforms names: `Impl`, `Input`,
`State`, `Output`.
* If no outputs is specified in the `state_machine` macro, an empty `Output`
enum is generated instead of using `()` for the sake of uniformity. No
attributes (e.g. `derive` and `repr`) are applied on an empty `Output`,
because many of them are simply not designed to work this way.
## Added
* A type alias `StateMachine` for `rust_fsm::StateMachine<Impl>` is now
generated inside the said module.

## [0.6.2] - 2024-05-11
### Changed
Expand Down
77 changes: 27 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ The `rust-fsm` crate provides a simple and universal framework for building
state machines in Rust with minimum effort.

The essential part of this crate is the `StateMachineImpl` trait. This trait
allows a developer to provide a strict state machine definition, e.g.
specify its:
allows a developer to provide a strict state machine definition, e.g. specify
its:

* An input alphabet - a set of entities that the state machine takes as
inputs and performs state transitions based on them.
Expand All @@ -30,7 +30,7 @@ of state machines:
* A Moore machine by providing an output function that do not depend on the
provided inputs.

## Features
## Usage in `no_std` environments

This library has the feature named `std` which is enabled by default. You
may want to import this library as
Expand All @@ -46,7 +46,7 @@ also enabled by default.
Initially this library was designed to build an easy to use DSL for defining
state machines on top of it. Using the DSL will require to connect an
additional crate `rust-fsm-dsl` (this is due to limitation of the procedural
macros system).
macros system).

### Using the DSL for defining state machines

Expand All @@ -57,24 +57,21 @@ use rust_fsm::*;

state_machine! {
derive(Debug)
repr_c(true)
CircuitBreaker(Closed)
circuit_breaker(Closed)

Closed(Unsuccessful) => Open [SetupTimer],
Open(TimerTriggered) => HalfOpen,
HalfOpen => {
Successful => Closed,
Unsuccessful => Open [SetupTimer],
Unsuccessful => Open [SetupTimer]
}
}
```

This code sample:

* Defines a state machine called `CircuitBreaker`;
* Defines a state machine called `circuit_breaker`;
* Derives the `Debug` trait for it (the `derive` section is optional);
* Adds repr(C) support to generated code for better FFI compatability
(the `repr_c` section is optional and defaults to false);
* Sets the initial state of this state machine to `Closed`;
* Defines state transitions. For example: on receiving the `Successful`
input when in the `HalfOpen` state, the machine must move to the `Closed`
Expand All @@ -84,56 +81,40 @@ This code sample:

This state machine can be used as follows:

```rust
```rust,ignore
// Initialize the state machine. The state is `Closed` now.
let mut machine: StateMachine<CircuitBreaker> = StateMachine::new();
let mut machine = circuit_breaker::StateMachine::new();
// Consume the `Successful` input. No state transition is performed.
let _ = machine.consume(&CircuitBreakerInput::Successful);
let _ = machine.consume(&circuit_breaker::Input::Successful);
// Consume the `Unsuccesful` input. The machine is moved to the `Open`
// state. The output is `SetupTimer`.
let output = machine.consume(&CircuitBreakerInput::Unsuccessful).unwrap();
let output = machine.consume(&circuit_breaker::Input::Unsuccessful).unwrap();
// Check the output
if let Some(CircuitBreakerOutput::SetupTimer) = output {
if let Some(circuit_breaker::Output::SetupTimer) = output {
// Set up the timer...
}
// Check the state
if let CircuitBreakerState::Open = machine.state() {
if let circuit_breaker::State::Open = machine.state() {
// Do something...
}
```

As you can see, the following entities are generated:

* An empty structure `CircuitBreaker` that implements the `StateMachineImpl`
trait.
* Enums `CircuitBreakerState`, `CircuitBreakerInput` and
`CircuitBreakerOutput` that represent the state, the input alphabet and
the output alphabet respectively.

Note that if there is no outputs in the specification, the output alphabet
is set to `()`. The set of states and the input alphabet must be non-empty
sets.

#### Visibility
The following entities are generated:

You can specify visibility like this:

```rust
state_machine! {
pub CircuitBreaker(Closed)
* An empty structure `circuit_breaker::Impl` that implements the
`StateMachineImpl` trait.
* Enums `circuit_breaker::State`, `circuit_breaker::Input` and
`circuit_breaker::Output` that represent the state, the input alphabet and the
output alphabet respectively.
* Type alias `circuit_breaker::StateMachine` that expands to
`StateMachine<circuit_breaker::Impl>`.

Closed(Unsuccessful) => Open [SetupTimer],
Open(TimerTriggered) => HalfOpen,
HalfOpen => {
Successful => Closed,
Unsuccessful => Open [SetupTimer],
}
}
```
Note that if there is no outputs in the specification, the output alphabet is an
empty enum and due to technical limitations of many Rust attributes, no
attributes (e.g. `derive`, `repr`) are applied to it.

Note that the default visibility is private just like for any structure. The
specified visibility will apply to all structures and enums generated by the
macro.
Within the `state_machine` macro you must define at least one state
transition.

### Without DSL

Expand All @@ -148,8 +129,4 @@ wrappers (for now there is only `StateMachine`).
You can see an example of the Circuit Breaker state machine in the
[project repository][repo].

[repo]: https://github.com/eugene-babichenko/rust-fsm/blob/master/rust-fsm/tests/circuit_breaker.rs
[docs-badge]: https://docs.rs/rust-fsm/badge.svg
[docs-link]: https://docs.rs/rust-fsm
[crate-badge]: https://img.shields.io/crates/v/rust-fsm.svg
[crate-link]: https://crates.io/crates/rust-fsm
[repo]: https://github.com/eugene-babichenko/rust-fsm/blob/master/tests/circuit_breaker.rs
120 changes: 57 additions & 63 deletions rust-fsm-dsl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pub fn state_machine(tokens: TokenStream) -> TokenStream {
return output.into();
}

let struct_name = input.name;
let fsm_name = input.name;
let visibility = input.visibility;

let transitions: Vec<_> = input
Expand Down Expand Up @@ -76,94 +76,88 @@ pub fn state_machine(tokens: TokenStream) -> TokenStream {
}
}

let states_enum_name = Ident::new(&format!("{}State", struct_name), struct_name.span());
let initial_state_name = &input.initial_state;

let inputs_enum_name = Ident::new(&format!("{}Input", struct_name), struct_name.span());

let mut transition_cases = vec![];
for transition in transitions.iter() {
let initial_state = &transition.initial_state;
let input_value = &transition.input_value;
let final_state = &transition.final_state;
transition_cases.push(quote! {
(#states_enum_name::#initial_state, #inputs_enum_name::#input_value) => {
Some(#states_enum_name::#final_state)
(State::#initial_state, Input::#input_value) => {
Some(State::#final_state)
}
});
}

let (outputs_repr, outputs_type, output_impl) = if !outputs.is_empty() {
let outputs_type_name = Ident::new(&format!("{}Output", struct_name), struct_name.span());
let outputs_repr = quote! {
#derives
#type_repr
#visibility enum #outputs_type_name {
#(#outputs),*
}
};

let outputs_type = quote! { #outputs_type_name };

let mut output_cases = vec![];
for transition in transitions.iter() {
if let Some(output_value) = &transition.output {
let initial_state = &transition.initial_state;
let input_value = &transition.input_value;
output_cases.push(quote! {
(#states_enum_name::#initial_state, #inputs_enum_name::#input_value) => {
Some(#outputs_type_name::#output_value)
}
});
}
let mut output_cases = vec![];
for transition in transitions.iter() {
if let Some(output_value) = &transition.output {
let initial_state = &transition.initial_state;
let input_value = &transition.input_value;
output_cases.push(quote! {
(State::#initial_state, Input::#input_value) => {
Some(Output::#output_value)
}
});
}
}

let output_impl = quote! {
match (state, input) {
#(#output_cases)*
_ => None,
}
};
let attrs = quote! {
#derives
#type_repr
};

(outputs_repr, outputs_type, output_impl)
// Many attrs and derives may work incorrectly (or simply not work) for
// empty enums, so we just skip them altogether if the output alphabet is
// empty.
let output_attrs = if outputs.is_empty() {
quote!()
} else {
(quote! {}, quote! { () }, quote! {None})
attrs.clone()
};

let output = quote! {
#derives
#type_repr
#visibility struct #struct_name;
#visibility mod #fsm_name {
#attrs
pub struct Impl;

#derives
#type_repr
#visibility enum #states_enum_name {
#(#states),*
}
pub type StateMachine = rust_fsm::StateMachine<Impl>;

#derives
#type_repr
#visibility enum #inputs_enum_name {
#(#inputs),*
}
#attrs
pub enum Input {
#(#inputs),*
}

#outputs_repr
#attrs
pub enum State {
#(#states),*
}

#output_attrs
pub enum Output {
#(#outputs),*
}

impl rust_fsm::StateMachineImpl for #struct_name {
type Input = #inputs_enum_name;
type State = #states_enum_name;
type Output = #outputs_type;
const INITIAL_STATE: Self::State = #states_enum_name::#initial_state_name;
impl rust_fsm::StateMachineImpl for Impl {
type Input = Input;
type State = State;
type Output = Output;
const INITIAL_STATE: Self::State = State::#initial_state_name;

fn transition(state: &Self::State, input: &Self::Input) -> Option<Self::State> {
match (state, input) {
#(#transition_cases)*
_ => None,
fn transition(state: &Self::State, input: &Self::Input) -> Option<Self::State> {
match (state, input) {
#(#transition_cases)*
_ => None,
}
}
}

fn output(state: &Self::State, input: &Self::Input) -> Option<Self::Output> {
#output_impl
fn output(state: &Self::State, input: &Self::Input) -> Option<Self::Output> {
match (state, input) {
#(#output_cases)*
_ => None,
}
}
}
}
};
Expand Down
35 changes: 20 additions & 15 deletions rust-fsm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,12 @@
//!
//! The DSL is parsed by the `state_machine` macro. Here is a little example.
//!
//! ```rust,ignore
//! ```rust
//! use rust_fsm::*;
//!
//! state_machine! {
//! derive(Debug)
//! CircuitBreaker(Closed)
//! circuit_breaker(Closed)
//!
//! Closed(Unsuccessful) => Open [SetupTimer],
//! Open(TimerTriggered) => HalfOpen,
Expand All @@ -67,7 +67,7 @@
//!
//! This code sample:
//!
//! * Defines a state machine called `CircuitBreaker`;
//! * Defines a state machine called `circuit_breaker`;
//! * Derives the `Debug` trait for it (the `derive` section is optional);
//! * Sets the initial state of this state machine to `Closed`;
//! * Defines state transitions. For example: on receiving the `Successful`
Expand All @@ -80,33 +80,38 @@
//!
//! ```rust,ignore
//! // Initialize the state machine. The state is `Closed` now.
//! let mut machine: StateMachine<CircuitBreaker> = StateMachine::new();
//! let mut machine = circuit_breaker::StateMachine::new();
//! // Consume the `Successful` input. No state transition is performed.
//! let _ = machine.consume(&CircuitBreakerInput::Successful);
//! let _ = machine.consume(&circuit_breaker::Input::Successful);
//! // Consume the `Unsuccesful` input. The machine is moved to the `Open`
//! // state. The output is `SetupTimer`.
//! let output = machine.consume(&CircuitBreakerInput::Unsuccessful).unwrap();
//! let output = machine.consume(&circuit_breaker::Input::Unsuccessful).unwrap();
//! // Check the output
//! if let Some(CircuitBreakerOutput::SetupTimer) = output {
//! if let Some(circuit_breaker::Output::SetupTimer) = output {
//! // Set up the timer...
//! }
//! // Check the state
//! if let CircuitBreakerState::Open = machine.state() {
//! if let circuit_breaker::State::Open = machine.state() {
//! // Do something...
//! }
//! ```
//!
//! As you can see, the following entities are generated:
//! The following entities are generated:
//!
//! * An empty structure `CircuitBreaker` that implements the `StateMachineImpl`
//! trait.
//! * Enums `CircuitBreakerState`, `CircuitBreakerInput` and
//! `CircuitBreakerOutput` that represent the state, the input alphabet and
//! * An empty structure `circuit_breaker::Impl` that implements the
//! `StateMachineImpl` trait.
//! * Enums `circuit_breaker::State`, `circuit_breaker::Input` and
//! `circuit_breaker::Output` that represent the state, the input alphabet and
//! the output alphabet respectively.
//! * Type alias `circuit_breaker::StateMachine` that expands to
//! `StateMachine<circuit_breaker::Impl>`.
//!
//! Note that if there is no outputs in the specification, the output alphabet
//! is set to `()`. The set of states and the input alphabet must be non-empty
//! sets.
//! is an empty enum and due to technical limitations of many Rust attributes,
//! no attributes (e.g. `derive`, `repr`) are applied to it.
//!
//! Within the `state_machine` macro you must define at least one state
//! transition.
//!
//! ## Without DSL
//!
Expand Down
Loading

0 comments on commit 890ff8b

Please sign in to comment.