├── .gitignore ├── LICENSE ├── Makefile ├── README.md └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | 5 | # Libraries 6 | *.lib 7 | *.a 8 | *.rlib 9 | 10 | lib/* 11 | docs/* 12 | bin/* 13 | 14 | # Shared objects (inc. Windows DLLs) 15 | *.dll 16 | *.so 17 | *.so.* 18 | *.dylib 19 | 20 | # Executables 21 | *.exe 22 | *.out 23 | *.app 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Daniel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | mkdir -p lib && rustc src/lib.rs --lib -o lib/fsm 3 | 4 | test: 5 | mkdir -p bin && rustc src/lib.rs -o bin/fsmtest --test 6 | ./bin/fsmtest 7 | 8 | docs: 9 | mkdir -p docs && rustdoc src/lib.rs -o docs/ 10 | 11 | docstest: 12 | mkdir -p docs && rustdoc src/lib.rs -o docs/ --test 13 | 14 | .PHONY: build docs test -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Finite State Machine (FSM) for Rust 2 | --- 3 | 4 | Rust-fsm is finite state machine implementation in Rust. It exposes specific macros to make it fairly expressive to work with. Currently, Scala-like syntax is an inspiration for this library. You can get pretty far with macros, except Rust is currently limited in mutli-statement macros. 5 | 6 | 7 | ## Install 8 | 9 | Todo 10 | 11 | ## Usage 12 | 13 | ```rust 14 | extern mod fsm; 15 | 16 | use fsm; 17 | 18 | fn main() { 19 | // Create a new set of states. 20 | // `State` defines a new module where each state is defined in. 21 | defstates! (State -> Unlocked, Locked); 22 | 23 | // Create a new FSM and pass an initial state: 24 | let mut machine = fsm::StateMachine::new(State::Unlocked); 25 | 26 | // Do something when we lock the machine: 27 | machine.when(State::Locked, || { 28 | println!("We have locked it again."); 29 | }); 30 | 31 | machine.switch(State::Locked); 32 | } 33 | ``` 34 | 35 | ## Docs 36 | 37 | ``` 38 | make docs 39 | open docs/fsm/index.html 40 | ``` 41 | 42 | ## License 43 | 44 | MIT -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[crate_id = "fsm#0.1"]; 2 | #[crate_type = "lib"]; 3 | #[feature(macro_rules)]; 4 | 5 | //! 6 | //! Rust-FSM provides a Finite State Machine implementation. 7 | //! 8 | //! ```rust 9 | //! extern mod fsm; 10 | //! 11 | //! fn main() { 12 | //! // Create a new set of states. 13 | //! // `State` defines a new module where each state is defined in. 14 | //! defstates! (State -> Unlocked, Locked); 15 | //! 16 | //! // Create a new FSM and pass an initial state: 17 | //! let mut machine = fsm::StateMachine::new(State::Unlocked); 18 | //! 19 | //! // Do something when we lock the machine: 20 | //! machine.when(State::Locked, || { 21 | //! println!("We have locked it again."); 22 | //! }); 23 | //! 24 | //! machine.switch(State::Locked); 25 | //! } 26 | //! ``` 27 | 28 | /// Create a new module that will contain an enum for each State and 29 | /// also implement the `Eq` trait for simple comparisons. 30 | /// 31 | /// ```rust 32 | /// defstates! (State -> Locked, Unlocked, Moved, Wrong, Weird) 33 | /// assert_eq!(State::Locked, 0); 34 | /// assert_eq!(State::Unlocked, 1); 35 | /// // ... 36 | /// ``` 37 | macro_rules! defstates( 38 | ($namespace:ident -> $($name:ident),+) => ( 39 | mod $namespace { 40 | pub enum State { 41 | $( 42 | $name, 43 | )+ 44 | } 45 | 46 | impl Eq for State { 47 | fn eq(&self, rs: &State) -> bool { 48 | *self as int == *rs as int 49 | } 50 | } 51 | } 52 | ); 53 | ) 54 | 55 | /// A representation of a state machine that holds the current state, 56 | /// as well as an owned vector of tuple elements. The tuple contains the 57 | /// state and a lambda, specified with a named lifetime. 58 | pub struct StateMachine<'a, T> { 59 | /// Store the currently selected state 60 | currentState: T, 61 | exprs: ~[(T, 'a ||)] 62 | } 63 | 64 | /// Establish two generic types parameters: `'a` which defines the lifetime 65 | /// of the closure/lambda to `.when` methods; and `T` which defines the type 66 | /// of state object. 67 | impl<'a, T: Eq> StateMachine<'a, T> { 68 | 69 | /// Creates a new instance of the `StateMachine` struct. We begin 70 | /// with an empty set of expressions and an initial state. 71 | pub fn new(initialState: T) -> StateMachine { 72 | StateMachine { currentState: initialState, exprs: ~[] } 73 | } 74 | 75 | /// Transition/switch the current state to another one. This will trigger 76 | /// any `.when` expressions that match. 77 | pub fn switch(&mut self, nextState: T) { 78 | self.currentState = nextState; 79 | for expr in self.exprs.iter() { 80 | match *expr { 81 | (ref state, ref func) => { 82 | if *state == self.currentState { 83 | (*func)(); 84 | } 85 | } 86 | } 87 | } 88 | } 89 | 90 | /// Pass a lambda/closure whenever a specific state is triggered. This is 91 | /// typically how and where the logic goes. `'a` defines a named lifetime 92 | /// based on the lambda, because lambda's capture their environment. 93 | pub fn when(&mut self, state: T, func: 'a ||) { 94 | self.exprs.push((state, func)); 95 | } 96 | } 97 | 98 | #[cfg(test)] 99 | mod test { 100 | 101 | 102 | #[test] 103 | fn test_sm_new() { 104 | defstates! (State -> One); 105 | let sm = ::StateMachine::new(State::One); 106 | assert_eq!(sm.currentState, State::One); 107 | } 108 | 109 | // FIXME: Replace with the `defstates!` macro. 110 | #[test] 111 | fn test_sm_switch() { 112 | 113 | enum State { 114 | Unlocked = 0x01, 115 | Locked 116 | } 117 | 118 | impl Eq for State { 119 | fn eq(&self, rs: &State) -> bool { 120 | *self as int == *rs as int 121 | } 122 | } 123 | 124 | let mut sm = ::StateMachine::new(Unlocked); 125 | sm.switch(Locked); 126 | assert_eq!(sm.currentState as int, Locked as int); 127 | } 128 | 129 | // FIXME: Replace with the `defstates!` macro. 130 | #[test] 131 | fn test_when() { 132 | 133 | enum State { 134 | Unlocked = 0x01, 135 | Locked 136 | } 137 | 138 | /// FIXME: This model is **super** akward to present as an API. 139 | /// Perhaps move away from enums and into custom types? 140 | impl Eq for State { 141 | fn eq(&self, rs: &State) -> bool { 142 | *self as int == *rs as int 143 | } 144 | } 145 | 146 | let mut sm = ::StateMachine::new(Unlocked); 147 | let mut called = false; 148 | 149 | sm.when(Locked, || { 150 | called = true; 151 | println!("Hello from Locked!"); 152 | }); 153 | 154 | assert_eq!(called, false); 155 | sm.switch(Locked); 156 | assert_eq!(called, true); 157 | } 158 | 159 | #[test] 160 | fn test_defstates_macro() { 161 | defstates! (State -> Woot, Wolf); 162 | assert_eq!(State::Woot as int, 0); 163 | assert_eq!(State::Wolf as int, 1); 164 | } 165 | } --------------------------------------------------------------------------------