simplefsm 0.2.0 → 0.2.1
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.
- checksums.yaml +7 -0
- data/lib/simplefsm.rb +105 -89
- metadata +13 -16
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 838b9cda4f9f7b0f591b871cc616ca400ad394f6
|
4
|
+
data.tar.gz: ae9d5e7ec54dbfb8231dc60268badbcd886101c0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cd41f768c7adc8096bb0b996ab030d7e27dea9b317258eaec4b690462b65b985f787c665f20c0ed9de1f9b09c105ebebc229b53ab2edc9ca0746b88a1eb5e454
|
7
|
+
data.tar.gz: e540da439383cdd2b15becb409ca0e41ddad1d20ed8e3f34f450c2059470171acb2cb367e5cfabcf69ee24d3e6e147f0c22f3f63ca407d4b0b2d48f2c0267a3d
|
data/lib/simplefsm.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# SimpleFSM - a DSL for finite state machines
|
2
2
|
#
|
3
|
-
#
|
4
3
|
# This module provides a domain specific language (DSL) that
|
5
4
|
# can be used to model a finite state machine (FSM) for any domain,
|
6
5
|
# including complex communication applications
|
@@ -12,43 +11,54 @@
|
|
12
11
|
# after the fsm keyword.
|
13
12
|
#
|
14
13
|
# Authors:: Edin Pjanic (mailto:edin.pjanic@untz.com), Amer Hasanovic (mailto:amer.hasanovic@untz.com)
|
15
|
-
# License::
|
14
|
+
# License:: MIT License
|
16
15
|
|
17
16
|
module SimpleFSM
|
18
|
-
VERSION = '0.2.
|
17
|
+
VERSION = '0.2.1'
|
19
18
|
|
19
|
+
# TransitionFactory instance is a temporary helper object
|
20
|
+
# for making transitions from a transitions_for code block parameter
|
21
|
+
class TransitionFactory
|
22
|
+
def initialize
|
23
|
+
@transitions = []
|
24
|
+
end
|
25
|
+
|
26
|
+
def event ev, args
|
27
|
+
t = {:event => ev}.merge!(args)
|
28
|
+
@transitions << t
|
29
|
+
end
|
30
|
+
|
31
|
+
def transitions
|
32
|
+
@transitions
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
#Instance and class methods that are to be injected to the host class
|
20
37
|
def initialize
|
21
|
-
|
38
|
+
self.current_state = {}
|
22
39
|
super
|
23
40
|
end
|
24
41
|
|
25
42
|
# start the machine
|
26
43
|
def run
|
27
|
-
|
28
|
-
if
|
29
|
-
send(
|
44
|
+
self.current_state= @@states.first
|
45
|
+
if current_state[:on]
|
46
|
+
self.send(current_state[:on][:enter], nil) if current_state[:on].has_key?(:enter)
|
30
47
|
end
|
31
48
|
end
|
32
49
|
|
33
|
-
# get the current FSM state name
|
34
|
-
def state
|
35
|
-
@state[:state] #.to_sym
|
36
|
-
end
|
37
|
-
|
38
50
|
# injecting the class methods for FSM definition
|
39
51
|
def self.included klass
|
40
52
|
klass.class_eval do
|
41
53
|
@@states ||= []
|
42
54
|
@@events ||= []
|
43
55
|
@@transitions ||= {}
|
44
|
-
@@current_state_setup = nil
|
45
56
|
|
46
57
|
def self.fsm (&block)
|
47
58
|
instance_eval(&block)
|
48
59
|
|
49
60
|
#Events methods definition
|
50
61
|
# - one method is defined for every event specified
|
51
|
-
|
52
62
|
@@events.each do |ev|
|
53
63
|
Kernel.send :define_method, ev do |*args|
|
54
64
|
fsm_prepare_state args
|
@@ -62,27 +72,27 @@ module SimpleFSM
|
|
62
72
|
args = []
|
63
73
|
end
|
64
74
|
|
65
|
-
if
|
66
|
-
st =
|
75
|
+
if current_state.class == Hash
|
76
|
+
st = current_state[:state]
|
67
77
|
else
|
68
|
-
st =
|
78
|
+
st = current_state
|
69
79
|
end
|
70
80
|
|
71
81
|
statetrans = @@transitions[st]
|
72
82
|
uniquestates = []
|
73
83
|
|
74
84
|
if statetrans
|
75
|
-
#
|
85
|
+
# Get all transitions for this event in the current state
|
76
86
|
trans = statetrans.select{|t| !t.select{|k, v| k==:event and v == ev}.empty?}
|
77
87
|
|
78
88
|
if trans and trans.size>0
|
79
|
-
#
|
89
|
+
# Index of the first transition that is triggered
|
80
90
|
index_triggered = trans.index do |v|
|
81
91
|
# Guard specifiers:
|
82
92
|
# :guard - all must be true
|
83
93
|
# :guard_not - all must be false
|
84
94
|
# :guard_or - at least one must be true
|
85
|
-
# All guard
|
95
|
+
# All guard specifiers must evaluate to true
|
86
96
|
# in order for transition to be triggered.
|
87
97
|
guard_all = true
|
88
98
|
guards_and = []
|
@@ -101,18 +111,21 @@ module SimpleFSM
|
|
101
111
|
guards_not.flatten!
|
102
112
|
end
|
103
113
|
|
104
|
-
|
105
|
-
guard_all &&=
|
106
|
-
guard_all &&=
|
114
|
+
# TODO: think again about those guards
|
115
|
+
guard_all &&= guards_and.all? {|g| self.send(g, args) } if guards_and.size > 0
|
116
|
+
guard_all &&= guards_or.any? {|g| self.send(g, args) } if guards_or.size > 0
|
117
|
+
guard_all &&= !guards_not.any? {|g| self.send(g, args) } if guards_not.size > 0
|
107
118
|
guard_all
|
108
119
|
end
|
109
120
|
if index_triggered
|
110
121
|
trans_triggered = trans[index_triggered]
|
111
122
|
new_state = trans_triggered[:new] if trans_triggered.has_key?(:new)
|
112
123
|
|
113
|
-
#START of :action keyword
|
124
|
+
#START of :action keyword
|
125
|
+
# Call procs for the current event
|
114
126
|
# :do keyword - is not prefered
|
115
127
|
# because it confuses source code editors
|
128
|
+
# :action is a prefered one
|
116
129
|
action_keys = ['do'.to_sym, :action]
|
117
130
|
|
118
131
|
doprocs = []
|
@@ -121,7 +134,7 @@ module SimpleFSM
|
|
121
134
|
end
|
122
135
|
doprocs.flatten!
|
123
136
|
|
124
|
-
doprocs.each {|p| send(p, args)} if doprocs.size > 0
|
137
|
+
doprocs.each {|p| self.send(p, args)} if doprocs.size > 0
|
125
138
|
#END of :action keyword
|
126
139
|
|
127
140
|
do_transform new_state, args
|
@@ -160,72 +173,56 @@ module SimpleFSM
|
|
160
173
|
#add state in case it haven't been defined
|
161
174
|
add_state_data sname
|
162
175
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
return
|
176
|
+
if block_given?
|
177
|
+
tf = TransitionFactory.new
|
178
|
+
tf.send :define_singleton_method, :yield_block, &block
|
179
|
+
tf.yield_block
|
180
|
+
trans << tf.transitions
|
181
|
+
trans.flatten!
|
170
182
|
end
|
183
|
+
trans.each{ |t| check_transition t, sname }
|
171
184
|
|
172
185
|
trans.each do |t|
|
173
|
-
|
174
|
-
raise "Error in transitions for :#{sname} in \'#{t.inspect}\'." +
|
175
|
-
"Transition must be a Hash Array."
|
176
|
-
return
|
177
|
-
end
|
178
|
-
|
179
|
-
add_transition sname, t
|
180
|
-
|
186
|
+
add_transition t, sname
|
181
187
|
@@events << t[:event] if !@@events.any? { |e| t[:event] == e }
|
182
188
|
end
|
183
189
|
|
184
|
-
|
185
|
-
if block_given?
|
186
|
-
@@current_state_setup = sname
|
187
|
-
yield
|
188
|
-
@@current_state_setup = nil
|
189
|
-
end
|
190
|
+
end
|
190
191
|
|
192
|
+
# event keyword
|
193
|
+
# returns transition that is to be added inside transitions_for method
|
194
|
+
def self.event ev, args #, &block
|
195
|
+
return {:event => ev}.merge!(args)
|
191
196
|
end
|
192
197
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
return
|
198
|
+
## Private class methods ######################
|
199
|
+
# Check whether given transition is valid
|
200
|
+
def self.check_transition tran, st='unknown'
|
201
|
+
ev = tran[:event] if tran.is_a?(Hash) and tran.has_key?(:event)
|
202
|
+
ev ||= "unknown"
|
199
203
|
|
200
|
-
|
201
|
-
if !args.has_key?(:new)
|
202
|
-
raise "Error in transitions for :#{sname}." +
|
203
|
-
"Transition MUST contain keys :event and :new.\n" +
|
204
|
-
"In: " + erroneous_trans.inspect
|
205
|
-
return
|
206
|
-
end
|
207
|
-
|
208
|
-
t = {:event => ev}.merge!(args)
|
204
|
+
if !tran or !tran.is_a?(Hash) or !tran.has_key?(:event) or !tran.has_key?(:new)
|
209
205
|
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
206
|
+
raise "Error in transition specification for event '#{ev}' of state '#{st}'.\n" +
|
207
|
+
"\t-> Transition MUST be a Hash and at least MUST contain both keywords 'event' and 'new'.\n" +
|
208
|
+
"\t-> Transition data: #{tran}.\n"
|
209
|
+
return
|
214
210
|
end
|
215
211
|
end
|
216
212
|
|
217
|
-
## private class methods ######################
|
218
|
-
|
219
213
|
# Add transition to state's transitions if it does not exist
|
220
|
-
def self.add_transition
|
214
|
+
def self.add_transition t, st
|
221
215
|
if !@@transitions[st].any? {|v| v == t}
|
222
216
|
@@transitions[st] << t
|
217
|
+
|
218
|
+
#add the state to @@states if it does not exist
|
223
219
|
add_state_data t[:new]
|
224
220
|
end
|
225
221
|
|
226
222
|
@@events << t[:event] if !@@events.any? { |e| t[:event] == e }
|
227
223
|
end
|
228
224
|
|
225
|
+
|
229
226
|
def self.add_state_data sname, data={:on=>nil}, overwrite=false
|
230
227
|
return if !sname
|
231
228
|
symname = sname.to_sym
|
@@ -237,19 +234,19 @@ module SimpleFSM
|
|
237
234
|
end
|
238
235
|
end
|
239
236
|
|
240
|
-
|
241
237
|
private_class_method :fsm, :state, :transitions_for, :event
|
238
|
+
private_class_method :add_state_data , :add_transition, :check_transition
|
242
239
|
|
243
|
-
private_class_method :add_state_data, :add_transition
|
244
240
|
|
245
241
|
end
|
246
242
|
end
|
247
243
|
|
244
|
+
# Private instance methods
|
248
245
|
private
|
249
246
|
|
250
247
|
# perform transition from current to the next state
|
251
248
|
def do_transform sname, args
|
252
|
-
# if new state is nil
|
249
|
+
# if new state is nil then don't change state
|
253
250
|
return if !sname
|
254
251
|
|
255
252
|
symname = sname.to_sym
|
@@ -259,14 +256,15 @@ module SimpleFSM
|
|
259
256
|
return
|
260
257
|
end
|
261
258
|
|
262
|
-
onexit =
|
259
|
+
# onexit, onenter = nil, nil
|
260
|
+
onexit = current_state[:on][:exit] if current_state.has_key?(:on) and current_state[:on] and current_state[:on].has_key?(:exit)
|
263
261
|
if newstate[:on]
|
264
262
|
onenter = newstate[:on][:enter] if newstate[:on].has_key?(:enter)
|
265
263
|
end
|
266
264
|
|
267
|
-
send(onexit, args) if onexit
|
268
|
-
|
269
|
-
send(onenter, args) if onenter
|
265
|
+
self.send(onexit, args) if onexit
|
266
|
+
self.current_state = newstate
|
267
|
+
self.send(onenter, args) if onenter
|
270
268
|
end
|
271
269
|
|
272
270
|
def fsm_responds_to? ev
|
@@ -281,31 +279,49 @@ module SimpleFSM
|
|
281
279
|
get_state_events(st).any?{|e| e == ev}
|
282
280
|
end
|
283
281
|
|
284
|
-
def get_state_events
|
282
|
+
def get_state_events st
|
285
283
|
ev = []
|
286
|
-
tr = @@transitions[
|
287
|
-
if tr
|
288
|
-
|
289
|
-
|
290
|
-
else
|
291
|
-
[]
|
292
|
-
end
|
284
|
+
tr = @@transitions[st[:state]]
|
285
|
+
ev << tr.map{|tran| tran[:event]} if tr
|
286
|
+
ev.uniq!
|
287
|
+
ev
|
293
288
|
end
|
294
289
|
|
290
|
+
# PLEASE OVERRIDE !
|
291
|
+
# ###################################################################
|
295
292
|
# The following methods should be overriden according to the application.
|
296
293
|
#
|
297
294
|
# They are called when any event is fired in order
|
298
|
-
# to perform state loading/saving
|
295
|
+
# to perform state loading/saving in case state is saved in
|
299
296
|
# an external database or similar facility.
|
300
297
|
#
|
301
|
-
#
|
302
|
-
#
|
303
|
-
#
|
304
|
-
|
298
|
+
# #current_state is a private accessor that returns a full state object
|
299
|
+
# #state is a public method that returns only the sate's name
|
300
|
+
# #fsm_prepare_state method is called before and
|
301
|
+
# #fsm_save_state method is called after actual state transition and all consequent actions.
|
302
|
+
|
303
|
+
def current_state
|
305
304
|
@state
|
306
305
|
end
|
307
306
|
|
307
|
+
def current_state= (st)
|
308
|
+
@state = st
|
309
|
+
end
|
310
|
+
|
311
|
+
def state
|
312
|
+
current_state[:state] #.to_sym
|
313
|
+
end
|
314
|
+
|
315
|
+
def fsm_prepare_state args
|
316
|
+
current_state
|
317
|
+
end
|
318
|
+
|
308
319
|
def fsm_save_state args
|
309
|
-
|
320
|
+
current_state
|
310
321
|
end
|
322
|
+
|
323
|
+
public :state
|
324
|
+
private :current_state, :current_state=
|
325
|
+
private :fsm_prepare_state, :fsm_save_state
|
326
|
+
|
311
327
|
end
|
metadata
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simplefsm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
5
|
-
prerelease:
|
4
|
+
version: 0.2.1
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Edin Pjanic
|
9
8
|
- Amer Hasanovic
|
10
|
-
autorequire:
|
9
|
+
autorequire:
|
11
10
|
bindir: bin
|
12
11
|
cert_chain: []
|
13
|
-
date:
|
12
|
+
date: 2017-04-16 00:00:00.000000000 Z
|
14
13
|
dependencies: []
|
15
|
-
description: A simple and lightweight domain specific language (DSL) for modeling
|
14
|
+
description: A simple and lightweight domain specific language (DSL) for modeling
|
15
|
+
finite state machines (FSM).
|
16
16
|
email:
|
17
17
|
- edin@ictlab.com.ba
|
18
18
|
- amer@ictlab.com.ba
|
@@ -23,7 +23,8 @@ files:
|
|
23
23
|
- lib/simplefsm.rb
|
24
24
|
homepage: http://github.com/edictlab/SimpleFSM
|
25
25
|
licenses: []
|
26
|
-
|
26
|
+
metadata: {}
|
27
|
+
post_install_message:
|
27
28
|
rdoc_options: []
|
28
29
|
require_paths:
|
29
30
|
- lib
|
@@ -31,20 +32,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
31
32
|
requirements:
|
32
33
|
- - ">="
|
33
34
|
- !ruby/object:Gem::Version
|
34
|
-
version:
|
35
|
-
MA==
|
36
|
-
none: false
|
35
|
+
version: '0'
|
37
36
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
38
37
|
requirements:
|
39
38
|
- - ">="
|
40
39
|
- !ruby/object:Gem::Version
|
41
|
-
version:
|
42
|
-
MA==
|
43
|
-
none: false
|
40
|
+
version: '0'
|
44
41
|
requirements: []
|
45
|
-
rubyforge_project:
|
46
|
-
rubygems_version:
|
47
|
-
signing_key:
|
48
|
-
specification_version:
|
42
|
+
rubyforge_project:
|
43
|
+
rubygems_version: 2.5.1
|
44
|
+
signing_key:
|
45
|
+
specification_version: 4
|
49
46
|
summary: SimpleFSM - a Ruby DSL for FSMs
|
50
47
|
test_files: []
|