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.
Files changed (406) hide show
  1. {nltk-3.2 → nltk-3.3}/INSTALL.txt +6 -6
  2. {nltk-3.2 → nltk-3.3}/LICENSE.txt +13 -13
  3. {nltk-3.2 → nltk-3.3}/MANIFEST.in +6 -6
  4. {nltk-3.2/nltk.egg-info → nltk-3.3}/PKG-INFO +13 -5
  5. nltk-3.2/README.txt → nltk-3.3/README.md +49 -48
  6. nltk-3.3/nltk/VERSION +1 -0
  7. {nltk-3.2 → nltk-3.3}/nltk/__init__.py +185 -185
  8. {nltk-3.2 → nltk-3.3}/nltk/app/__init__.py +52 -53
  9. {nltk-3.2 → nltk-3.3}/nltk/app/chartparser_app.py +2276 -2276
  10. {nltk-3.2 → nltk-3.3}/nltk/app/chunkparser_app.py +1262 -1264
  11. {nltk-3.2 → nltk-3.3}/nltk/app/collocations_app.py +347 -350
  12. {nltk-3.2 → nltk-3.3}/nltk/app/concordance_app.py +568 -570
  13. {nltk-3.2 → nltk-3.3}/nltk/app/nemo_app.py +156 -155
  14. {nltk-3.2 → nltk-3.3}/nltk/app/rdparser_app.py +892 -894
  15. {nltk-3.2 → nltk-3.3}/nltk/app/srparser_app.py +809 -809
  16. {nltk-3.2 → nltk-3.3}/nltk/app/wordfreq_app.py +32 -32
  17. {nltk-3.2 → nltk-3.3}/nltk/app/wordnet_app.py +968 -968
  18. {nltk-3.2 → nltk-3.3}/nltk/book.py +91 -90
  19. {nltk-3.2 → nltk-3.3}/nltk/ccg/__init__.py +22 -22
  20. {nltk-3.2 → nltk-3.3}/nltk/ccg/api.py +359 -335
  21. {nltk-3.2 → nltk-3.3}/nltk/ccg/chart.py +394 -361
  22. {nltk-3.2 → nltk-3.3}/nltk/ccg/combinator.py +343 -312
  23. {nltk-3.2 → nltk-3.3}/nltk/ccg/lexicon.py +328 -290
  24. nltk-3.3/nltk/ccg/logic.py +46 -0
  25. {nltk-3.2 → nltk-3.3}/nltk/chat/__init__.py +49 -49
  26. {nltk-3.2 → nltk-3.3}/nltk/chat/eliza.py +244 -244
  27. {nltk-3.2 → nltk-3.3}/nltk/chat/iesha.py +140 -140
  28. {nltk-3.2 → nltk-3.3}/nltk/chat/rude.py +92 -92
  29. {nltk-3.2 → nltk-3.3}/nltk/chat/suntsu.py +117 -117
  30. {nltk-3.2 → nltk-3.3}/nltk/chat/util.py +122 -120
  31. {nltk-3.2 → nltk-3.3}/nltk/chat/zen.py +282 -282
  32. {nltk-3.2 → nltk-3.3}/nltk/chunk/__init__.py +190 -190
  33. {nltk-3.2 → nltk-3.3}/nltk/chunk/api.py +51 -51
  34. {nltk-3.2 → nltk-3.3}/nltk/chunk/named_entity.py +331 -331
  35. {nltk-3.2 → nltk-3.3}/nltk/chunk/regexp.py +1390 -1384
  36. {nltk-3.2 → nltk-3.3}/nltk/chunk/util.py +600 -600
  37. {nltk-3.2 → nltk-3.3}/nltk/classify/__init__.py +98 -98
  38. {nltk-3.2 → nltk-3.3}/nltk/classify/api.py +193 -193
  39. {nltk-3.2 → nltk-3.3}/nltk/classify/decisiontree.py +295 -295
  40. {nltk-3.2 → nltk-3.3}/nltk/classify/maxent.py +1510 -1492
  41. {nltk-3.2 → nltk-3.3}/nltk/classify/megam.py +179 -178
  42. {nltk-3.2 → nltk-3.3}/nltk/classify/naivebayes.py +245 -242
  43. {nltk-3.2 → nltk-3.3}/nltk/classify/positivenaivebayes.py +170 -170
  44. {nltk-3.2 → nltk-3.3}/nltk/classify/rte_classify.py +160 -191
  45. {nltk-3.2 → nltk-3.3}/nltk/classify/scikitlearn.py +153 -151
  46. {nltk-3.2 → nltk-3.3}/nltk/classify/senna.py +183 -181
  47. {nltk-3.2 → nltk-3.3}/nltk/classify/svm.py +15 -15
  48. {nltk-3.2 → nltk-3.3}/nltk/classify/tadm.py +113 -112
  49. {nltk-3.2 → nltk-3.3}/nltk/classify/textcat.py +193 -193
  50. {nltk-3.2 → nltk-3.3}/nltk/classify/util.py +324 -312
  51. {nltk-3.2 → nltk-3.3}/nltk/classify/weka.py +346 -345
  52. {nltk-3.2 → nltk-3.3}/nltk/cluster/__init__.py +86 -86
  53. {nltk-3.2 → nltk-3.3}/nltk/cluster/api.py +74 -70
  54. {nltk-3.2 → nltk-3.3}/nltk/cluster/em.py +247 -247
  55. {nltk-3.2 → nltk-3.3}/nltk/cluster/gaac.py +168 -168
  56. {nltk-3.2 → nltk-3.3}/nltk/cluster/kmeans.py +221 -221
  57. {nltk-3.2 → nltk-3.3}/nltk/cluster/util.py +304 -289
  58. nltk-3.3/nltk/collections.py +640 -0
  59. {nltk-3.2 → nltk-3.3}/nltk/collocations.py +388 -388
  60. nltk-3.3/nltk/compat.py +365 -0
  61. {nltk-3.2 → nltk-3.3}/nltk/corpus/__init__.py +341 -320
  62. {nltk-3.2 → nltk-3.3}/nltk/corpus/europarl_raw.py +44 -44
  63. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/__init__.py +147 -145
  64. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/aligned.py +115 -114
  65. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/api.py +448 -446
  66. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/bnc.py +252 -252
  67. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/bracket_parse.py +227 -227
  68. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/categorized_sents.py +179 -178
  69. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/chasen.py +140 -139
  70. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/childes.py +522 -494
  71. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/chunked.py +212 -212
  72. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/cmudict.py +97 -95
  73. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/comparative_sents.py +280 -278
  74. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/conll.py +528 -521
  75. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/crubadan.py +116 -116
  76. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/dependency.py +101 -101
  77. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/framenet.py +3059 -2093
  78. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/ieer.py +112 -111
  79. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/indian.py +85 -86
  80. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/ipipan.py +332 -331
  81. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/knbc.py +187 -187
  82. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/lin.py +156 -156
  83. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/mte.py +311 -310
  84. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/nkjp.py +429 -428
  85. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/nombank.py +421 -421
  86. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/nps_chat.py +73 -73
  87. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/opinion_lexicon.py +115 -115
  88. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/panlex_lite.py +165 -165
  89. nltk-3.3/nltk/corpus/reader/pl196x.py +292 -0
  90. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/plaintext.py +232 -233
  91. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/ppattach.py +96 -95
  92. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/propbank.py +479 -481
  93. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/pros_cons.py +128 -126
  94. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/reviews.py +330 -328
  95. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/rte.py +144 -146
  96. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/semcor.py +256 -256
  97. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/senseval.py +203 -201
  98. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/sentiwordnet.py +137 -137
  99. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/sinica_treebank.py +75 -75
  100. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/string_category.py +60 -59
  101. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/switchboard.py +119 -119
  102. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/tagged.py +295 -294
  103. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/timit.py +452 -450
  104. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/toolbox.py +68 -68
  105. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/twitter.py +155 -157
  106. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/udhr.py +80 -80
  107. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/util.py +804 -800
  108. nltk-3.3/nltk/corpus/reader/verbnet.py +595 -0
  109. nltk-3.3/nltk/corpus/reader/wordlist.py +134 -0
  110. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/wordnet.py +2108 -2036
  111. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/xmldocs.py +392 -387
  112. {nltk-3.2 → nltk-3.3}/nltk/corpus/reader/ycoe.py +243 -242
  113. {nltk-3.2 → nltk-3.3}/nltk/corpus/util.py +144 -127
  114. {nltk-3.2 → nltk-3.3}/nltk/data.py +1487 -1451
  115. {nltk-3.2 → nltk-3.3}/nltk/decorators.py +219 -215
  116. {nltk-3.2 → nltk-3.3}/nltk/downloader.py +2278 -2274
  117. {nltk-3.2 → nltk-3.3}/nltk/draw/__init__.py +27 -27
  118. {nltk-3.2 → nltk-3.3}/nltk/draw/cfg.py +774 -777
  119. {nltk-3.2 → nltk-3.3}/nltk/draw/dispersion.py +58 -58
  120. {nltk-3.2 → nltk-3.3}/nltk/draw/table.py +1098 -1098
  121. {nltk-3.2 → nltk-3.3}/nltk/draw/tree.py +963 -966
  122. {nltk-3.2 → nltk-3.3}/nltk/draw/util.py +2356 -2356
  123. {nltk-3.2 → nltk-3.3}/nltk/featstruct.py +2617 -2500
  124. {nltk-3.2 → nltk-3.3}/nltk/grammar.py +1559 -1516
  125. {nltk-3.2 → nltk-3.3}/nltk/help.py +56 -56
  126. {nltk-3.2 → nltk-3.3}/nltk/inference/__init__.py +20 -20
  127. {nltk-3.2 → nltk-3.3}/nltk/inference/api.py +597 -589
  128. {nltk-3.2 → nltk-3.3}/nltk/inference/discourse.py +612 -608
  129. {nltk-3.2 → nltk-3.3}/nltk/inference/mace.py +311 -311
  130. {nltk-3.2 → nltk-3.3}/nltk/inference/nonmonotonic.py +509 -509
  131. {nltk-3.2 → nltk-3.3}/nltk/inference/prover9.py +431 -431
  132. {nltk-3.2 → nltk-3.3}/nltk/inference/resolution.py +687 -687
  133. {nltk-3.2 → nltk-3.3}/nltk/inference/tableau.py +607 -607
  134. {nltk-3.2 → nltk-3.3}/nltk/internals.py +982 -982
  135. {nltk-3.2 → nltk-3.3}/nltk/jsontags.py +63 -63
  136. {nltk-3.2 → nltk-3.3}/nltk/lazyimport.py +142 -142
  137. {nltk-3.2 → nltk-3.3}/nltk/metrics/__init__.py +30 -29
  138. {nltk-3.2 → nltk-3.3}/nltk/metrics/agreement.py +421 -401
  139. nltk-3.3/nltk/metrics/aline.py +607 -0
  140. {nltk-3.2 → nltk-3.3}/nltk/metrics/association.py +414 -410
  141. {nltk-3.2 → nltk-3.3}/nltk/metrics/confusionmatrix.py +206 -206
  142. {nltk-3.2 → nltk-3.3}/nltk/metrics/distance.py +207 -202
  143. {nltk-3.2 → nltk-3.3}/nltk/metrics/paice.py +381 -381
  144. {nltk-3.2 → nltk-3.3}/nltk/metrics/scores.py +228 -228
  145. {nltk-3.2 → nltk-3.3}/nltk/metrics/segmentation.py +231 -232
  146. {nltk-3.2 → nltk-3.3}/nltk/metrics/spearman.py +69 -69
  147. {nltk-3.2 → nltk-3.3}/nltk/misc/__init__.py +11 -11
  148. {nltk-3.2 → nltk-3.3}/nltk/misc/babelfish.py +10 -10
  149. {nltk-3.2 → nltk-3.3}/nltk/misc/chomsky.py +135 -133
  150. {nltk-3.2 → nltk-3.3}/nltk/misc/minimalset.py +83 -83
  151. {nltk-3.2 → nltk-3.3}/nltk/misc/sort.py +157 -157
  152. {nltk-3.2 → nltk-3.3}/nltk/misc/wordfinder.py +129 -129
  153. {nltk-3.2 → nltk-3.3}/nltk/parse/__init__.py +81 -80
  154. {nltk-3.2 → nltk-3.3}/nltk/parse/api.py +66 -66
  155. {nltk-3.2 → nltk-3.3}/nltk/parse/bllip.py +282 -282
  156. {nltk-3.2 → nltk-3.3}/nltk/parse/chart.py +1682 -1681
  157. nltk-3.3/nltk/parse/corenlp.py +780 -0
  158. {nltk-3.2 → nltk-3.3}/nltk/parse/dependencygraph.py +760 -758
  159. {nltk-3.2 → nltk-3.3}/nltk/parse/earleychart.py +452 -451
  160. {nltk-3.2 → nltk-3.3}/nltk/parse/evaluate.py +129 -129
  161. {nltk-3.2 → nltk-3.3}/nltk/parse/featurechart.py +580 -578
  162. {nltk-3.2 → nltk-3.3}/nltk/parse/generate.py +87 -76
  163. {nltk-3.2 → nltk-3.3}/nltk/parse/malt.py +356 -356
  164. {nltk-3.2 → nltk-3.3}/nltk/parse/nonprojectivedependencyparser.py +783 -783
  165. {nltk-3.2 → nltk-3.3}/nltk/parse/pchart.py +522 -522
  166. {nltk-3.2 → nltk-3.3}/nltk/parse/projectivedependencyparser.py +579 -578
  167. {nltk-3.2 → nltk-3.3}/nltk/parse/recursivedescent.py +655 -656
  168. {nltk-3.2 → nltk-3.3}/nltk/parse/shiftreduce.py +458 -459
  169. {nltk-3.2 → nltk-3.3}/nltk/parse/stanford.py +431 -406
  170. {nltk-3.2 → nltk-3.3}/nltk/parse/transitionparser.py +774 -772
  171. {nltk-3.2 → nltk-3.3}/nltk/parse/util.py +231 -231
  172. {nltk-3.2 → nltk-3.3}/nltk/parse/viterbi.py +401 -401
  173. {nltk-3.2 → nltk-3.3}/nltk/probability.py +2410 -2355
  174. {nltk-3.2 → nltk-3.3}/nltk/sem/__init__.py +61 -61
  175. {nltk-3.2 → nltk-3.3}/nltk/sem/boxer.py +1261 -1261
  176. {nltk-3.2 → nltk-3.3}/nltk/sem/chat80.py +782 -782
  177. {nltk-3.2 → nltk-3.3}/nltk/sem/cooper_storage.py +118 -118
  178. {nltk-3.2 → nltk-3.3}/nltk/sem/drt.py +1258 -1257
  179. {nltk-3.2 → nltk-3.3}/nltk/sem/drt_glue_demo.py +485 -489
  180. {nltk-3.2 → nltk-3.3}/nltk/sem/evaluate.py +790 -784
  181. {nltk-3.2 → nltk-3.3}/nltk/sem/glue.py +696 -694
  182. {nltk-3.2 → nltk-3.3}/nltk/sem/hole.py +386 -384
  183. {nltk-3.2 → nltk-3.3}/nltk/sem/lfg.py +210 -208
  184. {nltk-3.2 → nltk-3.3}/nltk/sem/linearlogic.py +451 -449
  185. {nltk-3.2 → nltk-3.3}/nltk/sem/logic.py +1910 -1910
  186. {nltk-3.2 → nltk-3.3}/nltk/sem/relextract.py +476 -483
  187. {nltk-3.2 → nltk-3.3}/nltk/sem/skolemize.py +101 -101
  188. {nltk-3.2 → nltk-3.3}/nltk/sem/util.py +249 -249
  189. {nltk-3.2 → nltk-3.3}/nltk/sentiment/__init__.py +14 -14
  190. {nltk-3.2 → nltk-3.3}/nltk/sentiment/sentiment_analyzer.py +229 -229
  191. {nltk-3.2 → nltk-3.3}/nltk/sentiment/util.py +762 -752
  192. {nltk-3.2 → nltk-3.3}/nltk/sentiment/vader.py +443 -445
  193. {nltk-3.2 → nltk-3.3}/nltk/stem/__init__.py +31 -33
  194. {nltk-3.2 → nltk-3.3}/nltk/stem/api.py +28 -25
  195. nltk-3.3/nltk/stem/arlstem.py +369 -0
  196. {nltk-3.2 → nltk-3.3}/nltk/stem/isri.py +345 -345
  197. {nltk-3.2 → nltk-3.3}/nltk/stem/lancaster.py +337 -310
  198. nltk-3.3/nltk/stem/porter.py +710 -0
  199. {nltk-3.2 → nltk-3.3}/nltk/stem/regexp.py +61 -61
  200. {nltk-3.2 → nltk-3.3}/nltk/stem/rslp.py +142 -142
  201. {nltk-3.2 → nltk-3.3}/nltk/stem/snowball.py +4236 -3712
  202. {nltk-3.2 → nltk-3.3}/nltk/stem/util.py +22 -12
  203. {nltk-3.2 → nltk-3.3}/nltk/stem/wordnet.py +51 -51
  204. {nltk-3.2 → nltk-3.3}/nltk/tag/__init__.py +152 -127
  205. {nltk-3.2 → nltk-3.3}/nltk/tag/api.py +86 -81
  206. {nltk-3.2 → nltk-3.3}/nltk/tag/brill.py +424 -426
  207. {nltk-3.2 → nltk-3.3}/nltk/tag/brill_trainer.py +608 -608
  208. {nltk-3.2 → nltk-3.3}/nltk/tag/crf.py +204 -200
  209. {nltk-3.2 → nltk-3.3}/nltk/tag/hmm.py +1277 -1277
  210. {nltk-3.2 → nltk-3.3}/nltk/tag/hunpos.py +132 -132
  211. {nltk-3.2 → nltk-3.3}/nltk/tag/mapping.py +101 -101
  212. {nltk-3.2 → nltk-3.3}/nltk/tag/perceptron.py +329 -314
  213. {nltk-3.2 → nltk-3.3}/nltk/tag/senna.py +147 -145
  214. {nltk-3.2 → nltk-3.3}/nltk/tag/sequential.py +740 -736
  215. {nltk-3.2 → nltk-3.3}/nltk/tag/stanford.py +219 -196
  216. {nltk-3.2 → nltk-3.3}/nltk/tag/tnt.py +595 -604
  217. {nltk-3.2 → nltk-3.3}/nltk/tag/util.py +72 -72
  218. {nltk-3.2 → nltk-3.3}/nltk/tbl/__init__.py +28 -28
  219. {nltk-3.2 → nltk-3.3}/nltk/tbl/api.py +1 -1
  220. {nltk-3.2 → nltk-3.3}/nltk/tbl/demo.py +366 -366
  221. {nltk-3.2 → nltk-3.3}/nltk/tbl/erroranalysis.py +39 -39
  222. {nltk-3.2 → nltk-3.3}/nltk/tbl/feature.py +259 -261
  223. {nltk-3.2 → nltk-3.3}/nltk/tbl/rule.py +321 -321
  224. {nltk-3.2 → nltk-3.3}/nltk/tbl/template.py +303 -308
  225. {nltk-3.2 → nltk-3.3}/nltk/test/__init__.py +18 -18
  226. {nltk-3.2 → nltk-3.3}/nltk/test/all.py +24 -23
  227. {nltk-3.2 → nltk-3.3}/nltk/test/bleu.doctest +14 -14
  228. {nltk-3.2 → nltk-3.3}/nltk/test/bnc.doctest +60 -60
  229. {nltk-3.2 → nltk-3.3}/nltk/test/ccg.doctest +376 -395
  230. nltk-3.3/nltk/test/ccg_semantics.doctest +553 -0
  231. {nltk-3.2 → nltk-3.3}/nltk/test/chat80.doctest +234 -234
  232. {nltk-3.2 → nltk-3.3}/nltk/test/childes.doctest +184 -184
  233. {nltk-3.2 → nltk-3.3}/nltk/test/childes_fixt.py +14 -13
  234. {nltk-3.2 → nltk-3.3}/nltk/test/chunk.doctest +373 -373
  235. {nltk-3.2 → nltk-3.3}/nltk/test/classify.doctest +185 -185
  236. {nltk-3.2 → nltk-3.3}/nltk/test/classify_fixt.py +11 -10
  237. nltk-3.3/nltk/test/collections.doctest +20 -0
  238. {nltk-3.2 → nltk-3.3}/nltk/test/collocations.doctest +276 -276
  239. {nltk-3.2 → nltk-3.3}/nltk/test/compat.doctest +134 -138
  240. {nltk-3.2 → nltk-3.3}/nltk/test/compat_fixt.py +9 -8
  241. nltk-3.3/nltk/test/concordance.doctest +55 -0
  242. {nltk-3.2 → nltk-3.3}/nltk/test/corpus.doctest +2200 -2212
  243. {nltk-3.2 → nltk-3.3}/nltk/test/corpus_fixt.py +3 -3
  244. {nltk-3.2 → nltk-3.3}/nltk/test/crubadan.doctest +65 -65
  245. {nltk-3.2 → nltk-3.3}/nltk/test/data.doctest +379 -379
  246. {nltk-3.2 → nltk-3.3}/nltk/test/dependency.doctest +241 -241
  247. {nltk-3.2 → nltk-3.3}/nltk/test/discourse.doctest +546 -546
  248. {nltk-3.2 → nltk-3.3}/nltk/test/discourse_fixt.py +14 -13
  249. {nltk-3.2 → nltk-3.3}/nltk/test/doctest_nose_plugin.py +159 -156
  250. {nltk-3.2 → nltk-3.3}/nltk/test/drt.doctest +517 -517
  251. {nltk-3.2 → nltk-3.3}/nltk/test/featgram.doctest +607 -607
  252. {nltk-3.2 → nltk-3.3}/nltk/test/featstruct.doctest +1229 -1230
  253. {nltk-3.2 → nltk-3.3}/nltk/test/framenet.doctest +288 -239
  254. {nltk-3.2 → nltk-3.3}/nltk/test/generate.doctest +67 -67
  255. {nltk-3.2 → nltk-3.3}/nltk/test/gensim.doctest +140 -141
  256. {nltk-3.2 → nltk-3.3}/nltk/test/gensim_fixt.py +10 -9
  257. {nltk-3.2 → nltk-3.3}/nltk/test/gluesemantics.doctest +384 -386
  258. {nltk-3.2 → nltk-3.3}/nltk/test/gluesemantics_malt.doctest +68 -68
  259. {nltk-3.2 → nltk-3.3}/nltk/test/gluesemantics_malt_fixt.py +12 -11
  260. {nltk-3.2 → nltk-3.3}/nltk/test/grammar.doctest +48 -48
  261. {nltk-3.2 → nltk-3.3}/nltk/test/grammartestsuites.doctest +109 -109
  262. {nltk-3.2 → nltk-3.3}/nltk/test/index.doctest +100 -100
  263. {nltk-3.2 → nltk-3.3}/nltk/test/inference.doctest +534 -534
  264. {nltk-3.2 → nltk-3.3}/nltk/test/inference_fixt.py +12 -11
  265. {nltk-3.2 → nltk-3.3}/nltk/test/internals.doctest +140 -140
  266. {nltk-3.2 → nltk-3.3}/nltk/test/japanese.doctest +48 -48
  267. {nltk-3.2 → nltk-3.3}/nltk/test/logic.doctest +1098 -1098
  268. {nltk-3.2 → nltk-3.3}/nltk/test/metrics.doctest +270 -270
  269. {nltk-3.2 → nltk-3.3}/nltk/test/misc.doctest +118 -118
  270. {nltk-3.2 → nltk-3.3}/nltk/test/nonmonotonic.doctest +286 -286
  271. {nltk-3.2 → nltk-3.3}/nltk/test/nonmonotonic_fixt.py +12 -11
  272. {nltk-3.2 → nltk-3.3}/nltk/test/paice.doctest +35 -35
  273. {nltk-3.2 → nltk-3.3}/nltk/test/parse.doctest +884 -884
  274. {nltk-3.2 → nltk-3.3}/nltk/test/portuguese_en.doctest +565 -565
  275. {nltk-3.2 → nltk-3.3}/nltk/test/portuguese_en_fixt.py +16 -16
  276. {nltk-3.2 → nltk-3.3}/nltk/test/probability.doctest +304 -304
  277. {nltk-3.2 → nltk-3.3}/nltk/test/probability_fixt.py +13 -12
  278. {nltk-3.2 → nltk-3.3}/nltk/test/propbank.doctest +176 -176
  279. {nltk-3.2 → nltk-3.3}/nltk/test/relextract.doctest +263 -263
  280. {nltk-3.2 → nltk-3.3}/nltk/test/resolution.doctest +221 -221
  281. {nltk-3.2 → nltk-3.3}/nltk/test/runtests.py +86 -64
  282. {nltk-3.2 → nltk-3.3}/nltk/test/segmentation_fixt.py +10 -10
  283. {nltk-3.2 → nltk-3.3}/nltk/test/semantics.doctest +665 -665
  284. {nltk-3.2 → nltk-3.3}/nltk/test/semantics_fixt.py +7 -7
  285. {nltk-3.2 → nltk-3.3}/nltk/test/sentiment.doctest +233 -233
  286. {nltk-3.2 → nltk-3.3}/nltk/test/sentiwordnet.doctest +41 -41
  287. {nltk-3.2 → nltk-3.3}/nltk/test/simple.doctest +85 -85
  288. {nltk-3.2 → nltk-3.3}/nltk/test/stem.doctest +78 -78
  289. {nltk-3.2 → nltk-3.3}/nltk/test/tag.doctest +33 -33
  290. {nltk-3.2 → nltk-3.3}/nltk/test/tokenize.doctest +273 -166
  291. {nltk-3.2 → nltk-3.3}/nltk/test/toolbox.doctest +307 -307
  292. {nltk-3.2 → nltk-3.3}/nltk/test/translate.doctest +242 -242
  293. {nltk-3.2 → nltk-3.3}/nltk/test/translate_fixt.py +3 -3
  294. {nltk-3.2 → nltk-3.3}/nltk/test/tree.doctest +1101 -1101
  295. {nltk-3.2 → nltk-3.3}/nltk/test/treeprettyprinter.doctest +127 -127
  296. {nltk-3.2 → nltk-3.3}/nltk/test/treetransforms.doctest +156 -156
  297. nltk-3.3/nltk/test/unit/test_2x_compat.py +63 -0
  298. nltk-3.3/nltk/test/unit/test_aline.py +52 -0
  299. nltk-3.3/nltk/test/unit/test_chunk.py +56 -0
  300. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_classify.py +55 -53
  301. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_collocations.py +88 -79
  302. nltk-3.3/nltk/test/unit/test_concordance.py +98 -0
  303. nltk-3.3/nltk/test/unit/test_corenlp.py +1000 -0
  304. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_corpora.py +200 -183
  305. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_corpus_views.py +45 -45
  306. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_hmm.py +88 -88
  307. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_json2csv_corpus.py +188 -187
  308. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_naivebayes.py +26 -26
  309. nltk-3.3/nltk/test/unit/test_rte_classify.py +86 -0
  310. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_seekable_unicode_stream_reader.py +124 -124
  311. nltk-3.3/nltk/test/unit/test_senna.py +74 -0
  312. nltk-3.3/nltk/test/unit/test_stem.py +121 -0
  313. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_tag.py +20 -20
  314. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_tgrep.py +628 -626
  315. nltk-3.3/nltk/test/unit/test_tokenize.py +168 -0
  316. {nltk-3.2 → nltk-3.3}/nltk/test/unit/test_twitter_auth.py +185 -185
  317. nltk-3.3/nltk/test/unit/test_wordnet.py +134 -0
  318. nltk-3.3/nltk/test/unit/translate/test_bleu.py +244 -0
  319. nltk-3.3/nltk/test/unit/translate/test_gdfa.py +57 -0
  320. {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm1.py +75 -75
  321. {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm2.py +87 -87
  322. {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm3.py +105 -105
  323. {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm4.py +126 -126
  324. {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm5.py +166 -166
  325. {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_ibm_model.py +270 -270
  326. nltk-3.3/nltk/test/unit/translate/test_nist.py +37 -0
  327. {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/test_stack_decoder.py +300 -300
  328. {nltk-3.2 → nltk-3.3}/nltk/test/unit/utils.py +41 -41
  329. {nltk-3.2 → nltk-3.3}/nltk/test/util.doctest +48 -48
  330. {nltk-3.2 → nltk-3.3}/nltk/test/wordnet.doctest +595 -581
  331. {nltk-3.2 → nltk-3.3}/nltk/test/wordnet_fixt.py +7 -6
  332. {nltk-3.2 → nltk-3.3}/nltk/test/wordnet_lch.doctest +53 -53
  333. {nltk-3.2 → nltk-3.3}/nltk/test/wsd.doctest +68 -68
  334. {nltk-3.2 → nltk-3.3}/nltk/text.py +663 -610
  335. {nltk-3.2 → nltk-3.3}/nltk/tgrep.py +938 -937
  336. {nltk-3.2 → nltk-3.3}/nltk/tokenize/__init__.py +130 -107
  337. {nltk-3.2 → nltk-3.3}/nltk/tokenize/api.py +77 -78
  338. {nltk-3.2 → nltk-3.3}/nltk/tokenize/casual.py +343 -342
  339. {nltk-3.2 → nltk-3.3}/nltk/tokenize/mwe.py +120 -120
  340. nltk-3.3/nltk/tokenize/nist.py +173 -0
  341. {nltk-3.2 → nltk-3.3}/nltk/tokenize/punkt.py +1620 -1601
  342. {nltk-3.2 → nltk-3.3}/nltk/tokenize/regexp.py +208 -202
  343. nltk-3.3/nltk/tokenize/repp.py +151 -0
  344. {nltk-3.2 → nltk-3.3}/nltk/tokenize/sexpr.py +140 -142
  345. {nltk-3.2 → nltk-3.3}/nltk/tokenize/simple.py +138 -136
  346. {nltk-3.2 → nltk-3.3}/nltk/tokenize/stanford.py +117 -113
  347. nltk-3.3/nltk/tokenize/stanford_segmenter.py +246 -0
  348. {nltk-3.2 → nltk-3.3}/nltk/tokenize/texttiling.py +458 -460
  349. nltk-3.3/nltk/tokenize/toktok.py +155 -0
  350. nltk-3.3/nltk/tokenize/treebank.py +363 -0
  351. nltk-3.3/nltk/tokenize/util.py +261 -0
  352. {nltk-3.2 → nltk-3.3}/nltk/toolbox.py +499 -497
  353. {nltk-3.2 → nltk-3.3}/nltk/translate/__init__.py +24 -24
  354. {nltk-3.2 → nltk-3.3}/nltk/translate/api.py +331 -321
  355. {nltk-3.2 → nltk-3.3}/nltk/translate/bleu_score.py +615 -556
  356. nltk-3.3/nltk/translate/chrf_score.py +137 -0
  357. {nltk-3.2 → nltk-3.3}/nltk/translate/gale_church.py +248 -235
  358. {nltk-3.2 → nltk-3.3}/nltk/translate/gdfa.py +135 -131
  359. nltk-3.3/nltk/translate/gleu_score.py +193 -0
  360. {nltk-3.2 → nltk-3.3}/nltk/translate/ibm1.py +250 -241
  361. {nltk-3.2 → nltk-3.3}/nltk/translate/ibm2.py +308 -308
  362. {nltk-3.2 → nltk-3.3}/nltk/translate/ibm3.py +337 -337
  363. {nltk-3.2 → nltk-3.3}/nltk/translate/ibm4.py +467 -467
  364. {nltk-3.2 → nltk-3.3}/nltk/translate/ibm5.py +639 -639
  365. {nltk-3.2 → nltk-3.3}/nltk/translate/ibm_model.py +536 -533
  366. {nltk-3.2 → nltk-3.3}/nltk/translate/metrics.py +40 -40
  367. nltk-3.3/nltk/translate/nist_score.py +178 -0
  368. {nltk-3.2 → nltk-3.3}/nltk/translate/phrase_based.py +175 -175
  369. {nltk-3.2 → nltk-3.3}/nltk/translate/ribes_score.py +325 -325
  370. {nltk-3.2 → nltk-3.3}/nltk/translate/stack_decoder.py +499 -495
  371. {nltk-3.2 → nltk-3.3}/nltk/tree.py +1605 -1603
  372. {nltk-3.2 → nltk-3.3}/nltk/treeprettyprinter.py +564 -564
  373. {nltk-3.2 → nltk-3.3}/nltk/treetransforms.py +309 -309
  374. {nltk-3.2 → nltk-3.3}/nltk/twitter/__init__.py +28 -28
  375. {nltk-3.2 → nltk-3.3}/nltk/twitter/api.py +141 -136
  376. {nltk-3.2 → nltk-3.3}/nltk/twitter/common.py +264 -258
  377. {nltk-3.2 → nltk-3.3}/nltk/twitter/twitter_demo.py +288 -288
  378. {nltk-3.2 → nltk-3.3}/nltk/twitter/twitterclient.py +530 -530
  379. {nltk-3.2 → nltk-3.3}/nltk/twitter/util.py +147 -146
  380. nltk-3.3/nltk/util.py +751 -0
  381. {nltk-3.2 → nltk-3.3}/nltk/wsd.py +53 -53
  382. {nltk-3.2 → nltk-3.3/nltk.egg-info}/PKG-INFO +13 -5
  383. {nltk-3.2 → nltk-3.3}/nltk.egg-info/SOURCES.txt +26 -3
  384. nltk-3.3/nltk.egg-info/requires.txt +31 -0
  385. {nltk-3.2 → nltk-3.3}/setup.cfg +0 -1
  386. {nltk-3.2 → nltk-3.3}/setup.py +102 -75
  387. nltk-3.2/nltk/VERSION +0 -1
  388. nltk-3.2/nltk/compat.py +0 -680
  389. nltk-3.2/nltk/corpus/reader/pl196x.py +0 -278
  390. nltk-3.2/nltk/corpus/reader/verbnet.py +0 -408
  391. nltk-3.2/nltk/corpus/reader/wordlist.py +0 -37
  392. nltk-3.2/nltk/sentiment/vader_lexicon.txt +0 -7517
  393. nltk-3.2/nltk/six.py +0 -868
  394. nltk-3.2/nltk/stem/porter.py +0 -692
  395. nltk-3.2/nltk/test/unit/test_2x_compat.py +0 -26
  396. nltk-3.2/nltk/test/unit/test_stem.py +0 -36
  397. nltk-3.2/nltk/test/unit/test_tokenize.py +0 -23
  398. nltk-3.2/nltk/test/unit/translate/test_bleu.py +0 -84
  399. nltk-3.2/nltk/tokenize/treebank.py +0 -124
  400. nltk-3.2/nltk/tokenize/util.py +0 -90
  401. nltk-3.2/nltk/util.py +0 -1364
  402. {nltk-3.2 → nltk-3.3}/nltk/test/unit/__init__.py +0 -0
  403. {nltk-3.2 → nltk-3.3}/nltk/test/unit/translate/__init__.py +0 -0
  404. {nltk-3.2 → nltk-3.3}/nltk.egg-info/dependency_links.txt +0 -0
  405. {nltk-3.2 → nltk-3.3}/nltk.egg-info/not-zip-safe +0 -0
  406. {nltk-3.2 → nltk-3.3}/nltk.egg-info/top_level.txt +0 -0
@@ -1,1681 +1,1682 @@
1
- # -*- coding: utf-8 -*-
2
- # Natural Language Toolkit: A Chart Parser
3
- #
4
- # Copyright (C) 2001-2015 NLTK Project
5
- # Author: Edward Loper <edloper@gmail.com>
6
- # Steven Bird <stevenbird1@gmail.com>
7
- # Jean Mark Gawron <gawron@mail.sdsu.edu>
8
- # Peter Ljunglöf <peter.ljunglof@heatherleaf.se>
9
- # URL: <http://nltk.org/>
10
- # For license information, see LICENSE.TXT
11
-
12
- """
13
- Data classes and parser implementations for "chart parsers", which
14
- use dynamic programming to efficiently parse a text. A chart
15
- parser derives parse trees for a text by iteratively adding "edges"
16
- to a "chart." Each edge represents a hypothesis about the tree
17
- structure for a subsequence of the text. The chart is a
18
- "blackboard" for composing and combining these hypotheses.
19
-
20
- When a chart parser begins parsing a text, it creates a new (empty)
21
- chart, spanning the text. It then incrementally adds new edges to the
22
- chart. A set of "chart rules" specifies the conditions under which
23
- new edges should be added to the chart. Once the chart reaches a
24
- stage where none of the chart rules adds any new edges, parsing is
25
- complete.
26
-
27
- Charts are encoded with the ``Chart`` class, and edges are encoded with
28
- the ``TreeEdge`` and ``LeafEdge`` classes. The chart parser module
29
- defines three chart parsers:
30
-
31
- - ``ChartParser`` is a simple and flexible chart parser. Given a
32
- set of chart rules, it will apply those rules to the chart until
33
- no more edges are added.
34
-
35
- - ``SteppingChartParser`` is a subclass of ``ChartParser`` that can
36
- be used to step through the parsing process.
37
- """
38
- from __future__ import print_function, division, unicode_literals
39
-
40
- import itertools
41
- import re
42
- import warnings
43
-
44
- from nltk import compat
45
- from nltk.tree import Tree
46
- from nltk.grammar import PCFG, is_nonterminal, is_terminal
47
- from nltk.util import OrderedDict
48
- from nltk.internals import raise_unorderable_types
49
- from nltk.compat import (total_ordering, python_2_unicode_compatible,
50
- unicode_repr)
51
-
52
- from nltk.parse.api import ParserI
53
-
54
-
55
- ########################################################################
56
- ## Edges
57
- ########################################################################
58
-
59
- @total_ordering
60
- class EdgeI(object):
61
- """
62
- A hypothesis about the structure of part of a sentence.
63
- Each edge records the fact that a structure is (partially)
64
- consistent with the sentence. An edge contains:
65
-
66
- - A span, indicating what part of the sentence is
67
- consistent with the hypothesized structure.
68
- - A left-hand side, specifying what kind of structure is
69
- hypothesized.
70
- - A right-hand side, specifying the contents of the
71
- hypothesized structure.
72
- - A dot position, indicating how much of the hypothesized
73
- structure is consistent with the sentence.
74
-
75
- Every edge is either complete or incomplete:
76
-
77
- - An edge is complete if its structure is fully consistent
78
- with the sentence.
79
- - An edge is incomplete if its structure is partially
80
- consistent with the sentence. For every incomplete edge, the
81
- span specifies a possible prefix for the edge's structure.
82
-
83
- There are two kinds of edge:
84
-
85
- - A ``TreeEdge`` records which trees have been found to
86
- be (partially) consistent with the text.
87
- - A ``LeafEdge`` records the tokens occurring in the text.
88
-
89
- The ``EdgeI`` interface provides a common interface to both types
90
- of edge, allowing chart parsers to treat them in a uniform manner.
91
- """
92
- def __init__(self):
93
- if self.__class__ == EdgeI:
94
- raise TypeError('Edge is an abstract interface')
95
-
96
- #////////////////////////////////////////////////////////////
97
- # Span
98
- #////////////////////////////////////////////////////////////
99
-
100
- def span(self):
101
- """
102
- Return a tuple ``(s, e)``, where ``tokens[s:e]`` is the
103
- portion of the sentence that is consistent with this
104
- edge's structure.
105
-
106
- :rtype: tuple(int, int)
107
- """
108
- raise NotImplementedError()
109
-
110
- def start(self):
111
- """
112
- Return the start index of this edge's span.
113
-
114
- :rtype: int
115
- """
116
- raise NotImplementedError()
117
-
118
- def end(self):
119
- """
120
- Return the end index of this edge's span.
121
-
122
- :rtype: int
123
- """
124
- raise NotImplementedError()
125
-
126
- def length(self):
127
- """
128
- Return the length of this edge's span.
129
-
130
- :rtype: int
131
- """
132
- raise NotImplementedError()
133
-
134
- #////////////////////////////////////////////////////////////
135
- # Left Hand Side
136
- #////////////////////////////////////////////////////////////
137
-
138
- def lhs(self):
139
- """
140
- Return this edge's left-hand side, which specifies what kind
141
- of structure is hypothesized by this edge.
142
-
143
- :see: ``TreeEdge`` and ``LeafEdge`` for a description of
144
- the left-hand side values for each edge type.
145
- """
146
- raise NotImplementedError()
147
-
148
- #////////////////////////////////////////////////////////////
149
- # Right Hand Side
150
- #////////////////////////////////////////////////////////////
151
-
152
- def rhs(self):
153
- """
154
- Return this edge's right-hand side, which specifies
155
- the content of the structure hypothesized by this edge.
156
-
157
- :see: ``TreeEdge`` and ``LeafEdge`` for a description of
158
- the right-hand side values for each edge type.
159
- """
160
- raise NotImplementedError()
161
-
162
- def dot(self):
163
- """
164
- Return this edge's dot position, which indicates how much of
165
- the hypothesized structure is consistent with the
166
- sentence. In particular, ``self.rhs[:dot]`` is consistent
167
- with ``tokens[self.start():self.end()]``.
168
-
169
- :rtype: int
170
- """
171
- raise NotImplementedError()
172
-
173
- def nextsym(self):
174
- """
175
- Return the element of this edge's right-hand side that
176
- immediately follows its dot.
177
-
178
- :rtype: Nonterminal or terminal or None
179
- """
180
- raise NotImplementedError()
181
-
182
- def is_complete(self):
183
- """
184
- Return True if this edge's structure is fully consistent
185
- with the text.
186
-
187
- :rtype: bool
188
- """
189
- raise NotImplementedError()
190
-
191
- def is_incomplete(self):
192
- """
193
- Return True if this edge's structure is partially consistent
194
- with the text.
195
-
196
- :rtype: bool
197
- """
198
- raise NotImplementedError()
199
-
200
- #////////////////////////////////////////////////////////////
201
- # Comparisons & hashing
202
- #////////////////////////////////////////////////////////////
203
-
204
- def __eq__(self, other):
205
- return (self.__class__ is other.__class__ and
206
- self._comparison_key == other._comparison_key)
207
-
208
- def __ne__(self, other):
209
- return not self == other
210
-
211
- def __lt__(self, other):
212
- if not isinstance(other, EdgeI):
213
- raise_unorderable_types("<", self, other)
214
- if self.__class__ is other.__class__:
215
- return self._comparison_key < other._comparison_key
216
- else:
217
- return self.__class__.__name__ < other.__class__.__name__
218
-
219
- def __hash__(self):
220
- try:
221
- return self._hash
222
- except AttributeError:
223
- self._hash = hash(self._comparison_key)
224
- return self._hash
225
-
226
-
227
- @python_2_unicode_compatible
228
- class TreeEdge(EdgeI):
229
- """
230
- An edge that records the fact that a tree is (partially)
231
- consistent with the sentence. A tree edge consists of:
232
-
233
- - A span, indicating what part of the sentence is
234
- consistent with the hypothesized tree.
235
- - A left-hand side, specifying the hypothesized tree's node
236
- value.
237
- - A right-hand side, specifying the hypothesized tree's
238
- children. Each element of the right-hand side is either a
239
- terminal, specifying a token with that terminal as its leaf
240
- value; or a nonterminal, specifying a subtree with that
241
- nonterminal's symbol as its node value.
242
- - A dot position, indicating which children are consistent
243
- with part of the sentence. In particular, if ``dot`` is the
244
- dot position, ``rhs`` is the right-hand size, ``(start,end)``
245
- is the span, and ``sentence`` is the list of tokens in the
246
- sentence, then ``tokens[start:end]`` can be spanned by the
247
- children specified by ``rhs[:dot]``.
248
-
249
- For more information about edges, see the ``EdgeI`` interface.
250
- """
251
- def __init__(self, span, lhs, rhs, dot=0):
252
- """
253
- Construct a new ``TreeEdge``.
254
-
255
- :type span: tuple(int, int)
256
- :param span: A tuple ``(s, e)``, where ``tokens[s:e]`` is the
257
- portion of the sentence that is consistent with the new
258
- edge's structure.
259
- :type lhs: Nonterminal
260
- :param lhs: The new edge's left-hand side, specifying the
261
- hypothesized tree's node value.
262
- :type rhs: list(Nonterminal and str)
263
- :param rhs: The new edge's right-hand side, specifying the
264
- hypothesized tree's children.
265
- :type dot: int
266
- :param dot: The position of the new edge's dot. This position
267
- specifies what prefix of the production's right hand side
268
- is consistent with the text. In particular, if
269
- ``sentence`` is the list of tokens in the sentence, then
270
- ``okens[span[0]:span[1]]`` can be spanned by the
271
- children specified by ``rhs[:dot]``.
272
- """
273
- self._span = span
274
- self._lhs = lhs
275
- rhs = tuple(rhs)
276
- self._rhs = rhs
277
- self._dot = dot
278
- self._comparison_key = (span, lhs, rhs, dot)
279
-
280
- @staticmethod
281
- def from_production(production, index):
282
- """
283
- Return a new ``TreeEdge`` formed from the given production.
284
- The new edge's left-hand side and right-hand side will
285
- be taken from ``production``; its span will be
286
- ``(index,index)``; and its dot position will be ``0``.
287
-
288
- :rtype: TreeEdge
289
- """
290
- return TreeEdge(span=(index, index), lhs=production.lhs(),
291
- rhs=production.rhs(), dot=0)
292
-
293
- def move_dot_forward(self, new_end):
294
- """
295
- Return a new ``TreeEdge`` formed from this edge.
296
- The new edge's dot position is increased by ``1``,
297
- and its end index will be replaced by ``new_end``.
298
-
299
- :param new_end: The new end index.
300
- :type new_end: int
301
- :rtype: TreeEdge
302
- """
303
- return TreeEdge(span=(self._span[0], new_end),
304
- lhs=self._lhs, rhs=self._rhs,
305
- dot=self._dot+1)
306
-
307
- # Accessors
308
- def lhs(self): return self._lhs
309
- def span(self): return self._span
310
- def start(self): return self._span[0]
311
- def end(self): return self._span[1]
312
- def length(self): return self._span[1] - self._span[0]
313
- def rhs(self): return self._rhs
314
- def dot(self): return self._dot
315
- def is_complete(self): return self._dot == len(self._rhs)
316
- def is_incomplete(self): return self._dot != len(self._rhs)
317
- def nextsym(self):
318
- if self._dot >= len(self._rhs): return None
319
- else: return self._rhs[self._dot]
320
-
321
- # String representation
322
- def __str__(self):
323
- str = '[%s:%s] ' % (self._span[0], self._span[1])
324
- str += '%-2r ->' % (self._lhs,)
325
-
326
- for i in range(len(self._rhs)):
327
- if i == self._dot: str += ' *'
328
- str += ' %s' % unicode_repr(self._rhs[i])
329
- if len(self._rhs) == self._dot: str += ' *'
330
- return str
331
-
332
- def __repr__(self):
333
- return '[Edge: %s]' % self
334
-
335
-
336
- @python_2_unicode_compatible
337
- class LeafEdge(EdgeI):
338
- """
339
- An edge that records the fact that a leaf value is consistent with
340
- a word in the sentence. A leaf edge consists of:
341
-
342
- - An index, indicating the position of the word.
343
- - A leaf, specifying the word's content.
344
-
345
- A leaf edge's left-hand side is its leaf value, and its right hand
346
- side is ``()``. Its span is ``[index, index+1]``, and its dot
347
- position is ``0``.
348
- """
349
- def __init__(self, leaf, index):
350
- """
351
- Construct a new ``LeafEdge``.
352
-
353
- :param leaf: The new edge's leaf value, specifying the word
354
- that is recorded by this edge.
355
- :param index: The new edge's index, specifying the position of
356
- the word that is recorded by this edge.
357
- """
358
- self._leaf = leaf
359
- self._index = index
360
- self._comparison_key = (leaf, index)
361
-
362
- # Accessors
363
- def lhs(self): return self._leaf
364
- def span(self): return (self._index, self._index+1)
365
- def start(self): return self._index
366
- def end(self): return self._index+1
367
- def length(self): return 1
368
- def rhs(self): return ()
369
- def dot(self): return 0
370
- def is_complete(self): return True
371
- def is_incomplete(self): return False
372
- def nextsym(self): return None
373
-
374
- # String representations
375
- def __str__(self):
376
- return '[%s:%s] %s' % (self._index, self._index+1, unicode_repr(self._leaf))
377
- def __repr__(self):
378
- return '[Edge: %s]' % (self)
379
-
380
- ########################################################################
381
- ## Chart
382
- ########################################################################
383
-
384
- class Chart(object):
385
- """
386
- A blackboard for hypotheses about the syntactic constituents of a
387
- sentence. A chart contains a set of edges, and each edge encodes
388
- a single hypothesis about the structure of some portion of the
389
- sentence.
390
-
391
- The ``select`` method can be used to select a specific collection
392
- of edges. For example ``chart.select(is_complete=True, start=0)``
393
- yields all complete edges whose start indices are 0. To ensure
394
- the efficiency of these selection operations, ``Chart`` dynamically
395
- creates and maintains an index for each set of attributes that
396
- have been selected on.
397
-
398
- In order to reconstruct the trees that are represented by an edge,
399
- the chart associates each edge with a set of child pointer lists.
400
- A child pointer list is a list of the edges that license an
401
- edge's right-hand side.
402
-
403
- :ivar _tokens: The sentence that the chart covers.
404
- :ivar _num_leaves: The number of tokens.
405
- :ivar _edges: A list of the edges in the chart
406
- :ivar _edge_to_cpls: A dictionary mapping each edge to a set
407
- of child pointer lists that are associated with that edge.
408
- :ivar _indexes: A dictionary mapping tuples of edge attributes
409
- to indices, where each index maps the corresponding edge
410
- attribute values to lists of edges.
411
- """
412
- def __init__(self, tokens):
413
- """
414
- Construct a new chart. The chart is initialized with the
415
- leaf edges corresponding to the terminal leaves.
416
-
417
- :type tokens: list
418
- :param tokens: The sentence that this chart will be used to parse.
419
- """
420
- # Record the sentence token and the sentence length.
421
- self._tokens = tuple(tokens)
422
- self._num_leaves = len(self._tokens)
423
-
424
- # Initialise the chart.
425
- self.initialize()
426
-
427
- def initialize(self):
428
- """
429
- Clear the chart.
430
- """
431
- # A list of edges contained in this chart.
432
- self._edges = []
433
-
434
- # The set of child pointer lists associated with each edge.
435
- self._edge_to_cpls = {}
436
-
437
- # Indexes mapping attribute values to lists of edges
438
- # (used by select()).
439
- self._indexes = {}
440
-
441
- #////////////////////////////////////////////////////////////
442
- # Sentence Access
443
- #////////////////////////////////////////////////////////////
444
-
445
- def num_leaves(self):
446
- """
447
- Return the number of words in this chart's sentence.
448
-
449
- :rtype: int
450
- """
451
- return self._num_leaves
452
-
453
- def leaf(self, index):
454
- """
455
- Return the leaf value of the word at the given index.
456
-
457
- :rtype: str
458
- """
459
- return self._tokens[index]
460
-
461
- def leaves(self):
462
- """
463
- Return a list of the leaf values of each word in the
464
- chart's sentence.
465
-
466
- :rtype: list(str)
467
- """
468
- return self._tokens
469
-
470
- #////////////////////////////////////////////////////////////
471
- # Edge access
472
- #////////////////////////////////////////////////////////////
473
-
474
- def edges(self):
475
- """
476
- Return a list of all edges in this chart. New edges
477
- that are added to the chart after the call to edges()
478
- will *not* be contained in this list.
479
-
480
- :rtype: list(EdgeI)
481
- :see: ``iteredges``, ``select``
482
- """
483
- return self._edges[:]
484
-
485
- def iteredges(self):
486
- """
487
- Return an iterator over the edges in this chart. It is
488
- not guaranteed that new edges which are added to the
489
- chart before the iterator is exhausted will also be generated.
490
-
491
- :rtype: iter(EdgeI)
492
- :see: ``edges``, ``select``
493
- """
494
- return iter(self._edges)
495
-
496
- # Iterating over the chart yields its edges.
497
- __iter__ = iteredges
498
-
499
- def num_edges(self):
500
- """
501
- Return the number of edges contained in this chart.
502
-
503
- :rtype: int
504
- """
505
- return len(self._edge_to_cpls)
506
-
507
- def select(self, **restrictions):
508
- """
509
- Return an iterator over the edges in this chart. Any
510
- new edges that are added to the chart before the iterator
511
- is exahusted will also be generated. ``restrictions``
512
- can be used to restrict the set of edges that will be
513
- generated.
514
-
515
- :param span: Only generate edges ``e`` where ``e.span()==span``
516
- :param start: Only generate edges ``e`` where ``e.start()==start``
517
- :param end: Only generate edges ``e`` where ``e.end()==end``
518
- :param length: Only generate edges ``e`` where ``e.length()==length``
519
- :param lhs: Only generate edges ``e`` where ``e.lhs()==lhs``
520
- :param rhs: Only generate edges ``e`` where ``e.rhs()==rhs``
521
- :param nextsym: Only generate edges ``e`` where
522
- ``e.nextsym()==nextsym``
523
- :param dot: Only generate edges ``e`` where ``e.dot()==dot``
524
- :param is_complete: Only generate edges ``e`` where
525
- ``e.is_complete()==is_complete``
526
- :param is_incomplete: Only generate edges ``e`` where
527
- ``e.is_incomplete()==is_incomplete``
528
- :rtype: iter(EdgeI)
529
- """
530
- # If there are no restrictions, then return all edges.
531
- if restrictions=={}: return iter(self._edges)
532
-
533
- # Find the index corresponding to the given restrictions.
534
- restr_keys = sorted(restrictions.keys())
535
- restr_keys = tuple(restr_keys)
536
-
537
- # If it doesn't exist, then create it.
538
- if restr_keys not in self._indexes:
539
- self._add_index(restr_keys)
540
-
541
- vals = tuple(restrictions[key] for key in restr_keys)
542
- return iter(self._indexes[restr_keys].get(vals, []))
543
-
544
- def _add_index(self, restr_keys):
545
- """
546
- A helper function for ``select``, which creates a new index for
547
- a given set of attributes (aka restriction keys).
548
- """
549
- # Make sure it's a valid index.
550
- for key in restr_keys:
551
- if not hasattr(EdgeI, key):
552
- raise ValueError('Bad restriction: %s' % key)
553
-
554
- # Create the index.
555
- index = self._indexes[restr_keys] = {}
556
-
557
- # Add all existing edges to the index.
558
- for edge in self._edges:
559
- vals = tuple(getattr(edge, key)() for key in restr_keys)
560
- index.setdefault(vals, []).append(edge)
561
-
562
- def _register_with_indexes(self, edge):
563
- """
564
- A helper function for ``insert``, which registers the new
565
- edge with all existing indexes.
566
- """
567
- for (restr_keys, index) in self._indexes.items():
568
- vals = tuple(getattr(edge, key)() for key in restr_keys)
569
- index.setdefault(vals, []).append(edge)
570
-
571
- #////////////////////////////////////////////////////////////
572
- # Edge Insertion
573
- #////////////////////////////////////////////////////////////
574
-
575
- def insert_with_backpointer(self, new_edge, previous_edge, child_edge):
576
- """
577
- Add a new edge to the chart, using a pointer to the previous edge.
578
- """
579
- cpls = self.child_pointer_lists(previous_edge)
580
- new_cpls = [cpl+(child_edge,) for cpl in cpls]
581
- return self.insert(new_edge, *new_cpls)
582
-
583
- def insert(self, edge, *child_pointer_lists):
584
- """
585
- Add a new edge to the chart, and return True if this operation
586
- modified the chart. In particular, return true iff the chart
587
- did not already contain ``edge``, or if it did not already associate
588
- ``child_pointer_lists`` with ``edge``.
589
-
590
- :type edge: EdgeI
591
- :param edge: The new edge
592
- :type child_pointer_lists: sequence of tuple(EdgeI)
593
- :param child_pointer_lists: A sequence of lists of the edges that
594
- were used to form this edge. This list is used to reconstruct
595
- the trees (or partial trees) that are associated with ``edge``.
596
- :rtype: bool
597
- """
598
- # Is it a new edge?
599
- if edge not in self._edge_to_cpls:
600
- # Add it to the list of edges.
601
- self._append_edge(edge)
602
- # Register with indexes.
603
- self._register_with_indexes(edge)
604
-
605
- # Get the set of child pointer lists for this edge.
606
- cpls = self._edge_to_cpls.setdefault(edge, OrderedDict())
607
- chart_was_modified = False
608
- for child_pointer_list in child_pointer_lists:
609
- child_pointer_list = tuple(child_pointer_list)
610
- if child_pointer_list not in cpls:
611
- # It's a new CPL; register it, and return true.
612
- cpls[child_pointer_list] = True
613
- chart_was_modified = True
614
- return chart_was_modified
615
-
616
- def _append_edge(self, edge):
617
- self._edges.append(edge)
618
-
619
- #////////////////////////////////////////////////////////////
620
- # Tree extraction & child pointer lists
621
- #////////////////////////////////////////////////////////////
622
-
623
- def parses(self, root, tree_class=Tree):
624
- """
625
- Return an iterator of the complete tree structures that span
626
- the entire chart, and whose root node is ``root``.
627
- """
628
- for edge in self.select(start=0, end=self._num_leaves, lhs=root):
629
- for tree in self.trees(edge, tree_class=tree_class, complete=True):
630
- yield tree
631
-
632
- def trees(self, edge, tree_class=Tree, complete=False):
633
- """
634
- Return an iterator of the tree structures that are associated
635
- with ``edge``.
636
-
637
- If ``edge`` is incomplete, then the unexpanded children will be
638
- encoded as childless subtrees, whose node value is the
639
- corresponding terminal or nonterminal.
640
-
641
- :rtype: list(Tree)
642
- :note: If two trees share a common subtree, then the same
643
- Tree may be used to encode that subtree in
644
- both trees. If you need to eliminate this subtree
645
- sharing, then create a deep copy of each tree.
646
- """
647
- return iter(self._trees(edge, complete, memo={}, tree_class=tree_class))
648
-
649
- def _trees(self, edge, complete, memo, tree_class):
650
- """
651
- A helper function for ``trees``.
652
-
653
- :param memo: A dictionary used to record the trees that we've
654
- generated for each edge, so that when we see an edge more
655
- than once, we can reuse the same trees.
656
- """
657
- # If we've seen this edge before, then reuse our old answer.
658
- if edge in memo:
659
- return memo[edge]
660
-
661
- # when we're reading trees off the chart, don't use incomplete edges
662
- if complete and edge.is_incomplete():
663
- return []
664
-
665
- # Leaf edges.
666
- if isinstance(edge, LeafEdge):
667
- leaf = self._tokens[edge.start()]
668
- memo[edge] = [leaf]
669
- return [leaf]
670
-
671
- # Until we're done computing the trees for edge, set
672
- # memo[edge] to be empty. This has the effect of filtering
673
- # out any cyclic trees (i.e., trees that contain themselves as
674
- # descendants), because if we reach this edge via a cycle,
675
- # then it will appear that the edge doesn't generate any trees.
676
- memo[edge] = []
677
- trees = []
678
- lhs = edge.lhs().symbol()
679
-
680
- # Each child pointer list can be used to form trees.
681
- for cpl in self.child_pointer_lists(edge):
682
- # Get the set of child choices for each child pointer.
683
- # child_choices[i] is the set of choices for the tree's
684
- # ith child.
685
- child_choices = [self._trees(cp, complete, memo, tree_class)
686
- for cp in cpl]
687
-
688
- # For each combination of children, add a tree.
689
- for children in itertools.product(*child_choices):
690
- trees.append(tree_class(lhs, children))
691
-
692
- # If the edge is incomplete, then extend it with "partial trees":
693
- if edge.is_incomplete():
694
- unexpanded = [tree_class(elt,[])
695
- for elt in edge.rhs()[edge.dot():]]
696
- for tree in trees:
697
- tree.extend(unexpanded)
698
-
699
- # Update the memoization dictionary.
700
- memo[edge] = trees
701
-
702
- # Return the list of trees.
703
- return trees
704
-
705
- def child_pointer_lists(self, edge):
706
- """
707
- Return the set of child pointer lists for the given edge.
708
- Each child pointer list is a list of edges that have
709
- been used to form this edge.
710
-
711
- :rtype: list(list(EdgeI))
712
- """
713
- # Make a copy, in case they modify it.
714
- return self._edge_to_cpls.get(edge, {}).keys()
715
-
716
- #////////////////////////////////////////////////////////////
717
- # Display
718
- #////////////////////////////////////////////////////////////
719
- def pretty_format_edge(self, edge, width=None):
720
- """
721
- Return a pretty-printed string representation of a given edge
722
- in this chart.
723
-
724
- :rtype: str
725
- :param width: The number of characters allotted to each
726
- index in the sentence.
727
- """
728
- if width is None: width = 50 // (self.num_leaves()+1)
729
- (start, end) = (edge.start(), edge.end())
730
-
731
- str = '|' + ('.'+' '*(width-1))*start
732
-
733
- # Zero-width edges are "#" if complete, ">" if incomplete
734
- if start == end:
735
- if edge.is_complete(): str += '#'
736
- else: str += '>'
737
-
738
- # Spanning complete edges are "[===]"; Other edges are
739
- # "[---]" if complete, "[--->" if incomplete
740
- elif edge.is_complete() and edge.span() == (0,self._num_leaves):
741
- str += '['+('='*width)*(end-start-1) + '='*(width-1)+']'
742
- elif edge.is_complete():
743
- str += '['+('-'*width)*(end-start-1) + '-'*(width-1)+']'
744
- else:
745
- str += '['+('-'*width)*(end-start-1) + '-'*(width-1)+'>'
746
-
747
- str += (' '*(width-1)+'.')*(self._num_leaves-end)
748
- return str + '| %s' % edge
749
-
750
- def pretty_format_leaves(self, width=None):
751
- """
752
- Return a pretty-printed string representation of this
753
- chart's leaves. This string can be used as a header
754
- for calls to ``pretty_format_edge``.
755
- """
756
- if width is None: width = 50 // (self.num_leaves()+1)
757
-
758
- if self._tokens is not None and width>1:
759
- header = '|.'
760
- for tok in self._tokens:
761
- header += tok[:width-1].center(width-1)+'.'
762
- header += '|'
763
- else:
764
- header = ''
765
-
766
- return header
767
-
768
- def pretty_format(self, width=None):
769
- """
770
- Return a pretty-printed string representation of this chart.
771
-
772
- :param width: The number of characters allotted to each
773
- index in the sentence.
774
- :rtype: str
775
- """
776
- if width is None: width = 50 // (self.num_leaves()+1)
777
- # sort edges: primary key=length, secondary key=start index.
778
- # (and filter out the token edges)
779
- edges = sorted([(e.length(), e.start(), e) for e in self])
780
- edges = [e for (_,_,e) in edges]
781
-
782
- return (self.pretty_format_leaves(width) + '\n' +
783
- '\n'.join(self.pretty_format_edge(edge, width) for edge in edges))
784
-
785
- #////////////////////////////////////////////////////////////
786
- # Display: Dot (AT&T Graphviz)
787
- #////////////////////////////////////////////////////////////
788
-
789
- def dot_digraph(self):
790
- # Header
791
- s = 'digraph nltk_chart {\n'
792
- #s += ' size="5,5";\n'
793
- s += ' rankdir=LR;\n'
794
- s += ' node [height=0.1,width=0.1];\n'
795
- s += ' node [style=filled, color="lightgray"];\n'
796
-
797
- # Set up the nodes
798
- for y in range(self.num_edges(), -1, -1):
799
- if y == 0:
800
- s += ' node [style=filled, color="black"];\n'
801
- for x in range(self.num_leaves()+1):
802
- if y == 0 or (x <= self._edges[y-1].start() or
803
- x >= self._edges[y-1].end()):
804
- s += ' %04d.%04d [label=""];\n' % (x,y)
805
-
806
- # Add a spacer
807
- s += ' x [style=invis]; x->0000.0000 [style=invis];\n'
808
-
809
- # Declare ranks.
810
- for x in range(self.num_leaves()+1):
811
- s += ' {rank=same;'
812
- for y in range(self.num_edges()+1):
813
- if y == 0 or (x <= self._edges[y-1].start() or
814
- x >= self._edges[y-1].end()):
815
- s += ' %04d.%04d' % (x,y)
816
- s += '}\n'
817
-
818
- # Add the leaves
819
- s += ' edge [style=invis, weight=100];\n'
820
- s += ' node [shape=plaintext]\n'
821
- s += ' 0000.0000'
822
- for x in range(self.num_leaves()):
823
- s += '->%s->%04d.0000' % (self.leaf(x), x+1)
824
- s += ';\n\n'
825
-
826
- # Add the edges
827
- s += ' edge [style=solid, weight=1];\n'
828
- for y, edge in enumerate(self):
829
- for x in range(edge.start()):
830
- s += (' %04d.%04d -> %04d.%04d [style="invis"];\n' %
831
- (x, y+1, x+1, y+1))
832
- s += (' %04d.%04d -> %04d.%04d [label="%s"];\n' %
833
- (edge.start(), y+1, edge.end(), y+1, edge))
834
- for x in range(edge.end(), self.num_leaves()):
835
- s += (' %04d.%04d -> %04d.%04d [style="invis"];\n' %
836
- (x, y+1, x+1, y+1))
837
- s += '}\n'
838
- return s
839
-
840
- ########################################################################
841
- ## Chart Rules
842
- ########################################################################
843
-
844
- class ChartRuleI(object):
845
- """
846
- A rule that specifies what new edges are licensed by any given set
847
- of existing edges. Each chart rule expects a fixed number of
848
- edges, as indicated by the class variable ``NUM_EDGES``. In
849
- particular:
850
-
851
- - A chart rule with ``NUM_EDGES=0`` specifies what new edges are
852
- licensed, regardless of existing edges.
853
- - A chart rule with ``NUM_EDGES=1`` specifies what new edges are
854
- licensed by a single existing edge.
855
- - A chart rule with ``NUM_EDGES=2`` specifies what new edges are
856
- licensed by a pair of existing edges.
857
-
858
- :type NUM_EDGES: int
859
- :cvar NUM_EDGES: The number of existing edges that this rule uses
860
- to license new edges. Typically, this number ranges from zero
861
- to two.
862
- """
863
- def apply(self, chart, grammar, *edges):
864
- """
865
- Return a generator that will add edges licensed by this rule
866
- and the given edges to the chart, one at a time. Each
867
- time the generator is resumed, it will either add a new
868
- edge and yield that edge; or return.
869
-
870
- :type edges: list(EdgeI)
871
- :param edges: A set of existing edges. The number of edges
872
- that should be passed to ``apply()`` is specified by the
873
- ``NUM_EDGES`` class variable.
874
- :rtype: iter(EdgeI)
875
- """
876
- raise NotImplementedError()
877
-
878
- def apply_everywhere(self, chart, grammar):
879
- """
880
- Return a generator that will add all edges licensed by
881
- this rule, given the edges that are currently in the
882
- chart, one at a time. Each time the generator is resumed,
883
- it will either add a new edge and yield that edge; or return.
884
-
885
- :rtype: iter(EdgeI)
886
- """
887
- raise NotImplementedError()
888
-
889
-
890
- @python_2_unicode_compatible
891
- class AbstractChartRule(ChartRuleI):
892
- """
893
- An abstract base class for chart rules. ``AbstractChartRule``
894
- provides:
895
-
896
- - A default implementation for ``apply``.
897
- - A default implementation for ``apply_everywhere``,
898
- (Currently, this implementation assumes that ``NUM_EDGES``<=3.)
899
- - A default implementation for ``__str__``, which returns a
900
- name based on the rule's class name.
901
- """
902
-
903
- # Subclasses must define apply.
904
- def apply(self, chart, grammar, *edges):
905
- raise NotImplementedError()
906
-
907
- # Default: loop through the given number of edges, and call
908
- # self.apply() for each set of edges.
909
- def apply_everywhere(self, chart, grammar):
910
- if self.NUM_EDGES == 0:
911
- for new_edge in self.apply(chart, grammar):
912
- yield new_edge
913
-
914
- elif self.NUM_EDGES == 1:
915
- for e1 in chart:
916
- for new_edge in self.apply(chart, grammar, e1):
917
- yield new_edge
918
-
919
- elif self.NUM_EDGES == 2:
920
- for e1 in chart:
921
- for e2 in chart:
922
- for new_edge in self.apply(chart, grammar, e1, e2):
923
- yield new_edge
924
-
925
- elif self.NUM_EDGES == 3:
926
- for e1 in chart:
927
- for e2 in chart:
928
- for e3 in chart:
929
- for new_edge in self.apply(chart,grammar,e1,e2,e3):
930
- yield new_edge
931
-
932
- else:
933
- raise AssertionError('NUM_EDGES>3 is not currently supported')
934
-
935
- # Default: return a name based on the class name.
936
- def __str__(self):
937
- # Add spaces between InitialCapsWords.
938
- return re.sub('([a-z])([A-Z])', r'\1 \2', self.__class__.__name__)
939
-
940
- #////////////////////////////////////////////////////////////
941
- # Fundamental Rule
942
- #////////////////////////////////////////////////////////////
943
-
944
- class FundamentalRule(AbstractChartRule):
945
- """
946
- A rule that joins two adjacent edges to form a single combined
947
- edge. In particular, this rule specifies that any pair of edges
948
-
949
- - ``[A -> alpha \* B beta][i:j]``
950
- - ``[B -> gamma \*][j:k]``
951
-
952
- licenses the edge:
953
-
954
- - ``[A -> alpha B * beta][i:j]``
955
- """
956
- NUM_EDGES = 2
957
- def apply(self, chart, grammar, left_edge, right_edge):
958
- # Make sure the rule is applicable.
959
- if not (left_edge.is_incomplete() and
960
- right_edge.is_complete() and
961
- left_edge.end() == right_edge.start() and
962
- left_edge.nextsym() == right_edge.lhs()):
963
- return
964
-
965
- # Construct the new edge.
966
- new_edge = left_edge.move_dot_forward(right_edge.end())
967
-
968
- # Insert it into the chart.
969
- if chart.insert_with_backpointer(new_edge, left_edge, right_edge):
970
- yield new_edge
971
-
972
- class SingleEdgeFundamentalRule(FundamentalRule):
973
- """
974
- A rule that joins a given edge with adjacent edges in the chart,
975
- to form combined edges. In particular, this rule specifies that
976
- either of the edges:
977
-
978
- - ``[A -> alpha \* B beta][i:j]``
979
- - ``[B -> gamma \*][j:k]``
980
-
981
- licenses the edge:
982
-
983
- - ``[A -> alpha B * beta][i:j]``
984
-
985
- if the other edge is already in the chart.
986
-
987
- :note: This is basically ``FundamentalRule``, with one edge left
988
- unspecified.
989
- """
990
- NUM_EDGES = 1
991
-
992
- def apply(self, chart, grammar, edge):
993
- if edge.is_incomplete():
994
- for new_edge in self._apply_incomplete(chart, grammar, edge):
995
- yield new_edge
996
- else:
997
- for new_edge in self._apply_complete(chart, grammar, edge):
998
- yield new_edge
999
-
1000
- def _apply_complete(self, chart, grammar, right_edge):
1001
- for left_edge in chart.select(end=right_edge.start(),
1002
- is_complete=False,
1003
- nextsym=right_edge.lhs()):
1004
- new_edge = left_edge.move_dot_forward(right_edge.end())
1005
- if chart.insert_with_backpointer(new_edge, left_edge, right_edge):
1006
- yield new_edge
1007
-
1008
- def _apply_incomplete(self, chart, grammar, left_edge):
1009
- for right_edge in chart.select(start=left_edge.end(),
1010
- is_complete=True,
1011
- lhs=left_edge.nextsym()):
1012
- new_edge = left_edge.move_dot_forward(right_edge.end())
1013
- if chart.insert_with_backpointer(new_edge, left_edge, right_edge):
1014
- yield new_edge
1015
-
1016
- #////////////////////////////////////////////////////////////
1017
- # Inserting Terminal Leafs
1018
- #////////////////////////////////////////////////////////////
1019
-
1020
- class LeafInitRule(AbstractChartRule):
1021
- NUM_EDGES=0
1022
- def apply(self, chart, grammar):
1023
- for index in range(chart.num_leaves()):
1024
- new_edge = LeafEdge(chart.leaf(index), index)
1025
- if chart.insert(new_edge, ()):
1026
- yield new_edge
1027
-
1028
- #////////////////////////////////////////////////////////////
1029
- # Top-Down Prediction
1030
- #////////////////////////////////////////////////////////////
1031
-
1032
- class TopDownInitRule(AbstractChartRule):
1033
- """
1034
- A rule licensing edges corresponding to the grammar productions for
1035
- the grammar's start symbol. In particular, this rule specifies that
1036
- ``[S -> \* alpha][0:i]`` is licensed for each grammar production
1037
- ``S -> alpha``, where ``S`` is the grammar's start symbol.
1038
- """
1039
- NUM_EDGES = 0
1040
- def apply(self, chart, grammar):
1041
- for prod in grammar.productions(lhs=grammar.start()):
1042
- new_edge = TreeEdge.from_production(prod, 0)
1043
- if chart.insert(new_edge, ()):
1044
- yield new_edge
1045
-
1046
- class TopDownPredictRule(AbstractChartRule):
1047
- """
1048
- A rule licensing edges corresponding to the grammar productions
1049
- for the nonterminal following an incomplete edge's dot. In
1050
- particular, this rule specifies that
1051
- ``[A -> alpha \* B beta][i:j]`` licenses the edge
1052
- ``[B -> \* gamma][j:j]`` for each grammar production ``B -> gamma``.
1053
-
1054
- :note: This rule corresponds to the Predictor Rule in Earley parsing.
1055
- """
1056
- NUM_EDGES = 1
1057
- def apply(self, chart, grammar, edge):
1058
- if edge.is_complete(): return
1059
- for prod in grammar.productions(lhs=edge.nextsym()):
1060
- new_edge = TreeEdge.from_production(prod, edge.end())
1061
- if chart.insert(new_edge, ()):
1062
- yield new_edge
1063
-
1064
- class CachedTopDownPredictRule(TopDownPredictRule):
1065
- """
1066
- A cached version of ``TopDownPredictRule``. After the first time
1067
- this rule is applied to an edge with a given ``end`` and ``next``,
1068
- it will not generate any more edges for edges with that ``end`` and
1069
- ``next``.
1070
-
1071
- If ``chart`` or ``grammar`` are changed, then the cache is flushed.
1072
- """
1073
- def __init__(self):
1074
- TopDownPredictRule.__init__(self)
1075
- self._done = {}
1076
-
1077
- def apply(self, chart, grammar, edge):
1078
- if edge.is_complete(): return
1079
- nextsym, index = edge.nextsym(), edge.end()
1080
- if not is_nonterminal(nextsym): return
1081
-
1082
- # If we've already applied this rule to an edge with the same
1083
- # next & end, and the chart & grammar have not changed, then
1084
- # just return (no new edges to add).
1085
- done = self._done.get((nextsym, index), (None,None))
1086
- if done[0] is chart and done[1] is grammar: return
1087
-
1088
- # Add all the edges indicated by the top down expand rule.
1089
- for prod in grammar.productions(lhs=nextsym):
1090
- # If the left corner in the predicted production is
1091
- # leaf, it must match with the input.
1092
- if prod.rhs():
1093
- first = prod.rhs()[0]
1094
- if is_terminal(first):
1095
- if index >= chart.num_leaves() or first != chart.leaf(index): continue
1096
-
1097
- new_edge = TreeEdge.from_production(prod, index)
1098
- if chart.insert(new_edge, ()):
1099
- yield new_edge
1100
-
1101
- # Record the fact that we've applied this rule.
1102
- self._done[nextsym, index] = (chart, grammar)
1103
-
1104
- #////////////////////////////////////////////////////////////
1105
- # Bottom-Up Prediction
1106
- #////////////////////////////////////////////////////////////
1107
-
1108
- class BottomUpPredictRule(AbstractChartRule):
1109
- """
1110
- A rule licensing any edge corresponding to a production whose
1111
- right-hand side begins with a complete edge's left-hand side. In
1112
- particular, this rule specifies that ``[A -> alpha \*]`` licenses
1113
- the edge ``[B -> \* A beta]`` for each grammar production ``B -> A beta``.
1114
- """
1115
- NUM_EDGES = 1
1116
- def apply(self, chart, grammar, edge):
1117
- if edge.is_incomplete(): return
1118
- for prod in grammar.productions(rhs=edge.lhs()):
1119
- new_edge = TreeEdge.from_production(prod, edge.start())
1120
- if chart.insert(new_edge, ()):
1121
- yield new_edge
1122
-
1123
- class BottomUpPredictCombineRule(BottomUpPredictRule):
1124
- """
1125
- A rule licensing any edge corresponding to a production whose
1126
- right-hand side begins with a complete edge's left-hand side. In
1127
- particular, this rule specifies that ``[A -> alpha \*]``
1128
- licenses the edge ``[B -> A \* beta]`` for each grammar
1129
- production ``B -> A beta``.
1130
-
1131
- :note: This is like ``BottomUpPredictRule``, but it also applies
1132
- the ``FundamentalRule`` to the resulting edge.
1133
- """
1134
- NUM_EDGES = 1
1135
- def apply(self, chart, grammar, edge):
1136
- if edge.is_incomplete(): return
1137
- for prod in grammar.productions(rhs=edge.lhs()):
1138
- new_edge = TreeEdge(edge.span(), prod.lhs(), prod.rhs(), 1)
1139
- if chart.insert(new_edge, (edge,)):
1140
- yield new_edge
1141
-
1142
- class EmptyPredictRule(AbstractChartRule):
1143
- """
1144
- A rule that inserts all empty productions as passive edges,
1145
- in every position in the chart.
1146
- """
1147
- NUM_EDGES = 0
1148
- def apply(self, chart, grammar):
1149
- for prod in grammar.productions(empty=True):
1150
- for index in compat.xrange(chart.num_leaves() + 1):
1151
- new_edge = TreeEdge.from_production(prod, index)
1152
- if chart.insert(new_edge, ()):
1153
- yield new_edge
1154
-
1155
-
1156
- ########################################################################
1157
- ## Filtered Bottom Up
1158
- ########################################################################
1159
-
1160
- class FilteredSingleEdgeFundamentalRule(SingleEdgeFundamentalRule):
1161
- def _apply_complete(self, chart, grammar, right_edge):
1162
- end = right_edge.end()
1163
- nexttoken = end < chart.num_leaves() and chart.leaf(end)
1164
- for left_edge in chart.select(end=right_edge.start(),
1165
- is_complete=False,
1166
- nextsym=right_edge.lhs()):
1167
- if _bottomup_filter(grammar, nexttoken, left_edge.rhs(), left_edge.dot()):
1168
- new_edge = left_edge.move_dot_forward(right_edge.end())
1169
- if chart.insert_with_backpointer(new_edge, left_edge, right_edge):
1170
- yield new_edge
1171
-
1172
- def _apply_incomplete(self, chart, grammar, left_edge):
1173
- for right_edge in chart.select(start=left_edge.end(),
1174
- is_complete=True,
1175
- lhs=left_edge.nextsym()):
1176
- end = right_edge.end()
1177
- nexttoken = end < chart.num_leaves() and chart.leaf(end)
1178
- if _bottomup_filter(grammar, nexttoken, left_edge.rhs(), left_edge.dot()):
1179
- new_edge = left_edge.move_dot_forward(right_edge.end())
1180
- if chart.insert_with_backpointer(new_edge, left_edge, right_edge):
1181
- yield new_edge
1182
-
1183
- class FilteredBottomUpPredictCombineRule(BottomUpPredictCombineRule):
1184
- def apply(self, chart, grammar, edge):
1185
- if edge.is_incomplete():
1186
- return
1187
-
1188
- end = edge.end()
1189
- nexttoken = end < chart.num_leaves() and chart.leaf(end)
1190
- for prod in grammar.productions(rhs=edge.lhs()):
1191
- if _bottomup_filter(grammar, nexttoken, prod.rhs()):
1192
- new_edge = TreeEdge(edge.span(), prod.lhs(), prod.rhs(), 1)
1193
- if chart.insert(new_edge, (edge,)):
1194
- yield new_edge
1195
-
1196
- def _bottomup_filter(grammar, nexttoken, rhs, dot=0):
1197
- if len(rhs) <= dot + 1:
1198
- return True
1199
- _next = rhs[dot + 1]
1200
- if is_terminal(_next):
1201
- return nexttoken == _next
1202
- else:
1203
- return grammar.is_leftcorner(_next, nexttoken)
1204
-
1205
-
1206
- ########################################################################
1207
- ## Generic Chart Parser
1208
- ########################################################################
1209
-
1210
- TD_STRATEGY = [LeafInitRule(),
1211
- TopDownInitRule(),
1212
- CachedTopDownPredictRule(),
1213
- SingleEdgeFundamentalRule()]
1214
- BU_STRATEGY = [LeafInitRule(),
1215
- EmptyPredictRule(),
1216
- BottomUpPredictRule(),
1217
- SingleEdgeFundamentalRule()]
1218
- BU_LC_STRATEGY = [LeafInitRule(),
1219
- EmptyPredictRule(),
1220
- BottomUpPredictCombineRule(),
1221
- SingleEdgeFundamentalRule()]
1222
-
1223
- LC_STRATEGY = [LeafInitRule(),
1224
- FilteredBottomUpPredictCombineRule(),
1225
- FilteredSingleEdgeFundamentalRule()]
1226
-
1227
- class ChartParser(ParserI):
1228
- """
1229
- A generic chart parser. A "strategy", or list of
1230
- ``ChartRuleI`` instances, is used to decide what edges to add to
1231
- the chart. In particular, ``ChartParser`` uses the following
1232
- algorithm to parse texts:
1233
-
1234
- | Until no new edges are added:
1235
- | For each *rule* in *strategy*:
1236
- | Apply *rule* to any applicable edges in the chart.
1237
- | Return any complete parses in the chart
1238
- """
1239
- def __init__(self, grammar, strategy=BU_LC_STRATEGY, trace=0,
1240
- trace_chart_width=50, use_agenda=True, chart_class=Chart):
1241
- """
1242
- Create a new chart parser, that uses ``grammar`` to parse
1243
- texts.
1244
-
1245
- :type grammar: CFG
1246
- :param grammar: The grammar used to parse texts.
1247
- :type strategy: list(ChartRuleI)
1248
- :param strategy: A list of rules that should be used to decide
1249
- what edges to add to the chart (top-down strategy by default).
1250
- :type trace: int
1251
- :param trace: The level of tracing that should be used when
1252
- parsing a text. ``0`` will generate no tracing output;
1253
- and higher numbers will produce more verbose tracing
1254
- output.
1255
- :type trace_chart_width: int
1256
- :param trace_chart_width: The default total width reserved for
1257
- the chart in trace output. The remainder of each line will
1258
- be used to display edges.
1259
- :type use_agenda: bool
1260
- :param use_agenda: Use an optimized agenda-based algorithm,
1261
- if possible.
1262
- :param chart_class: The class that should be used to create
1263
- the parse charts.
1264
- """
1265
- self._grammar = grammar
1266
- self._strategy = strategy
1267
- self._trace = trace
1268
- self._trace_chart_width = trace_chart_width
1269
- # If the strategy only consists of axioms (NUM_EDGES==0) and
1270
- # inference rules (NUM_EDGES==1), we can use an agenda-based algorithm:
1271
- self._use_agenda = use_agenda
1272
- self._chart_class = chart_class
1273
-
1274
- self._axioms = []
1275
- self._inference_rules = []
1276
- for rule in strategy:
1277
- if rule.NUM_EDGES == 0:
1278
- self._axioms.append(rule)
1279
- elif rule.NUM_EDGES == 1:
1280
- self._inference_rules.append(rule)
1281
- else:
1282
- self._use_agenda = False
1283
-
1284
- def grammar(self):
1285
- return self._grammar
1286
-
1287
- def _trace_new_edges(self, chart, rule, new_edges, trace, edge_width):
1288
- if not trace: return
1289
- print_rule_header = trace > 1
1290
- for edge in new_edges:
1291
- if print_rule_header:
1292
- print('%s:' % rule)
1293
- print_rule_header = False
1294
- print(chart.pretty_format_edge(edge, edge_width))
1295
-
1296
- def chart_parse(self, tokens, trace=None):
1297
- """
1298
- Return the final parse ``Chart`` from which all possible
1299
- parse trees can be extracted.
1300
-
1301
- :param tokens: The sentence to be parsed
1302
- :type tokens: list(str)
1303
- :rtype: Chart
1304
- """
1305
- if trace is None: trace = self._trace
1306
- trace_new_edges = self._trace_new_edges
1307
-
1308
- tokens = list(tokens)
1309
- self._grammar.check_coverage(tokens)
1310
- chart = self._chart_class(tokens)
1311
- grammar = self._grammar
1312
-
1313
- # Width, for printing trace edges.
1314
- trace_edge_width = self._trace_chart_width // (chart.num_leaves() + 1)
1315
- if trace: print(chart.pretty_format_leaves(trace_edge_width))
1316
-
1317
- if self._use_agenda:
1318
- # Use an agenda-based algorithm.
1319
- for axiom in self._axioms:
1320
- new_edges = list(axiom.apply(chart, grammar))
1321
- trace_new_edges(chart, axiom, new_edges, trace, trace_edge_width)
1322
-
1323
- inference_rules = self._inference_rules
1324
- agenda = chart.edges()
1325
- # We reverse the initial agenda, since it is a stack
1326
- # but chart.edges() functions as a queue.
1327
- agenda.reverse()
1328
- while agenda:
1329
- edge = agenda.pop()
1330
- for rule in inference_rules:
1331
- new_edges = list(rule.apply(chart, grammar, edge))
1332
- if trace:
1333
- trace_new_edges(chart, rule, new_edges, trace, trace_edge_width)
1334
- agenda += new_edges
1335
-
1336
- else:
1337
- # Do not use an agenda-based algorithm.
1338
- edges_added = True
1339
- while edges_added:
1340
- edges_added = False
1341
- for rule in self._strategy:
1342
- new_edges = list(rule.apply_everywhere(chart, grammar))
1343
- edges_added = len(new_edges)
1344
- trace_new_edges(chart, rule, new_edges, trace, trace_edge_width)
1345
-
1346
- # Return the final chart.
1347
- return chart
1348
-
1349
- def parse(self, tokens, tree_class=Tree):
1350
- chart = self.chart_parse(tokens)
1351
- return iter(chart.parses(self._grammar.start(), tree_class=tree_class))
1352
-
1353
- class TopDownChartParser(ChartParser):
1354
- """
1355
- A ``ChartParser`` using a top-down parsing strategy.
1356
- See ``ChartParser`` for more information.
1357
- """
1358
- def __init__(self, grammar, **parser_args):
1359
- ChartParser.__init__(self, grammar, TD_STRATEGY, **parser_args)
1360
-
1361
- class BottomUpChartParser(ChartParser):
1362
- """
1363
- A ``ChartParser`` using a bottom-up parsing strategy.
1364
- See ``ChartParser`` for more information.
1365
- """
1366
- def __init__(self, grammar, **parser_args):
1367
- if isinstance(grammar, PCFG):
1368
- warnings.warn("BottomUpChartParser only works for CFG, "
1369
- "use BottomUpProbabilisticChartParser instead",
1370
- category=DeprecationWarning)
1371
- ChartParser.__init__(self, grammar, BU_STRATEGY, **parser_args)
1372
-
1373
- class BottomUpLeftCornerChartParser(ChartParser):
1374
- """
1375
- A ``ChartParser`` using a bottom-up left-corner parsing strategy.
1376
- This strategy is often more efficient than standard bottom-up.
1377
- See ``ChartParser`` for more information.
1378
- """
1379
- def __init__(self, grammar, **parser_args):
1380
- ChartParser.__init__(self, grammar, BU_LC_STRATEGY, **parser_args)
1381
-
1382
- class LeftCornerChartParser(ChartParser):
1383
- def __init__(self, grammar, **parser_args):
1384
- if not grammar.is_nonempty():
1385
- raise ValueError("LeftCornerParser only works for grammars "
1386
- "without empty productions.")
1387
- ChartParser.__init__(self, grammar, LC_STRATEGY, **parser_args)
1388
-
1389
- ########################################################################
1390
- ## Stepping Chart Parser
1391
- ########################################################################
1392
-
1393
- class SteppingChartParser(ChartParser):
1394
- """
1395
- A ``ChartParser`` that allows you to step through the parsing
1396
- process, adding a single edge at a time. It also allows you to
1397
- change the parser's strategy or grammar midway through parsing a
1398
- text.
1399
-
1400
- The ``initialize`` method is used to start parsing a text. ``step``
1401
- adds a single edge to the chart. ``set_strategy`` changes the
1402
- strategy used by the chart parser. ``parses`` returns the set of
1403
- parses that has been found by the chart parser.
1404
-
1405
- :ivar _restart: Records whether the parser's strategy, grammar,
1406
- or chart has been changed. If so, then ``step`` must restart
1407
- the parsing algorithm.
1408
- """
1409
- def __init__(self, grammar, strategy=[], trace=0):
1410
- self._chart = None
1411
- self._current_chartrule = None
1412
- self._restart = False
1413
- ChartParser.__init__(self, grammar, strategy, trace)
1414
-
1415
- #////////////////////////////////////////////////////////////
1416
- # Initialization
1417
- #////////////////////////////////////////////////////////////
1418
-
1419
- def initialize(self, tokens):
1420
- "Begin parsing the given tokens."
1421
- self._chart = Chart(list(tokens))
1422
- self._restart = True
1423
-
1424
- #////////////////////////////////////////////////////////////
1425
- # Stepping
1426
- #////////////////////////////////////////////////////////////
1427
-
1428
- def step(self):
1429
- """
1430
- Return a generator that adds edges to the chart, one at a
1431
- time. Each time the generator is resumed, it adds a single
1432
- edge and yields that edge. If no more edges can be added,
1433
- then it yields None.
1434
-
1435
- If the parser's strategy, grammar, or chart is changed, then
1436
- the generator will continue adding edges using the new
1437
- strategy, grammar, or chart.
1438
-
1439
- Note that this generator never terminates, since the grammar
1440
- or strategy might be changed to values that would add new
1441
- edges. Instead, it yields None when no more edges can be
1442
- added with the current strategy and grammar.
1443
- """
1444
- if self._chart is None:
1445
- raise ValueError('Parser must be initialized first')
1446
- while True:
1447
- self._restart = False
1448
- w = 50 // (self._chart.num_leaves()+1)
1449
-
1450
- for e in self._parse():
1451
- if self._trace > 1: print(self._current_chartrule)
1452
- if self._trace > 0: print(self._chart.pretty_format_edge(e,w))
1453
- yield e
1454
- if self._restart: break
1455
- else:
1456
- yield None # No more edges.
1457
-
1458
- def _parse(self):
1459
- """
1460
- A generator that implements the actual parsing algorithm.
1461
- ``step`` iterates through this generator, and restarts it
1462
- whenever the parser's strategy, grammar, or chart is modified.
1463
- """
1464
- chart = self._chart
1465
- grammar = self._grammar
1466
- edges_added = 1
1467
- while edges_added > 0:
1468
- edges_added = 0
1469
- for rule in self._strategy:
1470
- self._current_chartrule = rule
1471
- for e in rule.apply_everywhere(chart, grammar):
1472
- edges_added += 1
1473
- yield e
1474
-
1475
- #////////////////////////////////////////////////////////////
1476
- # Accessors
1477
- #////////////////////////////////////////////////////////////
1478
-
1479
- def strategy(self):
1480
- "Return the strategy used by this parser."
1481
- return self._strategy
1482
-
1483
- def grammar(self):
1484
- "Return the grammar used by this parser."
1485
- return self._grammar
1486
-
1487
- def chart(self):
1488
- "Return the chart that is used by this parser."
1489
- return self._chart
1490
-
1491
- def current_chartrule(self):
1492
- "Return the chart rule used to generate the most recent edge."
1493
- return self._current_chartrule
1494
-
1495
- def parses(self, tree_class=Tree):
1496
- "Return the parse trees currently contained in the chart."
1497
- return self._chart.parses(self._grammar.start(), tree_class)
1498
-
1499
- #////////////////////////////////////////////////////////////
1500
- # Parser modification
1501
- #////////////////////////////////////////////////////////////
1502
-
1503
- def set_strategy(self, strategy):
1504
- """
1505
- Change the strategy that the parser uses to decide which edges
1506
- to add to the chart.
1507
-
1508
- :type strategy: list(ChartRuleI)
1509
- :param strategy: A list of rules that should be used to decide
1510
- what edges to add to the chart.
1511
- """
1512
- if strategy == self._strategy: return
1513
- self._strategy = strategy[:] # Make a copy.
1514
- self._restart = True
1515
-
1516
- def set_grammar(self, grammar):
1517
- "Change the grammar used by the parser."
1518
- if grammar is self._grammar: return
1519
- self._grammar = grammar
1520
- self._restart = True
1521
-
1522
- def set_chart(self, chart):
1523
- "Load a given chart into the chart parser."
1524
- if chart is self._chart: return
1525
- self._chart = chart
1526
- self._restart = True
1527
-
1528
- #////////////////////////////////////////////////////////////
1529
- # Standard parser methods
1530
- #////////////////////////////////////////////////////////////
1531
-
1532
- def parse(self, tokens, tree_class=Tree):
1533
- tokens = list(tokens)
1534
- self._grammar.check_coverage(tokens)
1535
-
1536
- # Initialize ourselves.
1537
- self.initialize(tokens)
1538
-
1539
- # Step until no more edges are generated.
1540
- for e in self.step():
1541
- if e is None: break
1542
-
1543
- # Return an iterator of complete parses.
1544
- return self.parses(tree_class=tree_class)
1545
-
1546
- ########################################################################
1547
- ## Demo Code
1548
- ########################################################################
1549
-
1550
- def demo_grammar():
1551
- from nltk.grammar import CFG
1552
- return CFG.fromstring("""
1553
- S -> NP VP
1554
- PP -> "with" NP
1555
- NP -> NP PP
1556
- VP -> VP PP
1557
- VP -> Verb NP
1558
- VP -> Verb
1559
- NP -> Det Noun
1560
- NP -> "John"
1561
- NP -> "I"
1562
- Det -> "the"
1563
- Det -> "my"
1564
- Det -> "a"
1565
- Noun -> "dog"
1566
- Noun -> "cookie"
1567
- Verb -> "ate"
1568
- Verb -> "saw"
1569
- Prep -> "with"
1570
- Prep -> "under"
1571
- """)
1572
-
1573
- def demo(choice=None,
1574
- print_times=True, print_grammar=False,
1575
- print_trees=True, trace=2,
1576
- sent='I saw John with a dog with my cookie', numparses=5):
1577
- """
1578
- A demonstration of the chart parsers.
1579
- """
1580
- import sys, time
1581
- from nltk import nonterminals, Production, CFG
1582
-
1583
- # The grammar for ChartParser and SteppingChartParser:
1584
- grammar = demo_grammar()
1585
- if print_grammar:
1586
- print("* Grammar")
1587
- print(grammar)
1588
-
1589
- # Tokenize the sample sentence.
1590
- print("* Sentence:")
1591
- print(sent)
1592
- tokens = sent.split()
1593
- print(tokens)
1594
- print()
1595
-
1596
- # Ask the user which parser to test,
1597
- # if the parser wasn't provided as an argument
1598
- if choice is None:
1599
- print(' 1: Top-down chart parser')
1600
- print(' 2: Bottom-up chart parser')
1601
- print(' 3: Bottom-up left-corner chart parser')
1602
- print(' 4: Left-corner chart parser with bottom-up filter')
1603
- print(' 5: Stepping chart parser (alternating top-down & bottom-up)')
1604
- print(' 6: All parsers')
1605
- print('\nWhich parser (1-6)? ', end=' ')
1606
- choice = sys.stdin.readline().strip()
1607
- print()
1608
-
1609
- choice = str(choice)
1610
- if choice not in "123456":
1611
- print('Bad parser number')
1612
- return
1613
-
1614
- # Keep track of how long each parser takes.
1615
- times = {}
1616
-
1617
- strategies = {'1': ('Top-down', TD_STRATEGY),
1618
- '2': ('Bottom-up', BU_STRATEGY),
1619
- '3': ('Bottom-up left-corner', BU_LC_STRATEGY),
1620
- '4': ('Filtered left-corner', LC_STRATEGY)}
1621
- choices = []
1622
- if choice in strategies: choices = [choice]
1623
- if choice=='6': choices = "1234"
1624
-
1625
- # Run the requested chart parser(s), except the stepping parser.
1626
- for strategy in choices:
1627
- print("* Strategy: " + strategies[strategy][0])
1628
- print()
1629
- cp = ChartParser(grammar, strategies[strategy][1], trace=trace)
1630
- t = time.time()
1631
- chart = cp.chart_parse(tokens)
1632
- parses = list(chart.parses(grammar.start()))
1633
-
1634
- times[strategies[strategy][0]] = time.time()-t
1635
- print("Nr edges in chart:", len(chart.edges()))
1636
- if numparses:
1637
- assert len(parses)==numparses, 'Not all parses found'
1638
- if print_trees:
1639
- for tree in parses: print(tree)
1640
- else:
1641
- print("Nr trees:", len(parses))
1642
- print()
1643
-
1644
- # Run the stepping parser, if requested.
1645
- if choice in "56":
1646
- print("* Strategy: Stepping (top-down vs bottom-up)")
1647
- print()
1648
- t = time.time()
1649
- cp = SteppingChartParser(grammar, trace=trace)
1650
- cp.initialize(tokens)
1651
- for i in range(5):
1652
- print('*** SWITCH TO TOP DOWN')
1653
- cp.set_strategy(TD_STRATEGY)
1654
- for j, e in enumerate(cp.step()):
1655
- if j>20 or e is None: break
1656
- print('*** SWITCH TO BOTTOM UP')
1657
- cp.set_strategy(BU_STRATEGY)
1658
- for j, e in enumerate(cp.step()):
1659
- if j>20 or e is None: break
1660
- times['Stepping'] = time.time()-t
1661
- print("Nr edges in chart:", len(cp.chart().edges()))
1662
- if numparses:
1663
- assert len(list(cp.parses()))==numparses, 'Not all parses found'
1664
- if print_trees:
1665
- for tree in cp.parses(): print(tree)
1666
- else:
1667
- print("Nr trees:", len(list(cp.parses())))
1668
- print()
1669
-
1670
- # Print the times of all parsers:
1671
- if not (print_times and times): return
1672
- print("* Parsing times")
1673
- print()
1674
- maxlen = max(len(key) for key in times)
1675
- format = '%' + repr(maxlen) + 's parser: %6.3fsec'
1676
- times_items = times.items()
1677
- for (parser, t) in sorted(times_items, key=lambda a:a[1]):
1678
- print(format % (parser, t))
1679
-
1680
- if __name__ == '__main__':
1681
- demo()
1
+ # -*- coding: utf-8 -*-
2
+ # Natural Language Toolkit: A Chart Parser
3
+ #
4
+ # Copyright (C) 2001-2018 NLTK Project
5
+ # Author: Edward Loper <edloper@gmail.com>
6
+ # Steven Bird <stevenbird1@gmail.com>
7
+ # Jean Mark Gawron <gawron@mail.sdsu.edu>
8
+ # Peter Ljunglöf <peter.ljunglof@heatherleaf.se>
9
+ # URL: <http://nltk.org/>
10
+ # For license information, see LICENSE.TXT
11
+
12
+ """
13
+ Data classes and parser implementations for "chart parsers", which
14
+ use dynamic programming to efficiently parse a text. A chart
15
+ parser derives parse trees for a text by iteratively adding "edges"
16
+ to a "chart." Each edge represents a hypothesis about the tree
17
+ structure for a subsequence of the text. The chart is a
18
+ "blackboard" for composing and combining these hypotheses.
19
+
20
+ When a chart parser begins parsing a text, it creates a new (empty)
21
+ chart, spanning the text. It then incrementally adds new edges to the
22
+ chart. A set of "chart rules" specifies the conditions under which
23
+ new edges should be added to the chart. Once the chart reaches a
24
+ stage where none of the chart rules adds any new edges, parsing is
25
+ complete.
26
+
27
+ Charts are encoded with the ``Chart`` class, and edges are encoded with
28
+ the ``TreeEdge`` and ``LeafEdge`` classes. The chart parser module
29
+ defines three chart parsers:
30
+
31
+ - ``ChartParser`` is a simple and flexible chart parser. Given a
32
+ set of chart rules, it will apply those rules to the chart until
33
+ no more edges are added.
34
+
35
+ - ``SteppingChartParser`` is a subclass of ``ChartParser`` that can
36
+ be used to step through the parsing process.
37
+ """
38
+ from __future__ import print_function, division, unicode_literals
39
+
40
+ import itertools
41
+ import re
42
+ import warnings
43
+ from functools import total_ordering
44
+
45
+ from six.moves import range
46
+
47
+ from nltk.tree import Tree
48
+ from nltk.grammar import PCFG, is_nonterminal, is_terminal
49
+ from nltk.util import OrderedDict
50
+ from nltk.internals import raise_unorderable_types
51
+ from nltk.compat import python_2_unicode_compatible, unicode_repr
52
+
53
+ from nltk.parse.api import ParserI
54
+
55
+
56
+ ########################################################################
57
+ ## Edges
58
+ ########################################################################
59
+
60
+ @total_ordering
61
+ class EdgeI(object):
62
+ """
63
+ A hypothesis about the structure of part of a sentence.
64
+ Each edge records the fact that a structure is (partially)
65
+ consistent with the sentence. An edge contains:
66
+
67
+ - A span, indicating what part of the sentence is
68
+ consistent with the hypothesized structure.
69
+ - A left-hand side, specifying what kind of structure is
70
+ hypothesized.
71
+ - A right-hand side, specifying the contents of the
72
+ hypothesized structure.
73
+ - A dot position, indicating how much of the hypothesized
74
+ structure is consistent with the sentence.
75
+
76
+ Every edge is either complete or incomplete:
77
+
78
+ - An edge is complete if its structure is fully consistent
79
+ with the sentence.
80
+ - An edge is incomplete if its structure is partially
81
+ consistent with the sentence. For every incomplete edge, the
82
+ span specifies a possible prefix for the edge's structure.
83
+
84
+ There are two kinds of edge:
85
+
86
+ - A ``TreeEdge`` records which trees have been found to
87
+ be (partially) consistent with the text.
88
+ - A ``LeafEdge`` records the tokens occurring in the text.
89
+
90
+ The ``EdgeI`` interface provides a common interface to both types
91
+ of edge, allowing chart parsers to treat them in a uniform manner.
92
+ """
93
+ def __init__(self):
94
+ if self.__class__ == EdgeI:
95
+ raise TypeError('Edge is an abstract interface')
96
+
97
+ #////////////////////////////////////////////////////////////
98
+ # Span
99
+ #////////////////////////////////////////////////////////////
100
+
101
+ def span(self):
102
+ """
103
+ Return a tuple ``(s, e)``, where ``tokens[s:e]`` is the
104
+ portion of the sentence that is consistent with this
105
+ edge's structure.
106
+
107
+ :rtype: tuple(int, int)
108
+ """
109
+ raise NotImplementedError()
110
+
111
+ def start(self):
112
+ """
113
+ Return the start index of this edge's span.
114
+
115
+ :rtype: int
116
+ """
117
+ raise NotImplementedError()
118
+
119
+ def end(self):
120
+ """
121
+ Return the end index of this edge's span.
122
+
123
+ :rtype: int
124
+ """
125
+ raise NotImplementedError()
126
+
127
+ def length(self):
128
+ """
129
+ Return the length of this edge's span.
130
+
131
+ :rtype: int
132
+ """
133
+ raise NotImplementedError()
134
+
135
+ #////////////////////////////////////////////////////////////
136
+ # Left Hand Side
137
+ #////////////////////////////////////////////////////////////
138
+
139
+ def lhs(self):
140
+ """
141
+ Return this edge's left-hand side, which specifies what kind
142
+ of structure is hypothesized by this edge.
143
+
144
+ :see: ``TreeEdge`` and ``LeafEdge`` for a description of
145
+ the left-hand side values for each edge type.
146
+ """
147
+ raise NotImplementedError()
148
+
149
+ #////////////////////////////////////////////////////////////
150
+ # Right Hand Side
151
+ #////////////////////////////////////////////////////////////
152
+
153
+ def rhs(self):
154
+ """
155
+ Return this edge's right-hand side, which specifies
156
+ the content of the structure hypothesized by this edge.
157
+
158
+ :see: ``TreeEdge`` and ``LeafEdge`` for a description of
159
+ the right-hand side values for each edge type.
160
+ """
161
+ raise NotImplementedError()
162
+
163
+ def dot(self):
164
+ """
165
+ Return this edge's dot position, which indicates how much of
166
+ the hypothesized structure is consistent with the
167
+ sentence. In particular, ``self.rhs[:dot]`` is consistent
168
+ with ``tokens[self.start():self.end()]``.
169
+
170
+ :rtype: int
171
+ """
172
+ raise NotImplementedError()
173
+
174
+ def nextsym(self):
175
+ """
176
+ Return the element of this edge's right-hand side that
177
+ immediately follows its dot.
178
+
179
+ :rtype: Nonterminal or terminal or None
180
+ """
181
+ raise NotImplementedError()
182
+
183
+ def is_complete(self):
184
+ """
185
+ Return True if this edge's structure is fully consistent
186
+ with the text.
187
+
188
+ :rtype: bool
189
+ """
190
+ raise NotImplementedError()
191
+
192
+ def is_incomplete(self):
193
+ """
194
+ Return True if this edge's structure is partially consistent
195
+ with the text.
196
+
197
+ :rtype: bool
198
+ """
199
+ raise NotImplementedError()
200
+
201
+ #////////////////////////////////////////////////////////////
202
+ # Comparisons & hashing
203
+ #////////////////////////////////////////////////////////////
204
+
205
+ def __eq__(self, other):
206
+ return (self.__class__ is other.__class__ and
207
+ self._comparison_key == other._comparison_key)
208
+
209
+ def __ne__(self, other):
210
+ return not self == other
211
+
212
+ def __lt__(self, other):
213
+ if not isinstance(other, EdgeI):
214
+ raise_unorderable_types("<", self, other)
215
+ if self.__class__ is other.__class__:
216
+ return self._comparison_key < other._comparison_key
217
+ else:
218
+ return self.__class__.__name__ < other.__class__.__name__
219
+
220
+ def __hash__(self):
221
+ try:
222
+ return self._hash
223
+ except AttributeError:
224
+ self._hash = hash(self._comparison_key)
225
+ return self._hash
226
+
227
+
228
+ @python_2_unicode_compatible
229
+ class TreeEdge(EdgeI):
230
+ """
231
+ An edge that records the fact that a tree is (partially)
232
+ consistent with the sentence. A tree edge consists of:
233
+
234
+ - A span, indicating what part of the sentence is
235
+ consistent with the hypothesized tree.
236
+ - A left-hand side, specifying the hypothesized tree's node
237
+ value.
238
+ - A right-hand side, specifying the hypothesized tree's
239
+ children. Each element of the right-hand side is either a
240
+ terminal, specifying a token with that terminal as its leaf
241
+ value; or a nonterminal, specifying a subtree with that
242
+ nonterminal's symbol as its node value.
243
+ - A dot position, indicating which children are consistent
244
+ with part of the sentence. In particular, if ``dot`` is the
245
+ dot position, ``rhs`` is the right-hand size, ``(start,end)``
246
+ is the span, and ``sentence`` is the list of tokens in the
247
+ sentence, then ``tokens[start:end]`` can be spanned by the
248
+ children specified by ``rhs[:dot]``.
249
+
250
+ For more information about edges, see the ``EdgeI`` interface.
251
+ """
252
+ def __init__(self, span, lhs, rhs, dot=0):
253
+ """
254
+ Construct a new ``TreeEdge``.
255
+
256
+ :type span: tuple(int, int)
257
+ :param span: A tuple ``(s, e)``, where ``tokens[s:e]`` is the
258
+ portion of the sentence that is consistent with the new
259
+ edge's structure.
260
+ :type lhs: Nonterminal
261
+ :param lhs: The new edge's left-hand side, specifying the
262
+ hypothesized tree's node value.
263
+ :type rhs: list(Nonterminal and str)
264
+ :param rhs: The new edge's right-hand side, specifying the
265
+ hypothesized tree's children.
266
+ :type dot: int
267
+ :param dot: The position of the new edge's dot. This position
268
+ specifies what prefix of the production's right hand side
269
+ is consistent with the text. In particular, if
270
+ ``sentence`` is the list of tokens in the sentence, then
271
+ ``okens[span[0]:span[1]]`` can be spanned by the
272
+ children specified by ``rhs[:dot]``.
273
+ """
274
+ self._span = span
275
+ self._lhs = lhs
276
+ rhs = tuple(rhs)
277
+ self._rhs = rhs
278
+ self._dot = dot
279
+ self._comparison_key = (span, lhs, rhs, dot)
280
+
281
+ @staticmethod
282
+ def from_production(production, index):
283
+ """
284
+ Return a new ``TreeEdge`` formed from the given production.
285
+ The new edge's left-hand side and right-hand side will
286
+ be taken from ``production``; its span will be
287
+ ``(index,index)``; and its dot position will be ``0``.
288
+
289
+ :rtype: TreeEdge
290
+ """
291
+ return TreeEdge(span=(index, index), lhs=production.lhs(),
292
+ rhs=production.rhs(), dot=0)
293
+
294
+ def move_dot_forward(self, new_end):
295
+ """
296
+ Return a new ``TreeEdge`` formed from this edge.
297
+ The new edge's dot position is increased by ``1``,
298
+ and its end index will be replaced by ``new_end``.
299
+
300
+ :param new_end: The new end index.
301
+ :type new_end: int
302
+ :rtype: TreeEdge
303
+ """
304
+ return TreeEdge(span=(self._span[0], new_end),
305
+ lhs=self._lhs, rhs=self._rhs,
306
+ dot=self._dot+1)
307
+
308
+ # Accessors
309
+ def lhs(self): return self._lhs
310
+ def span(self): return self._span
311
+ def start(self): return self._span[0]
312
+ def end(self): return self._span[1]
313
+ def length(self): return self._span[1] - self._span[0]
314
+ def rhs(self): return self._rhs
315
+ def dot(self): return self._dot
316
+ def is_complete(self): return self._dot == len(self._rhs)
317
+ def is_incomplete(self): return self._dot != len(self._rhs)
318
+ def nextsym(self):
319
+ if self._dot >= len(self._rhs): return None
320
+ else: return self._rhs[self._dot]
321
+
322
+ # String representation
323
+ def __str__(self):
324
+ str = '[%s:%s] ' % (self._span[0], self._span[1])
325
+ str += '%-2r ->' % (self._lhs,)
326
+
327
+ for i in range(len(self._rhs)):
328
+ if i == self._dot: str += ' *'
329
+ str += ' %s' % unicode_repr(self._rhs[i])
330
+ if len(self._rhs) == self._dot: str += ' *'
331
+ return str
332
+
333
+ def __repr__(self):
334
+ return '[Edge: %s]' % self
335
+
336
+
337
+ @python_2_unicode_compatible
338
+ class LeafEdge(EdgeI):
339
+ """
340
+ An edge that records the fact that a leaf value is consistent with
341
+ a word in the sentence. A leaf edge consists of:
342
+
343
+ - An index, indicating the position of the word.
344
+ - A leaf, specifying the word's content.
345
+
346
+ A leaf edge's left-hand side is its leaf value, and its right hand
347
+ side is ``()``. Its span is ``[index, index+1]``, and its dot
348
+ position is ``0``.
349
+ """
350
+ def __init__(self, leaf, index):
351
+ """
352
+ Construct a new ``LeafEdge``.
353
+
354
+ :param leaf: The new edge's leaf value, specifying the word
355
+ that is recorded by this edge.
356
+ :param index: The new edge's index, specifying the position of
357
+ the word that is recorded by this edge.
358
+ """
359
+ self._leaf = leaf
360
+ self._index = index
361
+ self._comparison_key = (leaf, index)
362
+
363
+ # Accessors
364
+ def lhs(self): return self._leaf
365
+ def span(self): return (self._index, self._index+1)
366
+ def start(self): return self._index
367
+ def end(self): return self._index+1
368
+ def length(self): return 1
369
+ def rhs(self): return ()
370
+ def dot(self): return 0
371
+ def is_complete(self): return True
372
+ def is_incomplete(self): return False
373
+ def nextsym(self): return None
374
+
375
+ # String representations
376
+ def __str__(self):
377
+ return '[%s:%s] %s' % (self._index, self._index+1, unicode_repr(self._leaf))
378
+ def __repr__(self):
379
+ return '[Edge: %s]' % (self)
380
+
381
+ ########################################################################
382
+ ## Chart
383
+ ########################################################################
384
+
385
+ class Chart(object):
386
+ """
387
+ A blackboard for hypotheses about the syntactic constituents of a
388
+ sentence. A chart contains a set of edges, and each edge encodes
389
+ a single hypothesis about the structure of some portion of the
390
+ sentence.
391
+
392
+ The ``select`` method can be used to select a specific collection
393
+ of edges. For example ``chart.select(is_complete=True, start=0)``
394
+ yields all complete edges whose start indices are 0. To ensure
395
+ the efficiency of these selection operations, ``Chart`` dynamically
396
+ creates and maintains an index for each set of attributes that
397
+ have been selected on.
398
+
399
+ In order to reconstruct the trees that are represented by an edge,
400
+ the chart associates each edge with a set of child pointer lists.
401
+ A child pointer list is a list of the edges that license an
402
+ edge's right-hand side.
403
+
404
+ :ivar _tokens: The sentence that the chart covers.
405
+ :ivar _num_leaves: The number of tokens.
406
+ :ivar _edges: A list of the edges in the chart
407
+ :ivar _edge_to_cpls: A dictionary mapping each edge to a set
408
+ of child pointer lists that are associated with that edge.
409
+ :ivar _indexes: A dictionary mapping tuples of edge attributes
410
+ to indices, where each index maps the corresponding edge
411
+ attribute values to lists of edges.
412
+ """
413
+ def __init__(self, tokens):
414
+ """
415
+ Construct a new chart. The chart is initialized with the
416
+ leaf edges corresponding to the terminal leaves.
417
+
418
+ :type tokens: list
419
+ :param tokens: The sentence that this chart will be used to parse.
420
+ """
421
+ # Record the sentence token and the sentence length.
422
+ self._tokens = tuple(tokens)
423
+ self._num_leaves = len(self._tokens)
424
+
425
+ # Initialise the chart.
426
+ self.initialize()
427
+
428
+ def initialize(self):
429
+ """
430
+ Clear the chart.
431
+ """
432
+ # A list of edges contained in this chart.
433
+ self._edges = []
434
+
435
+ # The set of child pointer lists associated with each edge.
436
+ self._edge_to_cpls = {}
437
+
438
+ # Indexes mapping attribute values to lists of edges
439
+ # (used by select()).
440
+ self._indexes = {}
441
+
442
+ #////////////////////////////////////////////////////////////
443
+ # Sentence Access
444
+ #////////////////////////////////////////////////////////////
445
+
446
+ def num_leaves(self):
447
+ """
448
+ Return the number of words in this chart's sentence.
449
+
450
+ :rtype: int
451
+ """
452
+ return self._num_leaves
453
+
454
+ def leaf(self, index):
455
+ """
456
+ Return the leaf value of the word at the given index.
457
+
458
+ :rtype: str
459
+ """
460
+ return self._tokens[index]
461
+
462
+ def leaves(self):
463
+ """
464
+ Return a list of the leaf values of each word in the
465
+ chart's sentence.
466
+
467
+ :rtype: list(str)
468
+ """
469
+ return self._tokens
470
+
471
+ #////////////////////////////////////////////////////////////
472
+ # Edge access
473
+ #////////////////////////////////////////////////////////////
474
+
475
+ def edges(self):
476
+ """
477
+ Return a list of all edges in this chart. New edges
478
+ that are added to the chart after the call to edges()
479
+ will *not* be contained in this list.
480
+
481
+ :rtype: list(EdgeI)
482
+ :see: ``iteredges``, ``select``
483
+ """
484
+ return self._edges[:]
485
+
486
+ def iteredges(self):
487
+ """
488
+ Return an iterator over the edges in this chart. It is
489
+ not guaranteed that new edges which are added to the
490
+ chart before the iterator is exhausted will also be generated.
491
+
492
+ :rtype: iter(EdgeI)
493
+ :see: ``edges``, ``select``
494
+ """
495
+ return iter(self._edges)
496
+
497
+ # Iterating over the chart yields its edges.
498
+ __iter__ = iteredges
499
+
500
+ def num_edges(self):
501
+ """
502
+ Return the number of edges contained in this chart.
503
+
504
+ :rtype: int
505
+ """
506
+ return len(self._edge_to_cpls)
507
+
508
+ def select(self, **restrictions):
509
+ """
510
+ Return an iterator over the edges in this chart. Any
511
+ new edges that are added to the chart before the iterator
512
+ is exahusted will also be generated. ``restrictions``
513
+ can be used to restrict the set of edges that will be
514
+ generated.
515
+
516
+ :param span: Only generate edges ``e`` where ``e.span()==span``
517
+ :param start: Only generate edges ``e`` where ``e.start()==start``
518
+ :param end: Only generate edges ``e`` where ``e.end()==end``
519
+ :param length: Only generate edges ``e`` where ``e.length()==length``
520
+ :param lhs: Only generate edges ``e`` where ``e.lhs()==lhs``
521
+ :param rhs: Only generate edges ``e`` where ``e.rhs()==rhs``
522
+ :param nextsym: Only generate edges ``e`` where
523
+ ``e.nextsym()==nextsym``
524
+ :param dot: Only generate edges ``e`` where ``e.dot()==dot``
525
+ :param is_complete: Only generate edges ``e`` where
526
+ ``e.is_complete()==is_complete``
527
+ :param is_incomplete: Only generate edges ``e`` where
528
+ ``e.is_incomplete()==is_incomplete``
529
+ :rtype: iter(EdgeI)
530
+ """
531
+ # If there are no restrictions, then return all edges.
532
+ if restrictions=={}: return iter(self._edges)
533
+
534
+ # Find the index corresponding to the given restrictions.
535
+ restr_keys = sorted(restrictions.keys())
536
+ restr_keys = tuple(restr_keys)
537
+
538
+ # If it doesn't exist, then create it.
539
+ if restr_keys not in self._indexes:
540
+ self._add_index(restr_keys)
541
+
542
+ vals = tuple(restrictions[key] for key in restr_keys)
543
+ return iter(self._indexes[restr_keys].get(vals, []))
544
+
545
+ def _add_index(self, restr_keys):
546
+ """
547
+ A helper function for ``select``, which creates a new index for
548
+ a given set of attributes (aka restriction keys).
549
+ """
550
+ # Make sure it's a valid index.
551
+ for key in restr_keys:
552
+ if not hasattr(EdgeI, key):
553
+ raise ValueError('Bad restriction: %s' % key)
554
+
555
+ # Create the index.
556
+ index = self._indexes[restr_keys] = {}
557
+
558
+ # Add all existing edges to the index.
559
+ for edge in self._edges:
560
+ vals = tuple(getattr(edge, key)() for key in restr_keys)
561
+ index.setdefault(vals, []).append(edge)
562
+
563
+ def _register_with_indexes(self, edge):
564
+ """
565
+ A helper function for ``insert``, which registers the new
566
+ edge with all existing indexes.
567
+ """
568
+ for (restr_keys, index) in self._indexes.items():
569
+ vals = tuple(getattr(edge, key)() for key in restr_keys)
570
+ index.setdefault(vals, []).append(edge)
571
+
572
+ #////////////////////////////////////////////////////////////
573
+ # Edge Insertion
574
+ #////////////////////////////////////////////////////////////
575
+
576
+ def insert_with_backpointer(self, new_edge, previous_edge, child_edge):
577
+ """
578
+ Add a new edge to the chart, using a pointer to the previous edge.
579
+ """
580
+ cpls = self.child_pointer_lists(previous_edge)
581
+ new_cpls = [cpl+(child_edge,) for cpl in cpls]
582
+ return self.insert(new_edge, *new_cpls)
583
+
584
+ def insert(self, edge, *child_pointer_lists):
585
+ """
586
+ Add a new edge to the chart, and return True if this operation
587
+ modified the chart. In particular, return true iff the chart
588
+ did not already contain ``edge``, or if it did not already associate
589
+ ``child_pointer_lists`` with ``edge``.
590
+
591
+ :type edge: EdgeI
592
+ :param edge: The new edge
593
+ :type child_pointer_lists: sequence of tuple(EdgeI)
594
+ :param child_pointer_lists: A sequence of lists of the edges that
595
+ were used to form this edge. This list is used to reconstruct
596
+ the trees (or partial trees) that are associated with ``edge``.
597
+ :rtype: bool
598
+ """
599
+ # Is it a new edge?
600
+ if edge not in self._edge_to_cpls:
601
+ # Add it to the list of edges.
602
+ self._append_edge(edge)
603
+ # Register with indexes.
604
+ self._register_with_indexes(edge)
605
+
606
+ # Get the set of child pointer lists for this edge.
607
+ cpls = self._edge_to_cpls.setdefault(edge, OrderedDict())
608
+ chart_was_modified = False
609
+ for child_pointer_list in child_pointer_lists:
610
+ child_pointer_list = tuple(child_pointer_list)
611
+ if child_pointer_list not in cpls:
612
+ # It's a new CPL; register it, and return true.
613
+ cpls[child_pointer_list] = True
614
+ chart_was_modified = True
615
+ return chart_was_modified
616
+
617
+ def _append_edge(self, edge):
618
+ self._edges.append(edge)
619
+
620
+ #////////////////////////////////////////////////////////////
621
+ # Tree extraction & child pointer lists
622
+ #////////////////////////////////////////////////////////////
623
+
624
+ def parses(self, root, tree_class=Tree):
625
+ """
626
+ Return an iterator of the complete tree structures that span
627
+ the entire chart, and whose root node is ``root``.
628
+ """
629
+ for edge in self.select(start=0, end=self._num_leaves, lhs=root):
630
+ for tree in self.trees(edge, tree_class=tree_class, complete=True):
631
+ yield tree
632
+
633
+ def trees(self, edge, tree_class=Tree, complete=False):
634
+ """
635
+ Return an iterator of the tree structures that are associated
636
+ with ``edge``.
637
+
638
+ If ``edge`` is incomplete, then the unexpanded children will be
639
+ encoded as childless subtrees, whose node value is the
640
+ corresponding terminal or nonterminal.
641
+
642
+ :rtype: list(Tree)
643
+ :note: If two trees share a common subtree, then the same
644
+ Tree may be used to encode that subtree in
645
+ both trees. If you need to eliminate this subtree
646
+ sharing, then create a deep copy of each tree.
647
+ """
648
+ return iter(self._trees(edge, complete, memo={}, tree_class=tree_class))
649
+
650
+ def _trees(self, edge, complete, memo, tree_class):
651
+ """
652
+ A helper function for ``trees``.
653
+
654
+ :param memo: A dictionary used to record the trees that we've
655
+ generated for each edge, so that when we see an edge more
656
+ than once, we can reuse the same trees.
657
+ """
658
+ # If we've seen this edge before, then reuse our old answer.
659
+ if edge in memo:
660
+ return memo[edge]
661
+
662
+ # when we're reading trees off the chart, don't use incomplete edges
663
+ if complete and edge.is_incomplete():
664
+ return []
665
+
666
+ # Leaf edges.
667
+ if isinstance(edge, LeafEdge):
668
+ leaf = self._tokens[edge.start()]
669
+ memo[edge] = [leaf]
670
+ return [leaf]
671
+
672
+ # Until we're done computing the trees for edge, set
673
+ # memo[edge] to be empty. This has the effect of filtering
674
+ # out any cyclic trees (i.e., trees that contain themselves as
675
+ # descendants), because if we reach this edge via a cycle,
676
+ # then it will appear that the edge doesn't generate any trees.
677
+ memo[edge] = []
678
+ trees = []
679
+ lhs = edge.lhs().symbol()
680
+
681
+ # Each child pointer list can be used to form trees.
682
+ for cpl in self.child_pointer_lists(edge):
683
+ # Get the set of child choices for each child pointer.
684
+ # child_choices[i] is the set of choices for the tree's
685
+ # ith child.
686
+ child_choices = [self._trees(cp, complete, memo, tree_class)
687
+ for cp in cpl]
688
+
689
+ # For each combination of children, add a tree.
690
+ for children in itertools.product(*child_choices):
691
+ trees.append(tree_class(lhs, children))
692
+
693
+ # If the edge is incomplete, then extend it with "partial trees":
694
+ if edge.is_incomplete():
695
+ unexpanded = [tree_class(elt,[])
696
+ for elt in edge.rhs()[edge.dot():]]
697
+ for tree in trees:
698
+ tree.extend(unexpanded)
699
+
700
+ # Update the memoization dictionary.
701
+ memo[edge] = trees
702
+
703
+ # Return the list of trees.
704
+ return trees
705
+
706
+ def child_pointer_lists(self, edge):
707
+ """
708
+ Return the set of child pointer lists for the given edge.
709
+ Each child pointer list is a list of edges that have
710
+ been used to form this edge.
711
+
712
+ :rtype: list(list(EdgeI))
713
+ """
714
+ # Make a copy, in case they modify it.
715
+ return self._edge_to_cpls.get(edge, {}).keys()
716
+
717
+ #////////////////////////////////////////////////////////////
718
+ # Display
719
+ #////////////////////////////////////////////////////////////
720
+ def pretty_format_edge(self, edge, width=None):
721
+ """
722
+ Return a pretty-printed string representation of a given edge
723
+ in this chart.
724
+
725
+ :rtype: str
726
+ :param width: The number of characters allotted to each
727
+ index in the sentence.
728
+ """
729
+ if width is None: width = 50 // (self.num_leaves()+1)
730
+ (start, end) = (edge.start(), edge.end())
731
+
732
+ str = '|' + ('.'+' '*(width-1))*start
733
+
734
+ # Zero-width edges are "#" if complete, ">" if incomplete
735
+ if start == end:
736
+ if edge.is_complete(): str += '#'
737
+ else: str += '>'
738
+
739
+ # Spanning complete edges are "[===]"; Other edges are
740
+ # "[---]" if complete, "[--->" if incomplete
741
+ elif edge.is_complete() and edge.span() == (0,self._num_leaves):
742
+ str += '['+('='*width)*(end-start-1) + '='*(width-1)+']'
743
+ elif edge.is_complete():
744
+ str += '['+('-'*width)*(end-start-1) + '-'*(width-1)+']'
745
+ else:
746
+ str += '['+('-'*width)*(end-start-1) + '-'*(width-1)+'>'
747
+
748
+ str += (' '*(width-1)+'.')*(self._num_leaves-end)
749
+ return str + '| %s' % edge
750
+
751
+ def pretty_format_leaves(self, width=None):
752
+ """
753
+ Return a pretty-printed string representation of this
754
+ chart's leaves. This string can be used as a header
755
+ for calls to ``pretty_format_edge``.
756
+ """
757
+ if width is None: width = 50 // (self.num_leaves()+1)
758
+
759
+ if self._tokens is not None and width>1:
760
+ header = '|.'
761
+ for tok in self._tokens:
762
+ header += tok[:width-1].center(width-1)+'.'
763
+ header += '|'
764
+ else:
765
+ header = ''
766
+
767
+ return header
768
+
769
+ def pretty_format(self, width=None):
770
+ """
771
+ Return a pretty-printed string representation of this chart.
772
+
773
+ :param width: The number of characters allotted to each
774
+ index in the sentence.
775
+ :rtype: str
776
+ """
777
+ if width is None: width = 50 // (self.num_leaves()+1)
778
+ # sort edges: primary key=length, secondary key=start index.
779
+ # (and filter out the token edges)
780
+ edges = sorted([(e.length(), e.start(), e) for e in self])
781
+ edges = [e for (_,_,e) in edges]
782
+
783
+ return (self.pretty_format_leaves(width) + '\n' +
784
+ '\n'.join(self.pretty_format_edge(edge, width) for edge in edges))
785
+
786
+ #////////////////////////////////////////////////////////////
787
+ # Display: Dot (AT&T Graphviz)
788
+ #////////////////////////////////////////////////////////////
789
+
790
+ def dot_digraph(self):
791
+ # Header
792
+ s = 'digraph nltk_chart {\n'
793
+ #s += ' size="5,5";\n'
794
+ s += ' rankdir=LR;\n'
795
+ s += ' node [height=0.1,width=0.1];\n'
796
+ s += ' node [style=filled, color="lightgray"];\n'
797
+
798
+ # Set up the nodes
799
+ for y in range(self.num_edges(), -1, -1):
800
+ if y == 0:
801
+ s += ' node [style=filled, color="black"];\n'
802
+ for x in range(self.num_leaves()+1):
803
+ if y == 0 or (x <= self._edges[y-1].start() or
804
+ x >= self._edges[y-1].end()):
805
+ s += ' %04d.%04d [label=""];\n' % (x,y)
806
+
807
+ # Add a spacer
808
+ s += ' x [style=invis]; x->0000.0000 [style=invis];\n'
809
+
810
+ # Declare ranks.
811
+ for x in range(self.num_leaves()+1):
812
+ s += ' {rank=same;'
813
+ for y in range(self.num_edges()+1):
814
+ if y == 0 or (x <= self._edges[y-1].start() or
815
+ x >= self._edges[y-1].end()):
816
+ s += ' %04d.%04d' % (x,y)
817
+ s += '}\n'
818
+
819
+ # Add the leaves
820
+ s += ' edge [style=invis, weight=100];\n'
821
+ s += ' node [shape=plaintext]\n'
822
+ s += ' 0000.0000'
823
+ for x in range(self.num_leaves()):
824
+ s += '->%s->%04d.0000' % (self.leaf(x), x+1)
825
+ s += ';\n\n'
826
+
827
+ # Add the edges
828
+ s += ' edge [style=solid, weight=1];\n'
829
+ for y, edge in enumerate(self):
830
+ for x in range(edge.start()):
831
+ s += (' %04d.%04d -> %04d.%04d [style="invis"];\n' %
832
+ (x, y+1, x+1, y+1))
833
+ s += (' %04d.%04d -> %04d.%04d [label="%s"];\n' %
834
+ (edge.start(), y+1, edge.end(), y+1, edge))
835
+ for x in range(edge.end(), self.num_leaves()):
836
+ s += (' %04d.%04d -> %04d.%04d [style="invis"];\n' %
837
+ (x, y+1, x+1, y+1))
838
+ s += '}\n'
839
+ return s
840
+
841
+ ########################################################################
842
+ ## Chart Rules
843
+ ########################################################################
844
+
845
+ class ChartRuleI(object):
846
+ """
847
+ A rule that specifies what new edges are licensed by any given set
848
+ of existing edges. Each chart rule expects a fixed number of
849
+ edges, as indicated by the class variable ``NUM_EDGES``. In
850
+ particular:
851
+
852
+ - A chart rule with ``NUM_EDGES=0`` specifies what new edges are
853
+ licensed, regardless of existing edges.
854
+ - A chart rule with ``NUM_EDGES=1`` specifies what new edges are
855
+ licensed by a single existing edge.
856
+ - A chart rule with ``NUM_EDGES=2`` specifies what new edges are
857
+ licensed by a pair of existing edges.
858
+
859
+ :type NUM_EDGES: int
860
+ :cvar NUM_EDGES: The number of existing edges that this rule uses
861
+ to license new edges. Typically, this number ranges from zero
862
+ to two.
863
+ """
864
+ def apply(self, chart, grammar, *edges):
865
+ """
866
+ Return a generator that will add edges licensed by this rule
867
+ and the given edges to the chart, one at a time. Each
868
+ time the generator is resumed, it will either add a new
869
+ edge and yield that edge; or return.
870
+
871
+ :type edges: list(EdgeI)
872
+ :param edges: A set of existing edges. The number of edges
873
+ that should be passed to ``apply()`` is specified by the
874
+ ``NUM_EDGES`` class variable.
875
+ :rtype: iter(EdgeI)
876
+ """
877
+ raise NotImplementedError()
878
+
879
+ def apply_everywhere(self, chart, grammar):
880
+ """
881
+ Return a generator that will add all edges licensed by
882
+ this rule, given the edges that are currently in the
883
+ chart, one at a time. Each time the generator is resumed,
884
+ it will either add a new edge and yield that edge; or return.
885
+
886
+ :rtype: iter(EdgeI)
887
+ """
888
+ raise NotImplementedError()
889
+
890
+
891
+ @python_2_unicode_compatible
892
+ class AbstractChartRule(ChartRuleI):
893
+ """
894
+ An abstract base class for chart rules. ``AbstractChartRule``
895
+ provides:
896
+
897
+ - A default implementation for ``apply``.
898
+ - A default implementation for ``apply_everywhere``,
899
+ (Currently, this implementation assumes that ``NUM_EDGES``<=3.)
900
+ - A default implementation for ``__str__``, which returns a
901
+ name based on the rule's class name.
902
+ """
903
+
904
+ # Subclasses must define apply.
905
+ def apply(self, chart, grammar, *edges):
906
+ raise NotImplementedError()
907
+
908
+ # Default: loop through the given number of edges, and call
909
+ # self.apply() for each set of edges.
910
+ def apply_everywhere(self, chart, grammar):
911
+ if self.NUM_EDGES == 0:
912
+ for new_edge in self.apply(chart, grammar):
913
+ yield new_edge
914
+
915
+ elif self.NUM_EDGES == 1:
916
+ for e1 in chart:
917
+ for new_edge in self.apply(chart, grammar, e1):
918
+ yield new_edge
919
+
920
+ elif self.NUM_EDGES == 2:
921
+ for e1 in chart:
922
+ for e2 in chart:
923
+ for new_edge in self.apply(chart, grammar, e1, e2):
924
+ yield new_edge
925
+
926
+ elif self.NUM_EDGES == 3:
927
+ for e1 in chart:
928
+ for e2 in chart:
929
+ for e3 in chart:
930
+ for new_edge in self.apply(chart,grammar,e1,e2,e3):
931
+ yield new_edge
932
+
933
+ else:
934
+ raise AssertionError('NUM_EDGES>3 is not currently supported')
935
+
936
+ # Default: return a name based on the class name.
937
+ def __str__(self):
938
+ # Add spaces between InitialCapsWords.
939
+ return re.sub('([a-z])([A-Z])', r'\1 \2', self.__class__.__name__)
940
+
941
+ #////////////////////////////////////////////////////////////
942
+ # Fundamental Rule
943
+ #////////////////////////////////////////////////////////////
944
+
945
+ class FundamentalRule(AbstractChartRule):
946
+ """
947
+ A rule that joins two adjacent edges to form a single combined
948
+ edge. In particular, this rule specifies that any pair of edges
949
+
950
+ - ``[A -> alpha \* B beta][i:j]``
951
+ - ``[B -> gamma \*][j:k]``
952
+
953
+ licenses the edge:
954
+
955
+ - ``[A -> alpha B * beta][i:j]``
956
+ """
957
+ NUM_EDGES = 2
958
+ def apply(self, chart, grammar, left_edge, right_edge):
959
+ # Make sure the rule is applicable.
960
+ if not (left_edge.is_incomplete() and
961
+ right_edge.is_complete() and
962
+ left_edge.end() == right_edge.start() and
963
+ left_edge.nextsym() == right_edge.lhs()):
964
+ return
965
+
966
+ # Construct the new edge.
967
+ new_edge = left_edge.move_dot_forward(right_edge.end())
968
+
969
+ # Insert it into the chart.
970
+ if chart.insert_with_backpointer(new_edge, left_edge, right_edge):
971
+ yield new_edge
972
+
973
+ class SingleEdgeFundamentalRule(FundamentalRule):
974
+ """
975
+ A rule that joins a given edge with adjacent edges in the chart,
976
+ to form combined edges. In particular, this rule specifies that
977
+ either of the edges:
978
+
979
+ - ``[A -> alpha \* B beta][i:j]``
980
+ - ``[B -> gamma \*][j:k]``
981
+
982
+ licenses the edge:
983
+
984
+ - ``[A -> alpha B * beta][i:j]``
985
+
986
+ if the other edge is already in the chart.
987
+
988
+ :note: This is basically ``FundamentalRule``, with one edge left
989
+ unspecified.
990
+ """
991
+ NUM_EDGES = 1
992
+
993
+ def apply(self, chart, grammar, edge):
994
+ if edge.is_incomplete():
995
+ for new_edge in self._apply_incomplete(chart, grammar, edge):
996
+ yield new_edge
997
+ else:
998
+ for new_edge in self._apply_complete(chart, grammar, edge):
999
+ yield new_edge
1000
+
1001
+ def _apply_complete(self, chart, grammar, right_edge):
1002
+ for left_edge in chart.select(end=right_edge.start(),
1003
+ is_complete=False,
1004
+ nextsym=right_edge.lhs()):
1005
+ new_edge = left_edge.move_dot_forward(right_edge.end())
1006
+ if chart.insert_with_backpointer(new_edge, left_edge, right_edge):
1007
+ yield new_edge
1008
+
1009
+ def _apply_incomplete(self, chart, grammar, left_edge):
1010
+ for right_edge in chart.select(start=left_edge.end(),
1011
+ is_complete=True,
1012
+ lhs=left_edge.nextsym()):
1013
+ new_edge = left_edge.move_dot_forward(right_edge.end())
1014
+ if chart.insert_with_backpointer(new_edge, left_edge, right_edge):
1015
+ yield new_edge
1016
+
1017
+ #////////////////////////////////////////////////////////////
1018
+ # Inserting Terminal Leafs
1019
+ #////////////////////////////////////////////////////////////
1020
+
1021
+ class LeafInitRule(AbstractChartRule):
1022
+ NUM_EDGES=0
1023
+ def apply(self, chart, grammar):
1024
+ for index in range(chart.num_leaves()):
1025
+ new_edge = LeafEdge(chart.leaf(index), index)
1026
+ if chart.insert(new_edge, ()):
1027
+ yield new_edge
1028
+
1029
+ #////////////////////////////////////////////////////////////
1030
+ # Top-Down Prediction
1031
+ #////////////////////////////////////////////////////////////
1032
+
1033
+ class TopDownInitRule(AbstractChartRule):
1034
+ """
1035
+ A rule licensing edges corresponding to the grammar productions for
1036
+ the grammar's start symbol. In particular, this rule specifies that
1037
+ ``[S -> \* alpha][0:i]`` is licensed for each grammar production
1038
+ ``S -> alpha``, where ``S`` is the grammar's start symbol.
1039
+ """
1040
+ NUM_EDGES = 0
1041
+ def apply(self, chart, grammar):
1042
+ for prod in grammar.productions(lhs=grammar.start()):
1043
+ new_edge = TreeEdge.from_production(prod, 0)
1044
+ if chart.insert(new_edge, ()):
1045
+ yield new_edge
1046
+
1047
+ class TopDownPredictRule(AbstractChartRule):
1048
+ """
1049
+ A rule licensing edges corresponding to the grammar productions
1050
+ for the nonterminal following an incomplete edge's dot. In
1051
+ particular, this rule specifies that
1052
+ ``[A -> alpha \* B beta][i:j]`` licenses the edge
1053
+ ``[B -> \* gamma][j:j]`` for each grammar production ``B -> gamma``.
1054
+
1055
+ :note: This rule corresponds to the Predictor Rule in Earley parsing.
1056
+ """
1057
+ NUM_EDGES = 1
1058
+ def apply(self, chart, grammar, edge):
1059
+ if edge.is_complete(): return
1060
+ for prod in grammar.productions(lhs=edge.nextsym()):
1061
+ new_edge = TreeEdge.from_production(prod, edge.end())
1062
+ if chart.insert(new_edge, ()):
1063
+ yield new_edge
1064
+
1065
+ class CachedTopDownPredictRule(TopDownPredictRule):
1066
+ """
1067
+ A cached version of ``TopDownPredictRule``. After the first time
1068
+ this rule is applied to an edge with a given ``end`` and ``next``,
1069
+ it will not generate any more edges for edges with that ``end`` and
1070
+ ``next``.
1071
+
1072
+ If ``chart`` or ``grammar`` are changed, then the cache is flushed.
1073
+ """
1074
+ def __init__(self):
1075
+ TopDownPredictRule.__init__(self)
1076
+ self._done = {}
1077
+
1078
+ def apply(self, chart, grammar, edge):
1079
+ if edge.is_complete(): return
1080
+ nextsym, index = edge.nextsym(), edge.end()
1081
+ if not is_nonterminal(nextsym): return
1082
+
1083
+ # If we've already applied this rule to an edge with the same
1084
+ # next & end, and the chart & grammar have not changed, then
1085
+ # just return (no new edges to add).
1086
+ done = self._done.get((nextsym, index), (None,None))
1087
+ if done[0] is chart and done[1] is grammar: return
1088
+
1089
+ # Add all the edges indicated by the top down expand rule.
1090
+ for prod in grammar.productions(lhs=nextsym):
1091
+ # If the left corner in the predicted production is
1092
+ # leaf, it must match with the input.
1093
+ if prod.rhs():
1094
+ first = prod.rhs()[0]
1095
+ if is_terminal(first):
1096
+ if index >= chart.num_leaves() or first != chart.leaf(index): continue
1097
+
1098
+ new_edge = TreeEdge.from_production(prod, index)
1099
+ if chart.insert(new_edge, ()):
1100
+ yield new_edge
1101
+
1102
+ # Record the fact that we've applied this rule.
1103
+ self._done[nextsym, index] = (chart, grammar)
1104
+
1105
+ #////////////////////////////////////////////////////////////
1106
+ # Bottom-Up Prediction
1107
+ #////////////////////////////////////////////////////////////
1108
+
1109
+ class BottomUpPredictRule(AbstractChartRule):
1110
+ """
1111
+ A rule licensing any edge corresponding to a production whose
1112
+ right-hand side begins with a complete edge's left-hand side. In
1113
+ particular, this rule specifies that ``[A -> alpha \*]`` licenses
1114
+ the edge ``[B -> \* A beta]`` for each grammar production ``B -> A beta``.
1115
+ """
1116
+ NUM_EDGES = 1
1117
+ def apply(self, chart, grammar, edge):
1118
+ if edge.is_incomplete(): return
1119
+ for prod in grammar.productions(rhs=edge.lhs()):
1120
+ new_edge = TreeEdge.from_production(prod, edge.start())
1121
+ if chart.insert(new_edge, ()):
1122
+ yield new_edge
1123
+
1124
+ class BottomUpPredictCombineRule(BottomUpPredictRule):
1125
+ """
1126
+ A rule licensing any edge corresponding to a production whose
1127
+ right-hand side begins with a complete edge's left-hand side. In
1128
+ particular, this rule specifies that ``[A -> alpha \*]``
1129
+ licenses the edge ``[B -> A \* beta]`` for each grammar
1130
+ production ``B -> A beta``.
1131
+
1132
+ :note: This is like ``BottomUpPredictRule``, but it also applies
1133
+ the ``FundamentalRule`` to the resulting edge.
1134
+ """
1135
+ NUM_EDGES = 1
1136
+ def apply(self, chart, grammar, edge):
1137
+ if edge.is_incomplete(): return
1138
+ for prod in grammar.productions(rhs=edge.lhs()):
1139
+ new_edge = TreeEdge(edge.span(), prod.lhs(), prod.rhs(), 1)
1140
+ if chart.insert(new_edge, (edge,)):
1141
+ yield new_edge
1142
+
1143
+ class EmptyPredictRule(AbstractChartRule):
1144
+ """
1145
+ A rule that inserts all empty productions as passive edges,
1146
+ in every position in the chart.
1147
+ """
1148
+ NUM_EDGES = 0
1149
+ def apply(self, chart, grammar):
1150
+ for prod in grammar.productions(empty=True):
1151
+ for index in range(chart.num_leaves() + 1):
1152
+ new_edge = TreeEdge.from_production(prod, index)
1153
+ if chart.insert(new_edge, ()):
1154
+ yield new_edge
1155
+
1156
+
1157
+ ########################################################################
1158
+ ## Filtered Bottom Up
1159
+ ########################################################################
1160
+
1161
+ class FilteredSingleEdgeFundamentalRule(SingleEdgeFundamentalRule):
1162
+ def _apply_complete(self, chart, grammar, right_edge):
1163
+ end = right_edge.end()
1164
+ nexttoken = end < chart.num_leaves() and chart.leaf(end)
1165
+ for left_edge in chart.select(end=right_edge.start(),
1166
+ is_complete=False,
1167
+ nextsym=right_edge.lhs()):
1168
+ if _bottomup_filter(grammar, nexttoken, left_edge.rhs(), left_edge.dot()):
1169
+ new_edge = left_edge.move_dot_forward(right_edge.end())
1170
+ if chart.insert_with_backpointer(new_edge, left_edge, right_edge):
1171
+ yield new_edge
1172
+
1173
+ def _apply_incomplete(self, chart, grammar, left_edge):
1174
+ for right_edge in chart.select(start=left_edge.end(),
1175
+ is_complete=True,
1176
+ lhs=left_edge.nextsym()):
1177
+ end = right_edge.end()
1178
+ nexttoken = end < chart.num_leaves() and chart.leaf(end)
1179
+ if _bottomup_filter(grammar, nexttoken, left_edge.rhs(), left_edge.dot()):
1180
+ new_edge = left_edge.move_dot_forward(right_edge.end())
1181
+ if chart.insert_with_backpointer(new_edge, left_edge, right_edge):
1182
+ yield new_edge
1183
+
1184
+ class FilteredBottomUpPredictCombineRule(BottomUpPredictCombineRule):
1185
+ def apply(self, chart, grammar, edge):
1186
+ if edge.is_incomplete():
1187
+ return
1188
+
1189
+ end = edge.end()
1190
+ nexttoken = end < chart.num_leaves() and chart.leaf(end)
1191
+ for prod in grammar.productions(rhs=edge.lhs()):
1192
+ if _bottomup_filter(grammar, nexttoken, prod.rhs()):
1193
+ new_edge = TreeEdge(edge.span(), prod.lhs(), prod.rhs(), 1)
1194
+ if chart.insert(new_edge, (edge,)):
1195
+ yield new_edge
1196
+
1197
+ def _bottomup_filter(grammar, nexttoken, rhs, dot=0):
1198
+ if len(rhs) <= dot + 1:
1199
+ return True
1200
+ _next = rhs[dot + 1]
1201
+ if is_terminal(_next):
1202
+ return nexttoken == _next
1203
+ else:
1204
+ return grammar.is_leftcorner(_next, nexttoken)
1205
+
1206
+
1207
+ ########################################################################
1208
+ ## Generic Chart Parser
1209
+ ########################################################################
1210
+
1211
+ TD_STRATEGY = [LeafInitRule(),
1212
+ TopDownInitRule(),
1213
+ CachedTopDownPredictRule(),
1214
+ SingleEdgeFundamentalRule()]
1215
+ BU_STRATEGY = [LeafInitRule(),
1216
+ EmptyPredictRule(),
1217
+ BottomUpPredictRule(),
1218
+ SingleEdgeFundamentalRule()]
1219
+ BU_LC_STRATEGY = [LeafInitRule(),
1220
+ EmptyPredictRule(),
1221
+ BottomUpPredictCombineRule(),
1222
+ SingleEdgeFundamentalRule()]
1223
+
1224
+ LC_STRATEGY = [LeafInitRule(),
1225
+ FilteredBottomUpPredictCombineRule(),
1226
+ FilteredSingleEdgeFundamentalRule()]
1227
+
1228
+ class ChartParser(ParserI):
1229
+ """
1230
+ A generic chart parser. A "strategy", or list of
1231
+ ``ChartRuleI`` instances, is used to decide what edges to add to
1232
+ the chart. In particular, ``ChartParser`` uses the following
1233
+ algorithm to parse texts:
1234
+
1235
+ | Until no new edges are added:
1236
+ | For each *rule* in *strategy*:
1237
+ | Apply *rule* to any applicable edges in the chart.
1238
+ | Return any complete parses in the chart
1239
+ """
1240
+ def __init__(self, grammar, strategy=BU_LC_STRATEGY, trace=0,
1241
+ trace_chart_width=50, use_agenda=True, chart_class=Chart):
1242
+ """
1243
+ Create a new chart parser, that uses ``grammar`` to parse
1244
+ texts.
1245
+
1246
+ :type grammar: CFG
1247
+ :param grammar: The grammar used to parse texts.
1248
+ :type strategy: list(ChartRuleI)
1249
+ :param strategy: A list of rules that should be used to decide
1250
+ what edges to add to the chart (top-down strategy by default).
1251
+ :type trace: int
1252
+ :param trace: The level of tracing that should be used when
1253
+ parsing a text. ``0`` will generate no tracing output;
1254
+ and higher numbers will produce more verbose tracing
1255
+ output.
1256
+ :type trace_chart_width: int
1257
+ :param trace_chart_width: The default total width reserved for
1258
+ the chart in trace output. The remainder of each line will
1259
+ be used to display edges.
1260
+ :type use_agenda: bool
1261
+ :param use_agenda: Use an optimized agenda-based algorithm,
1262
+ if possible.
1263
+ :param chart_class: The class that should be used to create
1264
+ the parse charts.
1265
+ """
1266
+ self._grammar = grammar
1267
+ self._strategy = strategy
1268
+ self._trace = trace
1269
+ self._trace_chart_width = trace_chart_width
1270
+ # If the strategy only consists of axioms (NUM_EDGES==0) and
1271
+ # inference rules (NUM_EDGES==1), we can use an agenda-based algorithm:
1272
+ self._use_agenda = use_agenda
1273
+ self._chart_class = chart_class
1274
+
1275
+ self._axioms = []
1276
+ self._inference_rules = []
1277
+ for rule in strategy:
1278
+ if rule.NUM_EDGES == 0:
1279
+ self._axioms.append(rule)
1280
+ elif rule.NUM_EDGES == 1:
1281
+ self._inference_rules.append(rule)
1282
+ else:
1283
+ self._use_agenda = False
1284
+
1285
+ def grammar(self):
1286
+ return self._grammar
1287
+
1288
+ def _trace_new_edges(self, chart, rule, new_edges, trace, edge_width):
1289
+ if not trace: return
1290
+ print_rule_header = trace > 1
1291
+ for edge in new_edges:
1292
+ if print_rule_header:
1293
+ print('%s:' % rule)
1294
+ print_rule_header = False
1295
+ print(chart.pretty_format_edge(edge, edge_width))
1296
+
1297
+ def chart_parse(self, tokens, trace=None):
1298
+ """
1299
+ Return the final parse ``Chart`` from which all possible
1300
+ parse trees can be extracted.
1301
+
1302
+ :param tokens: The sentence to be parsed
1303
+ :type tokens: list(str)
1304
+ :rtype: Chart
1305
+ """
1306
+ if trace is None: trace = self._trace
1307
+ trace_new_edges = self._trace_new_edges
1308
+
1309
+ tokens = list(tokens)
1310
+ self._grammar.check_coverage(tokens)
1311
+ chart = self._chart_class(tokens)
1312
+ grammar = self._grammar
1313
+
1314
+ # Width, for printing trace edges.
1315
+ trace_edge_width = self._trace_chart_width // (chart.num_leaves() + 1)
1316
+ if trace: print(chart.pretty_format_leaves(trace_edge_width))
1317
+
1318
+ if self._use_agenda:
1319
+ # Use an agenda-based algorithm.
1320
+ for axiom in self._axioms:
1321
+ new_edges = list(axiom.apply(chart, grammar))
1322
+ trace_new_edges(chart, axiom, new_edges, trace, trace_edge_width)
1323
+
1324
+ inference_rules = self._inference_rules
1325
+ agenda = chart.edges()
1326
+ # We reverse the initial agenda, since it is a stack
1327
+ # but chart.edges() functions as a queue.
1328
+ agenda.reverse()
1329
+ while agenda:
1330
+ edge = agenda.pop()
1331
+ for rule in inference_rules:
1332
+ new_edges = list(rule.apply(chart, grammar, edge))
1333
+ if trace:
1334
+ trace_new_edges(chart, rule, new_edges, trace, trace_edge_width)
1335
+ agenda += new_edges
1336
+
1337
+ else:
1338
+ # Do not use an agenda-based algorithm.
1339
+ edges_added = True
1340
+ while edges_added:
1341
+ edges_added = False
1342
+ for rule in self._strategy:
1343
+ new_edges = list(rule.apply_everywhere(chart, grammar))
1344
+ edges_added = len(new_edges)
1345
+ trace_new_edges(chart, rule, new_edges, trace, trace_edge_width)
1346
+
1347
+ # Return the final chart.
1348
+ return chart
1349
+
1350
+ def parse(self, tokens, tree_class=Tree):
1351
+ chart = self.chart_parse(tokens)
1352
+ return iter(chart.parses(self._grammar.start(), tree_class=tree_class))
1353
+
1354
+ class TopDownChartParser(ChartParser):
1355
+ """
1356
+ A ``ChartParser`` using a top-down parsing strategy.
1357
+ See ``ChartParser`` for more information.
1358
+ """
1359
+ def __init__(self, grammar, **parser_args):
1360
+ ChartParser.__init__(self, grammar, TD_STRATEGY, **parser_args)
1361
+
1362
+ class BottomUpChartParser(ChartParser):
1363
+ """
1364
+ A ``ChartParser`` using a bottom-up parsing strategy.
1365
+ See ``ChartParser`` for more information.
1366
+ """
1367
+ def __init__(self, grammar, **parser_args):
1368
+ if isinstance(grammar, PCFG):
1369
+ warnings.warn("BottomUpChartParser only works for CFG, "
1370
+ "use BottomUpProbabilisticChartParser instead",
1371
+ category=DeprecationWarning)
1372
+ ChartParser.__init__(self, grammar, BU_STRATEGY, **parser_args)
1373
+
1374
+ class BottomUpLeftCornerChartParser(ChartParser):
1375
+ """
1376
+ A ``ChartParser`` using a bottom-up left-corner parsing strategy.
1377
+ This strategy is often more efficient than standard bottom-up.
1378
+ See ``ChartParser`` for more information.
1379
+ """
1380
+ def __init__(self, grammar, **parser_args):
1381
+ ChartParser.__init__(self, grammar, BU_LC_STRATEGY, **parser_args)
1382
+
1383
+ class LeftCornerChartParser(ChartParser):
1384
+ def __init__(self, grammar, **parser_args):
1385
+ if not grammar.is_nonempty():
1386
+ raise ValueError("LeftCornerParser only works for grammars "
1387
+ "without empty productions.")
1388
+ ChartParser.__init__(self, grammar, LC_STRATEGY, **parser_args)
1389
+
1390
+ ########################################################################
1391
+ ## Stepping Chart Parser
1392
+ ########################################################################
1393
+
1394
+ class SteppingChartParser(ChartParser):
1395
+ """
1396
+ A ``ChartParser`` that allows you to step through the parsing
1397
+ process, adding a single edge at a time. It also allows you to
1398
+ change the parser's strategy or grammar midway through parsing a
1399
+ text.
1400
+
1401
+ The ``initialize`` method is used to start parsing a text. ``step``
1402
+ adds a single edge to the chart. ``set_strategy`` changes the
1403
+ strategy used by the chart parser. ``parses`` returns the set of
1404
+ parses that has been found by the chart parser.
1405
+
1406
+ :ivar _restart: Records whether the parser's strategy, grammar,
1407
+ or chart has been changed. If so, then ``step`` must restart
1408
+ the parsing algorithm.
1409
+ """
1410
+ def __init__(self, grammar, strategy=[], trace=0):
1411
+ self._chart = None
1412
+ self._current_chartrule = None
1413
+ self._restart = False
1414
+ ChartParser.__init__(self, grammar, strategy, trace)
1415
+
1416
+ #////////////////////////////////////////////////////////////
1417
+ # Initialization
1418
+ #////////////////////////////////////////////////////////////
1419
+
1420
+ def initialize(self, tokens):
1421
+ "Begin parsing the given tokens."
1422
+ self._chart = Chart(list(tokens))
1423
+ self._restart = True
1424
+
1425
+ #////////////////////////////////////////////////////////////
1426
+ # Stepping
1427
+ #////////////////////////////////////////////////////////////
1428
+
1429
+ def step(self):
1430
+ """
1431
+ Return a generator that adds edges to the chart, one at a
1432
+ time. Each time the generator is resumed, it adds a single
1433
+ edge and yields that edge. If no more edges can be added,
1434
+ then it yields None.
1435
+
1436
+ If the parser's strategy, grammar, or chart is changed, then
1437
+ the generator will continue adding edges using the new
1438
+ strategy, grammar, or chart.
1439
+
1440
+ Note that this generator never terminates, since the grammar
1441
+ or strategy might be changed to values that would add new
1442
+ edges. Instead, it yields None when no more edges can be
1443
+ added with the current strategy and grammar.
1444
+ """
1445
+ if self._chart is None:
1446
+ raise ValueError('Parser must be initialized first')
1447
+ while True:
1448
+ self._restart = False
1449
+ w = 50 // (self._chart.num_leaves()+1)
1450
+
1451
+ for e in self._parse():
1452
+ if self._trace > 1: print(self._current_chartrule)
1453
+ if self._trace > 0: print(self._chart.pretty_format_edge(e,w))
1454
+ yield e
1455
+ if self._restart: break
1456
+ else:
1457
+ yield None # No more edges.
1458
+
1459
+ def _parse(self):
1460
+ """
1461
+ A generator that implements the actual parsing algorithm.
1462
+ ``step`` iterates through this generator, and restarts it
1463
+ whenever the parser's strategy, grammar, or chart is modified.
1464
+ """
1465
+ chart = self._chart
1466
+ grammar = self._grammar
1467
+ edges_added = 1
1468
+ while edges_added > 0:
1469
+ edges_added = 0
1470
+ for rule in self._strategy:
1471
+ self._current_chartrule = rule
1472
+ for e in rule.apply_everywhere(chart, grammar):
1473
+ edges_added += 1
1474
+ yield e
1475
+
1476
+ #////////////////////////////////////////////////////////////
1477
+ # Accessors
1478
+ #////////////////////////////////////////////////////////////
1479
+
1480
+ def strategy(self):
1481
+ "Return the strategy used by this parser."
1482
+ return self._strategy
1483
+
1484
+ def grammar(self):
1485
+ "Return the grammar used by this parser."
1486
+ return self._grammar
1487
+
1488
+ def chart(self):
1489
+ "Return the chart that is used by this parser."
1490
+ return self._chart
1491
+
1492
+ def current_chartrule(self):
1493
+ "Return the chart rule used to generate the most recent edge."
1494
+ return self._current_chartrule
1495
+
1496
+ def parses(self, tree_class=Tree):
1497
+ "Return the parse trees currently contained in the chart."
1498
+ return self._chart.parses(self._grammar.start(), tree_class)
1499
+
1500
+ #////////////////////////////////////////////////////////////
1501
+ # Parser modification
1502
+ #////////////////////////////////////////////////////////////
1503
+
1504
+ def set_strategy(self, strategy):
1505
+ """
1506
+ Change the strategy that the parser uses to decide which edges
1507
+ to add to the chart.
1508
+
1509
+ :type strategy: list(ChartRuleI)
1510
+ :param strategy: A list of rules that should be used to decide
1511
+ what edges to add to the chart.
1512
+ """
1513
+ if strategy == self._strategy: return
1514
+ self._strategy = strategy[:] # Make a copy.
1515
+ self._restart = True
1516
+
1517
+ def set_grammar(self, grammar):
1518
+ "Change the grammar used by the parser."
1519
+ if grammar is self._grammar: return
1520
+ self._grammar = grammar
1521
+ self._restart = True
1522
+
1523
+ def set_chart(self, chart):
1524
+ "Load a given chart into the chart parser."
1525
+ if chart is self._chart: return
1526
+ self._chart = chart
1527
+ self._restart = True
1528
+
1529
+ #////////////////////////////////////////////////////////////
1530
+ # Standard parser methods
1531
+ #////////////////////////////////////////////////////////////
1532
+
1533
+ def parse(self, tokens, tree_class=Tree):
1534
+ tokens = list(tokens)
1535
+ self._grammar.check_coverage(tokens)
1536
+
1537
+ # Initialize ourselves.
1538
+ self.initialize(tokens)
1539
+
1540
+ # Step until no more edges are generated.
1541
+ for e in self.step():
1542
+ if e is None: break
1543
+
1544
+ # Return an iterator of complete parses.
1545
+ return self.parses(tree_class=tree_class)
1546
+
1547
+ ########################################################################
1548
+ ## Demo Code
1549
+ ########################################################################
1550
+
1551
+ def demo_grammar():
1552
+ from nltk.grammar import CFG
1553
+ return CFG.fromstring("""
1554
+ S -> NP VP
1555
+ PP -> "with" NP
1556
+ NP -> NP PP
1557
+ VP -> VP PP
1558
+ VP -> Verb NP
1559
+ VP -> Verb
1560
+ NP -> Det Noun
1561
+ NP -> "John"
1562
+ NP -> "I"
1563
+ Det -> "the"
1564
+ Det -> "my"
1565
+ Det -> "a"
1566
+ Noun -> "dog"
1567
+ Noun -> "cookie"
1568
+ Verb -> "ate"
1569
+ Verb -> "saw"
1570
+ Prep -> "with"
1571
+ Prep -> "under"
1572
+ """)
1573
+
1574
+ def demo(choice=None,
1575
+ print_times=True, print_grammar=False,
1576
+ print_trees=True, trace=2,
1577
+ sent='I saw John with a dog with my cookie', numparses=5):
1578
+ """
1579
+ A demonstration of the chart parsers.
1580
+ """
1581
+ import sys, time
1582
+ from nltk import nonterminals, Production, CFG
1583
+
1584
+ # The grammar for ChartParser and SteppingChartParser:
1585
+ grammar = demo_grammar()
1586
+ if print_grammar:
1587
+ print("* Grammar")
1588
+ print(grammar)
1589
+
1590
+ # Tokenize the sample sentence.
1591
+ print("* Sentence:")
1592
+ print(sent)
1593
+ tokens = sent.split()
1594
+ print(tokens)
1595
+ print()
1596
+
1597
+ # Ask the user which parser to test,
1598
+ # if the parser wasn't provided as an argument
1599
+ if choice is None:
1600
+ print(' 1: Top-down chart parser')
1601
+ print(' 2: Bottom-up chart parser')
1602
+ print(' 3: Bottom-up left-corner chart parser')
1603
+ print(' 4: Left-corner chart parser with bottom-up filter')
1604
+ print(' 5: Stepping chart parser (alternating top-down & bottom-up)')
1605
+ print(' 6: All parsers')
1606
+ print('\nWhich parser (1-6)? ', end=' ')
1607
+ choice = sys.stdin.readline().strip()
1608
+ print()
1609
+
1610
+ choice = str(choice)
1611
+ if choice not in "123456":
1612
+ print('Bad parser number')
1613
+ return
1614
+
1615
+ # Keep track of how long each parser takes.
1616
+ times = {}
1617
+
1618
+ strategies = {'1': ('Top-down', TD_STRATEGY),
1619
+ '2': ('Bottom-up', BU_STRATEGY),
1620
+ '3': ('Bottom-up left-corner', BU_LC_STRATEGY),
1621
+ '4': ('Filtered left-corner', LC_STRATEGY)}
1622
+ choices = []
1623
+ if choice in strategies: choices = [choice]
1624
+ if choice=='6': choices = "1234"
1625
+
1626
+ # Run the requested chart parser(s), except the stepping parser.
1627
+ for strategy in choices:
1628
+ print("* Strategy: " + strategies[strategy][0])
1629
+ print()
1630
+ cp = ChartParser(grammar, strategies[strategy][1], trace=trace)
1631
+ t = time.time()
1632
+ chart = cp.chart_parse(tokens)
1633
+ parses = list(chart.parses(grammar.start()))
1634
+
1635
+ times[strategies[strategy][0]] = time.time()-t
1636
+ print("Nr edges in chart:", len(chart.edges()))
1637
+ if numparses:
1638
+ assert len(parses)==numparses, 'Not all parses found'
1639
+ if print_trees:
1640
+ for tree in parses: print(tree)
1641
+ else:
1642
+ print("Nr trees:", len(parses))
1643
+ print()
1644
+
1645
+ # Run the stepping parser, if requested.
1646
+ if choice in "56":
1647
+ print("* Strategy: Stepping (top-down vs bottom-up)")
1648
+ print()
1649
+ t = time.time()
1650
+ cp = SteppingChartParser(grammar, trace=trace)
1651
+ cp.initialize(tokens)
1652
+ for i in range(5):
1653
+ print('*** SWITCH TO TOP DOWN')
1654
+ cp.set_strategy(TD_STRATEGY)
1655
+ for j, e in enumerate(cp.step()):
1656
+ if j>20 or e is None: break
1657
+ print('*** SWITCH TO BOTTOM UP')
1658
+ cp.set_strategy(BU_STRATEGY)
1659
+ for j, e in enumerate(cp.step()):
1660
+ if j>20 or e is None: break
1661
+ times['Stepping'] = time.time()-t
1662
+ print("Nr edges in chart:", len(cp.chart().edges()))
1663
+ if numparses:
1664
+ assert len(list(cp.parses()))==numparses, 'Not all parses found'
1665
+ if print_trees:
1666
+ for tree in cp.parses(): print(tree)
1667
+ else:
1668
+ print("Nr trees:", len(list(cp.parses())))
1669
+ print()
1670
+
1671
+ # Print the times of all parsers:
1672
+ if not (print_times and times): return
1673
+ print("* Parsing times")
1674
+ print()
1675
+ maxlen = max(len(key) for key in times)
1676
+ format = '%' + repr(maxlen) + 's parser: %6.3fsec'
1677
+ times_items = times.items()
1678
+ for (parser, t) in sorted(times_items, key=lambda a:a[1]):
1679
+ print(format % (parser, t))
1680
+
1681
+ if __name__ == '__main__':
1682
+ demo()