1
2
3
4
5
6
7
8 """
9 A version of first order predicate logic, built on
10 top of the untyped lambda calculus.
11 """
12
13 from nltk.internals import Counter
14 from nltk.tokenize.simple import WhitespaceTokenizer
15
16 _counter = Counter()
17
19
20 OLD_NLTK = 0
21 NEW_NLTK = 1
22 PROVER9 = 2
23
24
25 LAMBDA = ['\\', '\\', '\\']
26
27
28 EXISTS = ['some', 'exists', 'exists']
29 ALL = ['all', 'all', 'all']
30
31
32 DOT = ['.', '.', ' ']
33 OPEN = '('
34 CLOSE = ')'
35 COMMA = ','
36
37
38 NOT = ['not', '-', '-']
39 AND = ['and', '&', '&']
40 OR = ['or', '|', '|']
41 IMP = ['implies', '->', '->']
42 IFF = ['iff', '<->', '<->']
43 EQ = ['=', '=', '=']
44 NEQ = ['!=', '!=', '!=']
45
46
47 BINOPS = AND + OR + IMP + IFF
48 QUANTS = EXISTS + ALL
49 PUNCT = [DOT[0], OPEN, CLOSE, COMMA]
50
51 TOKENS = BINOPS + EQ + NEQ + QUANTS + LAMBDA + PUNCT + NOT
52
53
54 SYMBOLS = LAMBDA + PUNCT + [AND[1], OR[1], NOT[1], IMP[1], IFF[1]] +\
55 EQ + NEQ
56
57
60 """
61 @param name: the name of the variable
62 """
63 self.name = name
64
66 return self.__class__ == other.__class__ and self.name == other.name
67
69 assert isinstance(other, Variable)
70 if self.name == other.name:
71 return 0
72 elif self.name < other.name:
73 return -1
74 else:
75 return 1
76
79
81 return hash(self.name)
82
85
87 return 'Variable(\'' + self.name + '\')'
88
91
92
94 """
95 An interface for classes that can perform substitutions for
96 variables.
97 """
99 """
100 @return: The object that is obtained by replacing
101 each variable bound by C{bindings} with its values.
102 Aliases are already resolved. (maybe?)
103 @rtype: (any)
104 """
105 raise NotImplementedError()
106
108 """
109 @return: A list of all variables in this object.
110 """
111 raise NotImplementedError()
112
114 """This is the base abstract object for all logical expressions"""
115 - def __call__(self, other, *additional):
116 accum = self.applyto(other)
117 for a in additional:
118 accum = accum(a)
119 return accum
120
123
126
129
133
137
141
145
147 raise NotImplementedError()
148
149 - def tp_equals(self, other, prover_name='tableau'):
158
160 return hash(repr(self))
161
177
179 return '<' + self.__class__.__name__ + ' ' + str(self) + '>'
180
183
185 """
186 Return a set of all the variables that are available to be replaced.
187 This includes free (non-bound) variables as well as predicates.
188 @return: C{set} of C{Variable}s
189 """
190 raise NotImplementedError()
191
192 - def free(self, indvar_only=True):
193 """
194 Return a set of all the free (non-bound) variables in self. Variables
195 serving as predicates are no included.
196 @param indvar_only: C{boolean} only return individual variables?
197 @return: C{set} of C{Variable}s
198 """
199 raise NotImplementedError()
200
202 r"""
203 This class is used to represent two related types of logical expressions.
204
205 The first is a Predicate Expression, such as "P(x,y)". A predicate
206 expression is comprised of a VariableExpression as the predicate and a list
207 of Expressions as the arguments.
208
209 The second is a an application of one expression to another, such as
210 "(\x.dog(x))(fido)".
211
212 The reason Predicate Expressions are treated as Application Expressions is
213 that the VariableExpression predicate of the expression may be replaced with
214 another Expression, such as a LambdaExpression, which would mean that the
215 Predicate should be thought of as being applied to the arguments.
216
217 The LogicParser will always curry arguments in a application expression.
218 So, "\x y.see(x,y)(john,mary)" will be represented internally as
219 "((\x y.(see(x))(y))(john))(mary)". This simplifies the internals since
220 there will always be exactly one argument in an application.
221
222 The str() method will usually print the curried forms of application
223 expressions. The one exception is when the the application expression is
224 really a predicate expression (ie, underlying function is a
225 VariableExpression). This means that the example from above will be
226 returned as "(\x y.see(x,y)(john))(mary)".
227 """
228 - def __init__(self, function, argument):
229 """
230 @param function: C{Expression}, for the function expression
231 @param argument: C{Expression}, for the argument
232 """
233 self.function = function
234 self.argument = argument
235
237 function = self.function.simplify()
238 argument = self.argument.simplify()
239 if isinstance(function, LambdaExpression):
240 return function.term.replace(function.variable, argument).simplify()
241 else:
242 return self.__class__(function, argument)
243
244 - def replace(self, variable, expression, replace_bound=False):
245 """
246 Replace every instance of 'variable' with 'expression'
247 @param variable: C{Variable} The variable to replace
248 @param expression: C{Expression} The expression with which to replace it
249 @param replace_bound: C{boolean} Should bound variables be replaced?
250 """
251 function = self.function.replace(variable, expression, replace_bound)
252 argument = self.argument.replace(variable, expression, replace_bound)
253 return self.__class__(function, argument)
254
256 """
257 @see: Expression.variables()
258 """
259 return self.function.variables() | self.argument.variables()
260
261 - def free(self, indvar_only=True):
262 """
263 @see: Expression.free()
264 """
265 if isinstance(self.function, VariableExpression):
266 return self.argument.free(indvar_only)
267 else:
268 return self.function.free(indvar_only) | self.argument.free(indvar_only)
269
271 return self.__class__ == other.__class__ and \
272 self.function == other.function and \
273 self.argument == other.argument
274
301
303 """
304 return: A tuple (base-function, arg-list)
305 """
306 function = self.function
307 args = [self.argument]
308 while isinstance(function, ApplicationExpression):
309
310 args.insert(0, function.argument)
311 function = function.function
312 return (function, args)
313
315 """This class represents a variable to be used as a predicate or entity"""
317 """
318 @param variable: C{Variable}, for the variable
319 """
320 self.variable = variable
321
324
325 - def replace(self, variable, expression, replace_bound=False):
326 """
327 Replace every instance of 'variable' with 'expression'
328 @param variable: C{Variable} The variable to replace
329 @param expression: C{Expression} The expression with which to replace it
330 @param replace_bound: C{boolean} Should bound variables be replaced?
331 """
332 if self.variable == variable:
333 return expression
334 else:
335 return self
336
338 """
339 @see: Expression.variables()
340 """
341 return set([self.variable])
342
343 - def free(self, indvar_only=True):
344 """
345 @see: Expression.free()
346 """
347 if not indvar_only:
348 return set([self.variable])
349 else:
350 return set()
351
353 """Allow equality between instances of C{VariableExpression} and
354 C{IndividualVariableExpression}."""
355 return isinstance(other, VariableExpression) and \
356 self.variable == other.variable
357
359 return str(self.variable)
360
362 """This class represents variables that take the form of a single lowercase
363 character followed by zero or more digits."""
364 - def free(self, indvar_only=True):
365 """
366 @see: Expression.free()
367 """
368 return set([self.variable])
369
371 """This an abstract class for any Expression that binds a variable in an
372 Expression. This includes LambdaExpressions and Quanitifed Expressions"""
374 """
375 @param variable: C{Variable}, for the variable
376 @param term: C{Expression}, for the term
377 """
378 self.variable = variable
379 self.term = term
380
382 return self.__class__(self.variable, self.term.simplify())
383
384 - def replace(self, variable, expression, replace_bound=False):
385 """
386 Replace every instance of 'variable' with 'expression'
387 @param variable: C{Variable} The variable to replace
388 @param expression: C{Expression} The expression with which to replace it
389 @param replace_bound: C{boolean} Should bound variables be replaced?
390 """
391
392 if self.variable == variable:
393 if replace_bound:
394 return self.__class__(expression,
395 self.term.replace(variable, expression,
396 True))
397 else:
398 return self
399 else:
400
401
402 if self.variable in expression.free():
403 self = self.alpha_convert(unique_variable())
404
405
406 return self.__class__(self.variable,
407 self.term.replace(variable, expression,
408 replace_bound))
409
411 """Rename all occurrences of the variable introduced by this variable
412 binder in the expression to @C{newvar}.
413 @param newvar: C{Variable}, for the new variable
414 """
415 return self.__class__(newvar, self.term.replace(self.variable,
416 VariableExpression(newvar), True))
417
419 """
420 @see: Expression.variables()
421 """
422 return self.term.variables() - set([self.variable])
423
424 - def free(self, indvar_only=True):
425 """
426 @see: Expression.free()
427 """
428 return self.term.free(indvar_only) - set([self.variable])
429
431 r"""Defines equality modulo alphabetic variance. If we are comparing
432 \x.M and \y.N, then check equality of M and N[x/y]."""
433 if self.__class__ == other.__class__:
434 if self.variable == other.variable:
435 return self.term == other.term
436 else:
437
438 if is_indvar(self.variable.name):
439 varex = IndividualVariableExpression(self.variable)
440 else:
441 varex = VariableExpression(self.variable)
442 return self.term == other.term.replace(other.variable, varex)
443 else:
444 return False
445
450
455
459
463
467
469 return self.__class__(self.term.simplify())
470
471 - def replace(self, variable, expression, replace_bound=False):
472 """
473 Replace every instance of 'variable' with 'expression'
474 @param variable: C{Variable} The variable to replace
475 @param expression: C{Expression} The expression with which to replace it
476 @param replace_bound: C{boolean} Should bound variables be replaced?
477 """
478 return self.__class__(self.term.replace(variable, expression,
479 replace_bound))
480
482 """
483 @see: Expression.variables()
484 """
485 return self.term.variables()
486
487 - def free(self, indvar_only=True):
488 """
489 @see: Expression.free()
490 """
491 return self.term.free(indvar_only)
492
494 return self.__class__ == other.__class__ and self.term == other.term
495
502
505 self.first = first
506 self.second = second
507
510
511 - def replace(self, variable, expression, replace_bound=False):
512 """
513 Replace every instance of 'variable' with 'expression'
514 @param variable: C{Variable} The variable to replace
515 @param expression: C{Expression} The expression with which to replace it
516 @param replace_bound: C{boolean} Should bound variables be replaced?
517 """
518 return self.__class__(self.first.replace(variable, expression,
519 replace_bound),
520 self.second.replace(variable, expression,
521 replace_bound))
522
524 """
525 @see: Expression.variables()
526 """
527 return self.first.variables() | self.second.variables()
528
529 - def free(self, indvar_only=True):
530 """
531 @see: Expression.free()
532 """
533 return self.first.free(indvar_only) | self.second.free(indvar_only)
534
536 return self.__class__ == other.__class__ \
537 and self.first == other.first and self.second == other.second
538
542
544 """This class represents conjunctions"""
547
549 """This class represents disjunctions"""
552
554 """This class represents implications"""
557
559 """This class represents biconditionals"""
562
564 """This class represents equality expressions like "(x = y)"."""
567
568
570 """A lambda calculus expression parser."""
571
573 self._currentIndex = 0
574 self._buffer = []
575
577 """
578 Parse the expression.
579
580 @param data: C{str} for the input to be parsed
581 @returns: a parsed Expression
582 """
583 self._currentIndex = 0
584 self._buffer = self.process(data).split()
585 result = self.parse_Expression()
586 if self.inRange(0):
587 raise UnexpectedTokenException(self.token(0))
588 return result
589
612
614 """This method exists to be overridden"""
615 return Tokens.SYMBOLS
616
618 """Return TRUE if the given location is within the buffer"""
619 return self._currentIndex+location < len(self._buffer)
620
621 - def token(self, location=None):
622 """Get the next waiting token. If a location is given, then
623 return the token at currentIndex+location without advancing
624 currentIndex; setting it gives lookahead/lookback capability."""
625 try:
626 if location == None:
627 tok = self._buffer[self._currentIndex]
628 self._currentIndex += 1
629 else:
630 tok = self._buffer[self._currentIndex+location]
631 return tok
632 except IndexError:
633 raise UnexpectedTokenException, 'The given location is out of range'
634
637
652
670
673
676
699
712
724
744
750
765
767 """This method serves as a hook for other logic parsers that
768 have different equality expression classes"""
769 return EqualityExpression(first, second)
770
772 """Attempt to make a boolean expression. If the next token is a boolean
773 operator, then a BooleanExpression will be returned. Otherwise, the
774 parameter will be returned."""
775 if self.inRange(0):
776 factory = self.get_BooleanExpression_factory()
777 if factory:
778 self.token()
779 return self.make_BooleanExpression(factory, expression,
780 self.parse_Expression())
781
782 return expression
783
798
800 """This method exists to be overridden by parsers
801 with more complex logic for creating BooleanExpressions"""
802 return type(first, second)
803
828
831
837
840
848
850 if self.inRange(0):
851 return 'Next token: ' + self.token(0)
852 else:
853 return 'No more tokens'
854
855
857 LEAF = "<leaf>"
858
860 if strings:
861 for string in strings:
862 self.insert(string)
863
865 if len(string):
866 k = string[0]
867 if k not in self:
868 self[k] = StringTrie()
869 self[k].insert(string[1:])
870 else:
871
872 self[StringTrie.LEAF] = None
873
877
879 - def __init__(self, tok, expected=None):
886
887
889 """
890 An individual variable must be a single lowercase character followed by
891 zero or more digits.
892
893 @param expr: C{str}
894 @return: C{boolean} True if expr is of the correct form
895 """
896 try:
897 return expr[0].isalpha() and expr[0].islower() and \
898 (len(expr) == 1 or expr[1:].isdigit())
899 except TypeError:
900 return False
901
903 lp = LogicParser()
904 print '='*20 + 'Test parser' + '='*20
905 print lp.parse(r'john')
906 print lp.parse(r'man(x)')
907 print lp.parse(r'-man(x)')
908 print lp.parse(r'(man(x) & tall(x) & walks(x))')
909 print lp.parse(r'exists x.(man(x) & tall(x))')
910 print lp.parse(r'\x.man(x)')
911 print lp.parse(r'\x.man(x)(john)')
912 print lp.parse(r'\x y.sees(x,y)')
913 print lp.parse(r'\x y.sees(x,y)(a,b)')
914 print lp.parse(r'(\x.exists y.walks(x,y))(x)')
915 print lp.parse(r'exists x.(x = john)')
916 print lp.parse(r'\P Q.exists x.(P(x) & Q(x))')
917
918 print '='*20 + 'Test simplify' + '='*20
919 print lp.parse(r'\x.\y.sees(x,y)(john)(mary)').simplify()
920 print lp.parse(r'\x.\y.sees(x,y)(john, mary)').simplify()
921 print lp.parse(r'exists x.(man(x) & (\x.exists y.walks(x,y))(x))').simplify()
922 print lp.parse(r'(\P.\Q.exists x.(P(x) & Q(x)))(\x.dog(x))(\x.bark(x))').simplify()
923
924 print '='*20 + 'Test alpha conversion and binder expression equality' + '='*20
925 e1 = lp.parse('exists x.P(x)')
926 print e1
927 e2 = e1.alpha_convert(VariableExpression('z'))
928 print e2
929 print e1 == e2
930
931 if __name__ == '__main__':
932 demo()
933