项目作者: VKlayd

项目描述 :
FSM in Rust's macros.
高级语言: Rust
项目地址: git://github.com/VKlayd/rust_fsm_macros.git
创建时间: 2017-09-12T07:37:14Z
项目社区:https://github.com/VKlayd/rust_fsm_macros

开源协议:MIT License

下载


Finite State Machine generator in Rust’s macro

Overview

With this macro you can easily implement Finite State Machine in declarative way.

State machine consists of:

  • Name
  • Initial state
  • List of states
  • List of commands
  • List of state nodes

Each state node contains:

  • State
  • Context (optional)
  • List of command reactions

Each command reaction contains:

  • Command to react on
  • User-defined code of reaction (optional)
  • Next state of machine (optional)

Working example to begin with

Let’s say we’d like to implement such machine:

FSM example

Corresponding code will look like:

  1. #[macro_use] extern crate macro_machine;
  2. declare_machine!(
  3. MyMachine(A {counter: 0}) // Name and initial state with initial value
  4. states[A,B] // List of states
  5. commands[Next] // List of commands
  6. (A context{counter: i16}: // State node and this state context description with name binding
  7. >> { // Executed on state A enter
  8. println!("Enter A: {:?}", context);
  9. context.counter = context.counter + 1;
  10. }
  11. << { // Executed on state A leave
  12. println!("Leave A: {:?}", context);
  13. context.counter = context.counter + 1;
  14. }
  15. Next {
  16. println!("Next in A: {:?}", context);
  17. context.counter = context.counter + 1;
  18. } => B {counter: context.counter}; // Command Reaction. Now on command Next we add 1 to our context. Also we change state to B and init it with our counter value.
  19. )
  20. (B context{counter: i16}:
  21. >> {
  22. println!("Enter B: {:?}", context);
  23. context.counter = context.counter + 1;
  24. }
  25. << {
  26. println!("Leave B: {:?}", context);
  27. context.counter = context.counter + 1;
  28. }
  29. Next {
  30. println!("Next in B: {:?}", context);
  31. context.counter = context.counter + 1;
  32. } => A {counter: context.counter};
  33. )
  34. );
  35. fn main() {
  36. use MyMachine::*;
  37. let mut machine = MyMachine::new();
  38. machine.execute(&MyMachine::Commands::Next).unwrap();
  39. machine.execute(&MyMachine::Commands::Next).unwrap();
  40. }

Longer explanation

Simplest state machine example:

  1. #[macro_use] extern crate macro_machine;
  2. declare_machine!(
  3. Simple(A) // Name and initial State
  4. states[A,B] // list of States
  5. commands[Next] // list of Commands
  6. (A: // State Node
  7. Next => B; // Command Reaction. Just change state to B
  8. )
  9. (B:
  10. Next => A; // And back to A
  11. )
  12. );

So, now you can use state machine:

  1. fn main() {
  2. use Simple::*;
  3. let mut machine = Simple::new();
  4. machine.execute(&Simple::Commands::Next).unwrap();
  5. machine.execute(&Simple::Commands::Next).unwrap();
  6. }

You can add some intelligence to machine.

Each state can hold some data. On State change you can transmit some data between states.
It looks like you just create struct with some fields initialization:

  1. #[macro_use] extern crate macro_machine;
  2. declare_machine!(
  3. Simple(A{counter:0}) // Name and initial State with initial value
  4. states[A,B] // list of States
  5. commands[Next] // list of Commands
  6. (A context{counter:i16}: // State Node and this state context description with binding name
  7. Next {context.counter=context.counter+1}=> B{counter:context.counter}; // Command Reaction. Now on command Next we add 1 to our context. Also we change state to B and init it with our x value.
  8. )
  9. (B context{counter:i16}:
  10. Next {context.counter=context.counter+1}=> A{counter:context.counter};
  11. )
  12. );

Let’s check our state transmission:

  1. fn main() {
  2. use Simple::*;
  3. let mut machine = Simple::new();
  4. // We are in state A and have our initial value 0
  5. assert!(match machine.get_current_state(){
  6. States::A{context}=> if context.counter == 0 {true} else {false},
  7. _=>false
  8. });
  9. machine.execute(&Simple::Commands::Next).unwrap();
  10. // We are in state B and have counter == 1
  11. assert!(match machine.get_current_state(){
  12. States::B{context}=> if context.counter == 1 {true} else {false},
  13. _=>false
  14. });
  15. machine.execute(&Simple::Commands::Next).unwrap();
  16. // Again in state A and have counter == 2
  17. assert!(match machine.get_current_state(){
  18. States::A{context}=> if context.counter == 2 {true} else {false},
  19. _=>false
  20. });
  21. }

Also there is callbacks on each entrance and each leave of state.

  1. #[macro_use] extern crate macro_machine;
  2. declare_machine!(
  3. Simple(A{counter:0}) // Name and initial State with initial value
  4. states[A,B] // list of States
  5. commands[Next] // list of Commands
  6. (A context{counter:i16}: // State Node and this state context description with binding name
  7. >> {context.counter = context.counter+1;} // Execute when enter state A
  8. << {context.counter = context.counter+1;} // Execute when leave state A
  9. Next {context.counter=context.counter+1;} => B{counter:context.counter}; // Command Reaction. Now on command Next we add 1 to our context. Also we change state to B and init it with our x value.
  10. )
  11. (B context{counter:i16}:
  12. Next {context.counter=context.counter+1} => A{counter:context.counter};
  13. )
  14. );
  15. fn main() {
  16. use Simple::*;
  17. let mut machine = Simple::new();
  18. assert!(match machine.get_current_state(){
  19. // We are in state A and have value 1. Because Enter State callback executed.
  20. States::A{context}=> if context.counter == 1 {true} else {false},
  21. _=>false
  22. });
  23. machine.execute(&Simple::Commands::Next).unwrap();
  24. assert!(match machine.get_current_state(){
  25. // We are in state B and have counter == 3. Increment happen on User Code execution and execution of Leave state callback.
  26. States::B{context}=> {println!("context counter: {}", context.counter);if context.counter == 3 {true} else {false}},
  27. _=>false
  28. });
  29. machine.execute(&Simple::Commands::Next).unwrap();
  30. assert!(match machine.get_current_state(){
  31. // Again in state A and have counter == 5. Increment happen on User Code execution and on state A enter.
  32. States::A{context}=> if context.counter == 5 {true} else {false},
  33. _=>false
  34. });
  35. }

Example of Machine-scoped context. This context exist in machine life-time.

Let’s count machine’s state changes:

  1. #[macro_use] extern crate macro_machine;
  2. declare_machine!(
  3. Simple machine_context{counter: i16} (A) // Declare machine scoped context
  4. states[A,B]
  5. commands[Next]
  6. (A :
  7. >> {machine_context.counter=machine_context.counter+1;} // Add 1 when enter in state
  8. Next => B; // Just switch to other state
  9. )
  10. (B :
  11. >> {machine_context.counter=machine_context.counter+1;}
  12. Next => A;
  13. )
  14. );
  15. fn main() {
  16. use Simple::*;
  17. let mut machine = Simple::new(0); // Create machine and initiate machine context by 0
  18. let context = machine.get_inner_context();
  19. assert!(context.counter == 1);
  20. machine.execute(&Simple::Commands::Next).unwrap();
  21. let context = machine.get_inner_context();
  22. assert!(context.counter == 2);
  23. machine.execute(&Simple::Commands::Next).unwrap();
  24. let context = machine.get_inner_context();
  25. assert!(context.counter == 3);
  26. }

Changelog

0.2.0

  • Changed behavior of Leave action. Now it execute before new State context creation.
  • Add machine-scoped context. It can be used by all callbacks inside machine. Data in this context have machine’s life-time.