├── .gitignore ├── AutomataTheory.py ├── README.md ├── cli.py ├── graphs ├── graph_DFA.png ├── graph_NFA.png └── graph_minDFA.png ├── gui.py └── screenshots ├── screenshot_DFA.png ├── screenshot_NFA.png ├── screenshot_mainWindow.png └── screenshot_minDFA.png /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.png 3 | -------------------------------------------------------------------------------- /AutomataTheory.py: -------------------------------------------------------------------------------- 1 | from os import popen 2 | import time 3 | 4 | class Automata: 5 | """class to represent an Automata""" 6 | 7 | def __init__(self, language = set(['0', '1'])): 8 | self.states = set() 9 | self.startstate = None 10 | self.finalstates = [] 11 | self.transitions = dict() 12 | self.language = language 13 | 14 | @staticmethod 15 | def epsilon(): 16 | return ":e:" 17 | 18 | def setstartstate(self, state): 19 | self.startstate = state 20 | self.states.add(state) 21 | 22 | def addfinalstates(self, state): 23 | if isinstance(state, int): 24 | state = [state] 25 | for s in state: 26 | if s not in self.finalstates: 27 | self.finalstates.append(s) 28 | 29 | def addtransition(self, fromstate, tostate, inp): 30 | if isinstance(inp, str): 31 | inp = set([inp]) 32 | self.states.add(fromstate) 33 | self.states.add(tostate) 34 | if fromstate in self.transitions: 35 | if tostate in self.transitions[fromstate]: 36 | self.transitions[fromstate][tostate] = self.transitions[fromstate][tostate].union(inp) 37 | else: 38 | self.transitions[fromstate][tostate] = inp 39 | else: 40 | self.transitions[fromstate] = {tostate : inp} 41 | 42 | def addtransition_dict(self, transitions): 43 | for fromstate, tostates in transitions.items(): 44 | for state in tostates: 45 | self.addtransition(fromstate, state, tostates[state]) 46 | 47 | def gettransitions(self, state, key): 48 | if isinstance(state, int): 49 | state = [state] 50 | trstates = set() 51 | for st in state: 52 | if st in self.transitions: 53 | for tns in self.transitions[st]: 54 | if key in self.transitions[st][tns]: 55 | trstates.add(tns) 56 | return trstates 57 | 58 | def getEClose(self, findstate): 59 | allstates = set() 60 | states = set([findstate]) 61 | while len(states)!= 0: 62 | state = states.pop() 63 | allstates.add(state) 64 | if state in self.transitions: 65 | for tns in self.transitions[state]: 66 | if Automata.epsilon() in self.transitions[state][tns] and tns not in allstates: 67 | states.add(tns) 68 | return allstates 69 | 70 | def display(self): 71 | print("states:", self.states) 72 | print("start state: ", self.startstate) 73 | print("final states:", self.finalstates) 74 | print("transitions:") 75 | for fromstate, tostates in self.transitions.items(): 76 | for state in tostates: 77 | for char in tostates[state]: 78 | print(" ", fromstate, "->", state, "on '" + char + "'", end="") 79 | print() 80 | 81 | def getPrintText(self): 82 | text = "language: {" + ", ".join(self.language) + "}\n" 83 | text += "states: {" + ", ".join(map(str,self.states)) + "}\n" 84 | text += "start state: " + str(self.startstate) + "\n" 85 | text += "final states: {" + ", ".join(map(str,self.finalstates)) + "}\n" 86 | text += "transitions:\n" 87 | linecount = 5 88 | for fromstate, tostates in self.transitions.items(): 89 | for state in tostates: 90 | for char in tostates[state]: 91 | text += " " + str(fromstate) + " -> " + str(state) + " on '" + char + "'\n" 92 | linecount +=1 93 | return [text, linecount] 94 | 95 | def newBuildFromNumber(self, startnum): 96 | translations = {} 97 | for i in list(self.states): 98 | translations[i] = startnum 99 | startnum += 1 100 | rebuild = Automata(self.language) 101 | rebuild.setstartstate(translations[self.startstate]) 102 | rebuild.addfinalstates(translations[self.finalstates[0]]) 103 | for fromstate, tostates in self.transitions.items(): 104 | for state in tostates: 105 | rebuild.addtransition(translations[fromstate], translations[state], tostates[state]) 106 | return [rebuild, startnum] 107 | 108 | def newBuildFromEquivalentStates(self, equivalent, pos): 109 | rebuild = Automata(self.language) 110 | for fromstate, tostates in self.transitions.items(): 111 | for state in tostates: 112 | rebuild.addtransition(pos[fromstate], pos[state], tostates[state]) 113 | rebuild.setstartstate(pos[self.startstate]) 114 | for s in self.finalstates: 115 | rebuild.addfinalstates(pos[s]) 116 | return rebuild 117 | 118 | def getDotFile(self): 119 | dotFile = "digraph DFA {\nrankdir=LR\n" 120 | if len(self.states) != 0: 121 | dotFile += "root=s1\nstart [shape=point]\nstart->s%d\n" % self.startstate 122 | for state in self.states: 123 | if state in self.finalstates: 124 | dotFile += "s%d [shape=doublecircle]\n" % state 125 | else: 126 | dotFile += "s%d [shape=circle]\n" % state 127 | for fromstate, tostates in self.transitions.items(): 128 | for state in tostates: 129 | for char in tostates[state]: 130 | dotFile += 's%d->s%d [label="%s"]\n' % (fromstate, state, char) 131 | dotFile += "}" 132 | return dotFile 133 | 134 | class BuildAutomata: 135 | """class for building e-nfa basic structures""" 136 | 137 | @staticmethod 138 | def basicstruct(inp): 139 | state1 = 1 140 | state2 = 2 141 | basic = Automata() 142 | basic.setstartstate(state1) 143 | basic.addfinalstates(state2) 144 | basic.addtransition(1, 2, inp) 145 | return basic 146 | 147 | @staticmethod 148 | def plusstruct(a, b): 149 | [a, m1] = a.newBuildFromNumber(2) 150 | [b, m2] = b.newBuildFromNumber(m1) 151 | state1 = 1 152 | state2 = m2 153 | plus = Automata() 154 | plus.setstartstate(state1) 155 | plus.addfinalstates(state2) 156 | plus.addtransition(plus.startstate, a.startstate, Automata.epsilon()) 157 | plus.addtransition(plus.startstate, b.startstate, Automata.epsilon()) 158 | plus.addtransition(a.finalstates[0], plus.finalstates[0], Automata.epsilon()) 159 | plus.addtransition(b.finalstates[0], plus.finalstates[0], Automata.epsilon()) 160 | plus.addtransition_dict(a.transitions) 161 | plus.addtransition_dict(b.transitions) 162 | return plus 163 | 164 | @staticmethod 165 | def dotstruct(a, b): 166 | [a, m1] = a.newBuildFromNumber(1) 167 | [b, m2] = b.newBuildFromNumber(m1) 168 | state1 = 1 169 | state2 = m2-1 170 | dot = Automata() 171 | dot.setstartstate(state1) 172 | dot.addfinalstates(state2) 173 | dot.addtransition(a.finalstates[0], b.startstate, Automata.epsilon()) 174 | dot.addtransition_dict(a.transitions) 175 | dot.addtransition_dict(b.transitions) 176 | return dot 177 | 178 | @staticmethod 179 | def starstruct(a): 180 | [a, m1] = a.newBuildFromNumber(2) 181 | state1 = 1 182 | state2 = m1 183 | star = Automata() 184 | star.setstartstate(state1) 185 | star.addfinalstates(state2) 186 | star.addtransition(star.startstate, a.startstate, Automata.epsilon()) 187 | star.addtransition(star.startstate, star.finalstates[0], Automata.epsilon()) 188 | star.addtransition(a.finalstates[0], star.finalstates[0], Automata.epsilon()) 189 | star.addtransition(a.finalstates[0], a.startstate, Automata.epsilon()) 190 | star.addtransition_dict(a.transitions) 191 | return star 192 | 193 | 194 | class DFAfromNFA: 195 | """class for building dfa from e-nfa and minimise it""" 196 | 197 | def __init__(self, nfa): 198 | self.buildDFA(nfa) 199 | self.minimise() 200 | 201 | def getDFA(self): 202 | return self.dfa 203 | 204 | def getMinimisedDFA(self): 205 | return self.minDFA 206 | 207 | def displayDFA(self): 208 | self.dfa.display() 209 | 210 | def displayMinimisedDFA(self): 211 | self.minDFA.display() 212 | 213 | def buildDFA(self, nfa): 214 | allstates = dict() 215 | eclose = dict() 216 | count = 1 217 | state1 = nfa.getEClose(nfa.startstate) 218 | eclose[nfa.startstate] = state1 219 | dfa = Automata(nfa.language) 220 | dfa.setstartstate(count) 221 | states = [[state1, count]] 222 | allstates[count] = state1 223 | count += 1 224 | while len(states) != 0: 225 | [state, fromindex] = states.pop() 226 | for char in dfa.language: 227 | trstates = nfa.gettransitions(state, char) 228 | for s in list(trstates)[:]: 229 | if s not in eclose: 230 | eclose[s] = nfa.getEClose(s) 231 | trstates = trstates.union(eclose[s]) 232 | if len(trstates) != 0: 233 | if trstates not in allstates.values(): 234 | states.append([trstates, count]) 235 | allstates[count] = trstates 236 | toindex = count 237 | count += 1 238 | else: 239 | toindex = [k for k, v in allstates.items() if v == trstates][0] 240 | dfa.addtransition(fromindex, toindex, char) 241 | for value, state in allstates.items(): 242 | if nfa.finalstates[0] in state: 243 | dfa.addfinalstates(value) 244 | self.dfa = dfa 245 | 246 | def acceptsString(self, string): 247 | currentstate = self.dfa.startstate 248 | for ch in string: 249 | if ch==":e:": 250 | continue 251 | st = list(self.dfa.gettransitions(currentstate, ch)) 252 | if len(st) == 0: 253 | return False 254 | currentstate = st[0] 255 | if currentstate in self.dfa.finalstates: 256 | return True 257 | return False 258 | 259 | def minimise(self): 260 | states = list(self.dfa.states) 261 | n = len(states) 262 | unchecked = dict() 263 | count = 1 264 | distinguished = [] 265 | equivalent = dict(zip(range(len(states)), [{s} for s in states])) 266 | pos = dict(zip(states,range(len(states)))) 267 | for i in range(n-1): 268 | for j in range(i+1, n): 269 | if not ([states[i], states[j]] in distinguished or [states[j], states[i]] in distinguished): 270 | eq = 1 271 | toappend = [] 272 | for char in self.dfa.language: 273 | s1 = self.dfa.gettransitions(states[i], char) 274 | s2 = self.dfa.gettransitions(states[j], char) 275 | if len(s1) != len(s2): 276 | eq = 0 277 | break 278 | if len(s1) > 1: 279 | raise BaseException("Multiple transitions detected in DFA") 280 | elif len(s1) == 0: 281 | continue 282 | s1 = s1.pop() 283 | s2 = s2.pop() 284 | if s1 != s2: 285 | if [s1, s2] in distinguished or [s2, s1] in distinguished: 286 | eq = 0 287 | break 288 | else: 289 | toappend.append([s1, s2, char]) 290 | eq = -1 291 | if eq == 0: 292 | distinguished.append([states[i], states[j]]) 293 | elif eq == -1: 294 | s = [states[i], states[j]] 295 | s.extend(toappend) 296 | unchecked[count] = s 297 | count += 1 298 | else: 299 | p1 = pos[states[i]] 300 | p2 = pos[states[j]] 301 | if p1 != p2: 302 | st = equivalent.pop(p2) 303 | for s in st: 304 | pos[s] = p1 305 | equivalent[p1] = equivalent[p1].union(st) 306 | newFound = True 307 | while newFound and len(unchecked) > 0: 308 | newFound = False 309 | toremove = set() 310 | for p, pair in unchecked.items(): 311 | for tr in pair[2:]: 312 | if [tr[0], tr[1]] in distinguished or [tr[1], tr[0]] in distinguished: 313 | unchecked.pop(p) 314 | distinguished.append([pair[0], pair[1]]) 315 | newFound = True 316 | break 317 | for pair in unchecked.values(): 318 | p1 = pos[pair[0]] 319 | p2 = pos[pair[1]] 320 | if p1 != p2: 321 | st = equivalent.pop(p2) 322 | for s in st: 323 | pos[s] = p1 324 | equivalent[p1] = equivalent[p1].union(st) 325 | if len(equivalent) == len(states): 326 | self.minDFA = self.dfa 327 | else: 328 | self.minDFA = self.dfa.newBuildFromEquivalentStates(equivalent, pos) 329 | 330 | class NFAfromRegex: 331 | """class for building e-nfa from regular expressions""" 332 | 333 | def __init__(self, regex): 334 | self.star = '*' 335 | self.plus = '+' 336 | self.dot = '.' 337 | self.openingBracket = '(' 338 | self.closingBracket = ')' 339 | self.operators = [self.plus, self.dot] 340 | self.regex = regex 341 | self.alphabet = [chr(i) for i in range(65,91)] 342 | self.alphabet.extend([chr(i) for i in range(97,123)]) 343 | self.alphabet.extend([chr(i) for i in range(48,58)]) 344 | self.buildNFA() 345 | 346 | def getNFA(self): 347 | return self.nfa 348 | 349 | def displayNFA(self): 350 | self.nfa.display() 351 | 352 | def buildNFA(self): 353 | language = set() 354 | self.stack = [] 355 | self.automata = [] 356 | previous = "::e::" 357 | for char in self.regex: 358 | if char in self.alphabet: 359 | language.add(char) 360 | if previous != self.dot and (previous in self.alphabet or previous in [self.closingBracket,self.star]): 361 | self.addOperatorToStack(self.dot) 362 | self.automata.append(BuildAutomata.basicstruct(char)) 363 | elif char == self.openingBracket: 364 | if previous != self.dot and (previous in self.alphabet or previous in [self.closingBracket,self.star]): 365 | self.addOperatorToStack(self.dot) 366 | self.stack.append(char) 367 | elif char == self.closingBracket: 368 | if previous in self.operators: 369 | raise BaseException("Error processing '%s' after '%s'" % (char, previous)) 370 | while(1): 371 | if len(self.stack) == 0: 372 | raise BaseException("Error processing '%s'. Empty stack" % char) 373 | o = self.stack.pop() 374 | if o == self.openingBracket: 375 | break 376 | elif o in self.operators: 377 | self.processOperator(o) 378 | elif char == self.star: 379 | if previous in self.operators or previous == self.openingBracket or previous == self.star: 380 | raise BaseException("Error processing '%s' after '%s'" % (char, previous)) 381 | self.processOperator(char) 382 | elif char in self.operators: 383 | if previous in self.operators or previous == self.openingBracket: 384 | raise BaseException("Error processing '%s' after '%s'" % (char, previous)) 385 | else: 386 | self.addOperatorToStack(char) 387 | else: 388 | raise BaseException("Symbol '%s' is not allowed" % char) 389 | previous = char 390 | while len(self.stack) != 0: 391 | op = self.stack.pop() 392 | self.processOperator(op) 393 | if len(self.automata) > 1: 394 | print(self.automata) 395 | raise BaseException("Regex could not be parsed successfully") 396 | self.nfa = self.automata.pop() 397 | self.nfa.language = language 398 | 399 | def addOperatorToStack(self, char): 400 | while(1): 401 | if len(self.stack) == 0: 402 | break 403 | top = self.stack[len(self.stack)-1] 404 | if top == self.openingBracket: 405 | break 406 | if top == char or top == self.dot: 407 | op = self.stack.pop() 408 | self.processOperator(op) 409 | else: 410 | break 411 | self.stack.append(char) 412 | 413 | def processOperator(self, operator): 414 | if len(self.automata) == 0: 415 | raise BaseException("Error processing operator '%s'. Stack is empty" % operator) 416 | if operator == self.star: 417 | a = self.automata.pop() 418 | self.automata.append(BuildAutomata.starstruct(a)) 419 | elif operator in self.operators: 420 | if len(self.automata) < 2: 421 | raise BaseException("Error processing operator '%s'. Inadequate operands" % operator) 422 | a = self.automata.pop() 423 | b = self.automata.pop() 424 | if operator == self.plus: 425 | self.automata.append(BuildAutomata.plusstruct(b,a)) 426 | elif operator == self.dot: 427 | self.automata.append(BuildAutomata.dotstruct(b,a)) 428 | 429 | def drawGraph(automata, file = ""): 430 | """From https://github.com/max99x/automata-editor/blob/master/util.py""" 431 | f = popen(r"dot -Tpng -o graph%s.png" % file, 'w') 432 | try: 433 | f.write(automata.getDotFile()) 434 | except: 435 | raise BaseException("Error creating graph") 436 | finally: 437 | f.close() 438 | 439 | def isInstalled(program): 440 | """From http://stackoverflow.com/questions/377017/test-if-executable-exists-in-python""" 441 | import os 442 | def is_exe(fpath): 443 | return os.path.isfile(fpath) and os.access(fpath, os.X_OK) 444 | fpath, fname = os.path.split(program) 445 | if fpath: 446 | if is_exe(program) or is_exe(program+".exe"): 447 | return True 448 | else: 449 | for path in os.environ["PATH"].split(os.pathsep): 450 | exe_file = os.path.join(path, program) 451 | if is_exe(exe_file) or is_exe(exe_file+".exe"): 452 | return True 453 | return False 454 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Automata From Regular Expressions 2 | ================================= 3 | 4 | Overview and Usage 5 | ------------------ 6 | This is a python program to contruct [e-NFA][1], [DFA][2], and [minimised DFA][3] from a given [regular expression][4], built as a class project for Formal Language and Automata Theory. 7 | 8 | It requires the python programming language: Python 2.7 (http://python.org/download/) 9 | 10 | The program can be run by passing the regex as a command line argument: `python cli.py "(0+1)*+01*0"` 11 | 12 | Alternately, you can use the GUI built using the Tkinter Library: `python gui.py` 13 | 14 | You may get a 'command not found' error in Windows, which means python is not in your PATH. Check out how to add python to your path here: http://superuser.com/questions/143119/how-to-add-python-to-the-windows-path 15 | 16 | The program also has the ability to create graphs from the built automata. For that, it requires: 17 | * The GraphViz software to actually create the graphs. Get it from http://www.graphviz.org/Download.php. (This is a ~50MB download, but worth it!) 18 | * The Python Imaging Library (PIL) to read the png images into Tkinter. Get it from http://www.pythonware.com/products/pil/index.htm. 19 | 20 | Ubuntu users can install them by running `sudo apt-get install python-imaging-tk graphviz` 21 | 22 | Checkout the included screenshots. The GUI is not the best in the world, but it gets the work done :) 23 | 24 | Acknowledgements 25 | ---------------- 26 | * Prof S.K. Saha, for the project idea and concepts of Automata Theory 27 | * https://github.com/max99x/automata-editor, for general ideas and esp. for showing how to use GraphViz 28 | * The wonderful community over at http://stackoverflow.com, for helping get past all bottlenecks 29 | * The very helpful documentation of Tkinter at http://effbot.org/tkinterbook/ 30 | 31 | [1]: http://en.wikipedia.org/wiki/Nondeterministic_finite_automaton 32 | [2]: http://en.wikipedia.org/wiki/Deterministic_finite_automaton 33 | [3]: http://en.wikipedia.org/wiki/DFA_minimization 34 | [4]: http://en.wikipedia.org/wiki/Regular_expression 35 | 36 | License 37 | ------- 38 | GNU GPLv3 39 | -------------------------------------------------------------------------------- /cli.py: -------------------------------------------------------------------------------- 1 | from AutomataTheory import * 2 | import sys 3 | 4 | def main(): 5 | inp = "(01*1)*1" 6 | if len(sys.argv)>1: 7 | inp = sys.argv[1] 8 | print("Regular Expression: ", inp) 9 | nfaObj = NFAfromRegex(inp) 10 | nfa = nfaObj.getNFA() 11 | dfaObj = DFAfromNFA(nfa) 12 | dfa = dfaObj.getDFA() 13 | minDFA = dfaObj.getMinimisedDFA() 14 | print("\nNFA: ") 15 | nfaObj.displayNFA() 16 | print("\nDFA: ") 17 | dfaObj.displayDFA() 18 | print("\nMinimised DFA: ") 19 | dfaObj.displayMinimisedDFA() 20 | if isInstalled("dot"): 21 | drawGraph(dfa, "dfa") 22 | drawGraph(nfa, "nfa") 23 | drawGraph(minDFA, "mdfa") 24 | print("\nGraphs have been created in the code directory") 25 | print(minDFA.getDotFile()) 26 | 27 | if __name__ == '__main__': 28 | t = time.time() 29 | try: 30 | main() 31 | except BaseException as e: 32 | print("\nFailure:", e) 33 | print("\nExecution time: ", time.time() - t, "seconds") 34 | -------------------------------------------------------------------------------- /graphs/graph_DFA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdht0/automata-from-regex/1db6024e9a33f4db9573ed19dff037e2bbcb9d44/graphs/graph_DFA.png -------------------------------------------------------------------------------- /graphs/graph_NFA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdht0/automata-from-regex/1db6024e9a33f4db9573ed19dff037e2bbcb9d44/graphs/graph_NFA.png -------------------------------------------------------------------------------- /graphs/graph_minDFA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdht0/automata-from-regex/1db6024e9a33f4db9573ed19dff037e2bbcb9d44/graphs/graph_minDFA.png -------------------------------------------------------------------------------- /gui.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | try: 4 | from tkinter import * 5 | from tkinter import font as tkFont 6 | except ImportError as err: 7 | print("error: %s. Tkinter library is required for using the GUI." % err.message) 8 | sys.exit(1) 9 | 10 | from AutomataTheory import * 11 | 12 | dotFound = isInstalled("dot") 13 | if dotFound: 14 | try: 15 | from PIL import Image, ImageTk 16 | except ImportError as err: 17 | print("Notice: %s. The PIL library is required for displaying the graphs." % err.message) 18 | dotFound = False 19 | else: 20 | print("Notice: The GraphViz software is required for displaying the graphs.") 21 | 22 | class AutomataGUI: 23 | 24 | def __init__(self, root, dotFound): 25 | self.root = root 26 | self.initUI() 27 | self.selectedButton = 0 28 | self.dotFound = dotFound 29 | startRegex = "0+1*0" 30 | self.regexVar.set(startRegex) 31 | self.handleBuildRegexButton() 32 | 33 | def initUI(self): 34 | self.root.title("Automata From Regular Expressions") 35 | ScreenSizeX = self.root.winfo_screenwidth() 36 | ScreenSizeY = self.root.winfo_screenheight() 37 | ScreenRatioX = 0.7 38 | ScreenRatioY = 0.8 39 | self.FrameSizeX = int(ScreenSizeX * ScreenRatioX) 40 | self.FrameSizeY = int(ScreenSizeY * ScreenRatioY) 41 | print(self.FrameSizeY, self.FrameSizeX) 42 | FramePosX = (ScreenSizeX - self.FrameSizeX)/2 43 | FramePosY = (ScreenSizeY - self.FrameSizeY)/2 44 | padX = 10 45 | padY = 10 46 | self.root.geometry("%sx%s+%s+%s" % (self.FrameSizeX,self.FrameSizeY,FramePosX,FramePosY)) 47 | self.root.resizable(width=False, height=False) 48 | 49 | parentFrame = Frame(self.root, width = int(self.FrameSizeX - 2*padX), height = int(self.FrameSizeY - 2*padY)) 50 | parentFrame.grid(padx=padX, pady=padY, stick=E+W+N+S) 51 | 52 | regexFrame = Frame(parentFrame) 53 | enterRegexLabel = Label(regexFrame, text="Enter regular expression [operators allowed are plus (+), dot (.) and star (*)]:") 54 | self.regexVar = StringVar() 55 | self.regexField = Entry(regexFrame, width=80, textvariable=self.regexVar) 56 | buildRegexButton = Button(regexFrame, text="Build", width=10, command=self.handleBuildRegexButton) 57 | enterRegexLabel.grid(row=0, column=0, sticky=W) 58 | self.regexField.grid(row=1, column=0, sticky=W) 59 | buildRegexButton.grid(row=1, column=1, padx=5) 60 | 61 | testStringFrame = Frame(parentFrame) 62 | testStringLabel = Label(testStringFrame, text="Enter a test string: ") 63 | self.testVar = StringVar() 64 | self.testStringField = Entry(testStringFrame, width=80, textvariable=self.testVar) 65 | testStringButton = Button(testStringFrame, text="Test", width=10, command=self.handleTestStringButton) 66 | testStringLabel.grid(row=0, column=0, sticky=W) 67 | self.testStringField.grid(row=1, column=0, sticky=W) 68 | testStringButton.grid(row=1, column=1, padx=5) 69 | 70 | self.statusLabel = Label(parentFrame) 71 | 72 | buttonGroup = Frame(parentFrame) 73 | self.timingLabel = Label(buttonGroup, text="Idle...", width=50, justify=LEFT) 74 | nfaButton = Button(buttonGroup, text="NFA", width=15, command=self.handlenfaButton) 75 | dfaButton = Button(buttonGroup, text="DFA", width=15, command=self.handledfaButton) 76 | minDFAButton = Button(buttonGroup, text="Minimized DFA", width=15, command=self.handleminDFAButton) 77 | self.timingLabel.grid(row=0, column=0, sticky=W) 78 | nfaButton.grid(row=0, column=1) 79 | dfaButton.grid(row=0, column=2) 80 | minDFAButton.grid(row=0, column=3) 81 | 82 | automataCanvasFrame = Frame(parentFrame, height=100, width=100) 83 | self.cwidth = int(self.FrameSizeX - (2*padX + 20)) 84 | self.cheight = int(self.FrameSizeY * 0.6) 85 | self.automataCanvas = Canvas(automataCanvasFrame, bg='#FFFFFF', width= self.cwidth, height = self.cheight,scrollregion=(0,0,self.cwidth,self.cheight)) 86 | hbar=Scrollbar(automataCanvasFrame,orient=HORIZONTAL) 87 | hbar.pack(side=BOTTOM,fill=X) 88 | hbar.config(command=self.automataCanvas.xview) 89 | vbar=Scrollbar(automataCanvasFrame,orient=VERTICAL) 90 | vbar.pack(side=RIGHT,fill=Y) 91 | vbar.config(command=self.automataCanvas.yview) 92 | self.automataCanvas.config(xscrollcommand=hbar.set, yscrollcommand=vbar.set) 93 | self.canvasitems = [] 94 | self.automataCanvas.pack() 95 | 96 | self.bottomLabel = Label(parentFrame, text="Created by Siddhartha, Aamir, Vivek and Shikhar under Prof. S. K. Saha [Formal Language and Automata Theory, Dept. of CSE, BIT Mesra]") 97 | 98 | regexFrame.grid(row=0, column=0, sticky=W, padx=(50,0)) 99 | testStringFrame.grid(row=1, column=0, sticky=W, padx=(50,0)) 100 | self.statusLabel.grid(row=2, column=0, sticky=W, padx=(50,0)) 101 | buttonGroup.grid(row=3, column=0) 102 | automataCanvasFrame.grid(row=4, column=0, sticky=E+W+N+S) 103 | self.bottomLabel.grid(row=5, column=0, sticky=W, pady=10) 104 | 105 | def handleBuildRegexButton(self): 106 | t = time.time() 107 | try: 108 | inp = self.regexVar.get().replace(' ','') 109 | if inp == '': 110 | self.statusLabel.config(text="Detected empty regex!") 111 | return 112 | self.createAutomata(inp) 113 | except BaseException as e: 114 | self.statusLabel.config(text="Failure: %s" % e) 115 | self.timingLabel.configure(text="Operation completed in " + "%.4f" % (time.time() - t) + " seconds") 116 | self.displayAutomata() 117 | 118 | def handleTestStringButton(self): 119 | t = time.time() 120 | inp = self.testVar.get().replace(' ','') 121 | if inp == '': 122 | inp = [':e:'] 123 | if self.dfaObj.acceptsString(inp): 124 | self.statusLabel.config(text="Accepts :)") 125 | else: 126 | self.statusLabel.config(text="Does not accept :|") 127 | self.timingLabel.configure(text="Operation completed in " + "%.4f" % (time.time() - t) + " seconds") 128 | 129 | def handlenfaButton(self): 130 | self.selectedButton = 0 131 | self.displayAutomata() 132 | 133 | def handledfaButton(self): 134 | self.selectedButton = 1 135 | self.displayAutomata() 136 | 137 | def handleminDFAButton(self): 138 | self.selectedButton = 2 139 | self.displayAutomata() 140 | 141 | def createAutomata(self, inp): 142 | print("Regex: ", inp) 143 | nfaObj = NFAfromRegex(inp) 144 | self.nfa = nfaObj.getNFA() 145 | self.dfaObj = DFAfromNFA(self.nfa) 146 | self.dfa = self.dfaObj.getDFA() 147 | self.minDFA = self.dfaObj.getMinimisedDFA() 148 | if self.dotFound: 149 | drawGraph(self.dfa, "dfa") 150 | drawGraph(self.nfa, "nfa") 151 | drawGraph(self.minDFA, "mdfa") 152 | dfafile = "graphdfa.png" 153 | nfafile = "graphnfa.png" 154 | mindfafile = "graphmdfa.png" 155 | self.nfaimagefile = Image.open(nfafile) 156 | self.dfaimagefile = Image.open(dfafile) 157 | self.mindfaimagefile = Image.open(mindfafile) 158 | self.nfaimg = ImageTk.PhotoImage(self.nfaimagefile) 159 | self.dfaimg = ImageTk.PhotoImage(self.dfaimagefile) 160 | self.mindfaimg = ImageTk.PhotoImage(self.mindfaimagefile) 161 | 162 | def displayAutomata(self): 163 | for item in self.canvasitems: 164 | self.automataCanvas.delete(item) 165 | if self.selectedButton == 0: 166 | header = "e-NFA" 167 | automata = self.nfa 168 | if self.dotFound: 169 | image = self.nfaimg 170 | imagefile = self.nfaimagefile 171 | elif self.selectedButton == 1: 172 | header = "DFA" 173 | automata = self.dfa 174 | if self.dotFound: 175 | image = self.dfaimg 176 | imagefile = self.dfaimagefile 177 | elif self.selectedButton == 2: 178 | header = "Minimised DFA" 179 | automata = self.minDFA 180 | if self.dotFound: 181 | image = self.mindfaimg 182 | imagefile = self.mindfaimagefile 183 | font = tkFont.Font(family="times", size=20) 184 | (w,h) = (font.measure(header),font.metrics("linespace")) 185 | headerheight = h + 10 186 | itd = self.automataCanvas.create_text(10,10,text=header, font=font, anchor=NW) 187 | self.canvasitems.append(itd) 188 | [text, linecount] = automata.getPrintText() 189 | font = tkFont.Font(family="times", size=13) 190 | (w,h) = (font.measure(text),font.metrics("linespace")) 191 | textheight = headerheight + linecount * h + 20 192 | itd = self.automataCanvas.create_text(10, headerheight + 10,text=text, font=font, anchor=NW) 193 | self.canvasitems.append(itd) 194 | if self.dotFound: 195 | itd = self.automataCanvas.create_image(10, textheight, image=image, anchor=NW) 196 | self.canvasitems.append(itd) 197 | totalwidth = imagefile.size[0] + 10 198 | totalheight = imagefile.size[1] + textheight + 10 199 | else: 200 | totalwidth = self.cwidth + 10 201 | totalheight = textheight + 10 202 | if totalheight < self.cheight: 203 | totalheight = self.cheight 204 | if totalwidth < self.cwidth: 205 | totalwidth = self.cwidth 206 | self.automataCanvas.config(scrollregion=(0,0,totalwidth,totalheight)) 207 | 208 | def main(): 209 | global dotFound 210 | root = Tk() 211 | app = AutomataGUI(root, dotFound) 212 | root.mainloop() 213 | 214 | if __name__ == '__main__': 215 | main() 216 | -------------------------------------------------------------------------------- /screenshots/screenshot_DFA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdht0/automata-from-regex/1db6024e9a33f4db9573ed19dff037e2bbcb9d44/screenshots/screenshot_DFA.png -------------------------------------------------------------------------------- /screenshots/screenshot_NFA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdht0/automata-from-regex/1db6024e9a33f4db9573ed19dff037e2bbcb9d44/screenshots/screenshot_NFA.png -------------------------------------------------------------------------------- /screenshots/screenshot_mainWindow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdht0/automata-from-regex/1db6024e9a33f4db9573ed19dff037e2bbcb9d44/screenshots/screenshot_mainWindow.png -------------------------------------------------------------------------------- /screenshots/screenshot_minDFA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdht0/automata-from-regex/1db6024e9a33f4db9573ed19dff037e2bbcb9d44/screenshots/screenshot_minDFA.png --------------------------------------------------------------------------------