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

Source Code for Module nltk.draw.cfg

  1  # Natural Language Toolkit: CFG visualization 
  2  # 
  3  # Copyright (C) 2001-2011 NLTK Project 
  4  # Author: Edward Loper <edloper@gradient.cis.upenn.edu> 
  5  # URL: <http://www.nltk.org/> 
  6  # For license information, see LICENSE.TXT 
  7  # 
  8  # $Id: cfg.py 8730 2011-03-08 04:49:46Z StevenBird1 $ 
  9   
 10  """ 
 11  Visualization tools for CFGs. 
 12  """ 
 13   
 14  import re 
 15   
 16   
 17  """ 
 18  Idea for a nice demo: 
 19    - 3 panes: grammar, treelet, working area 
 20      - grammar is a list of productions 
 21      - when you select a production, the treelet that it licenses appears 
 22        in the treelet area 
 23      - the working area has the text on the bottom, and S at top.  When 
 24        you select a production, it shows (ghosted) the locations where 
 25        that production's treelet could be attached to either the text 
 26        or the tree rooted at S. 
 27      - the user can drag the treelet onto one of those (or click on them?) 
 28      - the user can delete pieces of the tree from the working area 
 29        (right click?) 
 30      - connecting top to bottom? drag one NP onto another? 
 31   
 32  +-------------------------------------------------------------+ 
 33  | S -> NP VP   |                 S                            | 
 34  |[NP -> Det N ]|                / \                           | 
 35  |     ...      |              NP  VP                          | 
 36  | N -> 'dog'   |                                              | 
 37  | N -> 'cat'   |                                              | 
 38  |     ...      |                                              | 
 39  +--------------+                                              | 
 40  |      NP      |                      Det     N               | 
 41  |     /  \     |                       |      |               | 
 42  |   Det   N    |  the    cat    saw   the    dog              | 
 43  |              |                                              | 
 44  +--------------+----------------------------------------------+ 
 45   
 46  Operations: 
 47    - connect a new treelet -- drag or click shadow 
 48    - delete a treelet -- right click 
 49      - if only connected to top, delete everything below 
 50      - if only connected to bottom, delete everything above 
 51    - connect top & bottom -- drag a leaf to a root or a root to a leaf 
 52    - disconnect top & bottom -- right click 
 53      - if connected to top & bottom, then disconnect 
 54  """ 
 55   
 56  from nltk.grammar import ContextFreeGrammar, Nonterminal, parse_cfg_production 
 57  from nltk.tree import Tree 
 58   
 59  from util import * 
 60  from tree import * 
 61   
 62   
 63  ###################################################################### 
 64  # Production List 
 65  ###################################################################### 
 66   
67 -class ProductionList(ColorizedList):
68 ARROW = SymbolWidget.SYMBOLS['rightarrow'] 69
70 - def _init_colortags(self, textwidget, options):
71 textwidget.tag_config('terminal', foreground='#006000') 72 textwidget.tag_config('arrow', font='symbol', underline='0') 73 textwidget.tag_config('nonterminal', foreground='blue', 74 font=('helvetica', -12, 'bold'))
75
76 - def _item_repr(self, item):
77 contents = [] 78 contents.append(('%s\t' % item.lhs(), 'nonterminal')) 79 contents.append((self.ARROW, 'arrow')) 80 for elt in item.rhs(): 81 if isinstance(elt, Nonterminal): 82 contents.append((' %s' % elt.symbol(), 'nonterminal')) 83 else: 84 contents.append((' %r' % elt, 'terminal')) 85 return contents
86 87 ###################################################################### 88 # CFG Editor 89 ###################################################################### 90 91 _CFGEditor_HELP = """ 92 93 The CFG Editor can be used to create or modify context free grammars. 94 A context free grammar consists of a start symbol and a list of 95 productions. The start symbol is specified by the text entry field in 96 the upper right hand corner of the editor; and the list of productions 97 are specified in the main text editing box. 98 99 Every non-blank line specifies a single production. Each production 100 has the form "LHS -> RHS," where LHS is a single nonterminal, and RHS 101 is a list of nonterminals and terminals. 102 103 Nonterminals must be a single word, such as S or NP or NP_subj. 104 Currently, nonterminals must consists of alphanumeric characters and 105 underscores (_). Nonterminals are colored blue. If you place the 106 mouse over any nonterminal, then all occurances of that nonterminal 107 will be highlighted. 108 109 Termianals must be surrounded by single quotes (') or double 110 quotes(\"). For example, "dog" and "New York" are terminals. 111 Currently, the string within the quotes must consist of alphanumeric 112 characters, underscores, and spaces. 113 114 To enter a new production, go to a blank line, and type a nonterminal, 115 followed by an arrow (->), followed by a sequence of terminals and 116 nonterminals. Note that "->" (dash + greater-than) is automatically 117 converted to an arrow symbol. When you move your cursor to a 118 different line, your production will automatically be colorized. If 119 there are any errors, they will be highlighted in red. 120 121 Note that the order of the productions is signifigant for some 122 algorithms. To re-order the productions, use cut and paste to move 123 them. 124 125 Use the buttons at the bottom of the window when you are done editing 126 the CFG: 127 - Ok: apply the new CFG, and exit the editor. 128 - Apply: apply the new CFG, and do not exit the editor. 129 - Reset: revert to the original CFG, and do not exit the editor. 130 - Cancel: revert to the original CFG, and exit the editor. 131 132 """ 133
134 -class CFGEditor(object):
135 """ 136 A dialog window for creating and editing context free grammars. 137 C{CFGEditor} places the following restrictions on what C{CFG}s can 138 be edited: 139 - All nonterminals must be strings consisting of word 140 characters. 141 - All terminals must be strings consisting of word characters 142 and space characters. 143 """ 144 # Regular expressions used by _analyze_line. Precompile them, so 145 # we can process the text faster. 146 ARROW = SymbolWidget.SYMBOLS['rightarrow'] 147 _LHS_RE = re.compile(r"(^\s*\w+\s*)(->|("+ARROW+"))") 148 _ARROW_RE = re.compile("\s*(->|("+ARROW+"))\s*") 149 _PRODUCTION_RE = re.compile(r"(^\s*\w+\s*)" + # LHS 150 "(->|("+ARROW+"))\s*" + # arrow 151 r"((\w+|'[\w ]*'|\"[\w ]*\"|\|)\s*)*$") # RHS 152 _TOKEN_RE = re.compile("\\w+|->|'[\\w ]+'|\"[\\w ]+\"|("+ARROW+")") 153 _BOLD = ('helvetica', -12, 'bold') 154
155 - def __init__(self, parent, cfg=None, set_cfg_callback=None):
156 self._parent = parent 157 if cfg is not None: self._cfg = cfg 158 else: self._cfg = ContextFreeGrammar(Nonterminal('S'), []) 159 self._set_cfg_callback = set_cfg_callback 160 161 self._highlight_matching_nonterminals = 1 162 163 # Create the top-level window. 164 self._top = Toplevel(parent) 165 self._init_bindings() 166 167 self._init_startframe() 168 self._startframe.pack(side='top', fill='x', expand=0) 169 self._init_prodframe() 170 self._prodframe.pack(side='top', fill='both', expand=1) 171 self._init_buttons() 172 self._buttonframe.pack(side='bottom', fill='x', expand=0) 173 174 self._textwidget.focus()
175
176 - def _init_startframe(self):
177 frame = self._startframe = Frame(self._top) 178 self._start = Entry(frame) 179 self._start.pack(side='right') 180 Label(frame, text='Start Symbol:').pack(side='right') 181 Label(frame, text='Productions:').pack(side='left') 182 self._start.insert(0, self._cfg.start().symbol())
183
184 - def _init_buttons(self):
185 frame = self._buttonframe = Frame(self._top) 186 Button(frame, text='Ok', command=self._ok, 187 underline=0, takefocus=0).pack(side='left') 188 Button(frame, text='Apply', command=self._apply, 189 underline=0, takefocus=0).pack(side='left') 190 Button(frame, text='Reset', command=self._reset, 191 underline=0, takefocus=0,).pack(side='left') 192 Button(frame, text='Cancel', command=self._cancel, 193 underline=0, takefocus=0).pack(side='left') 194 Button(frame, text='Help', command=self._help, 195 underline=0, takefocus=0).pack(side='right')
196
197 - def _init_bindings(self):
198 self._top.title('CFG Editor') 199 self._top.bind('<Control-q>', self._cancel) 200 self._top.bind('<Alt-q>', self._cancel) 201 self._top.bind('<Control-d>', self._cancel) 202 #self._top.bind('<Control-x>', self._cancel) 203 self._top.bind('<Alt-x>', self._cancel) 204 self._top.bind('<Escape>', self._cancel) 205 #self._top.bind('<Control-c>', self._cancel) 206 self._top.bind('<Alt-c>', self._cancel) 207 208 self._top.bind('<Control-o>', self._ok) 209 self._top.bind('<Alt-o>', self._ok) 210 self._top.bind('<Control-a>', self._apply) 211 self._top.bind('<Alt-a>', self._apply) 212 self._top.bind('<Control-r>', self._reset) 213 self._top.bind('<Alt-r>', self._reset) 214 self._top.bind('<Control-h>', self._help) 215 self._top.bind('<Alt-h>', self._help) 216 self._top.bind('<F1>', self._help)
217
218 - def _init_prodframe(self):
219 self._prodframe = Frame(self._top) 220 221 # Create the basic Text widget & scrollbar. 222 self._textwidget = Text(self._prodframe, background='#e0e0e0', 223 exportselection=1) 224 self._textscroll = Scrollbar(self._prodframe, takefocus=0, 225 orient='vertical') 226 self._textwidget.config(yscrollcommand = self._textscroll.set) 227 self._textscroll.config(command=self._textwidget.yview) 228 self._textscroll.pack(side='right', fill='y') 229 self._textwidget.pack(expand=1, fill='both', side='left') 230 231 # Initialize the colorization tags. Each nonterminal gets its 232 # own tag, so they aren't listed here. 233 self._textwidget.tag_config('terminal', foreground='#006000') 234 self._textwidget.tag_config('arrow', font='symbol') 235 self._textwidget.tag_config('error', background='red') 236 237 # Keep track of what line they're on. We use that to remember 238 # to re-analyze a line whenever they leave it. 239 self._linenum = 0 240 241 # Expand "->" to an arrow. 242 self._top.bind('>', self._replace_arrows) 243 244 # Re-colorize lines when appropriate. 245 self._top.bind('<<Paste>>', self._analyze) 246 self._top.bind('<KeyPress>', self._check_analyze) 247 self._top.bind('<ButtonPress>', self._check_analyze) 248 249 # Tab cycles focus. (why doesn't this work??) 250 def cycle(e, textwidget=self._textwidget): 251 textwidget.tk_focusNext().focus()
252 self._textwidget.bind('<Tab>', cycle) 253 254 prod_tuples = [(p.lhs(),[p.rhs()]) for p in self._cfg.productions()] 255 for i in range(len(prod_tuples)-1,0,-1): 256 if (prod_tuples[i][0] == prod_tuples[i-1][0]): 257 if () in prod_tuples[i][1]: continue 258 if () in prod_tuples[i-1][1]: continue 259 print prod_tuples[i-1][1] 260 print prod_tuples[i][1] 261 prod_tuples[i-1][1].extend(prod_tuples[i][1]) 262 del prod_tuples[i] 263 264 for lhs, rhss in prod_tuples: 265 print lhs, rhss 266 s = '%s ->' % lhs 267 for rhs in rhss: 268 for elt in rhs: 269 if isinstance(elt, Nonterminal): s += ' %s' % elt 270 else: s += ' %r' % elt 271 s += ' |' 272 s = s[:-2] + '\n' 273 self._textwidget.insert('end', s) 274 275 self._analyze()
276 277 # # Add the producitons to the text widget, and colorize them. 278 # prod_by_lhs = {} 279 # for prod in self._cfg.productions(): 280 # if len(prod.rhs()) > 0: 281 # prod_by_lhs.setdefault(prod.lhs(),[]).append(prod) 282 # for (lhs, prods) in prod_by_lhs.items(): 283 # self._textwidget.insert('end', '%s ->' % lhs) 284 # self._textwidget.insert('end', self._rhs(prods[0])) 285 # for prod in prods[1:]: 286 # print '\t|'+self._rhs(prod), 287 # self._textwidget.insert('end', '\t|'+self._rhs(prod)) 288 # print 289 # self._textwidget.insert('end', '\n') 290 # for prod in self._cfg.productions(): 291 # if len(prod.rhs()) == 0: 292 # self._textwidget.insert('end', '%s' % prod) 293 # self._analyze() 294 295 # def _rhs(self, prod): 296 # s = '' 297 # for elt in prod.rhs(): 298 # if isinstance(elt, Nonterminal): s += ' %s' % elt.symbol() 299 # else: s += ' %r' % elt 300 # return s 301
302 - def _clear_tags(self, linenum):
303 """ 304 Remove all tags (except C{arrow} and C{sel}) from the given 305 line of the text widget used for editing the productions. 306 """ 307 start = '%d.0'%linenum 308 end = '%d.end'%linenum 309 for tag in self._textwidget.tag_names(): 310 if tag not in ('arrow', 'sel'): 311 self._textwidget.tag_remove(tag, start, end)
312
313 - def _check_analyze(self, *e):
314 """ 315 Check if we've moved to a new line. If we have, then remove 316 all colorization from the line we moved to, and re-colorize 317 the line that we moved from. 318 """ 319 linenum = int(self._textwidget.index('insert').split('.')[0]) 320 if linenum != self._linenum: 321 self._clear_tags(linenum) 322 self._analyze_line(self._linenum) 323 self._linenum = linenum
324
325 - def _replace_arrows(self, *e):
326 """ 327 Replace any C{'->'} text strings with arrows (char \\256, in 328 symbol font). This searches the whole buffer, but is fast 329 enough to be done anytime they press '>'. 330 """ 331 arrow = '1.0' 332 while 1: 333 arrow = self._textwidget.search('->', arrow, 'end+1char') 334 if arrow == '': break 335 self._textwidget.delete(arrow, arrow+'+2char') 336 self._textwidget.insert(arrow, self.ARROW, 'arrow') 337 self._textwidget.insert(arrow, '\t') 338 339 arrow = '1.0' 340 while 1: 341 arrow = self._textwidget.search(self.ARROW, arrow+'+1char', 342 'end+1char') 343 if arrow == '': break 344 self._textwidget.tag_add('arrow', arrow, arrow+'+1char')
345
346 - def _analyze_token(self, match, linenum):
347 """ 348 Given a line number and a regexp match for a token on that 349 line, colorize the token. Note that the regexp match gives us 350 the token's text, start index (on the line), and end index (on 351 the line). 352 """ 353 # What type of token is it? 354 if match.group()[0] in "'\"": tag = 'terminal' 355 elif match.group() in ('->', self.ARROW): tag = 'arrow' 356 else: 357 # If it's a nonterminal, then set up new bindings, so we 358 # can highlight all instances of that nonterminal when we 359 # put the mouse over it. 360 tag = 'nonterminal_'+match.group() 361 if tag not in self._textwidget.tag_names(): 362 self._init_nonterminal_tag(tag) 363 364 start = '%d.%d' % (linenum, match.start()) 365 end = '%d.%d' % (linenum, match.end()) 366 self._textwidget.tag_add(tag, start, end)
367
368 - def _init_nonterminal_tag(self, tag, foreground='blue'):
369 self._textwidget.tag_config(tag, foreground=foreground, 370 font=CFGEditor._BOLD) 371 if not self._highlight_matching_nonterminals: 372 return 373 def enter(e, textwidget=self._textwidget, tag=tag): 374 textwidget.tag_config(tag, background='#80ff80')
375 def leave(e, textwidget=self._textwidget, tag=tag): 376 textwidget.tag_config(tag, background='') 377 self._textwidget.tag_bind(tag, '<Enter>', enter) 378 self._textwidget.tag_bind(tag, '<Leave>', leave) 379
380 - def _analyze_line(self, linenum):
381 """ 382 Colorize a given line. 383 """ 384 # Get rid of any tags that were previously on the line. 385 self._clear_tags(linenum) 386 387 # Get the line line's text string. 388 line = self._textwidget.get(`linenum`+'.0', `linenum`+'.end') 389 390 # If it's a valid production, then colorize each token. 391 if CFGEditor._PRODUCTION_RE.match(line): 392 # It's valid; Use _TOKEN_RE to tokenize the production, 393 # and call analyze_token on each token. 394 def analyze_token(match, self=self, linenum=linenum): 395 self._analyze_token(match, linenum) 396 return ''
397 CFGEditor._TOKEN_RE.sub(analyze_token, line) 398 elif line.strip() != '': 399 # It's invalid; show the user where the error is. 400 self._mark_error(linenum, line) 401
402 - def _mark_error(self, linenum, line):
403 """ 404 Mark the location of an error in a line. 405 """ 406 arrowmatch = CFGEditor._ARROW_RE.search(line) 407 if not arrowmatch: 408 # If there's no arrow at all, highlight the whole line. 409 start = '%d.0' % linenum 410 end = '%d.end' % linenum 411 elif not CFGEditor._LHS_RE.match(line): 412 # Otherwise, if the LHS is bad, highlight it. 413 start = '%d.0' % linenum 414 end = '%d.%d' % (linenum, arrowmatch.start()) 415 else: 416 # Otherwise, highlight the RHS. 417 start = '%d.%d' % (linenum, arrowmatch.end()) 418 end = '%d.end' % linenum 419 420 # If we're highlighting 0 chars, highlight the whole line. 421 if self._textwidget.compare(start, '==', end): 422 start = '%d.0' % linenum 423 end = '%d.end' % linenum 424 self._textwidget.tag_add('error', start, end)
425
426 - def _analyze(self, *e):
427 """ 428 Replace C{->} with arrows, and colorize the entire buffer. 429 """ 430 self._replace_arrows() 431 numlines = int(self._textwidget.index('end').split('.')[0]) 432 for linenum in range(1, numlines+1): # line numbers start at 1. 433 self._analyze_line(linenum)
434
435 - def _parse_productions(self):
436 """ 437 Parse the current contents of the textwidget buffer, to create 438 a list of productions. 439 """ 440 productions = [] 441 442 # Get the text, normalize it, and split it into lines. 443 text = self._textwidget.get('1.0', 'end') 444 text = re.sub(self.ARROW, '->', text) 445 text = re.sub('\t', ' ', text) 446 lines = text.split('\n') 447 448 # Convert each line to a CFG production 449 for line in lines: 450 line = line.strip() 451 if line=='': continue 452 productions += parse_cfg_production(line) 453 #if line.strip() == '': continue 454 #if not CFGEditor._PRODUCTION_RE.match(line): 455 # raise ValueError('Bad production string %r' % line) 456 # 457 #(lhs_str, rhs_str) = line.split('->') 458 #lhs = Nonterminal(lhs_str.strip()) 459 #rhs = [] 460 #def parse_token(match, rhs=rhs): 461 # token = match.group() 462 # if token[0] in "'\"": rhs.append(token[1:-1]) 463 # else: rhs.append(Nonterminal(token)) 464 # return '' 465 #CFGEditor._TOKEN_RE.sub(parse_token, rhs_str) 466 # 467 #productions.append(Production(lhs, *rhs)) 468 469 return productions
470
471 - def _destroy(self, *e):
472 if self._top is None: return 473 self._top.destroy() 474 self._top = None
475
476 - def _ok(self, *e):
477 self._apply() 478 self._destroy()
479
480 - def _apply(self, *e):
481 productions = self._parse_productions() 482 start = Nonterminal(self._start.get()) 483 cfg = ContextFreeGrammar(start, productions) 484 if self._set_cfg_callback is not None: 485 self._set_cfg_callback(cfg)
486
487 - def _reset(self, *e):
488 self._textwidget.delete('1.0', 'end') 489 for production in self._cfg.productions(): 490 self._textwidget.insert('end', '%s\n' % production) 491 self._analyze() 492 if self._set_cfg_callback is not None: 493 self._set_cfg_callback(self._cfg)
494
495 - def _cancel(self, *e):
496 try: self._reset() 497 except: pass 498 self._destroy()
499
500 - def _help(self, *e):
501 # The default font's not very legible; try using 'fixed' instead. 502 try: 503 ShowText(self._parent, 'Help: Chart Parser Demo', 504 (_CFGEditor_HELP).strip(), width=75, font='fixed') 505 except: 506 ShowText(self._parent, 'Help: Chart Parser Demo', 507 (_CFGEditor_HELP).strip(), width=75)
508 509 ###################################################################### 510 # New Demo (built tree based on cfg) 511 ###################################################################### 512
513 -class CFGDemo(object):
514 - def __init__(self, grammar, text):
515 self._grammar = grammar 516 self._text = text 517 518 # Set up the main window. 519 self._top = Tk() 520 self._top.title('Context Free Grammar Demo') 521 522 # Base font size 523 self._size = IntVar(self._top) 524 self._size.set(12) # = medium 525 526 # Set up the key bindings 527 self._init_bindings(self._top) 528 529 # Create the basic frames 530 frame1 = Frame(self._top) 531 frame1.pack(side='left', fill='y', expand=0) 532 self._init_menubar(self._top) 533 self._init_buttons(self._top) 534 self._init_grammar(frame1) 535 self._init_treelet(frame1) 536 self._init_workspace(self._top)
537 538 #////////////////////////////////////////////////// 539 # Initialization 540 #////////////////////////////////////////////////// 541
542 - def _init_bindings(self, top):
543 top.bind('<Control-q>', self.destroy)
544
545 - def _init_menubar(self, parent): pass
546
547 - def _init_buttons(self, parent): pass
548
549 - def _init_grammar(self, parent):
550 self._prodlist = ProductionList(parent, self._grammar, width=20) 551 self._prodlist.pack(side='top', fill='both', expand=1) 552 self._prodlist.focus() 553 self._prodlist.add_callback('select', self._selectprod_cb) 554 self._prodlist.add_callback('move', self._selectprod_cb)
555
556 - def _init_treelet(self, parent):
557 self._treelet_canvas = Canvas(parent, background='white') 558 self._treelet_canvas.pack(side='bottom', fill='x') 559 self._treelet = None
560
561 - def _init_workspace(self, parent):
562 self._workspace = CanvasFrame(parent, background='white') 563 self._workspace.pack(side='right', fill='both', expand=1) 564 self._tree = None 565 self.reset_workspace()
566 567 #////////////////////////////////////////////////// 568 # Workspace 569 #////////////////////////////////////////////////// 570
571 - def reset_workspace(self):
572 c = self._workspace.canvas() 573 fontsize = int(self._size.get()) 574 node_font = ('helvetica', -(fontsize+4), 'bold') 575 leaf_font = ('helvetica', -(fontsize+2)) 576 577 # Remove the old tree 578 if self._tree is not None: 579 self._workspace.remove_widget(self._tree) 580 581 # The root of the tree. 582 start = self._grammar.start().symbol() 583 rootnode = TextWidget(c, start, font=node_font, draggable=1) 584 585 # The leaves of the tree. 586 leaves = [] 587 for word in self._text: 588 if isinstance(word, Token): word = word.type() 589 leaves.append(TextWidget(c, word, font=leaf_font, draggable=1)) 590 591 # Put it all together into one tree 592 self._tree = TreeSegmentWidget(c, rootnode, leaves, 593 color='white') 594 595 # Add it to the workspace. 596 self._workspace.add_widget(self._tree) 597 598 # Move the leaves to the bottom of the workspace. 599 for leaf in leaves: leaf.move(0,100)
600 601 #self._nodes = {start:1} 602 #self._leaves = dict([(l,1) for l in leaves]) 603
604 - def workspace_markprod(self, production):
605 pass
606
607 - def _markproduction(self, prod, tree=None):
608 if tree is None: tree = self._tree 609 for i in range(len(tree.subtrees())-len(prod.rhs())): 610 if tree['color', i] == 'white': 611 self._markproduction 612 613 for j, node in enumerate(prod.rhs()): 614 widget = tree.subtrees()[i+j] 615 if (isinstance(node, Nonterminal) and 616 isinstance(widget, TreeSegmentWidget) and 617 node.symbol == widget.node().text()): 618 pass # matching nonterminal 619 elif (isinstance(node, (str, unicode)) and 620 isinstance(widget, TextWidget) and 621 node == widget.text()): 622 pass # matching nonterminal 623 else: break 624 else: 625 # Everything matched! 626 print 'MATCH AT', i
627 628 #////////////////////////////////////////////////// 629 # Grammar 630 #////////////////////////////////////////////////// 631
632 - def _selectprod_cb(self, production):
633 canvas = self._treelet_canvas 634 635 self._prodlist.highlight(production) 636 if self._treelet is not None: self._treelet.destroy() 637 638 # Convert the production to a tree. 639 rhs = production.rhs() 640 for (i, elt) in enumerate(rhs): 641 if isinstance(elt, Nonterminal): elt = Tree(elt) 642 tree = Tree(production.lhs().symbol(), *rhs) 643 644 # Draw the tree in the treelet area. 645 fontsize = int(self._size.get()) 646 node_font = ('helvetica', -(fontsize+4), 'bold') 647 leaf_font = ('helvetica', -(fontsize+2)) 648 self._treelet = tree_to_treesegment(canvas, tree, 649 node_font=node_font, 650 leaf_font=leaf_font) 651 self._treelet['draggable'] = 1 652 653 # Center the treelet. 654 (x1, y1, x2, y2) = self._treelet.bbox() 655 w, h = int(canvas['width']), int(canvas['height']) 656 self._treelet.move((w-x1-x2)/2, (h-y1-y2)/2) 657 658 # Mark the places where we can add it to the workspace. 659 self._markproduction(production)
660
661 - def destroy(self, *args):
662 self._top.destroy()
663
664 - def mainloop(self, *args, **kwargs):
665 self._top.mainloop(*args, **kwargs)
666
667 -def demo2():
668 from nltk import Nonterminal, Production, ContextFreeGrammar 669 nonterminals = 'S VP NP PP P N Name V Det' 670 (S, VP, NP, PP, P, N, Name, V, Det) = [Nonterminal(s) 671 for s in nonterminals.split()] 672 productions = ( 673 # Syntactic Productions 674 Production(S, [NP, VP]), 675 Production(NP, [Det, N]), 676 Production(NP, [NP, PP]), 677 Production(VP, [VP, PP]), 678 Production(VP, [V, NP, PP]), 679 Production(VP, [V, NP]), 680 Production(PP, [P, NP]), 681 Production(PP, []), 682 683 Production(PP, ['up', 'over', NP]), 684 685 # Lexical Productions 686 Production(NP, ['I']), Production(Det, ['the']), 687 Production(Det, ['a']), Production(N, ['man']), 688 Production(V, ['saw']), Production(P, ['in']), 689 Production(P, ['with']), Production(N, ['park']), 690 Production(N, ['dog']), Production(N, ['statue']), 691 Production(Det, ['my']), 692 ) 693 grammar = ContextFreeGrammar(S, productions) 694 695 text = 'I saw a man in the park'.split() 696 d=CFGDemo(grammar, text) 697 d.mainloop()
698 699 ###################################################################### 700 # Old Demo 701 ###################################################################### 702
703 -def demo():
704 from nltk import Nonterminal, parse_cfg 705 nonterminals = 'S VP NP PP P N Name V Det' 706 (S, VP, NP, PP, P, N, Name, V, Det) = [Nonterminal(s) 707 for s in nonterminals.split()] 708 709 grammar = parse_cfg(""" 710 S -> NP VP 711 PP -> P NP 712 NP -> Det N 713 NP -> NP PP 714 VP -> V NP 715 VP -> VP PP 716 Det -> 'a' 717 Det -> 'the' 718 Det -> 'my' 719 NP -> 'I' 720 N -> 'dog' 721 N -> 'man' 722 N -> 'park' 723 N -> 'statue' 724 V -> 'saw' 725 P -> 'in' 726 P -> 'up' 727 P -> 'over' 728 P -> 'with' 729 """) 730 731 def cb(grammar): print grammar 732 top = Tk() 733 editor = CFGEditor(top, grammar, cb) 734 Label(top, text='\nTesting CFG Editor\n').pack() 735 Button(top, text='Quit', command=top.destroy).pack() 736 top.mainloop()
737
738 -def demo3():
739 from nltk import Production 740 (S, VP, NP, PP, P, N, Name, V, Det) = \ 741 nonterminals('S, VP, NP, PP, P, N, Name, V, Det') 742 743 productions = ( 744 # Syntactic Productions 745 Production(S, [NP, VP]), 746 Production(NP, [Det, N]), 747 Production(NP, [NP, PP]), 748 Production(VP, [VP, PP]), 749 Production(VP, [V, NP, PP]), 750 Production(VP, [V, NP]), 751 Production(PP, [P, NP]), 752 Production(PP, []), 753 754 Production(PP, ['up', 'over', NP]), 755 756 # Lexical Productions 757 Production(NP, ['I']), Production(Det, ['the']), 758 Production(Det, ['a']), Production(N, ['man']), 759 Production(V, ['saw']), Production(P, ['in']), 760 Production(P, ['with']), Production(N, ['park']), 761 Production(N, ['dog']), Production(N, ['statue']), 762 Production(Det, ['my']), 763 ) 764 765 t = Tk() 766 def destroy(e, t=t): t.destroy() 767 t.bind('q', destroy) 768 p = ProductionList(t, productions) 769 p.pack(expand=1, fill='both') 770 p.add_callback('select', p.markonly) 771 p.add_callback('move', p.markonly) 772 p.focus() 773 p.mark(productions[2]) 774 p.mark(productions[8])
775 776 if __name__ == '__main__': demo() 777