Package jazzparser :: Package data :: Module assignments
[hide private]
[frames] | no frames]

Source Code for Module jazzparser.data.assignments

  1  """Assignment utilities for the Jazz Parser. 
  2   
  3  These are generic tools for assignments. They can be used for different  
  4  sorts of assignment and are useful for unification frameworks (e.g. 
  5  the music_roman unification procedure). 
  6   
  7  """ 
  8  """ 
  9  ============================== License ======================================== 
 10   Copyright (C) 2008, 2010-12 University of Edinburgh, Mark Granroth-Wilding 
 11    
 12   This file is part of The Jazz Parser. 
 13    
 14   The Jazz Parser is free software: you can redistribute it and/or modify 
 15   it under the terms of the GNU General Public License as published by 
 16   the Free Software Foundation, either version 3 of the License, or 
 17   (at your option) any later version. 
 18    
 19   The Jazz Parser is distributed in the hope that it will be useful, 
 20   but WITHOUT ANY WARRANTY; without even the implied warranty of 
 21   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 22   GNU General Public License for more details. 
 23    
 24   You should have received a copy of the GNU General Public License 
 25   along with The Jazz Parser.  If not, see <http://www.gnu.org/licenses/>. 
 26   
 27  ============================ End license ====================================== 
 28   
 29  """ 
 30  __author__ = "Mark Granroth-Wilding <mark.granroth-wilding@ed.ac.uk>"  
 31   
 32  import logging 
 33  # Get the logger from the logging system 
 34  logger = logging.getLogger("main_logger") 
 35   
 36   
37 -class EquivalenceAssignment(dict):
38 """ 39 A special kind of dict that stores not only a mapping from keys to 40 values, but also equivalence classes of keys. Manages the equivalence 41 classes so that values get assigned to all the keys as soon as 42 one of them is given a value. 43 """
44 - def __init__(self, *args, **kwargs):
45 super(EquivalenceAssignment, self).__init__(*args, **kwargs) 46 self.classes = [] 47 self.inconsistent = False
48
49 - def _pop_class(self, key):
50 """ 51 Return the equivalence class including the key, if one 52 exists, removing from the list of classes. 53 """ 54 for i,cls in enumerate(self.classes): 55 if key in cls: 56 return self.classes.pop(i) 57 return None
58
59 - def _get_class(self, key):
60 for cls in self.classes: 61 if key in cls: 62 return cls 63 return None
64
65 - def __setitem__(self, key, value):
66 # Check the equivalence classes for other keys equivalent to this one 67 cls = self._pop_class(key) 68 if key in self and self[key] is not None: 69 # We already have a value for this key. 70 # Resolve the conflict in a way appropriate to the data type. 71 value = self.resolve_conflict(self[key], value) 72 super(EquivalenceAssignment, self).__setitem__(key, value) 73 # Set all the other keys in the equiv class 74 if cls is not None: 75 for eq_key in cls: 76 # This recursively call setitem, but will not find the eq 77 # class again, because we popped it. 78 self[eq_key] = value
79
80 - def add_equivalence(self, key1, key2):
81 """ Asserts the equivalence of the two keys. """ 82 if key1 == key2: 83 # They're equal: no need to assert equivalence 84 return 85 # Look for an existing class for each key 86 key1_class = self._get_class(key1) 87 key2_class = self._get_class(key2) 88 if key1_class is not None and key2_class is not None: 89 if key1_class is key2_class: 90 # They're already equivalent 91 return 92 # Both keys are already in classes. Merge them 93 self.classes.remove(key1_class) 94 self.classes.remove(key2_class) 95 self.classes.append(key1_class | key2_class) 96 elif key1_class is not None: 97 # key1 is already in a class. Add key2 98 key1_class.add(key2) 99 elif key2_class is not None: 100 # key2 is already in a class. Add key1 101 key2_class.add(key1) 102 else: 103 # Neither key was found. Add a new equivalence class 104 self.classes.append(set([key1, key2])) 105 # Added an equivalence: set any values again to make sure the 106 # whole class gets the value 107 if key1 in self: 108 # setitem will take care of the equivalences 109 self[key1] = self[key1] 110 if key2 in self: 111 self[key2] = self[key2]
112
113 - def resolve_conflict(self, old_value, new_value):
114 """ 115 When two values are fighting for the same key, this method 116 decides which to pick, or raises an exception if the conflict 117 cannot be resolved (i.e. incompatible values). 118 By default this raises an exception if the values are not equal. 119 Should be overridden by subclasses. 120 """ 121 if old_value != new_value: 122 self.inconsistent = True 123 raise EquivalenceAssignment.IncompatibleAssignmentError, \ 124 "%s and %s cannot be assigned to the same key" % (old_value, new_value) 125 return old_value
126
127 - def update(self, ass):
128 if isinstance(ass, EquivalenceAssignment): 129 # Another eq assignment: update including the eq classes 130 for cls in ass.classes: 131 if len(cls) > 1: 132 cls = list(cls) 133 first = cls[0] 134 for other in cls[1:]: 135 self.add_equivalence(first, other) 136 super(EquivalenceAssignment, self).update(ass)
137
138 - def __str__(self):
139 return "<%s | [%s]>" % (", ".join(["%s=%s" % ass for ass in self.items()]), 140 ",".join(["(%s)" % ",".join(["%s" % key for key in cls]) for cls in self.classes]))
141
142 - class IncompatibleAssignmentError(Exception):
143 pass
144