Package nltk :: Package draw :: Module rdparser
[hide private]
[frames] | no frames]

Source Code for Module nltk.draw.rdparser

  1  # Natural Language Toolkit: Recursive Descent Parser Demo 
  2  # 
  3  # Copyright (C) 2001-2008 NLTK Project 
  4  # Author: Edward Loper <[email protected]> 
  5  # URL: <http://nltk.org> 
  6  # For license information, see LICENSE.TXT 
  7  # 
  8  # $Id: rdparser.py 6265 2008-07-26 09:25:03Z stevenbird $ 
  9   
 10  """ 
 11  A graphical tool for exploring the recursive descent parser. 
 12   
 13  The recursive descent parser maintains a tree, which records the 
 14  structure of the portion of the text that has been parsed.  It uses 
 15  CFG productions to expand the fringe of the tree, and matches its 
 16  leaves against the text.  Initially, the tree contains the start 
 17  symbol ("S").  It is shown in the main canvas, to the right of the 
 18  list of available expansions. 
 19   
 20  The parser builds up a tree structure for the text using three 
 21  operations:  
 22   
 23    - "expand" uses a CFG production to add children to a node on the 
 24      fringe of the tree. 
 25    - "match" compares a leaf in the tree to a text token. 
 26    - "backtrack" returns the tree to its state before the most recent 
 27      expand or match operation. 
 28   
 29  The parser maintains a list of tree locations called a "frontier" to 
 30  remember which nodes have not yet been expanded and which leaves have 
 31  not yet been matched against the text.  The leftmost frontier node is 
 32  shown in green, and the other frontier nodes are shown in blue.  The 
 33  parser always performs expand and match operations on the leftmost 
 34  element of the frontier. 
 35   
 36  You can control the parser's operation by using the "expand," "match," 
 37  and "backtrack" buttons; or you can use the "step" button to let the 
 38  parser automatically decide which operation to apply.  The parser uses 
 39  the following rules to decide which operation to apply: 
 40   
 41    - If the leftmost frontier element is a token, try matching it. 
 42    - If the leftmost frontier element is a node, try expanding it with 
 43      the first untried expansion. 
 44    - Otherwise, backtrack. 
 45   
 46  The "expand" button applies the untried expansion whose CFG production 
 47  is listed earliest in the grammar.  To manually choose which expansion 
 48  to apply, click on a CFG production from the list of available 
 49  expansions, on the left side of the main window. 
 50   
 51  The "autostep" button will let the parser continue applying 
 52  applications to the tree until it reaches a complete parse.  You can 
 53  cancel an autostep in progress at any time by clicking on the 
 54  "autostep" button again. 
 55   
 56  Keyboard Shortcuts:: 
 57        [Space]\t Perform the next expand, match, or backtrack operation 
 58        [a]\t Step through operations until the next complete parse 
 59        [e]\t Perform an expand operation 
 60        [m]\t Perform a match operation 
 61        [b]\t Perform a backtrack operation 
 62        [Delete]\t Reset the parser 
 63        [g]\t Show/hide available expansions list 
 64        [h]\t Help 
 65        [Ctrl-p]\t Print 
 66        [q]\t Quit 
 67  """ 
 68   
 69  import string 
 70  import tkFont 
 71  from Tkinter import * 
 72   
 73  from nltk import parse, tokenize 
 74   
 75  from nltk.draw.tree import * 
 76  from nltk.draw import * 
 77  from nltk.draw.cfg import * 
 78           
79 -class RecursiveDescentDemo(object):
80 """ 81 A graphical tool for exploring the recursive descent parser. The tool 82 displays the parser's tree and the remaining text, and allows the 83 user to control the parser's operation. In particular, the user 84 can expand subtrees on the frontier, match tokens on the frontier 85 against the text, and backtrack. A "step" button simply steps 86 through the parsing process, performing the operations that 87 C{RecursiveDescentParser} would use. 88 """
89 - def __init__(self, grammar, sent, trace=0):
90 self._sent = sent 91 self._parser = parse.SteppingRecursiveDescentParser(grammar, trace) 92 93 # Set up the main window. 94 self._top = Tk() 95 self._top.title('Recursive Descent Parser Demo') 96 97 # Set up key bindings. 98 self._init_bindings() 99 100 # Initialize the fonts. 101 self._init_fonts(self._top) 102 103 # Animations. animating_lock is a lock to prevent the demo 104 # from performing new operations while it's animating. 105 self._animation_frames = IntVar(self._top) 106 self._animation_frames.set(5) 107 self._animating_lock = 0 108 self._autostep = 0 109 110 # The user can hide the grammar. 111 self._show_grammar = IntVar(self._top) 112 self._show_grammar.set(1) 113 114 # Create the basic frames. 115 self._init_menubar(self._top) 116 self._init_buttons(self._top) 117 self._init_feedback(self._top) 118 self._init_grammar(self._top) 119 self._init_canvas(self._top) 120 121 # Initialize the parser. 122 self._parser.initialize(self._sent) 123 124 # Resize callback 125 self._canvas.bind('<Configure>', self._configure)
126 127 ######################################### 128 ## Initialization Helpers 129 ######################################### 130
131 - def _init_fonts(self, root):
132 # See: <http://www.astro.washington.edu/owen/ROTKFolklore.html> 133 self._sysfont = tkFont.Font(font=Button()["font"]) 134 root.option_add("*Font", self._sysfont) 135 136 # TWhat's our font size (default=same as sysfont) 137 self._size = IntVar(root) 138 self._size.set(self._sysfont.cget('size')) 139 140 self._boldfont = tkFont.Font(family='helvetica', weight='bold', 141 size=self._size.get()) 142 self._font = tkFont.Font(family='helvetica', 143 size=self._size.get()) 144 if self._size.get() < 0: big = self._size.get()-2 145 else: big = self._size.get()+2 146 self._bigfont = tkFont.Font(family='helvetica', weight='bold', 147 size=big)
148
149 - def _init_grammar(self, parent):
150 # Grammar view. 151 self._prodframe = listframe = Frame(parent) 152 self._prodframe.pack(fill='both', side='left', padx=2) 153 self._prodlist_label = Label(self._prodframe, font=self._boldfont, 154 text='Available Expansions') 155 self._prodlist_label.pack() 156 self._prodlist = Listbox(self._prodframe, selectmode='single', 157 relief='groove', background='white', 158 foreground='#909090', font=self._font, 159 selectforeground='#004040', 160 selectbackground='#c0f0c0') 161 162 self._prodlist.pack(side='right', fill='both', expand=1) 163 164 self._productions = list(self._parser.grammar().productions()) 165 for production in self._productions: 166 self._prodlist.insert('end', (' %s' % production)) 167 self._prodlist.config(height=min(len(self._productions), 25)) 168 169 # Add a scrollbar if there are more than 25 productions. 170 if len(self._productions) > 25: 171 listscroll = Scrollbar(self._prodframe, 172 orient='vertical') 173 self._prodlist.config(yscrollcommand = listscroll.set) 174 listscroll.config(command=self._prodlist.yview) 175 listscroll.pack(side='left', fill='y') 176 177 # If they select a production, apply it. 178 self._prodlist.bind('<<ListboxSelect>>', self._prodlist_select)
179
180 - def _init_bindings(self):
181 # Key bindings are a good thing. 182 self._top.bind('<Control-q>', self.destroy) 183 self._top.bind('<Control-x>', self.destroy) 184 self._top.bind('<Escape>', self.destroy) 185 self._top.bind('e', self.expand) 186 #self._top.bind('<Alt-e>', self.expand) 187 #self._top.bind('<Control-e>', self.expand) 188 self._top.bind('m', self.match) 189 self._top.bind('<Alt-m>', self.match) 190 self._top.bind('<Control-m>', self.match) 191 self._top.bind('b', self.backtrack) 192 self._top.bind('<Alt-b>', self.backtrack) 193 self._top.bind('<Control-b>', self.backtrack) 194 self._top.bind('<Control-z>', self.backtrack) 195 self._top.bind('<BackSpace>', self.backtrack) 196 self._top.bind('a', self.autostep) 197 #self._top.bind('<Control-a>', self.autostep) 198 self._top.bind('<Control-space>', self.autostep) 199 self._top.bind('<Control-c>', self.cancel_autostep) 200 self._top.bind('<space>', self.step) 201 self._top.bind('<Delete>', self.reset) 202 self._top.bind('<Control-p>', self.postscript) 203 #self._top.bind('<h>', self.help) 204 #self._top.bind('<Alt-h>', self.help) 205 self._top.bind('<Control-h>', self.help) 206 self._top.bind('<F1>', self.help) 207 #self._top.bind('<g>', self.toggle_grammar) 208 #self._top.bind('<Alt-g>', self.toggle_grammar) 209 #self._top.bind('<Control-g>', self.toggle_grammar) 210 self._top.bind('<Control-g>', self.edit_grammar) 211 self._top.bind('<Control-t>', self.edit_sentence)
212
213 - def _init_buttons(self, parent):
214 # Set up the frames. 215 self._buttonframe = buttonframe = Frame(parent) 216 buttonframe.pack(fill='none', side='bottom', padx=3, pady=2) 217 Button(buttonframe, text='Step', 218 background='#90c0d0', foreground='black', 219 command=self.step,).pack(side='left') 220 Button(buttonframe, text='Autostep', 221 background='#90c0d0', foreground='black', 222 command=self.autostep,).pack(side='left') 223 Button(buttonframe, text='Expand', underline=0, 224 background='#90f090', foreground='black', 225 command=self.expand).pack(side='left') 226 Button(buttonframe, text='Match', underline=0, 227 background='#90f090', foreground='black', 228 command=self.match).pack(side='left') 229 Button(buttonframe, text='Backtrack', underline=0, 230 background='#f0a0a0', foreground='black', 231 command=self.backtrack).pack(side='left')
232 # Replace autostep... 233 # self._autostep_button = Button(buttonframe, text='Autostep', 234 # underline=0, command=self.autostep) 235 # self._autostep_button.pack(side='left') 236
237 - def _configure(self, event):
238 self._autostep = 0 239 (x1, y1, x2, y2) = self._cframe.scrollregion() 240 y2 = event.height - 6 241 self._canvas['scrollregion'] = '%d %d %d %d' % (x1,y1,x2,y2) 242 self._redraw()
243
244 - def _init_feedback(self, parent):
245 self._feedbackframe = feedbackframe = Frame(parent) 246 feedbackframe.pack(fill='x', side='bottom', padx=3, pady=3) 247 self._lastoper_label = Label(feedbackframe, text='Last Operation:', 248 font=self._font) 249 self._lastoper_label.pack(side='left') 250 lastoperframe = Frame(feedbackframe, relief='sunken', border=1) 251 lastoperframe.pack(fill='x', side='right', expand=1, padx=5) 252 self._lastoper1 = Label(lastoperframe, foreground='#007070', 253 background='#f0f0f0', font=self._font) 254 self._lastoper2 = Label(lastoperframe, anchor='w', width=30, 255 foreground='#004040', background='#f0f0f0', 256 font=self._font) 257 self._lastoper1.pack(side='left') 258 self._lastoper2.pack(side='left', fill='x', expand=1)
259
260 - def _init_canvas(self, parent):
261 self._cframe = CanvasFrame(parent, background='white', 262 #width=525, height=250, 263 closeenough=10, 264 border=2, relief='sunken') 265 self._cframe.pack(expand=1, fill='both', side='top', pady=2) 266 canvas = self._canvas = self._cframe.canvas() 267 268 # Initially, there's no tree or text 269 self._tree = None 270 self._textwidgets = [] 271 self._textline = None
272
273 - def _init_menubar(self, parent):
274 menubar = Menu(parent) 275 276 filemenu = Menu(menubar, tearoff=0) 277 filemenu.add_command(label='Reset Parser', underline=0, 278 command=self.reset, accelerator='Del') 279 filemenu.add_command(label='Print to Postscript', underline=0, 280 command=self.postscript, accelerator='Ctrl-p') 281 filemenu.add_command(label='Exit', underline=1, 282 command=self.destroy, accelerator='Ctrl-x') 283 menubar.add_cascade(label='File', underline=0, menu=filemenu) 284 285 editmenu = Menu(menubar, tearoff=0) 286 editmenu.add_command(label='Edit Grammar', underline=5, 287 command=self.edit_grammar, 288 accelerator='Ctrl-g') 289 editmenu.add_command(label='Edit Text', underline=5, 290 command=self.edit_sentence, 291 accelerator='Ctrl-t') 292 menubar.add_cascade(label='Edit', underline=0, menu=editmenu) 293 294 rulemenu = Menu(menubar, tearoff=0) 295 rulemenu.add_command(label='Step', underline=1, 296 command=self.step, accelerator='Space') 297 rulemenu.add_separator() 298 rulemenu.add_command(label='Match', underline=0, 299 command=self.match, accelerator='Ctrl-m') 300 rulemenu.add_command(label='Expand', underline=0, 301 command=self.expand, accelerator='Ctrl-e') 302 rulemenu.add_separator() 303 rulemenu.add_command(label='Backtrack', underline=0, 304 command=self.backtrack, accelerator='Ctrl-b') 305 menubar.add_cascade(label='Apply', underline=0, menu=rulemenu) 306 307 viewmenu = Menu(menubar, tearoff=0) 308 viewmenu.add_checkbutton(label="Show Grammar", underline=0, 309 variable=self._show_grammar, 310 command=self._toggle_grammar) 311 viewmenu.add_separator() 312 viewmenu.add_radiobutton(label='Tiny', variable=self._size, 313 underline=0, value=10, command=self.resize) 314 viewmenu.add_radiobutton(label='Small', variable=self._size, 315 underline=0, value=12, command=self.resize) 316 viewmenu.add_radiobutton(label='Medium', variable=self._size, 317 underline=0, value=14, command=self.resize) 318 viewmenu.add_radiobutton(label='Large', variable=self._size, 319 underline=0, value=18, command=self.resize) 320 viewmenu.add_radiobutton(label='Huge', variable=self._size, 321 underline=0, value=24, command=self.resize) 322 menubar.add_cascade(label='View', underline=0, menu=viewmenu) 323 324 animatemenu = Menu(menubar, tearoff=0) 325 animatemenu.add_radiobutton(label="No Animation", underline=0, 326 variable=self._animation_frames, 327 value=0) 328 animatemenu.add_radiobutton(label="Slow Animation", underline=0, 329 variable=self._animation_frames, 330 value=10, accelerator='-') 331 animatemenu.add_radiobutton(label="Normal Animation", underline=0, 332 variable=self._animation_frames, 333 value=5, accelerator='=') 334 animatemenu.add_radiobutton(label="Fast Animation", underline=0, 335 variable=self._animation_frames, 336 value=2, accelerator='+') 337 menubar.add_cascade(label="Animate", underline=1, menu=animatemenu) 338 339 340 helpmenu = Menu(menubar, tearoff=0) 341 helpmenu.add_command(label='About', underline=0, 342 command=self.about) 343 helpmenu.add_command(label='Instructions', underline=0, 344 command=self.help, accelerator='F1') 345 menubar.add_cascade(label='Help', underline=0, menu=helpmenu) 346 347 parent.config(menu=menubar)
348 349 ######################################### 350 ## Helper 351 ######################################### 352
353 - def _get(self, widget, treeloc):
354 for i in treeloc: widget = widget.subtrees()[i] 355 if isinstance(widget, TreeSegmentWidget): 356 widget = widget.node() 357 return widget
358 359 ######################################### 360 ## Main draw procedure 361 ######################################### 362
363 - def _redraw(self):
364 canvas = self._canvas 365 366 # Delete the old tree, widgets, etc. 367 if self._tree is not None: 368 self._cframe.destroy_widget(self._tree) 369 for twidget in self._textwidgets: 370 self._cframe.destroy_widget(twidget) 371 if self._textline is not None: 372 self._canvas.delete(self._textline) 373 374 # Draw the tree. 375 helv = ('helvetica', -self._size.get()) 376 bold = ('helvetica', -self._size.get(), 'bold') 377 attribs = {'tree_color': '#000000', 'tree_width': 2, 378 'node_font': bold, 'leaf_font': helv,} 379 tree = self._parser.tree() 380 self._tree = tree_to_treesegment(canvas, tree, **attribs) 381 self._cframe.add_widget(self._tree, 30, 5) 382 383 # Draw the text. 384 helv = ('helvetica', -self._size.get()) 385 bottom = y = self._cframe.scrollregion()[3] 386 self._textwidgets = [TextWidget(canvas, word, font=self._font) 387 for word in self._sent] 388 for twidget in self._textwidgets: 389 self._cframe.add_widget(twidget, 0, 0) 390 twidget.move(0, bottom-twidget.bbox()[3]-5) 391 y = min(y, twidget.bbox()[1]) 392 393 # Draw a line over the text, to separate it from the tree. 394 self._textline = canvas.create_line(-5000, y-5, 5000, y-5, dash='.') 395 396 # Highlight appropriate nodes. 397 self._highlight_nodes() 398 self._highlight_prodlist() 399 400 # Make sure the text lines up. 401 self._position_text()
402 403
404 - def _redraw_quick(self):
405 # This should be more-or-less sufficient after an animation. 406 self._highlight_nodes() 407 self._highlight_prodlist() 408 self._position_text()
409
410 - def _highlight_nodes(self):
411 # Highlight the list of nodes to be checked. 412 bold = ('helvetica', -self._size.get(), 'bold') 413 for treeloc in self._parser.frontier()[:1]: 414 self._get(self._tree, treeloc)['color'] = '#20a050' 415 self._get(self._tree, treeloc)['font'] = bold 416 for treeloc in self._parser.frontier()[1:]: 417 self._get(self._tree, treeloc)['color'] = '#008080'
418
419 - def _highlight_prodlist(self):
420 # Highlight the productions that can be expanded. 421 # Boy, too bad tkinter doesn't implement Listbox.itemconfig; 422 # that would be pretty useful here. 423 self._prodlist.delete(0, 'end') 424 expandable = self._parser.expandable_productions() 425 untried = self._parser.untried_expandable_productions() 426 productions = self._productions 427 for index in range(len(productions)): 428 if productions[index] in expandable: 429 if productions[index] in untried: 430 self._prodlist.insert(index, ' %s' % productions[index]) 431 else: 432 self._prodlist.insert(index, ' %s (TRIED)' % 433 productions[index]) 434 self._prodlist.selection_set(index) 435 else: 436 self._prodlist.insert(index, ' %s' % productions[index])
437
438 - def _position_text(self):
439 # Line up the text widgets that are matched against the tree 440 numwords = len(self._sent) 441 num_matched = numwords - len(self._parser.remaining_text()) 442 leaves = self._tree_leaves()[:num_matched] 443 xmax = self._tree.bbox()[0] 444 for i in range(0, len(leaves)): 445 widget = self._textwidgets[i] 446 leaf = leaves[i] 447 widget['color'] = '#006040' 448 leaf['color'] = '#006040' 449 widget.move(leaf.bbox()[0] - widget.bbox()[0], 0) 450 xmax = widget.bbox()[2] + 10 451 452 # Line up the text widgets that are not matched against the tree. 453 for i in range(len(leaves), numwords): 454 widget = self._textwidgets[i] 455 widget['color'] = '#a0a0a0' 456 widget.move(xmax - widget.bbox()[0], 0) 457 xmax = widget.bbox()[2] + 10 458 459 # If we have a complete parse, make everything green :) 460 if self._parser.currently_complete(): 461 for twidget in self._textwidgets: 462 twidget['color'] = '#00a000' 463 464 # Move the matched leaves down to the text. 465 for i in range(0, len(leaves)): 466 widget = self._textwidgets[i] 467 leaf = leaves[i] 468 dy = widget.bbox()[1] - leaf.bbox()[3] - 10.0 469 dy = max(dy, leaf.parent().node().bbox()[3] - leaf.bbox()[3] + 10) 470 leaf.move(0, dy)
471
472 - def _tree_leaves(self, tree=None):
473 if tree is None: tree = self._tree 474 if isinstance(tree, TreeSegmentWidget): 475 leaves = [] 476 for child in tree.subtrees(): leaves += self._tree_leaves(child) 477 return leaves 478 else: 479 return [tree]
480 481 ######################################### 482 ## Button Callbacks 483 ######################################### 484
485 - def destroy(self, *e):
486 self._autostep = 0 487 if self._top is None: return 488 self._top.destroy() 489 self._top = None
490
491 - def reset(self, *e):
492 self._autostep = 0 493 self._parser.initialize(self._sent) 494 self._lastoper1['text'] = 'Reset Demo' 495 self._lastoper2['text'] = '' 496 self._redraw()
497
498 - def autostep(self, *e):
499 if self._animation_frames.get() == 0: 500 self._animation_frames.set(2) 501 if self._autostep: 502 self._autostep = 0 503 else: 504 self._autostep = 1 505 self._step()
506
507 - def cancel_autostep(self, *e):
508 #self._autostep_button['text'] = 'Autostep' 509 self._autostep = 0
510 511 # Make sure to stop auto-stepping if we get any user input.
512 - def step(self, *e): self._autostep = 0; self._step()
513 - def match(self, *e): self._autostep = 0; self._match()
514 - def expand(self, *e): self._autostep = 0; self._expand()
515 - def backtrack(self, *e): self._autostep = 0; self._backtrack()
516
517 - def _step(self):
518 if self._animating_lock: return 519 520 # Try expanding, matching, and backtracking (in that order) 521 if self._expand(): pass 522 elif self._parser.untried_match() and self._match(): pass 523 elif self._backtrack(): pass 524 else: 525 self._lastoper1['text'] = 'Finished' 526 self._lastoper2['text'] = '' 527 self._autostep = 0 528 529 # Check if we just completed a parse. 530 if self._parser.currently_complete(): 531 self._autostep = 0 532 self._lastoper2['text'] += ' [COMPLETE PARSE]'
533
534 - def _expand(self, *e):
535 if self._animating_lock: return 536 old_frontier = self._parser.frontier() 537 rv = self._parser.expand() 538 if rv is not None: 539 self._lastoper1['text'] = 'Expand:' 540 self._lastoper2['text'] = rv 541 self._prodlist.selection_clear(0, 'end') 542 index = self._productions.index(rv) 543 self._prodlist.selection_set(index) 544 self._animate_expand(old_frontier[0]) 545 return 1 546 else: 547 self._lastoper1['text'] = 'Expand:' 548 self._lastoper2['text'] = '(all expansions tried)' 549 return 0
550
551 - def _match(self, *e):
552 if self._animating_lock: return 553 old_frontier = self._parser.frontier() 554 rv = self._parser.match() 555 if rv is not None: 556 self._lastoper1['text'] = 'Match:' 557 self._lastoper2['text'] = rv 558 self._animate_match(old_frontier[0]) 559 return 1 560 else: 561 self._lastoper1['text'] = 'Match:' 562 self._lastoper2['text'] = '(failed)' 563 return 0
564
565 - def _backtrack(self, *e):
566 if self._animating_lock: return 567 if self._parser.backtrack(): 568 elt = self._parser.tree() 569 for i in self._parser.frontier()[0]: 570 elt = elt[i] 571 self._lastoper1['text'] = 'Backtrack' 572 self._lastoper2['text'] = '' 573 if isinstance(elt, Tree): 574 self._animate_backtrack(self._parser.frontier()[0]) 575 else: 576 self._animate_match_backtrack(self._parser.frontier()[0]) 577 return 1 578 else: 579 self._autostep = 0 580 self._lastoper1['text'] = 'Finished' 581 self._lastoper2['text'] = '' 582 return 0
583
584 - def about(self, *e):
585 ABOUT = ("NLTK Recursive Descent Parser Demo\n"+ 586 "Written by Edward Loper") 587 TITLE = 'About: Recursive Descent Parser Demo' 588 try: 589 from tkMessageBox import Message 590 Message(message=ABOUT, title=TITLE).show() 591 except: 592 ShowText(self._top, TITLE, ABOUT)
593
594 - def help(self, *e):
595 self._autostep = 0 596 # The default font's not very legible; try using 'fixed' instead. 597 try: 598 ShowText(self._top, 'Help: Recursive Descent Parser Demo', 599 (__doc__).strip(), width=75, font='fixed') 600 except: 601 ShowText(self._top, 'Help: Recursive Descent Parser Demo', 602 (__doc__).strip(), width=75)
603
604 - def postscript(self, *e):
605 self._autostep = 0 606 self._cframe.print_to_file()
607
608 - def mainloop(self, *args, **kwargs):
609 """ 610 Enter the Tkinter mainloop. This function must be called if 611 this demo is created from a non-interactive program (e.g. 612 from a secript); otherwise, the demo will close as soon as 613 the script completes. 614 """ 615 if in_idle(): return 616 self._top.mainloop(*args, **kwargs)
617
618 - def resize(self, size=None):
619 if size is not None: self._size.set(size) 620 size = self._size.get() 621 self._font.configure(size=-(abs(size))) 622 self._boldfont.configure(size=-(abs(size))) 623 self._sysfont.configure(size=-(abs(size))) 624 self._bigfont.configure(size=-(abs(size+2))) 625 self._redraw()
626 627 ######################################### 628 ## Expand Production Selection 629 ######################################### 630
631 - def _toggle_grammar(self, *e):
632 if self._show_grammar.get(): 633 self._prodframe.pack(fill='both', side='left', padx=2, 634 after=self._feedbackframe) 635 self._lastoper1['text'] = 'Show Grammar' 636 else: 637 self._prodframe.pack_forget() 638 self._lastoper1['text'] = 'Hide Grammar' 639 self._lastoper2['text'] = ''
640 641 # def toggle_grammar(self, *e): 642 # self._show_grammar = not self._show_grammar 643 # if self._show_grammar: 644 # self._prodframe.pack(fill='both', expand='y', side='left', 645 # after=self._feedbackframe) 646 # self._lastoper1['text'] = 'Show Grammar' 647 # else: 648 # self._prodframe.pack_forget() 649 # self._lastoper1['text'] = 'Hide Grammar' 650 # self._lastoper2['text'] = '' 651
652 - def _prodlist_select(self, event):
653 selection = self._prodlist.curselection() 654 if len(selection) != 1: return 655 index = int(selection[0]) 656 old_frontier = self._parser.frontier() 657 production = self._parser.expand(self._productions[index]) 658 659 if production: 660 self._lastoper1['text'] = 'Expand:' 661 self._lastoper2['text'] = production 662 self._prodlist.selection_clear(0, 'end') 663 self._prodlist.selection_set(index) 664 self._animate_expand(old_frontier[0]) 665 else: 666 # Reset the production selections. 667 self._prodlist.selection_clear(0, 'end') 668 for prod in self._parser.expandable_productions(): 669 index = self._productions.index(prod) 670 self._prodlist.selection_set(index)
671 672 ######################################### 673 ## Animation 674 ######################################### 675
676 - def _animate_expand(self, treeloc):
677 oldwidget = self._get(self._tree, treeloc) 678 oldtree = oldwidget.parent() 679 top = not isinstance(oldtree.parent(), TreeSegmentWidget) 680 681 tree = self._parser.tree() 682 for i in treeloc: 683 tree = tree[i] 684 685 widget = tree_to_treesegment(self._canvas, tree, 686 node_font=self._boldfont, 687 leaf_color='white', 688 tree_width=2, tree_color='white', 689 node_color='white', 690 leaf_font=self._font) 691 widget.node()['color'] = '#20a050' 692 693 (oldx, oldy) = oldtree.node().bbox()[:2] 694 (newx, newy) = widget.node().bbox()[:2] 695 widget.move(oldx-newx, oldy-newy) 696 697 if top: 698 self._cframe.add_widget(widget, 0, 5) 699 widget.move(30-widget.node().bbox()[0], 0) 700 self._tree = widget 701 else: 702 oldtree.parent().replace_child(oldtree, widget) 703 704 # Move the children over so they don't overlap. 705 # Line the children up in a strange way. 706 if widget.subtrees(): 707 dx = (oldx + widget.node().width()/2 - 708 widget.subtrees()[0].bbox()[0]/2 - 709 widget.subtrees()[0].bbox()[2]/2) 710 for subtree in widget.subtrees(): subtree.move(dx, 0) 711 712 self._makeroom(widget) 713 714 if top: 715 self._cframe.destroy_widget(oldtree) 716 else: 717 oldtree.destroy() 718 719 colors = ['gray%d' % (10*int(10*x/self._animation_frames.get())) 720 for x in range(self._animation_frames.get(),0,-1)] 721 722 # Move the text string down, if necessary. 723 dy = widget.bbox()[3] + 30 - self._canvas.coords(self._textline)[1] 724 if dy > 0: 725 for twidget in self._textwidgets: twidget.move(0, dy) 726 self._canvas.move(self._textline, 0, dy) 727 728 self._animate_expand_frame(widget, colors)
729
730 - def _makeroom(self, treeseg):
731 """ 732 Make sure that no sibling tree bbox's overlap. 733 """ 734 parent = treeseg.parent() 735 if not isinstance(parent, TreeSegmentWidget): return 736 737 index = parent.subtrees().index(treeseg) 738 739 # Handle siblings to the right 740 rsiblings = parent.subtrees()[index+1:] 741 if rsiblings: 742 dx = treeseg.bbox()[2] - rsiblings[0].bbox()[0] + 10 743 for sibling in rsiblings: sibling.move(dx, 0) 744 745 # Handle siblings to the left 746 if index > 0: 747 lsibling = parent.subtrees()[index-1] 748 dx = max(0, lsibling.bbox()[2] - treeseg.bbox()[0] + 10) 749 treeseg.move(dx, 0) 750 751 # Keep working up the tree. 752 self._makeroom(parent)
753
754 - def _animate_expand_frame(self, widget, colors):
755 if len(colors) > 0: 756 self._animating_lock = 1 757 widget['color'] = colors[0] 758 for subtree in widget.subtrees(): 759 if isinstance(subtree, TreeSegmentWidget): 760 subtree.node()['color'] = colors[0] 761 else: 762 subtree['color'] = colors[0] 763 self._top.after(50, self._animate_expand_frame, 764 widget, colors[1:]) 765 else: 766 widget['color'] = 'black' 767 for subtree in widget.subtrees(): 768 if isinstance(subtree, TreeSegmentWidget): 769 subtree.node()['color'] = 'black' 770 else: 771 subtree['color'] = 'black' 772 self._redraw_quick() 773 widget.node()['color'] = 'black' 774 self._animating_lock = 0 775 if self._autostep: self._step()
776
777 - def _animate_backtrack(self, treeloc):
778 # Flash red first, if we're animating. 779 if self._animation_frames.get() == 0: colors = [] 780 else: colors = ['#a00000', '#000000', '#a00000'] 781 colors += ['gray%d' % (10*int(10*x/(self._animation_frames.get()))) 782 for x in range(1, self._animation_frames.get()+1)] 783 784 widgets = [self._get(self._tree, treeloc).parent()] 785 for subtree in widgets[0].subtrees(): 786 if isinstance(subtree, TreeSegmentWidget): 787 widgets.append(subtree.node()) 788 else: 789 widgets.append(subtree) 790 791 self._animate_backtrack_frame(widgets, colors)
792
793 - def _animate_backtrack_frame(self, widgets, colors):
794 if len(colors) > 0: 795 self._animating_lock = 1 796 for widget in widgets: widget['color'] = colors[0] 797 self._top.after(50, self._animate_backtrack_frame, 798 widgets, colors[1:]) 799 else: 800 for widget in widgets[0].subtrees(): 801 widgets[0].remove_child(widget) 802 widget.destroy() 803 self._redraw_quick() 804 self._animating_lock = 0 805 if self._autostep: self._step()
806
807 - def _animate_match_backtrack(self, treeloc):
808 widget = self._get(self._tree, treeloc) 809 node = widget.parent().node() 810 dy = (1.0 * (node.bbox()[3] - widget.bbox()[1] + 14) / 811 max(1, self._animation_frames.get())) 812 self._animate_match_backtrack_frame(self._animation_frames.get(), 813 widget, dy)
814
815 - def _animate_match(self, treeloc):
816 widget = self._get(self._tree, treeloc) 817 818 dy = ((self._textwidgets[0].bbox()[1] - widget.bbox()[3] - 10.0) / 819 max(1, self._animation_frames.get())) 820 self._animate_match_frame(self._animation_frames.get(), widget, dy)
821
822 - def _animate_match_frame(self, frame, widget, dy):
823 if frame > 0: 824 self._animating_lock = 1 825 widget.move(0, dy) 826 self._top.after(10, self._animate_match_frame, 827 frame-1, widget, dy) 828 else: 829 widget['color'] = '#006040' 830 self._redraw_quick() 831 self._animating_lock = 0 832 if self._autostep: self._step()
833
834 - def _animate_match_backtrack_frame(self, frame, widget, dy):
835 if frame > 0: 836 self._animating_lock = 1 837 widget.move(0, dy) 838 self._top.after(10, self._animate_match_backtrack_frame, 839 frame-1, widget, dy) 840 else: 841 widget.parent().remove_child(widget) 842 widget.destroy() 843 self._animating_lock = 0 844 if self._autostep: self._step()
845
846 - def edit_grammar(self, *e):
847 CFGEditor(self._top, self._parser.grammar(), self.set_grammar)
848
849 - def set_grammar(self, grammar):
850 self._parser.set_grammar(grammar) 851 self._productions = list(grammar.productions()) 852 self._prodlist.delete(0, 'end') 853 for production in self._productions: 854 self._prodlist.insert('end', (' %s' % production))
855
856 - def edit_sentence(self, *e):
857 sentence = string.join(self._sent) 858 title = 'Edit Text' 859 instr = 'Enter a new sentence to parse.' 860 EntryDialog(self._top, sentence, instr, self.set_sentence, title)
861
862 - def set_sentence(self, sentence):
863 self._sent = sentence.split() #[XX] use tagged? 864 self.reset()
865
866 -def demo():
867 """ 868 Create a recursive descent parser demo, using a simple grammar and 869 text. 870 """ 871 from nltk import cfg 872 grammar = cfg.parse_cfg(""" 873 # Grammatical productions. 874 S -> NP VP 875 NP -> Det N PP | Det N 876 VP -> V NP PP | V NP | V 877 PP -> P NP 878 # Lexical productions. 879 NP -> 'I' 880 Det -> 'the' | 'a' 881 N -> 'man' | 'park' | 'dog' | 'telescope' 882 V -> 'ate' | 'saw' 883 P -> 'in' | 'under' | 'with' 884 """) 885 886 sent = 'the dog saw a man in the park'.split() 887 888 RecursiveDescentDemo(grammar, sent).mainloop()
889 890 if __name__ == '__main__': demo() 891