Package nltk :: Package sem :: Module logic
[hide private]
[frames] | no frames]

Source Code for Module nltk.sem.logic

   1  # Natural Language Toolkit: Logic 
   2  # 
   3  # Author: Dan Garrette <dhgarrette@gmail.com> 
   4  # 
   5  # Copyright (C) 2001-2011 NLTK Project 
   6  # URL: <http://www.nltk.org> 
   7  # For license information, see LICENSE.TXT 
   8   
   9  """ 
  10  A version of first order predicate logic, built on  
  11  top of the typed lambda calculus. 
  12  """ 
  13   
  14  import re 
  15  import operator 
  16   
  17  from nltk.compat import defaultdict 
  18  from nltk.internals import Counter 
  19   
  20  APP = 'APP' 
  21   
  22  _counter = Counter() 
  23   
24 -class Tokens(object):
25 LAMBDA = '\\'; LAMBDA_LIST = ['\\'] 26 27 #Quantifiers 28 EXISTS = 'exists'; EXISTS_LIST = ['some', 'exists', 'exist'] 29 ALL = 'all'; ALL_LIST = ['all', 'forall'] 30 31 #Punctuation 32 DOT = '.' 33 OPEN = '(' 34 CLOSE = ')' 35 COMMA = ',' 36 37 #Operations 38 NOT = '-'; NOT_LIST = ['not', '-', '!'] 39 AND = '&'; AND_LIST = ['and', '&', '^'] 40 OR = '|'; OR_LIST = ['or', '|'] 41 IMP = '->'; IMP_LIST = ['implies', '->', '=>'] 42 IFF = '<->'; IFF_LIST = ['iff', '<->', '<=>'] 43 EQ = '='; EQ_LIST = ['=', '=='] 44 NEQ = '!='; NEQ_LIST = ['!='] 45 46 #Collections of tokens 47 BINOPS = AND_LIST + OR_LIST + IMP_LIST + IFF_LIST 48 QUANTS = EXISTS_LIST + ALL_LIST 49 PUNCT = [DOT, OPEN, CLOSE, COMMA] 50 51 TOKENS = BINOPS + EQ_LIST + NEQ_LIST + QUANTS + LAMBDA_LIST + PUNCT + NOT_LIST 52 53 #Special 54 SYMBOLS = [x for x in TOKENS if re.match(r'^[-\\.(),!&^|>=<]*$', x)]
55 56
57 -def boolean_ops():
58 """ 59 Boolean operators 60 """ 61 names = ["negation", "conjunction", "disjunction", "implication", "equivalence"] 62 for pair in zip(names, [Tokens.NOT, Tokens.AND, Tokens.OR, Tokens.IMP, Tokens.IFF]): 63 print "%-15s\t%s" % pair
64
65 -def equality_preds():
66 """ 67 Equality predicates 68 """ 69 names = ["equality", "inequality"] 70 for pair in zip(names, [Tokens.EQ, Tokens.NEQ]): 71 print "%-15s\t%s" % pair
72
73 -def binding_ops():
74 """ 75 Binding operators 76 """ 77 names = ["existential", "universal", "lambda"] 78 for pair in zip(names, [Tokens.EXISTS, Tokens.ALL, Tokens.LAMBDA]): 79 print "%-15s\t%s" % pair
80 81
82 -class Variable(object):
83 - def __init__(self, name):
84 """ 85 @param name: the name of the variable 86 """ 87 assert isinstance(name, str), "%s is not a string" % name 88 self.name = name
89
90 - def __eq__(self, other):
91 return isinstance(other, Variable) and self.name == other.name
92
93 - def __neq__(self, other):
94 return not (self == other)
95
96 - def __cmp__(self, other):
97 return cmp(type(self), type(other)) or cmp(self.name, other.name)
98
99 - def substitute_bindings(self, bindings):
100 return bindings.get(self, self)
101
102 - def visit(self, function, combinator, default):
103 return default
104
105 - def __hash__(self):
106 return hash(self.name)
107
108 - def __str__(self):
109 return self.name
110
111 - def __repr__(self):
112 return 'Variable(\'' + self.name + '\')'
113 114
115 -def unique_variable(pattern=None, ignore=None):
116 """ 117 Return a new, unique variable. 118 param pattern: C{Variable} that is being replaced. The new variable must 119 be the same type. 120 param term: a C{set} of C{Variable}s that should not be returned from 121 this function. 122 return: C{Variable} 123 """ 124 if pattern is not None: 125 if is_indvar(pattern.name): 126 prefix = 'z' 127 elif is_funcvar(pattern.name): 128 prefix = 'F' 129 elif is_eventvar(pattern.name): 130 prefix = 'e0' 131 else: 132 assert False, "Cannot generate a unique constant" 133 else: 134 prefix = 'z' 135 136 v = Variable(prefix + str(_counter.get())) 137 while ignore is not None and v in ignore: 138 v = Variable(prefix + str(_counter.get())) 139 return v
140
141 -def skolem_function(univ_scope=None):
142 """ 143 Return a skolem function over the variables in univ_scope 144 param univ_scope 145 """ 146 skolem = VariableExpression(Variable('F%s' % _counter.get())) 147 if univ_scope: 148 for v in list(univ_scope): 149 skolem = skolem(VariableExpression(v)) 150 return skolem
151 152
153 -class Type(object):
154 - def __repr__(self):
155 return str(self)
156
157 - def __hash__(self):
158 return hash(str(self))
159
160 -class ComplexType(Type):
161 - def __init__(self, first, second):
162 assert(isinstance(first, Type)), "%s is not a Type" % first 163 assert(isinstance(second, Type)), "%s is not a Type" % second 164 self.first = first 165 self.second = second
166
167 - def __eq__(self, other):
168 return isinstance(other, ComplexType) and \ 169 self.first == other.first and \ 170 self.second == other.second
171
172 - def matches(self, other):
173 if isinstance(other, ComplexType): 174 return self.first.matches(other.first) and \ 175 self.second.matches(other.second) 176 else: 177 return self == ANY_TYPE
178
179 - def resolve(self, other):
180 if other == ANY_TYPE: 181 return self 182 elif isinstance(other, ComplexType): 183 f = self.first.resolve(other.first) 184 s = self.second.resolve(other.second) 185 if f and s: 186 return ComplexType(f,s) 187 else: 188 return None 189 elif self == ANY_TYPE: 190 return other 191 else: 192 return None
193
194 - def __str__(self):
195 if self == ANY_TYPE: 196 return str(ANY_TYPE) 197 else: 198 return '<%s,%s>' % (self.first, self.second)
199
200 - def str(self):
201 if self == ANY_TYPE: 202 return ANY_TYPE.str() 203 else: 204 return '(%s -> %s)' % (self.first.str(), self.second.str())
205
206 -class BasicType(Type):
207 - def __eq__(self, other):
208 return isinstance(other, BasicType) and str(self) == str(other)
209
210 - def matches(self, other):
211 return other == ANY_TYPE or self == other
212
213 - def resolve(self, other):
214 if self.matches(other): 215 return self 216 else: 217 return None
218
219 -class EntityType(BasicType):
220 - def __str__(self):
221 return 'e'
222
223 - def str(self):
224 return 'IND'
225
226 -class TruthValueType(BasicType):
227 - def __str__(self):
228 return 't'
229
230 - def str(self):
231 return 'BOOL'
232
233 -class EventType(BasicType):
234 - def __str__(self):
235 return 'v'
236
237 - def str(self):
238 return 'EVENT'
239
240 -class AnyType(BasicType, ComplexType):
241 - def __init__(self):
242 pass
243 244 first = property(lambda self: self) 245 second = property(lambda self: self) 246
247 - def __eq__(self, other):
248 return isinstance(other, AnyType) or other.__eq__(self)
249
250 - def matches(self, other):
251 return True
252
253 - def resolve(self, other):
254 return other
255
256 - def __str__(self):
257 return '?'
258
259 - def str(self):
260 return 'ANY'
261 262 263 TRUTH_TYPE = TruthValueType() 264 ENTITY_TYPE = EntityType() 265 EVENT_TYPE = EventType() 266 ANY_TYPE = AnyType() 267 268
269 -def parse_type(type_string):
270 assert isinstance(type_string, str) 271 type_string = type_string.replace(' ', '') #remove spaces 272 273 if type_string[0] == '<': 274 assert type_string[-1] == '>' 275 paren_count = 0 276 for i,char in enumerate(type_string): 277 if char == '<': 278 paren_count += 1 279 elif char == '>': 280 paren_count -= 1 281 assert paren_count > 0 282 elif char == ',': 283 if paren_count == 1: 284 break 285 return ComplexType(parse_type(type_string[1 :i ]), 286 parse_type(type_string[i+1:-1])) 287 elif type_string[0] == str(ENTITY_TYPE): 288 return ENTITY_TYPE 289 elif type_string[0] == str(TRUTH_TYPE): 290 return TRUTH_TYPE 291 elif type_string[0] == str(ANY_TYPE): 292 return ANY_TYPE 293 else: 294 raise ParseException("Unexpected character: '%s'." % type_string[0])
295 296
297 -class TypeException(Exception):
298 - def __init__(self, msg):
299 Exception.__init__(self, msg)
300
301 -class InconsistentTypeHierarchyException(TypeException):
302 - def __init__(self, variable, expression=None):
303 if expression: 304 msg = "The variable \'%s\' was found in multiple places with different"\ 305 " types in \'%s\'." % (variable, expression) 306 else: 307 msg = "The variable \'%s\' was found in multiple places with different"\ 308 " types." % (variable) 309 Exception.__init__(self, msg)
310
311 -class TypeResolutionException(TypeException):
312 - def __init__(self, expression, other_type):
313 Exception.__init__(self, "The type of '%s', '%s', cannot be " 314 "resolved with type '%s'" % \ 315 (expression, expression.type, other_type))
316
317 -class IllegalTypeException(TypeException):
318 - def __init__(self, expression, other_type, allowed_type):
319 Exception.__init__(self, "Cannot set type of %s '%s' to '%s'; " 320 "must match type '%s'." % 321 (expression.__class__.__name__, expression, 322 other_type, allowed_type))
323 324
325 -def typecheck(expressions, signature=None):
326 """ 327 Ensure correct typing across a collection of C{Expression}s. 328 @param expressions: a collection of expressions 329 @param signature: C{dict} that maps variable names to types (or string 330 representations of types) 331 """ 332 #typecheck and create master signature 333 for expression in expressions: 334 signature = expression.typecheck(signature) 335 #apply master signature to all expressions 336 for expression in expressions[:-1]: 337 expression.typecheck(signature) 338 return signature
339 340
341 -class SubstituteBindingsI(object):
342 """ 343 An interface for classes that can perform substitutions for 344 variables. 345 """
346 - def substitute_bindings(self, bindings):
347 """ 348 @return: The object that is obtained by replacing 349 each variable bound by C{bindings} with its values. 350 Aliases are already resolved. (maybe?) 351 @rtype: (any) 352 """ 353 raise NotImplementedError()
354
355 - def variables(self):
356 """ 357 @return: A list of all variables in this object. 358 """ 359 raise NotImplementedError()
360 361
362 -class Expression(SubstituteBindingsI):
363 """This is the base abstract object for all logical expressions""" 364
365 - def __call__(self, other, *additional):
366 accum = self.applyto(other) 367 for a in additional: 368 accum = accum(a) 369 return accum
370
371 - def applyto(self, other):
372 assert isinstance(other, Expression), "%s is not an Expression" % other 373 return ApplicationExpression(self, other)
374
375 - def __neg__(self):
376 return NegatedExpression(self)
377
378 - def negate(self):
379 """If this is a negated expression, remove the negation. 380 Otherwise add a negation.""" 381 return -self
382
383 - def __and__(self, other):
384 assert isinstance(other, Expression), "%s is not an Expression" % other 385 return AndExpression(self, other)
386
387 - def __or__(self, other):
388 assert isinstance(other, Expression), "%s is not an Expression" % other 389 return OrExpression(self, other)
390
391 - def __gt__(self, other):
392 assert isinstance(other, Expression), "%s is not an Expression" % other 393 return ImpExpression(self, other)
394
395 - def __lt__(self, other):
396 assert isinstance(other, Expression), "%s is not an Expression" % other 397 return IffExpression(self, other)
398
399 - def __eq__(self, other):
400 raise NotImplementedError()
401
402 - def __neq__(self, other):
403 return not (self == other)
404
405 - def equiv(self, other, prover=None):
406 """ 407 Check for logical equivalence. 408 Pass the expression (self <-> other) to the theorem prover. 409 If the prover says it is valid, then the self and other are equal. 410 411 @param other: an C{Expression} to check equality against 412 @param prover: a C{nltk.inference.api.Prover} 413 """ 414 assert isinstance(other, Expression), "%s is not an Expression" % other 415 416 if prover is None: 417 from nltk.inference import Prover9 418 prover = Prover9() 419 bicond = IffExpression(self.simplify(), other.simplify()) 420 return prover.prove(bicond)
421
422 - def __hash__(self):
423 return hash(repr(self))
424
425 - def substitute_bindings(self, bindings):
426 expr = self 427 for var in expr.variables(): 428 if var in bindings: 429 val = bindings[var] 430 if isinstance(val, Variable): 431 val = self.make_VariableExpression(val) 432 elif not isinstance(val, Expression): 433 raise ValueError('Can not substitute a non-expression ' 434 'value into an expression: %r' % (val,)) 435 # Substitute bindings in the target value. 436 val = val.substitute_bindings(bindings) 437 # Replace var w/ the target value. 438 expr = expr.replace(var, val) 439 return expr.simplify()
440
441 - def typecheck(self, signature=None):
442 """ 443 Infer and check types. Raise exceptions if necessary. 444 @param signature: C{dict} that maps variable names to types (or string 445 representations of types) 446 @return: the signature, plus any additional type mappings 447 """ 448 sig = defaultdict(list) 449 if signature: 450 for (key, val) in signature.iteritems(): 451 varEx = VariableExpression(Variable(key)) 452 if isinstance(val, Type): 453 varEx.type = val 454 else: 455 varEx.type = parse_type(val) 456 sig[key].append(varEx) 457 458 self._set_type(signature=sig) 459 460 return dict([(key, vars[0].type) for (key, vars) in sig.iteritems()])
461
462 - def findtype(self, variable):
463 """ 464 Find the type of the given variable as it is used in this expression. 465 For example, finding the type of "P" in "P(x) & Q(x,y)" yields "<e,t>" 466 @param variable: C{Variable} 467 """ 468 raise NotImplementedError()
469
470 - def _set_type(self, other_type=ANY_TYPE, signature=None):
471 """ 472 Set the type of this expression to be the given type. Raise type 473 exceptions where applicable. 474 @param other_type: C{Type} to set 475 @param signature: C{dict<str, list<AbstractVariableExpression>>} store 476 all variable expressions with a given name 477 """ 478 raise NotImplementedError()
479
480 - def replace(self, variable, expression, replace_bound=False, alpha_convert=True):
481 """ 482 Replace every instance of 'variable' with 'expression' 483 @param variable: C{Variable} The variable to replace 484 @param expression: C{Expression} The expression with which to replace it 485 @param replace_bound: C{boolean} Should bound variables be replaced? 486 """ 487 assert isinstance(variable, Variable), "%s is not a Variable" % variable 488 assert isinstance(expression, Expression), "%s is not an Expression" % expression 489 490 def combinator(a, *additional): 491 if len(additional) == 0: 492 return self.__class__(a) 493 elif len(additional) == 1: 494 return self.__class__(a, additional[0])
495 496 return self.visit(lambda e: e.replace(variable, expression, replace_bound, alpha_convert), 497 combinator, set())
498
499 - def normalize(self):
500 """Rename auto-generated unique variables""" 501 def f(e): 502 if isinstance(e,Variable): 503 if re.match(r'^z\d+$', e.name) or re.match(r'^e0\d+$', e.name): 504 return set([e]) 505 else: 506 return set([]) 507 else: 508 combinator = lambda *parts: reduce(operator.or_, parts) 509 return e.visit(f, combinator, set())
510 511 result = self 512 for i,v in enumerate(sorted(list(f(self)))): 513 if is_eventvar(v.name): 514 newVar = 'e0%s' % (i+1) 515 else: 516 newVar = 'z%s' % (i+1) 517 result = result.replace(v, 518 self.make_VariableExpression(Variable(newVar)), True) 519 return result 520
521 - def visit(self, function, combinator, default):
522 """ 523 Recursively visit sub expressions 524 @param function: C{Function} to call on each sub expression 525 @param combinator: C{Function} to combine the results of the 526 function calls 527 @return: result of combination 528 """ 529 raise NotImplementedError()
530
531 - def __repr__(self):
532 return '<%s %s>' % (self.__class__.__name__, self)
533
534 - def __str__(self):
535 return self.str()
536
537 - def variables(self):
538 """ 539 Return a set of all the variables that are available to be replaced. 540 This includes free (non-bound) variables as well as predicates. 541 @return: C{set} of C{Variable}s 542 """ 543 return self.visit(lambda e: isinstance(e,Variable) and 544 set([e]) or e.variables(), 545 lambda *parts: reduce(operator.or_, parts), set())
546
547 - def free(self, indvar_only=True):
548 """ 549 Return a set of all the free (non-bound) variables in self. Variables 550 serving as predicates are not included. 551 @param indvar_only: C{boolean} only return individual variables? 552 @return: C{set} of C{Variable}s 553 """ 554 return self.visit(lambda e: isinstance(e,Variable) and 555 set([e]) or e.free(indvar_only), 556 lambda *parts: reduce(operator.or_, parts), set())
557
558 - def simplify(self):
559 """ 560 @return: beta-converted version of this expression 561 """ 562 def combinator(a, *additional): 563 if len(additional) == 0: 564 return self.__class__(a) 565 elif len(additional) == 1: 566 return self.__class__(a, additional[0])
567 568 return self.visit(lambda e: isinstance(e,Variable) 569 and e or e.simplify(), combinator, set()) 570
571 - def make_VariableExpression(self, variable):
572 return VariableExpression(variable)
573 574
575 -class ApplicationExpression(Expression):
576 r""" 577 This class is used to represent two related types of logical expressions. 578 579 The first is a Predicate Expression, such as "P(x,y)". A predicate 580 expression is comprised of a C{FunctionVariableExpression} or 581 C{ConstantExpression} as the predicate and a list of Expressions as the 582 arguments. 583 584 The second is a an application of one expression to another, such as 585 "(\x.dog(x))(fido)". 586 587 The reason Predicate Expressions are treated as Application Expressions is 588 that the Variable Expression predicate of the expression may be replaced 589 with another Expression, such as a LambdaExpression, which would mean that 590 the Predicate should be thought of as being applied to the arguments. 591 592 The LogicParser will always curry arguments in a application expression. 593 So, "\x y.see(x,y)(john,mary)" will be represented internally as 594 "((\x y.(see(x))(y))(john))(mary)". This simplifies the internals since 595 there will always be exactly one argument in an application. 596 597 The str() method will usually print the curried forms of application 598 expressions. The one exception is when the the application expression is 599 really a predicate expression (ie, underlying function is an 600 C{AbstractVariableExpression}). This means that the example from above 601 will be returned as "(\x y.see(x,y)(john))(mary)". 602 """
603 - def __init__(self, function, argument):
604 """ 605 @param function: C{Expression}, for the function expression 606 @param argument: C{Expression}, for the argument 607 """ 608 assert isinstance(function, Expression), "%s is not an Expression" % function 609 assert isinstance(argument, Expression), "%s is not an Expression" % argument 610 self.function = function 611 self.argument = argument
612
613 - def simplify(self):
614 function = self.function.simplify() 615 argument = self.argument.simplify() 616 if isinstance(function, LambdaExpression): 617 return function.term.replace(function.variable, argument).simplify() 618 else: 619 return self.__class__(function, argument)
620
621 - def free(self, indvar_only=True):
622 """@see: Expression.free()""" 623 if isinstance(self.function, AbstractVariableExpression): 624 return self.argument.free(indvar_only) 625 else: 626 return self.function.free(indvar_only) | \ 627 self.argument.free(indvar_only)
628
629 - def _get_type(self):
630 if isinstance(self.function.type, ComplexType): 631 return self.function.type.second 632 else: 633 return ANY_TYPE
634 635 type = property(_get_type) 636
637 - def _set_type(self, other_type=ANY_TYPE, signature=None):
638 """@see Expression._set_type()""" 639 assert isinstance(other_type, Type) 640 641 if signature == None: 642 signature = defaultdict(list) 643 644 self.argument._set_type(ANY_TYPE, signature) 645 try: 646 self.function._set_type(ComplexType(self.argument.type, other_type), signature) 647 except TypeResolutionException: 648 raise TypeException( 649 "The function '%s' is of type '%s' and cannot be applied " 650 "to '%s' of type '%s'. Its argument must match type '%s'." 651 % (self.function, self.function.type, self.argument, 652 self.argument.type, self.function.type.first))
653
654 - def findtype(self, variable):
655 """@see Expression.findtype()""" 656 assert isinstance(variable, Variable), "%s is not a Variable" % variable 657 function, args = self.uncurry() 658 if not isinstance(function, AbstractVariableExpression): 659 #It's not a predicate expression ("P(x,y)"), so leave args curried 660 function = self.function 661 args = [self.argument] 662 663 found = [arg.findtype(variable) for arg in [function]+args] 664 665 unique = [] 666 for f in found: 667 if f != ANY_TYPE: 668 if unique: 669 for u in unique: 670 if f.matches(u): 671 break 672 else: 673 unique.append(f) 674 675 if len(unique) == 1: 676 return list(unique)[0] 677 else: 678 return ANY_TYPE
679
680 - def visit(self, function, combinator, default):
681 """@see: Expression.visit()""" 682 return combinator(function(self.function), function(self.argument))
683
684 - def __eq__(self, other):
685 return isinstance(other, ApplicationExpression) and \ 686 self.function == other.function and \ 687 self.argument == other.argument
688
689 - def __str__(self):
690 # uncurry the arguments and find the base function 691 function, args = self.uncurry() 692 if isinstance(function, AbstractVariableExpression): 693 #It's a predicate expression ("P(x,y)"), so uncurry arguments 694 arg_str = ','.join(map(str, args)) 695 else: 696 #Leave arguments curried 697 function = self.function 698 arg_str = str(self.argument) 699 700 function_str = str(function) 701 parenthesize_function = False 702 if isinstance(function, LambdaExpression): 703 if isinstance(function.term, ApplicationExpression): 704 if not isinstance(function.term.function, 705 AbstractVariableExpression): 706 parenthesize_function = True 707 elif not isinstance(function.term, BooleanExpression): 708 parenthesize_function = True 709 elif isinstance(function, ApplicationExpression): 710 parenthesize_function = True 711 712 if parenthesize_function: 713 function_str = Tokens.OPEN + function_str + Tokens.CLOSE 714 715 return function_str + Tokens.OPEN + arg_str + Tokens.CLOSE
716
717 - def uncurry(self):
718 """ 719 Uncurry this application expression 720 721 return: A tuple (base-function, arg-list) 722 """ 723 function = self.function 724 args = [self.argument] 725 while isinstance(function, ApplicationExpression): 726 #(\x.\y.sees(x,y)(john))(mary) 727 args.insert(0, function.argument) 728 function = function.function 729 return (function, args)
730 731 args = property(lambda self: self.uncurry()[1], doc="uncurried arg-list")
732
733 -class AbstractVariableExpression(Expression):
734 """This class represents a variable to be used as a predicate or entity"""
735 - def __init__(self, variable):
736 """ 737 @param variable: C{Variable}, for the variable 738 """ 739 assert isinstance(variable, Variable), "%s is not a Variable" % variable 740 self.variable = variable
741
742 - def simplify(self):
743 return self
744
745 - def replace(self, variable, expression, replace_bound=False, alpha_convert=True):
746 """@see: Expression.replace()""" 747 assert isinstance(variable, Variable), "%s is not an Variable" % variable 748 assert isinstance(expression, Expression), "%s is not an Expression" % expression 749 if self.variable == variable: 750 return expression 751 else: 752 return self
753
754 - def _set_type(self, other_type=ANY_TYPE, signature=None):
755 """@see Expression._set_type()""" 756 assert isinstance(other_type, Type) 757 758 if signature == None: 759 signature = defaultdict(list) 760 761 resolution = other_type 762 for varEx in signature[self.variable.name]: 763 resolution = varEx.type.resolve(resolution) 764 if not resolution: 765 raise InconsistentTypeHierarchyException(self) 766 767 signature[self.variable.name].append(self) 768 for varEx in signature[self.variable.name]: 769 varEx.type = resolution
770
771 - def findtype(self, variable):
772 """@see Expression.findtype()""" 773 assert isinstance(variable, Variable), "%s is not a Variable" % variable 774 if self.variable == variable: 775 return self.type 776 else: 777 return ANY_TYPE
778
779 - def visit(self, function, combinator, default):
780 """@see: Expression.visit()""" 781 return combinator(function(self.variable))
782
783 - def __eq__(self, other):
784 """Allow equality between instances of C{AbstractVariableExpression} 785 subtypes.""" 786 return isinstance(other, AbstractVariableExpression) and \ 787 self.variable == other.variable
788
789 - def __str__(self):
790 return str(self.variable)
791 792
793 -class IndividualVariableExpression(AbstractVariableExpression):
794 """This class represents variables that take the form of a single lowercase 795 character (other than 'e') followed by zero or more digits."""
796 - def _set_type(self, other_type=ANY_TYPE, signature=None):
797 """@see Expression._set_type()""" 798 assert isinstance(other_type, Type) 799 800 if signature == None: 801 signature = defaultdict(list) 802 803 if not other_type.matches(ENTITY_TYPE): 804 raise IllegalTypeException(self, other_type, ENTITY_TYPE) 805 806 signature[self.variable.name].append(self)
807 808 type = property(lambda self: ENTITY_TYPE, _set_type)
809
810 -class FunctionVariableExpression(AbstractVariableExpression):
811 """This class represents variables that take the form of a single uppercase 812 character followed by zero or more digits.""" 813 type = ANY_TYPE 814
815 - def free(self, indvar_only=True):
816 """@see: Expression.free()""" 817 if not indvar_only: 818 return set([self.variable]) 819 else: 820 return set()
821
822 -class EventVariableExpression(IndividualVariableExpression):
823 """This class represents variables that take the form of a single lowercase 824 'e' character followed by zero or more digits.""" 825 type = EVENT_TYPE
826
827 -class ConstantExpression(AbstractVariableExpression):
828 """This class represents variables that do not take the form of a single 829 character followed by zero or more digits.""" 830 type = ENTITY_TYPE 831
832 - def _set_type(self, other_type=ANY_TYPE, signature=None):
833 """@see Expression._set_type()""" 834 assert isinstance(other_type, Type) 835 836 if signature == None: 837 signature = defaultdict(list) 838 839 if other_type == ANY_TYPE: 840 #entity type by default, for individuals 841 resolution = ENTITY_TYPE 842 else: 843 resolution = other_type 844 if self.type != ENTITY_TYPE: 845 resolution = resolution.resolve(self.type) 846 847 for varEx in signature[self.variable.name]: 848 resolution = varEx.type.resolve(resolution) 849 if not resolution: 850 raise InconsistentTypeHierarchyException(self) 851 852 signature[self.variable.name].append(self) 853 for varEx in signature[self.variable.name]: 854 varEx.type = resolution
855
856 - def free(self, indvar_only=True):
857 """@see: Expression.free()""" 858 if not indvar_only: 859 return set([self.variable]) 860 else: 861 return set()
862 863
864 -def VariableExpression(variable):
865 """ 866 This is a factory method that instantiates and returns a subtype of 867 C{AbstractVariableExpression} appropriate for the given variable. 868 """ 869 assert isinstance(variable, Variable), "%s is not a Variable" % variable 870 if is_indvar(variable.name): 871 return IndividualVariableExpression(variable) 872 elif is_funcvar(variable.name): 873 return FunctionVariableExpression(variable) 874 elif is_eventvar(variable.name): 875 return EventVariableExpression(variable) 876 else: 877 return ConstantExpression(variable)
878 879
880 -class VariableBinderExpression(Expression):
881 """This an abstract class for any Expression that binds a variable in an 882 Expression. This includes LambdaExpressions and Quantified Expressions"""
883 - def __init__(self, variable, term):
884 """ 885 @param variable: C{Variable}, for the variable 886 @param term: C{Expression}, for the term 887 """ 888 assert isinstance(variable, Variable), "%s is not a Variable" % variable 889 assert isinstance(term, Expression), "%s is not an Expression" % term 890 self.variable = variable 891 self.term = term
892
893 - def replace(self, variable, expression, replace_bound=False, alpha_convert=True):
894 """@see: Expression.replace()""" 895 assert isinstance(variable, Variable), "%s is not a Variable" % variable 896 assert isinstance(expression, Expression), "%s is not an Expression" % expression 897 #if the bound variable is the thing being replaced 898 if self.variable == variable: 899 if replace_bound: 900 assert isinstance(expression, AbstractVariableExpression),\ 901 "%s is not a AbstractVariableExpression" % expression 902 return self.__class__(expression.variable, 903 self.term.replace(variable, expression, True, alpha_convert)) 904 else: 905 return self 906 else: 907 # if the bound variable appears in the expression, then it must 908 # be alpha converted to avoid a conflict 909 if alpha_convert and self.variable in expression.free(): 910 self = self.alpha_convert(unique_variable(pattern=self.variable)) 911 912 #replace in the term 913 return self.__class__(self.variable, 914 self.term.replace(variable, expression, replace_bound, alpha_convert))
915
916 - def alpha_convert(self, newvar):
917 """Rename all occurrences of the variable introduced by this variable 918 binder in the expression to @C{newvar}. 919 @param newvar: C{Variable}, for the new variable 920 """ 921 assert isinstance(newvar, Variable), "%s is not a Variable" % newvar 922 return self.__class__(newvar, 923 self.term.replace(self.variable, 924 VariableExpression(newvar), 925 True))
926
927 - def variables(self):
928 """@see: Expression.variables()""" 929 return self.term.variables() - set([self.variable])
930
931 - def free(self, indvar_only=True):
932 """@see: Expression.free()""" 933 return self.term.free(indvar_only) - set([self.variable])
934
935 - def findtype(self, variable):
936 """@see Expression.findtype()""" 937 assert isinstance(variable, Variable), "%s is not a Variable" % variable 938 if variable == self.variable: 939 return ANY_TYPE 940 else: 941 return self.term.findtype(variable)
942
943 - def visit(self, function, combinator, default):
944 """@see: Expression.visit()""" 945 return combinator(function(self.variable), function(self.term))
946
947 - def __eq__(self, other):
948 r"""Defines equality modulo alphabetic variance. If we are comparing 949 \x.M and \y.N, then check equality of M and N[x/y].""" 950 if isinstance(self, other.__class__) or \ 951 isinstance(other, self.__class__): 952 if self.variable == other.variable: 953 return self.term == other.term 954 else: 955 # Comparing \x.M and \y.N. Relabel y in N with x and continue. 956 varex = VariableExpression(self.variable) 957 return self.term == other.term.replace(other.variable, varex) 958 else: 959 return False
960 961
962 -class LambdaExpression(VariableBinderExpression):
963 type = property(lambda self: 964 ComplexType(self.term.findtype(self.variable), 965 self.term.type)) 966
967 - def _set_type(self, other_type=ANY_TYPE, signature=None):
968 """@see Expression._set_type()""" 969 assert isinstance(other_type, Type) 970 971 if signature == None: 972 signature = defaultdict(list) 973 974 self.term._set_type(other_type.second, signature) 975 if not self.type.resolve(other_type): 976 raise TypeResolutionException(self, other_type)
977
978 - def __str__(self):
979 variables = [self.variable] 980 term = self.term 981 while term.__class__ == self.__class__: 982 variables.append(term.variable) 983 term = term.term 984 return Tokens.LAMBDA + ' '.join(map(str, variables)) + \ 985 Tokens.DOT + str(term)
986 987
988 -class QuantifiedExpression(VariableBinderExpression):
989 type = property(lambda self: TRUTH_TYPE) 990
991 - def _set_type(self, other_type=ANY_TYPE, signature=None):
992 """@see Expression._set_type()""" 993 assert isinstance(other_type, Type) 994 995 if signature == None: 996 signature = defaultdict(list) 997 998 if not other_type.matches(TRUTH_TYPE): 999 raise IllegalTypeException(self, other_type, TRUTH_TYPE) 1000 self.term._set_type(TRUTH_TYPE, signature)
1001
1002 - def __str__(self):
1003 variables = [self.variable] 1004 term = self.term 1005 while term.__class__ == self.__class__: 1006 variables.append(term.variable) 1007 term = term.term 1008 return self.getQuantifier() + ' ' + ' '.join(map(str, variables)) + \ 1009 Tokens.DOT + str(term)
1010
1011 -class ExistsExpression(QuantifiedExpression):
1012 - def getQuantifier(self):
1013 return Tokens.EXISTS
1014
1015 -class AllExpression(QuantifiedExpression):
1016 - def getQuantifier(self):
1017 return Tokens.ALL
1018 1019
1020 -class NegatedExpression(Expression):
1021 - def __init__(self, term):
1022 assert isinstance(term, Expression), "%s is not an Expression" % term 1023 self.term = term
1024 1025 type = property(lambda self: TRUTH_TYPE) 1026
1027 - def _set_type(self, other_type=ANY_TYPE, signature=None):
1028 """@see Expression._set_type()""" 1029 assert isinstance(other_type, Type) 1030 1031 if signature == None: 1032 signature = defaultdict(list) 1033 1034 if not other_type.matches(TRUTH_TYPE): 1035 raise IllegalTypeException(self, other_type, TRUTH_TYPE) 1036 self.term._set_type(TRUTH_TYPE, signature)
1037
1038 - def findtype(self, variable):
1039 assert isinstance(variable, Variable), "%s is not a Variable" % variable 1040 return self.term.findtype(variable)
1041
1042 - def visit(self, function, combinator, default):
1043 """@see: Expression.visit()""" 1044 return combinator(function(self.term))
1045
1046 - def negate(self):
1047 """@see: Expression.negate()""" 1048 return self.term
1049
1050 - def __eq__(self, other):
1051 return isinstance(other, NegatedExpression) and self.term == other.term
1052
1053 - def __str__(self):
1054 return Tokens.NOT + str(self.term)
1055 1056
1057 -class BinaryExpression(Expression):
1058 - def __init__(self, first, second):
1059 assert isinstance(first, Expression), "%s is not an Expression" % first 1060 assert isinstance(second, Expression), "%s is not an Expression" % second 1061 self.first = first 1062 self.second = second
1063 1064 type = property(lambda self: TRUTH_TYPE) 1065
1066 - def findtype(self, variable):
1067 """@see Expression.findtype()""" 1068 assert isinstance(variable, Variable), "%s is not a Variable" % variable 1069 f = self.first.findtype(variable) 1070 s = self.second.findtype(variable) 1071 if f == s or s == ANY_TYPE: 1072 return f 1073 elif f == ANY_TYPE: 1074 return s 1075 else: 1076 return ANY_TYPE
1077
1078 - def visit(self, function, combinator, default):
1079 """@see: Expression.visit()""" 1080 return combinator(function(self.first), function(self.second))
1081
1082 - def __eq__(self, other):
1083 return (isinstance(self, other.__class__) or \ 1084 isinstance(other, self.__class__)) and \ 1085 self.first == other.first and self.second == other.second
1086
1087 - def __str__(self):
1088 first = self._str_subex(self.first) 1089 second = self._str_subex(self.second) 1090 return Tokens.OPEN + first + ' ' + self.getOp() \ 1091 + ' ' + second + Tokens.CLOSE
1092
1093 - def _str_subex(self, subex):
1094 return str(subex)
1095 1096
1097 -class BooleanExpression(BinaryExpression):
1098 - def _set_type(self, other_type=ANY_TYPE, signature=None):
1099 """@see Expression._set_type()""" 1100 assert isinstance(other_type, Type) 1101 1102 if signature == None: 1103 signature = defaultdict(list) 1104 1105 if not other_type.matches(TRUTH_TYPE): 1106 raise IllegalTypeException(self, other_type, TRUTH_TYPE) 1107 self.first._set_type(TRUTH_TYPE, signature) 1108 self.second._set_type(TRUTH_TYPE, signature)
1109
1110 -class AndExpression(BooleanExpression):
1111 """This class represents conjunctions"""
1112 - def getOp(self):
1113 return Tokens.AND
1114
1115 - def _str_subex(self, subex):
1116 s = str(subex) 1117 if isinstance(subex, AndExpression): 1118 return s[1:-1] 1119 return s
1120
1121 -class OrExpression(BooleanExpression):
1122 """This class represents disjunctions"""
1123 - def getOp(self):
1124 return Tokens.OR
1125
1126 - def _str_subex(self, subex):
1127 s = str(subex) 1128 if isinstance(subex, OrExpression): 1129 return s[1:-1] 1130 return s
1131
1132 -class ImpExpression(BooleanExpression):
1133 """This class represents implications"""
1134 - def getOp(self):
1135 return Tokens.IMP
1136
1137 -class IffExpression(BooleanExpression):
1138 """This class represents biconditionals"""
1139 - def getOp(self):
1140 return Tokens.IFF
1141 1142
1143 -class EqualityExpression(BinaryExpression):
1144 """This class represents equality expressions like "(x = y)"."""
1145 - def _set_type(self, other_type=ANY_TYPE, signature=None):
1146 """@see Expression._set_type()""" 1147 assert isinstance(other_type, Type) 1148 1149 if signature == None: 1150 signature = defaultdict(list) 1151 1152 if not other_type.matches(TRUTH_TYPE): 1153 raise IllegalTypeException(self, other_type, TRUTH_TYPE) 1154 self.first._set_type(ENTITY_TYPE, signature) 1155 self.second._set_type(ENTITY_TYPE, signature)
1156
1157 - def getOp(self):
1158 return Tokens.EQ
1159 1160
1161 -class LogicParser(object):
1162 """A lambda calculus expression parser.""" 1163
1164 - def __init__(self, type_check=False):
1165 """ 1166 @param type_check: C{boolean} should type checking be performed? 1167 to their types. 1168 """ 1169 assert isinstance(type_check, bool) 1170 1171 self._currentIndex = 0 1172 self._buffer = [] 1173 self.type_check = type_check 1174 1175 """A list of tuples of quote characters. The 4-tuple is comprised 1176 of the start character, the end character, the escape character, and 1177 a boolean indicating whether the quotes should be included in the 1178 result. Quotes are used to signify that a token should be treated as 1179 atomic, ignoring any special characters within the token. The escape 1180 character allows the quote end character to be used within the quote. 1181 If True, the boolean indicates that the final token should contain the 1182 quote and escape characters. 1183 This method exists to be overridden""" 1184 self.quote_chars = [] 1185 1186 self.operator_precedence = dict( 1187 [(x,1) for x in Tokens.LAMBDA_LIST] + \ 1188 [(x,2) for x in Tokens.NOT_LIST] + \ 1189 [(APP,3)] + \ 1190 [(x,4) for x in Tokens.EQ_LIST+Tokens.NEQ_LIST] + \ 1191 [(x,5) for x in Tokens.QUANTS] + \ 1192 [(x,6) for x in Tokens.AND_LIST] + \ 1193 [(x,7) for x in Tokens.OR_LIST] + \ 1194 [(x,8) for x in Tokens.IMP_LIST] + \ 1195 [(x,9) for x in Tokens.IFF_LIST] + \ 1196 [(None,10)]) 1197 self.right_associated_operations = [APP]
1198
1199 - def parse(self, data, signature=None):
1200 """ 1201 Parse the expression. 1202 1203 @param data: C{str} for the input to be parsed 1204 @param signature: C{dict<str, str>} that maps variable names to type 1205 strings 1206 @returns: a parsed Expression 1207 """ 1208 data = data.rstrip() 1209 1210 self._currentIndex = 0 1211 self._buffer, mapping = self.process(data) 1212 1213 try: 1214 result = self.parse_Expression(None) 1215 if self.inRange(0): 1216 raise UnexpectedTokenException(self._currentIndex+1, self.token(0)) 1217 except ParseException, e: 1218 msg = '%s\n%s\n%s^' % (e, data, ' '*mapping[e.index-1]) 1219 raise ParseException(None, msg) 1220 1221 if self.type_check: 1222 result.typecheck(signature) 1223 1224 return result
1225
1226 - def process(self, data):
1227 """Split the data into tokens""" 1228 out = [] 1229 mapping = {} 1230 tokenTrie = StringTrie(self.get_all_symbols()) 1231 token = '' 1232 data_idx = 0 1233 token_start_idx = data_idx 1234 while data_idx < len(data): 1235 cur_data_idx = data_idx 1236 quoted_token, data_idx = self.process_quoted_token(data_idx, data) 1237 if quoted_token: 1238 if not token: 1239 token_start_idx = cur_data_idx 1240 token += quoted_token 1241 continue 1242 1243 st = tokenTrie 1244 c = data[data_idx] 1245 symbol = '' 1246 while c in st: 1247 symbol += c 1248 st = st[c] 1249 if len(data)-data_idx > len(symbol): 1250 c = data[data_idx+len(symbol)] 1251 else: 1252 break 1253 if StringTrie.LEAF in st: 1254 #token is a complete symbol 1255 if token: 1256 mapping[len(out)] = token_start_idx 1257 out.append(token) 1258 token = '' 1259 mapping[len(out)] = data_idx 1260 out.append(symbol) 1261 data_idx += len(symbol) 1262 else: 1263 if data[data_idx] in ' \t\n': #any whitespace 1264 if token: 1265 mapping[len(out)] = token_start_idx 1266 out.append(token) 1267 token = '' 1268 else: 1269 if not token: 1270 token_start_idx = data_idx 1271 token += data[data_idx] 1272 data_idx += 1 1273 if token: 1274 mapping[len(out)] = token_start_idx 1275 out.append(token) 1276 mapping[len(out)] = len(data) 1277 mapping[len(out)+1] = len(data)+1 1278 return out, mapping
1279
1280 - def process_quoted_token(self, data_idx, data):
1281 token = '' 1282 c = data[data_idx] 1283 i = data_idx 1284 for start, end, escape, incl_quotes in self.quote_chars: 1285 if c == start: 1286 if incl_quotes: 1287 token += c 1288 i += 1 1289 while data[i] != end: 1290 if data[i] == escape: 1291 if incl_quotes: 1292 token += data[i] 1293 i += 1 1294 if len(data) == i: #if there are no more chars 1295 raise ParseException(None, "End of input reached. " 1296 "Escape character [%s] found at end." 1297 % escape) 1298 token += data[i] 1299 else: 1300 token += data[i] 1301 i += 1 1302 if len(data) == i: 1303 raise ParseException(None, "End of input reached. " 1304 "Expected: [%s]" % end) 1305 if incl_quotes: 1306 token += data[i] 1307 i += 1 1308 if not token: 1309 raise ParseException(None, 'Empty quoted token found') 1310 break 1311 return token, i
1312
1313 - def get_all_symbols(self):
1314 """This method exists to be overridden""" 1315 return Tokens.SYMBOLS
1316
1317 - def inRange(self, location):
1318 """Return TRUE if the given location is within the buffer""" 1319 return self._currentIndex+location < len(self._buffer)
1320
1321 - def token(self, location=None):
1322 """Get the next waiting token. If a location is given, then 1323 return the token at currentIndex+location without advancing 1324 currentIndex; setting it gives lookahead/lookback capability.""" 1325 try: 1326 if location == None: 1327 tok = self._buffer[self._currentIndex] 1328 self._currentIndex += 1 1329 else: 1330 tok = self._buffer[self._currentIndex+location] 1331 return tok 1332 except IndexError: 1333 raise ExpectedMoreTokensException(self._currentIndex+1)
1334
1335 - def isvariable(self, tok):
1336 return tok not in Tokens.TOKENS
1337
1338 - def parse_Expression(self, context):
1339 """Parse the next complete expression from the stream and return it.""" 1340 try: 1341 tok = self.token() 1342 except ExpectedMoreTokensException: 1343 raise ExpectedMoreTokensException(self._currentIndex+1, message='Expression expected.') 1344 1345 accum = self.handle(tok, context) 1346 1347 if not accum: 1348 raise UnexpectedTokenException(self._currentIndex, tok, message='Expression expected.') 1349 1350 return self.attempt_adjuncts(accum, context)
1351
1352 - def handle(self, tok, context):
1353 """This method is intended to be overridden for logics that 1354 use different operators or expressions""" 1355 if self.isvariable(tok): 1356 return self.handle_variable(tok, context) 1357 1358 elif tok in Tokens.NOT_LIST: 1359 return self.handle_negation(tok, context) 1360 1361 elif tok in Tokens.LAMBDA_LIST: 1362 return self.handle_lambda(tok, context) 1363 1364 elif tok in Tokens.QUANTS: 1365 return self.handle_quant(tok, context) 1366 1367 elif tok == Tokens.OPEN: 1368 return self.handle_open(tok, context)
1369
1370 - def attempt_adjuncts(self, expression, context):
1371 cur_idx = None 1372 while cur_idx != self._currentIndex: #while adjuncts are added 1373 cur_idx = self._currentIndex 1374 expression = self.attempt_EqualityExpression(expression, context) 1375 expression = self.attempt_ApplicationExpression(expression, context) 1376 expression = self.attempt_BooleanExpression(expression, context) 1377 return expression
1378
1379 - def handle_negation(self, tok, context):
1381
1382 - def make_NegatedExpression(self, expression):
1383 return NegatedExpression(expression)
1384
1385 - def handle_variable(self, tok, context):
1386 #It's either: 1) a predicate expression: sees(x,y) 1387 # 2) an application expression: P(x) 1388 # 3) a solo variable: john OR x 1389 accum = self.make_VariableExpression(tok) 1390 if self.inRange(0) and self.token(0) == Tokens.OPEN: 1391 #The predicate has arguments 1392 if not isinstance(accum, FunctionVariableExpression) and \ 1393 not isinstance(accum, ConstantExpression): 1394 raise ParseException(self._currentIndex, 1395 '\'%s\' is an illegal predicate name. ' 1396 'Individual variables may not be used as ' 1397 'predicates.' % tok) 1398 self.token() #swallow the Open Paren 1399 1400 #curry the arguments 1401 accum = self.make_ApplicationExpression(accum, self.parse_Expression(APP)) 1402 while self.inRange(0) and self.token(0) == Tokens.COMMA: 1403 self.token() #swallow the comma 1404 accum = self.make_ApplicationExpression(accum, self.parse_Expression(APP)) 1405 self.assertNextToken(Tokens.CLOSE) 1406 return accum
1407
1408 - def get_next_token_variable(self, description):
1409 try: 1410 tok = self.token() 1411 except ExpectedMoreTokensException, e: 1412 raise ExpectedMoreTokensException(e.index, 'Variable expected.') 1413 if isinstance(self.make_VariableExpression(tok), ConstantExpression): 1414 raise ParseException(self._currentIndex, 1415 '\'%s\' is an illegal variable name. ' 1416 'Constants may not be %s.' % (tok, description)) 1417 return Variable(tok)
1418
1419 - def handle_lambda(self, tok, context):
1420 # Expression is a lambda expression 1421 if not self.inRange(0): 1422 raise ExpectedMoreTokensException(self._currentIndex+2, 1423 message="Variable and Expression expected following lambda operator.") 1424 vars = [self.get_next_token_variable('abstracted')] 1425 while True: 1426 if not self.inRange(0) or (self.token(0) == Tokens.DOT and not self.inRange(1)): 1427 raise ExpectedMoreTokensException(self._currentIndex+2, message="Expression expected.") 1428 if not self.isvariable(self.token(0)): 1429 break 1430 # Support expressions like: \x y.M == \x.\y.M 1431 vars.append(self.get_next_token_variable('abstracted')) 1432 if self.inRange(0) and self.token(0) == Tokens.DOT: 1433 self.token() #swallow the dot 1434 1435 accum = self.parse_Expression(tok) 1436 while vars: 1437 accum = self.make_LambdaExpression(vars.pop(), accum) 1438 return accum
1439
1440 - def handle_quant(self, tok, context):
1441 # Expression is a quantified expression: some x.M 1442 factory = self.get_QuantifiedExpression_factory(tok) 1443 1444 if not self.inRange(0): 1445 raise ExpectedMoreTokensException(self._currentIndex+2, 1446 message="Variable and Expression expected following quantifier '%s'." % tok) 1447 vars = [self.get_next_token_variable('quantified')] 1448 while True: 1449 if not self.inRange(0) or (self.token(0) == Tokens.DOT and not self.inRange(1)): 1450 raise ExpectedMoreTokensException(self._currentIndex+2, message="Expression expected.") 1451 if not self.isvariable(self.token(0)): 1452 break 1453 # Support expressions like: some x y.M == some x.some y.M 1454 vars.append(self.get_next_token_variable('quantified')) 1455 if self.inRange(0) and self.token(0) == Tokens.DOT: 1456 self.token() #swallow the dot 1457 1458 accum = self.parse_Expression(tok) 1459 while vars: 1460 accum = self.make_QuanifiedExpression(factory, vars.pop(), accum) 1461 return accum
1462
1463 - def get_QuantifiedExpression_factory(self, tok):
1464 """This method serves as a hook for other logic parsers that 1465 have different quantifiers""" 1466 if tok in Tokens.EXISTS_LIST: 1467 return ExistsExpression 1468 elif tok in Tokens.ALL_LIST: 1469 return AllExpression 1470 else: 1471 self.assertToken(tok, Tokens.QUANTS)
1472
1473 - def make_QuanifiedExpression(self, factory, variable, term):
1474 return factory(variable, term)
1475
1476 - def handle_open(self, tok, context):
1477 #Expression is in parens 1478 accum = self.parse_Expression(None) 1479 self.assertNextToken(Tokens.CLOSE) 1480 return accum
1481
1482 - def attempt_EqualityExpression(self, expression, context):
1483 """Attempt to make an equality expression. If the next token is an 1484 equality operator, then an EqualityExpression will be returned. 1485 Otherwise, the parameter will be returned.""" 1486 if self.inRange(0): 1487 tok = self.token(0) 1488 if tok in Tokens.EQ_LIST + Tokens.NEQ_LIST and self.has_priority(tok, context): 1489 self.token() #swallow the "=" or "!=" 1490 expression = self.make_EqualityExpression(expression, self.parse_Expression(tok)) 1491 if tok in Tokens.NEQ_LIST: 1492 expression = self.make_NegatedExpression(expression) 1493 return expression
1494
1495 - def make_EqualityExpression(self, first, second):
1496 """This method serves as a hook for other logic parsers that 1497 have different equality expression classes""" 1498 return EqualityExpression(first, second)
1499
1500 - def attempt_BooleanExpression(self, expression, context):
1501 """Attempt to make a boolean expression. If the next token is a boolean 1502 operator, then a BooleanExpression will be returned. Otherwise, the 1503 parameter will be returned.""" 1504 while self.inRange(0): 1505 tok = self.token(0) 1506 factory = self.get_BooleanExpression_factory(tok) 1507 if factory and self.has_priority(tok, context): 1508 self.token() #swallow the operator 1509 expression = self.make_BooleanExpression(factory, expression, 1510 self.parse_Expression(tok)) 1511 else: 1512 break 1513 return expression
1514
1515 - def get_BooleanExpression_factory(self, tok):
1516 """This method serves as a hook for other logic parsers that 1517 have different boolean operators""" 1518 if tok in Tokens.AND_LIST: 1519 return AndExpression 1520 elif tok in Tokens.OR_LIST: 1521 return OrExpression 1522 elif tok in Tokens.IMP_LIST: 1523 return ImpExpression 1524 elif tok in Tokens.IFF_LIST: 1525 return IffExpression 1526 else: 1527 return None
1528
1529 - def make_BooleanExpression(self, factory, first, second):
1530 return factory(first, second)
1531
1532 - def attempt_ApplicationExpression(self, expression, context):
1533 """Attempt to make an application expression. The next tokens are 1534 a list of arguments in parens, then the argument expression is a 1535 function being applied to the arguments. Otherwise, return the 1536 argument expression.""" 1537 if self.has_priority(APP, context): 1538 if self.inRange(0) and self.token(0) == Tokens.OPEN: 1539 if not isinstance(expression, LambdaExpression) and \ 1540 not isinstance(expression, ApplicationExpression) and \ 1541 not isinstance(expression, FunctionVariableExpression) and \ 1542 not isinstance(expression, ConstantExpression): 1543 raise ParseException(self._currentIndex, 1544 "The function '" + str(expression) + 1545 "' is not a Lambda Expression, an " 1546 "Application Expression, or a " 1547 "functional predicate, so it may " 1548 "not take arguments.") 1549 self.token() #swallow then open paren 1550 #curry the arguments 1551 accum = self.make_ApplicationExpression(expression, self.parse_Expression(APP)) 1552 while self.inRange(0) and self.token(0) == Tokens.COMMA: 1553 self.token() #swallow the comma 1554 accum = self.make_ApplicationExpression(accum, self.parse_Expression(APP)) 1555 self.assertNextToken(Tokens.CLOSE) 1556 return accum 1557 return expression
1558
1559 - def make_ApplicationExpression(self, function, argument):
1560 return ApplicationExpression(function, argument)
1561
1562 - def make_VariableExpression(self, name):
1564
1565 - def make_LambdaExpression(self, variable, term):
1566 return LambdaExpression(variable, term)
1567
1568 - def has_priority(self, operation, context):
1569 return self.operator_precedence[operation] < self.operator_precedence[context] or \ 1570 (operation in self.right_associated_operations and \ 1571 self.operator_precedence[operation] == self.operator_precedence[context])
1572
1573 - def assertNextToken(self, expected):
1574 try: 1575 tok = self.token() 1576 except ExpectedMoreTokensException, e: 1577 raise ExpectedMoreTokensException(e.index, message="Expected token '%s'." % expected) 1578 1579 if isinstance(expected, list): 1580 if tok not in expected: 1581 raise UnexpectedTokenException(self._currentIndex, tok, expected) 1582 else: 1583 if tok != expected: 1584 raise UnexpectedTokenException(self._currentIndex, tok, expected)
1585
1586 - def assertToken(self, tok, expected):
1587 if isinstance(expected, list): 1588 if tok not in expected: 1589 raise UnexpectedTokenException(self._currentIndex, tok, expected) 1590 else: 1591 if tok != expected: 1592 raise UnexpectedTokenException(self._currentIndex, tok, expected)
1593
1594 - def __repr__(self):
1595 if self.inRange(0): 1596 msg = 'Next token: ' + self.token(0) 1597 else: 1598 msg = 'No more tokens' 1599 return '<' + self.__class__.__name__ + ': ' + msg + '>'
1600 1601
1602 -class StringTrie(defaultdict):
1603 LEAF = "<leaf>" 1604
1605 - def __init__(self, strings=None):
1606 defaultdict.__init__(self, StringTrie) 1607 if strings: 1608 for string in strings: 1609 self.insert(string)
1610
1611 - def insert(self, string):
1612 if len(string): 1613 self[string[0]].insert(string[1:]) 1614 else: 1615 #mark the string is complete 1616 self[StringTrie.LEAF] = None
1617 1618
1619 -class ParseException(Exception):
1620 - def __init__(self, index, message):
1621 self.index = index 1622 Exception.__init__(self, message)
1623
1624 -class UnexpectedTokenException(ParseException):
1625 - def __init__(self, index, unexpected=None, expected=None, message=None):
1626 if unexpected and expected: 1627 msg = "Unexpected token: '%s'. " \ 1628 "Expected token '%s'." % (unexpected, expected) 1629 elif unexpected: 1630 msg = "Unexpected token: '%s'." % unexpected 1631 if message: 1632 msg += ' '+message 1633 else: 1634 msg = "Expected token '%s'." % expected 1635 ParseException.__init__(self, index, msg)
1636
1637 -class ExpectedMoreTokensException(ParseException):
1638 - def __init__(self, index, message=None):
1639 if not message: 1640 message = 'More tokens expected.' 1641 ParseException.__init__(self, index, 'End of input found. ' + message)
1642 1643
1644 -def is_indvar(expr):
1645 """ 1646 An individual variable must be a single lowercase character other than 'e', 1647 followed by zero or more digits. 1648 1649 @param expr: C{str} 1650 @return: C{boolean} True if expr is of the correct form 1651 """ 1652 assert isinstance(expr, str), "%s is not a string" % expr 1653 return re.match(r'^[a-df-z]\d*$', expr) is not None
1654
1655 -def is_funcvar(expr):
1656 """ 1657 A function variable must be a single uppercase character followed by 1658 zero or more digits. 1659 1660 @param expr: C{str} 1661 @return: C{boolean} True if expr is of the correct form 1662 """ 1663 assert isinstance(expr, str), "%s is not a string" % expr 1664 return re.match(r'^[A-Z]\d*$', expr) is not None
1665
1666 -def is_eventvar(expr):
1667 """ 1668 An event variable must be a single lowercase 'e' character followed by 1669 zero or more digits. 1670 1671 @param expr: C{str} 1672 @return: C{boolean} True if expr is of the correct form 1673 """ 1674 assert isinstance(expr, str), "%s is not a string" % expr 1675 return re.match(r'^e\d*$', expr) is not None
1676 1677
1678 -def demo():
1679 p = LogicParser().parse 1680 print '='*20 + 'Test parser' + '='*20 1681 print p(r'john') 1682 print p(r'man(x)') 1683 print p(r'-man(x)') 1684 print p(r'(man(x) & tall(x) & walks(x))') 1685 print p(r'exists x.(man(x) & tall(x) & walks(x))') 1686 print p(r'\x.man(x)') 1687 print p(r'\x.man(x)(john)') 1688 print p(r'\x y.sees(x,y)') 1689 print p(r'\x y.sees(x,y)(a,b)') 1690 print p(r'(\x.exists y.walks(x,y))(x)') 1691 print p(r'exists x.x = y') 1692 print p(r'exists x.(x = y)') 1693 print p('P(x) & x=y & P(y)') 1694 print p(r'\P Q.exists x.(P(x) & Q(x))') 1695 print p(r'man(x) <-> tall(x)') 1696 1697 print '='*20 + 'Test simplify' + '='*20 1698 print p(r'\x.\y.sees(x,y)(john)(mary)').simplify() 1699 print p(r'\x.\y.sees(x,y)(john, mary)').simplify() 1700 print p(r'all x.(man(x) & (\x.exists y.walks(x,y))(x))').simplify() 1701 print p(r'(\P.\Q.exists x.(P(x) & Q(x)))(\x.dog(x))(\x.bark(x))').simplify() 1702 1703 print '='*20 + 'Test alpha conversion and binder expression equality' + '='*20 1704 e1 = p('exists x.P(x)') 1705 print e1 1706 e2 = e1.alpha_convert(Variable('z')) 1707 print e2 1708 print e1 == e2
1709
1710 -def demo_errors():
1711 print '='*20 + 'Test parser errors' + '='*20 1712 demoException('(P(x) & Q(x)') 1713 demoException('((P(x) &) & Q(x))') 1714 demoException('P(x) -> ') 1715 demoException('P(x') 1716 demoException('P(x,') 1717 demoException('P(x,)') 1718 demoException('exists') 1719 demoException('exists x.') 1720 demoException('\\') 1721 demoException('\\ x y.') 1722 demoException('P(x)Q(x)') 1723 demoException('(P(x)Q(x)') 1724 demoException('exists x -> y')
1725
1726 -def demoException(s):
1727 try: 1728 LogicParser().parse(s) 1729 except ParseException, e: 1730 print "%s: %s" % (e.__class__.__name__, e)
1731
1732 -def printtype(ex):
1733 print ex.str() + ' : ' + str(ex.type)
1734 1735 if __name__ == '__main__': 1736 demo() 1737 demo_errors() 1738