Package jazzparser :: Package formalisms :: Package base :: Module rules
[hide private]
[frames] | no frames]

Source Code for Module jazzparser.formalisms.base.rules

  1  """Base classes for grammatical rules. 
  2   
  3  Base formalism rules: this provides the base classes for formalisms  
  4  to define their rules with. All behaviour in here should be core CCG 
  5  rule behaviour common to all CCG formalisms. 
  6  Subclasses these rules in specific formalisms. 
  7   
  8  """ 
  9  """ 
 10  ============================== License ======================================== 
 11   Copyright (C) 2008, 2010-12 University of Edinburgh, Mark Granroth-Wilding 
 12    
 13   This file is part of The Jazz Parser. 
 14    
 15   The Jazz Parser is free software: you can redistribute it and/or modify 
 16   it under the terms of the GNU General Public License as published by 
 17   the Free Software Foundation, either version 3 of the License, or 
 18   (at your option) any later version. 
 19    
 20   The Jazz Parser is distributed in the hope that it will be useful, 
 21   but WITHOUT ANY WARRANTY; without even the implied warranty of 
 22   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 23   GNU General Public License for more details. 
 24    
 25   You should have received a copy of the GNU General Public License 
 26   along with The Jazz Parser.  If not, see <http://www.gnu.org/licenses/>. 
 27   
 28  ============================ End license ====================================== 
 29   
 30  """ 
 31  __author__ = "Mark Granroth-Wilding <mark.granroth-wilding@ed.ac.uk>"  
 32   
 33  import logging 
 34  import copy 
 35   
 36  from jazzparser import settings 
 37   
 38  # Get the logger from the logging system 
 39  logger = logging.getLogger("main_logger") 
 40   
41 -class Rule(object):
42 readable_rule = "Undefined" 43
44 - def __init__(self, formalism, *args, **kwargs):
45 from jazzparser.grammar import GrammarReadError 46 modalities = kwargs.pop('modalities', None) 47 grammar = kwargs.pop('grammar', None) 48 49 if formalism is None: 50 raise GrammarReadError, "%s rule object was instantiated without a formalism argument." % type(self).__name__ 51 self.formalism = formalism 52 if grammar is None: 53 logger.warn("%s rule object was instantiated without a pointer to the grammar." % type(self).__name__) 54 self.grammar = grammar 55 if modalities is None: 56 if self.grammar is None or self.grammar.modality_tree is None: 57 raise GrammarReadError, "%s rule object was instantiated without a modality hierarchy and the grammar doesn't contain one" % type(self).__name__ 58 self.modalities = self.grammar.modality_tree 59 else: 60 self.modalities = modalities
61
62 - def apply_rule(self, cat_list):
63 """ 64 *** This should be overridden by subclasses. *** 65 Applies the rule to combine the categories in cat_list. 66 67 Note that the returned semantics should always be in beta-normal form. 68 69 @return: a list of the possible categories resulting from the 70 application if the rule is valid for the given arguments, 71 otherwise None. 72 73 """ 74 raise NotImplementedError, "Called abstract Rule.apply_rule()"
75
76 - def __str__(self):
77 # This should be set for each implementation 78 return "%s" % (self.readable_rule)
79
80 - def apply_rule_semantics(self, cat_list):
81 """ 82 Performs the semantic processing involved in applying the rule to these 83 arguments. 84 85 This doesn't do any checks on the syntactic type. If it's not used 86 in a situation where you know that the syntactic part of the application 87 will work, it could produce a non-sensical semantics or even raise 88 errors. It's designed for speeding up applying a rule to many signs 89 known to have the same syntactic type (so that the syntactic checks 90 only need to be done once). 91 92 Depending on the formalism, this may not be any faster than calling 93 L{apply_rule} and getting the semantics from the results. In fact, 94 this is the default behaviour. Any sensible formalism will provide 95 a faster implementation of this method, though. 96 97 @return: list of the Semantics objects that would be the logical 98 form parts of the results of the rule application. 99 100 """ 101 return [res.semantics for res in self.apply_rule(cat_list)]
102
103 -class ApplicationRuleBase(Rule):
104 """ 105 Rule for standard CCG application. 106 """
107 - def __init__(self, *args, **kwargs):
108 """ 109 An application rule. May be forward or backward, depending on 110 the direction given in XML element. 111 112 """ 113 self.forward = (kwargs.get('dir', "forward") == "forward") 114 if self.forward: 115 self.name = ">" 116 self.internal_name = "appf" 117 else: 118 self.name = "<" 119 self.internal_name = "appb" 120 self.arity = 2 121 if self.forward: 122 self.readable_rule = "X/Y Y => X" 123 else: 124 self.readable_rule = "Y X\\Y => X" 125 126 super(ApplicationRuleBase, self).__init__(*args, **kwargs)
127
128 - def apply_rule(self, cat_list, conditions=None, proc_sems=None):
129 """ 130 conditions should be a callable that takes the functor followed 131 by the argument and returns a boolean. It will be run after 132 the basic applicability tests to check whether the rule should 133 be applied. 134 If a function is given in proc_sems, it will be called to 135 process the semantics of the inputs before the resultant 136 semantics is built. 137 """ 138 ################## Check the rule is valid 139 # Must be applied to 2 args 140 if len(cat_list) != 2: 141 return None 142 143 # Functor arg must be a slash category in the right direction 144 if self.forward: 145 # X/Y Y => X 146 functor = cat_list[0] 147 argument = cat_list[1] 148 else: 149 # Y X\Y => X 150 functor = cat_list[1] 151 argument = cat_list[0] 152 153 # Check correct cat type 154 if not self.formalism.Syntax.is_complex_category(functor.category): 155 return None 156 157 # Check right slash direction 158 if functor.category.slash.forward != self.forward: 159 return None 160 161 if conditions is not None: 162 if not conditions(functor, argument): 163 return None 164 165 functor_cat = functor.category.copy() 166 argument_cat = argument.category.copy() 167 self.formalism.distinguish_categories(functor_cat, argument_cat) 168 169 # Here's the crucial check: that the Y in the functor X/\Y is the same 170 # as the argument 171 unification_result = self.formalism.unify(functor_cat.argument, argument_cat, grammar=self.grammar) 172 if unification_result is None: 173 # Couldn't unify 174 return None 175 176 ################## Checks passed: apply the rule 177 # Just return category X 178 # Apply the unification substitutions 179 # No need to apply the mappings: they were only applicable to the argument 180 # Now ready to make the variable substitutions 181 category = unification_result.constraints.apply(functor_cat.result) 182 183 # Combine semantics by function application 184 new_functor = functor.semantics.copy() 185 new_argument = argument.semantics.copy() 186 # Run any additional processing on the semantics of the inputs 187 if proc_sems is not None: 188 proc_sems(new_functor, new_argument) 189 190 semantics = self.formalism.Semantics.apply(new_functor, new_argument, grammar=self.grammar) 191 192 result = self.formalism.Syntax.Sign(category, semantics) 193 194 if settings.WARN_ABOUT_FREE_VARS: 195 free_vars = semantics.lf.get_unbound_variables() 196 if free_vars: 197 logger.warn("Found free variables after application: %s in %s" % (",".join(["%s" % var for var in free_vars]), semantics)) 198 199 return [result]
200 201 202
203 -class CompositionRuleBase(Rule):
204 """ 205 Rule for standard CCG composition. 206 """
207 - def __init__(self, *args, **kwargs):
208 super(CompositionRuleBase, self).__init__(*args, **kwargs) 209 forward = (kwargs.get("dir", "forward") == "forward") 210 harmonic = (kwargs.get("harmonic", "true") == "true") 211 if forward: 212 if harmonic: 213 self.name = ">B" 214 self.readable_rule = "X/Y Y/Z =>B X/Z" 215 self.internal_name = "compf" 216 else: 217 self.name = ">Bx" 218 self.readable_rule = "X/Y Y\\Z =>Bx X\\Z" 219 self.internal_name = "xcompf" 220 else: 221 if harmonic: 222 self.name = "<B" 223 self.readable_rule = "Y\\Z X\\Y =>B X\\Z" 224 self.internal_name = "compb" 225 else: 226 self.name = "<Bx" 227 self.readable_rule = "Y/Z X\\Y =>Bx X/Z" 228 self.internal_name = "xcompb" 229 230 self.forward = forward 231 self.harmonic = harmonic 232 self.arity = 2
233
234 - def apply_rule(self, cat_list, conditions=None, proc_sems=None):
235 ################## Check the rule is valid 236 # Can only operate on 2 categories 237 if len(cat_list) != 2: 238 return None 239 240 first = cat_list[0] 241 second = cat_list[1] 242 243 if not self.formalism.Syntax.is_complex_category(first.category) or \ 244 not self.formalism.Syntax.is_complex_category(second.category): 245 return None 246 247 if self.harmonic: 248 if first.category.slash.forward != self.forward or \ 249 second.category.slash.forward != self.forward: 250 return None 251 else: 252 if not first.category.slash.forward or \ 253 second.category.slash.forward: 254 return None 255 256 if conditions is not None: 257 # Check extra conditions 258 if not conditions(first, second): 259 return None 260 261 # Now we try the unification 262 first_cat = first.category.copy() 263 second_cat = second.category.copy() 264 self.formalism.distinguish_categories(first_cat, second_cat) 265 266 # Extract the right bits according to whether it's forward or backward 267 if self.forward: 268 middle1 = first_cat.argument 269 middle2 = second_cat.result 270 else: 271 middle1 = first_cat.result 272 middle2 = second_cat.argument 273 274 # Check that composition is possible 275 unification_result = self.formalism.unify(middle1, middle2, grammar=self.grammar) 276 if unification_result is None: 277 return None 278 279 ################## Checks passed: apply the rule 280 # Extract the right bits according to whether it's forward or backward. 281 # This bit is the same for harmonic and crossing. 282 if self.forward: 283 result = first_cat.result 284 argument = second_cat.argument 285 else: 286 result = second_cat.result 287 argument = first_cat.argument 288 289 if not self.forward: 290 # Rename things like variables and slashes to match the unification conditions 291 unification_result.apply_all_mappings(result) 292 # Now ready to apply the variable bindings 293 result = unification_result.constraints.apply(result) 294 295 if self.forward: 296 # Rename things like variables and slashes to match the unification conditions 297 unification_result.apply_all_mappings(argument) 298 # Now ready to apply the bindings to this one 299 argument = unification_result.constraints.apply(argument) 300 301 # Harmonic: forward slashed result for forward slashed rule. 302 # Crossing: backward slashed result for forward slashed rule. 303 slash = self.formalism.Syntax.Slash(self.forward == self.harmonic) 304 category = self.formalism.Syntax.ComplexCategory( 305 result, \ 306 slash, \ 307 argument) 308 309 # Now sort out the semantics 310 if self.forward: 311 fun_f = first.semantics.copy() 312 fun_g = second.semantics.copy() 313 else: 314 fun_g = first.semantics.copy() 315 fun_f = second.semantics.copy() 316 317 # Make sure we don't confuse similarly-named variables 318 from jazzparser.formalisms.base.semantics.lambdacalc import distinguish_variables 319 distinguish_variables(fun_f, fun_g) 320 321 if proc_sems is not None: 322 proc_sems(fun_f, fun_g) 323 semantics = self.formalism.Semantics.compose(fun_f, fun_g) 324 325 # Make sure semantics is in BNF 326 semantics.beta_reduce(grammar=self.grammar) 327 328 if settings.WARN_ABOUT_FREE_VARS: 329 free_vars = semantics.lf.get_unbound_variables() 330 if free_vars: 331 logger.warn("Found free variables after composition: %s in %s" % (",".join(["%s" % var for var in free_vars]), semantics)) 332 333 result = self.formalism.Syntax.Sign(category, semantics) 334 return [result]
335