drt.py 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464
  1. # Natural Language Toolkit: Discourse Representation Theory (DRT)
  2. #
  3. # Author: Dan Garrette <dhgarrette@gmail.com>
  4. #
  5. # Copyright (C) 2001-2019 NLTK Project
  6. # URL: <http://nltk.org/>
  7. # For license information, see LICENSE.TXT
  8. from __future__ import print_function, unicode_literals
  9. import operator
  10. from functools import reduce
  11. from itertools import chain
  12. from six import string_types
  13. from nltk.compat import python_2_unicode_compatible
  14. from nltk.sem.logic import (
  15. APP,
  16. AbstractVariableExpression,
  17. AllExpression,
  18. AndExpression,
  19. ApplicationExpression,
  20. BinaryExpression,
  21. BooleanExpression,
  22. ConstantExpression,
  23. EqualityExpression,
  24. EventVariableExpression,
  25. ExistsExpression,
  26. Expression,
  27. FunctionVariableExpression,
  28. ImpExpression,
  29. IndividualVariableExpression,
  30. LambdaExpression,
  31. Tokens,
  32. LogicParser,
  33. NegatedExpression,
  34. OrExpression,
  35. Variable,
  36. is_eventvar,
  37. is_funcvar,
  38. is_indvar,
  39. unique_variable,
  40. )
  41. # Import Tkinter-based modules if they are available
  42. try:
  43. from six.moves.tkinter import Canvas, Tk
  44. from six.moves.tkinter_font import Font
  45. from nltk.util import in_idle
  46. except ImportError:
  47. # No need to print a warning here, nltk.draw has already printed one.
  48. pass
  49. class DrtTokens(Tokens):
  50. DRS = 'DRS'
  51. DRS_CONC = '+'
  52. PRONOUN = 'PRO'
  53. OPEN_BRACKET = '['
  54. CLOSE_BRACKET = ']'
  55. COLON = ':'
  56. PUNCT = [DRS_CONC, OPEN_BRACKET, CLOSE_BRACKET, COLON]
  57. SYMBOLS = Tokens.SYMBOLS + PUNCT
  58. TOKENS = Tokens.TOKENS + [DRS] + PUNCT
  59. class DrtParser(LogicParser):
  60. """A lambda calculus expression parser."""
  61. def __init__(self):
  62. LogicParser.__init__(self)
  63. self.operator_precedence = dict(
  64. [(x, 1) for x in DrtTokens.LAMBDA_LIST]
  65. + [(x, 2) for x in DrtTokens.NOT_LIST]
  66. + [(APP, 3)]
  67. + [(x, 4) for x in DrtTokens.EQ_LIST + Tokens.NEQ_LIST]
  68. + [(DrtTokens.COLON, 5)]
  69. + [(DrtTokens.DRS_CONC, 6)]
  70. + [(x, 7) for x in DrtTokens.OR_LIST]
  71. + [(x, 8) for x in DrtTokens.IMP_LIST]
  72. + [(None, 9)]
  73. )
  74. def get_all_symbols(self):
  75. """This method exists to be overridden"""
  76. return DrtTokens.SYMBOLS
  77. def isvariable(self, tok):
  78. return tok not in DrtTokens.TOKENS
  79. def handle(self, tok, context):
  80. """This method is intended to be overridden for logics that
  81. use different operators or expressions"""
  82. if tok in DrtTokens.NOT_LIST:
  83. return self.handle_negation(tok, context)
  84. elif tok in DrtTokens.LAMBDA_LIST:
  85. return self.handle_lambda(tok, context)
  86. elif tok == DrtTokens.OPEN:
  87. if self.inRange(0) and self.token(0) == DrtTokens.OPEN_BRACKET:
  88. return self.handle_DRS(tok, context)
  89. else:
  90. return self.handle_open(tok, context)
  91. elif tok.upper() == DrtTokens.DRS:
  92. self.assertNextToken(DrtTokens.OPEN)
  93. return self.handle_DRS(tok, context)
  94. elif self.isvariable(tok):
  95. if self.inRange(0) and self.token(0) == DrtTokens.COLON:
  96. return self.handle_prop(tok, context)
  97. else:
  98. return self.handle_variable(tok, context)
  99. def make_NegatedExpression(self, expression):
  100. return DrtNegatedExpression(expression)
  101. def handle_DRS(self, tok, context):
  102. # a DRS
  103. refs = self.handle_refs()
  104. if (
  105. self.inRange(0) and self.token(0) == DrtTokens.COMMA
  106. ): # if there is a comma (it's optional)
  107. self.token() # swallow the comma
  108. conds = self.handle_conds(context)
  109. self.assertNextToken(DrtTokens.CLOSE)
  110. return DRS(refs, conds, None)
  111. def handle_refs(self):
  112. self.assertNextToken(DrtTokens.OPEN_BRACKET)
  113. refs = []
  114. while self.inRange(0) and self.token(0) != DrtTokens.CLOSE_BRACKET:
  115. # Support expressions like: DRS([x y],C) == DRS([x,y],C)
  116. if refs and self.token(0) == DrtTokens.COMMA:
  117. self.token() # swallow the comma
  118. refs.append(self.get_next_token_variable('quantified'))
  119. self.assertNextToken(DrtTokens.CLOSE_BRACKET)
  120. return refs
  121. def handle_conds(self, context):
  122. self.assertNextToken(DrtTokens.OPEN_BRACKET)
  123. conds = []
  124. while self.inRange(0) and self.token(0) != DrtTokens.CLOSE_BRACKET:
  125. # Support expressions like: DRS([x y],C) == DRS([x, y],C)
  126. if conds and self.token(0) == DrtTokens.COMMA:
  127. self.token() # swallow the comma
  128. conds.append(self.process_next_expression(context))
  129. self.assertNextToken(DrtTokens.CLOSE_BRACKET)
  130. return conds
  131. def handle_prop(self, tok, context):
  132. variable = self.make_VariableExpression(tok)
  133. self.assertNextToken(':')
  134. drs = self.process_next_expression(DrtTokens.COLON)
  135. return DrtProposition(variable, drs)
  136. def make_EqualityExpression(self, first, second):
  137. """This method serves as a hook for other logic parsers that
  138. have different equality expression classes"""
  139. return DrtEqualityExpression(first, second)
  140. def get_BooleanExpression_factory(self, tok):
  141. """This method serves as a hook for other logic parsers that
  142. have different boolean operators"""
  143. if tok == DrtTokens.DRS_CONC:
  144. return lambda first, second: DrtConcatenation(first, second, None)
  145. elif tok in DrtTokens.OR_LIST:
  146. return DrtOrExpression
  147. elif tok in DrtTokens.IMP_LIST:
  148. def make_imp_expression(first, second):
  149. if isinstance(first, DRS):
  150. return DRS(first.refs, first.conds, second)
  151. if isinstance(first, DrtConcatenation):
  152. return DrtConcatenation(first.first, first.second, second)
  153. raise Exception('Antecedent of implication must be a DRS')
  154. return make_imp_expression
  155. else:
  156. return None
  157. def make_BooleanExpression(self, factory, first, second):
  158. return factory(first, second)
  159. def make_ApplicationExpression(self, function, argument):
  160. return DrtApplicationExpression(function, argument)
  161. def make_VariableExpression(self, name):
  162. return DrtVariableExpression(Variable(name))
  163. def make_LambdaExpression(self, variables, term):
  164. return DrtLambdaExpression(variables, term)
  165. class DrtExpression(object):
  166. """
  167. This is the base abstract DRT Expression from which every DRT
  168. Expression extends.
  169. """
  170. _drt_parser = DrtParser()
  171. @classmethod
  172. def fromstring(cls, s):
  173. return cls._drt_parser.parse(s)
  174. def applyto(self, other):
  175. return DrtApplicationExpression(self, other)
  176. def __neg__(self):
  177. return DrtNegatedExpression(self)
  178. def __and__(self, other):
  179. raise NotImplementedError()
  180. def __or__(self, other):
  181. assert isinstance(other, DrtExpression)
  182. return DrtOrExpression(self, other)
  183. def __gt__(self, other):
  184. assert isinstance(other, DrtExpression)
  185. if isinstance(self, DRS):
  186. return DRS(self.refs, self.conds, other)
  187. if isinstance(self, DrtConcatenation):
  188. return DrtConcatenation(self.first, self.second, other)
  189. raise Exception('Antecedent of implication must be a DRS')
  190. def equiv(self, other, prover=None):
  191. """
  192. Check for logical equivalence.
  193. Pass the expression (self <-> other) to the theorem prover.
  194. If the prover says it is valid, then the self and other are equal.
  195. :param other: an ``DrtExpression`` to check equality against
  196. :param prover: a ``nltk.inference.api.Prover``
  197. """
  198. assert isinstance(other, DrtExpression)
  199. f1 = self.simplify().fol()
  200. f2 = other.simplify().fol()
  201. return f1.equiv(f2, prover)
  202. @property
  203. def type(self):
  204. raise AttributeError(
  205. "'%s' object has no attribute 'type'" % self.__class__.__name__
  206. )
  207. def typecheck(self, signature=None):
  208. raise NotImplementedError()
  209. def __add__(self, other):
  210. return DrtConcatenation(self, other, None)
  211. def get_refs(self, recursive=False):
  212. """
  213. Return the set of discourse referents in this DRS.
  214. :param recursive: bool Also find discourse referents in subterms?
  215. :return: list of ``Variable`` objects
  216. """
  217. raise NotImplementedError()
  218. def is_pronoun_function(self):
  219. """ Is self of the form "PRO(x)"? """
  220. return (
  221. isinstance(self, DrtApplicationExpression)
  222. and isinstance(self.function, DrtAbstractVariableExpression)
  223. and self.function.variable.name == DrtTokens.PRONOUN
  224. and isinstance(self.argument, DrtIndividualVariableExpression)
  225. )
  226. def make_EqualityExpression(self, first, second):
  227. return DrtEqualityExpression(first, second)
  228. def make_VariableExpression(self, variable):
  229. return DrtVariableExpression(variable)
  230. def resolve_anaphora(self):
  231. return resolve_anaphora(self)
  232. def eliminate_equality(self):
  233. return self.visit_structured(lambda e: e.eliminate_equality(), self.__class__)
  234. def pretty_format(self):
  235. """
  236. Draw the DRS
  237. :return: the pretty print string
  238. """
  239. return '\n'.join(self._pretty())
  240. def pretty_print(self):
  241. print(self.pretty_format())
  242. def draw(self):
  243. DrsDrawer(self).draw()
  244. @python_2_unicode_compatible
  245. class DRS(DrtExpression, Expression):
  246. """A Discourse Representation Structure."""
  247. def __init__(self, refs, conds, consequent=None):
  248. """
  249. :param refs: list of ``DrtIndividualVariableExpression`` for the
  250. discourse referents
  251. :param conds: list of ``Expression`` for the conditions
  252. """
  253. self.refs = refs
  254. self.conds = conds
  255. self.consequent = consequent
  256. def replace(self, variable, expression, replace_bound=False, alpha_convert=True):
  257. """Replace all instances of variable v with expression E in self,
  258. where v is free in self."""
  259. if variable in self.refs:
  260. # if a bound variable is the thing being replaced
  261. if not replace_bound:
  262. return self
  263. else:
  264. i = self.refs.index(variable)
  265. if self.consequent:
  266. consequent = self.consequent.replace(
  267. variable, expression, True, alpha_convert
  268. )
  269. else:
  270. consequent = None
  271. return DRS(
  272. self.refs[:i] + [expression.variable] + self.refs[i + 1 :],
  273. [
  274. cond.replace(variable, expression, True, alpha_convert)
  275. for cond in self.conds
  276. ],
  277. consequent,
  278. )
  279. else:
  280. if alpha_convert:
  281. # any bound variable that appears in the expression must
  282. # be alpha converted to avoid a conflict
  283. for ref in set(self.refs) & expression.free():
  284. newvar = unique_variable(ref)
  285. newvarex = DrtVariableExpression(newvar)
  286. i = self.refs.index(ref)
  287. if self.consequent:
  288. consequent = self.consequent.replace(
  289. ref, newvarex, True, alpha_convert
  290. )
  291. else:
  292. consequent = None
  293. self = DRS(
  294. self.refs[:i] + [newvar] + self.refs[i + 1 :],
  295. [
  296. cond.replace(ref, newvarex, True, alpha_convert)
  297. for cond in self.conds
  298. ],
  299. consequent,
  300. )
  301. # replace in the conditions
  302. if self.consequent:
  303. consequent = self.consequent.replace(
  304. variable, expression, replace_bound, alpha_convert
  305. )
  306. else:
  307. consequent = None
  308. return DRS(
  309. self.refs,
  310. [
  311. cond.replace(variable, expression, replace_bound, alpha_convert)
  312. for cond in self.conds
  313. ],
  314. consequent,
  315. )
  316. def free(self):
  317. """:see: Expression.free()"""
  318. conds_free = reduce(operator.or_, [c.free() for c in self.conds], set())
  319. if self.consequent:
  320. conds_free.update(self.consequent.free())
  321. return conds_free - set(self.refs)
  322. def get_refs(self, recursive=False):
  323. """:see: AbstractExpression.get_refs()"""
  324. if recursive:
  325. conds_refs = self.refs + list(
  326. chain(*(c.get_refs(True) for c in self.conds))
  327. )
  328. if self.consequent:
  329. conds_refs.extend(self.consequent.get_refs(True))
  330. return conds_refs
  331. else:
  332. return self.refs
  333. def visit(self, function, combinator):
  334. """:see: Expression.visit()"""
  335. parts = list(map(function, self.conds))
  336. if self.consequent:
  337. parts.append(function(self.consequent))
  338. return combinator(parts)
  339. def visit_structured(self, function, combinator):
  340. """:see: Expression.visit_structured()"""
  341. consequent = function(self.consequent) if self.consequent else None
  342. return combinator(self.refs, list(map(function, self.conds)), consequent)
  343. def eliminate_equality(self):
  344. drs = self
  345. i = 0
  346. while i < len(drs.conds):
  347. cond = drs.conds[i]
  348. if (
  349. isinstance(cond, EqualityExpression)
  350. and isinstance(cond.first, AbstractVariableExpression)
  351. and isinstance(cond.second, AbstractVariableExpression)
  352. ):
  353. drs = DRS(
  354. list(set(drs.refs) - set([cond.second.variable])),
  355. drs.conds[:i] + drs.conds[i + 1 :],
  356. drs.consequent,
  357. )
  358. if cond.second.variable != cond.first.variable:
  359. drs = drs.replace(cond.second.variable, cond.first, False, False)
  360. i = 0
  361. i -= 1
  362. i += 1
  363. conds = []
  364. for cond in drs.conds:
  365. new_cond = cond.eliminate_equality()
  366. new_cond_simp = new_cond.simplify()
  367. if (
  368. not isinstance(new_cond_simp, DRS)
  369. or new_cond_simp.refs
  370. or new_cond_simp.conds
  371. or new_cond_simp.consequent
  372. ):
  373. conds.append(new_cond)
  374. consequent = drs.consequent.eliminate_equality() if drs.consequent else None
  375. return DRS(drs.refs, conds, consequent)
  376. def fol(self):
  377. if self.consequent:
  378. accum = None
  379. if self.conds:
  380. accum = reduce(AndExpression, [c.fol() for c in self.conds])
  381. if accum:
  382. accum = ImpExpression(accum, self.consequent.fol())
  383. else:
  384. accum = self.consequent.fol()
  385. for ref in self.refs[::-1]:
  386. accum = AllExpression(ref, accum)
  387. return accum
  388. else:
  389. if not self.conds:
  390. raise Exception("Cannot convert DRS with no conditions to FOL.")
  391. accum = reduce(AndExpression, [c.fol() for c in self.conds])
  392. for ref in map(Variable, self._order_ref_strings(self.refs)[::-1]):
  393. accum = ExistsExpression(ref, accum)
  394. return accum
  395. def _pretty(self):
  396. refs_line = ' '.join(self._order_ref_strings(self.refs))
  397. cond_lines = [
  398. cond
  399. for cond_line in [
  400. filter(lambda s: s.strip(), cond._pretty()) for cond in self.conds
  401. ]
  402. for cond in cond_line
  403. ]
  404. length = max([len(refs_line)] + list(map(len, cond_lines)))
  405. drs = (
  406. [
  407. ' _' + '_' * length + '_ ',
  408. '| ' + refs_line.ljust(length) + ' |',
  409. '|-' + '-' * length + '-|',
  410. ]
  411. + ['| ' + line.ljust(length) + ' |' for line in cond_lines]
  412. + ['|_' + '_' * length + '_|']
  413. )
  414. if self.consequent:
  415. return DrtBinaryExpression._assemble_pretty(
  416. drs, DrtTokens.IMP, self.consequent._pretty()
  417. )
  418. return drs
  419. def _order_ref_strings(self, refs):
  420. strings = ["%s" % ref for ref in refs]
  421. ind_vars = []
  422. func_vars = []
  423. event_vars = []
  424. other_vars = []
  425. for s in strings:
  426. if is_indvar(s):
  427. ind_vars.append(s)
  428. elif is_funcvar(s):
  429. func_vars.append(s)
  430. elif is_eventvar(s):
  431. event_vars.append(s)
  432. else:
  433. other_vars.append(s)
  434. return (
  435. sorted(other_vars)
  436. + sorted(event_vars, key=lambda v: int([v[2:], -1][len(v[2:]) == 0]))
  437. + sorted(func_vars, key=lambda v: (v[0], int([v[1:], -1][len(v[1:]) == 0])))
  438. + sorted(ind_vars, key=lambda v: (v[0], int([v[1:], -1][len(v[1:]) == 0])))
  439. )
  440. def __eq__(self, other):
  441. r"""Defines equality modulo alphabetic variance.
  442. If we are comparing \x.M and \y.N, then check equality of M and N[x/y]."""
  443. if isinstance(other, DRS):
  444. if len(self.refs) == len(other.refs):
  445. converted_other = other
  446. for (r1, r2) in zip(self.refs, converted_other.refs):
  447. varex = self.make_VariableExpression(r1)
  448. converted_other = converted_other.replace(r2, varex, True)
  449. if self.consequent == converted_other.consequent and len(
  450. self.conds
  451. ) == len(converted_other.conds):
  452. for c1, c2 in zip(self.conds, converted_other.conds):
  453. if not (c1 == c2):
  454. return False
  455. return True
  456. return False
  457. def __ne__(self, other):
  458. return not self == other
  459. __hash__ = Expression.__hash__
  460. def __str__(self):
  461. drs = '([%s],[%s])' % (
  462. ','.join(self._order_ref_strings(self.refs)),
  463. ', '.join("%s" % cond for cond in self.conds),
  464. ) # map(str, self.conds)))
  465. if self.consequent:
  466. return (
  467. DrtTokens.OPEN
  468. + drs
  469. + ' '
  470. + DrtTokens.IMP
  471. + ' '
  472. + "%s" % self.consequent
  473. + DrtTokens.CLOSE
  474. )
  475. return drs
  476. def DrtVariableExpression(variable):
  477. """
  478. This is a factory method that instantiates and returns a subtype of
  479. ``DrtAbstractVariableExpression`` appropriate for the given variable.
  480. """
  481. if is_indvar(variable.name):
  482. return DrtIndividualVariableExpression(variable)
  483. elif is_funcvar(variable.name):
  484. return DrtFunctionVariableExpression(variable)
  485. elif is_eventvar(variable.name):
  486. return DrtEventVariableExpression(variable)
  487. else:
  488. return DrtConstantExpression(variable)
  489. class DrtAbstractVariableExpression(DrtExpression, AbstractVariableExpression):
  490. def fol(self):
  491. return self
  492. def get_refs(self, recursive=False):
  493. """:see: AbstractExpression.get_refs()"""
  494. return []
  495. def _pretty(self):
  496. s = "%s" % self
  497. blank = ' ' * len(s)
  498. return [blank, blank, s, blank]
  499. def eliminate_equality(self):
  500. return self
  501. class DrtIndividualVariableExpression(
  502. DrtAbstractVariableExpression, IndividualVariableExpression
  503. ):
  504. pass
  505. class DrtFunctionVariableExpression(
  506. DrtAbstractVariableExpression, FunctionVariableExpression
  507. ):
  508. pass
  509. class DrtEventVariableExpression(
  510. DrtIndividualVariableExpression, EventVariableExpression
  511. ):
  512. pass
  513. class DrtConstantExpression(DrtAbstractVariableExpression, ConstantExpression):
  514. pass
  515. @python_2_unicode_compatible
  516. class DrtProposition(DrtExpression, Expression):
  517. def __init__(self, variable, drs):
  518. self.variable = variable
  519. self.drs = drs
  520. def replace(self, variable, expression, replace_bound=False, alpha_convert=True):
  521. if self.variable == variable:
  522. assert isinstance(
  523. expression, DrtAbstractVariableExpression
  524. ), "Can only replace a proposition label with a variable"
  525. return DrtProposition(
  526. expression.variable,
  527. self.drs.replace(variable, expression, replace_bound, alpha_convert),
  528. )
  529. else:
  530. return DrtProposition(
  531. self.variable,
  532. self.drs.replace(variable, expression, replace_bound, alpha_convert),
  533. )
  534. def eliminate_equality(self):
  535. return DrtProposition(self.variable, self.drs.eliminate_equality())
  536. def get_refs(self, recursive=False):
  537. return self.drs.get_refs(True) if recursive else []
  538. def __eq__(self, other):
  539. return (
  540. self.__class__ == other.__class__
  541. and self.variable == other.variable
  542. and self.drs == other.drs
  543. )
  544. def __ne__(self, other):
  545. return not self == other
  546. __hash__ = Expression.__hash__
  547. def fol(self):
  548. return self.drs.fol()
  549. def _pretty(self):
  550. drs_s = self.drs._pretty()
  551. blank = ' ' * len("%s" % self.variable)
  552. return (
  553. [blank + ' ' + line for line in drs_s[:1]]
  554. + ["%s" % self.variable + ':' + line for line in drs_s[1:2]]
  555. + [blank + ' ' + line for line in drs_s[2:]]
  556. )
  557. def visit(self, function, combinator):
  558. """:see: Expression.visit()"""
  559. return combinator([function(self.drs)])
  560. def visit_structured(self, function, combinator):
  561. """:see: Expression.visit_structured()"""
  562. return combinator(self.variable, function(self.drs))
  563. def __str__(self):
  564. return 'prop(%s, %s)' % (self.variable, self.drs)
  565. class DrtNegatedExpression(DrtExpression, NegatedExpression):
  566. def fol(self):
  567. return NegatedExpression(self.term.fol())
  568. def get_refs(self, recursive=False):
  569. """:see: AbstractExpression.get_refs()"""
  570. return self.term.get_refs(recursive)
  571. def _pretty(self):
  572. term_lines = self.term._pretty()
  573. return (
  574. [' ' + line for line in term_lines[:2]]
  575. + ['__ ' + line for line in term_lines[2:3]]
  576. + [' | ' + line for line in term_lines[3:4]]
  577. + [' ' + line for line in term_lines[4:]]
  578. )
  579. class DrtLambdaExpression(DrtExpression, LambdaExpression):
  580. def alpha_convert(self, newvar):
  581. """Rename all occurrences of the variable introduced by this variable
  582. binder in the expression to ``newvar``.
  583. :param newvar: ``Variable``, for the new variable
  584. """
  585. return self.__class__(
  586. newvar,
  587. self.term.replace(self.variable, DrtVariableExpression(newvar), True),
  588. )
  589. def fol(self):
  590. return LambdaExpression(self.variable, self.term.fol())
  591. def _pretty(self):
  592. variables = [self.variable]
  593. term = self.term
  594. while term.__class__ == self.__class__:
  595. variables.append(term.variable)
  596. term = term.term
  597. var_string = ' '.join("%s" % v for v in variables) + DrtTokens.DOT
  598. term_lines = term._pretty()
  599. blank = ' ' * len(var_string)
  600. return (
  601. [' ' + blank + line for line in term_lines[:1]]
  602. + [' \ ' + blank + line for line in term_lines[1:2]]
  603. + [' /\ ' + var_string + line for line in term_lines[2:3]]
  604. + [' ' + blank + line for line in term_lines[3:]]
  605. )
  606. class DrtBinaryExpression(DrtExpression, BinaryExpression):
  607. def get_refs(self, recursive=False):
  608. """:see: AbstractExpression.get_refs()"""
  609. return (
  610. self.first.get_refs(True) + self.second.get_refs(True) if recursive else []
  611. )
  612. def _pretty(self):
  613. return DrtBinaryExpression._assemble_pretty(
  614. self._pretty_subex(self.first),
  615. self.getOp(),
  616. self._pretty_subex(self.second),
  617. )
  618. @staticmethod
  619. def _assemble_pretty(first_lines, op, second_lines):
  620. max_lines = max(len(first_lines), len(second_lines))
  621. first_lines = _pad_vertically(first_lines, max_lines)
  622. second_lines = _pad_vertically(second_lines, max_lines)
  623. blank = ' ' * len(op)
  624. first_second_lines = list(zip(first_lines, second_lines))
  625. return (
  626. [
  627. ' ' + first_line + ' ' + blank + ' ' + second_line + ' '
  628. for first_line, second_line in first_second_lines[:2]
  629. ]
  630. + [
  631. '(' + first_line + ' ' + op + ' ' + second_line + ')'
  632. for first_line, second_line in first_second_lines[2:3]
  633. ]
  634. + [
  635. ' ' + first_line + ' ' + blank + ' ' + second_line + ' '
  636. for first_line, second_line in first_second_lines[3:]
  637. ]
  638. )
  639. def _pretty_subex(self, subex):
  640. return subex._pretty()
  641. class DrtBooleanExpression(DrtBinaryExpression, BooleanExpression):
  642. pass
  643. class DrtOrExpression(DrtBooleanExpression, OrExpression):
  644. def fol(self):
  645. return OrExpression(self.first.fol(), self.second.fol())
  646. def _pretty_subex(self, subex):
  647. if isinstance(subex, DrtOrExpression):
  648. return [line[1:-1] for line in subex._pretty()]
  649. return DrtBooleanExpression._pretty_subex(self, subex)
  650. class DrtEqualityExpression(DrtBinaryExpression, EqualityExpression):
  651. def fol(self):
  652. return EqualityExpression(self.first.fol(), self.second.fol())
  653. @python_2_unicode_compatible
  654. class DrtConcatenation(DrtBooleanExpression):
  655. """DRS of the form '(DRS + DRS)'"""
  656. def __init__(self, first, second, consequent=None):
  657. DrtBooleanExpression.__init__(self, first, second)
  658. self.consequent = consequent
  659. def replace(self, variable, expression, replace_bound=False, alpha_convert=True):
  660. """Replace all instances of variable v with expression E in self,
  661. where v is free in self."""
  662. first = self.first
  663. second = self.second
  664. consequent = self.consequent
  665. # If variable is bound
  666. if variable in self.get_refs():
  667. if replace_bound:
  668. first = first.replace(
  669. variable, expression, replace_bound, alpha_convert
  670. )
  671. second = second.replace(
  672. variable, expression, replace_bound, alpha_convert
  673. )
  674. if consequent:
  675. consequent = consequent.replace(
  676. variable, expression, replace_bound, alpha_convert
  677. )
  678. else:
  679. if alpha_convert:
  680. # alpha convert every ref that is free in 'expression'
  681. for ref in set(self.get_refs(True)) & expression.free():
  682. v = DrtVariableExpression(unique_variable(ref))
  683. first = first.replace(ref, v, True, alpha_convert)
  684. second = second.replace(ref, v, True, alpha_convert)
  685. if consequent:
  686. consequent = consequent.replace(ref, v, True, alpha_convert)
  687. first = first.replace(variable, expression, replace_bound, alpha_convert)
  688. second = second.replace(variable, expression, replace_bound, alpha_convert)
  689. if consequent:
  690. consequent = consequent.replace(
  691. variable, expression, replace_bound, alpha_convert
  692. )
  693. return self.__class__(first, second, consequent)
  694. def eliminate_equality(self):
  695. # TODO: at some point. for now, simplify.
  696. drs = self.simplify()
  697. assert not isinstance(drs, DrtConcatenation)
  698. return drs.eliminate_equality()
  699. def simplify(self):
  700. first = self.first.simplify()
  701. second = self.second.simplify()
  702. consequent = self.consequent.simplify() if self.consequent else None
  703. if isinstance(first, DRS) and isinstance(second, DRS):
  704. # For any ref that is in both 'first' and 'second'
  705. for ref in set(first.get_refs(True)) & set(second.get_refs(True)):
  706. # alpha convert the ref in 'second' to prevent collision
  707. newvar = DrtVariableExpression(unique_variable(ref))
  708. second = second.replace(ref, newvar, True)
  709. return DRS(first.refs + second.refs, first.conds + second.conds, consequent)
  710. else:
  711. return self.__class__(first, second, consequent)
  712. def get_refs(self, recursive=False):
  713. """:see: AbstractExpression.get_refs()"""
  714. refs = self.first.get_refs(recursive) + self.second.get_refs(recursive)
  715. if self.consequent and recursive:
  716. refs.extend(self.consequent.get_refs(True))
  717. return refs
  718. def getOp(self):
  719. return DrtTokens.DRS_CONC
  720. def __eq__(self, other):
  721. r"""Defines equality modulo alphabetic variance.
  722. If we are comparing \x.M and \y.N, then check equality of M and N[x/y]."""
  723. if isinstance(other, DrtConcatenation):
  724. self_refs = self.get_refs()
  725. other_refs = other.get_refs()
  726. if len(self_refs) == len(other_refs):
  727. converted_other = other
  728. for (r1, r2) in zip(self_refs, other_refs):
  729. varex = self.make_VariableExpression(r1)
  730. converted_other = converted_other.replace(r2, varex, True)
  731. return (
  732. self.first == converted_other.first
  733. and self.second == converted_other.second
  734. and self.consequent == converted_other.consequent
  735. )
  736. return False
  737. def __ne__(self, other):
  738. return not self == other
  739. __hash__ = DrtBooleanExpression.__hash__
  740. def fol(self):
  741. e = AndExpression(self.first.fol(), self.second.fol())
  742. if self.consequent:
  743. e = ImpExpression(e, self.consequent.fol())
  744. return e
  745. def _pretty(self):
  746. drs = DrtBinaryExpression._assemble_pretty(
  747. self._pretty_subex(self.first),
  748. self.getOp(),
  749. self._pretty_subex(self.second),
  750. )
  751. if self.consequent:
  752. drs = DrtBinaryExpression._assemble_pretty(
  753. drs, DrtTokens.IMP, self._pretty(self.consequent)
  754. )
  755. return drs
  756. def _pretty_subex(self, subex):
  757. if isinstance(subex, DrtConcatenation):
  758. return [line[1:-1] for line in subex._pretty()]
  759. return DrtBooleanExpression._pretty_subex(self, subex)
  760. def visit(self, function, combinator):
  761. """:see: Expression.visit()"""
  762. if self.consequent:
  763. return combinator(
  764. [function(self.first), function(self.second), function(self.consequent)]
  765. )
  766. else:
  767. return combinator([function(self.first), function(self.second)])
  768. def __str__(self):
  769. first = self._str_subex(self.first)
  770. second = self._str_subex(self.second)
  771. drs = Tokens.OPEN + first + ' ' + self.getOp() + ' ' + second + Tokens.CLOSE
  772. if self.consequent:
  773. return (
  774. DrtTokens.OPEN
  775. + drs
  776. + ' '
  777. + DrtTokens.IMP
  778. + ' '
  779. + "%s" % self.consequent
  780. + DrtTokens.CLOSE
  781. )
  782. return drs
  783. def _str_subex(self, subex):
  784. s = "%s" % subex
  785. if isinstance(subex, DrtConcatenation) and subex.consequent is None:
  786. return s[1:-1]
  787. return s
  788. class DrtApplicationExpression(DrtExpression, ApplicationExpression):
  789. def fol(self):
  790. return ApplicationExpression(self.function.fol(), self.argument.fol())
  791. def get_refs(self, recursive=False):
  792. """:see: AbstractExpression.get_refs()"""
  793. return (
  794. self.function.get_refs(True) + self.argument.get_refs(True)
  795. if recursive
  796. else []
  797. )
  798. def _pretty(self):
  799. function, args = self.uncurry()
  800. function_lines = function._pretty()
  801. args_lines = [arg._pretty() for arg in args]
  802. max_lines = max(map(len, [function_lines] + args_lines))
  803. function_lines = _pad_vertically(function_lines, max_lines)
  804. args_lines = [_pad_vertically(arg_lines, max_lines) for arg_lines in args_lines]
  805. func_args_lines = list(zip(function_lines, list(zip(*args_lines))))
  806. return (
  807. [
  808. func_line + ' ' + ' '.join(args_line) + ' '
  809. for func_line, args_line in func_args_lines[:2]
  810. ]
  811. + [
  812. func_line + '(' + ','.join(args_line) + ')'
  813. for func_line, args_line in func_args_lines[2:3]
  814. ]
  815. + [
  816. func_line + ' ' + ' '.join(args_line) + ' '
  817. for func_line, args_line in func_args_lines[3:]
  818. ]
  819. )
  820. def _pad_vertically(lines, max_lines):
  821. pad_line = [' ' * len(lines[0])]
  822. return lines + pad_line * (max_lines - len(lines))
  823. @python_2_unicode_compatible
  824. class PossibleAntecedents(list, DrtExpression, Expression):
  825. def free(self):
  826. """Set of free variables."""
  827. return set(self)
  828. def replace(self, variable, expression, replace_bound=False, alpha_convert=True):
  829. """Replace all instances of variable v with expression E in self,
  830. where v is free in self."""
  831. result = PossibleAntecedents()
  832. for item in self:
  833. if item == variable:
  834. self.append(expression)
  835. else:
  836. self.append(item)
  837. return result
  838. def _pretty(self):
  839. s = "%s" % self
  840. blank = ' ' * len(s)
  841. return [blank, blank, s]
  842. def __str__(self):
  843. return '[' + ','.join("%s" % it for it in self) + ']'
  844. class AnaphoraResolutionException(Exception):
  845. pass
  846. def resolve_anaphora(expression, trail=[]):
  847. if isinstance(expression, ApplicationExpression):
  848. if expression.is_pronoun_function():
  849. possible_antecedents = PossibleAntecedents()
  850. for ancestor in trail:
  851. for ref in ancestor.get_refs():
  852. refex = expression.make_VariableExpression(ref)
  853. # ==========================================================
  854. # Don't allow resolution to itself or other types
  855. # ==========================================================
  856. if refex.__class__ == expression.argument.__class__ and not (
  857. refex == expression.argument
  858. ):
  859. possible_antecedents.append(refex)
  860. if len(possible_antecedents) == 1:
  861. resolution = possible_antecedents[0]
  862. else:
  863. resolution = possible_antecedents
  864. return expression.make_EqualityExpression(expression.argument, resolution)
  865. else:
  866. r_function = resolve_anaphora(expression.function, trail + [expression])
  867. r_argument = resolve_anaphora(expression.argument, trail + [expression])
  868. return expression.__class__(r_function, r_argument)
  869. elif isinstance(expression, DRS):
  870. r_conds = []
  871. for cond in expression.conds:
  872. r_cond = resolve_anaphora(cond, trail + [expression])
  873. # if the condition is of the form '(x = [])' then raise exception
  874. if isinstance(r_cond, EqualityExpression):
  875. if isinstance(r_cond.first, PossibleAntecedents):
  876. # Reverse the order so that the variable is on the left
  877. temp = r_cond.first
  878. r_cond.first = r_cond.second
  879. r_cond.second = temp
  880. if isinstance(r_cond.second, PossibleAntecedents):
  881. if not r_cond.second:
  882. raise AnaphoraResolutionException(
  883. "Variable '%s' does not "
  884. "resolve to anything." % r_cond.first
  885. )
  886. r_conds.append(r_cond)
  887. if expression.consequent:
  888. consequent = resolve_anaphora(expression.consequent, trail + [expression])
  889. else:
  890. consequent = None
  891. return expression.__class__(expression.refs, r_conds, consequent)
  892. elif isinstance(expression, AbstractVariableExpression):
  893. return expression
  894. elif isinstance(expression, NegatedExpression):
  895. return expression.__class__(
  896. resolve_anaphora(expression.term, trail + [expression])
  897. )
  898. elif isinstance(expression, DrtConcatenation):
  899. if expression.consequent:
  900. consequent = resolve_anaphora(expression.consequent, trail + [expression])
  901. else:
  902. consequent = None
  903. return expression.__class__(
  904. resolve_anaphora(expression.first, trail + [expression]),
  905. resolve_anaphora(expression.second, trail + [expression]),
  906. consequent,
  907. )
  908. elif isinstance(expression, BinaryExpression):
  909. return expression.__class__(
  910. resolve_anaphora(expression.first, trail + [expression]),
  911. resolve_anaphora(expression.second, trail + [expression]),
  912. )
  913. elif isinstance(expression, LambdaExpression):
  914. return expression.__class__(
  915. expression.variable, resolve_anaphora(expression.term, trail + [expression])
  916. )
  917. class DrsDrawer(object):
  918. BUFFER = 3 # Space between elements
  919. TOPSPACE = 10 # Space above whole DRS
  920. OUTERSPACE = 6 # Space to the left, right, and bottom of the whle DRS
  921. def __init__(self, drs, size_canvas=True, canvas=None):
  922. """
  923. :param drs: ``DrtExpression``, The DRS to be drawn
  924. :param size_canvas: bool, True if the canvas size should be the exact size of the DRS
  925. :param canvas: ``Canvas`` The canvas on which to draw the DRS. If none is given, create a new canvas.
  926. """
  927. master = None
  928. if not canvas:
  929. master = Tk()
  930. master.title("DRT")
  931. font = Font(family='helvetica', size=12)
  932. if size_canvas:
  933. canvas = Canvas(master, width=0, height=0)
  934. canvas.font = font
  935. self.canvas = canvas
  936. (right, bottom) = self._visit(drs, self.OUTERSPACE, self.TOPSPACE)
  937. width = max(right + self.OUTERSPACE, 100)
  938. height = bottom + self.OUTERSPACE
  939. canvas = Canvas(master, width=width, height=height) # , bg='white')
  940. else:
  941. canvas = Canvas(master, width=300, height=300)
  942. canvas.pack()
  943. canvas.font = font
  944. self.canvas = canvas
  945. self.drs = drs
  946. self.master = master
  947. def _get_text_height(self):
  948. """Get the height of a line of text"""
  949. return self.canvas.font.metrics("linespace")
  950. def draw(self, x=OUTERSPACE, y=TOPSPACE):
  951. """Draw the DRS"""
  952. self._handle(self.drs, self._draw_command, x, y)
  953. if self.master and not in_idle():
  954. self.master.mainloop()
  955. else:
  956. return self._visit(self.drs, x, y)
  957. def _visit(self, expression, x, y):
  958. """
  959. Return the bottom-rightmost point without actually drawing the item
  960. :param expression: the item to visit
  961. :param x: the top of the current drawing area
  962. :param y: the left side of the current drawing area
  963. :return: the bottom-rightmost point
  964. """
  965. return self._handle(expression, self._visit_command, x, y)
  966. def _draw_command(self, item, x, y):
  967. """
  968. Draw the given item at the given location
  969. :param item: the item to draw
  970. :param x: the top of the current drawing area
  971. :param y: the left side of the current drawing area
  972. :return: the bottom-rightmost point
  973. """
  974. if isinstance(item, string_types):
  975. self.canvas.create_text(x, y, anchor='nw', font=self.canvas.font, text=item)
  976. elif isinstance(item, tuple):
  977. # item is the lower-right of a box
  978. (right, bottom) = item
  979. self.canvas.create_rectangle(x, y, right, bottom)
  980. horiz_line_y = (
  981. y + self._get_text_height() + (self.BUFFER * 2)
  982. ) # the line separating refs from conds
  983. self.canvas.create_line(x, horiz_line_y, right, horiz_line_y)
  984. return self._visit_command(item, x, y)
  985. def _visit_command(self, item, x, y):
  986. """
  987. Return the bottom-rightmost point without actually drawing the item
  988. :param item: the item to visit
  989. :param x: the top of the current drawing area
  990. :param y: the left side of the current drawing area
  991. :return: the bottom-rightmost point
  992. """
  993. if isinstance(item, string_types):
  994. return (x + self.canvas.font.measure(item), y + self._get_text_height())
  995. elif isinstance(item, tuple):
  996. return item
  997. def _handle(self, expression, command, x=0, y=0):
  998. """
  999. :param expression: the expression to handle
  1000. :param command: the function to apply, either _draw_command or _visit_command
  1001. :param x: the top of the current drawing area
  1002. :param y: the left side of the current drawing area
  1003. :return: the bottom-rightmost point
  1004. """
  1005. if command == self._visit_command:
  1006. # if we don't need to draw the item, then we can use the cached values
  1007. try:
  1008. # attempt to retrieve cached values
  1009. right = expression._drawing_width + x
  1010. bottom = expression._drawing_height + y
  1011. return (right, bottom)
  1012. except AttributeError:
  1013. # the values have not been cached yet, so compute them
  1014. pass
  1015. if isinstance(expression, DrtAbstractVariableExpression):
  1016. factory = self._handle_VariableExpression
  1017. elif isinstance(expression, DRS):
  1018. factory = self._handle_DRS
  1019. elif isinstance(expression, DrtNegatedExpression):
  1020. factory = self._handle_NegatedExpression
  1021. elif isinstance(expression, DrtLambdaExpression):
  1022. factory = self._handle_LambdaExpression
  1023. elif isinstance(expression, BinaryExpression):
  1024. factory = self._handle_BinaryExpression
  1025. elif isinstance(expression, DrtApplicationExpression):
  1026. factory = self._handle_ApplicationExpression
  1027. elif isinstance(expression, PossibleAntecedents):
  1028. factory = self._handle_VariableExpression
  1029. elif isinstance(expression, DrtProposition):
  1030. factory = self._handle_DrtProposition
  1031. else:
  1032. raise Exception(expression.__class__.__name__)
  1033. (right, bottom) = factory(expression, command, x, y)
  1034. # cache the values
  1035. expression._drawing_width = right - x
  1036. expression._drawing_height = bottom - y
  1037. return (right, bottom)
  1038. def _handle_VariableExpression(self, expression, command, x, y):
  1039. return command("%s" % expression, x, y)
  1040. def _handle_NegatedExpression(self, expression, command, x, y):
  1041. # Find the width of the negation symbol
  1042. right = self._visit_command(DrtTokens.NOT, x, y)[0]
  1043. # Handle term
  1044. (right, bottom) = self._handle(expression.term, command, right, y)
  1045. # Handle variables now that we know the y-coordinate
  1046. command(
  1047. DrtTokens.NOT,
  1048. x,
  1049. self._get_centered_top(y, bottom - y, self._get_text_height()),
  1050. )
  1051. return (right, bottom)
  1052. def _handle_DRS(self, expression, command, x, y):
  1053. left = x + self.BUFFER # indent the left side
  1054. bottom = y + self.BUFFER # indent the top
  1055. # Handle Discourse Referents
  1056. if expression.refs:
  1057. refs = ' '.join("%s" % r for r in expression.refs)
  1058. else:
  1059. refs = ' '
  1060. (max_right, bottom) = command(refs, left, bottom)
  1061. bottom += self.BUFFER * 2
  1062. # Handle Conditions
  1063. if expression.conds:
  1064. for cond in expression.conds:
  1065. (right, bottom) = self._handle(cond, command, left, bottom)
  1066. max_right = max(max_right, right)
  1067. bottom += self.BUFFER
  1068. else:
  1069. bottom += self._get_text_height() + self.BUFFER
  1070. # Handle Box
  1071. max_right += self.BUFFER
  1072. return command((max_right, bottom), x, y)
  1073. def _handle_ApplicationExpression(self, expression, command, x, y):
  1074. function, args = expression.uncurry()
  1075. if not isinstance(function, DrtAbstractVariableExpression):
  1076. # It's not a predicate expression ("P(x,y)"), so leave arguments curried
  1077. function = expression.function
  1078. args = [expression.argument]
  1079. # Get the max bottom of any element on the line
  1080. function_bottom = self._visit(function, x, y)[1]
  1081. max_bottom = max(
  1082. [function_bottom] + [self._visit(arg, x, y)[1] for arg in args]
  1083. )
  1084. line_height = max_bottom - y
  1085. # Handle 'function'
  1086. function_drawing_top = self._get_centered_top(
  1087. y, line_height, function._drawing_height
  1088. )
  1089. right = self._handle(function, command, x, function_drawing_top)[0]
  1090. # Handle open paren
  1091. centred_string_top = self._get_centered_top(
  1092. y, line_height, self._get_text_height()
  1093. )
  1094. right = command(DrtTokens.OPEN, right, centred_string_top)[0]
  1095. # Handle each arg
  1096. for (i, arg) in enumerate(args):
  1097. arg_drawing_top = self._get_centered_top(
  1098. y, line_height, arg._drawing_height
  1099. )
  1100. right = self._handle(arg, command, right, arg_drawing_top)[0]
  1101. if i + 1 < len(args):
  1102. # since it's not the last arg, add a comma
  1103. right = command(DrtTokens.COMMA + ' ', right, centred_string_top)[0]
  1104. # Handle close paren
  1105. right = command(DrtTokens.CLOSE, right, centred_string_top)[0]
  1106. return (right, max_bottom)
  1107. def _handle_LambdaExpression(self, expression, command, x, y):
  1108. # Find the width of the lambda symbol and abstracted variables
  1109. variables = DrtTokens.LAMBDA + "%s" % expression.variable + DrtTokens.DOT
  1110. right = self._visit_command(variables, x, y)[0]
  1111. # Handle term
  1112. (right, bottom) = self._handle(expression.term, command, right, y)
  1113. # Handle variables now that we know the y-coordinate
  1114. command(
  1115. variables, x, self._get_centered_top(y, bottom - y, self._get_text_height())
  1116. )
  1117. return (right, bottom)
  1118. def _handle_BinaryExpression(self, expression, command, x, y):
  1119. # Get the full height of the line, based on the operands
  1120. first_height = self._visit(expression.first, 0, 0)[1]
  1121. second_height = self._visit(expression.second, 0, 0)[1]
  1122. line_height = max(first_height, second_height)
  1123. # Handle open paren
  1124. centred_string_top = self._get_centered_top(
  1125. y, line_height, self._get_text_height()
  1126. )
  1127. right = command(DrtTokens.OPEN, x, centred_string_top)[0]
  1128. # Handle the first operand
  1129. first_height = expression.first._drawing_height
  1130. (right, first_bottom) = self._handle(
  1131. expression.first,
  1132. command,
  1133. right,
  1134. self._get_centered_top(y, line_height, first_height),
  1135. )
  1136. # Handle the operator
  1137. right = command(' %s ' % expression.getOp(), right, centred_string_top)[0]
  1138. # Handle the second operand
  1139. second_height = expression.second._drawing_height
  1140. (right, second_bottom) = self._handle(
  1141. expression.second,
  1142. command,
  1143. right,
  1144. self._get_centered_top(y, line_height, second_height),
  1145. )
  1146. # Handle close paren
  1147. right = command(DrtTokens.CLOSE, right, centred_string_top)[0]
  1148. return (right, max(first_bottom, second_bottom))
  1149. def _handle_DrtProposition(self, expression, command, x, y):
  1150. # Find the width of the negation symbol
  1151. right = command(expression.variable, x, y)[0]
  1152. # Handle term
  1153. (right, bottom) = self._handle(expression.term, command, right, y)
  1154. return (right, bottom)
  1155. def _get_centered_top(self, top, full_height, item_height):
  1156. """Get the y-coordinate of the point that a figure should start at if
  1157. its height is 'item_height' and it needs to be centered in an area that
  1158. starts at 'top' and is 'full_height' tall."""
  1159. return top + (full_height - item_height) / 2
  1160. def demo():
  1161. print('=' * 20 + 'TEST PARSE' + '=' * 20)
  1162. dexpr = DrtExpression.fromstring
  1163. print(dexpr(r'([x,y],[sees(x,y)])'))
  1164. print(dexpr(r'([x],[man(x), walks(x)])'))
  1165. print(dexpr(r'\x.\y.([],[sees(x,y)])'))
  1166. print(dexpr(r'\x.([],[walks(x)])(john)'))
  1167. print(dexpr(r'(([x],[walks(x)]) + ([y],[runs(y)]))'))
  1168. print(dexpr(r'(([],[walks(x)]) -> ([],[runs(x)]))'))
  1169. print(dexpr(r'([x],[PRO(x), sees(John,x)])'))
  1170. print(dexpr(r'([x],[man(x), -([],[walks(x)])])'))
  1171. print(dexpr(r'([],[(([x],[man(x)]) -> ([],[walks(x)]))])'))
  1172. print('=' * 20 + 'Test fol()' + '=' * 20)
  1173. print(dexpr(r'([x,y],[sees(x,y)])').fol())
  1174. print('=' * 20 + 'Test alpha conversion and lambda expression equality' + '=' * 20)
  1175. e1 = dexpr(r'\x.([],[P(x)])')
  1176. print(e1)
  1177. e2 = e1.alpha_convert(Variable('z'))
  1178. print(e2)
  1179. print(e1 == e2)
  1180. print('=' * 20 + 'Test resolve_anaphora()' + '=' * 20)
  1181. print(resolve_anaphora(dexpr(r'([x,y,z],[dog(x), cat(y), walks(z), PRO(z)])')))
  1182. print(
  1183. resolve_anaphora(dexpr(r'([],[(([x],[dog(x)]) -> ([y],[walks(y), PRO(y)]))])'))
  1184. )
  1185. print(resolve_anaphora(dexpr(r'(([x,y],[]) + ([],[PRO(x)]))')))
  1186. print('=' * 20 + 'Test pretty_print()' + '=' * 20)
  1187. dexpr(r"([],[])").pretty_print()
  1188. dexpr(
  1189. r"([],[([x],[big(x), dog(x)]) -> ([],[bark(x)]) -([x],[walk(x)])])"
  1190. ).pretty_print()
  1191. dexpr(r"([x,y],[x=y]) + ([z],[dog(z), walk(z)])").pretty_print()
  1192. dexpr(r"([],[([x],[]) | ([y],[]) | ([z],[dog(z), walk(z)])])").pretty_print()
  1193. dexpr(r"\P.\Q.(([x],[]) + P(x) + Q(x))(\x.([],[dog(x)]))").pretty_print()
  1194. def test_draw():
  1195. try:
  1196. from six.moves.tkinter import Tk
  1197. except ImportError:
  1198. from nose import SkipTest
  1199. raise SkipTest("tkinter is required, but it's not available.")
  1200. expressions = [
  1201. r'x',
  1202. r'([],[])',
  1203. r'([x],[])',
  1204. r'([x],[man(x)])',
  1205. r'([x,y],[sees(x,y)])',
  1206. r'([x],[man(x), walks(x)])',
  1207. r'\x.([],[man(x), walks(x)])',
  1208. r'\x y.([],[sees(x,y)])',
  1209. r'([],[(([],[walks(x)]) + ([],[runs(x)]))])',
  1210. r'([x],[man(x), -([],[walks(x)])])',
  1211. r'([],[(([x],[man(x)]) -> ([],[walks(x)]))])',
  1212. ]
  1213. for e in expressions:
  1214. d = DrtExpression.fromstring(e)
  1215. d.draw()
  1216. if __name__ == '__main__':
  1217. demo()