fsm 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,188 @@
1
+ class DotGraphPrinter
2
+ attr_accessor :orientation, :size, :color
3
+
4
+ # The following can be set to blocks of code that gives a default
5
+ # value for the node shapes, node labels and link labels, respectively.
6
+ attr_accessor :node_shaper, :node_labeler, :link_labeler
7
+
8
+ # A node shaper maps each node to a string describing its shape.
9
+ # Valid shapes are:
10
+ # "ellipse" (default)
11
+ # "box"
12
+ # "circle"
13
+ # "plaintext" (no outline)
14
+ # "doublecircle"
15
+ # "diamond"
16
+ # Not yet supported or untested once are:
17
+ # "polygon", "record", "epsf"
18
+ @@default_node_shaper = proc{|n| "box"}
19
+
20
+ @@default_node_labeler = proc{|n|
21
+ if Symbol===n
22
+ n.id2name
23
+ elsif String===n
24
+ n
25
+ else
26
+ n.inspect
27
+ end
28
+ }
29
+
30
+ @@default_link_labeler = proc{|info| info ? info.inspect : nil}
31
+
32
+ # links is either array of
33
+ # arrays [fromNode, toNode [, infoOnLink]], or
34
+ # objects with attributes :from, :to, :info
35
+ # nodes is array of node objects
36
+ # All nodes used in the links are used as nodes even if they are not
37
+ # in the "nodes" parameters.
38
+ def initialize(links = [], nodes = [])
39
+ @links, @nodes = links, add_nodes_in_links(links, nodes)
40
+ @node_attributes, @edge_attributes = Hash.new, Hash.new
41
+ set_default_values
42
+ end
43
+
44
+ def set_default_values
45
+ @color = "black"
46
+ @size = "9,11"
47
+ @orientation = "portrait"
48
+ @node_shaper = @@default_node_shaper
49
+ @node_labeler = @@default_node_labeler
50
+ @link_labeler = @@default_link_labeler
51
+ end
52
+
53
+ def write_to_file(filename, fileType = "ps")
54
+ dotfile = temp_filename(filename)
55
+ File.open(dotfile, "w") {|f| f.write to_dot_specification}
56
+ system "dot -T#{fileType} -o #{filename} #{dotfile}"
57
+ File.delete(dotfile)
58
+ end
59
+
60
+ def set_edge_attributes(anEdge, aHash)
61
+ # TODO check if attributes are valid dot edge attributes
62
+ edge = find_edge(anEdge)
63
+ set_attributes(edge, @edge_attributes, true, aHash)
64
+ end
65
+
66
+ def set_node_attributes(aNode, aHash)
67
+ # TODO check if attributes are valid dot node attributes
68
+ set_attributes(aNode, @node_attributes, true, aHash)
69
+ end
70
+
71
+ def to_dot_specification
72
+ set_edge_labels(@links)
73
+ set_node_labels_and_shape(@nodes)
74
+ "digraph G {\n" +
75
+ graph_parameters_to_dot_specification +
76
+ @nodes.uniq.map {|n| format_node(n)}.join(";\n") + ";\n" +
77
+ @links.uniq.map {|l| format_link(l)}.join(";\n") + ";\n" +
78
+ "}"
79
+ end
80
+
81
+ protected
82
+
83
+ def find_edge(anEdge)
84
+ @links.each do |link|
85
+ return link if source_and_dest(link) == source_and_dest(anEdge)
86
+ end
87
+ end
88
+
89
+ def set_attributes(key, hash, override, newAttributeHash)
90
+ h = hash[key] || Hash.new
91
+ newAttributeHash = all_keys_to_s(newAttributeHash)
92
+ newAttributeHash.each do |k, value|
93
+ h[k] = value unless h[k] and !override
94
+ end
95
+ hash[key] = h
96
+ end
97
+
98
+ def graph_parameters_to_dot_specification
99
+ "graph [\n" +
100
+ (self.size ? " size = #{@size.inspect},\n" : "") +
101
+ (self.orientation ? " orientation = #{@orientation},\n" : "") +
102
+ (self.color ? " color = #{@color}\n" : "") +
103
+ "]\n"
104
+
105
+ <<-dot_spec
106
+ graph[
107
+ rankdir = LR
108
+ ]
109
+ dot_spec
110
+ end
111
+
112
+ def each_node_in_links(links)
113
+ links.each do |l|
114
+ src, dest = source_and_dest(l)
115
+ yield src
116
+ yield dest
117
+ end
118
+ end
119
+
120
+ def add_nodes_in_links(links, nodes)
121
+ new_nodes = []
122
+ each_node_in_links(links) {|node| new_nodes.push node}
123
+ (nodes + new_nodes).uniq
124
+ end
125
+
126
+ def all_keys_to_s(aHash)
127
+ # MAYBE reuse existing hash?
128
+ Hash[*(aHash.map{|p| p[0] = p[0].to_s; p}.flatten)]
129
+ end
130
+
131
+ def set_edge_labels(edges)
132
+ edges.each do |edge|
133
+ src, dest, info = get_link_data(edge)
134
+ if info
135
+ label = @link_labeler.call(info)
136
+ set_attributes(edge, @edge_attributes, false, :label =>label) if label
137
+ end
138
+ end
139
+ end
140
+
141
+ def set_node_labels_and_shape(nodes)
142
+ nodes.each do |node|
143
+ set_attributes(node, @node_attributes, false,
144
+ :label => @node_labeler.call(node).inspect,
145
+ :shape => @node_shaper.call(node).inspect)
146
+ end
147
+ end
148
+
149
+ def get_link_data(link)
150
+ begin
151
+ return link.from, link.to, link.info
152
+ rescue Exception
153
+ return link[0], link[1], link[2]
154
+ end
155
+ end
156
+
157
+ def source_and_dest(link)
158
+ get_link_data(link)[0,2]
159
+ end
160
+
161
+ def format_attributes(attributes)
162
+ return "" unless attributes
163
+ strings = attributes.map {|a, v| "#{a}=#{v}"}
164
+ strings.length > 0 ? (" [" + strings.join(", ") + "]") : ("")
165
+ end
166
+
167
+ def mangle_node_name(node)
168
+ "n" + node.hash.abs.inspect
169
+ end
170
+
171
+ def format_link(link)
172
+ from, to, info = get_link_data(link)
173
+ mangle_node_name(from) + " -> " + mangle_node_name(to) +
174
+ format_attributes(@edge_attributes[link])
175
+ end
176
+
177
+ def format_node(node)
178
+ mangle_node_name(node) + format_attributes(@node_attributes[node])
179
+ end
180
+
181
+ def temp_filename(base = "tmp")
182
+ tmpfile = base + rand(100000).inspect
183
+ while test(?f, tmpfile)
184
+ tmpfile = base + rand(100000).inspect
185
+ end
186
+ tmpfile
187
+ end
188
+ end
@@ -0,0 +1,194 @@
1
+ unless defined? $__fsm_observer__
2
+ $__fsm_observer__ = __FILE__
3
+
4
+ module FSM
5
+ FSM::LIBDIR =
6
+ File::dirname(File::expand_path(__FILE__)) + File::SEPARATOR unless
7
+ defined? FSM::LIBDIR
8
+
9
+ FSM::INCDIR =
10
+ File::dirname(FSM::LIBDIR) + File::SEPARATOR unless
11
+ defined? FSM::INCDIR
12
+
13
+ require INCDIR + 'fsm'
14
+
15
+ class Observer
16
+ include Util
17
+
18
+ ANY = const 'ANY'
19
+
20
+ tattrs %w[
21
+ fsm
22
+ hooks
23
+ q
24
+ thread
25
+ dsl
26
+ ]
27
+
28
+ def initialize fsm, &b
29
+ sync_initialize
30
+
31
+ @fsm = fsm
32
+ @fsm.add_observer self
33
+
34
+ @hooks = Hash.new{|etype_h, etype|
35
+ etype_h[etype] = Hash.new{|fstate_h, fstate|
36
+ fstate_h[fstate] = Array.new
37
+ }
38
+ }
39
+
40
+ @q = Queue.new
41
+
42
+ @thread = Thread.new{
43
+ Thread.current.abort_on_exception = true
44
+ loop{
45
+ b, *a = @q.pop
46
+ break unless b
47
+ bcall b, *a
48
+ }
49
+ }
50
+
51
+ @dsl = DSL.new self
52
+ configure &b if b
53
+ end
54
+
55
+ def configure &b
56
+ ex{
57
+ @dsl.configure &b
58
+ }
59
+ end
60
+
61
+ def defer hook, *a
62
+ ex{
63
+ @q.push [hook, *a]
64
+ }
65
+ end
66
+
67
+ def idefer hook, *a
68
+ ihook = lambda{|*a| instance_exec *a, &hook }
69
+ defer ihook, *a
70
+ end
71
+
72
+ def on etype = Event::Any, fstate = ANY, &hook
73
+ ex{
74
+ idx = (@hooks[etype][fstate] << hook).size - 1
75
+ }
76
+ end
77
+
78
+ def on_entry *a, &b
79
+ on Event::Entry, *a, &b
80
+ end
81
+
82
+ def on_transition *a, &b
83
+ on Event::Transition, *a, &b
84
+ end
85
+
86
+ def on_exit *a, &b
87
+ on Event::Exit, *a, &b
88
+ end
89
+
90
+ def on_input *a, &b
91
+ on Event::Input, *a, &b
92
+ end
93
+
94
+ module Once; end
95
+
96
+ def once_on etype = Event::Any, fstate = ANY, &hook
97
+ on etype, fstate, &hook.extend(Once)
98
+ end
99
+
100
+ def once_on_entry *a, &b
101
+ once_on Event::Entry, *a, &b
102
+ end
103
+
104
+ def once_on_transition *a, &b
105
+ once_on Event::Transition, *a, &b
106
+ end
107
+
108
+ def once_on_exit *a, &b
109
+ once_on Event::Exit, *a, &b
110
+ end
111
+
112
+ def once_on_input *a, &b
113
+ once_on Event::Input, *a, &b
114
+ end
115
+
116
+ def wait_for_entry fstate, &hook
117
+ argv = [fstate]
118
+ unless fsm.state == fstate
119
+ q = Queue.new
120
+ once_on(Event::Entry, fstate){|*a| q.push a }
121
+ argv.replace q.pop
122
+ end
123
+ icall hook, *argv if hook
124
+ end
125
+ alias_method 'wait_for', 'wait_for_entry'
126
+
127
+ def wait_for_transition fstate, &hook
128
+ argv = [fstate]
129
+ unless fsm.state == fstate
130
+ q = Queue.new
131
+ once_on(Event::Transition, fstate){|*a| q.push a }
132
+ argv.replace q.pop
133
+ end
134
+ icall hook, *argv if hook
135
+ end
136
+
137
+ def wait_for_exit fstate, &hook
138
+ argv = [fstate]
139
+ unless fsm.state == fstate
140
+ q = Queue.new
141
+ once_on(Event::Exit, fstate){|*a| q.push a }
142
+ argv.replace q.pop
143
+ end
144
+ icall hook, *argv if hook
145
+ end
146
+
147
+ def wait_for_input fstate, &hook
148
+ argv = [fstate]
149
+ unless fsm.state == fstate
150
+ q = Queue.new
151
+ once_on(Event::Input, fstate){|*a| q.push a }
152
+ argv.replace q.pop
153
+ end
154
+ icall hook, *argv if hook
155
+ end
156
+
157
+ def fsm_event event
158
+ ex{
159
+ [event.type, Event::Any].each do |etype|
160
+ [event.state, ANY].each do |estate|
161
+ #@hooks[etype][estate].each{|hook| idefer hook, event}
162
+ hooks = []
163
+ while((hook = @hooks[etype][estate].shift))
164
+ idefer hook, event
165
+ hooks.push hook unless hook.is_a? Once
166
+ end
167
+ @hooks[etype][estate].replace hooks
168
+ end
169
+ end
170
+ }
171
+ end
172
+
173
+ def join
174
+ @thread.join
175
+ end
176
+
177
+ def kill
178
+ @thread.kill
179
+ end
180
+
181
+ alias_method 'stop', 'kill'
182
+
183
+ delegate %w[
184
+ start
185
+ transition
186
+ input
187
+ display
188
+ plot
189
+ state
190
+ ] => '@fsm'
191
+
192
+ end # class Observer
193
+ end # module FSM
194
+ end
@@ -0,0 +1,65 @@
1
+ unless defined? $__fsm_system__
2
+ $__fsm_system__ = __FILE__
3
+
4
+ module FSM
5
+ FSM::LIBDIR =
6
+ File::dirname(File::expand_path(__FILE__)) + File::SEPARATOR unless
7
+ defined? FSM::LIBDIR
8
+
9
+ FSM::INCDIR =
10
+ File::dirname(FSM::LIBDIR) + File::SEPARATOR unless
11
+ defined? FSM::INCDIR
12
+
13
+ require INCDIR + 'fsm'
14
+
15
+ class System
16
+ include Util
17
+
18
+ %w[
19
+ fsm
20
+ observer
21
+ ].each{|a| attr a}
22
+
23
+ def initialize fsm = FSM.new, &b
24
+ @fsm = fsm
25
+ @observer = Observer.new @fsm
26
+ @dsl = DSL.new self
27
+ configure &b if b
28
+ end
29
+
30
+ def configure &b
31
+ @dsl.configure &b
32
+ end
33
+
34
+ delegate %w[
35
+ graph
36
+ state_attributes
37
+ state
38
+ subscribers
39
+ subscribe
40
+ inspect
41
+ start
42
+ add_observer
43
+ transition
44
+ traverse
45
+ input
46
+ add_state
47
+ add_transition
48
+ plot
49
+ to_dot
50
+ display
51
+ ] => '@fsm'
52
+
53
+ delegate %w[
54
+ on
55
+ on_entry
56
+ on_transition
57
+ on_exit
58
+ on_input
59
+ join
60
+ kill
61
+ stop
62
+ ] => '@observer'
63
+ end # class System
64
+ end # module FSM
65
+ end
@@ -0,0 +1,172 @@
1
+ unless defined? $__fsm_util__
2
+ $__fsm_util__ = __FILE__
3
+
4
+ module FSM
5
+ FSM::LIBDIR =
6
+ File::dirname(File::expand_path(__FILE__)) + File::SEPARATOR unless
7
+ defined? FSM::LIBDIR
8
+
9
+ FSM::INCDIR =
10
+ File::dirname(FSM::LIBDIR) + File::SEPARATOR unless
11
+ defined? FSM::INCDIR
12
+
13
+ require INCDIR + 'fsm'
14
+
15
+ module Util
16
+ class Const
17
+ def self.new s
18
+ s = s.to_s
19
+ (@instances ||= {})[s] ||= super(s)
20
+ end
21
+
22
+ attr 'to_s'
23
+ alias_method 'to_str', 'to_s'
24
+ alias_method 'inspect', 'to_s'
25
+
26
+ def initialize s
27
+ @to_s = s.to_s
28
+ freeze
29
+ end
30
+ end
31
+
32
+ module Methods
33
+ include Sync_m
34
+
35
+ def const(*a)
36
+ Const.new(*a)
37
+ end
38
+
39
+ def string_list *list
40
+ list.flatten.map{|elem| elem.to_s}
41
+ end
42
+
43
+ def string *list
44
+ string_list(*list).first
45
+ end
46
+
47
+ def klass
48
+ self.class
49
+ end
50
+
51
+ def bcall b, *a
52
+ arity = b.arity
53
+ if arity > 0
54
+ a = a[0...arity]
55
+ elsif arity < 0
56
+ arity = arity.abs
57
+ a = a[0...arity] + (a[arity..-1] || [])
58
+ else
59
+ a.clear
60
+ end
61
+ b.call *a
62
+ end
63
+
64
+ def mcall obj, msg, *a
65
+ m = obj.method(msg).to_proc
66
+ bcall m, *a
67
+ end
68
+
69
+ def icall b, *a
70
+ arity = b.arity
71
+ if arity > 0
72
+ a = a[0...arity]
73
+ elsif arity < 0
74
+ arity = arity.abs
75
+ a = a[0...arity] + (a[arity..-1] || [])
76
+ else
77
+ a.clear
78
+ end
79
+ instance_exec *a, &b
80
+ end
81
+
82
+ def system *a, &b
83
+ verbose = $VERBOSE
84
+ $VERBOSE = nil
85
+ ::Kernel.system *a, &b
86
+ ensure
87
+ $VERBOSE = verbose
88
+ end
89
+
90
+ unless instance_methods.include? 'instance_exec'
91
+ def instance_exec *a, &b
92
+ m, n = nil, -1
93
+ loop{
94
+ m = "__instance_exec_#{ Thread.current.object_id.abs }_#{ n += 1 }__"
95
+ break unless respond_to? m
96
+ }
97
+ singleton_class{ define_method m, &b }
98
+ send m, *a
99
+ ensure
100
+ singleton_class{ undef_method m }
101
+ end
102
+ end
103
+
104
+ unless instance_methods.include? 'singleton_class'
105
+ def singleton_class &b
106
+ sc =
107
+ class << self; self; end
108
+ sc.module_eval &b if b
109
+ sc
110
+ end
111
+ end
112
+
113
+ def sh(&b) synchronize(:SH, &b) end
114
+ def ex(&b) synchronize(:EX, &b) end
115
+
116
+
117
+ def initialize(*a, &b)
118
+ sync_initialize
119
+ end
120
+ alias_method 'sync_init', 'sync_initialize'
121
+
122
+ end
123
+
124
+ module ClassMethods
125
+ include Methods
126
+
127
+ def tattrs *ms
128
+ ms.flatten.each do |m|
129
+ module_eval <<-code
130
+ def #{ m }(*a, &b)
131
+ if a.empty?
132
+ sh{ @#{ m } }
133
+ else
134
+ self.#{ m }= a.shift
135
+ end
136
+ end
137
+ alias_method '#{ m }?', '#{ m }'
138
+ def #{ m }= val
139
+ ex{ @#{ m } = val }
140
+ end
141
+ code
142
+ end
143
+ end
144
+ alias_method 'tattr', 'tattrs'
145
+
146
+ def delegate hash = {}
147
+ methods, to = hash.to_a.first
148
+ methods.each do |m|
149
+ module_eval <<-code
150
+ def #{ m } *a, &b
151
+ #{ to }.#{ m } *a, &b
152
+ end
153
+ code
154
+ end
155
+ end
156
+ end
157
+
158
+ module InstanceMethods
159
+ include Methods
160
+ end
161
+
162
+ def self.included other
163
+ other.extend ClassMethods
164
+ other.module_eval{
165
+ include InstanceMethods
166
+ include Sync_m
167
+ }
168
+ super
169
+ end
170
+ end # module Util
171
+ end # module FSM
172
+ end