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:
Corresponding code will look like:
#[macro_use] extern crate macro_machine;
declare_machine!(
MyMachine(A {counter: 0}) states[A,B] commands[Next] (A context{counter: i16}: >> { println!("Enter A: {:?}", context);
context.counter = context.counter + 1;
}
<< { println!("Leave A: {:?}", context);
context.counter = context.counter + 1;
}
Next {
println!("Next in A: {:?}", context);
context.counter = context.counter + 1;
} => B {counter: context.counter}; )
(B context{counter: i16}:
>> {
println!("Enter B: {:?}", context);
context.counter = context.counter + 1;
}
<< {
println!("Leave B: {:?}", context);
context.counter = context.counter + 1;
}
Next {
println!("Next in B: {:?}", context);
context.counter = context.counter + 1;
} => A {counter: context.counter};
)
);
fn main() {
use MyMachine::*;
let mut machine = MyMachine::new();
machine.execute(&MyMachine::Commands::Next).unwrap();
machine.execute(&MyMachine::Commands::Next).unwrap();
}
Longer explanation
Simplest state machine example:
#[macro_use] extern crate macro_machine;
declare_machine!(
Simple(A) states[A,B] commands[Next] (A: Next => B; )
(B:
Next => A; )
);
So, now you can use state machine:
fn main() {
use Simple::*;
let mut machine = Simple::new();
machine.execute(&Simple::Commands::Next).unwrap();
machine.execute(&Simple::Commands::Next).unwrap();
}
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:
#[macro_use] extern crate macro_machine;
declare_machine!(
Simple(A{counter:0}) states[A,B] commands[Next] (A context{counter:i16}: Next {context.counter=context.counter+1}=> B{counter:context.counter}; )
(B context{counter:i16}:
Next {context.counter=context.counter+1}=> A{counter:context.counter};
)
);
Let's check our state transmission:
fn main() {
use Simple::*;
let mut machine = Simple::new();
assert!(match machine.get_current_state(){
States::A{context}=> if context.counter == 0 {true} else {false},
_=>false
});
machine.execute(&Simple::Commands::Next).unwrap();
assert!(match machine.get_current_state(){
States::B{context}=> if context.counter == 1 {true} else {false},
_=>false
});
machine.execute(&Simple::Commands::Next).unwrap();
assert!(match machine.get_current_state(){
States::A{context}=> if context.counter == 2 {true} else {false},
_=>false
});
}
Also there is callbacks on each entrance and each leave of state.
#[macro_use] extern crate macro_machine;
declare_machine!(
Simple(A{counter:0}) states[A,B] commands[Next] (A context{counter:i16}: >> {context.counter = context.counter+1;} << {context.counter = context.counter+1;} Next {context.counter=context.counter+1;} => B{counter:context.counter}; )
(B context{counter:i16}:
Next {context.counter=context.counter+1} => A{counter:context.counter};
)
);
fn main() {
use Simple::*;
let mut machine = Simple::new();
assert!(match machine.get_current_state(){
States::A{context}=> if context.counter == 1 {true} else {false},
_=>false
});
machine.execute(&Simple::Commands::Next).unwrap();
assert!(match machine.get_current_state(){
States::B{context}=> {println!("context counter: {}", context.counter);if context.counter == 3 {true} else {false}},
_=>false
});
machine.execute(&Simple::Commands::Next).unwrap();
assert!(match machine.get_current_state(){
States::A{context}=> if context.counter == 5 {true} else {false},
_=>false
});
}
Example of Machine-scoped context. This context exist in machine life-time.
Let's count machine's state changes:
#[macro_use] extern crate macro_machine;
declare_machine!(
Simple machine_context{counter: i16} (A) states[A,B]
commands[Next]
(A :
>> {machine_context.counter=machine_context.counter+1;} Next => B; )
(B :
>> {machine_context.counter=machine_context.counter+1;}
Next => A;
)
);
fn main() {
use Simple::*;
let mut machine = Simple::new(0); let context = machine.get_inner_context();
assert!(context.counter == 1);
machine.execute(&Simple::Commands::Next).unwrap();
let context = machine.get_inner_context();
assert!(context.counter == 2);
machine.execute(&Simple::Commands::Next).unwrap();
let context = machine.get_inner_context();
assert!(context.counter == 3);
}
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.