123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073 |
- # Natural Language Toolkit: Logic
- #
- # Author: Dan Garrette <dhgarrette@gmail.com>
- #
- # Copyright (C) 2001-2019 NLTK Project
- # URL: <http://nltk.org>
- # For license information, see LICENSE.TXT
- """
- A version of first order predicate logic, built on
- top of the typed lambda calculus.
- """
- from __future__ import print_function, unicode_literals
- import re
- import operator
- from collections import defaultdict
- from functools import reduce, total_ordering
- from six import string_types
- from nltk.util import Trie
- from nltk.internals import Counter
- from nltk.compat import python_2_unicode_compatible
- APP = 'APP'
- _counter = Counter()
- class Tokens(object):
- LAMBDA = '\\'
- LAMBDA_LIST = ['\\']
- # Quantifiers
- EXISTS = 'exists'
- EXISTS_LIST = ['some', 'exists', 'exist']
- ALL = 'all'
- ALL_LIST = ['all', 'forall']
- # Punctuation
- DOT = '.'
- OPEN = '('
- CLOSE = ')'
- COMMA = ','
- # Operations
- NOT = '-'
- NOT_LIST = ['not', '-', '!']
- AND = '&'
- AND_LIST = ['and', '&', '^']
- OR = '|'
- OR_LIST = ['or', '|']
- IMP = '->'
- IMP_LIST = ['implies', '->', '=>']
- IFF = '<->'
- IFF_LIST = ['iff', '<->', '<=>']
- EQ = '='
- EQ_LIST = ['=', '==']
- NEQ = '!='
- NEQ_LIST = ['!=']
- # Collections of tokens
- BINOPS = AND_LIST + OR_LIST + IMP_LIST + IFF_LIST
- QUANTS = EXISTS_LIST + ALL_LIST
- PUNCT = [DOT, OPEN, CLOSE, COMMA]
- TOKENS = BINOPS + EQ_LIST + NEQ_LIST + QUANTS + LAMBDA_LIST + PUNCT + NOT_LIST
- # Special
- SYMBOLS = [x for x in TOKENS if re.match(r'^[-\\.(),!&^|>=<]*$', x)]
- def boolean_ops():
- """
- Boolean operators
- """
- names = ["negation", "conjunction", "disjunction", "implication", "equivalence"]
- for pair in zip(names, [Tokens.NOT, Tokens.AND, Tokens.OR, Tokens.IMP, Tokens.IFF]):
- print("%-15s\t%s" % pair)
- def equality_preds():
- """
- Equality predicates
- """
- names = ["equality", "inequality"]
- for pair in zip(names, [Tokens.EQ, Tokens.NEQ]):
- print("%-15s\t%s" % pair)
- def binding_ops():
- """
- Binding operators
- """
- names = ["existential", "universal", "lambda"]
- for pair in zip(names, [Tokens.EXISTS, Tokens.ALL, Tokens.LAMBDA]):
- print("%-15s\t%s" % pair)
- @python_2_unicode_compatible
- class LogicParser(object):
- """A lambda calculus expression parser."""
- def __init__(self, type_check=False):
- """
- :param type_check: bool should type checking be performed?
- to their types.
- """
- assert isinstance(type_check, bool)
- self._currentIndex = 0
- self._buffer = []
- self.type_check = type_check
- """A list of tuples of quote characters. The 4-tuple is comprised
- of the start character, the end character, the escape character, and
- a boolean indicating whether the quotes should be included in the
- result. Quotes are used to signify that a token should be treated as
- atomic, ignoring any special characters within the token. The escape
- character allows the quote end character to be used within the quote.
- If True, the boolean indicates that the final token should contain the
- quote and escape characters.
- This method exists to be overridden"""
- self.quote_chars = []
- self.operator_precedence = dict(
- [(x, 1) for x in Tokens.LAMBDA_LIST]
- + [(x, 2) for x in Tokens.NOT_LIST]
- + [(APP, 3)]
- + [(x, 4) for x in Tokens.EQ_LIST + Tokens.NEQ_LIST]
- + [(x, 5) for x in Tokens.QUANTS]
- + [(x, 6) for x in Tokens.AND_LIST]
- + [(x, 7) for x in Tokens.OR_LIST]
- + [(x, 8) for x in Tokens.IMP_LIST]
- + [(x, 9) for x in Tokens.IFF_LIST]
- + [(None, 10)]
- )
- self.right_associated_operations = [APP]
- def parse(self, data, signature=None):
- """
- Parse the expression.
- :param data: str for the input to be parsed
- :param signature: ``dict<str, str>`` that maps variable names to type
- strings
- :returns: a parsed Expression
- """
- data = data.rstrip()
- self._currentIndex = 0
- self._buffer, mapping = self.process(data)
- try:
- result = self.process_next_expression(None)
- if self.inRange(0):
- raise UnexpectedTokenException(self._currentIndex + 1, self.token(0))
- except LogicalExpressionException as e:
- msg = '%s\n%s\n%s^' % (e, data, ' ' * mapping[e.index - 1])
- raise LogicalExpressionException(None, msg)
- if self.type_check:
- result.typecheck(signature)
- return result
- def process(self, data):
- """Split the data into tokens"""
- out = []
- mapping = {}
- tokenTrie = Trie(self.get_all_symbols())
- token = ''
- data_idx = 0
- token_start_idx = data_idx
- while data_idx < len(data):
- cur_data_idx = data_idx
- quoted_token, data_idx = self.process_quoted_token(data_idx, data)
- if quoted_token:
- if not token:
- token_start_idx = cur_data_idx
- token += quoted_token
- continue
- st = tokenTrie
- c = data[data_idx]
- symbol = ''
- while c in st:
- symbol += c
- st = st[c]
- if len(data) - data_idx > len(symbol):
- c = data[data_idx + len(symbol)]
- else:
- break
- if Trie.LEAF in st:
- # token is a complete symbol
- if token:
- mapping[len(out)] = token_start_idx
- out.append(token)
- token = ''
- mapping[len(out)] = data_idx
- out.append(symbol)
- data_idx += len(symbol)
- else:
- if data[data_idx] in ' \t\n': # any whitespace
- if token:
- mapping[len(out)] = token_start_idx
- out.append(token)
- token = ''
- else:
- if not token:
- token_start_idx = data_idx
- token += data[data_idx]
- data_idx += 1
- if token:
- mapping[len(out)] = token_start_idx
- out.append(token)
- mapping[len(out)] = len(data)
- mapping[len(out) + 1] = len(data) + 1
- return out, mapping
- def process_quoted_token(self, data_idx, data):
- token = ''
- c = data[data_idx]
- i = data_idx
- for start, end, escape, incl_quotes in self.quote_chars:
- if c == start:
- if incl_quotes:
- token += c
- i += 1
- while data[i] != end:
- if data[i] == escape:
- if incl_quotes:
- token += data[i]
- i += 1
- if len(data) == i: # if there are no more chars
- raise LogicalExpressionException(
- None,
- "End of input reached. "
- "Escape character [%s] found at end." % escape,
- )
- token += data[i]
- else:
- token += data[i]
- i += 1
- if len(data) == i:
- raise LogicalExpressionException(
- None, "End of input reached. " "Expected: [%s]" % end
- )
- if incl_quotes:
- token += data[i]
- i += 1
- if not token:
- raise LogicalExpressionException(None, 'Empty quoted token found')
- break
- return token, i
- def get_all_symbols(self):
- """This method exists to be overridden"""
- return Tokens.SYMBOLS
- def inRange(self, location):
- """Return TRUE if the given location is within the buffer"""
- return self._currentIndex + location < len(self._buffer)
- def token(self, location=None):
- """Get the next waiting token. If a location is given, then
- return the token at currentIndex+location without advancing
- currentIndex; setting it gives lookahead/lookback capability."""
- try:
- if location is None:
- tok = self._buffer[self._currentIndex]
- self._currentIndex += 1
- else:
- tok = self._buffer[self._currentIndex + location]
- return tok
- except IndexError:
- raise ExpectedMoreTokensException(self._currentIndex + 1)
- def isvariable(self, tok):
- return tok not in Tokens.TOKENS
- def process_next_expression(self, context):
- """Parse the next complete expression from the stream and return it."""
- try:
- tok = self.token()
- except ExpectedMoreTokensException:
- raise ExpectedMoreTokensException(
- self._currentIndex + 1, message='Expression expected.'
- )
- accum = self.handle(tok, context)
- if not accum:
- raise UnexpectedTokenException(
- self._currentIndex, tok, message='Expression expected.'
- )
- return self.attempt_adjuncts(accum, context)
- def handle(self, tok, context):
- """This method is intended to be overridden for logics that
- use different operators or expressions"""
- if self.isvariable(tok):
- return self.handle_variable(tok, context)
- elif tok in Tokens.NOT_LIST:
- return self.handle_negation(tok, context)
- elif tok in Tokens.LAMBDA_LIST:
- return self.handle_lambda(tok, context)
- elif tok in Tokens.QUANTS:
- return self.handle_quant(tok, context)
- elif tok == Tokens.OPEN:
- return self.handle_open(tok, context)
- def attempt_adjuncts(self, expression, context):
- cur_idx = None
- while cur_idx != self._currentIndex: # while adjuncts are added
- cur_idx = self._currentIndex
- expression = self.attempt_EqualityExpression(expression, context)
- expression = self.attempt_ApplicationExpression(expression, context)
- expression = self.attempt_BooleanExpression(expression, context)
- return expression
- def handle_negation(self, tok, context):
- return self.make_NegatedExpression(self.process_next_expression(Tokens.NOT))
- def make_NegatedExpression(self, expression):
- return NegatedExpression(expression)
- def handle_variable(self, tok, context):
- # It's either: 1) a predicate expression: sees(x,y)
- # 2) an application expression: P(x)
- # 3) a solo variable: john OR x
- accum = self.make_VariableExpression(tok)
- if self.inRange(0) and self.token(0) == Tokens.OPEN:
- # The predicate has arguments
- if not isinstance(accum, FunctionVariableExpression) and not isinstance(
- accum, ConstantExpression
- ):
- raise LogicalExpressionException(
- self._currentIndex,
- "'%s' is an illegal predicate name. "
- "Individual variables may not be used as "
- "predicates." % tok,
- )
- self.token() # swallow the Open Paren
- # curry the arguments
- accum = self.make_ApplicationExpression(
- accum, self.process_next_expression(APP)
- )
- while self.inRange(0) and self.token(0) == Tokens.COMMA:
- self.token() # swallow the comma
- accum = self.make_ApplicationExpression(
- accum, self.process_next_expression(APP)
- )
- self.assertNextToken(Tokens.CLOSE)
- return accum
- def get_next_token_variable(self, description):
- try:
- tok = self.token()
- except ExpectedMoreTokensException as e:
- raise ExpectedMoreTokensException(e.index, 'Variable expected.')
- if isinstance(self.make_VariableExpression(tok), ConstantExpression):
- raise LogicalExpressionException(
- self._currentIndex,
- "'%s' is an illegal variable name. "
- "Constants may not be %s." % (tok, description),
- )
- return Variable(tok)
- def handle_lambda(self, tok, context):
- # Expression is a lambda expression
- if not self.inRange(0):
- raise ExpectedMoreTokensException(
- self._currentIndex + 2,
- message="Variable and Expression expected following lambda operator.",
- )
- vars = [self.get_next_token_variable('abstracted')]
- while True:
- if not self.inRange(0) or (
- self.token(0) == Tokens.DOT and not self.inRange(1)
- ):
- raise ExpectedMoreTokensException(
- self._currentIndex + 2, message="Expression expected."
- )
- if not self.isvariable(self.token(0)):
- break
- # Support expressions like: \x y.M == \x.\y.M
- vars.append(self.get_next_token_variable('abstracted'))
- if self.inRange(0) and self.token(0) == Tokens.DOT:
- self.token() # swallow the dot
- accum = self.process_next_expression(tok)
- while vars:
- accum = self.make_LambdaExpression(vars.pop(), accum)
- return accum
- def handle_quant(self, tok, context):
- # Expression is a quantified expression: some x.M
- factory = self.get_QuantifiedExpression_factory(tok)
- if not self.inRange(0):
- raise ExpectedMoreTokensException(
- self._currentIndex + 2,
- message="Variable and Expression expected following quantifier '%s'."
- % tok,
- )
- vars = [self.get_next_token_variable('quantified')]
- while True:
- if not self.inRange(0) or (
- self.token(0) == Tokens.DOT and not self.inRange(1)
- ):
- raise ExpectedMoreTokensException(
- self._currentIndex + 2, message="Expression expected."
- )
- if not self.isvariable(self.token(0)):
- break
- # Support expressions like: some x y.M == some x.some y.M
- vars.append(self.get_next_token_variable('quantified'))
- if self.inRange(0) and self.token(0) == Tokens.DOT:
- self.token() # swallow the dot
- accum = self.process_next_expression(tok)
- while vars:
- accum = self.make_QuanifiedExpression(factory, vars.pop(), accum)
- return accum
- def get_QuantifiedExpression_factory(self, tok):
- """This method serves as a hook for other logic parsers that
- have different quantifiers"""
- if tok in Tokens.EXISTS_LIST:
- return ExistsExpression
- elif tok in Tokens.ALL_LIST:
- return AllExpression
- else:
- self.assertToken(tok, Tokens.QUANTS)
- def make_QuanifiedExpression(self, factory, variable, term):
- return factory(variable, term)
- def handle_open(self, tok, context):
- # Expression is in parens
- accum = self.process_next_expression(None)
- self.assertNextToken(Tokens.CLOSE)
- return accum
- def attempt_EqualityExpression(self, expression, context):
- """Attempt to make an equality expression. If the next token is an
- equality operator, then an EqualityExpression will be returned.
- Otherwise, the parameter will be returned."""
- if self.inRange(0):
- tok = self.token(0)
- if tok in Tokens.EQ_LIST + Tokens.NEQ_LIST and self.has_priority(
- tok, context
- ):
- self.token() # swallow the "=" or "!="
- expression = self.make_EqualityExpression(
- expression, self.process_next_expression(tok)
- )
- if tok in Tokens.NEQ_LIST:
- expression = self.make_NegatedExpression(expression)
- return expression
- def make_EqualityExpression(self, first, second):
- """This method serves as a hook for other logic parsers that
- have different equality expression classes"""
- return EqualityExpression(first, second)
- def attempt_BooleanExpression(self, expression, context):
- """Attempt to make a boolean expression. If the next token is a boolean
- operator, then a BooleanExpression will be returned. Otherwise, the
- parameter will be returned."""
- while self.inRange(0):
- tok = self.token(0)
- factory = self.get_BooleanExpression_factory(tok)
- if factory and self.has_priority(tok, context):
- self.token() # swallow the operator
- expression = self.make_BooleanExpression(
- factory, expression, self.process_next_expression(tok)
- )
- else:
- break
- return expression
- def get_BooleanExpression_factory(self, tok):
- """This method serves as a hook for other logic parsers that
- have different boolean operators"""
- if tok in Tokens.AND_LIST:
- return AndExpression
- elif tok in Tokens.OR_LIST:
- return OrExpression
- elif tok in Tokens.IMP_LIST:
- return ImpExpression
- elif tok in Tokens.IFF_LIST:
- return IffExpression
- else:
- return None
- def make_BooleanExpression(self, factory, first, second):
- return factory(first, second)
- def attempt_ApplicationExpression(self, expression, context):
- """Attempt to make an application expression. The next tokens are
- a list of arguments in parens, then the argument expression is a
- function being applied to the arguments. Otherwise, return the
- argument expression."""
- if self.has_priority(APP, context):
- if self.inRange(0) and self.token(0) == Tokens.OPEN:
- if (
- not isinstance(expression, LambdaExpression)
- and not isinstance(expression, ApplicationExpression)
- and not isinstance(expression, FunctionVariableExpression)
- and not isinstance(expression, ConstantExpression)
- ):
- raise LogicalExpressionException(
- self._currentIndex,
- ("The function '%s" % expression)
- + "' is not a Lambda Expression, an "
- "Application Expression, or a "
- "functional predicate, so it may "
- "not take arguments.",
- )
- self.token() # swallow then open paren
- # curry the arguments
- accum = self.make_ApplicationExpression(
- expression, self.process_next_expression(APP)
- )
- while self.inRange(0) and self.token(0) == Tokens.COMMA:
- self.token() # swallow the comma
- accum = self.make_ApplicationExpression(
- accum, self.process_next_expression(APP)
- )
- self.assertNextToken(Tokens.CLOSE)
- return accum
- return expression
- def make_ApplicationExpression(self, function, argument):
- return ApplicationExpression(function, argument)
- def make_VariableExpression(self, name):
- return VariableExpression(Variable(name))
- def make_LambdaExpression(self, variable, term):
- return LambdaExpression(variable, term)
- def has_priority(self, operation, context):
- return self.operator_precedence[operation] < self.operator_precedence[
- context
- ] or (
- operation in self.right_associated_operations
- and self.operator_precedence[operation] == self.operator_precedence[context]
- )
- def assertNextToken(self, expected):
- try:
- tok = self.token()
- except ExpectedMoreTokensException as e:
- raise ExpectedMoreTokensException(
- e.index, message="Expected token '%s'." % expected
- )
- if isinstance(expected, list):
- if tok not in expected:
- raise UnexpectedTokenException(self._currentIndex, tok, expected)
- else:
- if tok != expected:
- raise UnexpectedTokenException(self._currentIndex, tok, expected)
- def assertToken(self, tok, expected):
- if isinstance(expected, list):
- if tok not in expected:
- raise UnexpectedTokenException(self._currentIndex, tok, expected)
- else:
- if tok != expected:
- raise UnexpectedTokenException(self._currentIndex, tok, expected)
- def __repr__(self):
- if self.inRange(0):
- msg = 'Next token: ' + self.token(0)
- else:
- msg = 'No more tokens'
- return '<' + self.__class__.__name__ + ': ' + msg + '>'
- def read_logic(s, logic_parser=None, encoding=None):
- """
- Convert a file of First Order Formulas into a list of {Expression}s.
- :param s: the contents of the file
- :type s: str
- :param logic_parser: The parser to be used to parse the logical expression
- :type logic_parser: LogicParser
- :param encoding: the encoding of the input string, if it is binary
- :type encoding: str
- :return: a list of parsed formulas.
- :rtype: list(Expression)
- """
- if encoding is not None:
- s = s.decode(encoding)
- if logic_parser is None:
- logic_parser = LogicParser()
- statements = []
- for linenum, line in enumerate(s.splitlines()):
- line = line.strip()
- if line.startswith('#') or line == '':
- continue
- try:
- statements.append(logic_parser.parse(line))
- except LogicalExpressionException:
- raise ValueError('Unable to parse line %s: %s' % (linenum, line))
- return statements
- @total_ordering
- @python_2_unicode_compatible
- class Variable(object):
- def __init__(self, name):
- """
- :param name: the name of the variable
- """
- assert isinstance(name, string_types), "%s is not a string" % name
- self.name = name
- def __eq__(self, other):
- return isinstance(other, Variable) and self.name == other.name
- def __ne__(self, other):
- return not self == other
- def __lt__(self, other):
- if not isinstance(other, Variable):
- raise TypeError
- return self.name < other.name
- def substitute_bindings(self, bindings):
- return bindings.get(self, self)
- def __hash__(self):
- return hash(self.name)
- def __str__(self):
- return self.name
- def __repr__(self):
- return "Variable('%s')" % self.name
- def unique_variable(pattern=None, ignore=None):
- """
- Return a new, unique variable.
- :param pattern: ``Variable`` that is being replaced. The new variable must
- be the same type.
- :param term: a set of ``Variable`` objects that should not be returned from
- this function.
- :rtype: Variable
- """
- if pattern is not None:
- if is_indvar(pattern.name):
- prefix = 'z'
- elif is_funcvar(pattern.name):
- prefix = 'F'
- elif is_eventvar(pattern.name):
- prefix = 'e0'
- else:
- assert False, "Cannot generate a unique constant"
- else:
- prefix = 'z'
- v = Variable("%s%s" % (prefix, _counter.get()))
- while ignore is not None and v in ignore:
- v = Variable("%s%s" % (prefix, _counter.get()))
- return v
- def skolem_function(univ_scope=None):
- """
- Return a skolem function over the variables in univ_scope
- param univ_scope
- """
- skolem = VariableExpression(Variable('F%s' % _counter.get()))
- if univ_scope:
- for v in list(univ_scope):
- skolem = skolem(VariableExpression(v))
- return skolem
- @python_2_unicode_compatible
- class Type(object):
- def __repr__(self):
- return "%s" % self
- def __hash__(self):
- return hash("%s" % self)
- @classmethod
- def fromstring(cls, s):
- return read_type(s)
- @python_2_unicode_compatible
- class ComplexType(Type):
- def __init__(self, first, second):
- assert isinstance(first, Type), "%s is not a Type" % first
- assert isinstance(second, Type), "%s is not a Type" % second
- self.first = first
- self.second = second
- def __eq__(self, other):
- return (
- isinstance(other, ComplexType)
- and self.first == other.first
- and self.second == other.second
- )
- def __ne__(self, other):
- return not self == other
- __hash__ = Type.__hash__
- def matches(self, other):
- if isinstance(other, ComplexType):
- return self.first.matches(other.first) and self.second.matches(other.second)
- else:
- return self == ANY_TYPE
- def resolve(self, other):
- if other == ANY_TYPE:
- return self
- elif isinstance(other, ComplexType):
- f = self.first.resolve(other.first)
- s = self.second.resolve(other.second)
- if f and s:
- return ComplexType(f, s)
- else:
- return None
- elif self == ANY_TYPE:
- return other
- else:
- return None
- def __str__(self):
- if self == ANY_TYPE:
- return "%s" % ANY_TYPE
- else:
- return '<%s,%s>' % (self.first, self.second)
- def str(self):
- if self == ANY_TYPE:
- return ANY_TYPE.str()
- else:
- return '(%s -> %s)' % (self.first.str(), self.second.str())
- class BasicType(Type):
- def __eq__(self, other):
- return isinstance(other, BasicType) and ("%s" % self) == ("%s" % other)
- def __ne__(self, other):
- return not self == other
- __hash__ = Type.__hash__
- def matches(self, other):
- return other == ANY_TYPE or self == other
- def resolve(self, other):
- if self.matches(other):
- return self
- else:
- return None
- @python_2_unicode_compatible
- class EntityType(BasicType):
- def __str__(self):
- return 'e'
- def str(self):
- return 'IND'
- @python_2_unicode_compatible
- class TruthValueType(BasicType):
- def __str__(self):
- return 't'
- def str(self):
- return 'BOOL'
- @python_2_unicode_compatible
- class EventType(BasicType):
- def __str__(self):
- return 'v'
- def str(self):
- return 'EVENT'
- @python_2_unicode_compatible
- class AnyType(BasicType, ComplexType):
- def __init__(self):
- pass
- @property
- def first(self):
- return self
- @property
- def second(self):
- return self
- def __eq__(self, other):
- return isinstance(other, AnyType) or other.__eq__(self)
- def __ne__(self, other):
- return not self == other
- __hash__ = Type.__hash__
- def matches(self, other):
- return True
- def resolve(self, other):
- return other
- def __str__(self):
- return '?'
- def str(self):
- return 'ANY'
- TRUTH_TYPE = TruthValueType()
- ENTITY_TYPE = EntityType()
- EVENT_TYPE = EventType()
- ANY_TYPE = AnyType()
- def read_type(type_string):
- assert isinstance(type_string, string_types)
- type_string = type_string.replace(' ', '') # remove spaces
- if type_string[0] == '<':
- assert type_string[-1] == '>'
- paren_count = 0
- for i, char in enumerate(type_string):
- if char == '<':
- paren_count += 1
- elif char == '>':
- paren_count -= 1
- assert paren_count > 0
- elif char == ',':
- if paren_count == 1:
- break
- return ComplexType(
- read_type(type_string[1:i]), read_type(type_string[i + 1 : -1])
- )
- elif type_string[0] == "%s" % ENTITY_TYPE:
- return ENTITY_TYPE
- elif type_string[0] == "%s" % TRUTH_TYPE:
- return TRUTH_TYPE
- elif type_string[0] == "%s" % ANY_TYPE:
- return ANY_TYPE
- else:
- raise LogicalExpressionException(None, "Unexpected character: '%s'." % type_string[0])
- class TypeException(Exception):
- def __init__(self, msg):
- super(TypeException, self).__init__(msg)
- class InconsistentTypeHierarchyException(TypeException):
- def __init__(self, variable, expression=None):
- if expression:
- msg = (
- "The variable '%s' was found in multiple places with different"
- " types in '%s'." % (variable, expression)
- )
- else:
- msg = (
- "The variable '%s' was found in multiple places with different"
- " types." % (variable)
- )
- super(InconsistentTypeHierarchyException, self).__init__(msg)
- class TypeResolutionException(TypeException):
- def __init__(self, expression, other_type):
- super(TypeResolutionException, self).__init__(
- "The type of '%s', '%s', cannot be resolved with type '%s'"
- % (expression, expression.type, other_type)
- )
- class IllegalTypeException(TypeException):
- def __init__(self, expression, other_type, allowed_type):
- super(IllegalTypeException, self).__init__(
- "Cannot set type of %s '%s' to '%s'; must match type '%s'."
- % (expression.__class__.__name__, expression, other_type, allowed_type)
- )
- def typecheck(expressions, signature=None):
- """
- Ensure correct typing across a collection of ``Expression`` objects.
- :param expressions: a collection of expressions
- :param signature: dict that maps variable names to types (or string
- representations of types)
- """
- # typecheck and create master signature
- for expression in expressions:
- signature = expression.typecheck(signature)
- # apply master signature to all expressions
- for expression in expressions[:-1]:
- expression.typecheck(signature)
- return signature
- class SubstituteBindingsI(object):
- """
- An interface for classes that can perform substitutions for
- variables.
- """
- def substitute_bindings(self, bindings):
- """
- :return: The object that is obtained by replacing
- each variable bound by ``bindings`` with its values.
- Aliases are already resolved. (maybe?)
- :rtype: (any)
- """
- raise NotImplementedError()
- def variables(self):
- """
- :return: A list of all variables in this object.
- """
- raise NotImplementedError()
- @python_2_unicode_compatible
- class Expression(SubstituteBindingsI):
- """This is the base abstract object for all logical expressions"""
- _logic_parser = LogicParser()
- _type_checking_logic_parser = LogicParser(type_check=True)
- @classmethod
- def fromstring(cls, s, type_check=False, signature=None):
- if type_check:
- return cls._type_checking_logic_parser.parse(s, signature)
- else:
- return cls._logic_parser.parse(s, signature)
- def __call__(self, other, *additional):
- accum = self.applyto(other)
- for a in additional:
- accum = accum(a)
- return accum
- def applyto(self, other):
- assert isinstance(other, Expression), "%s is not an Expression" % other
- return ApplicationExpression(self, other)
- def __neg__(self):
- return NegatedExpression(self)
- def negate(self):
- """If this is a negated expression, remove the negation.
- Otherwise add a negation."""
- return -self
- def __and__(self, other):
- if not isinstance(other, Expression):
- raise TypeError("%s is not an Expression" % other)
- return AndExpression(self, other)
- def __or__(self, other):
- if not isinstance(other, Expression):
- raise TypeError("%s is not an Expression" % other)
- return OrExpression(self, other)
- def __gt__(self, other):
- if not isinstance(other, Expression):
- raise TypeError("%s is not an Expression" % other)
- return ImpExpression(self, other)
- def __lt__(self, other):
- if not isinstance(other, Expression):
- raise TypeError("%s is not an Expression" % other)
- return IffExpression(self, other)
- def __eq__(self, other):
- raise NotImplementedError()
- def __ne__(self, other):
- return not self == other
- def equiv(self, other, prover=None):
- """
- Check for logical equivalence.
- Pass the expression (self <-> other) to the theorem prover.
- If the prover says it is valid, then the self and other are equal.
- :param other: an ``Expression`` to check equality against
- :param prover: a ``nltk.inference.api.Prover``
- """
- assert isinstance(other, Expression), "%s is not an Expression" % other
- if prover is None:
- from nltk.inference import Prover9
- prover = Prover9()
- bicond = IffExpression(self.simplify(), other.simplify())
- return prover.prove(bicond)
- def __hash__(self):
- return hash(repr(self))
- def substitute_bindings(self, bindings):
- expr = self
- for var in expr.variables():
- if var in bindings:
- val = bindings[var]
- if isinstance(val, Variable):
- val = self.make_VariableExpression(val)
- elif not isinstance(val, Expression):
- raise ValueError(
- 'Can not substitute a non-expression '
- 'value into an expression: %r' % (val,)
- )
- # Substitute bindings in the target value.
- val = val.substitute_bindings(bindings)
- # Replace var w/ the target value.
- expr = expr.replace(var, val)
- return expr.simplify()
- def typecheck(self, signature=None):
- """
- Infer and check types. Raise exceptions if necessary.
- :param signature: dict that maps variable names to types (or string
- representations of types)
- :return: the signature, plus any additional type mappings
- """
- sig = defaultdict(list)
- if signature:
- for key in signature:
- val = signature[key]
- varEx = VariableExpression(Variable(key))
- if isinstance(val, Type):
- varEx.type = val
- else:
- varEx.type = read_type(val)
- sig[key].append(varEx)
- self._set_type(signature=sig)
- return dict((key, sig[key][0].type) for key in sig)
- def findtype(self, variable):
- """
- Find the type of the given variable as it is used in this expression.
- For example, finding the type of "P" in "P(x) & Q(x,y)" yields "<e,t>"
- :param variable: Variable
- """
- raise NotImplementedError()
- def _set_type(self, other_type=ANY_TYPE, signature=None):
- """
- Set the type of this expression to be the given type. Raise type
- exceptions where applicable.
- :param other_type: Type
- :param signature: dict(str -> list(AbstractVariableExpression))
- """
- raise NotImplementedError()
- def replace(self, variable, expression, replace_bound=False, alpha_convert=True):
- """
- Replace every instance of 'variable' with 'expression'
- :param variable: ``Variable`` The variable to replace
- :param expression: ``Expression`` The expression with which to replace it
- :param replace_bound: bool Should bound variables be replaced?
- :param alpha_convert: bool Alpha convert automatically to avoid name clashes?
- """
- assert isinstance(variable, Variable), "%s is not a Variable" % variable
- assert isinstance(expression, Expression), (
- "%s is not an Expression" % expression
- )
- return self.visit_structured(
- lambda e: e.replace(variable, expression, replace_bound, alpha_convert),
- self.__class__,
- )
- def normalize(self, newvars=None):
- """Rename auto-generated unique variables"""
- def get_indiv_vars(e):
- if isinstance(e, IndividualVariableExpression):
- return set([e])
- elif isinstance(e, AbstractVariableExpression):
- return set()
- else:
- return e.visit(
- get_indiv_vars, lambda parts: reduce(operator.or_, parts, set())
- )
- result = self
- for i, e in enumerate(sorted(get_indiv_vars(self), key=lambda e: e.variable)):
- if isinstance(e, EventVariableExpression):
- newVar = e.__class__(Variable('e0%s' % (i + 1)))
- elif isinstance(e, IndividualVariableExpression):
- newVar = e.__class__(Variable('z%s' % (i + 1)))
- else:
- newVar = e
- result = result.replace(e.variable, newVar, True)
- return result
- def visit(self, function, combinator):
- """
- Recursively visit subexpressions. Apply 'function' to each
- subexpression and pass the result of each function application
- to the 'combinator' for aggregation:
- return combinator(map(function, self.subexpressions))
- Bound variables are neither applied upon by the function nor given to
- the combinator.
- :param function: ``Function<Expression,T>`` to call on each subexpression
- :param combinator: ``Function<list<T>,R>`` to combine the results of the
- function calls
- :return: result of combination ``R``
- """
- raise NotImplementedError()
- def visit_structured(self, function, combinator):
- """
- Recursively visit subexpressions. Apply 'function' to each
- subexpression and pass the result of each function application
- to the 'combinator' for aggregation. The combinator must have
- the same signature as the constructor. The function is not
- applied to bound variables, but they are passed to the
- combinator.
- :param function: ``Function`` to call on each subexpression
- :param combinator: ``Function`` with the same signature as the
- constructor, to combine the results of the function calls
- :return: result of combination
- """
- return self.visit(function, lambda parts: combinator(*parts))
- def __repr__(self):
- return '<%s %s>' % (self.__class__.__name__, self)
- def __str__(self):
- return self.str()
- def variables(self):
- """
- Return a set of all the variables for binding substitution.
- The variables returned include all free (non-bound) individual
- variables and any variable starting with '?' or '@'.
- :return: set of ``Variable`` objects
- """
- return self.free() | set(
- p for p in self.predicates() | self.constants() if re.match('^[?@]', p.name)
- )
- def free(self):
- """
- Return a set of all the free (non-bound) variables. This includes
- both individual and predicate variables, but not constants.
- :return: set of ``Variable`` objects
- """
- return self.visit(
- lambda e: e.free(), lambda parts: reduce(operator.or_, parts, set())
- )
- def constants(self):
- """
- Return a set of individual constants (non-predicates).
- :return: set of ``Variable`` objects
- """
- return self.visit(
- lambda e: e.constants(), lambda parts: reduce(operator.or_, parts, set())
- )
- def predicates(self):
- """
- Return a set of predicates (constants, not variables).
- :return: set of ``Variable`` objects
- """
- return self.visit(
- lambda e: e.predicates(), lambda parts: reduce(operator.or_, parts, set())
- )
- def simplify(self):
- """
- :return: beta-converted version of this expression
- """
- return self.visit_structured(lambda e: e.simplify(), self.__class__)
- def make_VariableExpression(self, variable):
- return VariableExpression(variable)
- @python_2_unicode_compatible
- class ApplicationExpression(Expression):
- r"""
- This class is used to represent two related types of logical expressions.
- The first is a Predicate Expression, such as "P(x,y)". A predicate
- expression is comprised of a ``FunctionVariableExpression`` or
- ``ConstantExpression`` as the predicate and a list of Expressions as the
- arguments.
- The second is a an application of one expression to another, such as
- "(\x.dog(x))(fido)".
- The reason Predicate Expressions are treated as Application Expressions is
- that the Variable Expression predicate of the expression may be replaced
- with another Expression, such as a LambdaExpression, which would mean that
- the Predicate should be thought of as being applied to the arguments.
- The logical expression reader will always curry arguments in a application expression.
- So, "\x y.see(x,y)(john,mary)" will be represented internally as
- "((\x y.(see(x))(y))(john))(mary)". This simplifies the internals since
- there will always be exactly one argument in an application.
- The str() method will usually print the curried forms of application
- expressions. The one exception is when the the application expression is
- really a predicate expression (ie, underlying function is an
- ``AbstractVariableExpression``). This means that the example from above
- will be returned as "(\x y.see(x,y)(john))(mary)".
- """
- def __init__(self, function, argument):
- """
- :param function: ``Expression``, for the function expression
- :param argument: ``Expression``, for the argument
- """
- assert isinstance(function, Expression), "%s is not an Expression" % function
- assert isinstance(argument, Expression), "%s is not an Expression" % argument
- self.function = function
- self.argument = argument
- def simplify(self):
- function = self.function.simplify()
- argument = self.argument.simplify()
- if isinstance(function, LambdaExpression):
- return function.term.replace(function.variable, argument).simplify()
- else:
- return self.__class__(function, argument)
- @property
- def type(self):
- if isinstance(self.function.type, ComplexType):
- return self.function.type.second
- else:
- return ANY_TYPE
- def _set_type(self, other_type=ANY_TYPE, signature=None):
- """:see Expression._set_type()"""
- assert isinstance(other_type, Type)
- if signature is None:
- signature = defaultdict(list)
- self.argument._set_type(ANY_TYPE, signature)
- try:
- self.function._set_type(
- ComplexType(self.argument.type, other_type), signature
- )
- except TypeResolutionException:
- raise TypeException(
- "The function '%s' is of type '%s' and cannot be applied "
- "to '%s' of type '%s'. Its argument must match type '%s'."
- % (
- self.function,
- self.function.type,
- self.argument,
- self.argument.type,
- self.function.type.first,
- )
- )
- def findtype(self, variable):
- """:see Expression.findtype()"""
- assert isinstance(variable, Variable), "%s is not a Variable" % variable
- if self.is_atom():
- function, args = self.uncurry()
- else:
- # It's not a predicate expression ("P(x,y)"), so leave args curried
- function = self.function
- args = [self.argument]
- found = [arg.findtype(variable) for arg in [function] + args]
- unique = []
- for f in found:
- if f != ANY_TYPE:
- if unique:
- for u in unique:
- if f.matches(u):
- break
- else:
- unique.append(f)
- if len(unique) == 1:
- return list(unique)[0]
- else:
- return ANY_TYPE
- def constants(self):
- """:see: Expression.constants()"""
- if isinstance(self.function, AbstractVariableExpression):
- function_constants = set()
- else:
- function_constants = self.function.constants()
- return function_constants | self.argument.constants()
- def predicates(self):
- """:see: Expression.predicates()"""
- if isinstance(self.function, ConstantExpression):
- function_preds = set([self.function.variable])
- else:
- function_preds = self.function.predicates()
- return function_preds | self.argument.predicates()
- def visit(self, function, combinator):
- """:see: Expression.visit()"""
- return combinator([function(self.function), function(self.argument)])
- def __eq__(self, other):
- return (
- isinstance(other, ApplicationExpression)
- and self.function == other.function
- and self.argument == other.argument
- )
- def __ne__(self, other):
- return not self == other
- __hash__ = Expression.__hash__
- def __str__(self):
- # uncurry the arguments and find the base function
- if self.is_atom():
- function, args = self.uncurry()
- arg_str = ','.join("%s" % arg for arg in args)
- else:
- # Leave arguments curried
- function = self.function
- arg_str = "%s" % self.argument
- function_str = "%s" % function
- parenthesize_function = False
- if isinstance(function, LambdaExpression):
- if isinstance(function.term, ApplicationExpression):
- if not isinstance(function.term.function, AbstractVariableExpression):
- parenthesize_function = True
- elif not isinstance(function.term, BooleanExpression):
- parenthesize_function = True
- elif isinstance(function, ApplicationExpression):
- parenthesize_function = True
- if parenthesize_function:
- function_str = Tokens.OPEN + function_str + Tokens.CLOSE
- return function_str + Tokens.OPEN + arg_str + Tokens.CLOSE
- def uncurry(self):
- """
- Uncurry this application expression
- return: A tuple (base-function, arg-list)
- """
- function = self.function
- args = [self.argument]
- while isinstance(function, ApplicationExpression):
- # (\x.\y.sees(x,y)(john))(mary)
- args.insert(0, function.argument)
- function = function.function
- return (function, args)
- @property
- def pred(self):
- """
- Return uncurried base-function.
- If this is an atom, then the result will be a variable expression.
- Otherwise, it will be a lambda expression.
- """
- return self.uncurry()[0]
- @property
- def args(self):
- """
- Return uncurried arg-list
- """
- return self.uncurry()[1]
- def is_atom(self):
- """
- Is this expression an atom (as opposed to a lambda expression applied
- to a term)?
- """
- return isinstance(self.pred, AbstractVariableExpression)
- @total_ordering
- @python_2_unicode_compatible
- class AbstractVariableExpression(Expression):
- """This class represents a variable to be used as a predicate or entity"""
- def __init__(self, variable):
- """
- :param variable: ``Variable``, for the variable
- """
- assert isinstance(variable, Variable), "%s is not a Variable" % variable
- self.variable = variable
- def simplify(self):
- return self
- def replace(self, variable, expression, replace_bound=False, alpha_convert=True):
- """:see: Expression.replace()"""
- assert isinstance(variable, Variable), "%s is not an Variable" % variable
- assert isinstance(expression, Expression), (
- "%s is not an Expression" % expression
- )
- if self.variable == variable:
- return expression
- else:
- return self
- def _set_type(self, other_type=ANY_TYPE, signature=None):
- """:see Expression._set_type()"""
- assert isinstance(other_type, Type)
- if signature is None:
- signature = defaultdict(list)
- resolution = other_type
- for varEx in signature[self.variable.name]:
- resolution = varEx.type.resolve(resolution)
- if not resolution:
- raise InconsistentTypeHierarchyException(self)
- signature[self.variable.name].append(self)
- for varEx in signature[self.variable.name]:
- varEx.type = resolution
- def findtype(self, variable):
- """:see Expression.findtype()"""
- assert isinstance(variable, Variable), "%s is not a Variable" % variable
- if self.variable == variable:
- return self.type
- else:
- return ANY_TYPE
- def predicates(self):
- """:see: Expression.predicates()"""
- return set()
- def __eq__(self, other):
- """Allow equality between instances of ``AbstractVariableExpression``
- subtypes."""
- return (
- isinstance(other, AbstractVariableExpression)
- and self.variable == other.variable
- )
- def __ne__(self, other):
- return not self == other
- def __lt__(self, other):
- if not isinstance(other, AbstractVariableExpression):
- raise TypeError
- return self.variable < other.variable
- __hash__ = Expression.__hash__
- def __str__(self):
- return "%s" % self.variable
- class IndividualVariableExpression(AbstractVariableExpression):
- """This class represents variables that take the form of a single lowercase
- character (other than 'e') followed by zero or more digits."""
- def _set_type(self, other_type=ANY_TYPE, signature=None):
- """:see Expression._set_type()"""
- assert isinstance(other_type, Type)
- if signature is None:
- signature = defaultdict(list)
- if not other_type.matches(ENTITY_TYPE):
- raise IllegalTypeException(self, other_type, ENTITY_TYPE)
- signature[self.variable.name].append(self)
- def _get_type(self):
- return ENTITY_TYPE
- type = property(_get_type, _set_type)
- def free(self):
- """:see: Expression.free()"""
- return set([self.variable])
- def constants(self):
- """:see: Expression.constants()"""
- return set()
- class FunctionVariableExpression(AbstractVariableExpression):
- """This class represents variables that take the form of a single uppercase
- character followed by zero or more digits."""
- type = ANY_TYPE
- def free(self):
- """:see: Expression.free()"""
- return set([self.variable])
- def constants(self):
- """:see: Expression.constants()"""
- return set()
- class EventVariableExpression(IndividualVariableExpression):
- """This class represents variables that take the form of a single lowercase
- 'e' character followed by zero or more digits."""
- type = EVENT_TYPE
- class ConstantExpression(AbstractVariableExpression):
- """This class represents variables that do not take the form of a single
- character followed by zero or more digits."""
- type = ENTITY_TYPE
- def _set_type(self, other_type=ANY_TYPE, signature=None):
- """:see Expression._set_type()"""
- assert isinstance(other_type, Type)
- if signature is None:
- signature = defaultdict(list)
- if other_type == ANY_TYPE:
- # entity type by default, for individuals
- resolution = ENTITY_TYPE
- else:
- resolution = other_type
- if self.type != ENTITY_TYPE:
- resolution = resolution.resolve(self.type)
- for varEx in signature[self.variable.name]:
- resolution = varEx.type.resolve(resolution)
- if not resolution:
- raise InconsistentTypeHierarchyException(self)
- signature[self.variable.name].append(self)
- for varEx in signature[self.variable.name]:
- varEx.type = resolution
- def free(self):
- """:see: Expression.free()"""
- return set()
- def constants(self):
- """:see: Expression.constants()"""
- return set([self.variable])
- def VariableExpression(variable):
- """
- This is a factory method that instantiates and returns a subtype of
- ``AbstractVariableExpression`` appropriate for the given variable.
- """
- assert isinstance(variable, Variable), "%s is not a Variable" % variable
- if is_indvar(variable.name):
- return IndividualVariableExpression(variable)
- elif is_funcvar(variable.name):
- return FunctionVariableExpression(variable)
- elif is_eventvar(variable.name):
- return EventVariableExpression(variable)
- else:
- return ConstantExpression(variable)
- class VariableBinderExpression(Expression):
- """This an abstract class for any Expression that binds a variable in an
- Expression. This includes LambdaExpressions and Quantified Expressions"""
- def __init__(self, variable, term):
- """
- :param variable: ``Variable``, for the variable
- :param term: ``Expression``, for the term
- """
- assert isinstance(variable, Variable), "%s is not a Variable" % variable
- assert isinstance(term, Expression), "%s is not an Expression" % term
- self.variable = variable
- self.term = term
- def replace(self, variable, expression, replace_bound=False, alpha_convert=True):
- """:see: Expression.replace()"""
- assert isinstance(variable, Variable), "%s is not a Variable" % variable
- assert isinstance(expression, Expression), (
- "%s is not an Expression" % expression
- )
- # if the bound variable is the thing being replaced
- if self.variable == variable:
- if replace_bound:
- assert isinstance(expression, AbstractVariableExpression), (
- "%s is not a AbstractVariableExpression" % expression
- )
- return self.__class__(
- expression.variable,
- self.term.replace(variable, expression, True, alpha_convert),
- )
- else:
- return self
- else:
- # if the bound variable appears in the expression, then it must
- # be alpha converted to avoid a conflict
- if alpha_convert and self.variable in expression.free():
- self = self.alpha_convert(unique_variable(pattern=self.variable))
- # replace in the term
- return self.__class__(
- self.variable,
- self.term.replace(variable, expression, replace_bound, alpha_convert),
- )
- def alpha_convert(self, newvar):
- """Rename all occurrences of the variable introduced by this variable
- binder in the expression to ``newvar``.
- :param newvar: ``Variable``, for the new variable
- """
- assert isinstance(newvar, Variable), "%s is not a Variable" % newvar
- return self.__class__(
- newvar, self.term.replace(self.variable, VariableExpression(newvar), True)
- )
- def free(self):
- """:see: Expression.free()"""
- return self.term.free() - set([self.variable])
- def findtype(self, variable):
- """:see Expression.findtype()"""
- assert isinstance(variable, Variable), "%s is not a Variable" % variable
- if variable == self.variable:
- return ANY_TYPE
- else:
- return self.term.findtype(variable)
- def visit(self, function, combinator):
- """:see: Expression.visit()"""
- return combinator([function(self.term)])
- def visit_structured(self, function, combinator):
- """:see: Expression.visit_structured()"""
- return combinator(self.variable, function(self.term))
- def __eq__(self, other):
- r"""Defines equality modulo alphabetic variance. If we are comparing
- \x.M and \y.N, then check equality of M and N[x/y]."""
- if isinstance(self, other.__class__) or isinstance(other, self.__class__):
- if self.variable == other.variable:
- return self.term == other.term
- else:
- # Comparing \x.M and \y.N. Relabel y in N with x and continue.
- varex = VariableExpression(self.variable)
- return self.term == other.term.replace(other.variable, varex)
- else:
- return False
- def __ne__(self, other):
- return not self == other
- __hash__ = Expression.__hash__
- @python_2_unicode_compatible
- class LambdaExpression(VariableBinderExpression):
- @property
- def type(self):
- return ComplexType(self.term.findtype(self.variable), self.term.type)
- def _set_type(self, other_type=ANY_TYPE, signature=None):
- """:see Expression._set_type()"""
- assert isinstance(other_type, Type)
- if signature is None:
- signature = defaultdict(list)
- self.term._set_type(other_type.second, signature)
- if not self.type.resolve(other_type):
- raise TypeResolutionException(self, other_type)
- def __str__(self):
- variables = [self.variable]
- term = self.term
- while term.__class__ == self.__class__:
- variables.append(term.variable)
- term = term.term
- return (
- Tokens.LAMBDA
- + ' '.join("%s" % v for v in variables)
- + Tokens.DOT
- + "%s" % term
- )
- @python_2_unicode_compatible
- class QuantifiedExpression(VariableBinderExpression):
- @property
- def type(self):
- return TRUTH_TYPE
- def _set_type(self, other_type=ANY_TYPE, signature=None):
- """:see Expression._set_type()"""
- assert isinstance(other_type, Type)
- if signature is None:
- signature = defaultdict(list)
- if not other_type.matches(TRUTH_TYPE):
- raise IllegalTypeException(self, other_type, TRUTH_TYPE)
- self.term._set_type(TRUTH_TYPE, signature)
- def __str__(self):
- variables = [self.variable]
- term = self.term
- while term.__class__ == self.__class__:
- variables.append(term.variable)
- term = term.term
- return (
- self.getQuantifier()
- + ' '
- + ' '.join("%s" % v for v in variables)
- + Tokens.DOT
- + "%s" % term
- )
- class ExistsExpression(QuantifiedExpression):
- def getQuantifier(self):
- return Tokens.EXISTS
- class AllExpression(QuantifiedExpression):
- def getQuantifier(self):
- return Tokens.ALL
- @python_2_unicode_compatible
- class NegatedExpression(Expression):
- def __init__(self, term):
- assert isinstance(term, Expression), "%s is not an Expression" % term
- self.term = term
- @property
- def type(self):
- return TRUTH_TYPE
- def _set_type(self, other_type=ANY_TYPE, signature=None):
- """:see Expression._set_type()"""
- assert isinstance(other_type, Type)
- if signature is None:
- signature = defaultdict(list)
- if not other_type.matches(TRUTH_TYPE):
- raise IllegalTypeException(self, other_type, TRUTH_TYPE)
- self.term._set_type(TRUTH_TYPE, signature)
- def findtype(self, variable):
- assert isinstance(variable, Variable), "%s is not a Variable" % variable
- return self.term.findtype(variable)
- def visit(self, function, combinator):
- """:see: Expression.visit()"""
- return combinator([function(self.term)])
- def negate(self):
- """:see: Expression.negate()"""
- return self.term
- def __eq__(self, other):
- return isinstance(other, NegatedExpression) and self.term == other.term
- def __ne__(self, other):
- return not self == other
- __hash__ = Expression.__hash__
- def __str__(self):
- return Tokens.NOT + "%s" % self.term
- @python_2_unicode_compatible
- class BinaryExpression(Expression):
- def __init__(self, first, second):
- assert isinstance(first, Expression), "%s is not an Expression" % first
- assert isinstance(second, Expression), "%s is not an Expression" % second
- self.first = first
- self.second = second
- @property
- def type(self):
- return TRUTH_TYPE
- def findtype(self, variable):
- """:see Expression.findtype()"""
- assert isinstance(variable, Variable), "%s is not a Variable" % variable
- f = self.first.findtype(variable)
- s = self.second.findtype(variable)
- if f == s or s == ANY_TYPE:
- return f
- elif f == ANY_TYPE:
- return s
- else:
- return ANY_TYPE
- def visit(self, function, combinator):
- """:see: Expression.visit()"""
- return combinator([function(self.first), function(self.second)])
- def __eq__(self, other):
- return (
- (isinstance(self, other.__class__) or isinstance(other, self.__class__))
- and self.first == other.first
- and self.second == other.second
- )
- def __ne__(self, other):
- return not self == other
- __hash__ = Expression.__hash__
- def __str__(self):
- first = self._str_subex(self.first)
- second = self._str_subex(self.second)
- return Tokens.OPEN + first + ' ' + self.getOp() + ' ' + second + Tokens.CLOSE
- def _str_subex(self, subex):
- return "%s" % subex
- class BooleanExpression(BinaryExpression):
- def _set_type(self, other_type=ANY_TYPE, signature=None):
- """:see Expression._set_type()"""
- assert isinstance(other_type, Type)
- if signature is None:
- signature = defaultdict(list)
- if not other_type.matches(TRUTH_TYPE):
- raise IllegalTypeException(self, other_type, TRUTH_TYPE)
- self.first._set_type(TRUTH_TYPE, signature)
- self.second._set_type(TRUTH_TYPE, signature)
- class AndExpression(BooleanExpression):
- """This class represents conjunctions"""
- def getOp(self):
- return Tokens.AND
- def _str_subex(self, subex):
- s = "%s" % subex
- if isinstance(subex, AndExpression):
- return s[1:-1]
- return s
- class OrExpression(BooleanExpression):
- """This class represents disjunctions"""
- def getOp(self):
- return Tokens.OR
- def _str_subex(self, subex):
- s = "%s" % subex
- if isinstance(subex, OrExpression):
- return s[1:-1]
- return s
- class ImpExpression(BooleanExpression):
- """This class represents implications"""
- def getOp(self):
- return Tokens.IMP
- class IffExpression(BooleanExpression):
- """This class represents biconditionals"""
- def getOp(self):
- return Tokens.IFF
- class EqualityExpression(BinaryExpression):
- """This class represents equality expressions like "(x = y)"."""
- def _set_type(self, other_type=ANY_TYPE, signature=None):
- """:see Expression._set_type()"""
- assert isinstance(other_type, Type)
- if signature is None:
- signature = defaultdict(list)
- if not other_type.matches(TRUTH_TYPE):
- raise IllegalTypeException(self, other_type, TRUTH_TYPE)
- self.first._set_type(ENTITY_TYPE, signature)
- self.second._set_type(ENTITY_TYPE, signature)
- def getOp(self):
- return Tokens.EQ
- ### Utilities
- class LogicalExpressionException(Exception):
- def __init__(self, index, message):
- self.index = index
- Exception.__init__(self, message)
- class UnexpectedTokenException(LogicalExpressionException):
- def __init__(self, index, unexpected=None, expected=None, message=None):
- if unexpected and expected:
- msg = "Unexpected token: '%s'. " "Expected token '%s'." % (
- unexpected,
- expected,
- )
- elif unexpected:
- msg = "Unexpected token: '%s'." % unexpected
- if message:
- msg += ' ' + message
- else:
- msg = "Expected token '%s'." % expected
- LogicalExpressionException.__init__(self, index, msg)
- class ExpectedMoreTokensException(LogicalExpressionException):
- def __init__(self, index, message=None):
- if not message:
- message = 'More tokens expected.'
- LogicalExpressionException.__init__(
- self, index, 'End of input found. ' + message
- )
- def is_indvar(expr):
- """
- An individual variable must be a single lowercase character other than 'e',
- followed by zero or more digits.
- :param expr: str
- :return: bool True if expr is of the correct form
- """
- assert isinstance(expr, string_types), "%s is not a string" % expr
- return re.match(r'^[a-df-z]\d*$', expr) is not None
- def is_funcvar(expr):
- """
- A function variable must be a single uppercase character followed by
- zero or more digits.
- :param expr: str
- :return: bool True if expr is of the correct form
- """
- assert isinstance(expr, string_types), "%s is not a string" % expr
- return re.match(r'^[A-Z]\d*$', expr) is not None
- def is_eventvar(expr):
- """
- An event variable must be a single lowercase 'e' character followed by
- zero or more digits.
- :param expr: str
- :return: bool True if expr is of the correct form
- """
- assert isinstance(expr, string_types), "%s is not a string" % expr
- return re.match(r'^e\d*$', expr) is not None
- def demo():
- lexpr = Expression.fromstring
- print('=' * 20 + 'Test reader' + '=' * 20)
- print(lexpr(r'john'))
- print(lexpr(r'man(x)'))
- print(lexpr(r'-man(x)'))
- print(lexpr(r'(man(x) & tall(x) & walks(x))'))
- print(lexpr(r'exists x.(man(x) & tall(x) & walks(x))'))
- print(lexpr(r'\x.man(x)'))
- print(lexpr(r'\x.man(x)(john)'))
- print(lexpr(r'\x y.sees(x,y)'))
- print(lexpr(r'\x y.sees(x,y)(a,b)'))
- print(lexpr(r'(\x.exists y.walks(x,y))(x)'))
- print(lexpr(r'exists x.x = y'))
- print(lexpr(r'exists x.(x = y)'))
- print(lexpr('P(x) & x=y & P(y)'))
- print(lexpr(r'\P Q.exists x.(P(x) & Q(x))'))
- print(lexpr(r'man(x) <-> tall(x)'))
- print('=' * 20 + 'Test simplify' + '=' * 20)
- print(lexpr(r'\x.\y.sees(x,y)(john)(mary)').simplify())
- print(lexpr(r'\x.\y.sees(x,y)(john, mary)').simplify())
- print(lexpr(r'all x.(man(x) & (\x.exists y.walks(x,y))(x))').simplify())
- print(lexpr(r'(\P.\Q.exists x.(P(x) & Q(x)))(\x.dog(x))(\x.bark(x))').simplify())
- print('=' * 20 + 'Test alpha conversion and binder expression equality' + '=' * 20)
- e1 = lexpr('exists x.P(x)')
- print(e1)
- e2 = e1.alpha_convert(Variable('z'))
- print(e2)
- print(e1 == e2)
- def demo_errors():
- print('=' * 20 + 'Test reader errors' + '=' * 20)
- demoException('(P(x) & Q(x)')
- demoException('((P(x) &) & Q(x))')
- demoException('P(x) -> ')
- demoException('P(x')
- demoException('P(x,')
- demoException('P(x,)')
- demoException('exists')
- demoException('exists x.')
- demoException('\\')
- demoException('\\ x y.')
- demoException('P(x)Q(x)')
- demoException('(P(x)Q(x)')
- demoException('exists x -> y')
- def demoException(s):
- try:
- Expression.fromstring(s)
- except LogicalExpressionException as e:
- print("%s: %s" % (e.__class__.__name__, e))
- def printtype(ex):
- print("%s : %s" % (ex.str(), ex.type))
- if __name__ == '__main__':
- demo()
- # demo_errors()
|