stamina 0.4.0 → 0.5.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.
Files changed (116) hide show
  1. data/CHANGELOG.md +22 -5
  2. data/LICENCE.md +2 -2
  3. data/bin/stamina +1 -7
  4. data/lib/stamina.rb +10 -19
  5. metadata +54 -333
  6. data/.gemtest +0 -0
  7. data/Gemfile +0 -2
  8. data/Gemfile.lock +0 -37
  9. data/Manifest.txt +0 -16
  10. data/README.md +0 -78
  11. data/Rakefile +0 -23
  12. data/example/adl/automaton.adl +0 -49
  13. data/example/adl/sample.adl +0 -53
  14. data/example/basic/characteristic_sample.adl +0 -32
  15. data/example/basic/target.adl +0 -9
  16. data/example/competition/31_test.adl +0 -1500
  17. data/example/competition/31_training.adl +0 -1759
  18. data/lib/stamina/abbadingo.rb +0 -2
  19. data/lib/stamina/abbadingo/random_dfa.rb +0 -48
  20. data/lib/stamina/abbadingo/random_sample.rb +0 -146
  21. data/lib/stamina/adl.rb +0 -298
  22. data/lib/stamina/automaton.rb +0 -1263
  23. data/lib/stamina/automaton/complete.rb +0 -36
  24. data/lib/stamina/automaton/equivalence.rb +0 -55
  25. data/lib/stamina/automaton/metrics.rb +0 -78
  26. data/lib/stamina/automaton/minimize.rb +0 -25
  27. data/lib/stamina/automaton/minimize/hopcroft.rb +0 -116
  28. data/lib/stamina/automaton/minimize/pitchies.rb +0 -64
  29. data/lib/stamina/automaton/strip.rb +0 -16
  30. data/lib/stamina/automaton/walking.rb +0 -363
  31. data/lib/stamina/classifier.rb +0 -52
  32. data/lib/stamina/command.rb +0 -45
  33. data/lib/stamina/command/abbadingo_dfa.rb +0 -81
  34. data/lib/stamina/command/abbadingo_samples.rb +0 -40
  35. data/lib/stamina/command/adl2dot.rb +0 -71
  36. data/lib/stamina/command/classify.rb +0 -48
  37. data/lib/stamina/command/help.rb +0 -27
  38. data/lib/stamina/command/infer.rb +0 -141
  39. data/lib/stamina/command/metrics.rb +0 -51
  40. data/lib/stamina/command/robustness.rb +0 -22
  41. data/lib/stamina/command/score.rb +0 -35
  42. data/lib/stamina/errors.rb +0 -23
  43. data/lib/stamina/ext/math.rb +0 -20
  44. data/lib/stamina/induction/blue_fringe.rb +0 -265
  45. data/lib/stamina/induction/commons.rb +0 -156
  46. data/lib/stamina/induction/rpni.rb +0 -186
  47. data/lib/stamina/induction/union_find.rb +0 -377
  48. data/lib/stamina/input_string.rb +0 -123
  49. data/lib/stamina/loader.rb +0 -1
  50. data/lib/stamina/markable.rb +0 -42
  51. data/lib/stamina/sample.rb +0 -267
  52. data/lib/stamina/scoring.rb +0 -213
  53. data/lib/stamina/utils.rb +0 -1
  54. data/lib/stamina/utils/decorate.rb +0 -81
  55. data/lib/stamina/version.rb +0 -14
  56. data/stamina.gemspec +0 -191
  57. data/stamina.noespec +0 -32
  58. data/tasks/debug_mail.rake +0 -78
  59. data/tasks/debug_mail.txt +0 -13
  60. data/tasks/gem.rake +0 -68
  61. data/tasks/spec_test.rake +0 -79
  62. data/tasks/unit_test.rake +0 -77
  63. data/tasks/yard.rake +0 -51
  64. data/test/stamina/abbadingo/random_dfa_test.rb +0 -16
  65. data/test/stamina/abbadingo/random_sample_test.rb +0 -78
  66. data/test/stamina/adl_test.rb +0 -516
  67. data/test/stamina/automaton/classifier_test.rb +0 -259
  68. data/test/stamina/automaton/complete_test.rb +0 -58
  69. data/test/stamina/automaton/equivalence_test.rb +0 -120
  70. data/test/stamina/automaton/metrics_test.rb +0 -36
  71. data/test/stamina/automaton/minimize/hopcroft_test.rb +0 -15
  72. data/test/stamina/automaton/minimize/minimize_test.rb +0 -55
  73. data/test/stamina/automaton/minimize/pitchies_test.rb +0 -15
  74. data/test/stamina/automaton/minimize/rice_edu_10.adl +0 -16
  75. data/test/stamina/automaton/minimize/rice_edu_10.min.adl +0 -13
  76. data/test/stamina/automaton/minimize/rice_edu_13.adl +0 -13
  77. data/test/stamina/automaton/minimize/rice_edu_13.min.adl +0 -7
  78. data/test/stamina/automaton/minimize/should_strip_1.adl +0 -8
  79. data/test/stamina/automaton/minimize/should_strip_1.min.adl +0 -6
  80. data/test/stamina/automaton/minimize/unknown_1.adl +0 -16
  81. data/test/stamina/automaton/minimize/unknown_1.min.adl +0 -12
  82. data/test/stamina/automaton/strip_test.rb +0 -36
  83. data/test/stamina/automaton/to_dot_test.rb +0 -64
  84. data/test/stamina/automaton/walking/dfa_delta_test.rb +0 -39
  85. data/test/stamina/automaton/walking_test.rb +0 -206
  86. data/test/stamina/automaton_additional_test.rb +0 -190
  87. data/test/stamina/automaton_test.rb +0 -1104
  88. data/test/stamina/exit.rb +0 -3
  89. data/test/stamina/induction/blue_fringe_test.rb +0 -83
  90. data/test/stamina/induction/induction_test.rb +0 -70
  91. data/test/stamina/induction/redblue_mergesamestatebug_expected.adl +0 -19
  92. data/test/stamina/induction/redblue_mergesamestatebug_pta.dot +0 -64
  93. data/test/stamina/induction/redblue_mergesamestatebug_sample.adl +0 -9
  94. data/test/stamina/induction/redblue_universal_expected.adl +0 -4
  95. data/test/stamina/induction/redblue_universal_sample.adl +0 -5
  96. data/test/stamina/induction/rpni_inria_expected.adl +0 -7
  97. data/test/stamina/induction/rpni_inria_sample.adl +0 -9
  98. data/test/stamina/induction/rpni_test.rb +0 -129
  99. data/test/stamina/induction/rpni_test_pta.dot +0 -22
  100. data/test/stamina/induction/rpni_universal_expected.adl +0 -4
  101. data/test/stamina/induction/rpni_universal_sample.adl +0 -4
  102. data/test/stamina/induction/union_find_test.rb +0 -124
  103. data/test/stamina/input_string_test.rb +0 -323
  104. data/test/stamina/markable_test.rb +0 -70
  105. data/test/stamina/randdfa.adl +0 -66
  106. data/test/stamina/sample.adl +0 -4
  107. data/test/stamina/sample_classify_test.rb +0 -149
  108. data/test/stamina/sample_test.rb +0 -290
  109. data/test/stamina/scoring_test.rb +0 -63
  110. data/test/stamina/small_dfa.dot +0 -16
  111. data/test/stamina/small_dfa.gif +0 -0
  112. data/test/stamina/small_nfa.dot +0 -18
  113. data/test/stamina/small_nfa.gif +0 -0
  114. data/test/stamina/stamina_test.rb +0 -80
  115. data/test/stamina/utils/decorate_test.rb +0 -65
  116. data/test/test_all.rb +0 -7
@@ -1,1263 +0,0 @@
1
- module Stamina
2
-
3
- #
4
- # Automaton data-structure.
5
- #
6
- # == Examples
7
- # The following example uses a lot of useful DRY shortcuts, so, if it does not
8
- # fit you needs then, read on!):
9
- #
10
- # # Building an automaton for the regular language a(ba)*
11
- # fa = Automaton.new do
12
- # add_state(:initial => true)
13
- # add_state(:accepting => true)
14
- # connect(0,1,'a')
15
- # connect(1,0,'b')
16
- # end
17
- #
18
- # # It accepts 'a b a b a', rejects 'a b' as well as ''
19
- # puts fa.accepts?('? a b a b a') # prints true
20
- # puts fa.accepts?('? a b') # prints false
21
- # puts fa.rejects?('?') # prints true
22
- #
23
- # == Four things you need to know
24
- # 1. Automaton, State and Edge classes implement a Markable design pattern, that
25
- # is, you can read and write any key/value pair you want on them using the []
26
- # and []= operators. Note that the following keys are used by Stamina itself,
27
- # with the obvious semantics (for automata and transducers):
28
- # - <tt>:initial</tt>, <tt>:accepting</tt>, <tt>:error</tt> on State;
29
- # expected to be _true_ or _false_ (_nil_ and ommitted are considered as false).
30
- # Shortcuts for querying and setting these attributes are provided by State.
31
- # - <tt>:symbol</tt> on Edge, with shortcuts as well on Edge.
32
- # The convention is to use _nil_ for the epsilon symbol (aka non observable)
33
- # on non deterministic automata.
34
- # The following keys are reserved for future extensions:
35
- # - <tt>:output</tt> on State and Edge.
36
- # - <tt>:short_prefix</tt> on State.
37
- # See also the "About states and edges" subsection of the design choices.
38
- # 2. Why using State methods State#step and State#delta ? The Automaton class includes
39
- # the Walking module by default, which is much more powerful !
40
- # 3. The constructor of this class executes the argument block (between <tt>do</tt>
41
- # and <tt>end</tt>) with instance_eval by default. You won't be able to invoke
42
- # the methods defined in the scope of your block in such a case. See new
43
- # for details.
44
- # 4. This class has not been designed with efficiency in mind. If you experiment
45
- # performance problems, read the "About Automaton modifications" sub section
46
- # of the design choices.
47
- #
48
- # == Design choices
49
- # This section fully details the design choices that has been made for the
50
- # implementation of the Automaton data structure used by Stamina. It is provided
51
- # because Automaton is one of the core classes of Stamina, that probably all
52
- # users (and contributors) will use. Automaton usage is really user-friendly,
53
- # so <b>you are normally not required</b> to read this section in the first
54
- # place ! Read it only if of interest for you, or if you experiment unexpected
55
- # results.
56
- #
57
- # === One Automaton class only
58
- # One class only implements all kinds of automata: deterministic, non-deterministic,
59
- # transducers, prefix-tree-acceptors, etc. The Markable design pattern on states and
60
- # edges should allow you to make anything you could find useful with this class.
61
- #
62
- # === Adjacency-list graph
63
- # This class implements an automaton using a adjacent-list graph structure.
64
- # The automaton has state and edge array lists and exposes them through the
65
- # _states_ and _edges_ accessors. In order to let users enjoy the enumerability
66
- # of Ruby's arrays while allowing automata to be modified, these arrays are
67
- # externaly modifiable. However, <b>users are not expected to modify them!</b>
68
- # and future versions of Stamina will certainly remove this ability.
69
- #
70
- # === Indices exposed
71
- # State and Edge indices in these arrays are exposed by this class. Unless stated
72
- # explicitely, all methods taking state or edge arguments support indices as well.
73
- # Moreover, ith_state, ith_states, ith_edge and ith_edges methods provide powerful
74
- # access to states and edges by indices. All these methods are robust to invalid
75
- # indices (and raise an IndexError if incorrectly invoked) but do not allow
76
- # negative indexing (unlike ruby arrays).
77
- #
78
- # States and edges know their index in the corresponding array and expose them
79
- # through the (read-only) _index_ accessor. These indices are always valid;
80
- # without deletion of states or edges in the automaton, they are guaranteed not
81
- # to change. Indices saved in your own variables must be considered deprecated
82
- # each time you perform a deletion ! That's the only rule to respect if you plan
83
- # to use indices.
84
- #
85
- # Indices exposition may seem a strange choice and could be interpreted as
86
- # breaking OOP's best practice. You are not required to use them but, as will
87
- # quiclky appear, using them is really powerful and leads to beautiful code!
88
- # If you don't remove any state or edge, this class guarantees that indices
89
- # are assigned in the same order as invocations of add_state and add_edge (as
90
- # well as their plural forms and aliases).
91
- #
92
- # === About states and edges
93
- # Edges know their source and target states, which are exposed through the
94
- # _source_ and _target_ (read-only) accessors (also aliased as _from_ and _to_).
95
- # States keep their incoming and outgoing edges in arrays, which are accessible
96
- # (in fact, a copy) using State#in_edges and State#out_edges. If you use them
97
- # for walking the automaton in a somewhat standard way, consider using the Walking
98
- # module instead!
99
- #
100
- # Common attributes of states and edges are installed using the Markable pattern
101
- # itself:
102
- # - <tt>:initial</tt>, <tt>:accepting</tt> and <tt>:error</tt> on states. These
103
- # attributes are expected to be _true_ or _false_ (_nil_ and ommitted are also
104
- # supported and both considered as false).
105
- # - <tt>:symbol</tt> on edges. Any object you want as long as it responds to the
106
- # <tt><=></tt> operator. Also, the convention is to use _nil_ for the epsilon
107
- # symbol (aka non observable) on non deterministic automata.
108
- #
109
- # In addition, useful shortcuts are available:
110
- # - <tt>s.initial?</tt> is a shortcut for <tt>s[:initial]</tt> if _s_ is a State
111
- # - <tt>s.initial!</tt> is a shortcut for <tt>s[:initial]=true</tt> if _s_ is a State
112
- # - Similar shortcuts are available for :accepting and :error
113
- # - <tt>e.symbol</tt> is a shortcut for <tt>e[:symbol]</tt> if _e_ is an Edge
114
- # - <tt>e.symbol='a'</tt> is a shortcut for <tt>e[:symbol]='a'</tt> if _e_ is an Edge
115
- #
116
- # Following keys should be considered reserved by Stamina for future extensions:
117
- # - <tt>:output</tt> on State and Edge.
118
- # - <tt>:short_prefix</tt> on State.
119
- #
120
- # === About Automaton modifications
121
- # This class has not been implemented with efficiency in mind. In particular, we expect
122
- # the vast majority of Stamina core algorithms considering automata as immutable values.
123
- # For this reason, the Automaton class does not handle modifications really efficiently.
124
- #
125
- # So, if you experiment performance problems, consider what follows:
126
- # 1. Why updating an automaton ? Building a fresh one is much more clean and efficient !
127
- # This is particularly true for removals.
128
- # 2. If you can create multiples states or edges at once, consider the plural form
129
- # of the modification methods: add_n_states and drop_states. Those methods are
130
- # optimized for multiple updates.
131
- #
132
- # == Detailed API
133
- class Automaton
134
- include Stamina::Markable
135
-
136
- #
137
- # Automaton state.
138
- #
139
- class State
140
- include Stamina::Markable
141
- attr_reader :automaton, :index
142
-
143
- #
144
- # Creates a state.
145
- #
146
- # Arguments:
147
- # - automaton: parent automaton of the state.
148
- # - index: index of the state in the state list.
149
- # - data: user data attached to this state.
150
- #
151
- def initialize(automaton, index, data)
152
- @automaton = automaton
153
- @index = index
154
- @data = data.dup
155
- @out_edges = []
156
- @in_edges = []
157
- @epsilon_closure = nil
158
- end
159
-
160
- ### public read-only section ###############################################
161
- public
162
-
163
- #
164
- # Returns true if this state is an initial state, false otherwise.
165
- #
166
- def initial?() return false unless @data[:initial]; @data[:initial] end
167
-
168
- #
169
- # Sets this state as an initial state.
170
- #
171
- def initial!() @data[:initial] = true end
172
-
173
- #
174
- # Returns true if this state is an accepting state, false otherwise.
175
- #
176
- def accepting?() return false unless @data[:accepting]; @data[:accepting] end
177
-
178
- #
179
- # Sets this state as an accepting state.
180
- #
181
- def accepting!() @data[:accepting] = true end
182
-
183
- #
184
- # Returns true if this state is an error state, false otherwise.
185
- #
186
- def error?() return false unless @data[:error]; @data[:error] end
187
-
188
- #
189
- # Sets this state as an error state.
190
- #
191
- def error!() @data[:error] = true end
192
-
193
- #
194
- # Returns true if this state is deterministic, false otherwise.
195
- #
196
- def deterministic?
197
- outs = out_symbols
198
- (outs.size==@out_edges.size) and not(outs.include?(nil))
199
- end
200
-
201
- # Checks if this state is a sink state or not. Sink states are defined as
202
- # non accepting states having no outgoing transition or only loop
203
- # transitions.
204
- def sink?
205
- return false if accepting?
206
- out_edges.each{|e| return false unless e.target==self}
207
- true
208
- end
209
-
210
- #
211
- # Returns an array containing all incoming edges of the state. Edges are
212
- # sorted if _sorted_ is set to true. If two incoming edges have same symbol
213
- # no order is guaranteed between them.
214
- #
215
- # Returned array may be modified.
216
- #
217
- def in_edges(sorted=false)
218
- sorted ? @in_edges.sort : @in_edges.dup
219
- end
220
-
221
- #
222
- # Returns an array containing all outgoing edges of the state. Edges are
223
- # sorted if _sorted_ is set to true. If two outgoing edges have same symbol
224
- # no order is guaranteed between them.
225
- #
226
- # Returned array may be modified.
227
- #
228
- def out_edges(sorted=false)
229
- sorted ? @out_edges.sort : @out_edges.dup
230
- end
231
-
232
- #
233
- # Returns an array with the different symbols appearing on incoming edges.
234
- # Returned array does not contain duplicates. Symbols are sorted in the
235
- # array if _sorted_ is set to true.
236
- #
237
- # Returned array may be modified.
238
- #
239
- def in_symbols(sorted=false)
240
- symbols = @in_edges.collect{|e| e.symbol}.uniq
241
- return sorted ? (symbols.sort &automaton.symbols_comparator) : symbols
242
- end
243
-
244
- #
245
- # Returns an array with the different symbols appearing on outgoing edges.
246
- # Returned array does not contain duplicates. Symbols are sorted in the
247
- # array if _sorted_ is set to true.
248
- #
249
- # Returned array may be modified.
250
- #
251
- def out_symbols(sorted=false)
252
- symbols = @out_edges.collect{|e| e.symbol}.uniq
253
- return sorted ? (symbols.sort &automaton.symbols_comparator) : symbols
254
- end
255
-
256
- #
257
- # Returns an array with adjacent states (in or out edge).
258
- #
259
- # Returned array may be modified.
260
- #
261
- def adjacent_states()
262
- (in_adjacent_states+out_adjacent_states).uniq
263
- end
264
-
265
- #
266
- # Returns an array with adjacent states along an incoming edge (without
267
- # duplicates).
268
- #
269
- # Returned array may be modified.
270
- #
271
- def in_adjacent_states()
272
- (@in_edges.collect {|e| e.source}).uniq
273
- end
274
-
275
- #
276
- # Returns an array with adjacent states along an outgoing edge (whithout
277
- # duplicates).
278
- #
279
- # Returned array may be modified.
280
- #
281
- def out_adjacent_states()
282
- (@out_edges.collect {|e| e.target}).uniq
283
- end
284
-
285
- #
286
- # Returns reachable states from this one with an input _symbol_. Returned
287
- # array does not contain duplicates and may be modified. This method if not
288
- # epsilon symbol aware.
289
- #
290
- def step(symbol)
291
- @out_edges.select{|e| e.symbol==symbol}.collect{|e| e.target}
292
- end
293
-
294
- #
295
- # Returns the state reached from this one with an input _symbol_, or nil if
296
- # no such state. This method is not epsilon symbol aware. Moreover it is
297
- # expected to be used on deterministic states only. If the state is not
298
- # deterministic, the method returns one reachable state if such a state
299
- # exists; which one is returned must be considered non deterministic.
300
- #
301
- def dfa_step(symbol)
302
- @out_edges.each {|e| return e.target if e.symbol==symbol}
303
- nil
304
- end
305
-
306
- #
307
- # Computes the epsilon closure of this state. Epsilon closure is the set of
308
- # all states reached from this one with a <tt>eps*</tt> input (sequence of
309
- # zero or more epsilon symbols). The current state is always contained in
310
- # the epsilon closure. Returns an unsorted array without duplicates; this
311
- # array may not be modified.
312
- #
313
- def epsilon_closure()
314
- @epsilon_closure ||= compute_epsilon_closure(Set.new).to_a.freeze
315
- end
316
-
317
- #
318
- # Internal implementation of epsilon_closure. _result_ is expected to be
319
- # a Set instance, is modified and is the returned value.
320
- #
321
- def compute_epsilon_closure(result)
322
- result << self
323
- step(nil).each do |t|
324
- t.compute_epsilon_closure(result) unless result.include?(t)
325
- end
326
- raise if result.nil?
327
- return result
328
- end
329
-
330
- #
331
- # Computes an array representing the set of states that can be reached from
332
- # this state with a given input _symbol_. Returned array does not contain
333
- # duplicates and may be modified. No particular ordering of states in the
334
- # array is guaranteed.
335
- #
336
- # This method is epsilon symbol aware (represented with nil) on non
337
- # deterministic automata, meaning that it actually computes the set of
338
- # reachable states through strings respecting the <tt>eps* symbol eps*</tt>
339
- # regular expression, where eps is the epsilon symbol.
340
- #
341
- def delta(symbol)
342
- if automaton.deterministic?
343
- target = dfa_delta(symbol)
344
- target.nil? ? [] : [target]
345
- else
346
- # 1) first compute epsilon closure of self
347
- at_epsilon = epsilon_closure
348
-
349
- # 2) now, look where we can go from there
350
- at_espilon_then_symbol = at_epsilon.collect do |s|
351
- s.step(symbol)
352
- end.flatten.uniq
353
-
354
- # 3) look where we can go from there using epsilon
355
- result = at_espilon_then_symbol.collect do |s|
356
- s.epsilon_closure
357
- end.flatten.uniq
358
-
359
- # return result as an array
360
- result
361
- end
362
- end
363
-
364
- #
365
- # Returns the target state that can be reached from this state with _symbol_
366
- # input. Returns nil if no such state exists.
367
- #
368
- # This method is expected to be used on deterministic automata. Unlike delta,
369
- # it returns a State instance (or nil), not an array of states. When used on
370
- # non deterministic automata, it returns a state immediately reachable from
371
- # this state with _symbol_ input, or nil if no such state exists. This
372
- # method is not epsilon aware.
373
- #
374
- def dfa_delta(symbol)
375
- return nil if symbol.nil?
376
- edge = @out_edges.find{|e| e.symbol==symbol}
377
- edge.nil? ? nil : edge.target
378
- end
379
-
380
- #
381
- # Provides comparator of states, based on the index in the automaton state
382
- # list. This method returns nil unless _o_ is a State from the same
383
- # automaton than self.
384
- #
385
- def <=>(o)
386
- return nil unless State===o
387
- return nil unless automaton===o.automaton
388
- return index <=> o.index
389
- end
390
-
391
- # Returns a string representation
392
- def inspect
393
- 's' << @index.to_s
394
- end
395
-
396
- # Returns a string representation
397
- def to_s
398
- 's' << @index.to_s
399
- end
400
-
401
- ### protected write section ################################################
402
- protected
403
-
404
- # Changes the index of this state in the state list. This method is only
405
- # expected to be used by the automaton itself.
406
- def index=(i) @index=i end
407
-
408
- #
409
- # Fired by Loaded when a user data is changed. The message is forwarded to
410
- # the automaton.
411
- #
412
- def state_changed(what, description)
413
- @epsilon_closure = nil
414
- @automaton.send(:state_changed, what, description)
415
- end
416
-
417
- # Adds an incoming edge to the state.
418
- def add_incoming_edge(edge)
419
- @epsilon_closure = nil
420
- @in_edges << edge
421
- end
422
-
423
- # Adds an outgoing edge to the state.
424
- def add_outgoing_edge(edge)
425
- @epsilon_closure = nil
426
- @out_edges << edge
427
- end
428
-
429
- # Adds an incoming edge to the state.
430
- def drop_incoming_edge(edge)
431
- @epsilon_closure = nil
432
- @in_edges.delete(edge)
433
- end
434
-
435
- # Adds an outgoing edge to the state.
436
- def drop_outgoing_edge(edge)
437
- @epsilon_closure = nil
438
- @out_edges.delete(edge)
439
- end
440
-
441
- protected :compute_epsilon_closure
442
- end
443
-
444
- #
445
- # Automaton edge.
446
- #
447
- class Edge
448
- include Stamina::Markable
449
- attr_reader :automaton, :index, :from, :to
450
-
451
- #
452
- # Creates an edge.
453
- #
454
- # Arguments:
455
- # - automaton: parent automaton of the edge.
456
- # - index: index of the edge in the edge list.
457
- # - data: user data attached to this edge.
458
- # - from: source state of the edge.
459
- # - to: target state of the edge.
460
- #
461
- def initialize(automaton, index, data, from, to)
462
- @automaton, @index = automaton, index
463
- @data = data
464
- @from, @to = from, to
465
- end
466
-
467
- # Returns edge symbol.
468
- def symbol()
469
- @data[:symbol]
470
- end
471
-
472
- # Sets edge symbol.
473
- def symbol=(symbol)
474
- @data[:symbol] = symbol
475
- end
476
-
477
- alias :source :from
478
- alias :target :to
479
-
480
- #
481
- # Provides comparator of edges, based on the index in the automaton edge
482
- # list. This method returns nil unless _o_ is an Edge from the same
483
- # automaton than self.
484
- # Once again, this method has nothing to do with equality, it looks at an
485
- # index and ID only.
486
- #
487
- def <=>(o)
488
- return nil unless Edge===o
489
- return nil unless automaton===o.automaton
490
- return index <=> o.index
491
- end
492
-
493
- # Returns a string representation
494
- def inspect
495
- 'e' << @index.to_s
496
- end
497
-
498
- # Returns a string representation
499
- def to_s
500
- 'e' << @index.to_s
501
- end
502
-
503
- ### protected write section ################################################
504
- protected
505
-
506
- # Changes the index of this edge in the edge list. This method is only
507
- # expected to be used by the automaton itself.
508
- def index=(i) @index=i end
509
-
510
- #
511
- # Fired by Loaded when a user data is changed. The message if forwarded to
512
- # the automaton.
513
- #
514
- def state_changed(what, infos)
515
- @automaton.send(:state_changed, what, infos)
516
- end
517
-
518
- end
519
-
520
- ### Automaton class ##########################################################
521
- public
522
-
523
- # State list and edge list of the automaton
524
- attr_reader :states, :edges
525
-
526
- #
527
- # Creates an empty automaton and executes the block passed as argument. The _onself_
528
- # argument dictates the way _block_ is executed:
529
- # - when set to false, the block is executed traditionnally (i.e. using yield).
530
- # In this case, methods invocations must be performed on the automaton object
531
- # passed as block argument.
532
- # - when set to _true_ (by default) the block is executed in the context of the
533
- # automaton itself (i.e. with instance_eval), allowing call of its methods
534
- # without prefixing them by the automaton variable. The automaton still
535
- # passes itself as first block argument. Note that in this case, you won't be
536
- # able to invoke a method defined in the scope of your block.
537
- #
538
- # Example:
539
- # # The DRY way to do:
540
- # Automaton.new do |automaton| # automaton will not be used here, but it is passed
541
- # add_state(:initial => true)
542
- # add_state(:accepting => true)
543
- # connect(0, 1, 'a')
544
- # connect(1, 0, 'b')
545
- #
546
- # # method_in_caller_scope() # commented because not allowed here !!
547
- # end
548
- #
549
- # # The other way:
550
- # Automaton.new(false) do |automaton| # automaton MUST be used here
551
- # automaton.add_state(:initial => true)
552
- # automaton.add_state(:accepting => true)
553
- # automaton.connect(0, 1, 'a')
554
- # automaton.connect(1, 0, 'b')
555
- #
556
- # method_in_caller_scope() # allowed in this variant !!
557
- # end
558
- #
559
- def initialize(onself=true, &block) # :yields: automaton
560
- @states = []
561
- @edges = []
562
- @initials = nil
563
- @alphabet = nil
564
- @deterministic = nil
565
-
566
- # if there's a block, execute it now!
567
- if block_given?
568
- if onself
569
- if RUBY_VERSION >= "1.9.0"
570
- instance_exec(self, &block)
571
- else
572
- instance_eval(&block)
573
- end
574
- else
575
- block.call(self)
576
- end
577
- end
578
- end
579
-
580
- ### public read-only section #################################################
581
- public
582
-
583
- #
584
- # Returns a symbols comparator taking epsilon symbols into account. Comparator
585
- # is provided as Proc instance which is a lambda function.
586
- #
587
- def symbols_comparator
588
- @symbols_comparator ||= Kernel.lambda do |a,b|
589
- if a==b then 0
590
- elsif a.nil? then -1
591
- elsif b.nil? then 1
592
- else a <=> b
593
- end
594
- end
595
- end
596
-
597
- # Returns the number of states
598
- def state_count() @states.size end
599
-
600
- # Returns the number of edges
601
- def edge_count() @edges.size end
602
-
603
- #
604
- # Returns the i-th state of the state list.
605
- #
606
- # Raises:
607
- # - ArgumentError unless i is an Integer
608
- # - IndexError if i is not in [0..state_count)
609
- #
610
- def ith_state(i)
611
- raise(ArgumentError, "Integer expected, #{i} found.", caller)\
612
- unless Integer === i
613
- raise(ArgumentError, "Invalid state index #{i}", caller)\
614
- unless i>=0 and i<state_count
615
- @states[i]
616
- end
617
-
618
- #
619
- # Returns state associated with the supplied state name, throws an exception if no such state can be found.
620
- #
621
- def get_state(name)
622
- raise(ArgumentError, "String expected, #{name} found.", caller)\
623
- unless String === name
624
- result = states.find do |s|
625
- name == s[:name]
626
- end
627
- raise(ArgumentError, "State #{name} was not found", caller)\
628
- if result.nil?
629
- result
630
- end
631
-
632
- #
633
- # Returns the i-th states of the state list.
634
- #
635
- # Raises:
636
- # - ArgumentError unless all _i_ are integers
637
- # - IndexError unless all _i_ are in [0..state_count)
638
- #
639
- def ith_states(*i)
640
- i.collect{|j| ith_state(j)}
641
- end
642
-
643
- #
644
- # Returns the i-th edge of the edge list.
645
- #
646
- # Raises:
647
- # - ArgumentError unless i is an Integer
648
- # - IndexError if i is not in [0..state_count)
649
- #
650
- def ith_edge(i)
651
- raise(ArgumentError, "Integer expected, #{i} found.", caller)\
652
- unless Integer === i
653
- raise(ArgumentError, "Invalid edge index #{i}", caller)\
654
- unless i>=0 and i<edge_count
655
- @edges[i]
656
- end
657
-
658
- #
659
- # Returns the i-th edges of the edge list.
660
- #
661
- # Raises:
662
- # - ArgumentError unless all _i_ are integers
663
- # - IndexError unless all _i_ are in [0..edge_count)
664
- #
665
- def ith_edges(*i)
666
- i.collect{|j| ith_edge(j)}
667
- end
668
-
669
- #
670
- # Calls block for each state of the automaton state list. States are
671
- # enumerated in index order.
672
- #
673
- def each_state() @states.each {|s| yield s if block_given?} end
674
-
675
- #
676
- # Calls block for each edge of the automaton edge list. Edges are
677
- # enumerated in index order.
678
- #
679
- def each_edge() @edges.each {|e| yield e if block_given?} end
680
-
681
- #
682
- # Returns an array with incoming edges of _state_. Edges are sorted by symbols
683
- # if _sorted_ is set to true. If two incoming edges have same symbol, no
684
- # order is guaranteed between them. Returned array may be modified.
685
- #
686
- # If _state_ is an Integer, this method returns the incoming edges of the
687
- # state'th state in the state list.
688
- #
689
- # Raises:
690
- # - IndexError if state is an Integer and state<0 or state>=state_count.
691
- # - ArgumentError if _state_ is not a valid state for this automaton.
692
- #
693
- def in_edges(state, sorted=false) to_state(state).in_edges(sorted) end
694
-
695
- #
696
- # Returns an array with outgoing edges of _state_. Edges are sorted by symbols
697
- # if _sorted_ is set to true. If two incoming edges have same symbol, no
698
- # order is guaranteed between them. Returned array may be modified.
699
- #
700
- # If _state_ is an Integer, this method returns the outgoing edges of the
701
- # state'th state in the state list.
702
- #
703
- # Raises:
704
- # - IndexError if state is an Integer and state<0 or state>=state_count.
705
- # - ArgumentError if state is not a valid state (not a state or not from this
706
- # automaton)
707
- #
708
- def out_edges(state, sorted=false) to_state(state).out_edges(sorted) end
709
-
710
- #
711
- # Returns an array with the different symbols appearing on incoming edges of
712
- # _state_. Returned array does not contain duplicates and may be modified;
713
- # it is sorted if _sorted_ is set to true.
714
- #
715
- # If _state_ is an Integer, this method returns the incoming symbols of the
716
- # state'th state in the state list.
717
- #
718
- # Raises:
719
- # - IndexError if state is an Integer and state<0 or state>=state_count.
720
- # - ArgumentError if _state_ is not a valid state for this automaton.
721
- #
722
- def in_symbols(state, sorted=false) to_state(state).in_symbols(sorted) end
723
-
724
- #
725
- # Returns an array with the different symbols appearing on outgoing edges of
726
- # _state_. Returned array does not contain duplicates and may be modified;
727
- # it is sorted if _sorted_ is set to true.
728
- #
729
- # If _state_ is an Integer, this method returns the outgoing symbols of the
730
- # state'th state in the state list.
731
- #
732
- # Raises:
733
- # - IndexError if state is an Integer and state<0 or state>=state_count.
734
- # - ArgumentError if state is not a valid state (not a state or not from this
735
- # automaton)
736
- #
737
- def out_symbols(state, sorted=false) to_state(state).out_symbols(sorted) end
738
-
739
- #
740
- # Returns an array with adjacent states (along incoming and outgoing edges)
741
- # of _state_. Returned array does not contain duplicates; it may be modified.
742
- #
743
- # If _state_ is an Integer, this method returns the adjacent states of the
744
- # state'th state in the state list.
745
- #
746
- # Raises:
747
- # - IndexError if state is an Integer and state<0 or state>=state_count.
748
- # - ArgumentError if state is not a valid state (not a state or not from this
749
- # automaton)
750
- #
751
- def adjacent_states(state) to_state(state).adjacent_states() end
752
-
753
- #
754
- # Returns an array with adjacent states (along incoming edges) of _state_.
755
- # Returned array does not contain duplicates; it may be modified.
756
- #
757
- # If _state_ is an Integer, this method returns the incoming adjacent states
758
- # of the state'th state in the state list.
759
- #
760
- # Raises:
761
- # - IndexError if state is an Integer and state<0 or state>=state_count.
762
- # - ArgumentError if state is not a valid state (not a state or not from this
763
- # automaton)
764
- #
765
- def in_adjacent_states(state) to_state(state).in_adjacent_states() end
766
-
767
- #
768
- # Returns an array with adjacent states (along outgoing edges) of _state_.
769
- # Returned array does not contain duplicates; it may be modified.
770
- #
771
- # If _state_ is an Integer, this method returns the outgoing adjacent states
772
- # of the state'th state in the state list.
773
- #
774
- # Raises:
775
- # - IndexError if state is an Integer and state<0 or state>=state_count.
776
- # - ArgumentError if state is not a valid state (not a state or not from this
777
- # automaton)
778
- #
779
- def out_adjacent_states(state) to_state(state).out_adjacent_states() end
780
-
781
- #
782
- # Collects all initial states of this Automaton and returns it. Returned array
783
- # does not contain duplicates and may be modified.
784
- #
785
- # This method is epsilon symbol aware (represented with nil) on
786
- # non-deterministic automata, meaning that it actually computes the set of
787
- # reachable states from an initial state through strings respecting the
788
- # <tt>eps*</tt> regular expression, where eps is the epsilon symbol.
789
- #
790
- def initial_states
791
- @initials = compute_initial_states if @initials.nil? or @initials.empty?
792
- @initials
793
- end
794
-
795
- #
796
- # Returns the initial state of the automaton. This method is expected to used
797
- # on deterministic automata only. Unlike initial_states, it returns one State
798
- # instance instead of an Array.
799
- #
800
- # When used with a non deterministic automaton, it returns one of the states
801
- # tagged as initial. Which one is returned must be considered a non
802
- # deterministic choice. This method is not epsilon symbol aware.
803
- #
804
- def initial_state
805
- initial_states[0]
806
- end
807
-
808
- # Internal implementation of initial_states.
809
- def compute_initial_states()
810
- initials = @states.select {|s| s.initial?}
811
- initials.collect{|s| s.epsilon_closure}.flatten.uniq
812
- end
813
-
814
- ### public write section #####################################################
815
- public
816
-
817
- #
818
- # Adds a new state.
819
- #
820
- # Arguments:
821
- # - data: user-data to attach to the state (see Automaton documentation).
822
- #
823
- # Raises:
824
- # - ArgumentError if _data_ is not a valid state data.
825
- #
826
- def add_state(data={})
827
- data = to_valid_state_data(data)
828
-
829
- # create new state, add it to state-list
830
- state = State.new(self, state_count, data)
831
- @states << state
832
-
833
- # let the automaton know that something has changed
834
- state_changed(:state_added, state)
835
-
836
- # return created state
837
- state
838
- end
839
- alias :create_state :add_state
840
-
841
- #
842
- # Adds _n_ new states in the automaton. Created states are returned as an
843
- # ordered array (order of states according to their index in state list).
844
- #
845
- # _data_ is duplicated for each created state.
846
- #
847
- def add_n_states(n, data={})
848
- created = []
849
- n.times do |i|
850
- created << add_state(data.dup)
851
- end
852
- created
853
- end
854
- alias :create_n_states :add_n_states
855
-
856
- #
857
- # Adds a new edge, connecting _from_ and _to_ states of the automaton.
858
- #
859
- # Arguments:
860
- # - from: either a State or a valid state index (Integer).
861
- # - to: either a State or a valid state index (Integer).
862
- # - data: user data to attach to the created edge (see Automaton documentation).
863
- #
864
- # Raises:
865
- # - IndexError if _from_ is an Integer but not in [0..state_count)
866
- # - IndexError if _to_ is an Integer but not in [0..state_count)
867
- # - ArgumentError if _from_ is not a valid state for this automaton.
868
- # - ArgumentError if _to_ is not a valid state for this automaton.
869
- # - ArgumentError if _data_ is not a valid edge data.
870
- #
871
- def add_edge(from, to, data)
872
- from, to, data = to_state(from), to_state(to), to_valid_edge_data(data)
873
-
874
- # create edge, install it, add it to edge-list
875
- edge = Edge.new(self, edge_count, data, from, to)
876
- @edges << edge
877
- from.send(:add_outgoing_edge, edge)
878
- to.send(:add_incoming_edge, edge)
879
-
880
- # let automaton know that something has changed
881
- state_changed(:edge_added, edge)
882
-
883
- # return created edge
884
- edge
885
- end
886
- alias :create_edge :add_edge
887
- alias :connect :add_edge
888
-
889
- # Adds all states and transitions (as copies) from a different automaton.
890
- # Returns the initial state of the added part. In order to ensure that names of
891
- # the new states do not clash with names of existing states, state names may have
892
- # to be removed from added states; this is the case if _clear_names_ is set to true.
893
- # None of the added states are made initial.
894
- def add_automaton(what,clear_names=true)
895
- map_what_self = {}
896
- what.states.each do |state|
897
- map_what_self[state]=add_state(state.data)
898
- map_what_self[state][:name]=nil if clear_names
899
- map_what_self[state][:initial]=false
900
- end
901
- what.edges.each do |edge|
902
- add_edge(map_what_self[edge.from],map_what_self[edge.to],edge.data)
903
- end
904
- map_what_self[what.initial_state]
905
- end
906
-
907
- # Constructs a replica of this automaton and returns a copy.
908
- # This copy can be modified in whatever way without affecting the original
909
- # automaton.
910
- def dup
911
- Automaton.new(false) do |fa|
912
- initial = fa.add_automaton(self,false)
913
- initial[:initial] = true unless initial.nil?
914
- end
915
- end
916
-
917
- #
918
- # Drops a state of the automaton, as well as all connected edges to that state.
919
- # If _state_ is an integer, the state-th state of the state list is removed.
920
- # This method returns the automaton itself.
921
- #
922
- # Raises:
923
- # - IndexError if _edge_ is an Integer but not in [0..edge_count)
924
- # - ArgumentError if _edge_ is not a valid edge for this automaton.
925
- #
926
- def drop_state(state)
927
- state = to_state(state)
928
- # remove edges first: drop_edges ensures that edge list is coherent
929
- drop_edges(*(state.in_edges + state.out_edges).uniq)
930
-
931
- # remove state now and renumber
932
- @states.delete_at(state.index)
933
- state.index.upto(state_count-1) do |i|
934
- @states[i].send(:index=, i)
935
- end
936
- state.send(:index=, -1)
937
-
938
- state_changed(:state_dropped, state)
939
- self
940
- end
941
- alias :delete_state :drop_state
942
-
943
- #
944
- # Drops all states passed as parameter as well as all their connected edges.
945
- # Arguments may be state instances, as well as valid state indices. Duplicates
946
- # are even supported. This method has no effect on the automaton and raises
947
- # an error if some state argument is not valid.
948
- #
949
- # Raises:
950
- # - ArgumentError if one state in _states_ is not a valid state of this
951
- # automaton.
952
- #
953
- def drop_states(*states)
954
- # check states first
955
- states = states.collect{|s| to_state(s)}.uniq.sort
956
- edges = states.collect{|s| (s.in_edges + s.out_edges).uniq}.flatten.uniq.sort
957
-
958
- # Remove all edges, we do not use drop_edges to avoid spending too much
959
- # time reindexing edges. Moreover, we can do it that way because we take
960
- # edges in reverse indexing order (has been sorted previously)
961
- until edges.empty?
962
- edge = edges.pop
963
- edge.source.send(:drop_outgoing_edge,edge)
964
- edge.target.send(:drop_incoming_edge,edge)
965
- @edges.delete_at(edge.index)
966
- edge.send(:index=, -1)
967
- state_changed(:edge_dropped, edge)
968
- end
969
-
970
- # Remove all states, same kind of hack is used
971
- until states.empty?
972
- state = states.pop
973
- @states.delete_at(state.index)
974
- state.send(:index=, -1)
975
- state_changed(:state_dropped, state)
976
- end
977
-
978
- # sanitize state and edge lists
979
- @states.each_with_index {|s,i| s.send(:index=,i)}
980
- @edges.each_with_index {|e,i| e.send(:index=,i)}
981
-
982
- self
983
- end
984
-
985
- #
986
- # Drops an edge in the automaton. If _edge_ is an integer, the edge-th edge
987
- # of the edge list is removed. This method returns the automaton itself.
988
- #
989
- # Raises:
990
- # - IndexError if _edge_ is an Integer but not in [0..edge_count)
991
- # - ArgumentError if _edge_ is not a valid edge for this automaton.
992
- #
993
- def drop_edge(edge)
994
- edge = to_edge(edge)
995
- @edges.delete_at(edge.index)
996
- edge.from.send(:drop_outgoing_edge,edge)
997
- edge.to.send(:drop_incoming_edge,edge)
998
- edge.index.upto(edge_count-1) do |i|
999
- @edges[i].send(:index=, i)
1000
- end
1001
- edge.send(:index=,-1)
1002
- state_changed(:edge_dropped, edge)
1003
- self
1004
- end
1005
- alias :delete_edge :drop_edge
1006
-
1007
- #
1008
- # Drops all edges passed as parameters. Arguments may be edge objects,
1009
- # as well as valid edge indices. Duplicates are even supported. This method
1010
- # has no effect on the automaton and raises an error if some edge argument
1011
- # is not valid.
1012
- #
1013
- # Raises:
1014
- # - ArgumentError if one edge in _edges_ is not a valid edge of this automaton.
1015
- #
1016
- def drop_edges(*edges)
1017
- # check edges first
1018
- edges = edges.collect{|e| to_edge(e)}.uniq
1019
-
1020
- # remove all edges
1021
- edges.each do |e|
1022
- @edges.delete(e)
1023
- e.from.send(:drop_outgoing_edge,e)
1024
- e.to.send(:drop_incoming_edge,e)
1025
- e.send(:index=, -1)
1026
- state_changed(:edge_dropped, e)
1027
- end
1028
- @edges.each_with_index do |e,i|
1029
- e.send(:index=,i)
1030
- end
1031
-
1032
- self
1033
- end
1034
- alias :delete_edges :drop_edges
1035
-
1036
- ### protected section ########################################################
1037
- protected
1038
-
1039
- #
1040
- # Converts a _state_ argument to a valid State of this automaton.
1041
- # There are three ways to refer to a state, by position in the internal
1042
- # collection of states, using an instance of State and using a name of a
1043
- # state (represented with a String).
1044
- #
1045
- # Raises:
1046
- # - IndexError if state is an Integer and state<0 or state>=state_count.
1047
- # - ArgumentError if state is not a valid state (not a state or not from this
1048
- # automaton)
1049
- #
1050
- def to_state(state)
1051
- case state
1052
- when State
1053
- return state if state.automaton==self and state==@states[state.index]
1054
- raise ArgumentError, "Not a state of this automaton", caller
1055
- when Integer
1056
- return ith_state(state)
1057
- when String
1058
- result = get_state(state)
1059
- return result unless result.nil?
1060
- end
1061
- raise ArgumentError, "Invalid state argument #{state}", caller
1062
- end
1063
-
1064
- #
1065
- # Converts an _edge_ argument to a valid Edge of this automaton.
1066
- #
1067
- # Raises:
1068
- # - IndexError if _edge_ is an Integer but not in [0..edge_count)
1069
- # - ArgumentError if _edge_ is not a valid edge (not a edge or not from this
1070
- # automaton)
1071
- #
1072
- def to_edge(edge)
1073
- case edge
1074
- when Edge
1075
- return edge if edge.automaton==self and edge==@edges[edge.index]
1076
- raise ArgumentError, "Not an edge of this automaton", caller
1077
- when Integer
1078
- return ith_edge(edge)
1079
- end
1080
- raise ArgumentError, "Invalid edge argument #{edge}", caller
1081
- end
1082
-
1083
- #
1084
- # Checks if a given user-data contains enough information to be attached to
1085
- # a given state. Returns the data if ok.
1086
- #
1087
- # Raises:
1088
- # - ArgumentError if data is not considered a valid state data.
1089
- #
1090
- def to_valid_state_data(data)
1091
- raise(ArgumentError,
1092
- "User data should be an Hash", caller) unless Hash===data
1093
- data
1094
- end
1095
-
1096
- #
1097
- # Checks if a given user-data contains enough information to be attached to
1098
- # a given edge. Returns the data if ok.
1099
- #
1100
- # Raises:
1101
- # - ArgumentError if data is not considered a valid edge data.
1102
- #
1103
- def to_valid_edge_data(data)
1104
- return {:symbol => data} if data.nil? or data.is_a?(String)
1105
- raise(ArgumentError,
1106
- "User data should be an Hash", caller) unless Hash===data
1107
- raise(ArgumentError,
1108
- "User data should contain a :symbol attribute.",
1109
- caller) unless data.has_key?(:symbol)
1110
- raise(ArgumentError,
1111
- "Edge :symbol attribute cannot be an array.",
1112
- caller) if Array===data[:symbol]
1113
- data
1114
- end
1115
-
1116
- ### public sections with useful utilities ####################################
1117
- public
1118
-
1119
- # Returns true if the automaton is deterministic, false otherwise
1120
- def deterministic?
1121
- @deterministic = @states.reject{|s| s.deterministic?}.empty? if @deterministic.nil?
1122
- @deterministic
1123
- end
1124
-
1125
- ### public & protected sections about alphabet ###############################
1126
- protected
1127
-
1128
- # Deduces the alphabet from the automaton edges.
1129
- def deduce_alphabet
1130
- edges.collect{|e| e.symbol}.uniq.compact.sort
1131
- end
1132
-
1133
- public
1134
-
1135
- # Returns the alphabet of the automaton.
1136
- def alphabet
1137
- @alphabet || deduce_alphabet
1138
- end
1139
-
1140
- # Sets the aphabet of the automaton. _alph_ is expected to be an array without
1141
- # nil nor duplicated. This method raises an ArgumentError otherwise. Such an
1142
- # error is also raised if a symbol used on the automaton edges is not included
1143
- # in _alph_.
1144
- def alphabet=(alph)
1145
- raise ArgumentError, "Invalid alphabet" unless alph.uniq.compact.size==alph.size
1146
- raise ArgumentError, "Invalid alphabet" unless deduce_alphabet.reject{|s| alph.include?(s)}.empty?
1147
- @alphabet = alph.sort
1148
- end
1149
-
1150
- ### public section about dot utilities #######################################
1151
- protected
1152
-
1153
- #
1154
- # Converts a hash of attributes (typically automaton, state or edge attributes)
1155
- # to a <code>[...]</code> dot string. Braces are part of the output.
1156
- #
1157
- def attributes2dot(attrs)
1158
- buffer = ""
1159
- attrs.keys.sort{|k1,k2| k1.to_s <=> k2.to_s}.each do |key|
1160
- buffer << " " unless buffer.empty?
1161
- value = attrs[key].to_s.gsub('"','\"')
1162
- buffer << "#{key}=\"#{value}\""
1163
- end
1164
- buffer
1165
- end
1166
-
1167
- public
1168
-
1169
- #
1170
- # Generates a dot output from an automaton. The rewriter block takes
1171
- # two arguments: the first one is a Markable instance (graph, state or
1172
- # edge), the second one indicates which kind of element is passed (through
1173
- # :automaton, :state or :edge symbol). The rewriter is expected to return a
1174
- # hash-like object providing dot attributes for the element.
1175
- #
1176
- # When no rewriter is provided, a default one is used by default, providing
1177
- # the following behavior:
1178
- # - on :automaton
1179
- #
1180
- # {:rankdir => "LR"}
1181
- #
1182
- # - on :state
1183
- #
1184
- # {:shape => "doublecircle/circle" (following accepting?),
1185
- # :style => "filled",
1186
- # :fillcolor => "green/red/white" (if initial?/error?/else, respectively)}
1187
- #
1188
- # - on edge
1189
- #
1190
- # {:label => "#{edge.symbol}"}
1191
- #
1192
- def to_dot(&rewriter)
1193
- unless rewriter
1194
- to_dot do |elm, kind|
1195
- case kind
1196
- when :automaton
1197
- {:rankdir => "LR"}
1198
- when :state
1199
- {:shape => (elm.accepting? ? "doublecircle" : "circle"),
1200
- :style => "filled",
1201
- :color => "black",
1202
- :fillcolor => (elm.initial? ? "green" : (elm.error? ? "red" : "white"))}
1203
- when :edge
1204
- {:label => elm.symbol.nil? ? '' : elm.symbol.to_s}
1205
- end
1206
- end
1207
- else
1208
- buffer = "digraph G {\n"
1209
- attrs = attributes2dot(rewriter.call(self, :automaton))
1210
- buffer << " graph [#{attrs}];\n"
1211
- states.each do |s|
1212
- attrs = attributes2dot(rewriter.call(s, :state))
1213
- buffer << " #{s.index} [#{attrs}];\n"
1214
- end
1215
- edges.each do |e|
1216
- attrs = attributes2dot(rewriter.call(e, :edge))
1217
- buffer << " #{e.source.index} -> #{e.target.index} [#{attrs}];\n"
1218
- end
1219
- buffer << "}\n"
1220
- end
1221
- end
1222
-
1223
- ### public section about adl utilities #######################################
1224
- public
1225
-
1226
- # Prints this automaton in ADL format
1227
- def to_adl(buffer = "")
1228
- Stamina::ADL.print_automaton(self, buffer)
1229
- end
1230
-
1231
- ### public section about reordering ##########################################
1232
- public
1233
-
1234
- # Uses a comparator block to reorder the state list.
1235
- def order_states(&block)
1236
- raise ArgumentError, "A comparator block must be given" unless block_given?
1237
- raise ArgumentError, "A comparator block of arity 2 must be given" unless block.arity==2
1238
- @states.sort!(&block)
1239
- @states.each_with_index{|s,i| s.send(:index=, i)}
1240
- self
1241
- end
1242
-
1243
- ### protected section about changes ##########################################
1244
- protected
1245
-
1246
- #
1247
- # Fires by write method when an automaton change occurs.
1248
- #
1249
- def state_changed(what, infos)
1250
- @initials = nil
1251
- @deterministic = nil
1252
- end
1253
-
1254
- protected :compute_initial_states
1255
- end # class Automaton
1256
-
1257
- end # module Stamina
1258
- require 'stamina/automaton/walking'
1259
- require 'stamina/automaton/complete'
1260
- require 'stamina/automaton/strip'
1261
- require 'stamina/automaton/equivalence'
1262
- require 'stamina/automaton/minimize'
1263
- require 'stamina/automaton/metrics'