nltk 3.2__zip → 3.3__zip
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nltk-3.2 → nltk-3.3}/INSTALL.txt +6 -6
- {nltk-3.2 → nltk-3.3}/LICENSE.txt +13 -13
- {nltk-3.2 → nltk-3.3}/MANIFEST.in +6 -6
- {nltk-3.2/nltk.egg-info → nltk-3.3}/PKG-INFO +13 -5
- nltk-3.2/README.txt → nltk-3.3/README.md +49 -48
- nltk-3.3/nltk/VERSION +1 -0
- {nltk-3.2 → nltk-3.3}/nltk/__init__.py +185 -185
- {nltk-3.2 → nltk-3.3}/nltk/app/__init__.py +52 -53
- {nltk-3.2 → nltk-3.3}/nltk/app/chartparser_app.py +2276 -2276
- {nltk-3.2 → nltk-3.3}/nltk/app/chunkparser_app.py +1262 -1264
- {nltk-3.2 → nltk-3.3}/nltk/app/collocations_app.py +347 -350
- {nltk-3.2 → nltk-3.3}/nltk/app/concordance_app.py +568 -570
- {nltk-3.2 → nltk-3.3}/nltk/app/nemo_app.py +156 -155
- {nltk-3.2 → nltk-3.3}/nltk/app/rdparser_app.py +892 -894
- {nltk-3.2 → nltk-3.3}/nltk/app/srparser_app.py +809 -809
- {nltk-3.2 → nltk-3.3}/nltk/app/wordfreq_app.py +32 -32
- {nltk-3.2 → nltk-3.3}/nltk/app/wordnet_app.py +968 -968
- {nltk-3.2 → nltk-3.3}/nltk/book.py +91 -90
- {nltk-3.2 → nltk-3.3}/nltk/ccg/__init__.py +22 -22
- {nltk-3.2 → nltk-3.3}/nltk/ccg/api.py +359 -335
- {nltk-3.2 → nltk-3.3}/nltk/ccg/chart.py +394 -361
- {nltk-3.2 → nltk-3.3}/nltk/ccg/combinator.py +343 -312
- {nltk-3.2 → nltk-3.3}/nltk/ccg/lexicon.py +328 -290
- nltk-3.3/nltk/ccg/logic.py +46 -0
- {nltk-3.2 → nltk-3.3}/nltk/chat/__init__.py +49 -49
- {nltk-3.2 → nltk-3.3}/nltk/chat/eliza.py +244 -244
- {nltk-3.2 → nltk-3.3}/nltk/chat/iesha.py +140 -140
- {nltk-3.2 → nltk-3.3}/nltk/chat/rude.py +92 -92
- {nltk-3.2 → nltk-3.3}/nltk/chat/suntsu.py +117 -117
- {nltk-3.2 → nltk-3.3}/nltk/chat/util.py +122 -120
- {nltk-3.2 → nltk-3.3}/nltk/chat/zen.py +282 -282
- {nltk-3.2 → nltk-3.3}/nltk/chunk/__init__.py +190 -190
- {nltk-3.2 → nltk-3.3}/nltk/chunk/api.py +51 -51
- {nltk-3.2 → nltk-3.3}/nltk/chunk/named_entity.py +331 -331
- {nltk-3.2 → nltk-3.3}/nltk/chunk/regexp.py +1390 -1384
- {nltk-3.2 → nltk-3.3}/nltk/chunk/util.py +600 -600
- {nltk-3.2 → nltk-3.3}/nltk/classify/__init__.py +98 -98
- {nltk-3.2 → nltk-3.3}/nltk/classify/api.py +193 -193
- {nltk-3.2 → nltk-3.3}/nltk/classify/decisiontree.py +295 -295
- {nltk-3.2 → nltk-3.3}/nltk/classify/maxent.py +1510 -1492
- {nltk-3.2 → nltk-3.3}/nltk/classify/megam.py +179 -178
- {nltk-3.2 → nltk-3.3}/nltk/classify/naivebayes.py +245 -242
- {nltk-3.2 → nltk-3.3}/nltk/classify/positivenaivebayes.py +170 -170
- {nltk-3.2 → nltk-3.3}/nltk/classify/rte_classify.py +160 -191
- {nltk-3.2 → nltk-3.3}/nltk/classify/scikitlearn.py +153 -151
- {nltk-3.2 → nltk-3.3}/nltk/classify/senna.py +183 -181
- {nltk-3.2 → nltk-3.3}/nltk/classify/svm.py +15 -15
- {nltk-3.2 → nltk-3.3}/nltk/classify/tadm.py +113 -112
- {nltk-3.2 → nltk-3.3}/nltk/classify/textcat.py +193 -193
- {nltk-3.2 → nltk-3.3}/nltk/classify/util.py +324 -312
- {nltk-3.2 → nltk-3.3}/nltk/classify/weka.py +346 -345
- {nltk-3.2 → nltk-3.3}/nltk/cluster/__init__.py +86 -86
- {nltk-3.2 → nltk-3.3}/nltk/cluster/api.py +74 -70
- {nltk-3.2 → nltk-3.3}/nltk/cluster/em.py +247 -247
- {nltk-3.2 → nltk-3.3}/nltk/cluster/gaac.py +168 -168
- {nltk-3.2 → nltk-3.3}/nltk/cluster/kmeans.py +221 -221
- {nltk-3.2 → nltk-3.3}/nltk/cluster/util.py +304 -289
- nltk-3.3/nltk/collections.py +640 -0
- {nltk-3.2 → nltk-3.3}/nltk/collocations.py +388 -388
- nltk-3.3/nltk/compat.py +365 -0
- {nltk-3.2 → nltk-3.3}/nltk/corpus/__init__.py +341 -320
- {nltk-3.2 → nltk-3.3}/nltk/corpus/europarl_raw.py +44 -44
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/__init__.py +147 -145
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/aligned.py +115 -114
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/api.py +448 -446
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/bnc.py +252 -252
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/bracket_parse.py +227 -227
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/categorized_sents.py +179 -178
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/chasen.py +140 -139
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/childes.py +522 -494
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/chunked.py +212 -212
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/cmudict.py +97 -95
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/comparative_sents.py +280 -278
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/conll.py +528 -521
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/crubadan.py +116 -116
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/dependency.py +101 -101
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/framenet.py +3059 -2093
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/ieer.py +112 -111
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/indian.py +85 -86
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/ipipan.py +332 -331
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/knbc.py +187 -187
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/lin.py +156 -156
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/mte.py +311 -310
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/nkjp.py +429 -428
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/nombank.py +421 -421
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/nps_chat.py +73 -73
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/opinion_lexicon.py +115 -115
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/panlex_lite.py +165 -165
- nltk-3.3/nltk/corpus/reader/pl196x.py +292 -0
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/plaintext.py +232 -233
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/ppattach.py +96 -95
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/propbank.py +479 -481
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/pros_cons.py +128 -126
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/reviews.py +330 -328
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/rte.py +144 -146
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/semcor.py +256 -256
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/senseval.py +203 -201
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/sentiwordnet.py +137 -137
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/sinica_treebank.py +75 -75
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/string_category.py +60 -59
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/switchboard.py +119 -119
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/tagged.py +295 -294
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/timit.py +452 -450
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/toolbox.py +68 -68
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/twitter.py +155 -157
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/udhr.py +80 -80
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/util.py +804 -800
- nltk-3.3/nltk/corpus/reader/verbnet.py +595 -0
- nltk-3.3/nltk/corpus/reader/wordlist.py +134 -0
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/wordnet.py +2108 -2036
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/xmldocs.py +392 -387
- {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/ycoe.py +243 -242
- {nltk-3.2 → nltk-3.3}/nltk/corpus/util.py +144 -127
- {nltk-3.2 → nltk-3.3}/nltk/data.py +1487 -1451
- {nltk-3.2 → nltk-3.3}/nltk/decorators.py +219 -215
- {nltk-3.2 → nltk-3.3}/nltk/downloader.py +2278 -2274
- {nltk-3.2 → nltk-3.3}/nltk/draw/__init__.py +27 -27
- {nltk-3.2 → nltk-3.3}/nltk/draw/cfg.py +774 -777
- {nltk-3.2 → nltk-3.3}/nltk/draw/dispersion.py +58 -58
- {nltk-3.2 → nltk-3.3}/nltk/draw/table.py +1098 -1098
- {nltk-3.2 → nltk-3.3}/nltk/draw/tree.py +963 -966
- {nltk-3.2 → nltk-3.3}/nltk/draw/util.py +2356 -2356
- {nltk-3.2 → nltk-3.3}/nltk/featstruct.py +2617 -2500
- {nltk-3.2 → nltk-3.3}/nltk/grammar.py +1559 -1516
- {nltk-3.2 → nltk-3.3}/nltk/help.py +56 -56
- {nltk-3.2 → nltk-3.3}/nltk/inference/__init__.py +20 -20
- {nltk-3.2 → nltk-3.3}/nltk/inference/api.py +597 -589
- {nltk-3.2 → nltk-3.3}/nltk/inference/discourse.py +612 -608
- {nltk-3.2 → nltk-3.3}/nltk/inference/mace.py +311 -311
- {nltk-3.2 → nltk-3.3}/nltk/inference/nonmonotonic.py +509 -509
- {nltk-3.2 → nltk-3.3}/nltk/inference/prover9.py +431 -431
- {nltk-3.2 → nltk-3.3}/nltk/inference/resolution.py +687 -687
- {nltk-3.2 → nltk-3.3}/nltk/inference/tableau.py +607 -607
- {nltk-3.2 → nltk-3.3}/nltk/internals.py +982 -982
- {nltk-3.2 → nltk-3.3}/nltk/jsontags.py +63 -63
- {nltk-3.2 → nltk-3.3}/nltk/lazyimport.py +142 -142
- {nltk-3.2 → nltk-3.3}/nltk/metrics/__init__.py +30 -29
- {nltk-3.2 → nltk-3.3}/nltk/metrics/agreement.py +421 -401
- nltk-3.3/nltk/metrics/aline.py +607 -0
- {nltk-3.2 → nltk-3.3}/nltk/metrics/association.py +414 -410
- {nltk-3.2 → nltk-3.3}/nltk/metrics/confusionmatrix.py +206 -206
- {nltk-3.2 → nltk-3.3}/nltk/metrics/distance.py +207 -202
- {nltk-3.2 → nltk-3.3}/nltk/metrics/paice.py +381 -381
- {nltk-3.2 → nltk-3.3}/nltk/metrics/scores.py +228 -228
- {nltk-3.2 → nltk-3.3}/nltk/metrics/segmentation.py +231 -232
- {nltk-3.2 → nltk-3.3}/nltk/metrics/spearman.py +69 -69
- {nltk-3.2 → nltk-3.3}/nltk/misc/__init__.py +11 -11
- {nltk-3.2 → nltk-3.3}/nltk/misc/babelfish.py +10 -10
- {nltk-3.2 → nltk-3.3}/nltk/misc/chomsky.py +135 -133
- {nltk-3.2 → nltk-3.3}/nltk/misc/minimalset.py +83 -83
- {nltk-3.2 → nltk-3.3}/nltk/misc/sort.py +157 -157
- {nltk-3.2 → nltk-3.3}/nltk/misc/wordfinder.py +129 -129
- {nltk-3.2 → nltk-3.3}/nltk/parse/__init__.py +81 -80
- {nltk-3.2 → nltk-3.3}/nltk/parse/api.py +66 -66
- {nltk-3.2 → nltk-3.3}/nltk/parse/bllip.py +282 -282
- {nltk-3.2 → nltk-3.3}/nltk/parse/chart.py +1682 -1681
- nltk-3.3/nltk/parse/corenlp.py +780 -0
- {nltk-3.2 → nltk-3.3}/nltk/parse/dependencygraph.py +760 -758
- {nltk-3.2 → nltk-3.3}/nltk/parse/earleychart.py +452 -451
- {nltk-3.2 → nltk-3.3}/nltk/parse/evaluate.py +129 -129
- {nltk-3.2 → nltk-3.3}/nltk/parse/featurechart.py +580 -578
- {nltk-3.2 → nltk-3.3}/nltk/parse/generate.py +87 -76
- {nltk-3.2 → nltk-3.3}/nltk/parse/malt.py +356 -356
- {nltk-3.2 → nltk-3.3}/nltk/parse/nonprojectivedependencyparser.py +783 -783
- {nltk-3.2 → nltk-3.3}/nltk/parse/pchart.py +522 -522
- {nltk-3.2 → nltk-3.3}/nltk/parse/projectivedependencyparser.py +579 -578
- {nltk-3.2 → nltk-3.3}/nltk/parse/recursivedescent.py +655 -656
- {nltk-3.2 → nltk-3.3}/nltk/parse/shiftreduce.py +458 -459
- {nltk-3.2 → nltk-3.3}/nltk/parse/stanford.py +431 -406
- {nltk-3.2 → nltk-3.3}/nltk/parse/transitionparser.py +774 -772
- {nltk-3.2 → nltk-3.3}/nltk/parse/util.py +231 -231
- {nltk-3.2 → nltk-3.3}/nltk/parse/viterbi.py +401 -401
- {nltk-3.2 → nltk-3.3}/nltk/probability.py +2410 -2355
- {nltk-3.2 → nltk-3.3}/nltk/sem/__init__.py +61 -61
- {nltk-3.2 → nltk-3.3}/nltk/sem/boxer.py +1261 -1261
- {nltk-3.2 → nltk-3.3}/nltk/sem/chat80.py +782 -782
- {nltk-3.2 → nltk-3.3}/nltk/sem/cooper_storage.py +118 -118
- {nltk-3.2 → nltk-3.3}/nltk/sem/drt.py +1258 -1257
- {nltk-3.2 → nltk-3.3}/nltk/sem/drt_glue_demo.py +485 -489
- {nltk-3.2 → nltk-3.3}/nltk/sem/evaluate.py +790 -784
- {nltk-3.2 → nltk-3.3}/nltk/sem/glue.py +696 -694
- {nltk-3.2 → nltk-3.3}/nltk/sem/hole.py +386 -384
- {nltk-3.2 → nltk-3.3}/nltk/sem/lfg.py +210 -208
- {nltk-3.2 → nltk-3.3}/nltk/sem/linearlogic.py +451 -449
- {nltk-3.2 → nltk-3.3}/nltk/sem/logic.py +1910 -1910
- {nltk-3.2 → nltk-3.3}/nltk/sem/relextract.py +476 -483
- {nltk-3.2 → nltk-3.3}/nltk/sem/skolemize.py +101 -101
- {nltk-3.2 → nltk-3.3}/nltk/sem/util.py +249 -249
- {nltk-3.2 → nltk-3.3}/nltk/sentiment/__init__.py +14 -14
- {nltk-3.2 → nltk-3.3}/nltk/sentiment/sentiment_analyzer.py +229 -229
- {nltk-3.2 → nltk-3.3}/nltk/sentiment/util.py +762 -752
- {nltk-3.2 → nltk-3.3}/nltk/sentiment/vader.py +443 -445
- {nltk-3.2 → nltk-3.3}/nltk/stem/__init__.py +31 -33
- {nltk-3.2 → nltk-3.3}/nltk/stem/api.py +28 -25
- nltk-3.3/nltk/stem/arlstem.py +369 -0
- {nltk-3.2 → nltk-3.3}/nltk/stem/isri.py +345 -345
- {nltk-3.2 → nltk-3.3}/nltk/stem/lancaster.py +337 -310
- nltk-3.3/nltk/stem/porter.py +710 -0
- {nltk-3.2 → nltk-3.3}/nltk/stem/regexp.py +61 -61
- {nltk-3.2 → nltk-3.3}/nltk/stem/rslp.py +142 -142
- {nltk-3.2 → nltk-3.3}/nltk/stem/snowball.py +4236 -3712
- {nltk-3.2 → nltk-3.3}/nltk/stem/util.py +22 -12
- {nltk-3.2 → nltk-3.3}/nltk/stem/wordnet.py +51 -51
- {nltk-3.2 → nltk-3.3}/nltk/tag/__init__.py +152 -127
- {nltk-3.2 → nltk-3.3}/nltk/tag/api.py +86 -81
- {nltk-3.2 → nltk-3.3}/nltk/tag/brill.py +424 -426
- {nltk-3.2 → nltk-3.3}/nltk/tag/brill_trainer.py +608 -608
- {nltk-3.2 → nltk-3.3}/nltk/tag/crf.py +204 -200
- {nltk-3.2 → nltk-3.3}/nltk/tag/hmm.py +1277 -1277
- {nltk-3.2 → nltk-3.3}/nltk/tag/hunpos.py +132 -132
- {nltk-3.2 → nltk-3.3}/nltk/tag/mapping.py +101 -101
- {nltk-3.2 → nltk-3.3}/nltk/tag/perceptron.py +329 -314
- {nltk-3.2 → nltk-3.3}/nltk/tag/senna.py +147 -145
- {nltk-3.2 → nltk-3.3}/nltk/tag/sequential.py +740 -736
- {nltk-3.2 → nltk-3.3}/nltk/tag/stanford.py +219 -196
- {nltk-3.2 → nltk-3.3}/nltk/tag/tnt.py +595 -604
- {nltk-3.2 → nltk-3.3}/nltk/tag/util.py +72 -72
- {nltk-3.2 → nltk-3.3}/nltk/tbl/__init__.py +28 -28
- {nltk-3.2 → nltk-3.3}/nltk/tbl/api.py +1 -1
- {nltk-3.2 → nltk-3.3}/nltk/tbl/demo.py +366 -366
- {nltk-3.2 → nltk-3.3}/nltk/tbl/erroranalysis.py +39 -39
- {nltk-3.2 → nltk-3.3}/nltk/tbl/feature.py +259 -261
- {nltk-3.2 → nltk-3.3}/nltk/tbl/rule.py +321 -321
- {nltk-3.2 → nltk-3.3}/nltk/tbl/template.py +303 -308
- {nltk-3.2 → nltk-3.3}/nltk/test/__init__.py +18 -18
- {nltk-3.2 → nltk-3.3}/nltk/test/all.py +24 -23
- {nltk-3.2 → nltk-3.3}/nltk/test/bleu.doctest +14 -14
- {nltk-3.2 → nltk-3.3}/nltk/test/bnc.doctest +60 -60
- {nltk-3.2 → nltk-3.3}/nltk/test/ccg.doctest +376 -395
- nltk-3.3/nltk/test/ccg_semantics.doctest +553 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/chat80.doctest +234 -234
- {nltk-3.2 → nltk-3.3}/nltk/test/childes.doctest +184 -184
- {nltk-3.2 → nltk-3.3}/nltk/test/childes_fixt.py +14 -13
- {nltk-3.2 → nltk-3.3}/nltk/test/chunk.doctest +373 -373
- {nltk-3.2 → nltk-3.3}/nltk/test/classify.doctest +185 -185
- {nltk-3.2 → nltk-3.3}/nltk/test/classify_fixt.py +11 -10
- nltk-3.3/nltk/test/collections.doctest +20 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/collocations.doctest +276 -276
- {nltk-3.2 → nltk-3.3}/nltk/test/compat.doctest +134 -138
- {nltk-3.2 → nltk-3.3}/nltk/test/compat_fixt.py +9 -8
- nltk-3.3/nltk/test/concordance.doctest +55 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/corpus.doctest +2200 -2212
- {nltk-3.2 → nltk-3.3}/nltk/test/corpus_fixt.py +3 -3
- {nltk-3.2 → nltk-3.3}/nltk/test/crubadan.doctest +65 -65
- {nltk-3.2 → nltk-3.3}/nltk/test/data.doctest +379 -379
- {nltk-3.2 → nltk-3.3}/nltk/test/dependency.doctest +241 -241
- {nltk-3.2 → nltk-3.3}/nltk/test/discourse.doctest +546 -546
- {nltk-3.2 → nltk-3.3}/nltk/test/discourse_fixt.py +14 -13
- {nltk-3.2 → nltk-3.3}/nltk/test/doctest_nose_plugin.py +159 -156
- {nltk-3.2 → nltk-3.3}/nltk/test/drt.doctest +517 -517
- {nltk-3.2 → nltk-3.3}/nltk/test/featgram.doctest +607 -607
- {nltk-3.2 → nltk-3.3}/nltk/test/featstruct.doctest +1229 -1230
- {nltk-3.2 → nltk-3.3}/nltk/test/framenet.doctest +288 -239
- {nltk-3.2 → nltk-3.3}/nltk/test/generate.doctest +67 -67
- {nltk-3.2 → nltk-3.3}/nltk/test/gensim.doctest +140 -141
- {nltk-3.2 → nltk-3.3}/nltk/test/gensim_fixt.py +10 -9
- {nltk-3.2 → nltk-3.3}/nltk/test/gluesemantics.doctest +384 -386
- {nltk-3.2 → nltk-3.3}/nltk/test/gluesemantics_malt.doctest +68 -68
- {nltk-3.2 → nltk-3.3}/nltk/test/gluesemantics_malt_fixt.py +12 -11
- {nltk-3.2 → nltk-3.3}/nltk/test/grammar.doctest +48 -48
- {nltk-3.2 → nltk-3.3}/nltk/test/grammartestsuites.doctest +109 -109
- {nltk-3.2 → nltk-3.3}/nltk/test/index.doctest +100 -100
- {nltk-3.2 → nltk-3.3}/nltk/test/inference.doctest +534 -534
- {nltk-3.2 → nltk-3.3}/nltk/test/inference_fixt.py +12 -11
- {nltk-3.2 → nltk-3.3}/nltk/test/internals.doctest +140 -140
- {nltk-3.2 → nltk-3.3}/nltk/test/japanese.doctest +48 -48
- {nltk-3.2 → nltk-3.3}/nltk/test/logic.doctest +1098 -1098
- {nltk-3.2 → nltk-3.3}/nltk/test/metrics.doctest +270 -270
- {nltk-3.2 → nltk-3.3}/nltk/test/misc.doctest +118 -118
- {nltk-3.2 → nltk-3.3}/nltk/test/nonmonotonic.doctest +286 -286
- {nltk-3.2 → nltk-3.3}/nltk/test/nonmonotonic_fixt.py +12 -11
- {nltk-3.2 → nltk-3.3}/nltk/test/paice.doctest +35 -35
- {nltk-3.2 → nltk-3.3}/nltk/test/parse.doctest +884 -884
- {nltk-3.2 → nltk-3.3}/nltk/test/portuguese_en.doctest +565 -565
- {nltk-3.2 → nltk-3.3}/nltk/test/portuguese_en_fixt.py +16 -16
- {nltk-3.2 → nltk-3.3}/nltk/test/probability.doctest +304 -304
- {nltk-3.2 → nltk-3.3}/nltk/test/probability_fixt.py +13 -12
- {nltk-3.2 → nltk-3.3}/nltk/test/propbank.doctest +176 -176
- {nltk-3.2 → nltk-3.3}/nltk/test/relextract.doctest +263 -263
- {nltk-3.2 → nltk-3.3}/nltk/test/resolution.doctest +221 -221
- {nltk-3.2 → nltk-3.3}/nltk/test/runtests.py +86 -64
- {nltk-3.2 → nltk-3.3}/nltk/test/segmentation_fixt.py +10 -10
- {nltk-3.2 → nltk-3.3}/nltk/test/semantics.doctest +665 -665
- {nltk-3.2 → nltk-3.3}/nltk/test/semantics_fixt.py +7 -7
- {nltk-3.2 → nltk-3.3}/nltk/test/sentiment.doctest +233 -233
- {nltk-3.2 → nltk-3.3}/nltk/test/sentiwordnet.doctest +41 -41
- {nltk-3.2 → nltk-3.3}/nltk/test/simple.doctest +85 -85
- {nltk-3.2 → nltk-3.3}/nltk/test/stem.doctest +78 -78
- {nltk-3.2 → nltk-3.3}/nltk/test/tag.doctest +33 -33
- {nltk-3.2 → nltk-3.3}/nltk/test/tokenize.doctest +273 -166
- {nltk-3.2 → nltk-3.3}/nltk/test/toolbox.doctest +307 -307
- {nltk-3.2 → nltk-3.3}/nltk/test/translate.doctest +242 -242
- {nltk-3.2 → nltk-3.3}/nltk/test/translate_fixt.py +3 -3
- {nltk-3.2 → nltk-3.3}/nltk/test/tree.doctest +1101 -1101
- {nltk-3.2 → nltk-3.3}/nltk/test/treeprettyprinter.doctest +127 -127
- {nltk-3.2 → nltk-3.3}/nltk/test/treetransforms.doctest +156 -156
- nltk-3.3/nltk/test/unit/test_2x_compat.py +63 -0
- nltk-3.3/nltk/test/unit/test_aline.py +52 -0
- nltk-3.3/nltk/test/unit/test_chunk.py +56 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_classify.py +55 -53
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_collocations.py +88 -79
- nltk-3.3/nltk/test/unit/test_concordance.py +98 -0
- nltk-3.3/nltk/test/unit/test_corenlp.py +1000 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_corpora.py +200 -183
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_corpus_views.py +45 -45
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_hmm.py +88 -88
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_json2csv_corpus.py +188 -187
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_naivebayes.py +26 -26
- nltk-3.3/nltk/test/unit/test_rte_classify.py +86 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_seekable_unicode_stream_reader.py +124 -124
- nltk-3.3/nltk/test/unit/test_senna.py +74 -0
- nltk-3.3/nltk/test/unit/test_stem.py +121 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_tag.py +20 -20
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_tgrep.py +628 -626
- nltk-3.3/nltk/test/unit/test_tokenize.py +168 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_twitter_auth.py +185 -185
- nltk-3.3/nltk/test/unit/test_wordnet.py +134 -0
- nltk-3.3/nltk/test/unit/translate/test_bleu.py +244 -0
- nltk-3.3/nltk/test/unit/translate/test_gdfa.py +57 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm1.py +75 -75
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm2.py +87 -87
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm3.py +105 -105
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm4.py +126 -126
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm5.py +166 -166
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm_model.py +270 -270
- nltk-3.3/nltk/test/unit/translate/test_nist.py +37 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_stack_decoder.py +300 -300
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/utils.py +41 -41
- {nltk-3.2 → nltk-3.3}/nltk/test/util.doctest +48 -48
- {nltk-3.2 → nltk-3.3}/nltk/test/wordnet.doctest +595 -581
- {nltk-3.2 → nltk-3.3}/nltk/test/wordnet_fixt.py +7 -6
- {nltk-3.2 → nltk-3.3}/nltk/test/wordnet_lch.doctest +53 -53
- {nltk-3.2 → nltk-3.3}/nltk/test/wsd.doctest +68 -68
- {nltk-3.2 → nltk-3.3}/nltk/text.py +663 -610
- {nltk-3.2 → nltk-3.3}/nltk/tgrep.py +938 -937
- {nltk-3.2 → nltk-3.3}/nltk/tokenize/__init__.py +130 -107
- {nltk-3.2 → nltk-3.3}/nltk/tokenize/api.py +77 -78
- {nltk-3.2 → nltk-3.3}/nltk/tokenize/casual.py +343 -342
- {nltk-3.2 → nltk-3.3}/nltk/tokenize/mwe.py +120 -120
- nltk-3.3/nltk/tokenize/nist.py +173 -0
- {nltk-3.2 → nltk-3.3}/nltk/tokenize/punkt.py +1620 -1601
- {nltk-3.2 → nltk-3.3}/nltk/tokenize/regexp.py +208 -202
- nltk-3.3/nltk/tokenize/repp.py +151 -0
- {nltk-3.2 → nltk-3.3}/nltk/tokenize/sexpr.py +140 -142
- {nltk-3.2 → nltk-3.3}/nltk/tokenize/simple.py +138 -136
- {nltk-3.2 → nltk-3.3}/nltk/tokenize/stanford.py +117 -113
- nltk-3.3/nltk/tokenize/stanford_segmenter.py +246 -0
- {nltk-3.2 → nltk-3.3}/nltk/tokenize/texttiling.py +458 -460
- nltk-3.3/nltk/tokenize/toktok.py +155 -0
- nltk-3.3/nltk/tokenize/treebank.py +363 -0
- nltk-3.3/nltk/tokenize/util.py +261 -0
- {nltk-3.2 → nltk-3.3}/nltk/toolbox.py +499 -497
- {nltk-3.2 → nltk-3.3}/nltk/translate/__init__.py +24 -24
- {nltk-3.2 → nltk-3.3}/nltk/translate/api.py +331 -321
- {nltk-3.2 → nltk-3.3}/nltk/translate/bleu_score.py +615 -556
- nltk-3.3/nltk/translate/chrf_score.py +137 -0
- {nltk-3.2 → nltk-3.3}/nltk/translate/gale_church.py +248 -235
- {nltk-3.2 → nltk-3.3}/nltk/translate/gdfa.py +135 -131
- nltk-3.3/nltk/translate/gleu_score.py +193 -0
- {nltk-3.2 → nltk-3.3}/nltk/translate/ibm1.py +250 -241
- {nltk-3.2 → nltk-3.3}/nltk/translate/ibm2.py +308 -308
- {nltk-3.2 → nltk-3.3}/nltk/translate/ibm3.py +337 -337
- {nltk-3.2 → nltk-3.3}/nltk/translate/ibm4.py +467 -467
- {nltk-3.2 → nltk-3.3}/nltk/translate/ibm5.py +639 -639
- {nltk-3.2 → nltk-3.3}/nltk/translate/ibm_model.py +536 -533
- {nltk-3.2 → nltk-3.3}/nltk/translate/metrics.py +40 -40
- nltk-3.3/nltk/translate/nist_score.py +178 -0
- {nltk-3.2 → nltk-3.3}/nltk/translate/phrase_based.py +175 -175
- {nltk-3.2 → nltk-3.3}/nltk/translate/ribes_score.py +325 -325
- {nltk-3.2 → nltk-3.3}/nltk/translate/stack_decoder.py +499 -495
- {nltk-3.2 → nltk-3.3}/nltk/tree.py +1605 -1603
- {nltk-3.2 → nltk-3.3}/nltk/treeprettyprinter.py +564 -564
- {nltk-3.2 → nltk-3.3}/nltk/treetransforms.py +309 -309
- {nltk-3.2 → nltk-3.3}/nltk/twitter/__init__.py +28 -28
- {nltk-3.2 → nltk-3.3}/nltk/twitter/api.py +141 -136
- {nltk-3.2 → nltk-3.3}/nltk/twitter/common.py +264 -258
- {nltk-3.2 → nltk-3.3}/nltk/twitter/twitter_demo.py +288 -288
- {nltk-3.2 → nltk-3.3}/nltk/twitter/twitterclient.py +530 -530
- {nltk-3.2 → nltk-3.3}/nltk/twitter/util.py +147 -146
- nltk-3.3/nltk/util.py +751 -0
- {nltk-3.2 → nltk-3.3}/nltk/wsd.py +53 -53
- {nltk-3.2 → nltk-3.3/nltk.egg-info}/PKG-INFO +13 -5
- {nltk-3.2 → nltk-3.3}/nltk.egg-info/SOURCES.txt +26 -3
- nltk-3.3/nltk.egg-info/requires.txt +31 -0
- {nltk-3.2 → nltk-3.3}/setup.cfg +0 -1
- {nltk-3.2 → nltk-3.3}/setup.py +102 -75
- nltk-3.2/nltk/VERSION +0 -1
- nltk-3.2/nltk/compat.py +0 -680
- nltk-3.2/nltk/corpus/reader/pl196x.py +0 -278
- nltk-3.2/nltk/corpus/reader/verbnet.py +0 -408
- nltk-3.2/nltk/corpus/reader/wordlist.py +0 -37
- nltk-3.2/nltk/sentiment/vader_lexicon.txt +0 -7517
- nltk-3.2/nltk/six.py +0 -868
- nltk-3.2/nltk/stem/porter.py +0 -692
- nltk-3.2/nltk/test/unit/test_2x_compat.py +0 -26
- nltk-3.2/nltk/test/unit/test_stem.py +0 -36
- nltk-3.2/nltk/test/unit/test_tokenize.py +0 -23
- nltk-3.2/nltk/test/unit/translate/test_bleu.py +0 -84
- nltk-3.2/nltk/tokenize/treebank.py +0 -124
- nltk-3.2/nltk/tokenize/util.py +0 -90
- nltk-3.2/nltk/util.py +0 -1364
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/__init__.py +0 -0
- {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/__init__.py +0 -0
- {nltk-3.2 → nltk-3.3}/nltk.egg-info/dependency_links.txt +0 -0
- {nltk-3.2 → nltk-3.3}/nltk.egg-info/not-zip-safe +0 -0
- {nltk-3.2 → nltk-3.3}/nltk.egg-info/top_level.txt +0 -0
|
@@ -1,894 +1,892 @@
|
|
|
1
|
-
# Natural Language Toolkit: Recursive Descent Parser Application
|
|
2
|
-
#
|
|
3
|
-
# Copyright (C) 2001-
|
|
4
|
-
# Author: Edward Loper <edloper@gmail.com>
|
|
5
|
-
# URL: <http://nltk.org/>
|
|
6
|
-
# For license information, see LICENSE.TXT
|
|
7
|
-
|
|
8
|
-
"""
|
|
9
|
-
A graphical tool for exploring the recursive descent parser.
|
|
10
|
-
|
|
11
|
-
The recursive descent parser maintains a tree, which records the
|
|
12
|
-
structure of the portion of the text that has been parsed. It uses
|
|
13
|
-
CFG productions to expand the fringe of the tree, and matches its
|
|
14
|
-
leaves against the text. Initially, the tree contains the start
|
|
15
|
-
symbol ("S"). It is shown in the main canvas, to the right of the
|
|
16
|
-
list of available expansions.
|
|
17
|
-
|
|
18
|
-
The parser builds up a tree structure for the text using three
|
|
19
|
-
operations:
|
|
20
|
-
|
|
21
|
-
- "expand" uses a CFG production to add children to a node on the
|
|
22
|
-
fringe of the tree.
|
|
23
|
-
- "match" compares a leaf in the tree to a text token.
|
|
24
|
-
- "backtrack" returns the tree to its state before the most recent
|
|
25
|
-
expand or match operation.
|
|
26
|
-
|
|
27
|
-
The parser maintains a list of tree locations called a "frontier" to
|
|
28
|
-
remember which nodes have not yet been expanded and which leaves have
|
|
29
|
-
not yet been matched against the text. The leftmost frontier node is
|
|
30
|
-
shown in green, and the other frontier nodes are shown in blue. The
|
|
31
|
-
parser always performs expand and match operations on the leftmost
|
|
32
|
-
element of the frontier.
|
|
33
|
-
|
|
34
|
-
You can control the parser's operation by using the "expand," "match,"
|
|
35
|
-
and "backtrack" buttons; or you can use the "step" button to let the
|
|
36
|
-
parser automatically decide which operation to apply. The parser uses
|
|
37
|
-
the following rules to decide which operation to apply:
|
|
38
|
-
|
|
39
|
-
- If the leftmost frontier element is a token, try matching it.
|
|
40
|
-
- If the leftmost frontier element is a node, try expanding it with
|
|
41
|
-
the first untried expansion.
|
|
42
|
-
- Otherwise, backtrack.
|
|
43
|
-
|
|
44
|
-
The "expand" button applies the untried expansion whose CFG production
|
|
45
|
-
is listed earliest in the grammar. To manually choose which expansion
|
|
46
|
-
to apply, click on a CFG production from the list of available
|
|
47
|
-
expansions, on the left side of the main window.
|
|
48
|
-
|
|
49
|
-
The "autostep" button will let the parser continue applying
|
|
50
|
-
applications to the tree until it reaches a complete parse. You can
|
|
51
|
-
cancel an autostep in progress at any time by clicking on the
|
|
52
|
-
"autostep" button again.
|
|
53
|
-
|
|
54
|
-
Keyboard Shortcuts::
|
|
55
|
-
[Space]\t Perform the next expand, match, or backtrack operation
|
|
56
|
-
[a]\t Step through operations until the next complete parse
|
|
57
|
-
[e]\t Perform an expand operation
|
|
58
|
-
[m]\t Perform a match operation
|
|
59
|
-
[b]\t Perform a backtrack operation
|
|
60
|
-
[Delete]\t Reset the parser
|
|
61
|
-
[g]\t Show/hide available expansions list
|
|
62
|
-
[h]\t Help
|
|
63
|
-
[Ctrl-p]\t Print
|
|
64
|
-
[q]\t Quit
|
|
65
|
-
"""
|
|
66
|
-
from __future__ import division
|
|
67
|
-
|
|
68
|
-
import
|
|
69
|
-
from tkinter import (Listbox, IntVar, Button,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
from nltk.tree import Tree
|
|
73
|
-
from nltk.util import in_idle
|
|
74
|
-
from nltk.parse import SteppingRecursiveDescentParser
|
|
75
|
-
from nltk.draw.util import TextWidget, ShowText, CanvasFrame, EntryDialog
|
|
76
|
-
from nltk.draw import CFGEditor, TreeSegmentWidget, tree_to_treesegment
|
|
77
|
-
|
|
78
|
-
class RecursiveDescentApp(object):
|
|
79
|
-
"""
|
|
80
|
-
A graphical tool for exploring the recursive descent parser. The tool
|
|
81
|
-
displays the parser's tree and the remaining text, and allows the
|
|
82
|
-
user to control the parser's operation. In particular, the user
|
|
83
|
-
can expand subtrees on the frontier, match tokens on the frontier
|
|
84
|
-
against the text, and backtrack. A "step" button simply steps
|
|
85
|
-
through the parsing process, performing the operations that
|
|
86
|
-
``RecursiveDescentParser`` would use.
|
|
87
|
-
"""
|
|
88
|
-
def __init__(self, grammar, sent, trace=0):
|
|
89
|
-
self._sent = sent
|
|
90
|
-
self._parser = SteppingRecursiveDescentParser(grammar, trace)
|
|
91
|
-
|
|
92
|
-
# Set up the main window.
|
|
93
|
-
self._top = Tk()
|
|
94
|
-
self._top.title('Recursive Descent Parser Application')
|
|
95
|
-
|
|
96
|
-
# Set up key bindings.
|
|
97
|
-
self._init_bindings()
|
|
98
|
-
|
|
99
|
-
# Initialize the fonts.
|
|
100
|
-
self._init_fonts(self._top)
|
|
101
|
-
|
|
102
|
-
# Animations. animating_lock is a lock to prevent the demo
|
|
103
|
-
# from performing new operations while it's animating.
|
|
104
|
-
self._animation_frames = IntVar(self._top)
|
|
105
|
-
self._animation_frames.set(5)
|
|
106
|
-
self._animating_lock = 0
|
|
107
|
-
self._autostep = 0
|
|
108
|
-
|
|
109
|
-
# The user can hide the grammar.
|
|
110
|
-
self._show_grammar = IntVar(self._top)
|
|
111
|
-
self._show_grammar.set(1)
|
|
112
|
-
|
|
113
|
-
# Create the basic frames.
|
|
114
|
-
self._init_menubar(self._top)
|
|
115
|
-
self._init_buttons(self._top)
|
|
116
|
-
self._init_feedback(self._top)
|
|
117
|
-
self._init_grammar(self._top)
|
|
118
|
-
self._init_canvas(self._top)
|
|
119
|
-
|
|
120
|
-
# Initialize the parser.
|
|
121
|
-
self._parser.initialize(self._sent)
|
|
122
|
-
|
|
123
|
-
# Resize callback
|
|
124
|
-
self._canvas.bind('<Configure>', self._configure)
|
|
125
|
-
|
|
126
|
-
#########################################
|
|
127
|
-
## Initialization Helpers
|
|
128
|
-
#########################################
|
|
129
|
-
|
|
130
|
-
def _init_fonts(self, root):
|
|
131
|
-
# See: <http://www.astro.washington.edu/owen/ROTKFolklore.html>
|
|
132
|
-
self._sysfont =
|
|
133
|
-
root.option_add("*Font", self._sysfont)
|
|
134
|
-
|
|
135
|
-
# TWhat's our font size (default=same as sysfont)
|
|
136
|
-
self._size = IntVar(root)
|
|
137
|
-
self._size.set(self._sysfont.cget('size'))
|
|
138
|
-
|
|
139
|
-
self._boldfont =
|
|
140
|
-
size=self._size.get())
|
|
141
|
-
self._font =
|
|
142
|
-
size=self._size.get())
|
|
143
|
-
if self._size.get() < 0: big = self._size.get()-2
|
|
144
|
-
else: big = self._size.get()+2
|
|
145
|
-
self._bigfont =
|
|
146
|
-
size=big)
|
|
147
|
-
|
|
148
|
-
def _init_grammar(self, parent):
|
|
149
|
-
# Grammar view.
|
|
150
|
-
self._prodframe = listframe = Frame(parent)
|
|
151
|
-
self._prodframe.pack(fill='both', side='left', padx=2)
|
|
152
|
-
self._prodlist_label = Label(self._prodframe, font=self._boldfont,
|
|
153
|
-
text='Available Expansions')
|
|
154
|
-
self._prodlist_label.pack()
|
|
155
|
-
self._prodlist = Listbox(self._prodframe, selectmode='single',
|
|
156
|
-
relief='groove', background='white',
|
|
157
|
-
foreground='#909090', font=self._font,
|
|
158
|
-
selectforeground='#004040',
|
|
159
|
-
selectbackground='#c0f0c0')
|
|
160
|
-
|
|
161
|
-
self._prodlist.pack(side='right', fill='both', expand=1)
|
|
162
|
-
|
|
163
|
-
self._productions = list(self._parser.grammar().productions())
|
|
164
|
-
for production in self._productions:
|
|
165
|
-
self._prodlist.insert('end', (' %s' % production))
|
|
166
|
-
self._prodlist.config(height=min(len(self._productions), 25))
|
|
167
|
-
|
|
168
|
-
# Add a scrollbar if there are more than 25 productions.
|
|
169
|
-
if len(self._productions) > 25:
|
|
170
|
-
listscroll = Scrollbar(self._prodframe,
|
|
171
|
-
orient='vertical')
|
|
172
|
-
self._prodlist.config(yscrollcommand = listscroll.set)
|
|
173
|
-
listscroll.config(command=self._prodlist.yview)
|
|
174
|
-
listscroll.pack(side='left', fill='y')
|
|
175
|
-
|
|
176
|
-
# If they select a production, apply it.
|
|
177
|
-
self._prodlist.bind('<<ListboxSelect>>', self._prodlist_select)
|
|
178
|
-
|
|
179
|
-
def _init_bindings(self):
|
|
180
|
-
# Key bindings are a good thing.
|
|
181
|
-
self._top.bind('<Control-q>', self.destroy)
|
|
182
|
-
self._top.bind('<Control-x>', self.destroy)
|
|
183
|
-
self._top.bind('<Escape>', self.destroy)
|
|
184
|
-
self._top.bind('e', self.expand)
|
|
185
|
-
#self._top.bind('<Alt-e>', self.expand)
|
|
186
|
-
#self._top.bind('<Control-e>', self.expand)
|
|
187
|
-
self._top.bind('m', self.match)
|
|
188
|
-
self._top.bind('<Alt-m>', self.match)
|
|
189
|
-
self._top.bind('<Control-m>', self.match)
|
|
190
|
-
self._top.bind('b', self.backtrack)
|
|
191
|
-
self._top.bind('<Alt-b>', self.backtrack)
|
|
192
|
-
self._top.bind('<Control-b>', self.backtrack)
|
|
193
|
-
self._top.bind('<Control-z>', self.backtrack)
|
|
194
|
-
self._top.bind('<BackSpace>', self.backtrack)
|
|
195
|
-
self._top.bind('a', self.autostep)
|
|
196
|
-
#self._top.bind('<Control-a>', self.autostep)
|
|
197
|
-
self._top.bind('<Control-space>', self.autostep)
|
|
198
|
-
self._top.bind('<Control-c>', self.cancel_autostep)
|
|
199
|
-
self._top.bind('<space>', self.step)
|
|
200
|
-
self._top.bind('<Delete>', self.reset)
|
|
201
|
-
self._top.bind('<Control-p>', self.postscript)
|
|
202
|
-
#self._top.bind('<h>', self.help)
|
|
203
|
-
#self._top.bind('<Alt-h>', self.help)
|
|
204
|
-
self._top.bind('<Control-h>', self.help)
|
|
205
|
-
self._top.bind('<F1>', self.help)
|
|
206
|
-
#self._top.bind('<g>', self.toggle_grammar)
|
|
207
|
-
#self._top.bind('<Alt-g>', self.toggle_grammar)
|
|
208
|
-
#self._top.bind('<Control-g>', self.toggle_grammar)
|
|
209
|
-
self._top.bind('<Control-g>', self.edit_grammar)
|
|
210
|
-
self._top.bind('<Control-t>', self.edit_sentence)
|
|
211
|
-
|
|
212
|
-
def _init_buttons(self, parent):
|
|
213
|
-
# Set up the frames.
|
|
214
|
-
self._buttonframe = buttonframe = Frame(parent)
|
|
215
|
-
buttonframe.pack(fill='none', side='bottom', padx=3, pady=2)
|
|
216
|
-
Button(buttonframe, text='Step',
|
|
217
|
-
background='#90c0d0', foreground='black',
|
|
218
|
-
command=self.step,).pack(side='left')
|
|
219
|
-
Button(buttonframe, text='Autostep',
|
|
220
|
-
background='#90c0d0', foreground='black',
|
|
221
|
-
command=self.autostep,).pack(side='left')
|
|
222
|
-
Button(buttonframe, text='Expand', underline=0,
|
|
223
|
-
background='#90f090', foreground='black',
|
|
224
|
-
command=self.expand).pack(side='left')
|
|
225
|
-
Button(buttonframe, text='Match', underline=0,
|
|
226
|
-
background='#90f090', foreground='black',
|
|
227
|
-
command=self.match).pack(side='left')
|
|
228
|
-
Button(buttonframe, text='Backtrack', underline=0,
|
|
229
|
-
background='#f0a0a0', foreground='black',
|
|
230
|
-
command=self.backtrack).pack(side='left')
|
|
231
|
-
# Replace autostep...
|
|
232
|
-
# self._autostep_button = Button(buttonframe, text='Autostep',
|
|
233
|
-
# underline=0, command=self.autostep)
|
|
234
|
-
# self._autostep_button.pack(side='left')
|
|
235
|
-
|
|
236
|
-
def _configure(self, event):
|
|
237
|
-
self._autostep = 0
|
|
238
|
-
(x1, y1, x2, y2) = self._cframe.scrollregion()
|
|
239
|
-
y2 = event.height - 6
|
|
240
|
-
self._canvas['scrollregion'] = '%d %d %d %d' % (x1,y1,x2,y2)
|
|
241
|
-
self._redraw()
|
|
242
|
-
|
|
243
|
-
def _init_feedback(self, parent):
|
|
244
|
-
self._feedbackframe = feedbackframe = Frame(parent)
|
|
245
|
-
feedbackframe.pack(fill='x', side='bottom', padx=3, pady=3)
|
|
246
|
-
self._lastoper_label = Label(feedbackframe, text='Last Operation:',
|
|
247
|
-
font=self._font)
|
|
248
|
-
self._lastoper_label.pack(side='left')
|
|
249
|
-
lastoperframe = Frame(feedbackframe, relief='sunken', border=1)
|
|
250
|
-
lastoperframe.pack(fill='x', side='right', expand=1, padx=5)
|
|
251
|
-
self._lastoper1 = Label(lastoperframe, foreground='#007070',
|
|
252
|
-
background='#f0f0f0', font=self._font)
|
|
253
|
-
self._lastoper2 = Label(lastoperframe, anchor='w', width=30,
|
|
254
|
-
foreground='#004040', background='#f0f0f0',
|
|
255
|
-
font=self._font)
|
|
256
|
-
self._lastoper1.pack(side='left')
|
|
257
|
-
self._lastoper2.pack(side='left', fill='x', expand=1)
|
|
258
|
-
|
|
259
|
-
def _init_canvas(self, parent):
|
|
260
|
-
self._cframe = CanvasFrame(parent, background='white',
|
|
261
|
-
#width=525, height=250,
|
|
262
|
-
closeenough=10,
|
|
263
|
-
border=2, relief='sunken')
|
|
264
|
-
self._cframe.pack(expand=1, fill='both', side='top', pady=2)
|
|
265
|
-
canvas = self._canvas = self._cframe.canvas()
|
|
266
|
-
|
|
267
|
-
# Initially, there's no tree or text
|
|
268
|
-
self._tree = None
|
|
269
|
-
self._textwidgets = []
|
|
270
|
-
self._textline = None
|
|
271
|
-
|
|
272
|
-
def _init_menubar(self, parent):
|
|
273
|
-
menubar = Menu(parent)
|
|
274
|
-
|
|
275
|
-
filemenu = Menu(menubar, tearoff=0)
|
|
276
|
-
filemenu.add_command(label='Reset Parser', underline=0,
|
|
277
|
-
command=self.reset, accelerator='Del')
|
|
278
|
-
filemenu.add_command(label='Print to Postscript', underline=0,
|
|
279
|
-
command=self.postscript, accelerator='Ctrl-p')
|
|
280
|
-
filemenu.add_command(label='Exit', underline=1,
|
|
281
|
-
command=self.destroy, accelerator='Ctrl-x')
|
|
282
|
-
menubar.add_cascade(label='File', underline=0, menu=filemenu)
|
|
283
|
-
|
|
284
|
-
editmenu = Menu(menubar, tearoff=0)
|
|
285
|
-
editmenu.add_command(label='Edit Grammar', underline=5,
|
|
286
|
-
command=self.edit_grammar,
|
|
287
|
-
accelerator='Ctrl-g')
|
|
288
|
-
editmenu.add_command(label='Edit Text', underline=5,
|
|
289
|
-
command=self.edit_sentence,
|
|
290
|
-
accelerator='Ctrl-t')
|
|
291
|
-
menubar.add_cascade(label='Edit', underline=0, menu=editmenu)
|
|
292
|
-
|
|
293
|
-
rulemenu = Menu(menubar, tearoff=0)
|
|
294
|
-
rulemenu.add_command(label='Step', underline=1,
|
|
295
|
-
command=self.step, accelerator='Space')
|
|
296
|
-
rulemenu.add_separator()
|
|
297
|
-
rulemenu.add_command(label='Match', underline=0,
|
|
298
|
-
command=self.match, accelerator='Ctrl-m')
|
|
299
|
-
rulemenu.add_command(label='Expand', underline=0,
|
|
300
|
-
command=self.expand, accelerator='Ctrl-e')
|
|
301
|
-
rulemenu.add_separator()
|
|
302
|
-
rulemenu.add_command(label='Backtrack', underline=0,
|
|
303
|
-
command=self.backtrack, accelerator='Ctrl-b')
|
|
304
|
-
menubar.add_cascade(label='Apply', underline=0, menu=rulemenu)
|
|
305
|
-
|
|
306
|
-
viewmenu = Menu(menubar, tearoff=0)
|
|
307
|
-
viewmenu.add_checkbutton(label="Show Grammar", underline=0,
|
|
308
|
-
variable=self._show_grammar,
|
|
309
|
-
command=self._toggle_grammar)
|
|
310
|
-
viewmenu.add_separator()
|
|
311
|
-
viewmenu.add_radiobutton(label='Tiny', variable=self._size,
|
|
312
|
-
underline=0, value=10, command=self.resize)
|
|
313
|
-
viewmenu.add_radiobutton(label='Small', variable=self._size,
|
|
314
|
-
underline=0, value=12, command=self.resize)
|
|
315
|
-
viewmenu.add_radiobutton(label='Medium', variable=self._size,
|
|
316
|
-
underline=0, value=14, command=self.resize)
|
|
317
|
-
viewmenu.add_radiobutton(label='Large', variable=self._size,
|
|
318
|
-
underline=0, value=18, command=self.resize)
|
|
319
|
-
viewmenu.add_radiobutton(label='Huge', variable=self._size,
|
|
320
|
-
underline=0, value=24, command=self.resize)
|
|
321
|
-
menubar.add_cascade(label='View', underline=0, menu=viewmenu)
|
|
322
|
-
|
|
323
|
-
animatemenu = Menu(menubar, tearoff=0)
|
|
324
|
-
animatemenu.add_radiobutton(label="No Animation", underline=0,
|
|
325
|
-
variable=self._animation_frames,
|
|
326
|
-
value=0)
|
|
327
|
-
animatemenu.add_radiobutton(label="Slow Animation", underline=0,
|
|
328
|
-
variable=self._animation_frames,
|
|
329
|
-
value=10, accelerator='-')
|
|
330
|
-
animatemenu.add_radiobutton(label="Normal Animation", underline=0,
|
|
331
|
-
variable=self._animation_frames,
|
|
332
|
-
value=5, accelerator='=')
|
|
333
|
-
animatemenu.add_radiobutton(label="Fast Animation", underline=0,
|
|
334
|
-
variable=self._animation_frames,
|
|
335
|
-
value=2, accelerator='+')
|
|
336
|
-
menubar.add_cascade(label="Animate", underline=1, menu=animatemenu)
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
helpmenu = Menu(menubar, tearoff=0)
|
|
340
|
-
helpmenu.add_command(label='About', underline=0,
|
|
341
|
-
command=self.about)
|
|
342
|
-
helpmenu.add_command(label='Instructions', underline=0,
|
|
343
|
-
command=self.help, accelerator='F1')
|
|
344
|
-
menubar.add_cascade(label='Help', underline=0, menu=helpmenu)
|
|
345
|
-
|
|
346
|
-
parent.config(menu=menubar)
|
|
347
|
-
|
|
348
|
-
#########################################
|
|
349
|
-
## Helper
|
|
350
|
-
#########################################
|
|
351
|
-
|
|
352
|
-
def _get(self, widget, treeloc):
|
|
353
|
-
for i in treeloc: widget = widget.subtrees()[i]
|
|
354
|
-
if isinstance(widget, TreeSegmentWidget):
|
|
355
|
-
widget = widget.label()
|
|
356
|
-
return widget
|
|
357
|
-
|
|
358
|
-
#########################################
|
|
359
|
-
## Main draw procedure
|
|
360
|
-
#########################################
|
|
361
|
-
|
|
362
|
-
def _redraw(self):
|
|
363
|
-
canvas = self._canvas
|
|
364
|
-
|
|
365
|
-
# Delete the old tree, widgets, etc.
|
|
366
|
-
if self._tree is not None:
|
|
367
|
-
self._cframe.destroy_widget(self._tree)
|
|
368
|
-
for twidget in self._textwidgets:
|
|
369
|
-
self._cframe.destroy_widget(twidget)
|
|
370
|
-
if self._textline is not None:
|
|
371
|
-
self._canvas.delete(self._textline)
|
|
372
|
-
|
|
373
|
-
# Draw the tree.
|
|
374
|
-
helv = ('helvetica', -self._size.get())
|
|
375
|
-
bold = ('helvetica', -self._size.get(), 'bold')
|
|
376
|
-
attribs = {'tree_color': '#000000', 'tree_width': 2,
|
|
377
|
-
'node_font': bold, 'leaf_font': helv,}
|
|
378
|
-
tree = self._parser.tree()
|
|
379
|
-
self._tree = tree_to_treesegment(canvas, tree, **attribs)
|
|
380
|
-
self._cframe.add_widget(self._tree, 30, 5)
|
|
381
|
-
|
|
382
|
-
# Draw the text.
|
|
383
|
-
helv = ('helvetica', -self._size.get())
|
|
384
|
-
bottom = y = self._cframe.scrollregion()[3]
|
|
385
|
-
self._textwidgets = [TextWidget(canvas, word, font=self._font)
|
|
386
|
-
for word in self._sent]
|
|
387
|
-
for twidget in self._textwidgets:
|
|
388
|
-
self._cframe.add_widget(twidget, 0, 0)
|
|
389
|
-
twidget.move(0, bottom-twidget.bbox()[3]-5)
|
|
390
|
-
y = min(y, twidget.bbox()[1])
|
|
391
|
-
|
|
392
|
-
# Draw a line over the text, to separate it from the tree.
|
|
393
|
-
self._textline = canvas.create_line(-5000, y-5, 5000, y-5, dash='.')
|
|
394
|
-
|
|
395
|
-
# Highlight appropriate nodes.
|
|
396
|
-
self._highlight_nodes()
|
|
397
|
-
self._highlight_prodlist()
|
|
398
|
-
|
|
399
|
-
# Make sure the text lines up.
|
|
400
|
-
self._position_text()
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
def _redraw_quick(self):
|
|
404
|
-
# This should be more-or-less sufficient after an animation.
|
|
405
|
-
self._highlight_nodes()
|
|
406
|
-
self._highlight_prodlist()
|
|
407
|
-
self._position_text()
|
|
408
|
-
|
|
409
|
-
def _highlight_nodes(self):
|
|
410
|
-
# Highlight the list of nodes to be checked.
|
|
411
|
-
bold = ('helvetica', -self._size.get(), 'bold')
|
|
412
|
-
for treeloc in self._parser.frontier()[:1]:
|
|
413
|
-
self._get(self._tree, treeloc)['color'] = '#20a050'
|
|
414
|
-
self._get(self._tree, treeloc)['font'] = bold
|
|
415
|
-
for treeloc in self._parser.frontier()[1:]:
|
|
416
|
-
self._get(self._tree, treeloc)['color'] = '#008080'
|
|
417
|
-
|
|
418
|
-
def _highlight_prodlist(self):
|
|
419
|
-
# Highlight the productions that can be expanded.
|
|
420
|
-
# Boy, too bad tkinter doesn't implement Listbox.itemconfig;
|
|
421
|
-
# that would be pretty useful here.
|
|
422
|
-
self._prodlist.delete(0, 'end')
|
|
423
|
-
expandable = self._parser.expandable_productions()
|
|
424
|
-
untried = self._parser.untried_expandable_productions()
|
|
425
|
-
productions = self._productions
|
|
426
|
-
for index in range(len(productions)):
|
|
427
|
-
if productions[index] in expandable:
|
|
428
|
-
if productions[index] in untried:
|
|
429
|
-
self._prodlist.insert(index, ' %s' % productions[index])
|
|
430
|
-
else:
|
|
431
|
-
self._prodlist.insert(index, ' %s (TRIED)' %
|
|
432
|
-
productions[index])
|
|
433
|
-
self._prodlist.selection_set(index)
|
|
434
|
-
else:
|
|
435
|
-
self._prodlist.insert(index, ' %s' % productions[index])
|
|
436
|
-
|
|
437
|
-
def _position_text(self):
|
|
438
|
-
# Line up the text widgets that are matched against the tree
|
|
439
|
-
numwords = len(self._sent)
|
|
440
|
-
num_matched = numwords - len(self._parser.remaining_text())
|
|
441
|
-
leaves = self._tree_leaves()[:num_matched]
|
|
442
|
-
xmax = self._tree.bbox()[0]
|
|
443
|
-
for i in range(0, len(leaves)):
|
|
444
|
-
widget = self._textwidgets[i]
|
|
445
|
-
leaf = leaves[i]
|
|
446
|
-
widget['color'] = '#006040'
|
|
447
|
-
leaf['color'] = '#006040'
|
|
448
|
-
widget.move(leaf.bbox()[0] - widget.bbox()[0], 0)
|
|
449
|
-
xmax = widget.bbox()[2] + 10
|
|
450
|
-
|
|
451
|
-
# Line up the text widgets that are not matched against the tree.
|
|
452
|
-
for i in range(len(leaves), numwords):
|
|
453
|
-
widget = self._textwidgets[i]
|
|
454
|
-
widget['color'] = '#a0a0a0'
|
|
455
|
-
widget.move(xmax - widget.bbox()[0], 0)
|
|
456
|
-
xmax = widget.bbox()[2] + 10
|
|
457
|
-
|
|
458
|
-
# If we have a complete parse, make everything green :)
|
|
459
|
-
if self._parser.currently_complete():
|
|
460
|
-
for twidget in self._textwidgets:
|
|
461
|
-
twidget['color'] = '#00a000'
|
|
462
|
-
|
|
463
|
-
# Move the matched leaves down to the text.
|
|
464
|
-
for i in range(0, len(leaves)):
|
|
465
|
-
widget = self._textwidgets[i]
|
|
466
|
-
leaf = leaves[i]
|
|
467
|
-
dy = widget.bbox()[1] - leaf.bbox()[3] - 10.0
|
|
468
|
-
dy = max(dy, leaf.parent().label().bbox()[3] - leaf.bbox()[3] + 10)
|
|
469
|
-
leaf.move(0, dy)
|
|
470
|
-
|
|
471
|
-
def _tree_leaves(self, tree=None):
|
|
472
|
-
if tree is None: tree = self._tree
|
|
473
|
-
if isinstance(tree, TreeSegmentWidget):
|
|
474
|
-
leaves = []
|
|
475
|
-
for child in tree.subtrees(): leaves += self._tree_leaves(child)
|
|
476
|
-
return leaves
|
|
477
|
-
else:
|
|
478
|
-
return [tree]
|
|
479
|
-
|
|
480
|
-
#########################################
|
|
481
|
-
## Button Callbacks
|
|
482
|
-
#########################################
|
|
483
|
-
|
|
484
|
-
def destroy(self, *e):
|
|
485
|
-
self._autostep = 0
|
|
486
|
-
if self._top is None: return
|
|
487
|
-
self._top.destroy()
|
|
488
|
-
self._top = None
|
|
489
|
-
|
|
490
|
-
def reset(self, *e):
|
|
491
|
-
self._autostep = 0
|
|
492
|
-
self._parser.initialize(self._sent)
|
|
493
|
-
self._lastoper1['text'] = 'Reset Application'
|
|
494
|
-
self._lastoper2['text'] = ''
|
|
495
|
-
self._redraw()
|
|
496
|
-
|
|
497
|
-
def autostep(self, *e):
|
|
498
|
-
if self._animation_frames.get() == 0:
|
|
499
|
-
self._animation_frames.set(2)
|
|
500
|
-
if self._autostep:
|
|
501
|
-
self._autostep = 0
|
|
502
|
-
else:
|
|
503
|
-
self._autostep = 1
|
|
504
|
-
self._step()
|
|
505
|
-
|
|
506
|
-
def cancel_autostep(self, *e):
|
|
507
|
-
#self._autostep_button['text'] = 'Autostep'
|
|
508
|
-
self._autostep = 0
|
|
509
|
-
|
|
510
|
-
# Make sure to stop auto-stepping if we get any user input.
|
|
511
|
-
def step(self, *e): self._autostep = 0; self._step()
|
|
512
|
-
def match(self, *e): self._autostep = 0; self._match()
|
|
513
|
-
def expand(self, *e): self._autostep = 0; self._expand()
|
|
514
|
-
def backtrack(self, *e): self._autostep = 0; self._backtrack()
|
|
515
|
-
|
|
516
|
-
def _step(self):
|
|
517
|
-
if self._animating_lock: return
|
|
518
|
-
|
|
519
|
-
# Try expanding, matching, and backtracking (in that order)
|
|
520
|
-
if self._expand(): pass
|
|
521
|
-
elif self._parser.untried_match() and self._match(): pass
|
|
522
|
-
elif self._backtrack(): pass
|
|
523
|
-
else:
|
|
524
|
-
self._lastoper1['text'] = 'Finished'
|
|
525
|
-
self._lastoper2['text'] = ''
|
|
526
|
-
self._autostep = 0
|
|
527
|
-
|
|
528
|
-
# Check if we just completed a parse.
|
|
529
|
-
if self._parser.currently_complete():
|
|
530
|
-
self._autostep = 0
|
|
531
|
-
self._lastoper2['text'] += ' [COMPLETE PARSE]'
|
|
532
|
-
|
|
533
|
-
def _expand(self, *e):
|
|
534
|
-
if self._animating_lock: return
|
|
535
|
-
old_frontier = self._parser.frontier()
|
|
536
|
-
rv = self._parser.expand()
|
|
537
|
-
if rv is not None:
|
|
538
|
-
self._lastoper1['text'] = 'Expand:'
|
|
539
|
-
self._lastoper2['text'] = rv
|
|
540
|
-
self._prodlist.selection_clear(0, 'end')
|
|
541
|
-
index = self._productions.index(rv)
|
|
542
|
-
self._prodlist.selection_set(index)
|
|
543
|
-
self._animate_expand(old_frontier[0])
|
|
544
|
-
return True
|
|
545
|
-
else:
|
|
546
|
-
self._lastoper1['text'] = 'Expand:'
|
|
547
|
-
self._lastoper2['text'] = '(all expansions tried)'
|
|
548
|
-
return False
|
|
549
|
-
|
|
550
|
-
def _match(self, *e):
|
|
551
|
-
if self._animating_lock: return
|
|
552
|
-
old_frontier = self._parser.frontier()
|
|
553
|
-
rv = self._parser.match()
|
|
554
|
-
if rv is not None:
|
|
555
|
-
self._lastoper1['text'] = 'Match:'
|
|
556
|
-
self._lastoper2['text'] = rv
|
|
557
|
-
self._animate_match(old_frontier[0])
|
|
558
|
-
return True
|
|
559
|
-
else:
|
|
560
|
-
self._lastoper1['text'] = 'Match:'
|
|
561
|
-
self._lastoper2['text'] = '(failed)'
|
|
562
|
-
return False
|
|
563
|
-
|
|
564
|
-
def _backtrack(self, *e):
|
|
565
|
-
if self._animating_lock: return
|
|
566
|
-
if self._parser.backtrack():
|
|
567
|
-
elt = self._parser.tree()
|
|
568
|
-
for i in self._parser.frontier()[0]:
|
|
569
|
-
elt = elt[i]
|
|
570
|
-
self._lastoper1['text'] = 'Backtrack'
|
|
571
|
-
self._lastoper2['text'] = ''
|
|
572
|
-
if isinstance(elt, Tree):
|
|
573
|
-
self._animate_backtrack(self._parser.frontier()[0])
|
|
574
|
-
else:
|
|
575
|
-
self._animate_match_backtrack(self._parser.frontier()[0])
|
|
576
|
-
return True
|
|
577
|
-
else:
|
|
578
|
-
self._autostep = 0
|
|
579
|
-
self._lastoper1['text'] = 'Finished'
|
|
580
|
-
self._lastoper2['text'] = ''
|
|
581
|
-
return False
|
|
582
|
-
|
|
583
|
-
def about(self, *e):
|
|
584
|
-
ABOUT = ("NLTK Recursive Descent Parser Application\n"+
|
|
585
|
-
"Written by Edward Loper")
|
|
586
|
-
TITLE = 'About: Recursive Descent Parser Application'
|
|
587
|
-
try:
|
|
588
|
-
from
|
|
589
|
-
Message(message=ABOUT, title=TITLE).show()
|
|
590
|
-
except:
|
|
591
|
-
ShowText(self._top, TITLE, ABOUT)
|
|
592
|
-
|
|
593
|
-
def help(self, *e):
|
|
594
|
-
self._autostep = 0
|
|
595
|
-
# The default font's not very legible; try using 'fixed' instead.
|
|
596
|
-
try:
|
|
597
|
-
ShowText(self._top, 'Help: Recursive Descent Parser Application',
|
|
598
|
-
(__doc__ or '').strip(), width=75, font='fixed')
|
|
599
|
-
except:
|
|
600
|
-
ShowText(self._top, 'Help: Recursive Descent Parser Application',
|
|
601
|
-
(__doc__ or '').strip(), width=75)
|
|
602
|
-
|
|
603
|
-
def postscript(self, *e):
|
|
604
|
-
self._autostep = 0
|
|
605
|
-
self._cframe.print_to_file()
|
|
606
|
-
|
|
607
|
-
def mainloop(self, *args, **kwargs):
|
|
608
|
-
"""
|
|
609
|
-
Enter the Tkinter mainloop. This function must be called if
|
|
610
|
-
this demo is created from a non-interactive program (e.g.
|
|
611
|
-
from a secript); otherwise, the demo will close as soon as
|
|
612
|
-
the script completes.
|
|
613
|
-
"""
|
|
614
|
-
if in_idle(): return
|
|
615
|
-
self._top.mainloop(*args, **kwargs)
|
|
616
|
-
|
|
617
|
-
def resize(self, size=None):
|
|
618
|
-
if size is not None: self._size.set(size)
|
|
619
|
-
size = self._size.get()
|
|
620
|
-
self._font.configure(size=-(abs(size)))
|
|
621
|
-
self._boldfont.configure(size=-(abs(size)))
|
|
622
|
-
self._sysfont.configure(size=-(abs(size)))
|
|
623
|
-
self._bigfont.configure(size=-(abs(size+2)))
|
|
624
|
-
self._redraw()
|
|
625
|
-
|
|
626
|
-
#########################################
|
|
627
|
-
## Expand Production Selection
|
|
628
|
-
#########################################
|
|
629
|
-
|
|
630
|
-
def _toggle_grammar(self, *e):
|
|
631
|
-
if self._show_grammar.get():
|
|
632
|
-
self._prodframe.pack(fill='both', side='left', padx=2,
|
|
633
|
-
after=self._feedbackframe)
|
|
634
|
-
self._lastoper1['text'] = 'Show Grammar'
|
|
635
|
-
else:
|
|
636
|
-
self._prodframe.pack_forget()
|
|
637
|
-
self._lastoper1['text'] = 'Hide Grammar'
|
|
638
|
-
self._lastoper2['text'] = ''
|
|
639
|
-
|
|
640
|
-
# def toggle_grammar(self, *e):
|
|
641
|
-
# self._show_grammar = not self._show_grammar
|
|
642
|
-
# if self._show_grammar:
|
|
643
|
-
# self._prodframe.pack(fill='both', expand='y', side='left',
|
|
644
|
-
# after=self._feedbackframe)
|
|
645
|
-
# self._lastoper1['text'] = 'Show Grammar'
|
|
646
|
-
# else:
|
|
647
|
-
# self._prodframe.pack_forget()
|
|
648
|
-
# self._lastoper1['text'] = 'Hide Grammar'
|
|
649
|
-
# self._lastoper2['text'] = ''
|
|
650
|
-
|
|
651
|
-
def _prodlist_select(self, event):
|
|
652
|
-
selection = self._prodlist.curselection()
|
|
653
|
-
if len(selection) != 1: return
|
|
654
|
-
index = int(selection[0])
|
|
655
|
-
old_frontier = self._parser.frontier()
|
|
656
|
-
production = self._parser.expand(self._productions[index])
|
|
657
|
-
|
|
658
|
-
if production:
|
|
659
|
-
self._lastoper1['text'] = 'Expand:'
|
|
660
|
-
self._lastoper2['text'] = production
|
|
661
|
-
self._prodlist.selection_clear(0, 'end')
|
|
662
|
-
self._prodlist.selection_set(index)
|
|
663
|
-
self._animate_expand(old_frontier[0])
|
|
664
|
-
else:
|
|
665
|
-
# Reset the production selections.
|
|
666
|
-
self._prodlist.selection_clear(0, 'end')
|
|
667
|
-
for prod in self._parser.expandable_productions():
|
|
668
|
-
index = self._productions.index(prod)
|
|
669
|
-
self._prodlist.selection_set(index)
|
|
670
|
-
|
|
671
|
-
#########################################
|
|
672
|
-
## Animation
|
|
673
|
-
#########################################
|
|
674
|
-
|
|
675
|
-
def _animate_expand(self, treeloc):
|
|
676
|
-
oldwidget = self._get(self._tree, treeloc)
|
|
677
|
-
oldtree = oldwidget.parent()
|
|
678
|
-
top = not isinstance(oldtree.parent(), TreeSegmentWidget)
|
|
679
|
-
|
|
680
|
-
tree = self._parser.tree()
|
|
681
|
-
for i in treeloc:
|
|
682
|
-
tree = tree[i]
|
|
683
|
-
|
|
684
|
-
widget = tree_to_treesegment(self._canvas, tree,
|
|
685
|
-
node_font=self._boldfont,
|
|
686
|
-
leaf_color='white',
|
|
687
|
-
tree_width=2, tree_color='white',
|
|
688
|
-
node_color='white',
|
|
689
|
-
leaf_font=self._font)
|
|
690
|
-
widget.label()['color'] = '#20a050'
|
|
691
|
-
|
|
692
|
-
(oldx, oldy) = oldtree.label().bbox()[:2]
|
|
693
|
-
(newx, newy) = widget.label().bbox()[:2]
|
|
694
|
-
widget.move(oldx-newx, oldy-newy)
|
|
695
|
-
|
|
696
|
-
if top:
|
|
697
|
-
self._cframe.add_widget(widget, 0, 5)
|
|
698
|
-
widget.move(30-widget.label().bbox()[0], 0)
|
|
699
|
-
self._tree = widget
|
|
700
|
-
else:
|
|
701
|
-
oldtree.parent().replace_child(oldtree, widget)
|
|
702
|
-
|
|
703
|
-
# Move the children over so they don't overlap.
|
|
704
|
-
# Line the children up in a strange way.
|
|
705
|
-
if widget.subtrees():
|
|
706
|
-
dx = (oldx + widget.label().width()/2 -
|
|
707
|
-
widget.subtrees()[0].bbox()[0]/2 -
|
|
708
|
-
widget.subtrees()[0].bbox()[2]/2)
|
|
709
|
-
for subtree in widget.subtrees(): subtree.move(dx, 0)
|
|
710
|
-
|
|
711
|
-
self._makeroom(widget)
|
|
712
|
-
|
|
713
|
-
if top:
|
|
714
|
-
self._cframe.destroy_widget(oldtree)
|
|
715
|
-
else:
|
|
716
|
-
oldtree.destroy()
|
|
717
|
-
|
|
718
|
-
colors = ['gray%d' % (10*int(10*x/self._animation_frames.get()))
|
|
719
|
-
for x in range(self._animation_frames.get(),0,-1)]
|
|
720
|
-
|
|
721
|
-
# Move the text string down, if necessary.
|
|
722
|
-
dy = widget.bbox()[3] + 30 - self._canvas.coords(self._textline)[1]
|
|
723
|
-
if dy > 0:
|
|
724
|
-
for twidget in self._textwidgets: twidget.move(0, dy)
|
|
725
|
-
self._canvas.move(self._textline, 0, dy)
|
|
726
|
-
|
|
727
|
-
self._animate_expand_frame(widget, colors)
|
|
728
|
-
|
|
729
|
-
def _makeroom(self, treeseg):
|
|
730
|
-
"""
|
|
731
|
-
Make sure that no sibling tree bbox's overlap.
|
|
732
|
-
"""
|
|
733
|
-
parent = treeseg.parent()
|
|
734
|
-
if not isinstance(parent, TreeSegmentWidget): return
|
|
735
|
-
|
|
736
|
-
index = parent.subtrees().index(treeseg)
|
|
737
|
-
|
|
738
|
-
# Handle siblings to the right
|
|
739
|
-
rsiblings = parent.subtrees()[index+1:]
|
|
740
|
-
if rsiblings:
|
|
741
|
-
dx = treeseg.bbox()[2] - rsiblings[0].bbox()[0] + 10
|
|
742
|
-
for sibling in rsiblings: sibling.move(dx, 0)
|
|
743
|
-
|
|
744
|
-
# Handle siblings to the left
|
|
745
|
-
if index > 0:
|
|
746
|
-
lsibling = parent.subtrees()[index-1]
|
|
747
|
-
dx = max(0, lsibling.bbox()[2] - treeseg.bbox()[0] + 10)
|
|
748
|
-
treeseg.move(dx, 0)
|
|
749
|
-
|
|
750
|
-
# Keep working up the tree.
|
|
751
|
-
self._makeroom(parent)
|
|
752
|
-
|
|
753
|
-
def _animate_expand_frame(self, widget, colors):
|
|
754
|
-
if len(colors) > 0:
|
|
755
|
-
self._animating_lock = 1
|
|
756
|
-
widget['color'] = colors[0]
|
|
757
|
-
for subtree in widget.subtrees():
|
|
758
|
-
if isinstance(subtree, TreeSegmentWidget):
|
|
759
|
-
subtree.label()['color'] = colors[0]
|
|
760
|
-
else:
|
|
761
|
-
subtree['color'] = colors[0]
|
|
762
|
-
self._top.after(50, self._animate_expand_frame,
|
|
763
|
-
widget, colors[1:])
|
|
764
|
-
else:
|
|
765
|
-
widget['color'] = 'black'
|
|
766
|
-
for subtree in widget.subtrees():
|
|
767
|
-
if isinstance(subtree, TreeSegmentWidget):
|
|
768
|
-
subtree.label()['color'] = 'black'
|
|
769
|
-
else:
|
|
770
|
-
subtree['color'] = 'black'
|
|
771
|
-
self._redraw_quick()
|
|
772
|
-
widget.label()['color'] = 'black'
|
|
773
|
-
self._animating_lock = 0
|
|
774
|
-
if self._autostep: self._step()
|
|
775
|
-
|
|
776
|
-
def _animate_backtrack(self, treeloc):
|
|
777
|
-
# Flash red first, if we're animating.
|
|
778
|
-
if self._animation_frames.get() == 0: colors = []
|
|
779
|
-
else: colors = ['#a00000', '#000000', '#a00000']
|
|
780
|
-
colors += ['gray%d' % (10*int(10*x/(self._animation_frames.get())))
|
|
781
|
-
for x in range(1, self._animation_frames.get()+1)]
|
|
782
|
-
|
|
783
|
-
widgets = [self._get(self._tree, treeloc).parent()]
|
|
784
|
-
for subtree in widgets[0].subtrees():
|
|
785
|
-
if isinstance(subtree, TreeSegmentWidget):
|
|
786
|
-
widgets.append(subtree.label())
|
|
787
|
-
else:
|
|
788
|
-
widgets.append(subtree)
|
|
789
|
-
|
|
790
|
-
self._animate_backtrack_frame(widgets, colors)
|
|
791
|
-
|
|
792
|
-
def _animate_backtrack_frame(self, widgets, colors):
|
|
793
|
-
if len(colors) > 0:
|
|
794
|
-
self._animating_lock = 1
|
|
795
|
-
for widget in widgets: widget['color'] = colors[0]
|
|
796
|
-
self._top.after(50, self._animate_backtrack_frame,
|
|
797
|
-
widgets, colors[1:])
|
|
798
|
-
else:
|
|
799
|
-
for widget in widgets[0].subtrees():
|
|
800
|
-
widgets[0].remove_child(widget)
|
|
801
|
-
widget.destroy()
|
|
802
|
-
self._redraw_quick()
|
|
803
|
-
self._animating_lock = 0
|
|
804
|
-
if self._autostep: self._step()
|
|
805
|
-
|
|
806
|
-
def _animate_match_backtrack(self, treeloc):
|
|
807
|
-
widget = self._get(self._tree, treeloc)
|
|
808
|
-
node = widget.parent().label()
|
|
809
|
-
dy = ((node.bbox()[3] - widget.bbox()[1] + 14) /
|
|
810
|
-
max(1, self._animation_frames.get()))
|
|
811
|
-
self._animate_match_backtrack_frame(self._animation_frames.get(),
|
|
812
|
-
widget, dy)
|
|
813
|
-
|
|
814
|
-
def _animate_match(self, treeloc):
|
|
815
|
-
widget = self._get(self._tree, treeloc)
|
|
816
|
-
|
|
817
|
-
dy = ((self._textwidgets[0].bbox()[1] - widget.bbox()[3] - 10.0) /
|
|
818
|
-
max(1, self._animation_frames.get()))
|
|
819
|
-
self._animate_match_frame(self._animation_frames.get(), widget, dy)
|
|
820
|
-
|
|
821
|
-
def _animate_match_frame(self, frame, widget, dy):
|
|
822
|
-
if frame > 0:
|
|
823
|
-
self._animating_lock = 1
|
|
824
|
-
widget.move(0, dy)
|
|
825
|
-
self._top.after(10, self._animate_match_frame,
|
|
826
|
-
frame-1, widget, dy)
|
|
827
|
-
else:
|
|
828
|
-
widget['color'] = '#006040'
|
|
829
|
-
self._redraw_quick()
|
|
830
|
-
self._animating_lock = 0
|
|
831
|
-
if self._autostep: self._step()
|
|
832
|
-
|
|
833
|
-
def _animate_match_backtrack_frame(self, frame, widget, dy):
|
|
834
|
-
if frame > 0:
|
|
835
|
-
self._animating_lock = 1
|
|
836
|
-
widget.move(0, dy)
|
|
837
|
-
self._top.after(10, self._animate_match_backtrack_frame,
|
|
838
|
-
frame-1, widget, dy)
|
|
839
|
-
else:
|
|
840
|
-
widget.parent().remove_child(widget)
|
|
841
|
-
widget.destroy()
|
|
842
|
-
self._animating_lock = 0
|
|
843
|
-
if self._autostep: self._step()
|
|
844
|
-
|
|
845
|
-
def edit_grammar(self, *e):
|
|
846
|
-
CFGEditor(self._top, self._parser.grammar(), self.set_grammar)
|
|
847
|
-
|
|
848
|
-
def set_grammar(self, grammar):
|
|
849
|
-
self._parser.set_grammar(grammar)
|
|
850
|
-
self._productions = list(grammar.productions())
|
|
851
|
-
self._prodlist.delete(0, 'end')
|
|
852
|
-
for production in self._productions:
|
|
853
|
-
self._prodlist.insert('end', (' %s' % production))
|
|
854
|
-
|
|
855
|
-
def edit_sentence(self, *e):
|
|
856
|
-
sentence = " ".join(self._sent)
|
|
857
|
-
title = 'Edit Text'
|
|
858
|
-
instr = 'Enter a new sentence to parse.'
|
|
859
|
-
EntryDialog(self._top, sentence, instr, self.set_sentence, title)
|
|
860
|
-
|
|
861
|
-
def set_sentence(self, sentence):
|
|
862
|
-
self._sent = sentence.split() #[XX] use tagged?
|
|
863
|
-
self.reset()
|
|
864
|
-
|
|
865
|
-
def app():
|
|
866
|
-
"""
|
|
867
|
-
Create a recursive descent parser demo, using a simple grammar and
|
|
868
|
-
text.
|
|
869
|
-
"""
|
|
870
|
-
from nltk.grammar import CFG
|
|
871
|
-
grammar = CFG.fromstring("""
|
|
872
|
-
# Grammatical productions.
|
|
873
|
-
S -> NP VP
|
|
874
|
-
NP -> Det N PP | Det N
|
|
875
|
-
VP -> V NP PP | V NP | V
|
|
876
|
-
PP -> P NP
|
|
877
|
-
# Lexical productions.
|
|
878
|
-
NP -> 'I'
|
|
879
|
-
Det -> 'the' | 'a'
|
|
880
|
-
N -> 'man' | 'park' | 'dog' | 'telescope'
|
|
881
|
-
V -> 'ate' | 'saw'
|
|
882
|
-
P -> 'in' | 'under' | 'with'
|
|
883
|
-
""")
|
|
884
|
-
|
|
885
|
-
sent = 'the dog saw a man in the park'.split()
|
|
886
|
-
|
|
887
|
-
RecursiveDescentApp(grammar, sent).mainloop()
|
|
888
|
-
|
|
889
|
-
if __name__ == '__main__':
|
|
890
|
-
app()
|
|
891
|
-
|
|
892
|
-
__all__ = ['app']
|
|
893
|
-
|
|
894
|
-
|
|
1
|
+
# Natural Language Toolkit: Recursive Descent Parser Application
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2001-2018 NLTK Project
|
|
4
|
+
# Author: Edward Loper <edloper@gmail.com>
|
|
5
|
+
# URL: <http://nltk.org/>
|
|
6
|
+
# For license information, see LICENSE.TXT
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
A graphical tool for exploring the recursive descent parser.
|
|
10
|
+
|
|
11
|
+
The recursive descent parser maintains a tree, which records the
|
|
12
|
+
structure of the portion of the text that has been parsed. It uses
|
|
13
|
+
CFG productions to expand the fringe of the tree, and matches its
|
|
14
|
+
leaves against the text. Initially, the tree contains the start
|
|
15
|
+
symbol ("S"). It is shown in the main canvas, to the right of the
|
|
16
|
+
list of available expansions.
|
|
17
|
+
|
|
18
|
+
The parser builds up a tree structure for the text using three
|
|
19
|
+
operations:
|
|
20
|
+
|
|
21
|
+
- "expand" uses a CFG production to add children to a node on the
|
|
22
|
+
fringe of the tree.
|
|
23
|
+
- "match" compares a leaf in the tree to a text token.
|
|
24
|
+
- "backtrack" returns the tree to its state before the most recent
|
|
25
|
+
expand or match operation.
|
|
26
|
+
|
|
27
|
+
The parser maintains a list of tree locations called a "frontier" to
|
|
28
|
+
remember which nodes have not yet been expanded and which leaves have
|
|
29
|
+
not yet been matched against the text. The leftmost frontier node is
|
|
30
|
+
shown in green, and the other frontier nodes are shown in blue. The
|
|
31
|
+
parser always performs expand and match operations on the leftmost
|
|
32
|
+
element of the frontier.
|
|
33
|
+
|
|
34
|
+
You can control the parser's operation by using the "expand," "match,"
|
|
35
|
+
and "backtrack" buttons; or you can use the "step" button to let the
|
|
36
|
+
parser automatically decide which operation to apply. The parser uses
|
|
37
|
+
the following rules to decide which operation to apply:
|
|
38
|
+
|
|
39
|
+
- If the leftmost frontier element is a token, try matching it.
|
|
40
|
+
- If the leftmost frontier element is a node, try expanding it with
|
|
41
|
+
the first untried expansion.
|
|
42
|
+
- Otherwise, backtrack.
|
|
43
|
+
|
|
44
|
+
The "expand" button applies the untried expansion whose CFG production
|
|
45
|
+
is listed earliest in the grammar. To manually choose which expansion
|
|
46
|
+
to apply, click on a CFG production from the list of available
|
|
47
|
+
expansions, on the left side of the main window.
|
|
48
|
+
|
|
49
|
+
The "autostep" button will let the parser continue applying
|
|
50
|
+
applications to the tree until it reaches a complete parse. You can
|
|
51
|
+
cancel an autostep in progress at any time by clicking on the
|
|
52
|
+
"autostep" button again.
|
|
53
|
+
|
|
54
|
+
Keyboard Shortcuts::
|
|
55
|
+
[Space]\t Perform the next expand, match, or backtrack operation
|
|
56
|
+
[a]\t Step through operations until the next complete parse
|
|
57
|
+
[e]\t Perform an expand operation
|
|
58
|
+
[m]\t Perform a match operation
|
|
59
|
+
[b]\t Perform a backtrack operation
|
|
60
|
+
[Delete]\t Reset the parser
|
|
61
|
+
[g]\t Show/hide available expansions list
|
|
62
|
+
[h]\t Help
|
|
63
|
+
[Ctrl-p]\t Print
|
|
64
|
+
[q]\t Quit
|
|
65
|
+
"""
|
|
66
|
+
from __future__ import division
|
|
67
|
+
|
|
68
|
+
from six.moves.tkinter_font import Font
|
|
69
|
+
from six.moves.tkinter import (Listbox, IntVar, Button, Frame, Label, Menu,
|
|
70
|
+
Scrollbar, Tk)
|
|
71
|
+
|
|
72
|
+
from nltk.tree import Tree
|
|
73
|
+
from nltk.util import in_idle
|
|
74
|
+
from nltk.parse import SteppingRecursiveDescentParser
|
|
75
|
+
from nltk.draw.util import TextWidget, ShowText, CanvasFrame, EntryDialog
|
|
76
|
+
from nltk.draw import CFGEditor, TreeSegmentWidget, tree_to_treesegment
|
|
77
|
+
|
|
78
|
+
class RecursiveDescentApp(object):
|
|
79
|
+
"""
|
|
80
|
+
A graphical tool for exploring the recursive descent parser. The tool
|
|
81
|
+
displays the parser's tree and the remaining text, and allows the
|
|
82
|
+
user to control the parser's operation. In particular, the user
|
|
83
|
+
can expand subtrees on the frontier, match tokens on the frontier
|
|
84
|
+
against the text, and backtrack. A "step" button simply steps
|
|
85
|
+
through the parsing process, performing the operations that
|
|
86
|
+
``RecursiveDescentParser`` would use.
|
|
87
|
+
"""
|
|
88
|
+
def __init__(self, grammar, sent, trace=0):
|
|
89
|
+
self._sent = sent
|
|
90
|
+
self._parser = SteppingRecursiveDescentParser(grammar, trace)
|
|
91
|
+
|
|
92
|
+
# Set up the main window.
|
|
93
|
+
self._top = Tk()
|
|
94
|
+
self._top.title('Recursive Descent Parser Application')
|
|
95
|
+
|
|
96
|
+
# Set up key bindings.
|
|
97
|
+
self._init_bindings()
|
|
98
|
+
|
|
99
|
+
# Initialize the fonts.
|
|
100
|
+
self._init_fonts(self._top)
|
|
101
|
+
|
|
102
|
+
# Animations. animating_lock is a lock to prevent the demo
|
|
103
|
+
# from performing new operations while it's animating.
|
|
104
|
+
self._animation_frames = IntVar(self._top)
|
|
105
|
+
self._animation_frames.set(5)
|
|
106
|
+
self._animating_lock = 0
|
|
107
|
+
self._autostep = 0
|
|
108
|
+
|
|
109
|
+
# The user can hide the grammar.
|
|
110
|
+
self._show_grammar = IntVar(self._top)
|
|
111
|
+
self._show_grammar.set(1)
|
|
112
|
+
|
|
113
|
+
# Create the basic frames.
|
|
114
|
+
self._init_menubar(self._top)
|
|
115
|
+
self._init_buttons(self._top)
|
|
116
|
+
self._init_feedback(self._top)
|
|
117
|
+
self._init_grammar(self._top)
|
|
118
|
+
self._init_canvas(self._top)
|
|
119
|
+
|
|
120
|
+
# Initialize the parser.
|
|
121
|
+
self._parser.initialize(self._sent)
|
|
122
|
+
|
|
123
|
+
# Resize callback
|
|
124
|
+
self._canvas.bind('<Configure>', self._configure)
|
|
125
|
+
|
|
126
|
+
#########################################
|
|
127
|
+
## Initialization Helpers
|
|
128
|
+
#########################################
|
|
129
|
+
|
|
130
|
+
def _init_fonts(self, root):
|
|
131
|
+
# See: <http://www.astro.washington.edu/owen/ROTKFolklore.html>
|
|
132
|
+
self._sysfont = Font(font=Button()["font"])
|
|
133
|
+
root.option_add("*Font", self._sysfont)
|
|
134
|
+
|
|
135
|
+
# TWhat's our font size (default=same as sysfont)
|
|
136
|
+
self._size = IntVar(root)
|
|
137
|
+
self._size.set(self._sysfont.cget('size'))
|
|
138
|
+
|
|
139
|
+
self._boldfont = Font(family='helvetica', weight='bold',
|
|
140
|
+
size=self._size.get())
|
|
141
|
+
self._font = Font(family='helvetica',
|
|
142
|
+
size=self._size.get())
|
|
143
|
+
if self._size.get() < 0: big = self._size.get()-2
|
|
144
|
+
else: big = self._size.get()+2
|
|
145
|
+
self._bigfont = Font(family='helvetica', weight='bold',
|
|
146
|
+
size=big)
|
|
147
|
+
|
|
148
|
+
def _init_grammar(self, parent):
|
|
149
|
+
# Grammar view.
|
|
150
|
+
self._prodframe = listframe = Frame(parent)
|
|
151
|
+
self._prodframe.pack(fill='both', side='left', padx=2)
|
|
152
|
+
self._prodlist_label = Label(self._prodframe, font=self._boldfont,
|
|
153
|
+
text='Available Expansions')
|
|
154
|
+
self._prodlist_label.pack()
|
|
155
|
+
self._prodlist = Listbox(self._prodframe, selectmode='single',
|
|
156
|
+
relief='groove', background='white',
|
|
157
|
+
foreground='#909090', font=self._font,
|
|
158
|
+
selectforeground='#004040',
|
|
159
|
+
selectbackground='#c0f0c0')
|
|
160
|
+
|
|
161
|
+
self._prodlist.pack(side='right', fill='both', expand=1)
|
|
162
|
+
|
|
163
|
+
self._productions = list(self._parser.grammar().productions())
|
|
164
|
+
for production in self._productions:
|
|
165
|
+
self._prodlist.insert('end', (' %s' % production))
|
|
166
|
+
self._prodlist.config(height=min(len(self._productions), 25))
|
|
167
|
+
|
|
168
|
+
# Add a scrollbar if there are more than 25 productions.
|
|
169
|
+
if len(self._productions) > 25:
|
|
170
|
+
listscroll = Scrollbar(self._prodframe,
|
|
171
|
+
orient='vertical')
|
|
172
|
+
self._prodlist.config(yscrollcommand = listscroll.set)
|
|
173
|
+
listscroll.config(command=self._prodlist.yview)
|
|
174
|
+
listscroll.pack(side='left', fill='y')
|
|
175
|
+
|
|
176
|
+
# If they select a production, apply it.
|
|
177
|
+
self._prodlist.bind('<<ListboxSelect>>', self._prodlist_select)
|
|
178
|
+
|
|
179
|
+
def _init_bindings(self):
|
|
180
|
+
# Key bindings are a good thing.
|
|
181
|
+
self._top.bind('<Control-q>', self.destroy)
|
|
182
|
+
self._top.bind('<Control-x>', self.destroy)
|
|
183
|
+
self._top.bind('<Escape>', self.destroy)
|
|
184
|
+
self._top.bind('e', self.expand)
|
|
185
|
+
#self._top.bind('<Alt-e>', self.expand)
|
|
186
|
+
#self._top.bind('<Control-e>', self.expand)
|
|
187
|
+
self._top.bind('m', self.match)
|
|
188
|
+
self._top.bind('<Alt-m>', self.match)
|
|
189
|
+
self._top.bind('<Control-m>', self.match)
|
|
190
|
+
self._top.bind('b', self.backtrack)
|
|
191
|
+
self._top.bind('<Alt-b>', self.backtrack)
|
|
192
|
+
self._top.bind('<Control-b>', self.backtrack)
|
|
193
|
+
self._top.bind('<Control-z>', self.backtrack)
|
|
194
|
+
self._top.bind('<BackSpace>', self.backtrack)
|
|
195
|
+
self._top.bind('a', self.autostep)
|
|
196
|
+
#self._top.bind('<Control-a>', self.autostep)
|
|
197
|
+
self._top.bind('<Control-space>', self.autostep)
|
|
198
|
+
self._top.bind('<Control-c>', self.cancel_autostep)
|
|
199
|
+
self._top.bind('<space>', self.step)
|
|
200
|
+
self._top.bind('<Delete>', self.reset)
|
|
201
|
+
self._top.bind('<Control-p>', self.postscript)
|
|
202
|
+
#self._top.bind('<h>', self.help)
|
|
203
|
+
#self._top.bind('<Alt-h>', self.help)
|
|
204
|
+
self._top.bind('<Control-h>', self.help)
|
|
205
|
+
self._top.bind('<F1>', self.help)
|
|
206
|
+
#self._top.bind('<g>', self.toggle_grammar)
|
|
207
|
+
#self._top.bind('<Alt-g>', self.toggle_grammar)
|
|
208
|
+
#self._top.bind('<Control-g>', self.toggle_grammar)
|
|
209
|
+
self._top.bind('<Control-g>', self.edit_grammar)
|
|
210
|
+
self._top.bind('<Control-t>', self.edit_sentence)
|
|
211
|
+
|
|
212
|
+
def _init_buttons(self, parent):
|
|
213
|
+
# Set up the frames.
|
|
214
|
+
self._buttonframe = buttonframe = Frame(parent)
|
|
215
|
+
buttonframe.pack(fill='none', side='bottom', padx=3, pady=2)
|
|
216
|
+
Button(buttonframe, text='Step',
|
|
217
|
+
background='#90c0d0', foreground='black',
|
|
218
|
+
command=self.step,).pack(side='left')
|
|
219
|
+
Button(buttonframe, text='Autostep',
|
|
220
|
+
background='#90c0d0', foreground='black',
|
|
221
|
+
command=self.autostep,).pack(side='left')
|
|
222
|
+
Button(buttonframe, text='Expand', underline=0,
|
|
223
|
+
background='#90f090', foreground='black',
|
|
224
|
+
command=self.expand).pack(side='left')
|
|
225
|
+
Button(buttonframe, text='Match', underline=0,
|
|
226
|
+
background='#90f090', foreground='black',
|
|
227
|
+
command=self.match).pack(side='left')
|
|
228
|
+
Button(buttonframe, text='Backtrack', underline=0,
|
|
229
|
+
background='#f0a0a0', foreground='black',
|
|
230
|
+
command=self.backtrack).pack(side='left')
|
|
231
|
+
# Replace autostep...
|
|
232
|
+
# self._autostep_button = Button(buttonframe, text='Autostep',
|
|
233
|
+
# underline=0, command=self.autostep)
|
|
234
|
+
# self._autostep_button.pack(side='left')
|
|
235
|
+
|
|
236
|
+
def _configure(self, event):
|
|
237
|
+
self._autostep = 0
|
|
238
|
+
(x1, y1, x2, y2) = self._cframe.scrollregion()
|
|
239
|
+
y2 = event.height - 6
|
|
240
|
+
self._canvas['scrollregion'] = '%d %d %d %d' % (x1,y1,x2,y2)
|
|
241
|
+
self._redraw()
|
|
242
|
+
|
|
243
|
+
def _init_feedback(self, parent):
|
|
244
|
+
self._feedbackframe = feedbackframe = Frame(parent)
|
|
245
|
+
feedbackframe.pack(fill='x', side='bottom', padx=3, pady=3)
|
|
246
|
+
self._lastoper_label = Label(feedbackframe, text='Last Operation:',
|
|
247
|
+
font=self._font)
|
|
248
|
+
self._lastoper_label.pack(side='left')
|
|
249
|
+
lastoperframe = Frame(feedbackframe, relief='sunken', border=1)
|
|
250
|
+
lastoperframe.pack(fill='x', side='right', expand=1, padx=5)
|
|
251
|
+
self._lastoper1 = Label(lastoperframe, foreground='#007070',
|
|
252
|
+
background='#f0f0f0', font=self._font)
|
|
253
|
+
self._lastoper2 = Label(lastoperframe, anchor='w', width=30,
|
|
254
|
+
foreground='#004040', background='#f0f0f0',
|
|
255
|
+
font=self._font)
|
|
256
|
+
self._lastoper1.pack(side='left')
|
|
257
|
+
self._lastoper2.pack(side='left', fill='x', expand=1)
|
|
258
|
+
|
|
259
|
+
def _init_canvas(self, parent):
|
|
260
|
+
self._cframe = CanvasFrame(parent, background='white',
|
|
261
|
+
#width=525, height=250,
|
|
262
|
+
closeenough=10,
|
|
263
|
+
border=2, relief='sunken')
|
|
264
|
+
self._cframe.pack(expand=1, fill='both', side='top', pady=2)
|
|
265
|
+
canvas = self._canvas = self._cframe.canvas()
|
|
266
|
+
|
|
267
|
+
# Initially, there's no tree or text
|
|
268
|
+
self._tree = None
|
|
269
|
+
self._textwidgets = []
|
|
270
|
+
self._textline = None
|
|
271
|
+
|
|
272
|
+
def _init_menubar(self, parent):
|
|
273
|
+
menubar = Menu(parent)
|
|
274
|
+
|
|
275
|
+
filemenu = Menu(menubar, tearoff=0)
|
|
276
|
+
filemenu.add_command(label='Reset Parser', underline=0,
|
|
277
|
+
command=self.reset, accelerator='Del')
|
|
278
|
+
filemenu.add_command(label='Print to Postscript', underline=0,
|
|
279
|
+
command=self.postscript, accelerator='Ctrl-p')
|
|
280
|
+
filemenu.add_command(label='Exit', underline=1,
|
|
281
|
+
command=self.destroy, accelerator='Ctrl-x')
|
|
282
|
+
menubar.add_cascade(label='File', underline=0, menu=filemenu)
|
|
283
|
+
|
|
284
|
+
editmenu = Menu(menubar, tearoff=0)
|
|
285
|
+
editmenu.add_command(label='Edit Grammar', underline=5,
|
|
286
|
+
command=self.edit_grammar,
|
|
287
|
+
accelerator='Ctrl-g')
|
|
288
|
+
editmenu.add_command(label='Edit Text', underline=5,
|
|
289
|
+
command=self.edit_sentence,
|
|
290
|
+
accelerator='Ctrl-t')
|
|
291
|
+
menubar.add_cascade(label='Edit', underline=0, menu=editmenu)
|
|
292
|
+
|
|
293
|
+
rulemenu = Menu(menubar, tearoff=0)
|
|
294
|
+
rulemenu.add_command(label='Step', underline=1,
|
|
295
|
+
command=self.step, accelerator='Space')
|
|
296
|
+
rulemenu.add_separator()
|
|
297
|
+
rulemenu.add_command(label='Match', underline=0,
|
|
298
|
+
command=self.match, accelerator='Ctrl-m')
|
|
299
|
+
rulemenu.add_command(label='Expand', underline=0,
|
|
300
|
+
command=self.expand, accelerator='Ctrl-e')
|
|
301
|
+
rulemenu.add_separator()
|
|
302
|
+
rulemenu.add_command(label='Backtrack', underline=0,
|
|
303
|
+
command=self.backtrack, accelerator='Ctrl-b')
|
|
304
|
+
menubar.add_cascade(label='Apply', underline=0, menu=rulemenu)
|
|
305
|
+
|
|
306
|
+
viewmenu = Menu(menubar, tearoff=0)
|
|
307
|
+
viewmenu.add_checkbutton(label="Show Grammar", underline=0,
|
|
308
|
+
variable=self._show_grammar,
|
|
309
|
+
command=self._toggle_grammar)
|
|
310
|
+
viewmenu.add_separator()
|
|
311
|
+
viewmenu.add_radiobutton(label='Tiny', variable=self._size,
|
|
312
|
+
underline=0, value=10, command=self.resize)
|
|
313
|
+
viewmenu.add_radiobutton(label='Small', variable=self._size,
|
|
314
|
+
underline=0, value=12, command=self.resize)
|
|
315
|
+
viewmenu.add_radiobutton(label='Medium', variable=self._size,
|
|
316
|
+
underline=0, value=14, command=self.resize)
|
|
317
|
+
viewmenu.add_radiobutton(label='Large', variable=self._size,
|
|
318
|
+
underline=0, value=18, command=self.resize)
|
|
319
|
+
viewmenu.add_radiobutton(label='Huge', variable=self._size,
|
|
320
|
+
underline=0, value=24, command=self.resize)
|
|
321
|
+
menubar.add_cascade(label='View', underline=0, menu=viewmenu)
|
|
322
|
+
|
|
323
|
+
animatemenu = Menu(menubar, tearoff=0)
|
|
324
|
+
animatemenu.add_radiobutton(label="No Animation", underline=0,
|
|
325
|
+
variable=self._animation_frames,
|
|
326
|
+
value=0)
|
|
327
|
+
animatemenu.add_radiobutton(label="Slow Animation", underline=0,
|
|
328
|
+
variable=self._animation_frames,
|
|
329
|
+
value=10, accelerator='-')
|
|
330
|
+
animatemenu.add_radiobutton(label="Normal Animation", underline=0,
|
|
331
|
+
variable=self._animation_frames,
|
|
332
|
+
value=5, accelerator='=')
|
|
333
|
+
animatemenu.add_radiobutton(label="Fast Animation", underline=0,
|
|
334
|
+
variable=self._animation_frames,
|
|
335
|
+
value=2, accelerator='+')
|
|
336
|
+
menubar.add_cascade(label="Animate", underline=1, menu=animatemenu)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
helpmenu = Menu(menubar, tearoff=0)
|
|
340
|
+
helpmenu.add_command(label='About', underline=0,
|
|
341
|
+
command=self.about)
|
|
342
|
+
helpmenu.add_command(label='Instructions', underline=0,
|
|
343
|
+
command=self.help, accelerator='F1')
|
|
344
|
+
menubar.add_cascade(label='Help', underline=0, menu=helpmenu)
|
|
345
|
+
|
|
346
|
+
parent.config(menu=menubar)
|
|
347
|
+
|
|
348
|
+
#########################################
|
|
349
|
+
## Helper
|
|
350
|
+
#########################################
|
|
351
|
+
|
|
352
|
+
def _get(self, widget, treeloc):
|
|
353
|
+
for i in treeloc: widget = widget.subtrees()[i]
|
|
354
|
+
if isinstance(widget, TreeSegmentWidget):
|
|
355
|
+
widget = widget.label()
|
|
356
|
+
return widget
|
|
357
|
+
|
|
358
|
+
#########################################
|
|
359
|
+
## Main draw procedure
|
|
360
|
+
#########################################
|
|
361
|
+
|
|
362
|
+
def _redraw(self):
|
|
363
|
+
canvas = self._canvas
|
|
364
|
+
|
|
365
|
+
# Delete the old tree, widgets, etc.
|
|
366
|
+
if self._tree is not None:
|
|
367
|
+
self._cframe.destroy_widget(self._tree)
|
|
368
|
+
for twidget in self._textwidgets:
|
|
369
|
+
self._cframe.destroy_widget(twidget)
|
|
370
|
+
if self._textline is not None:
|
|
371
|
+
self._canvas.delete(self._textline)
|
|
372
|
+
|
|
373
|
+
# Draw the tree.
|
|
374
|
+
helv = ('helvetica', -self._size.get())
|
|
375
|
+
bold = ('helvetica', -self._size.get(), 'bold')
|
|
376
|
+
attribs = {'tree_color': '#000000', 'tree_width': 2,
|
|
377
|
+
'node_font': bold, 'leaf_font': helv,}
|
|
378
|
+
tree = self._parser.tree()
|
|
379
|
+
self._tree = tree_to_treesegment(canvas, tree, **attribs)
|
|
380
|
+
self._cframe.add_widget(self._tree, 30, 5)
|
|
381
|
+
|
|
382
|
+
# Draw the text.
|
|
383
|
+
helv = ('helvetica', -self._size.get())
|
|
384
|
+
bottom = y = self._cframe.scrollregion()[3]
|
|
385
|
+
self._textwidgets = [TextWidget(canvas, word, font=self._font)
|
|
386
|
+
for word in self._sent]
|
|
387
|
+
for twidget in self._textwidgets:
|
|
388
|
+
self._cframe.add_widget(twidget, 0, 0)
|
|
389
|
+
twidget.move(0, bottom-twidget.bbox()[3]-5)
|
|
390
|
+
y = min(y, twidget.bbox()[1])
|
|
391
|
+
|
|
392
|
+
# Draw a line over the text, to separate it from the tree.
|
|
393
|
+
self._textline = canvas.create_line(-5000, y-5, 5000, y-5, dash='.')
|
|
394
|
+
|
|
395
|
+
# Highlight appropriate nodes.
|
|
396
|
+
self._highlight_nodes()
|
|
397
|
+
self._highlight_prodlist()
|
|
398
|
+
|
|
399
|
+
# Make sure the text lines up.
|
|
400
|
+
self._position_text()
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def _redraw_quick(self):
|
|
404
|
+
# This should be more-or-less sufficient after an animation.
|
|
405
|
+
self._highlight_nodes()
|
|
406
|
+
self._highlight_prodlist()
|
|
407
|
+
self._position_text()
|
|
408
|
+
|
|
409
|
+
def _highlight_nodes(self):
|
|
410
|
+
# Highlight the list of nodes to be checked.
|
|
411
|
+
bold = ('helvetica', -self._size.get(), 'bold')
|
|
412
|
+
for treeloc in self._parser.frontier()[:1]:
|
|
413
|
+
self._get(self._tree, treeloc)['color'] = '#20a050'
|
|
414
|
+
self._get(self._tree, treeloc)['font'] = bold
|
|
415
|
+
for treeloc in self._parser.frontier()[1:]:
|
|
416
|
+
self._get(self._tree, treeloc)['color'] = '#008080'
|
|
417
|
+
|
|
418
|
+
def _highlight_prodlist(self):
|
|
419
|
+
# Highlight the productions that can be expanded.
|
|
420
|
+
# Boy, too bad tkinter doesn't implement Listbox.itemconfig;
|
|
421
|
+
# that would be pretty useful here.
|
|
422
|
+
self._prodlist.delete(0, 'end')
|
|
423
|
+
expandable = self._parser.expandable_productions()
|
|
424
|
+
untried = self._parser.untried_expandable_productions()
|
|
425
|
+
productions = self._productions
|
|
426
|
+
for index in range(len(productions)):
|
|
427
|
+
if productions[index] in expandable:
|
|
428
|
+
if productions[index] in untried:
|
|
429
|
+
self._prodlist.insert(index, ' %s' % productions[index])
|
|
430
|
+
else:
|
|
431
|
+
self._prodlist.insert(index, ' %s (TRIED)' %
|
|
432
|
+
productions[index])
|
|
433
|
+
self._prodlist.selection_set(index)
|
|
434
|
+
else:
|
|
435
|
+
self._prodlist.insert(index, ' %s' % productions[index])
|
|
436
|
+
|
|
437
|
+
def _position_text(self):
|
|
438
|
+
# Line up the text widgets that are matched against the tree
|
|
439
|
+
numwords = len(self._sent)
|
|
440
|
+
num_matched = numwords - len(self._parser.remaining_text())
|
|
441
|
+
leaves = self._tree_leaves()[:num_matched]
|
|
442
|
+
xmax = self._tree.bbox()[0]
|
|
443
|
+
for i in range(0, len(leaves)):
|
|
444
|
+
widget = self._textwidgets[i]
|
|
445
|
+
leaf = leaves[i]
|
|
446
|
+
widget['color'] = '#006040'
|
|
447
|
+
leaf['color'] = '#006040'
|
|
448
|
+
widget.move(leaf.bbox()[0] - widget.bbox()[0], 0)
|
|
449
|
+
xmax = widget.bbox()[2] + 10
|
|
450
|
+
|
|
451
|
+
# Line up the text widgets that are not matched against the tree.
|
|
452
|
+
for i in range(len(leaves), numwords):
|
|
453
|
+
widget = self._textwidgets[i]
|
|
454
|
+
widget['color'] = '#a0a0a0'
|
|
455
|
+
widget.move(xmax - widget.bbox()[0], 0)
|
|
456
|
+
xmax = widget.bbox()[2] + 10
|
|
457
|
+
|
|
458
|
+
# If we have a complete parse, make everything green :)
|
|
459
|
+
if self._parser.currently_complete():
|
|
460
|
+
for twidget in self._textwidgets:
|
|
461
|
+
twidget['color'] = '#00a000'
|
|
462
|
+
|
|
463
|
+
# Move the matched leaves down to the text.
|
|
464
|
+
for i in range(0, len(leaves)):
|
|
465
|
+
widget = self._textwidgets[i]
|
|
466
|
+
leaf = leaves[i]
|
|
467
|
+
dy = widget.bbox()[1] - leaf.bbox()[3] - 10.0
|
|
468
|
+
dy = max(dy, leaf.parent().label().bbox()[3] - leaf.bbox()[3] + 10)
|
|
469
|
+
leaf.move(0, dy)
|
|
470
|
+
|
|
471
|
+
def _tree_leaves(self, tree=None):
|
|
472
|
+
if tree is None: tree = self._tree
|
|
473
|
+
if isinstance(tree, TreeSegmentWidget):
|
|
474
|
+
leaves = []
|
|
475
|
+
for child in tree.subtrees(): leaves += self._tree_leaves(child)
|
|
476
|
+
return leaves
|
|
477
|
+
else:
|
|
478
|
+
return [tree]
|
|
479
|
+
|
|
480
|
+
#########################################
|
|
481
|
+
## Button Callbacks
|
|
482
|
+
#########################################
|
|
483
|
+
|
|
484
|
+
def destroy(self, *e):
|
|
485
|
+
self._autostep = 0
|
|
486
|
+
if self._top is None: return
|
|
487
|
+
self._top.destroy()
|
|
488
|
+
self._top = None
|
|
489
|
+
|
|
490
|
+
def reset(self, *e):
|
|
491
|
+
self._autostep = 0
|
|
492
|
+
self._parser.initialize(self._sent)
|
|
493
|
+
self._lastoper1['text'] = 'Reset Application'
|
|
494
|
+
self._lastoper2['text'] = ''
|
|
495
|
+
self._redraw()
|
|
496
|
+
|
|
497
|
+
def autostep(self, *e):
|
|
498
|
+
if self._animation_frames.get() == 0:
|
|
499
|
+
self._animation_frames.set(2)
|
|
500
|
+
if self._autostep:
|
|
501
|
+
self._autostep = 0
|
|
502
|
+
else:
|
|
503
|
+
self._autostep = 1
|
|
504
|
+
self._step()
|
|
505
|
+
|
|
506
|
+
def cancel_autostep(self, *e):
|
|
507
|
+
#self._autostep_button['text'] = 'Autostep'
|
|
508
|
+
self._autostep = 0
|
|
509
|
+
|
|
510
|
+
# Make sure to stop auto-stepping if we get any user input.
|
|
511
|
+
def step(self, *e): self._autostep = 0; self._step()
|
|
512
|
+
def match(self, *e): self._autostep = 0; self._match()
|
|
513
|
+
def expand(self, *e): self._autostep = 0; self._expand()
|
|
514
|
+
def backtrack(self, *e): self._autostep = 0; self._backtrack()
|
|
515
|
+
|
|
516
|
+
def _step(self):
|
|
517
|
+
if self._animating_lock: return
|
|
518
|
+
|
|
519
|
+
# Try expanding, matching, and backtracking (in that order)
|
|
520
|
+
if self._expand(): pass
|
|
521
|
+
elif self._parser.untried_match() and self._match(): pass
|
|
522
|
+
elif self._backtrack(): pass
|
|
523
|
+
else:
|
|
524
|
+
self._lastoper1['text'] = 'Finished'
|
|
525
|
+
self._lastoper2['text'] = ''
|
|
526
|
+
self._autostep = 0
|
|
527
|
+
|
|
528
|
+
# Check if we just completed a parse.
|
|
529
|
+
if self._parser.currently_complete():
|
|
530
|
+
self._autostep = 0
|
|
531
|
+
self._lastoper2['text'] += ' [COMPLETE PARSE]'
|
|
532
|
+
|
|
533
|
+
def _expand(self, *e):
|
|
534
|
+
if self._animating_lock: return
|
|
535
|
+
old_frontier = self._parser.frontier()
|
|
536
|
+
rv = self._parser.expand()
|
|
537
|
+
if rv is not None:
|
|
538
|
+
self._lastoper1['text'] = 'Expand:'
|
|
539
|
+
self._lastoper2['text'] = rv
|
|
540
|
+
self._prodlist.selection_clear(0, 'end')
|
|
541
|
+
index = self._productions.index(rv)
|
|
542
|
+
self._prodlist.selection_set(index)
|
|
543
|
+
self._animate_expand(old_frontier[0])
|
|
544
|
+
return True
|
|
545
|
+
else:
|
|
546
|
+
self._lastoper1['text'] = 'Expand:'
|
|
547
|
+
self._lastoper2['text'] = '(all expansions tried)'
|
|
548
|
+
return False
|
|
549
|
+
|
|
550
|
+
def _match(self, *e):
|
|
551
|
+
if self._animating_lock: return
|
|
552
|
+
old_frontier = self._parser.frontier()
|
|
553
|
+
rv = self._parser.match()
|
|
554
|
+
if rv is not None:
|
|
555
|
+
self._lastoper1['text'] = 'Match:'
|
|
556
|
+
self._lastoper2['text'] = rv
|
|
557
|
+
self._animate_match(old_frontier[0])
|
|
558
|
+
return True
|
|
559
|
+
else:
|
|
560
|
+
self._lastoper1['text'] = 'Match:'
|
|
561
|
+
self._lastoper2['text'] = '(failed)'
|
|
562
|
+
return False
|
|
563
|
+
|
|
564
|
+
def _backtrack(self, *e):
|
|
565
|
+
if self._animating_lock: return
|
|
566
|
+
if self._parser.backtrack():
|
|
567
|
+
elt = self._parser.tree()
|
|
568
|
+
for i in self._parser.frontier()[0]:
|
|
569
|
+
elt = elt[i]
|
|
570
|
+
self._lastoper1['text'] = 'Backtrack'
|
|
571
|
+
self._lastoper2['text'] = ''
|
|
572
|
+
if isinstance(elt, Tree):
|
|
573
|
+
self._animate_backtrack(self._parser.frontier()[0])
|
|
574
|
+
else:
|
|
575
|
+
self._animate_match_backtrack(self._parser.frontier()[0])
|
|
576
|
+
return True
|
|
577
|
+
else:
|
|
578
|
+
self._autostep = 0
|
|
579
|
+
self._lastoper1['text'] = 'Finished'
|
|
580
|
+
self._lastoper2['text'] = ''
|
|
581
|
+
return False
|
|
582
|
+
|
|
583
|
+
def about(self, *e):
|
|
584
|
+
ABOUT = ("NLTK Recursive Descent Parser Application\n"+
|
|
585
|
+
"Written by Edward Loper")
|
|
586
|
+
TITLE = 'About: Recursive Descent Parser Application'
|
|
587
|
+
try:
|
|
588
|
+
from six.moves.tkinter_messagebox import Message
|
|
589
|
+
Message(message=ABOUT, title=TITLE).show()
|
|
590
|
+
except:
|
|
591
|
+
ShowText(self._top, TITLE, ABOUT)
|
|
592
|
+
|
|
593
|
+
def help(self, *e):
|
|
594
|
+
self._autostep = 0
|
|
595
|
+
# The default font's not very legible; try using 'fixed' instead.
|
|
596
|
+
try:
|
|
597
|
+
ShowText(self._top, 'Help: Recursive Descent Parser Application',
|
|
598
|
+
(__doc__ or '').strip(), width=75, font='fixed')
|
|
599
|
+
except:
|
|
600
|
+
ShowText(self._top, 'Help: Recursive Descent Parser Application',
|
|
601
|
+
(__doc__ or '').strip(), width=75)
|
|
602
|
+
|
|
603
|
+
def postscript(self, *e):
|
|
604
|
+
self._autostep = 0
|
|
605
|
+
self._cframe.print_to_file()
|
|
606
|
+
|
|
607
|
+
def mainloop(self, *args, **kwargs):
|
|
608
|
+
"""
|
|
609
|
+
Enter the Tkinter mainloop. This function must be called if
|
|
610
|
+
this demo is created from a non-interactive program (e.g.
|
|
611
|
+
from a secript); otherwise, the demo will close as soon as
|
|
612
|
+
the script completes.
|
|
613
|
+
"""
|
|
614
|
+
if in_idle(): return
|
|
615
|
+
self._top.mainloop(*args, **kwargs)
|
|
616
|
+
|
|
617
|
+
def resize(self, size=None):
|
|
618
|
+
if size is not None: self._size.set(size)
|
|
619
|
+
size = self._size.get()
|
|
620
|
+
self._font.configure(size=-(abs(size)))
|
|
621
|
+
self._boldfont.configure(size=-(abs(size)))
|
|
622
|
+
self._sysfont.configure(size=-(abs(size)))
|
|
623
|
+
self._bigfont.configure(size=-(abs(size+2)))
|
|
624
|
+
self._redraw()
|
|
625
|
+
|
|
626
|
+
#########################################
|
|
627
|
+
## Expand Production Selection
|
|
628
|
+
#########################################
|
|
629
|
+
|
|
630
|
+
def _toggle_grammar(self, *e):
|
|
631
|
+
if self._show_grammar.get():
|
|
632
|
+
self._prodframe.pack(fill='both', side='left', padx=2,
|
|
633
|
+
after=self._feedbackframe)
|
|
634
|
+
self._lastoper1['text'] = 'Show Grammar'
|
|
635
|
+
else:
|
|
636
|
+
self._prodframe.pack_forget()
|
|
637
|
+
self._lastoper1['text'] = 'Hide Grammar'
|
|
638
|
+
self._lastoper2['text'] = ''
|
|
639
|
+
|
|
640
|
+
# def toggle_grammar(self, *e):
|
|
641
|
+
# self._show_grammar = not self._show_grammar
|
|
642
|
+
# if self._show_grammar:
|
|
643
|
+
# self._prodframe.pack(fill='both', expand='y', side='left',
|
|
644
|
+
# after=self._feedbackframe)
|
|
645
|
+
# self._lastoper1['text'] = 'Show Grammar'
|
|
646
|
+
# else:
|
|
647
|
+
# self._prodframe.pack_forget()
|
|
648
|
+
# self._lastoper1['text'] = 'Hide Grammar'
|
|
649
|
+
# self._lastoper2['text'] = ''
|
|
650
|
+
|
|
651
|
+
def _prodlist_select(self, event):
|
|
652
|
+
selection = self._prodlist.curselection()
|
|
653
|
+
if len(selection) != 1: return
|
|
654
|
+
index = int(selection[0])
|
|
655
|
+
old_frontier = self._parser.frontier()
|
|
656
|
+
production = self._parser.expand(self._productions[index])
|
|
657
|
+
|
|
658
|
+
if production:
|
|
659
|
+
self._lastoper1['text'] = 'Expand:'
|
|
660
|
+
self._lastoper2['text'] = production
|
|
661
|
+
self._prodlist.selection_clear(0, 'end')
|
|
662
|
+
self._prodlist.selection_set(index)
|
|
663
|
+
self._animate_expand(old_frontier[0])
|
|
664
|
+
else:
|
|
665
|
+
# Reset the production selections.
|
|
666
|
+
self._prodlist.selection_clear(0, 'end')
|
|
667
|
+
for prod in self._parser.expandable_productions():
|
|
668
|
+
index = self._productions.index(prod)
|
|
669
|
+
self._prodlist.selection_set(index)
|
|
670
|
+
|
|
671
|
+
#########################################
|
|
672
|
+
## Animation
|
|
673
|
+
#########################################
|
|
674
|
+
|
|
675
|
+
def _animate_expand(self, treeloc):
|
|
676
|
+
oldwidget = self._get(self._tree, treeloc)
|
|
677
|
+
oldtree = oldwidget.parent()
|
|
678
|
+
top = not isinstance(oldtree.parent(), TreeSegmentWidget)
|
|
679
|
+
|
|
680
|
+
tree = self._parser.tree()
|
|
681
|
+
for i in treeloc:
|
|
682
|
+
tree = tree[i]
|
|
683
|
+
|
|
684
|
+
widget = tree_to_treesegment(self._canvas, tree,
|
|
685
|
+
node_font=self._boldfont,
|
|
686
|
+
leaf_color='white',
|
|
687
|
+
tree_width=2, tree_color='white',
|
|
688
|
+
node_color='white',
|
|
689
|
+
leaf_font=self._font)
|
|
690
|
+
widget.label()['color'] = '#20a050'
|
|
691
|
+
|
|
692
|
+
(oldx, oldy) = oldtree.label().bbox()[:2]
|
|
693
|
+
(newx, newy) = widget.label().bbox()[:2]
|
|
694
|
+
widget.move(oldx-newx, oldy-newy)
|
|
695
|
+
|
|
696
|
+
if top:
|
|
697
|
+
self._cframe.add_widget(widget, 0, 5)
|
|
698
|
+
widget.move(30-widget.label().bbox()[0], 0)
|
|
699
|
+
self._tree = widget
|
|
700
|
+
else:
|
|
701
|
+
oldtree.parent().replace_child(oldtree, widget)
|
|
702
|
+
|
|
703
|
+
# Move the children over so they don't overlap.
|
|
704
|
+
# Line the children up in a strange way.
|
|
705
|
+
if widget.subtrees():
|
|
706
|
+
dx = (oldx + widget.label().width()/2 -
|
|
707
|
+
widget.subtrees()[0].bbox()[0]/2 -
|
|
708
|
+
widget.subtrees()[0].bbox()[2]/2)
|
|
709
|
+
for subtree in widget.subtrees(): subtree.move(dx, 0)
|
|
710
|
+
|
|
711
|
+
self._makeroom(widget)
|
|
712
|
+
|
|
713
|
+
if top:
|
|
714
|
+
self._cframe.destroy_widget(oldtree)
|
|
715
|
+
else:
|
|
716
|
+
oldtree.destroy()
|
|
717
|
+
|
|
718
|
+
colors = ['gray%d' % (10*int(10*x/self._animation_frames.get()))
|
|
719
|
+
for x in range(self._animation_frames.get(),0,-1)]
|
|
720
|
+
|
|
721
|
+
# Move the text string down, if necessary.
|
|
722
|
+
dy = widget.bbox()[3] + 30 - self._canvas.coords(self._textline)[1]
|
|
723
|
+
if dy > 0:
|
|
724
|
+
for twidget in self._textwidgets: twidget.move(0, dy)
|
|
725
|
+
self._canvas.move(self._textline, 0, dy)
|
|
726
|
+
|
|
727
|
+
self._animate_expand_frame(widget, colors)
|
|
728
|
+
|
|
729
|
+
def _makeroom(self, treeseg):
|
|
730
|
+
"""
|
|
731
|
+
Make sure that no sibling tree bbox's overlap.
|
|
732
|
+
"""
|
|
733
|
+
parent = treeseg.parent()
|
|
734
|
+
if not isinstance(parent, TreeSegmentWidget): return
|
|
735
|
+
|
|
736
|
+
index = parent.subtrees().index(treeseg)
|
|
737
|
+
|
|
738
|
+
# Handle siblings to the right
|
|
739
|
+
rsiblings = parent.subtrees()[index+1:]
|
|
740
|
+
if rsiblings:
|
|
741
|
+
dx = treeseg.bbox()[2] - rsiblings[0].bbox()[0] + 10
|
|
742
|
+
for sibling in rsiblings: sibling.move(dx, 0)
|
|
743
|
+
|
|
744
|
+
# Handle siblings to the left
|
|
745
|
+
if index > 0:
|
|
746
|
+
lsibling = parent.subtrees()[index-1]
|
|
747
|
+
dx = max(0, lsibling.bbox()[2] - treeseg.bbox()[0] + 10)
|
|
748
|
+
treeseg.move(dx, 0)
|
|
749
|
+
|
|
750
|
+
# Keep working up the tree.
|
|
751
|
+
self._makeroom(parent)
|
|
752
|
+
|
|
753
|
+
def _animate_expand_frame(self, widget, colors):
|
|
754
|
+
if len(colors) > 0:
|
|
755
|
+
self._animating_lock = 1
|
|
756
|
+
widget['color'] = colors[0]
|
|
757
|
+
for subtree in widget.subtrees():
|
|
758
|
+
if isinstance(subtree, TreeSegmentWidget):
|
|
759
|
+
subtree.label()['color'] = colors[0]
|
|
760
|
+
else:
|
|
761
|
+
subtree['color'] = colors[0]
|
|
762
|
+
self._top.after(50, self._animate_expand_frame,
|
|
763
|
+
widget, colors[1:])
|
|
764
|
+
else:
|
|
765
|
+
widget['color'] = 'black'
|
|
766
|
+
for subtree in widget.subtrees():
|
|
767
|
+
if isinstance(subtree, TreeSegmentWidget):
|
|
768
|
+
subtree.label()['color'] = 'black'
|
|
769
|
+
else:
|
|
770
|
+
subtree['color'] = 'black'
|
|
771
|
+
self._redraw_quick()
|
|
772
|
+
widget.label()['color'] = 'black'
|
|
773
|
+
self._animating_lock = 0
|
|
774
|
+
if self._autostep: self._step()
|
|
775
|
+
|
|
776
|
+
def _animate_backtrack(self, treeloc):
|
|
777
|
+
# Flash red first, if we're animating.
|
|
778
|
+
if self._animation_frames.get() == 0: colors = []
|
|
779
|
+
else: colors = ['#a00000', '#000000', '#a00000']
|
|
780
|
+
colors += ['gray%d' % (10*int(10*x/(self._animation_frames.get())))
|
|
781
|
+
for x in range(1, self._animation_frames.get()+1)]
|
|
782
|
+
|
|
783
|
+
widgets = [self._get(self._tree, treeloc).parent()]
|
|
784
|
+
for subtree in widgets[0].subtrees():
|
|
785
|
+
if isinstance(subtree, TreeSegmentWidget):
|
|
786
|
+
widgets.append(subtree.label())
|
|
787
|
+
else:
|
|
788
|
+
widgets.append(subtree)
|
|
789
|
+
|
|
790
|
+
self._animate_backtrack_frame(widgets, colors)
|
|
791
|
+
|
|
792
|
+
def _animate_backtrack_frame(self, widgets, colors):
|
|
793
|
+
if len(colors) > 0:
|
|
794
|
+
self._animating_lock = 1
|
|
795
|
+
for widget in widgets: widget['color'] = colors[0]
|
|
796
|
+
self._top.after(50, self._animate_backtrack_frame,
|
|
797
|
+
widgets, colors[1:])
|
|
798
|
+
else:
|
|
799
|
+
for widget in widgets[0].subtrees():
|
|
800
|
+
widgets[0].remove_child(widget)
|
|
801
|
+
widget.destroy()
|
|
802
|
+
self._redraw_quick()
|
|
803
|
+
self._animating_lock = 0
|
|
804
|
+
if self._autostep: self._step()
|
|
805
|
+
|
|
806
|
+
def _animate_match_backtrack(self, treeloc):
|
|
807
|
+
widget = self._get(self._tree, treeloc)
|
|
808
|
+
node = widget.parent().label()
|
|
809
|
+
dy = ((node.bbox()[3] - widget.bbox()[1] + 14) /
|
|
810
|
+
max(1, self._animation_frames.get()))
|
|
811
|
+
self._animate_match_backtrack_frame(self._animation_frames.get(),
|
|
812
|
+
widget, dy)
|
|
813
|
+
|
|
814
|
+
def _animate_match(self, treeloc):
|
|
815
|
+
widget = self._get(self._tree, treeloc)
|
|
816
|
+
|
|
817
|
+
dy = ((self._textwidgets[0].bbox()[1] - widget.bbox()[3] - 10.0) /
|
|
818
|
+
max(1, self._animation_frames.get()))
|
|
819
|
+
self._animate_match_frame(self._animation_frames.get(), widget, dy)
|
|
820
|
+
|
|
821
|
+
def _animate_match_frame(self, frame, widget, dy):
|
|
822
|
+
if frame > 0:
|
|
823
|
+
self._animating_lock = 1
|
|
824
|
+
widget.move(0, dy)
|
|
825
|
+
self._top.after(10, self._animate_match_frame,
|
|
826
|
+
frame-1, widget, dy)
|
|
827
|
+
else:
|
|
828
|
+
widget['color'] = '#006040'
|
|
829
|
+
self._redraw_quick()
|
|
830
|
+
self._animating_lock = 0
|
|
831
|
+
if self._autostep: self._step()
|
|
832
|
+
|
|
833
|
+
def _animate_match_backtrack_frame(self, frame, widget, dy):
|
|
834
|
+
if frame > 0:
|
|
835
|
+
self._animating_lock = 1
|
|
836
|
+
widget.move(0, dy)
|
|
837
|
+
self._top.after(10, self._animate_match_backtrack_frame,
|
|
838
|
+
frame-1, widget, dy)
|
|
839
|
+
else:
|
|
840
|
+
widget.parent().remove_child(widget)
|
|
841
|
+
widget.destroy()
|
|
842
|
+
self._animating_lock = 0
|
|
843
|
+
if self._autostep: self._step()
|
|
844
|
+
|
|
845
|
+
def edit_grammar(self, *e):
|
|
846
|
+
CFGEditor(self._top, self._parser.grammar(), self.set_grammar)
|
|
847
|
+
|
|
848
|
+
def set_grammar(self, grammar):
|
|
849
|
+
self._parser.set_grammar(grammar)
|
|
850
|
+
self._productions = list(grammar.productions())
|
|
851
|
+
self._prodlist.delete(0, 'end')
|
|
852
|
+
for production in self._productions:
|
|
853
|
+
self._prodlist.insert('end', (' %s' % production))
|
|
854
|
+
|
|
855
|
+
def edit_sentence(self, *e):
|
|
856
|
+
sentence = " ".join(self._sent)
|
|
857
|
+
title = 'Edit Text'
|
|
858
|
+
instr = 'Enter a new sentence to parse.'
|
|
859
|
+
EntryDialog(self._top, sentence, instr, self.set_sentence, title)
|
|
860
|
+
|
|
861
|
+
def set_sentence(self, sentence):
|
|
862
|
+
self._sent = sentence.split() #[XX] use tagged?
|
|
863
|
+
self.reset()
|
|
864
|
+
|
|
865
|
+
def app():
|
|
866
|
+
"""
|
|
867
|
+
Create a recursive descent parser demo, using a simple grammar and
|
|
868
|
+
text.
|
|
869
|
+
"""
|
|
870
|
+
from nltk.grammar import CFG
|
|
871
|
+
grammar = CFG.fromstring("""
|
|
872
|
+
# Grammatical productions.
|
|
873
|
+
S -> NP VP
|
|
874
|
+
NP -> Det N PP | Det N
|
|
875
|
+
VP -> V NP PP | V NP | V
|
|
876
|
+
PP -> P NP
|
|
877
|
+
# Lexical productions.
|
|
878
|
+
NP -> 'I'
|
|
879
|
+
Det -> 'the' | 'a'
|
|
880
|
+
N -> 'man' | 'park' | 'dog' | 'telescope'
|
|
881
|
+
V -> 'ate' | 'saw'
|
|
882
|
+
P -> 'in' | 'under' | 'with'
|
|
883
|
+
""")
|
|
884
|
+
|
|
885
|
+
sent = 'the dog saw a man in the park'.split()
|
|
886
|
+
|
|
887
|
+
RecursiveDescentApp(grammar, sent).mainloop()
|
|
888
|
+
|
|
889
|
+
if __name__ == '__main__':
|
|
890
|
+
app()
|
|
891
|
+
|
|
892
|
+
__all__ = ['app']
|