stamina 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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'