rlsm 1.0.0 → 1.1.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.
- data/{README.txt → README} +10 -23
- data/Rakefile +79 -7
- data/ext/array/array_c_ext.c +137 -0
- data/ext/array/extconf.rb +2 -0
- data/ext/binop/binop_c_ext.c +57 -0
- data/ext/binop/extconf.rb +2 -0
- data/ext/monoid/extconf.rb +2 -0
- data/ext/monoid/monoid_c_ext.c +330 -0
- data/lib/rlsm.rb +10 -14
- data/lib/rlsm/binary_operation.rb +151 -0
- data/lib/rlsm/dfa.rb +418 -602
- data/lib/rlsm/helper.rb +12 -0
- data/lib/rlsm/monoid.rb +454 -694
- data/lib/rlsm/regexp.rb +125 -0
- data/lib/rlsm/regexp_parser.rb +450 -0
- data/test/helpers.rb +66 -0
- data/test/test_binop.rb +119 -0
- data/test/test_dfa.rb +435 -0
- data/test/test_monoid.rb +552 -0
- data/test/test_regexp.rb +440 -0
- metadata +109 -37
- data/History.txt +0 -6
- data/Manifest.txt +0 -18
- data/bin/smon +0 -39
- data/data/monoids.db +0 -0
- data/lib/database.rb +0 -95
- data/lib/monkey_patching/array_ext.rb +0 -50
- data/lib/rlsm/re.rb +0 -504
- data/lib/smon/base.rb +0 -284
- data/lib/smon/db.rb +0 -98
- data/lib/smon/dot.rb +0 -65
- data/lib/smon/latex.rb +0 -313
- data/lib/smon/smon.rb +0 -183
- data/stdarb.tex +0 -118
data/lib/rlsm.rb
CHANGED
@@ -1,17 +1,13 @@
|
|
1
|
-
$:.unshift File.expand_path(File.dirname(__FILE__))
|
2
|
-
|
3
|
-
require 'monkey_patching/array_ext'
|
4
|
-
|
5
1
|
module RLSM
|
6
|
-
VERSION =
|
7
|
-
end
|
2
|
+
VERSION = "1.1.0"
|
8
3
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
class DFAException < RLSMException; end
|
13
|
-
class REException < RLSMException; end
|
4
|
+
def self.lib_path_to(file_name)
|
5
|
+
File.join(File.dirname(__FILE__), 'rlsm', file_name)
|
6
|
+
end
|
14
7
|
|
15
|
-
|
16
|
-
|
17
|
-
|
8
|
+
autoload :BinaryOperation, RLSM.lib_path_to("binary_operation.rb")
|
9
|
+
autoload :Monoid, RLSM.lib_path_to("monoid.rb")
|
10
|
+
autoload :DFA, RLSM.lib_path_to("dfa.rb")
|
11
|
+
autoload :RegExp, RLSM.lib_path_to("regexp.rb")
|
12
|
+
end
|
13
|
+
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'helper')
|
2
|
+
RLSM.require_extension 'binop'
|
3
|
+
|
4
|
+
module RLSM
|
5
|
+
class BinaryOperation
|
6
|
+
class << self; alias original_new new; end
|
7
|
+
=begin rdoc
|
8
|
+
Creates a BinaryOperation from given description and validates the result.
|
9
|
+
Raises a ParseError if validation fails.
|
10
|
+
|
11
|
+
The description is of the form
|
12
|
+
[<elements>:]<table>
|
13
|
+
where the optional <elements> part lists the elements on which the binary
|
14
|
+
operation acts in the order in which they appear in the table part.
|
15
|
+
The elements are seperated by commas. The commas are optional if each
|
16
|
+
element is identified by a single letter. no withespaces are allowed in
|
17
|
+
an element identifier.
|
18
|
+
|
19
|
+
The table part describes the transition table of the binary operation.
|
20
|
+
Rows are seperated by at least one whitespace character and elements in
|
21
|
+
a row are seperated by commas. The commas are optional if each element
|
22
|
+
is identified by a single letter.
|
23
|
+
If commas are used in one row to seperate the elements, one must use
|
24
|
+
commas in each row.
|
25
|
+
|
26
|
+
*Remark*: If the elements part is omitted it is assumed that
|
27
|
+
the elements are appearing in the right order and all elements
|
28
|
+
are appearing. This is the case for a monoid with neutral element
|
29
|
+
in the first row.
|
30
|
+
|
31
|
+
*Examples*
|
32
|
+
012 120 201
|
33
|
+
012:000 000 000
|
34
|
+
x1,x2,x3: x1,x2,x2 x2,x2,x1 x3,x2,x1
|
35
|
+
000 000 000 -> error
|
36
|
+
0,1,2 120 2,1,0 -> error
|
37
|
+
=end
|
38
|
+
def self.new(description)
|
39
|
+
binop = new!(description)
|
40
|
+
validate(binop)
|
41
|
+
|
42
|
+
binop
|
43
|
+
end
|
44
|
+
|
45
|
+
#Like new, but without validation of the input.
|
46
|
+
def self.new!(description)
|
47
|
+
original_new *parse(description)
|
48
|
+
end
|
49
|
+
|
50
|
+
#:call-seq:
|
51
|
+
# original_new(table,elements,mapping) -> BinaryOperation
|
52
|
+
#
|
53
|
+
#Creates a BinaryOperation. The arguments are a table an array with elements
|
54
|
+
#and a mapping.
|
55
|
+
#*Remark*: Use this method only if you know what you're doing. No
|
56
|
+
#validation or argument checking is made. Its primary porpose is to speed up
|
57
|
+
#monoid generation in the RLSM::Monoid.each method.
|
58
|
+
def initialize(table, elements, mapping)
|
59
|
+
@table, @elements, @mapping = table, elements, mapping
|
60
|
+
@order = @mapping.size
|
61
|
+
end
|
62
|
+
|
63
|
+
#Internal representation of the table.
|
64
|
+
attr_reader :table
|
65
|
+
|
66
|
+
#Array with all different elements
|
67
|
+
attr_reader :elements
|
68
|
+
|
69
|
+
#The number of elements.
|
70
|
+
attr_reader :order
|
71
|
+
|
72
|
+
#Maps element names to numbers.
|
73
|
+
attr_reader :mapping
|
74
|
+
|
75
|
+
#Calculate the product of +x+ and +y+.
|
76
|
+
#
|
77
|
+
#If at least one of the given elements is unknown raises an ArgumentError.
|
78
|
+
def [](x, y)
|
79
|
+
if @mapping[x].nil?
|
80
|
+
raise ArgumentError, "Unknown element #{x}"
|
81
|
+
elsif @mapping[y].nil?
|
82
|
+
raise ArgumentError, "Unknown element #{y}"
|
83
|
+
else
|
84
|
+
@elements[@table[@mapping[x]*@order + @mapping[y]]]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
#Checks if binary operation is associative.
|
89
|
+
def associative?
|
90
|
+
!non_associative_triple
|
91
|
+
end
|
92
|
+
|
93
|
+
#Checks if binary operation is commutative.
|
94
|
+
def commutative?
|
95
|
+
is_commutative
|
96
|
+
end
|
97
|
+
|
98
|
+
#Checks if the binary operation is associative. If not raises an BinOpError.
|
99
|
+
def enforce_associativity
|
100
|
+
nat = non_associative_triple
|
101
|
+
|
102
|
+
unless nat.nil?
|
103
|
+
err_str = "(#{nat[0]}#{nat[0]})#{nat[0]} != #{nat[0]}(#{nat[1]}#{nat[2]})"
|
104
|
+
raise BinOpError, "Associativity required, but #{err_str}."
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
def self.validate(binop)
|
110
|
+
if binop.table.size == 0
|
111
|
+
raise ArgumentError, "No elements given."
|
112
|
+
end
|
113
|
+
|
114
|
+
if binop.table.size != binop.mapping.size**2
|
115
|
+
raise ParseError, "Either to many elements or wrong table format."
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.parse(description)
|
120
|
+
mapping = {}
|
121
|
+
elements = []
|
122
|
+
table = []
|
123
|
+
if description =~ /\s*:\s*/
|
124
|
+
elements = desc2ary(Regexp.last_match.pre_match)
|
125
|
+
elements.each_with_index { |el,i| mapping[el] = i }
|
126
|
+
|
127
|
+
desc2ary(Regexp.last_match.post_match).
|
128
|
+
each { |el| table << mapping[el] }
|
129
|
+
else
|
130
|
+
desc2ary(description).each do |element|
|
131
|
+
if mapping[element].nil?
|
132
|
+
mapping[element] = elements.size
|
133
|
+
elements << element
|
134
|
+
end
|
135
|
+
|
136
|
+
table << mapping[element]
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
[table, elements, mapping]
|
141
|
+
end
|
142
|
+
|
143
|
+
def self.desc2ary(str)
|
144
|
+
if str.include?(',')
|
145
|
+
str.gsub(/^\s+/,'').gsub(/,*\s+,*/,',').split(',')
|
146
|
+
else
|
147
|
+
str.gsub(/\s+/,'').scan(/./)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
data/lib/rlsm/dfa.rb
CHANGED
@@ -1,717 +1,533 @@
|
|
1
|
-
require File.
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
Because we know, that some states have no influence on the generated language, we must find some criterias which states aren't required.
|
63
|
-
|
64
|
-
===Unreachable states
|
65
|
-
We notice, that in the definition it isn't required, that for each state +s+ a word +w+ exists, such that <tt>T*(w,i) = s</tt>. We will call a state +s+ _reachable_ if such a word exists and _unreachable_ otherwise.
|
66
|
-
|
67
|
-
It is easy to see, that an unreachable state has no impact on the generated language, so a first step towards a minimal DFA is to reject all unreachable states.
|
68
|
-
|
69
|
-
A problem may be, that after removing the unreachable states, say the remaining states are +S'+, it is now possible that <tt>T|AxS'</tt> isn't defined everywhere. We will treat this in the next paragrapgh
|
70
|
-
|
71
|
-
===Completeness of a DFA
|
72
|
-
It is convenient to discard the requirement, that +T+ is defined on +AxS+. It is for most use-cases sufficent, that +T+ is a function on a subset +D(T)+ of +AxS+.
|
73
|
-
|
74
|
-
In this case, we say a state +s+ _accepts_ the letter +c+ if <tt>(c,s)</tt> is an element of +D(T)+. We say a DFA is _complete_ iff +D(T)=AxS+, i.e. each state accepts all alphabet letters.
|
75
|
-
|
76
|
-
====An algorithm to complete a DFA
|
77
|
-
If we have a noncomplete DFA <tt>(A,S,I,F,T)</tt> we can complete it in the following way:
|
78
|
-
1. Add a _trap_ _state_ +t+.
|
79
|
-
2. Define <tt>T':Su{t} -> Su{t}</tt> by <tt>T'=T</tt> for points where +T+ is defined and <tt>T'(c,s) = t</tt> elsewhere.
|
80
|
-
Now the DFA <tt>(A,Su{t},I,F,T')</tt> is complete.
|
81
|
-
|
82
|
-
====Remark
|
83
|
-
For a noncomplete DFA +M+ it is also possible to define a generalized transition function: If +N+ is the in the above way completed DFA of +M+ and +F+ its generalized transition function. We define
|
84
|
-
D(T*) := { (w,s) | w in A*, F(w,s) != t }
|
85
|
-
and
|
86
|
-
T* := F|D(T*)
|
87
|
-
Now +T*+ is a generalized transition function for a noncomplete DFA.
|
88
|
-
|
89
|
-
Also the completion doesn't change the generated language.
|
90
|
-
|
91
|
-
===Equivalent states
|
92
|
-
We say two states <tt>s,t</tt> are _equivalent_ iff for all words +w+ <tt>T*(w,s)</tt> is a final state iff and only if <tt>T*(w,t)</tt> is a final state.
|
93
|
-
|
94
|
-
This is an equivalence relation on the states +S+.
|
95
|
-
|
96
|
-
===Dead states
|
97
|
-
We say a state +s+ is _dead_ (or a _trap_ _state_) iff for all letters +l+ <tt>T(l,s) = s</tt> and +s+ isn't a final state.
|
98
|
-
|
99
|
-
The same as for unreachable states is true for dead states. They have no influence on the generated language. Moreover each dead state is equivalent to another dead state, because both aren't final states and they don't change for a word.
|
100
|
-
|
101
|
-
===The algorithm
|
102
|
-
0. Eliminate all unreachable states (leaving a potential uncomplete DFA)
|
103
|
-
1. Partitionate the set of remaining states by the equivalence relation of states. We get a set +S'+ of equivalence classes of states.
|
104
|
-
2. Define +i'+ to be +[i]+ in +S'+.
|
105
|
-
3. Define +F'+ to be the classes +[f]+ for each +f+ in +F+.
|
106
|
-
4. Define <tt>T' : AxS' -> S'</tt> by <tt>T'(l,[s]) := [T(l,s)]</tt>
|
107
|
-
|
108
|
-
The DFA (A,S',i',F',T') is then a minimal DFA (but not necessarily complete anymore). If one whishes a complete minimal DFA, complete the previous DFA.
|
109
|
-
|
110
|
-
For the calculation of the partition there are several algorithms but we choose the table filling algorithm.
|
111
|
-
=end
|
112
|
-
|
113
|
-
class RLSM::DFA
|
114
|
-
=begin rdoc
|
115
|
-
This method takes as parameter a hash with following required keys:
|
116
|
-
[:+initial+] The initial state.
|
117
|
-
[:+finals+] An array of the final states.
|
118
|
-
[:+transitions+] An array of the transitions.
|
119
|
-
=end
|
120
|
-
def self.create(args = {})
|
121
|
-
args[:transitions].map! { |t| t.map { |x| x.to_s } }
|
122
|
-
args[:initial] = args[:initial].to_s
|
123
|
-
args[:finals].map! { |f| f.to_s }
|
124
|
-
args[:alphabet] = args[:transitions].map { |t| t.first }.uniq.sort
|
125
|
-
args[:states] = args[:transitions].map { |t| t[1,2] }.flatten.uniq.sort
|
126
|
-
|
127
|
-
new args
|
128
|
-
end
|
129
|
-
=begin rdoc
|
130
|
-
It is not intended to use this method directly, use create instead.
|
131
|
-
|
132
|
-
This method takes as parameter a hash with following required keys:
|
133
|
-
[:+alphabet+] An array of alphabet characters.
|
134
|
-
[:+states+] An array of states.
|
135
|
-
[:+initial+] The initial state.
|
136
|
-
[:+finals+] An array of the final states.
|
137
|
-
[:+transitions+] An array of the transitions.
|
138
|
-
|
139
|
-
It is required that all occuring states are in the states array and all transition labels are in the alphabet. Also the states and the alphabet must be unique.
|
140
|
-
=end
|
141
|
-
def initialize(args = {})
|
142
|
-
validate_presence_of_required_keys(args)
|
143
|
-
|
144
|
-
validate_uniqueness_of_alphabet_and_states(args)
|
145
|
-
validate_transition_labels_are_in_the_alphabet(args)
|
146
|
-
validate_all_states_matches(args)
|
147
|
-
validate_transition_uniquness(args)
|
1
|
+
require File.join(File.dirname(__FILE__), 'helper')
|
2
|
+
require File.join(File.dirname(__FILE__), 'regexp')
|
3
|
+
require File.join(File.dirname(__FILE__), 'monoid')
|
4
|
+
RLSM::require_extension('array')
|
5
|
+
|
6
|
+
require "strscan"
|
7
|
+
|
8
|
+
module RLSM
|
9
|
+
class DFA
|
10
|
+
#Synonym for new.
|
11
|
+
def self.[](description)
|
12
|
+
new(description)
|
13
|
+
end
|
14
|
+
|
15
|
+
#Creates a new DFA. The +description+ is a string which describes states and transitions.
|
16
|
+
#A state is described by a descriptor, which consists of latin letters and numbers (no whitespaces).
|
17
|
+
#
|
18
|
+
#To indicate a final state, the state descriptor starts with a *. This is allowed multiple times, even for the same state.
|
19
|
+
#
|
20
|
+
#The initial state starts with a right brace ('}'). This is only allowed once.
|
21
|
+
#
|
22
|
+
#A transition is described as follows
|
23
|
+
# STATE -LETTERLIST-> STATE
|
24
|
+
# were +STATE+ is a state descriptor (* and } modifiers allowed) and +LETTERLIST+ is either a single letter of the alphabet or a comma seperated list of alphabet letters.
|
25
|
+
#
|
26
|
+
#*Remark*: This desription format may be the worst design desicion in the whole gem ...
|
27
|
+
#
|
28
|
+
#*Example*:
|
29
|
+
# RLSM::DFA["}s1-a,b->*s2"]
|
30
|
+
# RLSM::DFA["}s1 s2 *s3 s1-a->s2 s2-a,b->*s3 s3-b->s1"]
|
31
|
+
# RLSM::DFA["}s1 s2 *s3 }s1-a->s2 s2-a,b->*s3 s3-b->s1"] # => Error
|
32
|
+
def initialize(description)
|
33
|
+
parse_initial_state(description)
|
34
|
+
parse_states(description)
|
35
|
+
parse_final_states(description)
|
36
|
+
parse_transitions(description)
|
37
|
+
end
|
38
|
+
|
39
|
+
#Initial state of the DFA.
|
40
|
+
attr_reader :initial_state
|
41
|
+
|
42
|
+
#Array of accepting states.
|
43
|
+
attr_reader :final_states
|
44
|
+
|
45
|
+
#Array of all states
|
46
|
+
attr_reader :states
|
47
|
+
|
48
|
+
#The alphabet of the DFA.
|
49
|
+
attr_reader :alphabet
|
50
|
+
|
51
|
+
#Returns array of transitions (a transition is a triple consisting of start, destination and label).
|
52
|
+
def transitions
|
53
|
+
return [] if @transitions.nil?
|
54
|
+
|
55
|
+
result = []
|
56
|
+
@transitions.each do |state,labels|
|
57
|
+
labels.each do |label,dest|
|
58
|
+
result << [state,dest,label]
|
59
|
+
end
|
60
|
+
end
|
148
61
|
|
149
|
-
|
150
|
-
@states = args[:states].clone
|
151
|
-
@initial_state = args[:initial].clone
|
152
|
-
@finals = args[:finals].clone
|
153
|
-
@transitions = args[:transitions].inject({}) do |res,tr|
|
154
|
-
(res[tr.first] ||= {})[tr[1]] = tr[2]
|
155
|
-
res
|
62
|
+
result
|
156
63
|
end
|
157
|
-
end
|
158
64
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
res = []
|
164
|
-
@alphabet.each do |letter|
|
165
|
-
res |= @transitions[letter].to_a.map { |x| [letter] + x }
|
166
|
-
end
|
167
|
-
|
168
|
-
res
|
169
|
-
end
|
170
|
-
|
171
|
-
#Returns the number of states.
|
172
|
-
def num_states
|
173
|
-
@states.size
|
174
|
-
end
|
175
|
-
|
176
|
-
#Returns the number of final states.
|
177
|
-
def num_finals
|
178
|
-
@finals.size
|
179
|
-
end
|
180
|
-
|
181
|
-
#Returns the state in which the DFA halts if started in state +s+ and given the word +w+. Returns +nil+ if some state doesn't accept the actual character on the way.
|
182
|
-
def [](w,s)
|
183
|
-
unless @states.include? s
|
184
|
-
raise DFAException, "Given state '#{s}' isn't a state in this DFA."
|
185
|
-
end
|
186
|
-
|
187
|
-
w.scan(/./).inject(s) do |s, c|
|
188
|
-
unless @alphabet.include? c
|
189
|
-
raise DFAException, "Given character '#{c}' isn't in the alphabet."
|
65
|
+
#Processes given +string+ starting in state +state+.
|
66
|
+
def [](state, string)
|
67
|
+
unless @states.include?(state)
|
68
|
+
raise DFAError, "Unknown state: #{state}"
|
190
69
|
end
|
191
|
-
@transitions[c][s]
|
192
|
-
end
|
193
|
-
end
|
194
70
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
#Test first some required conditions
|
204
|
-
return false unless @alphabet == other.alphabet
|
205
|
-
return false unless @states.size == other.num_states
|
206
|
-
return false unless @finals.size == other.num_finals
|
71
|
+
present_state = state
|
72
|
+
string.scan(/./).each do |letter|
|
73
|
+
if @transitions[present_state].nil?
|
74
|
+
return nil
|
75
|
+
else
|
76
|
+
present_state = @transitions[present_state][letter]
|
77
|
+
end
|
78
|
+
end
|
207
79
|
|
208
|
-
|
209
|
-
iso = @states.permutations.find do |p|
|
210
|
-
isomorphism?(p, other)
|
80
|
+
present_state
|
211
81
|
end
|
212
82
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
def reachable?(state)
|
218
|
-
raise DFAException, "Unknown state: #{state}" unless @states.include? state
|
219
|
-
(@reachable_states ||= reachable_states).include? state
|
220
|
-
end
|
221
|
-
|
222
|
-
#Returns true if given state is dead, raises an Exception if the state isn't in the DFA.
|
223
|
-
def dead?(s)
|
224
|
-
raise DFAException, "Unknown state: #{s}" unless @states.include? s
|
225
|
-
|
226
|
-
s != @initial_state and
|
227
|
-
!@finals.include?(s) and
|
228
|
-
@alphabet.all? { |l| [nil,s].include? self[l,s] }
|
229
|
-
end
|
83
|
+
#Processes given +string+ starting in the initial state.
|
84
|
+
def <<(string)
|
85
|
+
self[@initial_state, string]
|
86
|
+
end
|
230
87
|
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
end
|
88
|
+
#Checks if this DFA accepts the given +string+, i.e. halts in a final state after processing the string.
|
89
|
+
def accepts?(string)
|
90
|
+
@final_states.include?( self << string )
|
91
|
+
end
|
236
92
|
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
93
|
+
#Checks if given +state+ is dead, i.e. its not a final state and it hs no outgoing transitions.
|
94
|
+
def dead?(state)
|
95
|
+
raise DFAError, "Unknown state: #{state}" unless @states.include?(state)
|
96
|
+
|
97
|
+
state != @initial_state and
|
98
|
+
! @final_states.include?(state) and
|
99
|
+
@alphabet.all? { |let| [nil,state].include? @transitions[state][let] }
|
100
|
+
end
|
241
101
|
|
242
|
-
|
243
|
-
|
244
|
-
|
102
|
+
#Checks if given +state+ is reachable, i.e. it exists a +string+, such that <tt>self << string == state</tt> is true.
|
103
|
+
def reachable?(state)
|
104
|
+
raise DFAError, "Unknown state: #{state}" unless @states.include?(state)
|
245
105
|
|
246
|
-
|
247
|
-
unless trap
|
248
|
-
trap = @states.sort.last.succ
|
249
|
-
@states << trap
|
106
|
+
reachable_states.include? state
|
250
107
|
end
|
251
108
|
|
252
|
-
|
253
|
-
|
254
|
-
|
109
|
+
#Checks if each state is reachable. See reachable?.
|
110
|
+
def connected?
|
111
|
+
@states.all? { |state| reachable?(state) }
|
112
|
+
end
|
113
|
+
|
114
|
+
#Checks if DFA is complete, i.e. each state accepts every alphabet letter.
|
115
|
+
def complete?
|
116
|
+
@states.all? do |state|
|
117
|
+
@alphabet.all? do |letter|
|
118
|
+
self[state,letter]
|
119
|
+
end
|
255
120
|
end
|
256
121
|
end
|
257
122
|
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
remove_states *@states.find_all { |s| dead?(s) }
|
280
|
-
self
|
281
|
-
end
|
282
|
-
|
283
|
-
=begin
|
284
|
-
Minimizes the DFA. Takes as optional parameter a hash with only processed key :+rename+. The possible values are:
|
285
|
-
[:+new+] States will be renamed to 's0', 's1', ...
|
286
|
-
[:+min+] For each class of states the state with the smallest name is taken
|
287
|
-
[:+join+] The names from each class are joined, so a class with states 'a' and 'b' will yielld a new state 'ab'. This is the default.
|
288
|
-
=end
|
289
|
-
def minimize!(opts = {})
|
290
|
-
connect!
|
291
|
-
remove_dead_states!
|
292
|
-
unless minimal?
|
293
|
-
part = state_partition
|
294
|
-
merge_states *part
|
295
|
-
|
296
|
-
case opts[:rename]
|
297
|
-
when :new : rename_states()
|
298
|
-
when :min : rename_states part.map { |cls| cls.min }
|
299
|
-
else
|
300
|
-
rename_states part.map { |cls| cls.join }
|
123
|
+
#Calculates the transition monoid of the DFA.
|
124
|
+
def transition_monoid
|
125
|
+
maps = [@states.dup]
|
126
|
+
elements = ['id']
|
127
|
+
|
128
|
+
length = 1
|
129
|
+
found_all_elements = false
|
130
|
+
|
131
|
+
until found_all_elements
|
132
|
+
words(length) do |word|
|
133
|
+
found_all_elements = true
|
134
|
+
|
135
|
+
map = get_map(word)
|
136
|
+
unless maps.include?(map)
|
137
|
+
maps << map
|
138
|
+
elements << word
|
139
|
+
found_all_elements = false
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
length += 1
|
301
144
|
end
|
302
|
-
end
|
303
145
|
|
304
|
-
|
305
|
-
end
|
146
|
+
monoid_description = elements.join(',')
|
306
147
|
|
307
|
-
|
308
|
-
|
309
|
-
|
148
|
+
elements[1..-1].each do |element|
|
149
|
+
monoid_description += " #{element},"
|
150
|
+
monoid_description += elements[1..-1].map do |element2|
|
151
|
+
elements[maps.index(get_map(element+element2))]
|
152
|
+
end.join(',')
|
153
|
+
end
|
310
154
|
|
311
|
-
|
312
|
-
raise DFAException, "Wrong number of state names given."
|
155
|
+
RLSM::Monoid[ monoid_description ]
|
313
156
|
end
|
314
157
|
|
315
|
-
|
316
|
-
|
158
|
+
#Checks for equality. A DFA is equal to +other+ iff it has the same alphabet, states, final states, initial state and transitions.
|
159
|
+
def ==(other)
|
160
|
+
begin
|
161
|
+
@alphabet == other.alphabet and
|
162
|
+
@initial_state == other.initial_state and
|
163
|
+
@states.sort == other.states.sort and
|
164
|
+
@final_states.sort == other.final_states.sort and
|
165
|
+
transitions.sort == other.transitions.sort
|
166
|
+
rescue
|
167
|
+
false
|
168
|
+
end
|
317
169
|
end
|
318
170
|
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
@states = arg.clone
|
325
|
-
end
|
171
|
+
#Checks if the DFA is minimal.
|
172
|
+
def minimal?
|
173
|
+
equivalent_state_pairs.empty?
|
174
|
+
end
|
326
175
|
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
end
|
176
|
+
#Alters the DFA in place to an equivalent minmal one.
|
177
|
+
def minimize!
|
178
|
+
remove_unneeded_states!
|
331
179
|
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
180
|
+
unless minimal?
|
181
|
+
state_classes = get_equivalent_state_classes
|
182
|
+
|
183
|
+
states = state_classes.map { |cls| cls.min }
|
336
184
|
|
337
|
-
|
338
|
-
def minimize(opts = {})
|
339
|
-
Marshal.load(Marshal.dump(self)).minimize!(opts)
|
340
|
-
end
|
185
|
+
rename = {}
|
341
186
|
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
end
|
187
|
+
@states.each do |state|
|
188
|
+
rename[state] = state_classes.find { |cls| cls.include?(state) }.min
|
189
|
+
end
|
346
190
|
|
347
|
-
|
348
|
-
def transition_monoid
|
349
|
-
RLSM::Monoid.new get_binary_operation, :rename => true
|
350
|
-
end
|
191
|
+
transitions = {}
|
351
192
|
|
352
|
-
|
353
|
-
|
354
|
-
minimize.transition_monoid
|
355
|
-
end
|
193
|
+
@transitions.each_pair do |state, transition|
|
194
|
+
start = (transitions[rename[state]] ||= {})
|
356
195
|
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
196
|
+
transition.each_pair do |letter, destination|
|
197
|
+
start[letter] = rename[destination]
|
198
|
+
end
|
199
|
+
end
|
361
200
|
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
201
|
+
@initial_state = rename[@initial_state]
|
202
|
+
@states = states
|
203
|
+
@final_states.map! { |state| rename[state] }
|
204
|
+
@final_states.uniq!
|
205
|
+
@transitions = transitions
|
206
|
+
end
|
366
207
|
|
367
|
-
|
368
|
-
def to_re
|
369
|
-
les = []
|
370
|
-
les << initialize_row_for(initial_state)
|
371
|
-
(@states - [initial_state]).each do |state|
|
372
|
-
les << initialize_row_for(state)
|
208
|
+
self
|
373
209
|
end
|
374
210
|
|
375
|
-
#
|
376
|
-
|
377
|
-
|
378
|
-
simplify_les_row(les.pop)[:final]
|
379
|
-
end
|
380
|
-
|
381
|
-
def inspect
|
382
|
-
"<#{self.class}: #{@states.join(',')}>"
|
383
|
-
end
|
384
|
-
|
385
|
-
def to_s
|
386
|
-
output = []
|
387
|
-
output << @states.inject(['']) do |res, state|
|
388
|
-
prefix = ''
|
389
|
-
prefix += '*' if @finals.include? state
|
390
|
-
prefix += '->' if initial_state == state
|
391
|
-
|
392
|
-
res + [prefix + ' ' + state + ' ']
|
211
|
+
#Returns an equivalent minmal DFA.
|
212
|
+
def minimize
|
213
|
+
Marshal.load(Marshal.dump(self)).minimize!
|
393
214
|
end
|
394
215
|
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
216
|
+
#Checks if +self+ is isomorph to other.
|
217
|
+
def =~(other)
|
218
|
+
return false if other.class != self.class ||
|
219
|
+
@alphabet != other.alphabet ||
|
220
|
+
@states.size != other.states.size ||
|
221
|
+
@final_states.size != other.final_states.size ||
|
222
|
+
transitions.size != other.transitions.size
|
223
|
+
|
224
|
+
bijective_maps_to(other).any? do |bijection|
|
225
|
+
transitions.all? do |s1,s2,letter|
|
226
|
+
other.transitions.include?([bijection[s1],bijection[s2],letter])
|
403
227
|
end
|
404
228
|
end
|
405
|
-
|
406
|
-
output << column.clone
|
407
229
|
end
|
408
230
|
|
409
|
-
#
|
410
|
-
|
411
|
-
|
412
|
-
|
231
|
+
#Checks if +self+ is equivalent to +other+, i.e. they accepts the same language.
|
232
|
+
def equivalent_to(other)
|
233
|
+
return false if other.class != self.class
|
234
|
+
|
235
|
+
minimize =~ other.minimize
|
413
236
|
end
|
414
237
|
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
238
|
+
#Calculates a regular expression which represents the same languge as the DFA.
|
239
|
+
def to_regexp
|
240
|
+
les = []
|
241
|
+
les << initialize_row_for(@initial_state)
|
242
|
+
(@states - [@initial_state]).each do |state|
|
243
|
+
les << initialize_row_for(state)
|
244
|
+
end
|
420
245
|
|
421
|
-
|
422
|
-
|
246
|
+
#Solve for the initial state
|
247
|
+
les = update_les(les, simplify_les_row(les.pop)) until les.size == 1
|
423
248
|
|
424
|
-
|
425
|
-
def initialize_row_for(state)
|
426
|
-
row = { :state => state.clone }
|
427
|
-
@states.each do |s|
|
428
|
-
row[s] = RLSM::RE.new
|
249
|
+
simplify_les_row(les.pop)[:final]
|
429
250
|
end
|
430
251
|
|
431
|
-
|
432
|
-
|
252
|
+
#Simply returns self.
|
253
|
+
def to_dfa
|
254
|
+
self
|
433
255
|
end
|
434
256
|
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
row[:final] = RLSM::RE.new
|
257
|
+
#Returns the transition monoid of the equivalent minimal DFA (which is in fact isomorph to the syntactic monoid of the represented language.
|
258
|
+
def to_monoid
|
259
|
+
minimize.transition_monoid
|
439
260
|
end
|
440
261
|
|
441
|
-
|
442
|
-
|
262
|
+
private
|
263
|
+
def initialize_row_for(state)
|
264
|
+
row = { :state => state.clone, :final => RLSM::RegExp.new('') }
|
265
|
+
@states.each do |s|
|
266
|
+
row[s] = RLSM::RegExp.new ''
|
267
|
+
end
|
443
268
|
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
else
|
451
|
-
row[state] += re * r[state]
|
452
|
-
end
|
269
|
+
@alphabet.each do |letter|
|
270
|
+
row[self[state,letter]] |= RLSM::RegExp.new(letter) if self[state,letter]
|
271
|
+
end
|
272
|
+
|
273
|
+
if @final_states.include? state
|
274
|
+
row[:final] = RLSM::RegExp.empty_word
|
453
275
|
end
|
454
276
|
|
455
|
-
row[:final] += re * r[:final]
|
456
277
|
row
|
457
278
|
end
|
458
|
-
end
|
459
279
|
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
280
|
+
def update_les(les,act_row)
|
281
|
+
les.map do |row|
|
282
|
+
re = row[act_row[:state]]
|
283
|
+
@states.each do |state|
|
284
|
+
if state == act_row[:state]
|
285
|
+
row[state] = RLSM::RegExp.new ''
|
286
|
+
else
|
287
|
+
row[state] |= re + act_row[state]
|
288
|
+
end
|
469
289
|
end
|
290
|
+
|
291
|
+
row[:final] |= re + act_row[:final]
|
292
|
+
row
|
470
293
|
end
|
294
|
+
end
|
471
295
|
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
end.inject({}) { |r,v| r[v.first] = v.last; r }
|
483
|
-
res
|
484
|
-
end
|
485
|
-
end
|
486
|
-
|
487
|
-
def get_binary_operation
|
488
|
-
maps = []
|
489
|
-
elements = []
|
490
|
-
l = 0
|
491
|
-
loop do
|
492
|
-
new = false
|
493
|
-
each_word_of_length(l) do |w|
|
494
|
-
map = get_map(w)
|
495
|
-
unless maps.include? map
|
496
|
-
maps << map
|
497
|
-
elements << w
|
498
|
-
new = true
|
296
|
+
def simplify_les_row(row)
|
297
|
+
#Have we something like Ri = ... + xRi + ...
|
298
|
+
if row[row[:state]].parse_tree != RLSM::RE::EmptySet[]
|
299
|
+
re = row[row[:state]].star
|
300
|
+
@states.each do |state|
|
301
|
+
if state == row[:state]
|
302
|
+
row[state] = RLSM::RegExp.new ''
|
303
|
+
else
|
304
|
+
row[state] = re + row[state]
|
305
|
+
end
|
499
306
|
end
|
307
|
+
|
308
|
+
row[:final] = re + row[:final]
|
500
309
|
end
|
501
|
-
break unless new
|
502
|
-
l += 1
|
503
|
-
end
|
504
310
|
|
505
|
-
|
506
|
-
(0...elements.size).to_a.product((0...elements.size).to_a).each do |i,j|
|
507
|
-
(binop[i] ||= [])[j] = maps.index(get_map(elements[i] + elements[j]))
|
311
|
+
row
|
508
312
|
end
|
509
313
|
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
word = (@alphabet.first.to_s*l).scan(/./)
|
515
|
-
yield word.join
|
516
|
-
|
517
|
-
until word == (@alphabet.last.to_s*l).scan(/./)
|
518
|
-
word = get_next_word(word)
|
519
|
-
yield word.join
|
520
|
-
end
|
521
|
-
end
|
314
|
+
def bijective_maps_to(other)
|
315
|
+
bijective_maps = other.states.permutations.map do |perm|
|
316
|
+
Hash[*@states.zip(perm).flatten]
|
317
|
+
end
|
522
318
|
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
if i > 1
|
527
|
-
(1...i).each do |j|
|
528
|
-
word[-j] = @alphabet.first
|
319
|
+
bijective_maps.select do |map|
|
320
|
+
other.initial_state == map[@initial_state] &&
|
321
|
+
@final_states.all? { |fi| other.final_states.include?(map[fi]) }
|
529
322
|
end
|
530
323
|
end
|
531
324
|
|
532
|
-
|
533
|
-
|
325
|
+
def remove_unneeded_states!
|
326
|
+
unneeded_states, states = @states.partition do |state|
|
327
|
+
dead?(state) or not reachable?(state)
|
328
|
+
end
|
534
329
|
|
535
|
-
|
536
|
-
|
537
|
-
|
330
|
+
transitions = self.transitions.reject do |transition|
|
331
|
+
unneeded_states.any? { |dead| transition.include?(dead) }
|
332
|
+
end
|
538
333
|
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
334
|
+
@states = states
|
335
|
+
@reachable_states = @states
|
336
|
+
@final_states &= states
|
337
|
+
@transitions = {}
|
338
|
+
transitions.each do |start,destination,letter|
|
339
|
+
(@transitions[start] ||= {})[letter] = destination
|
340
|
+
end
|
545
341
|
end
|
342
|
+
|
343
|
+
def equivalent_state_pairs
|
344
|
+
@states << nil unless complete?
|
546
345
|
|
547
|
-
|
548
|
-
@alphabet.each do |l|
|
549
|
-
tr = @transitions[l].map.reject { |p| states.any? { |s| p.include? s } }
|
550
|
-
@transitions[l] = tr.inject({}) { |r,x| r[x.first] = x.last; r }
|
551
|
-
end
|
552
|
-
end
|
346
|
+
different, indifferent = [], []
|
553
347
|
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
348
|
+
states[0..-2].each_with_index do |state1, i|
|
349
|
+
states[(i+1)..-1].each do |state2|
|
350
|
+
if @final_states.include?(state1) ^ @final_states.include?(state2)
|
351
|
+
different << [state1, state2]
|
352
|
+
else
|
353
|
+
indifferent << [state1, state2]
|
354
|
+
end
|
355
|
+
end
|
559
356
|
end
|
560
|
-
res
|
561
|
-
end
|
562
|
-
end
|
563
357
|
|
564
|
-
|
565
|
-
|
566
|
-
|
358
|
+
begin
|
359
|
+
new_different = indifferent.select do |state1, state2|
|
360
|
+
|
361
|
+
@alphabet.any? do |letter|
|
362
|
+
test_pair = [self[state1,letter], self[state2,letter]]
|
363
|
+
different.include?(test_pair) or
|
364
|
+
different.include?(test_pair.reverse)
|
365
|
+
end
|
366
|
+
end
|
567
367
|
|
568
|
-
|
569
|
-
|
368
|
+
different |= new_different
|
369
|
+
indifferent -= new_different
|
370
|
+
end until new_different.empty?
|
570
371
|
|
571
|
-
|
572
|
-
|
573
|
-
|
372
|
+
@states.compact!
|
373
|
+
|
374
|
+
indifferent.reject { |pair| pair.include?(nil) }
|
574
375
|
end
|
575
|
-
end
|
576
376
|
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
cls.min
|
582
|
-
end
|
583
|
-
end
|
377
|
+
def get_equivalent_state_classes
|
378
|
+
pairs = equivalent_state_pairs
|
379
|
+
|
380
|
+
classes = []
|
584
381
|
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
res[-1] |= pair
|
382
|
+
@states.each do |state|
|
383
|
+
next if classes.any? { |cls| cls.include? state }
|
384
|
+
cls = [state]
|
385
|
+
cls |= pairs.find_all { |pair| pair.include?(state) }
|
386
|
+
classes << cls.flatten.compact.uniq
|
591
387
|
end
|
592
388
|
|
593
|
-
|
594
|
-
res.uniq
|
389
|
+
classes
|
595
390
|
end
|
596
|
-
end
|
597
391
|
|
598
|
-
|
599
|
-
|
600
|
-
|
392
|
+
def parse_initial_state(description)
|
393
|
+
unless description.count('}') == 1
|
394
|
+
raise DFAError, "None or at least two initial states."
|
395
|
+
end
|
396
|
+
|
397
|
+
@initial_state = description[/\}\s*\*?(\w+)/,1]
|
601
398
|
end
|
602
|
-
end
|
603
399
|
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
400
|
+
def parse_final_states(description)
|
401
|
+
@final_states = []
|
402
|
+
|
403
|
+
desc = StringScanner.new(description)
|
404
|
+
loop do
|
405
|
+
break unless desc.scan(/[^*]*\*\}?/)
|
406
|
+
final_state = desc.scan(/\w+/)
|
407
|
+
@final_states << final_state unless @final_states.include?(final_state)
|
609
408
|
end
|
610
409
|
end
|
611
|
-
end
|
612
410
|
|
613
|
-
|
614
|
-
|
411
|
+
def parse_states(description)
|
412
|
+
desc = description.gsub(/[*}]/,'')
|
413
|
+
desc.gsub!(/\s+/,' ')
|
414
|
+
desc.gsub!(/\s*-+(\w+,?)+-+>\s*/, ' ')
|
615
415
|
|
616
|
-
|
617
|
-
new_dis = table_fill_step distinguished, equivalent
|
618
|
-
break if new_dis.empty?
|
619
|
-
distinguished |= new_dis
|
620
|
-
equivalent -= new_dis
|
416
|
+
@states = desc.split.uniq
|
621
417
|
end
|
622
|
-
equivalent.reject { |p| p.include? nil }
|
623
|
-
end
|
624
418
|
|
625
|
-
|
626
|
-
|
627
|
-
|
419
|
+
def parse_transitions(description)
|
420
|
+
@alphabet = []
|
421
|
+
@transitions = {}
|
422
|
+
|
423
|
+
return unless description =~ /[->]/
|
424
|
+
|
425
|
+
parser = transition_parser(description)
|
426
|
+
|
427
|
+
loop do
|
428
|
+
transaction = parser.scan(/(\w+)-((\w+,?)+)->(\w+)/)
|
429
|
+
if transaction
|
430
|
+
start = parser[1]
|
431
|
+
labels = parser[2].split(',').uniq
|
432
|
+
destination = parser[4]
|
628
433
|
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
434
|
+
insert_transition(start,destination,labels)
|
435
|
+
insert_alphabet_letters(labels)
|
436
|
+
elsif parser.scan(/\w+/)
|
437
|
+
#do nothing (states already parsed)
|
438
|
+
else
|
439
|
+
raise DFAError, "Parse Error, could not parse #{description}"
|
635
440
|
end
|
441
|
+
|
442
|
+
break unless parser.scan(/ /)
|
636
443
|
end
|
637
|
-
new_states.compact!
|
638
|
-
end
|
639
444
|
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
445
|
+
unless parser.eos?
|
446
|
+
raise DFAError, "Parse Error, could not parse #{description}"
|
447
|
+
end
|
448
|
+
|
449
|
+
@alphabet = @alphabet.sort
|
450
|
+
end
|
451
|
+
|
452
|
+
def transition_parser(description)
|
453
|
+
#simplifying the description for easier parsing
|
454
|
+
desc = description.gsub(/[*}]/, '')[/^\s*(.+?)\s*$/,1]
|
455
|
+
desc.gsub!(/\s+/, ' ')
|
456
|
+
desc.gsub!(/\s*-+\s*/,'-')
|
457
|
+
desc.gsub!(/>\s*/,'>')
|
458
|
+
desc.gsub!(/\s*,\s*/,',')
|
459
|
+
|
460
|
+
StringScanner.new(desc)
|
461
|
+
end
|
462
|
+
|
463
|
+
def insert_transition(start,destination,labels)
|
464
|
+
labels.each do |label|
|
465
|
+
trans = (@transitions[start] ||= {})
|
466
|
+
if trans[label].nil?
|
467
|
+
trans[label] = destination
|
468
|
+
elsif trans[label] != destination
|
469
|
+
raise DFAError, "Parse Error: Transition labels must be uniq."
|
470
|
+
end
|
471
|
+
end
|
647
472
|
end
|
648
|
-
end
|
649
473
|
|
650
|
-
|
651
|
-
|
652
|
-
return false unless o.initial_state == p[@initial_state]
|
653
|
-
|
654
|
-
unless @finals.map { |s| p[s] }.all? { |f| o.finals.include? f }
|
655
|
-
return false
|
474
|
+
def insert_alphabet_letters(labels)
|
475
|
+
@alphabet |= labels
|
656
476
|
end
|
657
477
|
|
658
|
-
|
659
|
-
|
478
|
+
def reachable_states
|
479
|
+
@reachable_states ||= calc_reachable_states
|
660
480
|
end
|
661
|
-
end
|
662
481
|
|
663
|
-
|
664
|
-
|
665
|
-
|
482
|
+
def calc_reachable_states
|
483
|
+
reachable = []
|
484
|
+
new_states = [@initial_state]
|
485
|
+
|
486
|
+
until new_states.empty?
|
487
|
+
reachable |= new_states
|
488
|
+
new_states = []
|
489
|
+
reachable.each do |state|
|
490
|
+
@alphabet.each do |letter|
|
491
|
+
new_state = self[state,letter]
|
492
|
+
|
493
|
+
unless new_state.nil? or reachable.include?(new_state)
|
494
|
+
new_states << new_state
|
495
|
+
end
|
496
|
+
end
|
497
|
+
end
|
498
|
+
new_states.compact!
|
499
|
+
end
|
500
|
+
|
501
|
+
reachable
|
666
502
|
end
|
667
|
-
end
|
668
503
|
|
669
|
-
def validate_all_states_matches(args)
|
670
|
-
unless args[:states].include? args[:initial]
|
671
|
-
raise DFAException, "Given initial state isn't in :states."
|
672
|
-
end
|
673
504
|
|
674
|
-
|
675
|
-
|
676
|
-
raise(DFAException,
|
677
|
-
"Given final states #{bad.inspect} aren't in :states.")
|
505
|
+
def get_map(word)
|
506
|
+
@states.map { |state| self[state,word] }
|
678
507
|
end
|
679
508
|
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
unless bad.empty?
|
684
|
-
raise(DFAException,
|
685
|
-
("Given transitions #{bad.inspect}" +
|
686
|
-
"have states which aren't in :states."))
|
687
|
-
end
|
688
|
-
end
|
509
|
+
def words(length)
|
510
|
+
word = [@alphabet.first]*length
|
511
|
+
last = [@alphabet.last]*length
|
689
512
|
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
raise(DFAException,
|
694
|
-
"#{key} has duplicated entries: #{args[key].inspect}")
|
513
|
+
until word == last
|
514
|
+
yield word.join
|
515
|
+
word = get_next(word)
|
695
516
|
end
|
517
|
+
|
518
|
+
yield last.join
|
696
519
|
end
|
697
|
-
end
|
698
520
|
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
end
|
707
|
-
|
708
|
-
def validate_transition_uniquness(args)
|
709
|
-
args[:alphabet].product(args[:states]).each do |c,s|
|
710
|
-
tr = args[:transitions].find_all { |l,s1,s2| l == c and s1 == s }
|
711
|
-
if tr.size > 1
|
712
|
-
raise DFAException, ("There are multiple transitons from state " +
|
713
|
-
"#{tr.first[1]} with label #{tr.first[0]}.")
|
521
|
+
def get_next(word)
|
522
|
+
i = (1..word.length).find { |i| word[-i] != @alphabet.last }
|
523
|
+
word[-i] = @alphabet[@alphabet.index(word[-i])+1]
|
524
|
+
if i > 1
|
525
|
+
(1...i).each do |j|
|
526
|
+
word[-j] = @alphabet.first
|
527
|
+
end
|
714
528
|
end
|
529
|
+
|
530
|
+
word
|
715
531
|
end
|
716
|
-
end
|
717
|
-
end
|
532
|
+
end # of class DFA
|
533
|
+
end # of module RLSM
|