barebone-fsm 0.0.1.1 → 0.0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +86 -0
- data/lib/barebone-fsm.rb +57 -69
- metadata +5 -3
data/README.rdoc
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
== Overview
|
|
2
|
+
This module implements a basic finite-state machine (FSM).
|
|
3
|
+
An FSM consists of a finite number of states,
|
|
4
|
+
with one of them being the current state of the FSM.
|
|
5
|
+
Transitions are defined between states, which are triggered on events.
|
|
6
|
+
For details on FSM, see this wiki page: {FSM}[http://en.wikipedia.org/wiki/Finite-state_machine].
|
|
7
|
+
|
|
8
|
+
The motivation behind the module was to implement a very basic barebone FSM in Ruby.
|
|
9
|
+
Features are kept at minimum as well as the code.
|
|
10
|
+
Only two classes for the FSM are defined as the following:
|
|
11
|
+
* FSM -> the finite state machine class
|
|
12
|
+
* FSMState -> the state class
|
|
13
|
+
|
|
14
|
+
Author:: Md. Imrul Hassan (mailto:mihassan@gmail.com)
|
|
15
|
+
Copyright:: Copyright: Md. Imrul Hassan, 2013
|
|
16
|
+
|
|
17
|
+
== Features
|
|
18
|
+
Apart from having support for states and events, this module offers the following features:
|
|
19
|
+
1. Default state
|
|
20
|
+
2. Default event for each state
|
|
21
|
+
3. Entry and exit events for each state
|
|
22
|
+
4. DSL like coding style
|
|
23
|
+
5. Access to state variables (including @state and @event) inside event blocks.
|
|
24
|
+
Currently the state variables can only be shared among event blocks of the same state.
|
|
25
|
+
|
|
26
|
+
== Usage
|
|
27
|
+
The FSM can be setup and triggered Succinctly using Domain Specific Language(DSL) like coding style.
|
|
28
|
+
There are two methods to build and run which are defined for both FSM and FSMState classes.
|
|
29
|
+
These methods, #build and #run, are alias of each other and can be used interchangebly.
|
|
30
|
+
A basic fintite-state machine for a microwave is simulated in the following example:
|
|
31
|
+
|
|
32
|
+
fsm = FSM::FSM.new(:stopped) # :stopped is the default state to fall back
|
|
33
|
+
|
|
34
|
+
fsm.build do # the states and events can be configured within the build block
|
|
35
|
+
state :stopped do # the events can be setup within the state block for :stopped state
|
|
36
|
+
event :open do # the event block is run when the event :open is triggered
|
|
37
|
+
puts "[Stopped]: Door Opened"
|
|
38
|
+
:open # the return value of the event block, :open, is the next state
|
|
39
|
+
end
|
|
40
|
+
event :start do
|
|
41
|
+
puts "[Stopped]: Started"
|
|
42
|
+
:started
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
state :open do
|
|
46
|
+
event :close do
|
|
47
|
+
puts "[#{@state}]: Door #{@event}" # the event block has access to state variables such as @state and @event
|
|
48
|
+
:stopped
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
state :started do
|
|
52
|
+
event :open do
|
|
53
|
+
@open_time = Time::now # new state variables can be defined
|
|
54
|
+
puts "[Started]: Door Opened"
|
|
55
|
+
:open
|
|
56
|
+
end
|
|
57
|
+
event :stop do
|
|
58
|
+
puts "The door was opened at #{@open_time}" # state variables from other event blocks can be used
|
|
59
|
+
puts "[Started]: Door Stopped after #{elapsed_time}"
|
|
60
|
+
:stopped
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
fsm.run do # the events can be triggered from the run block
|
|
66
|
+
event :start
|
|
67
|
+
event :open
|
|
68
|
+
event :close
|
|
69
|
+
event :start
|
|
70
|
+
event :stop
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
puts fsm
|
|
74
|
+
|
|
75
|
+
== Status
|
|
76
|
+
The module works as it is, but further testing and documentation is needed.
|
|
77
|
+
The api is not stable yet, it may go trhough lots of changes before the first stable version is released.
|
|
78
|
+
I am open to any suggestion or request to support custom features.
|
|
79
|
+
|
|
80
|
+
== Changes
|
|
81
|
+
* Version: 0.0.1.2
|
|
82
|
+
* @state instance variable for FSM is dropped in favour of the #state instance method.
|
|
83
|
+
* #state method and index operator [] now accept nil arguement for state name, which returns the current state.
|
|
84
|
+
* Two methods #build and #run are added to FSM and FSMState classes to support DSL like features.
|
|
85
|
+
* Explicit parameter passing for the event blocks is no longer available.
|
|
86
|
+
Instead the block code for the events now have access to all instance variables of FSMState class such as @state and @event.
|
data/lib/barebone-fsm.rb
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
##
|
|
2
|
-
# == Overview
|
|
3
|
-
# ---
|
|
4
2
|
# This module implements a basic finite-state machine (FSM).
|
|
5
3
|
# An FSM consists of a finite number of states,
|
|
6
4
|
# with one of them being the current state of the FSM.
|
|
@@ -12,62 +10,12 @@
|
|
|
12
10
|
# Only two classes for the FSM are defined as the following:
|
|
13
11
|
# * FSM -> the finite state machine class
|
|
14
12
|
# * FSMState -> the state class
|
|
13
|
+
#
|
|
14
|
+
# For further details see the README file.
|
|
15
15
|
#
|
|
16
16
|
# Author:: Md. Imrul Hassan (mailto:mihassan@gmail.com)
|
|
17
17
|
# Copyright:: Copyright: Md. Imrul Hassan, 2013
|
|
18
18
|
#
|
|
19
|
-
# == Status
|
|
20
|
-
# The module works, but further testing and documentation is needed.
|
|
21
|
-
# The api is not stable yet, I may decide to change the way FSM is used.
|
|
22
|
-
#
|
|
23
|
-
# == Features
|
|
24
|
-
# Apart from having support for states and events, this module offers the following features:
|
|
25
|
-
# 1. Default state
|
|
26
|
-
# 2. Default event for each state
|
|
27
|
-
# 3. Entry and exit events for each state
|
|
28
|
-
#
|
|
29
|
-
# == Usage
|
|
30
|
-
# ---
|
|
31
|
-
# A simple example:
|
|
32
|
-
#
|
|
33
|
-
# fsm = FSM::FSM.new
|
|
34
|
-
# state = fsm[:state_name]
|
|
35
|
-
# state.event(:event_name) do |st, ev|
|
|
36
|
-
# puts "#{ev} triggered on state #{st}"
|
|
37
|
-
# :new_state}
|
|
38
|
-
# end
|
|
39
|
-
# fsm.event :event_name
|
|
40
|
-
#
|
|
41
|
-
# A complete example:
|
|
42
|
-
#
|
|
43
|
-
# fsm = FSM::FSM.new(:stopped)
|
|
44
|
-
#
|
|
45
|
-
# fsm[:stopped].event(:open) { puts "[Stopped]: Door Opened"; :open}
|
|
46
|
-
# fsm[:stopped].event(:start) { puts "[Stopped]: Started"; :started}
|
|
47
|
-
# fsm[:stopped].event(:enter) {|st, ev| puts "[Stopped]: Eneter into state #{st}"}
|
|
48
|
-
# fsm[:stopped].event(:exit) {|st, ev| puts "[Stopped]: Exit from state #{st}"}
|
|
49
|
-
#
|
|
50
|
-
# fsm[:open].event(:close) { puts "[Open]: Door Closed"; :stopped}
|
|
51
|
-
# fsm[:open].event(:default) {|st, ev| puts "[Open]: Event [#{ev}] not defined in state #{st}"; :open}
|
|
52
|
-
# fsm[:open].event(:enter) {|st, ev| puts "[Open]: Eneter into state #{st}"}
|
|
53
|
-
# fsm[:open].event(:exit) {|st, ev| puts "[Open]: Exit from state #{st}"}
|
|
54
|
-
#
|
|
55
|
-
# fsm[:started].event(:open) { puts "[Started]: Door Opened"; :open}
|
|
56
|
-
# fsm[:started].event(:stop) { puts "[Started]: Stopped"; :stopped}
|
|
57
|
-
# fsm[:started].event(:enter) {|st, ev| puts "[Started]: Eneter into state #{st}"}
|
|
58
|
-
# fsm[:started].event(:exit) {|st, ev| puts "[Started]: Exit from state #{st}"}
|
|
59
|
-
#
|
|
60
|
-
# puts fsm
|
|
61
|
-
#
|
|
62
|
-
# fsm.event :open
|
|
63
|
-
# fsm.event :start
|
|
64
|
-
# fsm.event :close
|
|
65
|
-
# fsm.event :start
|
|
66
|
-
# fsm.event :stop
|
|
67
|
-
# fsm.event :start
|
|
68
|
-
#
|
|
69
|
-
# puts sm
|
|
70
|
-
#
|
|
71
19
|
module FSM
|
|
72
20
|
|
|
73
21
|
# == Overview
|
|
@@ -83,31 +31,45 @@ module FSM
|
|
|
83
31
|
#
|
|
84
32
|
class FSMState
|
|
85
33
|
|
|
86
|
-
|
|
34
|
+
# The readonly state variable represents an unique state.
|
|
35
|
+
# Though it can have any data type, usage of symbol or string is preferable.
|
|
36
|
+
attr_reader :state
|
|
87
37
|
|
|
88
38
|
def initialize(state_name)
|
|
89
|
-
@
|
|
39
|
+
@state = state_name
|
|
90
40
|
@events = {}
|
|
91
41
|
end
|
|
92
42
|
|
|
93
43
|
def to_s()
|
|
94
|
-
@
|
|
44
|
+
@state.to_s +
|
|
95
45
|
": [" +
|
|
96
46
|
@events.keys.map(&:to_s).join(', ') +
|
|
97
47
|
"]"
|
|
98
48
|
end
|
|
99
49
|
|
|
50
|
+
# When the event_block is provided, it sets up a new event for this state.
|
|
51
|
+
# Otherwise, when the event_block is missing, the event_name is triggered.
|
|
52
|
+
# If the event is nil or not already setup, then the default event is triggered.
|
|
100
53
|
def event(event_name, &event_block)
|
|
101
|
-
if block_given? then
|
|
54
|
+
if event_name and block_given? then
|
|
102
55
|
@events[event_name] = event_block
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
56
|
+
elsif event_name and @events.has_key? event_name then
|
|
57
|
+
@event = event_name
|
|
58
|
+
self.instance_eval &@events[@event]
|
|
59
|
+
elsif @events.has_key? :default then
|
|
60
|
+
@event = :default
|
|
61
|
+
self.instance_eval &@events[@event]
|
|
109
62
|
end
|
|
110
63
|
end
|
|
64
|
+
|
|
65
|
+
# The #build/#run method sets up the events as given in the build_block.
|
|
66
|
+
# Only event method is supported within the build_block with the name of the event and an optional block supplied.
|
|
67
|
+
# The operation for each such line is carried out by the #event method.
|
|
68
|
+
def build(&build_block)
|
|
69
|
+
self.instance_eval &build_block
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
alias_method :run, :build
|
|
111
73
|
|
|
112
74
|
end
|
|
113
75
|
|
|
@@ -131,7 +93,6 @@ module FSM
|
|
|
131
93
|
# end
|
|
132
94
|
#
|
|
133
95
|
class FSM
|
|
134
|
-
attr_reader :state
|
|
135
96
|
|
|
136
97
|
def initialize(default_state=nil)
|
|
137
98
|
@states = {}
|
|
@@ -145,19 +106,36 @@ module FSM
|
|
|
145
106
|
"FSM" +
|
|
146
107
|
": {" +
|
|
147
108
|
@states.values.map{ |st|
|
|
148
|
-
(st.
|
|
109
|
+
(st.state==@state ? ">" : "") + st.to_s
|
|
149
110
|
}.join(', ') +
|
|
150
111
|
"}"
|
|
151
112
|
end
|
|
152
113
|
|
|
153
|
-
|
|
154
|
-
|
|
114
|
+
# It returns the FSMState object for state_name.
|
|
115
|
+
# If the state is missing, then it first sets up the state, and then returns the newly created state.
|
|
116
|
+
def [](state_name=nil)
|
|
117
|
+
state_name ||= @state
|
|
118
|
+
if state_name and not @states.has_key? state_name then
|
|
155
119
|
@states[state_name] = FSMState.new(state_name)
|
|
156
120
|
@state ||= state_name
|
|
157
121
|
end
|
|
158
122
|
@states[state_name]
|
|
159
123
|
end
|
|
124
|
+
|
|
125
|
+
# When the state_block is provided, it sets up a new state.
|
|
126
|
+
# Otherwise, when the state_block is missing, the FSMState object for state_name is returned.
|
|
127
|
+
# If called without any parameter, then the current state is returned.
|
|
128
|
+
def state(state_name=nil, &state_block)
|
|
129
|
+
if block_given? then
|
|
130
|
+
self.[](state_name).build &state_block
|
|
131
|
+
else
|
|
132
|
+
self.[](state_name)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
160
135
|
|
|
136
|
+
# It triggers the event_name event and changes the state of the FSM to its new state.
|
|
137
|
+
# The :entry and :exit events are called on the leaving state and the entering state.
|
|
138
|
+
# If the event does not mention the new state, then the state changes to the default state.
|
|
161
139
|
def event(event_name)
|
|
162
140
|
@states[@state].event :exit
|
|
163
141
|
new_state = @states[@state].event event_name
|
|
@@ -168,5 +146,15 @@ module FSM
|
|
|
168
146
|
@states[@state].event :enter
|
|
169
147
|
end
|
|
170
148
|
|
|
149
|
+
# The #build/#run method sets up the states and events as given in the build_block.
|
|
150
|
+
# Only state and event methods are supported within the build_block with the name of the state/event and a block supplied.
|
|
151
|
+
# The operation for each such line is carried out by the #state/#event method.
|
|
152
|
+
def build(&build_block)
|
|
153
|
+
self.instance_eval &build_block
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
alias_method :run, :build
|
|
157
|
+
|
|
171
158
|
end
|
|
172
|
-
|
|
159
|
+
|
|
160
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: barebone-fsm
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.1.
|
|
4
|
+
version: 0.0.1.2
|
|
5
5
|
prerelease:
|
|
6
6
|
platform: ruby
|
|
7
7
|
authors:
|
|
@@ -9,16 +9,18 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2013-01-
|
|
12
|
+
date: 2013-01-19 00:00:00.000000000 Z
|
|
13
13
|
dependencies: []
|
|
14
14
|
description: A barebone implementation of finite state machine keeping simplicity
|
|
15
15
|
in mind.
|
|
16
16
|
email: mihassan@gmail.com
|
|
17
17
|
executables: []
|
|
18
18
|
extensions: []
|
|
19
|
-
extra_rdoc_files:
|
|
19
|
+
extra_rdoc_files:
|
|
20
|
+
- README.rdoc
|
|
20
21
|
files:
|
|
21
22
|
- lib/barebone-fsm.rb
|
|
23
|
+
- README.rdoc
|
|
22
24
|
homepage: http://rubygems.org/gems/barebone-fsm
|
|
23
25
|
licenses: []
|
|
24
26
|
post_install_message:
|