simplefsm 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|