fsm 0.0.0

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.
@@ -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