rlsm 0.2.4 → 0.4.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.
@@ -1,39 +1,60 @@
1
- #
2
- # This file is part of the RLSM gem.
3
- #
4
- #(The MIT License)
5
- #
6
- #Copyright (c) 2008 Gunther Diemant <g.diemant@gmx.net>
7
- #
8
- #Permission is hereby granted, free of charge, to any person obtaining
9
- #a copy of this software and associated documentation files (the
10
- #'Software'), to deal in the Software without restriction, including
11
- #without limitation the rights to use, copy, modify, merge, publish,
12
- #distribute, sublicense, and/or sell copies of the Software, and to
13
- #permit persons to whom the Software is furnished to do so, subject to
14
- #the following conditions:
15
- #
16
- #The above copyright notice and this permission notice shall be
17
- #included in all copies or substantial portions of the Software.
18
- #
19
- #THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
20
- #EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21
- #MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22
- #IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
23
- #CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24
- #TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25
- #SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
- #
27
-
28
-
29
- $:.unshift File.join(File.dirname(__FILE__), 'rlsm')
30
-
31
- require 'monoid'
32
- require 'dfa'
33
- require "regexp"
34
- require "mgen"
35
- require "monoid_db"
1
+ $:.unshift File.expand_path(File.dirname(__FILE__))
36
2
 
37
3
  module RLSM
38
- VERSION = "0.2.4"
4
+ VERSION = '0.4.0'
5
+ end
6
+
7
+ #Setting up the exception classes.
8
+ class RLSMException < Exception; end
9
+ class MonoidException < RLSMException; end
10
+ class DFAException < RLSMException; end
11
+ class REException < RLSMException; end
12
+
13
+ #--
14
+ #Monkey patching the Array class
15
+ class Array # :nodoc:
16
+ #Returns all permutations of the array.
17
+ def permutations
18
+ return [self] if size < 2
19
+ perm = []
20
+ each { |e| (self - [e]).permutations.each { |p| perm << ([e] + p) } }
21
+ perm
22
+ end
23
+
24
+ #Returns the powerset of the array (interpreted as set).
25
+ def powerset
26
+ ret = self.inject([[]]) do |acc, x|
27
+ res = []
28
+ acc.each { |s| res << s; res << ([x]+s).sort }
29
+ res
30
+ end
31
+
32
+ ret.sort_lex
33
+ end
34
+
35
+ #Sorts an array of arrays more or less lexicographical.
36
+ def sort_lex
37
+ sort { |s1, s2| s1.size == s2.size ? s1 <=> s2 : s1.size <=> s2.size }
38
+ end
39
+
40
+ #Returns the cartesian product of self and the given arrays
41
+ def product(*args)
42
+ args.inject(self.map { |x| [x] }) do |res,arr|
43
+ new = []
44
+ arr.each do |x|
45
+ new += res.map { |tup| tup += [x] }
46
+ end
47
+ new
48
+ end
49
+ end
50
+
51
+ #Returns all unordered pairs.
52
+ def unordered_pairs
53
+ pairs = []
54
+ (0...size-1).each do |i|
55
+ pairs |= self[i+1..-1].map { |x| [self[i],x] }
56
+ end
57
+
58
+ pairs
59
+ end
39
60
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rlsm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - asmodis
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-11-17 00:00:00 +01:00
12
+ date: 2009-02-20 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -40,35 +40,10 @@ files:
40
40
  - Rakefile
41
41
  - bin/smon
42
42
  - lib/data/monoids.db
43
+ - lib/dfa.rb
44
+ - lib/monoid.rb
45
+ - lib/re.rb
43
46
  - lib/rlsm.rb
44
- - lib/rlsm/dfa.rb
45
- - lib/rlsm/exceptions.rb
46
- - lib/rlsm/mgen.rb
47
- - lib/rlsm/monkey_patching.rb
48
- - lib/rlsm/monoid.rb
49
- - lib/rlsm/monoid_db.rb
50
- - lib/rlsm/regexp.rb
51
- - lib/rlsm/regexp_nodes/concat.rb
52
- - lib/rlsm/regexp_nodes/primexp.rb
53
- - lib/rlsm/regexp_nodes/renodes.rb
54
- - lib/rlsm/regexp_nodes/star.rb
55
- - lib/rlsm/regexp_nodes/union.rb
56
- - lib/smon/commands/db_find.rb
57
- - lib/smon/commands/db_stat.rb
58
- - lib/smon/commands/exit.rb
59
- - lib/smon/commands/help.rb
60
- - lib/smon/commands/intro.rb
61
- - lib/smon/commands/monoid.rb
62
- - lib/smon/commands/quit.rb
63
- - lib/smon/commands/regexp.rb
64
- - lib/smon/commands/reload.rb
65
- - lib/smon/commands/show.rb
66
- - lib/smon/presenter.rb
67
- - lib/smon/presenter/txt_presenter.rb
68
- - lib/smon/smon.rb
69
- - test/dfa_spec.rb
70
- - test/monoid_spec.rb
71
- - test/regexp_spec.rb
72
47
  has_rdoc: true
73
48
  homepage: http://www.github.com/asmodis/rlsm
74
49
  post_install_message:
@@ -1,705 +0,0 @@
1
- #
2
- # This file is part of the RLSM gem.
3
- #
4
- #(The MIT License)
5
- #
6
- #Copyright (c) 2008 Gunther Diemant <g.diemant@gmx.net>
7
- #
8
- #Permission is hereby granted, free of charge, to any person obtaining
9
- #a copy of this software and associated documentation files (the
10
- #'Software'), to deal in the Software without restriction, including
11
- #without limitation the rights to use, copy, modify, merge, publish,
12
- #distribute, sublicense, and/or sell copies of the Software, and to
13
- #permit persons to whom the Software is furnished to do so, subject to
14
- #the following conditions:
15
- #
16
- #The above copyright notice and this permission notice shall be
17
- #included in all copies or substantial portions of the Software.
18
- #
19
- #THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
20
- #EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21
- #MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22
- #IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
23
- #CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24
- #TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25
- #SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
- #
27
-
28
-
29
- require File.join(File.dirname(__FILE__), 'monkey_patching')
30
- require File.join(File.dirname(__FILE__), 'monoid')
31
- require File.join(File.dirname(__FILE__), 'regexp')
32
- require File.join(File.dirname(__FILE__), 'exceptions.rb')
33
-
34
- module RLSM
35
- class DFA
36
- def initialize(alph, states, initial, finals, transitions)
37
- @alphabet = alph.sort
38
-
39
- #State identifiers should be unique and shouldn't be trap
40
- if states.uniq != states or states.include? 'trap'
41
- raise DFAException, "Bad states. State names must be unique and not 'trap'"
42
- end
43
-
44
- #Create the states
45
- @states = states.map do |state|
46
- if state == initial
47
- if finals.include? state
48
- State.new(self, state.to_s, :initial => true, :final => true)
49
- else
50
- State.new(self, state.to_s, :initial => true)
51
- end
52
- elsif finals.include? state
53
- State.new(self, state.to_s, :final => true)
54
- else
55
- State.new(self, state.to_s)
56
- end
57
- end
58
-
59
- #Add the transitions and check for completness
60
- @states.each do |state|
61
- transitions.select { |c, s1, s2| s1.to_s == state.label }.each do |c, s1, s2|
62
- state.add_transition c, _get_state(s2.to_s)
63
- end
64
- end
65
-
66
- #Calculate the reachable states
67
- @reachable_states = [initial_state]
68
- changed = true
69
-
70
- while changed
71
- changed = false
72
- (@states - @reachable_states).each do |state|
73
- if @reachable_states.any? { |s| s.reachable_states.include? state }
74
- @reachable_states << state
75
- changed = true
76
- end
77
- end
78
- end
79
-
80
- #Bring the initial state to index 0
81
- ii = @states.index(initial_state)
82
- if ii != 0
83
- @states[0], @states[ii] = @states[ii], @states[0]
84
- end
85
- end
86
-
87
- attr_reader :alphabet, :reachable_states
88
-
89
- #Returns the initial state
90
- def initial_state
91
- @initial ||= @states.find { |state| state.initial? }.deep_copy
92
- end
93
-
94
- #Returns an array of all final states.
95
- def final_states
96
- @finals ||= @states.find_all { |state| state.final? }.deep_copy
97
- end
98
-
99
- #Returns an array of all states.
100
- def states
101
- @states.deep_copy
102
- end
103
-
104
- #The number of states
105
- def num_states
106
- @states.size
107
- end
108
-
109
- #The number of final states.
110
- def num_finals
111
- final_states.size
112
- end
113
-
114
- #Returns the state in which the DFA halts if started in state and given str. Returns nil if this string isn't parseable by the DFA started in state. If the state is omitted, default start state is the initial state (surprising, isn't it).
115
- def process(str, state = nil)
116
- s = state || initial_state
117
- str.each_char do |char|
118
- s = s.process(char)
119
- return nil if s.nil?
120
- end
121
-
122
- s
123
- end
124
-
125
- #Returns true if the given string is accepted by the DFA.
126
- def accepts?(str)
127
- s = process(str)
128
- #Full String parsed, are we now in a final state or was an error?
129
- s ? s.final? : false
130
- end
131
-
132
- #Returns an array of states. The state at position i is the state in which the DFA halts, if started in states[i] and given str.
133
- #
134
- #Caution: If a state can't process str, for this state nil is returned. In a complete DFA for any valid input string, a non nil State will be returnd.
135
- def transition_function(str)
136
- @states.map { |state| process(str, state) }
137
- end
138
-
139
- #Returns true if a DFA isomorphism between self and other exists.
140
- def isomorph_to?(other)
141
- #First a few necessary conditions
142
- return false if num_states != other.num_states
143
- return false if num_finals != other.num_finals
144
- return false if alphabet != other.alphabet
145
-
146
- initial_index = @states.index(initial_state)
147
- final_indices = final_states.map { |f| @states.index(f) }
148
- (0...num_states).to_a.permutations.each do |per|
149
- #Initial state must map to initial state
150
- next unless other.states[per[initial_index]].initial?
151
-
152
- #Finals must map to finals
153
- next unless final_indices.all? { |fi| other.states[fi].final? }
154
-
155
- #Transactions respected?
156
- bad = @states.find do |state|
157
- not @alphabet.all? do |char|
158
- si = @states.index(state)
159
- ps = state.process(char)
160
-
161
- if ps.nil?
162
- other.states[per[si]].process(char).nil?
163
- else
164
- os = other.states[per[si]].process(char)
165
- if os.nil?
166
- false
167
- else
168
- per[@states.index(ps)] == other.states.index(os)
169
- end
170
- end
171
- end
172
- end
173
-
174
- #Found an iso, i.e. no bad states?
175
- return true unless bad
176
- end
177
-
178
- #No isomorphism found
179
- return false
180
- end
181
-
182
- #A synonym for isomorph_to?
183
- def ==(other)
184
- isomorph_to? other
185
- end
186
-
187
- #Returns true, if the two DFAs accepts the same language, i.e. the mimimized DFAs are isomorph.
188
- def equivalent_to?(other)
189
- minimze == other.minimize
190
- end
191
-
192
- #Returns true, if the two DFAs accepts languages of the same structure, i.e. if the languages differs only in the used alphabet. For example the languages aab and bba are similar.
193
- def similar_to?(other)
194
- dfa1 = minimize
195
- dfa2 = other.minimize
196
-
197
- #First a few necessary conditions
198
- return false if dfa1.num_states != dfa2.num_states
199
- return false if dfa1.num_finals != dfa2.num_finals
200
-
201
- dfa1.alphabet.permutations do |per|
202
- #Get the states
203
- states = other.states.map { |st| st.label }
204
-
205
- #Get the initial
206
- initial = other.initial_state.label
207
-
208
- #Get the finals
209
- finals = other.final_states.map { |fs| fs.label }
210
-
211
- #Get the transitions
212
- trans = []
213
- other.states.each do |s|
214
- s.transitions.each_pair { |c,os| trans << [c,s.label, os.label].dup }
215
- end
216
-
217
- #Alter the transitions wrt to the permutation
218
- trans.map! do |c,s1,s2|
219
- [per[other.alphabet.index(c)],s1,s2]
220
- end
221
-
222
- #Exisits now an isomorphism between self and the new dfa?
223
- return true if self == new(@alphabet, states, initial, finals, trans)
224
- end
225
-
226
- #No similarity found
227
- return false
228
- end
229
-
230
- #Returns a minimal DFA which accepts the same language (see minimize!)
231
- def minimize(opts = {})
232
- self.deep_copy.minimize!(opts)
233
- end
234
-
235
- #Alters the DFA to a minimal one which accepts the same language.
236
- #If the DFA is complete, then the minimal DFA returned is also complete, i.e. if there is a trap state (a dead state, but without, the DFA isn't complete), it will not be deleted. To do so, call remove_dead_states after minimizing.
237
- #If passed :rename_states => true, the state labels will be renamed to something short (propably 0,1,2,...).
238
- def minimize!(opts = {})
239
- #First step: remove unreachable states
240
- remove_unreachable_states!
241
-
242
- complete_temp = complete?
243
-
244
- #Second step: Find all equivalent states
245
- #Create the initial state partition
246
- sp = @states.partition { |state| state.final? }
247
- sp_labels = sp.map { |sc| sc.map {|st| st.label} }.sort
248
-
249
- #Calculate the new state partition for the first time
250
- nsp = new_state_partition(sp)
251
- nsp_labels = nsp.map { |sc| sc.map {|st| st.label} }.sort
252
-
253
- #Find all state classes (repeating process until nothing changes)
254
- while sp_labels != nsp_labels
255
- sp, nsp = nsp.deep_copy, new_state_partition(nsp)
256
-
257
- sp_labels = sp.map { |sc| sc.map {|st| st.label} }.sort
258
- nsp_labels = nsp.map { |sc| sc.map {|st| st.label} }.sort
259
- end
260
-
261
- #Third step: Are we done?
262
- #Check if the DFA was already minimal
263
- return self if sp.all? { |sc| sc.size == 1 }
264
-
265
- #Fourth step: Constructing the new DFA:
266
- #1 the states
267
- @states = sp.map do |sc|
268
- state_label = sc.map { |s| s.label }.join
269
- if sc.include? initial_state
270
- if final_states.any? {|f| sc.include? f }
271
- State.new(self, state_label, :initial => true, :final => true)
272
- else
273
- State.new(self, state_label, :initial => true)
274
- end
275
- elsif final_states.any? { |f| sc.include? f }
276
- State.new(self, state_label, :final => true)
277
- else
278
- State.new(self, state_label)
279
- end
280
- end
281
-
282
- #2 the transitions
283
- @states.each_with_index do |state, sc_index|
284
- sp[sc_index].first.transitions.each_pair do |char, s2|
285
- state.add_transition char, @states[_class_index_of(sp,s2)]
286
- end
287
- end
288
-
289
-
290
- #3 delete dead states
291
- remove_dead_states!(:except_trap => complete_temp)
292
-
293
- #4 Force recalculation of initial and final states
294
- @initial, @finals = nil, nil
295
-
296
- #Bring the initial state to index 0
297
- ii = @states.index(initial_state)
298
- if ii != 0
299
- @states[0], @states[ii] = @states[ii], @states[0]
300
- end
301
-
302
-
303
- #5 Was renaming of the states requested?
304
- if opts[:rename_states]
305
- @states.each_with_index do |state,index|
306
- state.label = index.to_s
307
- end
308
- end
309
-
310
- #6 update the reachable states (all are reachable in a minimal DFA)
311
- @reachable_states = @states.deep_copy
312
-
313
-
314
- self
315
- end
316
-
317
- #Returns true if the DFA is minimal (see minimize!).
318
- def minimal?
319
- num_states == minimize.num_states
320
- end
321
-
322
- #Returns a complete DFA which accepts the same language.
323
- def complete
324
- self.deep_copy.complete!
325
- end
326
-
327
- #Adds a dead state and adds to every other state a transition to this state for all missing alphabet elements. If the DFA is already complete nothing happens.
328
- #
329
- #In either case the complete DFA is returned.
330
- def complete!
331
- #Is work to do?
332
- return self if complete?
333
-
334
- #Create the trap state
335
- trap = State.new(self, 'trap')
336
- @alphabet.each do |char|
337
- trap.add_transition char, trap
338
- end
339
-
340
- #Add the necassery transitions
341
- @states.each do |state|
342
- unless state.complete?
343
- (@alphabet - state.accepted_chars).each do |char|
344
- state.add_transition char, trap
345
- end
346
- end
347
- end
348
-
349
- #Add the trap state to the DFA
350
- @states << trap
351
- @reachable_states << trap
352
-
353
- self
354
- end
355
-
356
- #Returns true if this DFA is complete, i.e. all states accepts all alphabet symbols.
357
- def complete?
358
- @states.all? do |state|
359
- state.complete?
360
- end
361
- end
362
-
363
- #Returns an array of dead states (a state is dead, if it is not final and has outdegree 0)
364
- def dead_states
365
- @states.find_all { |state| state.dead? }
366
- end
367
-
368
- #Returns true if the DFA has dead states (see also dead_states)
369
- def dead_states?
370
- @states.find { |state| state.dead? } ? true : false
371
- end
372
-
373
- #Removes all dead_states. If passed :except_trap => true, trap states wouldn't be removed.
374
- def remove_dead_states!(opt = {})
375
- @states.each do |state|
376
- state.remove_transitions_to_dead_states(opt)
377
- end
378
-
379
- #Remove the states
380
- if opt[:except_trap]
381
- @states = @states.reject do |state|
382
- state.dead? and not state.trap?
383
- end
384
- @reachable_states = @reachable_states.reject do |state|
385
- state.dead? and not state.trap?
386
- end
387
- else
388
- @states = @states.reject { |state| state.dead? }
389
- @reachable_states = @reachable_states.reject { |state| state.dead? }
390
- end
391
-
392
- self
393
- end
394
-
395
- #Returns a copy with dead states removed.
396
- def remove_dead_states(opt = {})
397
- self.deep_copy.remove_dead_states!(opt)
398
- end
399
-
400
- #Returns an array of states, wich aren't reachable from the initial state.
401
- def unreachable_states
402
- @states.find_all { |state| state.unreachable?}
403
- end
404
-
405
- #Returns true if the DFA has unreachable states
406
- def unreachable_states?
407
- @states.find { |state| state.unreachable? } ? true : false
408
- end
409
-
410
- #Removes all unreachable states.
411
- def remove_unreachable_states!
412
- #No transition update necessary, because each state which reaches an unreachble state must be unreachable.
413
- @states = @states.reject { |state| state.unreachable? }
414
- @reachable_states = @states.deep_copy
415
-
416
- self
417
- end
418
-
419
- #Returns a copy with unreachable states removed.
420
- def remove_unreachable_states
421
- self.deep_copy.remove_unreachable_states!
422
- end
423
-
424
- #Returns an RLSM::RegExp instance representing the same language as this DFA.
425
- def to_regexp
426
- #No finals => no language
427
- return RLSM::RegExp.new if final_states.empty?
428
-
429
- #Calculate the coeffizients matrix
430
- #1 empty matrix with lambdas for final states
431
- matr = @states.map do |st|
432
- last = st.final? ? [RLSM::RegExp.new('&')] : [RLSM::RegExp.new]
433
- [RLSM::RegExp.new]*num_states + last
434
- end
435
-
436
- #2 remaining coeffizients
437
- @states.each_with_index do |state,i|
438
- state.transitions.each_pair do |ch, st|
439
- matr[i][@states.index(st)] += RLSM::RegExp.new(ch)
440
- end
441
- end
442
-
443
- #Solve the matrix for matr[0][0] (Remember 0 is index of initial state)
444
- (num_states-1).downto 1 do |i|
445
- #Depends Ri on itself? If so apply well known simplify rule
446
- # R = AR +B -> R = A*B
447
- unless matr[i][i].empty?
448
- matr[i].map! { |re| matr[i][i].star * re }
449
- matr[i][i] = RLSM::RegExp.new
450
- end
451
-
452
- #Substitute know Ri in everey row above
453
- ri = matr.pop
454
-
455
- matr.map! do |row|
456
- row.each_with_index do |re,j|
457
- row[j] = re + row[i]*ri[j]
458
- end
459
-
460
- row[i] = RLSM::RegExp.new
461
-
462
- row
463
- end
464
-
465
- end
466
-
467
- #Examine now the last remaining first row (irritating...)
468
- regexp = matr.pop
469
-
470
- if regexp[0].empty? #R0 depends not on R0
471
- return regexp.last
472
- else #R0 depends on R0
473
- return regexp[0].star * regexp.last
474
- end
475
- end
476
-
477
- #Calculate the transition monoid of the DFA. Because it is only possible for an complete DFA to compute the TM, the TM is calculated for the DFA returned by complete.
478
- def transition_monoid
479
- dfa = self.complete
480
-
481
- #Calculate the monoid elements
482
- trans_tab = [["", dfa.transition_function("")]]
483
-
484
- new_elements = true
485
- str_length = 1
486
- while new_elements
487
- new_elements = false
488
- dfa.each_str_with_length(str_length) do |str|
489
- tf = dfa.transition_function(str)
490
- unless trans_tab.map { |s,f| f}.include? tf
491
- trans_tab << [str, tf]
492
- new_elements = true
493
- end
494
- end
495
- str_length += 1
496
- end
497
-
498
- #Calculate the binary operation
499
- binop = [(0...trans_tab.size).to_a]
500
-
501
- (1...trans_tab.size).each do |i|
502
- str = trans_tab[i].first
503
- binop << trans_tab.map do |s, tf|
504
- trans_tab.map {|st,f| f }.index(dfa.transition_function(str + s))
505
- end
506
- end
507
-
508
-
509
- RLSM::Monoid.new binop.map { |row| row.join(',') }.join(' ')
510
- end
511
-
512
- #Returns the syntactic monoid which belongs to the language accepted by the DFA. (In fact, the transition monoid of the minimal DFA is returned, both monoids are isomorph)
513
- def syntactic_monoid
514
- minimize.transition_monoid
515
- end
516
-
517
- #Returns a string represantation
518
- def to_s
519
- str = "\t| #{@alphabet.join(' | ')} \n"
520
- str += '-'*(str.length+7) + "\n"
521
- str + @states.map { |state| state.to_s }.join("\n")
522
- end
523
-
524
- def inspect
525
- "<#{self.class}: #{@states.map {|c| c.label }.join(', ') }"
526
- end
527
-
528
-
529
-
530
- protected
531
- def each_str_with_length(length)
532
- if length == 0
533
- yield ""
534
- return
535
- end
536
-
537
- str = [@alphabet.first.clone]*length
538
- pos = length-1
539
- finished = false
540
- loop do
541
- yield str.join
542
-
543
- loop do
544
- if str[pos] == @alphabet.last
545
- if pos == 0
546
- finished = true
547
- break
548
- end
549
- str[pos] = @alphabet.first
550
- pos -= 1
551
- else
552
- str[pos] = @alphabet[@alphabet.index(str[pos])+1]
553
- pos += 1 unless pos == length-1
554
- break
555
- end
556
- end
557
-
558
- break if finished
559
- end
560
- end
561
-
562
- private
563
- def _get_state(label)
564
- @states.find { |state| state.label == label }
565
- end
566
-
567
- def _class_index_of(sp, s)
568
- sp.each_with_index { |sc,i| return i if sc.include? s }
569
- end
570
-
571
- def new_state_partition(sp)
572
- res = []
573
- sp.each do |set|
574
- partitionate_set(set, sp).compact.each do |s|
575
- res << s
576
- end
577
- end
578
-
579
- res
580
- end
581
-
582
- def partitionate_set(s, p)
583
- #A singelton set is already partitionated
584
- return s if s.empty?
585
-
586
- acc_chars = s.first.accepted_chars.sort
587
-
588
- #Find the classes in which the first element of s is transitionated
589
- classes = {}
590
-
591
- acc_chars.each do |c|
592
- classes[c] = p.find { |set| set.include? s.first.process(c) }
593
- end
594
-
595
- first_class = s.find_all do |x|
596
- acc_chars.all? { |c| classes[c].include? x.process(c) } and
597
- acc_chars == x.accepted_chars.sort
598
- end
599
-
600
- rest = s - first_class
601
-
602
- [first_class, *partitionate_set(rest, p)]
603
- end
604
- end
605
-
606
- class DFA::State
607
- def initialize(dfa, label, opts = {})
608
- @label = label
609
- @dfa = dfa
610
- @is_initial = opts[:initial] || false
611
- @is_final = opts[:final] || false
612
-
613
- @trans = {}
614
- end
615
-
616
- attr_accessor :label
617
-
618
- def initial?
619
- @is_initial
620
- end
621
-
622
- def final?
623
- @is_final
624
- end
625
-
626
- #Return true if this state is dead. A state is dead if there is know edge leading to another state and the state himself isn't a final state.
627
- def dead?
628
- (@trans.empty? or reachable_states == [self]) and not final?
629
- end
630
-
631
- #Return true if this state is a trap state. A trap state is a dead_state which accepts all alphabet elements and is reachable. A trap state is useful if a complete DFA is requested.
632
- def trap?
633
- dead? and reachable? and accepted_chars.sort == @dfa.alphabet.sort
634
- end
635
-
636
- def add_transition(char, dest_state)
637
- if @trans.key? char
638
- raise DFAException, "Have already a transition for #{char}"
639
- end
640
-
641
- @trans[char] = dest_state
642
- end
643
-
644
- def process(char)
645
- if @trans.key? char
646
- @trans[char]
647
- else
648
- nil
649
- end
650
- end
651
-
652
- def complete?
653
- @trans.size == @dfa.alphabet.size
654
- end
655
-
656
- def accepted_chars
657
- @trans.keys
658
- end
659
-
660
- def reachable_states
661
- @trans.values
662
- end
663
-
664
- def transitions
665
- @trans
666
- end
667
-
668
- def remove_transitions_to_dead_states(opt = {})
669
- if opt[:except_trap]
670
- @trans = @trans.reject do |char, state|
671
- state.dead? and not state.trap?
672
- end
673
- else
674
- @trans = @trans.reject { |char, state| state.dead? }
675
- end
676
- end
677
-
678
- def reachable?
679
- @dfa.reachable_states.include? self
680
- end
681
-
682
- def unreachable?
683
- not reachable?
684
- end
685
-
686
- def to_s
687
- str = ''
688
- str += '-> ' if initial?
689
- str += '* ' if final?
690
- str += @label.to_s + "\t| "
691
- str += @dfa.alphabet.map { |c| process(c) ? process(c).label : ' ' }.join(' | ')
692
-
693
- str
694
- end
695
-
696
- def ==(other)
697
- return false if other.class != DFA::State
698
- return false if label != other.label
699
-
700
- @trans.all? do |char, state|
701
- state.label == other.process(char).label
702
- end
703
- end
704
- end
705
- end