Subversion Repositories programming

Rev

Rev 138 | Rev 142 | Go to most recent revision | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 138 Rev 140
Line 11... Line 11...
11
# - Made the nfa.__next_states() function always return a valid reference
11
# - Made the nfa.__next_states() function always return a valid reference
12
#   to a list in the dictionary. This means you should NEVER use
12
#   to a list in the dictionary. This means you should NEVER use
13
#   self.trans_func[(state, letter)] in code anywhere now :)
13
#   self.trans_func[(state, letter)] in code anywhere now :)
14
# - Final states are now checked for validity.
14
# - Final states are now checked for validity.
15
#
15
#
-
 
16
# - 2005-10-19
-
 
17
# - Everything now works, with a menu even.
-
 
18
#
-
 
19
# - 2005-10-20
-
 
20
# - Added the check for <Python-2.3 compatibility.
-
 
21
# - Commented the source more.
-
 
22
#
16
 
23
 
17
################################################################################
24
################################################################################
18
# IMPORTANT NOTES
25
# IMPORTANT NOTES
19
################################################################################
26
################################################################################
-
 
27
# The DFA table format:
-
 
28
#
-
 
29
# [ [Final, 0, NFA_Equivalent, letter1, letter2, ..., lettern],
-
 
30
# [ [Final, 1, NFA_Equivalent, letter1, letter2, ..., lettern],
-
 
31
# [ [Final, 2, NFA_Equivalent, letter1, letter2, ..., lettern] ]
-
 
32
#
-
 
33
################################################################################
-
 
34
# The nfa.trans_func format:
-
 
35
#
-
 
36
# dict[(state, letter)] = [list, of, states]
-
 
37
#
20
################################################################################
38
################################################################################
21
 
39
 
-
 
40
# Check for <Python-2.3 compatibility (boolean values)
-
 
41
try:
-
 
42
  True, False
-
 
43
except NameError:
-
 
44
  (True, False) = (1, 0)
-
 
45
 
22
import sys, string
46
import sys, string
23
 
47
 
24
class nfa:
48
class nfa:
25
    """Implements a Non-Deterministic Finite Automaton"""
49
    """Implements a Non-Deterministic Finite Automaton"""
26
 
50
 
27
    def __init__(self):
51
    def __init__(self):
-
 
52
        """Constructor for the nfa object"""
28
        self.__clear()
53
        self.__clear()
29
 
54
 
30
    def __clear(self):
55
    def __clear(self):
-
 
56
        """Clear all instance variables of the nfa class"""
31
        self.trans_func = {}
57
        self.trans_func = {}
32
        self.num_states = 0
58
        self.num_states = 0
33
        self.final_states = []
59
        self.final_states = []
34
        self.initial_state = 0
60
        self.initial_state = 0
35
        self.possible_letters = []
61
        self.possible_letters = []
36
        self.dfa_table = []
62
        self.dfa_table = []
37
 
63
 
38
    def __state_in_range(self, state):
64
    def __state_in_range(self, state):
39
        """Check if a given state is in the acceptable range"""
65
        """Check if a given state is in the acceptable range"""
40
 
66
 
-
 
67
        # Check to make sure this can be converted to an int.
-
 
68
        # Return False if the conversion fails.
41
        try:
69
        try:
42
            temp = int(state)
70
            temp = int(state)
43
        except (TypeError, ValueError):
71
        except (TypeError, ValueError):
44
            return False
72
            return False
45
 
73
 
-
 
74
        # Make sure this is a state in the range
46
        if temp >= 0 and temp < self.num_states:
75
        if temp >= 0 and temp < self.num_states:
47
            return True
76
            return True
48
 
77
 
-
 
78
        # We weren't in the correct range, so return False
49
        return False
79
        return False
50
 
80
 
51
    def __input_num_states(self):
81
    def __input_num_states(self):
52
        """Get the number of states this automaton will have"""
82
        """Ask the user (nicely) for the number of states this automaton will have"""
53
 
83
 
54
        done = False
84
        done = False
55
        while not done:
85
        while not done:
56
            num_states = raw_input('How many states in this automaton: ')
86
            num_states = raw_input('How many states in this automaton: ')
57
            print
87
            print
58
 
88
 
-
 
89
            # Make sure we can convert the value successfully, then set
-
 
90
            # the num_states variable.
59
            try:
91
            try:
60
                self.num_states = int(num_states)
92
                self.num_states = int(num_states)
61
                done = True
93
                done = True
62
            except (TypeError, ValueError):
94
            except (TypeError, ValueError):
63
                print 'Bad input, not an integer, try again'
95
                print 'Bad input, not an integer, try again'
64
                print
96
                print
65
 
97
 
66
    def __input_final_states(self):
98
    def __input_final_states(self):
67
        """Get final states for this automaton"""
99
        """Ask the user for a list of final states for this automaton"""
68
 
100
 
69
        print 'Enter final states on one line, seperated by spaces'
101
        print 'Enter final states on one line, seperated by spaces'
70
 
102
 
71
        done = False
103
        done = False
72
        while not done:
104
        while not done:
73
            final_states = raw_input('Final states: ')
105
            final_states = raw_input('Final states: ')
74
            print
106
            print
75
 
107
 
-
 
108
            # Check each value entered, one by one, and set bad_data if there
-
 
109
            # was a state out of the valid range
76
            bad_data = False
110
            bad_data = False
77
            for i in final_states.split():
111
            for i in final_states.split():
78
                if not self.__state_in_range(i):
112
                if not self.__state_in_range(i):
79
                    print 'State %s out of range. All input discarded.' % (i, )
113
                    print 'State %s out of range. All input discarded.' % (i, )
80
                    print 'Try again please'
114
                    print 'Try again please'
81
                    print
115
                    print
82
                    bad_data = True
116
                    bad_data = True
83
                    break
117
                    break
84
 
118
 
85
            # if we left the for loop with bad data
119
            # If we left the for loop with bad data, start over.
86
            if bad_data:
120
            if bad_data:
87
                continue
121
                continue
88
 
122
 
89
            # all data is good
123
            # All data is good, read the values into the final_states list.
90
            for i in final_states.split():
124
            for i in final_states.split():
91
                if self.__state_in_range(i):
125
                if self.__state_in_range(i):
92
                    self.final_states.append(int(i))
126
                    self.final_states.append(int(i))
93
 
127
 
94
            done = True
128
            done = True
95
 
129
 
96
    def __input_all_edges(self):
130
    def __input_all_edges(self):
97
        """Read all of the edges for this automaton"""
131
        """Ask the user to input all of the edges for this automaton"""
98
 
132
 
99
        print 'Edges take the form "from HERE, by LETTER, to THERE"'
133
        print 'Edges take the form "from HERE, by LETTER, to THERE"'
100
        print 'Enter something like: "1 a 1" to represent the following'
134
        print 'Enter something like: "1 a 3" to represent the following'
101
        print 'from q1, by a, go to q1'
135
        print 'from q1, by a, go to q3'
102
        print
136
        print
103
        print 'Conditions:'
137
        print 'RULES:'
-
 
138
        print '------------------------------------------------------'
104
        print '1. Blank line to end'
139
        print '1. Enter a blank line to stop reading edges'
105
        print '2. ^ is the empty string'
140
        print '2. ^ is the empty string'
-
 
141
        print '3. Enter one letter at a time (no multi-letter states)'
106
        print
142
        print
107
 
143
 
-
 
144
        # Read edges, one by one. Check them as they are entered.
108
        done = False
145
        done = False
109
        while not done:
146
        while not done:
110
            edge = raw_input('Edge: ')
147
            edge = raw_input('Edge: ')
111
 
148
 
-
 
149
            # Check to see if we got a blank line
112
            if len(edge) == 0:
150
            if len(edge) == 0:
113
                done = True
151
                done = True
114
                continue
152
                continue
115
 
153
 
-
 
154
            # If we didn't get exactly 3 arguments, try again
116
            if len(edge.split()) != 3:
155
            if len(edge.split()) != 3:
117
                print 'Bad edge entered'
156
                print 'Bad edge entered.'
-
 
157
                print 'Exactly 3 arguments are required for a valid edge.'
118
                print
158
                print
119
                continue
159
                continue
120
 
160
 
-
 
161
            # All checks appear fine, set a variable for each piece
121
            (cur_st, letter, next_st) = edge.split()
162
            (cur_st, letter, next_st) = edge.split()
122
 
163
 
123
            # Make sure the states entered are in range
164
            # Make sure the states entered are in range
124
            if self.__state_in_range(cur_st) and self.__state_in_range(next_st):
165
            if self.__state_in_range(cur_st) and self.__state_in_range(next_st):
125
                new_state = False
166
                new_state = False
126
                cur_st = int(cur_st)
167
                cur_st = int(cur_st)    # convert to int
127
                next_st = int(next_st)
168
                next_st = int(next_st)  # convert to int
128
 
169
 
129
                # Add the state to the list if it's not there already
170
                # Add the state to the list if it's not there already
130
                if next_st not in self.__next_states(cur_st, letter):
171
                if next_st not in self.__next_states(cur_st, letter):
131
                    self.__next_states(cur_st, letter).append(next_st)
172
                    self.__next_states(cur_st, letter).append(next_st)
132
 
173
 
133
            else:
174
            else: # At least one state was not in range
134
                print 'Invalid current or next state entered'
175
                print 'Invalid current or next state entered'
135
                print
176
                print
136
                continue
177
                continue
137
 
178
 
138
    def __input_automaton(self):
179
    def __input_automaton(self):
Line 140... Line 181...
140
 
181
 
141
        self.__input_num_states()
182
        self.__input_num_states()
142
        self.__input_final_states()
183
        self.__input_final_states()
143
        self.__input_all_edges()
184
        self.__input_all_edges()
144
 
185
 
145
        # Make sure to visit all '^' states (to make printing easier, later)
186
        # Make sure to visit all '^' states (to make printing easier)
-
 
187
        # TODO: See if you need this anymore (I don't think so)
146
        for i in range(self.num_states):
188
        #for i in range(self.num_states):
147
            self.__E(i)
189
        #    self.__E(i)
148
 
190
 
149
    def main_menu(self):
191
    def main_menu(self):
-
 
192
        """Display the main menu for this automaton"""
-
 
193
 
150
        done = False
194
        done = False
151
        automaton_entered = False
195
        automaton_entered = False
152
 
196
 
153
        while not done:
197
        while not done:
154
            print 'Menu:'
198
            print 'Menu:'
Line 176... Line 220...
176
            else:
220
            else:
177
                print 'Bad Selection'
221
                print 'Bad Selection'
178
                print
222
                print
179
 
223
 
180
    def __output_dfa(self):
224
    def __output_dfa(self):
-
 
225
        """Generate and print the DFA that corresponds to the NFA entered"""
-
 
226
 
181
        print
227
        print
182
        print 'Initial State: %s' % (0, )
228
        print 'Initial State: %s' % (0, )
183
        print
229
        print
184
        self.__generate_dfa_table()
230
        self.__generate_dfa_table()
185
        self.__print_dfa_table()
231
        self.__print_dfa_table()
186
        print
232
        print
187
 
233
 
188
    def __print_dfa_table(self):
234
    def __print_dfa_table(self):
-
 
235
        """Print out a nicely spaced representation of the DFA's table"""
-
 
236
 
189
        #header1 = '%-8s%-8s%-20s' % ('Final', 'Number', 'NFA Equiv')
237
        #header1 = '%-8s%-8s%-20s' % ('Final', 'Number', 'NFA Equiv')
190
        header1 = '%-8s%-8s' % ('Final', 'State #')
238
        header1 = '%-8s%-8s' % ('Final', 'State #')
191
        header2 = ''
239
        header2 = ''
192
 
240
 
193
        for l in self.possible_letters:
241
        for l in self.possible_letters:
Line 219... Line 267...
219
            next = self.trans_func[(state, letter)]
267
            next = self.trans_func[(state, letter)]
220
        except KeyError:
268
        except KeyError:
221
            # Create a new empty list for this state if it doesn't exist yet
269
            # Create a new empty list for this state if it doesn't exist yet
222
            next = self.trans_func[(state, letter)] = []
270
            next = self.trans_func[(state, letter)] = []
223
 
271
 
-
 
272
        # Make sure that we can go to ourselves by the empty letter
224
        if letter == '^' and state not in next:
273
        if letter == '^' and state not in next:
225
            next.append(state)
274
            next.append(state)
226
 
275
 
227
        return next
276
        return next
228
 
277
 
Line 256... Line 305...
256
                        storage.append(j)
305
                        storage.append(j)
257
 
306
 
258
        return storage
307
        return storage
259
 
308
 
260
    def __by_letter(self, states, letter):
309
    def __by_letter(self, states, letter):
261
        """Returns a list of states to where you can go from a list of states,
310
        """Returns a list of states to where you can go from a group (list)
262
        by a certain letter"""
311
        of states, with a certain letter"""
263
 
312
 
264
        from_states = []
313
        from_states = []
265
 
314
 
-
 
315
        # Make sure E() has been called on every state here.
-
 
316
        # It should have been already, but just to make sure,
-
 
317
        # we'll do it here, too :)
266
        for s in states:
318
        for s in states:
267
            from_states.extend(self.__E(s))
319
            from_states.extend(self.__E(s))
268
 
320
 
269
        from_states = self.__unique_sort(from_states)
321
        from_states = self.__unique_sort(from_states)
270
 
322
 
271
        new_states = []
323
        new_states = []
272
 
324
 
-
 
325
        # For each state, find the next states, and add them to the list
273
        for s in from_states:
326
        for s in from_states:
274
            new_states.extend(self.__next_states(s, letter))
327
            new_states.extend(self.__next_states(s, letter))
275
 
328
 
276
        new_states = self.__unique_sort(new_states)
329
        new_states = self.__unique_sort(new_states)
277
 
330
 
Line 306... Line 359...
306
                return True
359
                return True
307
 
360
 
308
        return False
361
        return False
309
 
362
 
310
    def __get_temp_record(self, num_letters):
363
    def __get_temp_record(self, num_letters):
-
 
364
        """Create a record (for the DFA table) that has the proper number
-
 
365
        of slots"""
-
 
366
 
311
        blank = None
367
        blank = None
312
        return [blank for i in range(num_letters + 3)]
368
        return [blank for i in range(num_letters + 3)]
313
 
369
 
314
    def __get_possible_letters(self):
370
    def __get_possible_letters(self):
-
 
371
        """Create a list of all the possible letters for the NFA,
-
 
372
        and store it in possible_letters"""
315
 
373
 
316
        # get the list of all letters
374
        # Get the list of all letters
317
        possible_letters = []
375
        possible_letters = []
318
 
376
 
319
        for (state, letter) in self.trans_func.keys():
377
        for (state, letter) in self.trans_func.keys():
320
            if letter not in possible_letters and letter != '^':
378
            if letter not in possible_letters and letter != '^':
321
                possible_letters.append(letter)
379
                possible_letters.append(letter)
322
 
380
 
323
        possible_letters = self.__unique_sort(possible_letters)
381
        possible_letters = self.__unique_sort(possible_letters)
324
        self.possible_letters = possible_letters
382
        self.possible_letters = possible_letters
325
        
383
 
326
    def __generate_dfa_table(self):
384
    def __generate_dfa_table(self):
-
 
385
        """Generate a table for the DFA representation of the NFA entered earlier"""
327
 
386
 
328
        # get all the possible letters
387
        # Get all the possible letters
329
        self.__get_possible_letters()
388
        self.__get_possible_letters()
330
 
389
 
331
        # prime the dfa table
390
        # Prime the dfa table with the first state
332
        self.make_basic_record(self.__E(0))
391
        self.make_basic_record(self.__E(0))
333
 
392
 
-
 
393
        # Loop until we resolve every letter in the table
334
        while self.find_unresolved_letter() != (-1, -1): #loop until we've resolved everything
394
        while self.find_unresolved_letter() != (-1, -1):
335
            (record, letter_pos) = self.find_unresolved_letter()
395
            (record, letter_pos) = self.find_unresolved_letter()
336
            self.resolve_letter(record, letter_pos)
396
            self.resolve_letter(record, letter_pos)
337
 
397
 
338
    def resolve_letter(self, record, letter_pos):
398
    def resolve_letter(self, record, letter_pos):
-
 
399
        """Resolve a letter in the table, either adding a new entry if the
-
 
400
        required state doesn't already exist, or putting a link to
-
 
401
        an existing state"""
-
 
402
 
-
 
403
        # Get the NFA equivalent multi-state
339
        fromstates = self.dfa_table[record][2]
404
        fromstates = self.dfa_table[record][2]
340
        tostates = []
405
        tostates = []
341
 
406
 
-
 
407
        # Find all the states we can go to from our multi-state
342
        for s in fromstates:
408
        for s in fromstates:
343
            tostates.extend(self.__next_states(s, self.possible_letters[letter_pos-3]))
409
            tostates.extend(self.__next_states(s, self.possible_letters[letter_pos-3]))
344
 
410
 
-
 
411
        tostates = self.__unique_sort(tostates)
-
 
412
 
-
 
413
        # Make the letter we are trying to resolve point to a record in the table.
-
 
414
        # make_basic_record() will return the correct record, even if it needs to
-
 
415
        # create a new one.
345
        self.dfa_table[record][letter_pos] = self.make_basic_record(tostates)
416
        self.dfa_table[record][letter_pos] = self.make_basic_record(tostates)
346
 
417
 
347
    def find_unresolved_letter(self): # WORKING
418
    def find_unresolved_letter(self):
348
        """Returns an index pair of an unresolved letter that exists in the dfa_table.
419
        """Returns an index pair of an unresolved letter that exists in the dfa_table.
349
        If there are no more letters, return (-1, -1)"""
420
        If there are no more letters, return (-1, -1)"""
350
 
421
 
351
        for i in range(len(self.dfa_table)):
422
        for i in range(len(self.dfa_table)):
352
            for j in range(len(self.possible_letters)):
423
            for j in range(len(self.possible_letters)):
Line 354... Line 425...
354
                    return (i, j+3)
425
                    return (i, j+3)
355
 
426
 
356
        return (-1, -1)
427
        return (-1, -1)
357
 
428
 
358
    def make_basic_record(self, from_states):
429
    def make_basic_record(self, from_states):
-
 
430
        """Create a record, if necessary, for the states "from_states."
-
 
431
        If a corresponding state already exists, return it's index. A new state
-
 
432
        will have everything but the letters filled in."""
359
 
433
 
360
        if self.dfa_state_exists(from_states) == -1: #doesn't exist
434
        if self.dfa_state_exists(from_states) == -1: #doesn't exist
361
            temp_record = self.__get_temp_record(len(self.possible_letters))
435
            temp_record = self.__get_temp_record(len(self.possible_letters))
362
            recordnum = len(self.dfa_table)
436
            recordnum = len(self.dfa_table)
363
 
437