├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | *.un~ 4 | .idea 5 | macro_machine.iml 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "macro_machine" 3 | version = "0.2.0" 4 | authors = ["Victor Korkin "] 5 | 6 | description = "State machine generator using macros." 7 | 8 | readme = "README.md" 9 | 10 | repository = "https://github.com/VKlayd/rust_fsm_macros" 11 | 12 | keywords = ["state","machine","fsm","macro"] 13 | 14 | license = "MIT" 15 | 16 | [dependencies] 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Victor Korkin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Finite State Machine generator in Rust's macro 2 | 3 | ## Overview 4 | 5 | With this macro you can easily implement Finite State Machine in declarative way. 6 | 7 | State machine consists of: 8 | 9 | * Name 10 | * Initial state 11 | * List of **states** 12 | * List of **commands** 13 | * List of **state nodes** 14 | 15 | Each **state node** contains: 16 | 17 | * State 18 | * Context (optional) 19 | * List of **command reactions** 20 | 21 | Each **command reaction** contains: 22 | 23 | * Command to react on 24 | * User-defined code of reaction (optional) 25 | * Next state of machine (optional) 26 | 27 | ## Working example to begin with 28 | 29 | Let's say we'd like to implement such machine: 30 | 31 | ![FSM example](http://www.plantuml.com/plantuml/proxy?src=https://gist.githubusercontent.com/goldenreign/e363fe08501362d9618f2012f1ddfe2f/raw/07bb4bc604a204e7bcef38154135ee8a14c10f5b/gistfile1.puml "FSM example") 32 | 33 | Corresponding code will look like: 34 | 35 | ```rust 36 | #[macro_use] extern crate macro_machine; 37 | declare_machine!( 38 | MyMachine(A {counter: 0}) // Name and initial state with initial value 39 | states[A,B] // List of states 40 | commands[Next] // List of commands 41 | (A context{counter: i16}: // State node and this state context description with name binding 42 | >> { // Executed on state A enter 43 | println!("Enter A: {:?}", context); 44 | context.counter = context.counter + 1; 45 | } 46 | << { // Executed on state A leave 47 | println!("Leave A: {:?}", context); 48 | context.counter = context.counter + 1; 49 | } 50 | Next { 51 | println!("Next in A: {:?}", context); 52 | context.counter = context.counter + 1; 53 | } => 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. 54 | ) 55 | (B context{counter: i16}: 56 | >> { 57 | println!("Enter B: {:?}", context); 58 | context.counter = context.counter + 1; 59 | } 60 | << { 61 | println!("Leave B: {:?}", context); 62 | context.counter = context.counter + 1; 63 | } 64 | Next { 65 | println!("Next in B: {:?}", context); 66 | context.counter = context.counter + 1; 67 | } => A {counter: context.counter}; 68 | ) 69 | ); 70 | 71 | fn main() { 72 | use MyMachine::*; 73 | let mut machine = MyMachine::new(); 74 | machine.execute(&MyMachine::Commands::Next).unwrap(); 75 | machine.execute(&MyMachine::Commands::Next).unwrap(); 76 | } 77 | ``` 78 | 79 | ## Longer explanation 80 | 81 | Simplest state machine example: 82 | 83 | ```rust 84 | #[macro_use] extern crate macro_machine; 85 | declare_machine!( 86 | Simple(A) // Name and initial State 87 | states[A,B] // list of States 88 | commands[Next] // list of Commands 89 | (A: // State Node 90 | Next => B; // Command Reaction. Just change state to B 91 | ) 92 | (B: 93 | Next => A; // And back to A 94 | ) 95 | ); 96 | ``` 97 | 98 | So, now you can use state machine: 99 | 100 | ```rust 101 | fn main() { 102 | use Simple::*; 103 | let mut machine = Simple::new(); 104 | machine.execute(&Simple::Commands::Next).unwrap(); 105 | machine.execute(&Simple::Commands::Next).unwrap(); 106 | } 107 | ``` 108 | 109 | You can add some intelligence to machine. 110 | 111 | Each state can hold some data. On State change you can transmit some data between states. 112 | It looks like you just create struct with some fields initialization: 113 | 114 | ```rust 115 | #[macro_use] extern crate macro_machine; 116 | declare_machine!( 117 | Simple(A{counter:0}) // Name and initial State with initial value 118 | states[A,B] // list of States 119 | commands[Next] // list of Commands 120 | (A context{counter:i16}: // State Node and this state context description with binding name 121 | 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. 122 | ) 123 | (B context{counter:i16}: 124 | Next {context.counter=context.counter+1}=> A{counter:context.counter}; 125 | ) 126 | ); 127 | ``` 128 | 129 | Let's check our state transmission: 130 | 131 | ```rust 132 | fn main() { 133 | use Simple::*; 134 | let mut machine = Simple::new(); 135 | 136 | // We are in state A and have our initial value 0 137 | assert!(match machine.get_current_state(){ 138 | States::A{context}=> if context.counter == 0 {true} else {false}, 139 | _=>false 140 | }); 141 | machine.execute(&Simple::Commands::Next).unwrap(); 142 | 143 | // We are in state B and have counter == 1 144 | assert!(match machine.get_current_state(){ 145 | States::B{context}=> if context.counter == 1 {true} else {false}, 146 | _=>false 147 | }); 148 | machine.execute(&Simple::Commands::Next).unwrap(); 149 | 150 | // Again in state A and have counter == 2 151 | assert!(match machine.get_current_state(){ 152 | States::A{context}=> if context.counter == 2 {true} else {false}, 153 | _=>false 154 | }); 155 | } 156 | ``` 157 | 158 | Also there is callbacks on each entrance and each leave of state. 159 | 160 | ```rust 161 | #[macro_use] extern crate macro_machine; 162 | declare_machine!( 163 | Simple(A{counter:0}) // Name and initial State with initial value 164 | states[A,B] // list of States 165 | commands[Next] // list of Commands 166 | (A context{counter:i16}: // State Node and this state context description with binding name 167 | >> {context.counter = context.counter+1;} // Execute when enter state A 168 | << {context.counter = context.counter+1;} // Execute when leave state A 169 | 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. 170 | ) 171 | (B context{counter:i16}: 172 | Next {context.counter=context.counter+1} => A{counter:context.counter}; 173 | ) 174 | ); 175 | fn main() { 176 | use Simple::*; 177 | let mut machine = Simple::new(); 178 | assert!(match machine.get_current_state(){ 179 | // We are in state A and have value 1. Because Enter State callback executed. 180 | States::A{context}=> if context.counter == 1 {true} else {false}, 181 | _=>false 182 | }); 183 | machine.execute(&Simple::Commands::Next).unwrap(); 184 | assert!(match machine.get_current_state(){ 185 | // We are in state B and have counter == 3. Increment happen on User Code execution and execution of Leave state callback. 186 | States::B{context}=> {println!("context counter: {}", context.counter);if context.counter == 3 {true} else {false}}, 187 | _=>false 188 | }); 189 | machine.execute(&Simple::Commands::Next).unwrap(); 190 | assert!(match machine.get_current_state(){ 191 | // Again in state A and have counter == 5. Increment happen on User Code execution and on state A enter. 192 | States::A{context}=> if context.counter == 5 {true} else {false}, 193 | _=>false 194 | }); 195 | } 196 | ``` 197 | 198 | Example of Machine-scoped context. This context exist in machine life-time. 199 | 200 | Let's count machine's state changes: 201 | 202 | ```rust 203 | #[macro_use] extern crate macro_machine; 204 | declare_machine!( 205 | Simple machine_context{counter: i16} (A) // Declare machine scoped context 206 | states[A,B] 207 | commands[Next] 208 | (A : 209 | >> {machine_context.counter=machine_context.counter+1;} // Add 1 when enter in state 210 | Next => B; // Just switch to other state 211 | ) 212 | (B : 213 | >> {machine_context.counter=machine_context.counter+1;} 214 | Next => A; 215 | ) 216 | ); 217 | fn main() { 218 | use Simple::*; 219 | let mut machine = Simple::new(0); // Create machine and initiate machine context by 0 220 | let context = machine.get_inner_context(); 221 | assert!(context.counter == 1); 222 | machine.execute(&Simple::Commands::Next).unwrap(); 223 | let context = machine.get_inner_context(); 224 | assert!(context.counter == 2); 225 | machine.execute(&Simple::Commands::Next).unwrap(); 226 | let context = machine.get_inner_context(); 227 | assert!(context.counter == 3); 228 | } 229 | ``` 230 | 231 | ## Changelog 232 | 233 | ### 0.2.0 234 | 235 | * Changed behavior of Leave action. Now it execute before new State context creation. 236 | * Add machine-scoped context. It can be used by all callbacks inside machine. Data in this context have machine's life-time. 237 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(unused_must_use)] 2 | //! State machine generator 3 | //! 4 | //! State machine consists of: 5 | //! Name, initial state, List of States, List of Commands, list of States Nodes. 6 | //! Each State Node contain: Name, State Context (optional), list of Command Reactions. 7 | //! Each Command Reaction contain: Command to react on, user-defined code of reaction (optional) and 8 | //! next State of machine (optional). 9 | //! 10 | //! Simplest state machine example: 11 | //! 12 | //! ``` 13 | //! #[macro_use] extern crate macro_machine; 14 | //! 15 | //! declare_machine!( 16 | //! Simple (A) // Name and initial State 17 | //! states[A,B] // list of States 18 | //! commands[Next] // list of Commands 19 | //! (A: // State Node 20 | //! Next => B; // Command Reaction. Just change state to B 21 | //! ) 22 | //! (B: 23 | //! Next => A; 24 | //! ) 25 | //! ); 26 | //! 27 | //! # fn main() { 28 | //! use Simple::*; 29 | //! 30 | //! let mut machine = Simple::new(); 31 | //! assert!(match machine.get_current_state(){States::A{..}=>true,_=>false}); 32 | //! machine.execute(&Simple::Commands::Next).unwrap(); 33 | //! assert!(match machine.get_current_state(){States::B{..}=>true,_=>false}); 34 | //! machine.execute(&Simple::Commands::Next).unwrap(); 35 | //! assert!(match machine.get_current_state(){States::A{..}=>true,_=>false}); 36 | //! # } 37 | //! ``` 38 | //! 39 | //! You can add some intelligence to machine: 40 | //! 41 | //! ``` 42 | //! #[macro_use] extern crate macro_machine; 43 | //! 44 | //! declare_machine!( 45 | //! Simple (A{counter:0}) // Name and initial State with initial value 46 | //! states[A,B] // list of States 47 | //! commands[Next] // list of Commands 48 | //! (A context{counter:i16}: // State Node and this state context description with binding name 49 | //! 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. 50 | //! ) 51 | //! (B context{counter:i16}: 52 | //! Next {context.counter=context.counter+1}=> A{counter:context.counter}; 53 | //! ) 54 | //! ); 55 | //! 56 | //! # fn main() { 57 | //! use Simple::*; 58 | //! 59 | //! let mut machine = Simple::new(); 60 | //! assert!(match machine.get_current_state(){ 61 | //! States::A{context}=> if context.counter == 0 {true} else {false}, // We are in state A and have our initial value 0 62 | //! _=>false 63 | //! }); 64 | //! machine.execute(&Simple::Commands::Next).unwrap(); 65 | //! assert!(match machine.get_current_state(){ 66 | //! States::B{context}=> if context.counter == 1 {true} else {false}, // We are in state B and have counter == 1 67 | //! _=>false 68 | //! }); 69 | //! machine.execute(&Simple::Commands::Next).unwrap(); 70 | //! assert!(match machine.get_current_state(){ 71 | //! States::A{context}=> if context.counter == 2 {true} else {false}, // Again in state A and have counter == 2 72 | //! _=>false 73 | //! }); 74 | //! # } 75 | //! ``` 76 | //! ``` 77 | //! #[macro_use] extern crate macro_machine; 78 | //! 79 | //! declare_machine!( 80 | //! Simple (A{counter:0}) // Name and initial State with initial value 81 | //! states[A,B] // list of States 82 | //! commands[Next] // list of Commands 83 | //! (A context{counter:i16}: // State Node and this state context description with binding name 84 | //! >> {context.counter = context.counter+1;} // Execute when enter state A 85 | //! << {context.counter = context.counter+1;} // Execute when leave state A 86 | //! 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. 87 | //! ) 88 | //! (B context{counter:i16}: 89 | //! Next {context.counter=context.counter+1} => A{counter:context.counter}; 90 | //! ) 91 | //! ); 92 | //! 93 | //! # fn main() { 94 | //! use Simple::*; 95 | //! 96 | //! let mut machine = Simple::new(); 97 | //! assert!(match machine.get_current_state(){ 98 | //! 99 | //! // We are in state A and have value 1. Because Enter State callback executed. 100 | //! 101 | //! States::A{context}=> if context.counter == 1 {true} else {false}, 102 | //! _=>false 103 | //! }); 104 | //! machine.execute(&Simple::Commands::Next).unwrap(); 105 | //! assert!(match machine.get_current_state(){ 106 | //! 107 | //! // We are in state B and have counter == 3. Increment happen on User Code execution. Execution of Leave state callback happen after we transfer data to the next state. 108 | //! 109 | //! States::B{context}=> {println!("context counter: {}", context.counter);if context.counter == 3 {true} else {false}}, 110 | //! _=>false 111 | //! }); 112 | //! machine.execute(&Simple::Commands::Next).unwrap(); 113 | //! assert!(match machine.get_current_state(){ 114 | //! 115 | //! // Again in state A and have counter == 5. Increment happen on User Code execution and on state A enter. 116 | //! 117 | //! States::A{context}=> if context.counter == 5 {true} else {false}, 118 | //! _=>false 119 | //! }); 120 | //! # } 121 | //! ``` 122 | //! 123 | //! Example of Machine-scoped context. This context exist in machine life-time. 124 | //! 125 | //! Lets count machine's state changes: 126 | //! 127 | //! ``` 128 | //! #[macro_use] extern crate macro_machine; 129 | //! 130 | //! declare_machine!( 131 | //! Simple machine_context{counter: i16} (A) // Declare machine scoped context 132 | //! states[A,B] 133 | //! commands[Next] 134 | //! (A : 135 | //! >> {machine_context.counter=machine_context.counter+1;} // Add 1 when enter in state 136 | //! Next => B; // Just switch to other state 137 | //! ) 138 | //! (B : 139 | //! >> {machine_context.counter=machine_context.counter+1;} 140 | //! Next => A; 141 | //! ) 142 | //! ); 143 | //! 144 | //! # fn main() { 145 | //! use Simple::*; 146 | //! 147 | //! let mut machine = Simple::new(0); 148 | //! let context = machine.get_inner_context(); 149 | //! assert!(context.counter == 1); 150 | //! machine.execute(&Simple::Commands::Next).unwrap(); 151 | //! let context = machine.get_inner_context(); 152 | //! assert!(context.counter == 2); 153 | //! machine.execute(&Simple::Commands::Next).unwrap(); 154 | //! let context = machine.get_inner_context(); 155 | //! assert!(context.counter == 3); 156 | //! # } 157 | //! ``` 158 | //! 159 | 160 | #[macro_export] 161 | macro_rules! declare_machine { 162 | 163 | // Initialize state by values 164 | (@inner next $new_state:ident{$($new_el:ident:$new_el_val:expr),*}) => ( 165 | $new_state{$($new_el:$new_el_val),*} 166 | ); 167 | 168 | // if state have no fields to initialize. Just for remove redundant curl braces. 169 | (@inner next $new_state:ident) => ( 170 | $new_state{} 171 | ); 172 | 173 | // If Event have user-defined code and move machine to new state. Execute code and return new state. 174 | (@inner command @$glob_context:ident@ $sel:ident:$cur:ident;$callback:block;$new_state:ident$({$($new_el:ident:$new_el_val:expr),*})*) => ( 175 | { 176 | declare_machine!(@inner context $sel $cur); 177 | $callback; 178 | $cur.leave($glob_context).unwrap(); 179 | Some(States::$new_state{context: declare_machine!(@inner next $new_state$({$($new_el:$new_el_val),*})*)}) 180 | } 181 | ); 182 | 183 | // If Event have user-defined code and don't move machine to new state. Execute code and return __SameState__ . 184 | (@inner command @$glob_context:ident@ $sel:ident:$cur:ident;$callback:block;) => ( 185 | { 186 | declare_machine!(@inner context $sel $cur); 187 | $callback; 188 | Some(States::__SameState__) 189 | } 190 | ); 191 | 192 | // If Event have no user-defined code and move machine to new state. Just return new state. 193 | (@inner command @$glob_context:ident@ $sel:ident:$cur:ident; ;$new_state:ident$({$($new_el:ident:$new_el_val:expr),*})*) => ( 194 | { 195 | declare_machine!(@inner context $sel $cur); 196 | $cur.leave($glob_context).unwrap(); 197 | Some(States::$new_state{context: declare_machine!(@inner next $new_state$({$($new_el:$new_el_val),*})*)}) 198 | } 199 | ); 200 | 201 | // If Event have nothing to do on event. Just return __SameState__. 202 | (@inner command @$glob_context:ident@ $sel:ident:$cur:ident ; ;) => ( 203 | Some(States::__SameState__) 204 | ); 205 | 206 | // If Event have user-defined code and move machine to new state. Execute code and return new state. 207 | (@inner command @$glob_context:ident@ $sel:ident:$cur:ident;$callback:block;$new_state:ident$({$($new_el:ident:$new_el_val:expr),*})*) => ( 208 | { 209 | declare_machine!(@inner context $sel $cur); 210 | $callback; 211 | $cur.leave($glob_context).unwrap(); 212 | Some(States::$new_state{context: declare_machine!(@inner next $new_state$({$($new_el:$new_el_val),*})*)}) 213 | } 214 | ); 215 | 216 | // If Event have user-defined code and don't move machine to new state. Execute code and return __SameState__ . 217 | (@inner command @$glob_context:ident@ $sel:ident:$cur:ident;$callback:block;) => ( 218 | { 219 | declare_machine!(@inner context $sel $cur); 220 | $callback; 221 | Some(States::__SameState__) 222 | } 223 | ); 224 | 225 | // If Event have no user-defined code and move machine to new state. Just return new state. 226 | (@inner command @$glob_context:ident@ $sel:ident:$cur:ident; ;$new_state:ident$({$($new_el:ident:$new_el_val:expr),*})*) => ( 227 | { 228 | declare_machine!(@inner context $sel $cur); 229 | $cur.leave($glob_context).unwrap(); 230 | Some(States::$new_state{context: declare_machine!(@inner next $new_state$({$($new_el:$new_el_val),*})*)}) 231 | } 232 | ); 233 | 234 | // If Event have nothing to do on event. Just return __SameState__. 235 | (@inner command @$glob_context:ident@ $sel:ident:$cur:ident ; ;) => ( 236 | Some(States::__SameState__) 237 | ); 238 | 239 | (@inner context $ss:ident $sel:ident)=>(let $sel = $ss;); 240 | (@inner context $ss:ident )=>(); 241 | 242 | // Enter/Leave processors with and without user-defined code. 243 | (@inner >> $($sel:ident)* @$glob_context:ident@ $income:block) => ( 244 | fn enter(&mut self, $glob_context: &mut MachineContext) -> Result<(), ()> { 245 | declare_machine!(@inner context self $($sel)*); 246 | $income 247 | Ok(()) 248 | } 249 | ); 250 | (@inner << $($sel:ident)* @$glob_context:ident@ $outcome:block) => ( 251 | fn leave(&mut self, $glob_context: &mut MachineContext) -> Result<(), ()> { 252 | declare_machine!(@inner context self $($sel)*); 253 | $outcome 254 | Ok(()) 255 | } 256 | ); 257 | (@inner >> $($sel:ident)* @$glob_context:ident@ ) => ( 258 | fn enter(&mut self, $glob_context: &mut MachineContext) -> Result<(), ()> { 259 | Ok(()) 260 | } 261 | ); 262 | (@inner << $($sel:ident)* @$glob_context:ident@ ) => ( 263 | fn leave(&mut self, $glob_context: &mut MachineContext) -> Result<(), ()> { 264 | Ok(()) 265 | } 266 | ); 267 | 268 | // This structs keep user-defined contexts for states. 269 | (@inner params $state:ident {$($el:ident:$typ:ty);*}) => ( 270 | #[derive(Debug)] 271 | #[derive(PartialEq)] 272 | #[derive(Copy)] 273 | #[derive(Clone)] 274 | pub struct $state {pub $($el:$typ),*} 275 | ); 276 | (@inner params $state:ident) => ( 277 | #[derive(Debug)] 278 | #[derive(PartialEq)] 279 | #[derive(Copy)] 280 | #[derive(Clone)] 281 | pub struct $state {} 282 | ); 283 | (@inner initial $initial:ident{$($init_field:ident:$init_val:expr),*}) => ($initial{$($init_field: $init_val),*}); 284 | (@inner initial $initial:ident) => ($initial{}); 285 | 286 | (@cmd_processor $sel:ident @$glob_context:ident@ ($($cmd:ident $($callback:block)* => $($new_state:ident$({$($new_el:ident:$new_el_val:expr),*})*)*;)*))=>( 287 | fn do_job(&mut self, cmd: & Commands, $glob_context: &mut MachineContext) -> Option { 288 | match *cmd { 289 | $(Commands::$cmd => {declare_machine!(@inner command @$glob_context@ self:$sel;$($callback)*;$($new_state$({$($new_el:$new_el_val),*})*)*)})* 290 | _ => None 291 | } 292 | } 293 | ); 294 | 295 | (@state $gc_name:ident; $($state:ident @ $sel:ident ; $($income:block)*; ($job:tt); $($outcome:block)*@),*) => ( 296 | $( 297 | impl CanDoJob for $state { 298 | declare_machine!(@cmd_processor $sel @$gc_name@ $job); 299 | declare_machine!(@inner >> $sel @$gc_name@ $($income)*); 300 | declare_machine!(@inner << $sel @$gc_name@ $($outcome)*); 301 | } 302 | )* 303 | ); 304 | (@state ; $($state:ident @ $sel:ident ; $($income:block)*; ($job:tt); $($outcome:block)* @),*) => ( 305 | $( 306 | impl CanDoJob for $state { 307 | declare_machine!(@cmd_processor $sel @__@ $job); 308 | declare_machine!(@inner >> $sel @__@ $($income)*); 309 | declare_machine!(@inner << $sel @__@ $($outcome)*); 310 | } 311 | )* 312 | ); 313 | 314 | (@state $gc_name:ident; $($state:ident@; $($income:block)*; ($job:tt); $($outcome:block)*@),*) => ( 315 | $( 316 | impl CanDoJob for $state { 317 | declare_machine!(@cmd_processor ___ @$gc_name@ $job); 318 | declare_machine!(@inner >> ___ @$gc_name@ $($income)*); 319 | declare_machine!(@inner << ___ @$gc_name@ $($outcome)*); 320 | } 321 | )* 322 | ); 323 | (@state ; $($state:ident@; $($income:block)*; ($job:tt); $($outcome:block)*@),*) => ( 324 | $( 325 | impl CanDoJob for $state { 326 | declare_machine!(@cmd_processor ___ @__@ $job); 327 | declare_machine!(@inner >> ___ @__@ $($income)*); 328 | declare_machine!(@inner << ___ @__@ $($outcome)*); 329 | } 330 | )* 331 | ); 332 | 333 | // Main pattern 334 | 335 | ( 336 | $machine:ident $($gc_name:ident{$($context_field:ident:$context_type:ty),*})* ($initial:ident$({$($init_field:ident:$init_val:expr),*})*) 337 | states[$($states:ident),*] 338 | commands[$($commands:ident),*] 339 | 340 | $(($state:ident $($sel:ident)*$({$($el:ident:$typ:ty);*})*: 341 | $(>> $income:block)* 342 | $(<< $outcome:block)* 343 | $($cmd:ident $($callback:block)* => $($new_state:ident$({$($new_el:ident:$new_el_val:expr),*})*)*;)* 344 | ))* 345 | ) => ( 346 | #[allow(non_snake_case)] 347 | #[allow(unused_imports)] 348 | #[allow(dead_code)] 349 | #[allow(unused_variables)] 350 | mod $machine { 351 | use super::*; 352 | trait CanDoJob { 353 | fn do_job(&mut self, cmd: &Commands, global_context: &mut MachineContext) -> Option; 354 | fn leave(&mut self, &mut MachineContext) -> Result<(), ()>; 355 | fn enter(&mut self, &mut MachineContext) -> Result<(), ()>; 356 | } 357 | 358 | $( 359 | declare_machine!(@inner params $state $({$($el:$typ);*})*); 360 | )* 361 | 362 | declare_machine!(@state $($gc_name)*;$($state @ $($sel)* ; $($income)*; (($($cmd $($callback)* => $($new_state $({$($new_el:$new_el_val),*})*)*;)*)); $($outcome)*@),*); 363 | 364 | #[derive(Debug)] 365 | #[derive(PartialEq)] 366 | #[derive(Copy)] 367 | #[derive(Clone)] 368 | pub enum States { 369 | __SameState__, 370 | $($states {context: $states}),* 371 | } 372 | 373 | #[derive(Debug)] 374 | #[derive(PartialEq)] 375 | pub enum Commands { 376 | $($commands),* 377 | } 378 | 379 | #[derive(Clone)] 380 | pub struct MachineContext {$($(pub $context_field: $context_type),*)*} 381 | 382 | pub struct Machine { 383 | state: States, 384 | context: MachineContext 385 | } 386 | pub fn new($($($context_field: $context_type),*)*) -> Machine { 387 | let mut context = declare_machine!(@inner initial $initial $({$($init_field: $init_val),*})*); 388 | let mut machine_context = MachineContext{$($($context_field: $context_field),*)*}; 389 | context.enter(&mut machine_context).unwrap(); 390 | Machine{state: States::$initial{context: context}, context: machine_context} 391 | } 392 | 393 | impl Machine { 394 | pub fn execute(&mut self, cmd: & Commands) -> Result<(),()>{ 395 | match { 396 | match self.state { 397 | States::__SameState__ => None, 398 | $(States::$state{ ref mut context } => context.do_job(cmd, &mut self.context)),* 399 | } 400 | } { 401 | Some(x) => { 402 | match x { 403 | States::__SameState__ => {}, 404 | _ => { 405 | self.change_state(x) 406 | } 407 | };Ok(()) 408 | }, 409 | None => {println!("Wrong operation {:?} for {:?} state!", cmd, self.state); Err(())} 410 | } 411 | } 412 | fn change_state(&mut self, new_state: States) { 413 | self.state = new_state; 414 | match self.state { 415 | States::__SameState__ => Ok(()), 416 | $(States::$state{ ref mut context } => context.enter(&mut self.context)),* 417 | }.unwrap(); 418 | } 419 | pub fn get_current_state(&self) -> States { 420 | self.state.clone() 421 | } 422 | pub fn get_inner_context(&self) -> MachineContext { 423 | self.context.clone() 424 | } 425 | } 426 | } 427 | ) 428 | } 429 | 430 | #[cfg(test)] 431 | mod tests { 432 | fn tes(x:i16) { 433 | println!("x:{}",x); 434 | } 435 | 436 | declare_machine!( 437 | Mach1 (New{x:0}) 438 | 439 | states[New,InConfig,Operational] 440 | commands[Configure, ConfigureDone, Drop] 441 | 442 | ( New context{x: i16}: 443 | >> {println!("Enter {:?}", context)} 444 | << {println!("Leave {:?}", context)} 445 | Configure {println!("In New with context val: {}", context.x);} => InConfig{x:context.x+1, y:0}; 446 | ConfigureDone => New{x:0}; 447 | ) 448 | ( InConfig context{x:i16; y:i16}: 449 | >> {println!("Enter {:?}", context)} 450 | << {println!("Leave {:?}", context)} 451 | ConfigureDone {tes(context.x)}=> Operational; 452 | ) 453 | ( Operational context: 454 | >> {println!("Enter {:?}", context)} 455 | << {println!("Leave {:?}", context)} 456 | ConfigureDone =>; 457 | Drop => New{x:0}; 458 | ) 459 | ); 460 | 461 | declare_machine!( 462 | Mach2 (State1) 463 | 464 | states[State1,State2,State3] 465 | commands[ToState1, ToState2, ToState3] 466 | 467 | ( State1 : 468 | ToState2 => State2; 469 | ) 470 | ( State2 : 471 | ToState3 => State3; 472 | ) 473 | ( State3 : 474 | ToState1 => State1; 475 | ) 476 | ); 477 | 478 | #[test] 479 | fn test1() { 480 | let mut m = Mach1::new(); 481 | m.execute(&Mach1::Commands::Configure).unwrap(); 482 | m.execute(&Mach1::Commands::ConfigureDone).unwrap(); 483 | m.execute(&Mach1::Commands::Drop).unwrap(); 484 | m.execute(&Mach1::Commands::Configure).unwrap(); 485 | m.execute(&Mach1::Commands::ConfigureDone).unwrap(); 486 | m.execute(&Mach1::Commands::ConfigureDone).unwrap(); 487 | m.execute(&Mach1::Commands::ConfigureDone).unwrap(); 488 | } 489 | 490 | #[test] 491 | #[should_panic] 492 | fn test2() { 493 | let mut m = Mach2::new(); 494 | m.execute(&Mach2::Commands::ToState2).unwrap(); 495 | m.execute(&Mach2::Commands::ToState3).unwrap(); 496 | m.execute(&Mach2::Commands::ToState1).unwrap(); 497 | m.execute(&Mach2::Commands::ToState3).unwrap(); 498 | } 499 | 500 | declare_machine!( 501 | Mach3 glob_cont{id:i16}(State1{counter:0}) 502 | 503 | states[State1,State2,State3] 504 | commands[ToState1, ToState2, ToState3] 505 | 506 | ( State1 cont{counter:i16}: 507 | >>{println!("Mach {} enter {:?}", glob_cont.id, cont);} 508 | <<{cont.counter+=1; println!("Mach {} leave {:?}", glob_cont.id, cont);} 509 | ToState2 => State2{counter: cont.counter}; 510 | ) 511 | ( State2 cont{counter:i16}: 512 | >>{println!("Mach {} enter {:?}", glob_cont.id, cont);} 513 | <<{cont.counter+=1; println!("Mach {} leave {:?}", glob_cont.id, cont);} 514 | ToState3 => State3{counter: cont.counter}; 515 | ) 516 | ( State3 cont{counter:i16}: 517 | >>{println!("Mach {} enter {:?}", glob_cont.id, cont);} 518 | <<{cont.counter+=1; println!("Mach {} leave {:?}", glob_cont.id, cont);} 519 | ToState1 => State1{counter: cont.counter}; 520 | ) 521 | ); 522 | 523 | #[test] 524 | fn test3() { 525 | let mut m = Mach3::new(0); 526 | let mut m1 = Mach3::new(1); 527 | m1.execute(&Mach3::Commands::ToState2).unwrap(); 528 | m.execute(&Mach3::Commands::ToState2).unwrap(); 529 | m.execute(&Mach3::Commands::ToState3).unwrap(); 530 | m1.execute(&Mach3::Commands::ToState3).unwrap(); 531 | } 532 | 533 | #[derive(Clone)] 534 | pub struct InnerMachineContext { 535 | id: i16, 536 | name: String, 537 | counter: i16 538 | } 539 | 540 | declare_machine!( 541 | Mach4 inner{st: InnerMachineContext} (State1) 542 | states[State1,State2,State3] 543 | commands[ToState1, ToState2, ToState3] 544 | 545 | ( State1 : 546 | << {println!("id={} name={} counter={}", inner.st.id, inner.st.name, inner.st.counter);} 547 | ToState2 {inner.st.counter+=1;}=> State2; 548 | ) 549 | ( State2 : 550 | << {println!("id={} name={} counter={}", inner.st.id, inner.st.name, inner.st.counter);} 551 | ToState3 {inner.st.counter+=1;}=> State3; 552 | ) 553 | ( State3 : 554 | << {println!("id={} name={} counter={}", inner.st.id, inner.st.name, inner.st.counter);} 555 | ToState1 {inner.st.counter+=1;}=> State1; 556 | ) 557 | ); 558 | #[test] 559 | fn test4() { 560 | let mut m = Mach4::new(InnerMachineContext{id:0, name: String::from("Mach 0"), counter: 0}); 561 | let mut m1 = Mach4::new(InnerMachineContext{id:1, name: String::from("Mach 1"), counter: 0}); 562 | let mut m2 = Mach4::new(InnerMachineContext{id:2, name: String::from("Mach 2"), counter: 0}); 563 | m.execute(&Mach4::Commands::ToState2).unwrap(); 564 | m.execute(&Mach4::Commands::ToState3).unwrap(); 565 | m1.execute(&Mach4::Commands::ToState2).unwrap(); 566 | m.execute(&Mach4::Commands::ToState1).unwrap(); 567 | m1.execute(&Mach4::Commands::ToState3).unwrap(); 568 | m2.execute(&Mach4::Commands::ToState2).unwrap(); 569 | m.execute(&Mach4::Commands::ToState2).unwrap(); 570 | m2.execute(&Mach4::Commands::ToState3).unwrap(); 571 | m1.execute(&Mach4::Commands::ToState1).unwrap(); 572 | } 573 | } 574 | --------------------------------------------------------------------------------