tokn 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
data/lib/tokn/state.rb CHANGED
@@ -1,320 +1,317 @@
1
1
  require 'set'
2
2
  require_relative 'tools'
3
- req 'tokn_const'
4
3
 
5
-
6
- # A state within a state machine (NFA or DFA); also, various utility functions
7
- # for manipulating state machines. Observe that a state machine can be
8
- # referred to by its start state.
9
- #
10
- # Each state has a set of directed edges to other states, where each edge is
11
- # labelled with a CodeSet.
12
- #
13
- # It also has a unique id (unique within a particular state machine),
14
- # and a (boolean) final state flag.
15
- #
16
- # For debug purposes, both the state and its edges can be labelled.
17
- #
18
- class State
19
- include Tokn
20
-
21
- attr_accessor :id
22
- attr_accessor :finalState
23
- alias_method :finalState?, :finalState
24
- attr_accessor :label
4
+ module ToknInternal
25
5
 
26
- # Edges are a list of [label:CharSetRange, dest:State] pairs
27
- attr_reader :edges
28
-
29
- # Produce a readable description of an NFA, for debug purposes
30
- #
31
- # > st start state
6
+ # A state within a state machine (NFA or DFA); also, various utility functions
7
+ # for manipulating state machines. Observe that a state machine can be
8
+ # referred to by its start state.
32
9
  #
33
- def self.dumpNFA(st)
34
- str = "NFA:\n"
35
- map,_,_ = st.reachableStates
36
- map.each do |s|
37
- str += " "+d(s)+"\n"
38
- str += " edges= "+d(s.edges)+"\n"
39
- s.edges.each{ |lbl,dest| str += " "+d(lbl)+" ==> "+d(dest)+"\n"}
40
- end
41
- str
42
- end
43
-
44
- def hash
45
- return @id
46
- end
47
-
48
- def eql?(other)
49
- return id == other.id
50
- end
51
-
52
- def initialize(id)
53
- @edges = []
54
- @id = id
55
- end
56
-
57
- def clearEdges
58
- @edges.clear
59
- end
60
-
61
- # Add an edge
62
- # codeSet : the character codes to label it with
63
- # destState : destination state
64
- #
65
- def addEdge(codeSet,destState)
66
- @edges.push([codeSet, destState])
67
- end
68
-
69
- # Add a e-transition edge
70
- # destState : destination state
71
- #
72
- def addEps(destState)
73
- addEdge(CodeSet.new(EPSILON), destState)
74
- end
75
-
76
- def inspect
77
- name
78
- end
79
-
80
- def name
81
- nm = 'S' + d(id)
82
- if label
83
- nm += ": "+label
84
- end
85
- nm
86
- end
87
-
88
- # Normalize a state machine.
10
+ # Each state has a set of directed edges to other states, where each edge is
11
+ # labelled with a CodeSet.
89
12
  #
90
- # For each state:
91
- # [] merge edges that go to a common state
92
- # [] delete edges that have empty labels
93
- # [] sort edges by destination state ids
13
+ # It also has a unique id (unique within a particular state machine),
14
+ # and a (boolean) final state flag.
94
15
  #
95
- # > start state
16
+ # For debug purposes, both the state and its edges can be labelled.
96
17
  #
97
- def self.normalizeStates(startState)
98
- stateSet, _,_ = startState.reachableStates
99
- stateSet.map{|s| s.normalize}
100
- end
101
-
102
-
103
- # Generate a PDF of the state machine;
104
- # Makes a system call to the dot utility to convert a .dot file to a .pdf
105
- #
106
- def generatePDF(title = "nfa")
107
- stateList = {}
18
+ class State
108
19
 
109
- startState = self
110
- genAux(stateList, startState)
20
+ attr_accessor :id
21
+ attr_accessor :finalState
22
+ alias_method :finalState?, :finalState
23
+ attr_accessor :label
111
24
 
112
- g = ""
113
- g += "digraph "+title+" {\n"
114
- g += " '' [shape=none]\n"
25
+ # Edges are a list of [label:CharSetRange, dest:State] pairs
26
+ attr_reader :edges
115
27
 
116
- stateList.each_value do |s|
117
- g += " '" + s.name + "' [shape="
118
- if s.finalState?
119
- g += "doubleoctagon"
120
- else
121
- g += "octagon"
28
+ # Produce a readable description of an NFA, for debug purposes
29
+ #
30
+ # > st start state
31
+ #
32
+ def self.dumpNFA(st)
33
+ str = "NFA:\n"
34
+ map,_,_ = st.reachableStates
35
+ map.each do |s|
36
+ str += " "+d(s)+"\n"
37
+ str += " edges= "+d(s.edges)+"\n"
38
+ s.edges.each{ |lbl,dest| str += " "+d(lbl)+" ==> "+d(dest)+"\n"}
122
39
  end
123
- g += "]\n"
40
+ str
124
41
  end
125
42
 
126
- g += "\n"
127
- g += " '' -> '" + startState.name + "'\n"
128
- stateList.each_value do |s|
129
- s.edges.each do |crs, s2|
130
- g += " '"+s.name+"' -> '" + s2.name + "' [label='"
131
- g += d(crs)
132
- g += "'][fontname=Courier][fontsize=12]\n"
133
- end
43
+ def hash
44
+ return @id
134
45
  end
135
46
 
136
- g += "\n}\n"
137
- g.gsub!( /'/, '"' )
138
-
139
- dotToPDF(g,title)
140
- end
141
-
47
+ def eql?(other)
48
+ return id == other.id
49
+ end
142
50
 
143
- # Normalize a state
144
- #
145
- # [] merge edges that go to a common state
146
- # [] delete edges that have empty labels
147
- # [] sort edges by destination state ids
148
- #
149
- def normalize()
51
+ def initialize(id)
52
+ @edges = []
53
+ @id = id
54
+ end
150
55
 
151
- db = false
56
+ def clearEdges
57
+ @edges.clear
58
+ end
152
59
 
153
- !db || pr("\n\nnormalize state:\n %s\nedges=\n%s\n",d(self),d(@edges))
60
+ # Add an edge
61
+ # codeSet : the character codes to label it with
62
+ # destState : destination state
63
+ #
64
+ def addEdge(codeSet,destState)
65
+ @edges.push([codeSet, destState])
66
+ end
154
67
 
155
- @edges.sort!{|x,y|
156
- label1,dest1 = x
157
- label2,dest2 = y
158
- dest1.id <=> dest2.id
159
- }
160
- !db || pr(" sorted edges: %s\n",d(@edges))
68
+ # Add a e-transition edge
69
+ # destState : destination state
70
+ #
71
+ def addEps(destState)
72
+ addEdge(CodeSet.new(EPSILON), destState)
73
+ end
161
74
 
162
- newEdges = []
163
- prevLabel, prevDest = nil,nil
75
+ def inspect
76
+ name
77
+ end
78
+
79
+ def name
80
+ nm = 'S' + d(id)
81
+ if label
82
+ nm += ": "+label
83
+ end
84
+ nm
85
+ end
86
+
87
+ # Normalize a state machine.
88
+ #
89
+ # For each state:
90
+ # [] merge edges that go to a common state
91
+ # [] delete edges that have empty labels
92
+ # [] sort edges by destination state ids
93
+ #
94
+ # > start state
95
+ #
96
+ def self.normalizeStates(startState)
97
+ stateSet, _,_ = startState.reachableStates
98
+ stateSet.map{|s| s.normalize}
99
+ end
164
100
 
165
- edges.each do |label,dest|
166
- !db || pr(" processing edge %s, %s\n",d(label),d(dest))
167
101
 
168
- if prevDest and prevDest.id == dest.id
169
- # changed = true
170
- !db || pr(" adding set %s to prevLabel %s...\n",d(label),d(prevLabel))
171
- prevLabel.addSet(label)
172
- !db || pr(" ...now %s\n",d(prevLabel))
173
- else
174
- if prevDest
175
- newEdges.push([prevLabel,prevDest])
176
- end
177
- # Must start a fresh copy! Don't want to modify the original label.
178
- prevLabel = label.makeCopy()
179
- prevDest = dest
180
- !db || pr(" pushed onto new edges\n")
102
+ # Generate a PDF of the state machine;
103
+ # Makes a system call to the dot utility to convert a .dot file to a .pdf
104
+ #
105
+ def generatePDF(title = "nfa")
106
+ stateList = {}
107
+
108
+ startState = self
109
+ genAux(stateList, startState)
110
+
111
+ g = ""
112
+ g += "digraph "+title+" {\n"
113
+ g += " '' [shape=none]\n"
114
+
115
+ stateList.each_value do |s|
116
+ g += " '" + s.name + "' [shape="
117
+ if s.finalState?
118
+ g += "doubleoctagon"
119
+ else
120
+ g += "octagon"
181
121
  end
122
+ g += "]\n"
182
123
  end
183
- if prevDest
184
- newEdges.push([prevLabel,prevDest])
185
- end
186
-
187
- @edges = newEdges
188
- !db || pr("edges now: %s\n",d(@edges))
189
- end
190
-
191
-
192
- # Duplicate the NFA reachable from this state, possibly with new ids
193
- #
194
- # > dupBaseId : lowest id to use for duplicate; if nil, uses
195
- # next available id
196
- # < [ map of original states => duplicate states;
197
- # 1 + highest id in new NFA ]
198
- #
199
- def duplicateNFA(dupBaseId = nil)
200
- oldStates, oldMinId, oldMaxId = reachableStates()
201
- dupBaseId ||= oldMaxId
202
124
 
203
-
204
- oldToNewStateMap = {}
125
+ g += "\n"
126
+ g += " '' -> '" + startState.name + "'\n"
127
+ stateList.each_value do |s|
128
+ s.edges.each do |crs, s2|
129
+ g += " '"+s.name+"' -> '" + s2.name + "' [label='"
130
+ g += d(crs)
131
+ g += "'][fontname=Courier][fontsize=12]\n"
132
+ end
133
+ end
205
134
 
206
- oldStates.each do |s|
207
- s2 = State.new((s.id - oldMinId) + dupBaseId)
208
- s2.finalState = s.finalState?
209
- s2.label = s.label
135
+ g += "\n}\n"
136
+ g.gsub!( /'/, '"' )
210
137
 
211
- oldToNewStateMap[s] = s2
138
+ dotToPDF(g,title)
212
139
  end
140
+
213
141
 
214
- oldStates.each do |s|
215
- s2 = oldToNewStateMap[s]
216
- s.edges.each{ |lbl,dest| s2.addEdge(lbl, oldToNewStateMap[dest])}
142
+ # Normalize a state
143
+ #
144
+ # [] merge edges that go to a common state
145
+ # [] delete edges that have empty labels
146
+ # [] sort edges by destination state ids
147
+ #
148
+ def normalize()
149
+
150
+ db = false
151
+
152
+ !db || pr("\n\nnormalize state:\n %s\nedges=\n%s\n",d(self),d(@edges))
153
+
154
+ @edges.sort!{|x,y|
155
+ label1,dest1 = x
156
+ label2,dest2 = y
157
+ dest1.id <=> dest2.id
158
+ }
159
+ !db || pr(" sorted edges: %s\n",d(@edges))
160
+
161
+ newEdges = []
162
+ prevLabel, prevDest = nil,nil
163
+
164
+ edges.each do |label,dest|
165
+ !db || pr(" processing edge %s, %s\n",d(label),d(dest))
166
+
167
+ if prevDest and prevDest.id == dest.id
168
+ # changed = true
169
+ !db || pr(" adding set %s to prevLabel %s...\n",d(label),d(prevLabel))
170
+ prevLabel.addSet(label)
171
+ !db || pr(" ...now %s\n",d(prevLabel))
172
+ else
173
+ if prevDest
174
+ newEdges.push([prevLabel,prevDest])
175
+ end
176
+ # Must start a fresh copy! Don't want to modify the original label.
177
+ prevLabel = label.makeCopy()
178
+ prevDest = dest
179
+ !db || pr(" pushed onto new edges\n")
180
+ end
181
+ end
182
+ if prevDest
183
+ newEdges.push([prevLabel,prevDest])
184
+ end
185
+
186
+ @edges = newEdges
187
+ !db || pr("edges now: %s\n",d(@edges))
217
188
  end
218
-
219
- [oldToNewStateMap, (oldMaxId - oldMinId) + dupBaseId]
220
- end
221
-
222
-
223
189
 
224
- # Construct the reverse of the NFA starting at this state
225
- # < start state of reversed NFA
226
- #
227
- def reverseNFA()
228
-
229
- stateSet, minId, maxId = reachableStates()
230
-
231
- edgeList = []
232
-
233
- newStartStateList = []
234
- newFinalStateList = []
235
-
236
- newStateMap = {}
237
-
238
- stateSet.each do |s|
239
-
240
- u = State.new(s.id)
241
- newStateMap[u.id] = u
190
+
191
+ # Duplicate the NFA reachable from this state, possibly with new ids
192
+ #
193
+ # > dupBaseId : lowest id to use for duplicate; if nil, uses
194
+ # next available id
195
+ # < [ map of original states => duplicate states;
196
+ # 1 + highest id in new NFA ]
197
+ #
198
+ def duplicateNFA(dupBaseId = nil)
199
+ oldStates, oldMinId, oldMaxId = reachableStates()
200
+ dupBaseId ||= oldMaxId
242
201
 
243
- if s.id == self.id
244
- newFinalStateList.push(u)
245
- u.finalState = true
246
- end
202
+
203
+ oldToNewStateMap = {}
247
204
 
248
- if s.finalState?
249
- newStartStateList.push(u)
205
+ oldStates.each do |s|
206
+ s2 = State.new((s.id - oldMinId) + dupBaseId)
207
+ s2.finalState = s.finalState?
208
+ s2.label = s.label
209
+
210
+ oldToNewStateMap[s] = s2
250
211
  end
251
212
 
252
- s.edges.each {|lbl, dest| edgeList.push([dest.id, s.id, lbl])}
213
+ oldStates.each do |s|
214
+ s2 = oldToNewStateMap[s]
215
+ s.edges.each{ |lbl,dest| s2.addEdge(lbl, oldToNewStateMap[dest])}
216
+ end
253
217
 
218
+ [oldToNewStateMap, (oldMaxId - oldMinId) + dupBaseId]
254
219
  end
255
-
256
- edgeList.each do |srcId, destId, lbl|
257
- srcState = newStateMap[srcId]
258
- destState = newStateMap[destId]
259
- srcState.addEdge(lbl, destState)
260
- end
261
-
262
- # Create a distinguished start node that points to each of the start nodes
263
- w = State.new(maxId)
264
- newStartStateList.each {|s| w.addEps(s)}
265
- w
266
- end
267
220
 
268
-
269
- # Build set of states reachable from this state
270
- #
271
- # > list of starting states
272
- # < [ set, set of states reachable from those states
273
- # minId, lowest id in set
274
- # maxId 1 + highest id in set
275
- # ]
276
- #
277
- def reachableStates()
278
- set = Set.new
279
- stack = []
280
- stack.push(self)
281
-
282
- maxId = nil
283
- minId = nil
221
+
284
222
 
285
- while !stack.empty?
286
- st = stack.pop
287
- set.add(st)
223
+ # Construct the reverse of the NFA starting at this state
224
+ # < start state of reversed NFA
225
+ #
226
+ def reverseNFA()
227
+
228
+ stateSet, minId, maxId = reachableStates()
229
+
230
+ edgeList = []
231
+
232
+ newStartStateList = []
233
+ newFinalStateList = []
288
234
 
289
- if !minId || minId > st.id
290
- minId = st.id
235
+ newStateMap = {}
236
+
237
+ stateSet.each do |s|
238
+
239
+ u = State.new(s.id)
240
+ newStateMap[u.id] = u
241
+
242
+ if s.id == self.id
243
+ newFinalStateList.push(u)
244
+ u.finalState = true
245
+ end
246
+
247
+ if s.finalState?
248
+ newStartStateList.push(u)
249
+ end
250
+
251
+ s.edges.each {|lbl, dest| edgeList.push([dest.id, s.id, lbl])}
252
+
291
253
  end
292
- if !maxId || maxId <= st.id
293
- maxId = 1 + st.id
254
+
255
+ edgeList.each do |srcId, destId, lbl|
256
+ srcState = newStateMap[srcId]
257
+ destState = newStateMap[destId]
258
+ srcState.addEdge(lbl, destState)
294
259
  end
295
260
 
296
- st.edges.each do |lbl, dest|
297
- if set.add?(dest)
298
- stack.push(dest)
261
+ # Create a distinguished start node that points to each of the start nodes
262
+ w = State.new(maxId)
263
+ newStartStateList.each {|s| w.addEps(s)}
264
+ w
265
+ end
266
+
267
+
268
+ # Build set of states reachable from this state
269
+ #
270
+ # > list of starting states
271
+ # < [ set, set of states reachable from those states
272
+ # minId, lowest id in set
273
+ # maxId 1 + highest id in set
274
+ # ]
275
+ #
276
+ def reachableStates()
277
+ set = Set.new
278
+ stack = []
279
+ stack.push(self)
280
+
281
+ maxId = nil
282
+ minId = nil
283
+
284
+ while !stack.empty?
285
+ st = stack.pop
286
+ set.add(st)
287
+
288
+ if !minId || minId > st.id
289
+ minId = st.id
290
+ end
291
+ if !maxId || maxId <= st.id
292
+ maxId = 1 + st.id
293
+ end
294
+
295
+ st.edges.each do |lbl, dest|
296
+ if set.add?(dest)
297
+ stack.push(dest)
298
+ end
299
299
  end
300
300
  end
301
+ [set, minId, maxId]
301
302
  end
302
- [set, minId, maxId]
303
- end
304
-
303
+
305
304
 
306
-
307
-
308
- end
309
-
310
-
311
-
312
- private
313
-
314
- def genAux(stateList, st)
315
- if not stateList.member?(st.name)
316
- stateList[st.name] = st
317
- st.edges.each {|label, dest| genAux(stateList, dest)}
305
+ private
306
+
307
+ def genAux(stateList, st)
308
+ if not stateList.member?(st.name)
309
+ stateList[st.name] = st
310
+ st.edges.each {|label, dest| genAux(stateList, dest)}
311
+ end
312
+ end
313
+
318
314
  end
319
- end
315
+
316
+ end # module ToknInternal
320
317