Package pyffi :: Package object_models :: Package xml :: Module bit_struct
[hide private]
[frames] | no frames]

Source Code for Module pyffi.object_models.xml.bit_struct

  1  """Implements base class for bitstruct types.""" 
  2   
  3  # -------------------------------------------------------------------------- 
  4  # ***** BEGIN LICENSE BLOCK ***** 
  5  # 
  6  # Copyright (c) 2007-2011, Python File Format Interface 
  7  # All rights reserved. 
  8  # 
  9  # Redistribution and use in source and binary forms, with or without 
 10  # modification, are permitted provided that the following conditions 
 11  # are met: 
 12  # 
 13  #    * Redistributions of source code must retain the above copyright 
 14  #      notice, this list of conditions and the following disclaimer. 
 15  # 
 16  #    * Redistributions in binary form must reproduce the above 
 17  #      copyright notice, this list of conditions and the following 
 18  #      disclaimer in the documentation and/or other materials provided 
 19  #      with the distribution. 
 20  # 
 21  #    * Neither the name of the Python File Format Interface 
 22  #      project nor the names of its contributors may be used to endorse 
 23  #      or promote products derived from this software without specific 
 24  #      prior written permission. 
 25  # 
 26  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 27  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 28  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 29  # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 30  # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 31  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 32  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 33  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 34  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 35  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 36  # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 37  # POSSIBILITY OF SUCH DAMAGE. 
 38  # 
 39  # ***** END LICENSE BLOCK ***** 
 40  # -------------------------------------------------------------------------- 
 41   
 42  # note: some imports are defined at the end to avoid problems with circularity 
 43   
 44  from functools import partial 
 45  from itertools import izip 
 46  import struct 
 47   
 48  from pyffi.object_models.editable import EditableSpinBox # for Bits 
 49  from pyffi.utils.graph import DetailNode, EdgeFilter 
50 51 -class _MetaBitStructBase(type):
52 """This metaclass checks for the presence of a _attrs attribute. 53 For each attribute in _attrs, an <attrname> property is generated which 54 gets and sets bit fields. Used as metaclass of BitStructBase."""
55 - def __init__(cls, name, bases, dct):
56 super(_MetaBitStructBase, cls).__init__(name, bases, dct) 57 # consistency checks 58 if not '_attrs' in dct: 59 raise TypeError('%s: missing _attrs attribute'%cls) 60 if not '_numbytes' in dct: 61 raise TypeError('%s: missing _numbytes attribute'%cls) 62 63 # check storage type 64 if cls._numbytes == 1: 65 cls._struct = 'B' 66 elif cls._numbytes == 2: 67 cls._struct = 'H' 68 elif cls._numbytes == 4: 69 cls._struct = 'I' 70 else: 71 raise RuntimeError("unsupported bitstruct numbytes") 72 73 # template type? 74 cls._is_template = False 75 # does the type contain a Ref or a Ptr? 76 cls._has_links = False 77 # does the type contain a Ref? 78 cls._has_refs = False 79 # does the type contain a string? 80 cls._has_strings = False 81 for attr in dct['_attrs']: 82 # get and set basic attributes 83 setattr(cls, attr.name, property( 84 partial(BitStructBase.get_attribute, name = attr.name), 85 partial(BitStructBase.set_attribute, name = attr.name), 86 doc=attr.doc)) 87 88 # precalculate the attribute list 89 cls._attribute_list = cls._get_attribute_list() 90 91 # precalculate the attribute name list 92 cls._names = cls._get_names()
93
94 -class Bits(DetailNode, EditableSpinBox):
95 """Basic implementation of a n-bit unsigned integer type (without read 96 and write)."""
97 - def __init__(self, numbits = 1, default = 0, parent = None):
98 # parent disabled for performance 99 #self._parent = weakref.ref(parent) if parent else None 100 self._value = default 101 self._numbits = numbits
102
103 - def get_value(self):
104 """Return stored value.""" 105 return self._value
106
107 - def set_value(self, value):
108 """Set value to C{value}.""" 109 if not isinstance(value, (int, long)): 110 raise TypeError("bitstruct attribute must be integer") 111 if value >> self._numbits: 112 raise ValueError('value out of range (%i)' % value) 113 self._value = value
114
115 - def __str__(self):
116 return str(self.get_value())
117 118 # DetailNode 119
120 - def get_detail_display(self):
121 """Return an object that can be used to display the instance.""" 122 return str(self._value)
123 124 # EditableSpinBox functions 125
126 - def get_editor_value(self):
127 return self.get_value()
128
129 - def set_editor_value(self, editorvalue):
130 self.set_value(editorvalue)
131
132 - def get_editor_minimum(self):
133 return 0
134
135 - def get_editor_maximum(self):
136 return (1 << self._numbits) - 1
137
138 -class BitStructBase(DetailNode):
139 """Base class from which all file bitstruct types are derived. 140 141 The BitStructBase class implements the basic bitstruct interface: 142 it will initialize all attributes using the class interface 143 using the _attrs class variable, represent them as strings, and so on. 144 The class variable _attrs must be declared every derived class 145 interface. 146 147 Each item in the class _attrs list stores the information about 148 the attribute as stored for instance in the xml file, and the 149 _<name>_value_ instance variable stores the actual attribute 150 instance. 151 152 Direct access to the attributes is implemented using a <name> 153 property which invokes the get_attribute and set_attribute 154 functions, as demonstrated below. 155 156 See the pyffi.XmlHandler class for a more advanced example. 157 158 >>> from pyffi.object_models.xml.basic import BasicBase 159 >>> from pyffi.object_models.xml.expression import Expression 160 >>> from pyffi.object_models.xml import BitStructAttribute as Attr 161 >>> class SimpleFormat(object): 162 ... @staticmethod 163 ... def name_attribute(name): 164 ... return name 165 >>> class Flags(BitStructBase): 166 ... _numbytes = 1 167 ... _attrs = [ 168 ... Attr(SimpleFormat, dict(name = 'a', numbits = '3')), 169 ... Attr(SimpleFormat, dict(name = 'b', numbits = '1'))] 170 >>> SimpleFormat.Flags = Flags 171 >>> y = Flags() 172 >>> y.a = 5 173 >>> y.b = 1 174 >>> print(y) # doctest:+ELLIPSIS 175 <class 'pyffi.object_models.xml.bit_struct.Flags'> instance at 0x... 176 * a : 5 177 * b : 1 178 <BLANKLINE> 179 >>> y.to_int(None) 180 13 181 >>> y.from_int(9, None) 182 >>> print(y) # doctest:+ELLIPSIS 183 <class 'pyffi.object_models.xml.bit_struct.Flags'> instance at 0x... 184 * a : 1 185 * b : 1 186 <BLANKLINE> 187 """ 188 189 __metaclass__ = _MetaBitStructBase 190 191 _attrs = [] 192 _numbytes = 1 # default width of a bitstruct 193 _games = {} 194 arg = None # default argument 195 196 # initialize all attributes
197 - def __init__(self, template = None, argument = None, parent = None):
198 """The constructor takes a tempate: any attribute whose type, 199 or template type, is type(None) - which corresponds to 200 TEMPLATE in the xml description - will be replaced by this 201 type. The argument is what the ARG xml tags will be replaced with. 202 203 :param template: If the class takes a template type 204 argument, then this argument describes the template type. 205 :param argument: If the class takes a type argument, then 206 it is described here. 207 :param parent: The parent of this instance, that is, the instance this 208 array is an attribute of.""" 209 # used to track names of attributes that have already been added 210 # is faster than self.__dict__.has_key(...) 211 names = [] 212 # initialize argument 213 self.arg = argument 214 # save parent (note: disabled for performance) 215 #self._parent = weakref.ref(parent) if parent else None 216 # initialize item list 217 # this list is used for instance by qskope to display the structure 218 # in a tree view 219 self._items = [] 220 # initialize attributes 221 for attr in self._attribute_list: 222 # skip attributes with dupiclate names 223 if attr.name in names: 224 continue 225 names.append(attr.name) 226 227 # instantiate the integer 228 if attr.default != None: 229 attr_instance = Bits(numbits = attr.numbits, 230 default = attr.default, 231 parent = self) 232 else: 233 attr_instance = Bits(numbits = attr.numbits, 234 parent = self) 235 236 # assign attribute value 237 setattr(self, "_%s_value_" % attr.name, attr_instance) 238 239 # add instance to item list 240 self._items.append(attr_instance)
241
242 - def deepcopy(self, block):
243 """Copy attributes from a given block (one block class must be a 244 subclass of the other). Returns self.""" 245 # check class lineage 246 if isinstance(self, block.__class__): 247 attrlist = block._get_filtered_attribute_list() 248 elif isinstance(block, self.__class__): 249 attrlist = self._get_filtered_attribute_list() 250 else: 251 raise ValueError("deepcopy: classes %s and %s unrelated" 252 % (self.__class__.__name__, block.__class__.__name__)) 253 # copy the attributes 254 for attr in attrlist: 255 setattr(self, attr.name, getattr(block, attr.name)) 256 257 return self
258 259 # string of all attributes
260 - def __str__(self):
261 text = '%s instance at 0x%08X\n' % (self.__class__, id(self)) 262 # used to track names of attributes that have already been added 263 # is faster than self.__dict__.has_key(...) 264 for attr in self._get_filtered_attribute_list(): 265 # append string 266 attr_str_lines = str( 267 getattr(self, "_%s_value_" % attr.name)).splitlines() 268 if len(attr_str_lines) > 1: 269 text += '* %s :\n' % attr.name 270 for attr_str in attr_str_lines: 271 text += ' %s\n' % attr_str 272 else: 273 text += '* %s : %s\n' % (attr.name, attr_str_lines[0]) 274 return text
275
276 - def read(self, stream, data):
277 """Read structure from stream.""" 278 # read all attributes 279 value = struct.unpack(data._byte_order + self._struct, 280 stream.read(self._numbytes))[0] 281 # set the structure variables 282 self.from_int(value, data)
283
284 - def from_int(self, value, data):
285 """Set structure values from integer.""" 286 bitpos = 0 287 for attr in self._get_filtered_attribute_list(data): 288 #print(attr.name) # debug 289 attrvalue = (value >> bitpos) & ((1 << attr.numbits) - 1) 290 setattr(self, attr.name, attrvalue) 291 bitpos += attr.numbits
292
293 - def to_int(self, data):
294 # implementation note: not defined via __int__ because conversion 295 # takes arguments 296 """Get as integer.""" 297 value = 0 298 bitpos = 0 299 for attr in self._get_filtered_attribute_list(data): 300 attrvalue = getattr(self, attr.name) 301 value |= (attrvalue & ((1 << attr.numbits) - 1)) << bitpos 302 bitpos += attr.numbits 303 return value
304
305 - def write(self, stream, data):
306 """Write structure to stream.""" 307 stream.write(struct.pack(data._byte_order + self._struct, 308 self.to_int(data)))
309 313 317
318 - def get_strings(self, data):
319 """Get list of all strings in the structure.""" 320 return []
321
322 - def get_refs(self, data=None):
323 """Get list of all references in the structure. Refs are 324 links that point down the tree. For instance, if you need to parse 325 the whole tree starting from the root you would use get_refs and not 326 get_links, as get_links could result in infinite recursion.""" 327 return []
328
329 - def get_size(self, data=None):
330 """Calculate the structure size in bytes.""" 331 return self._numbytes
332
333 - def get_hash(self, data=None):
334 """Calculate a hash for the structure, as a tuple.""" 335 # calculate hash 336 hsh = [] 337 for attr in self._get_filtered_attribute_list(data): 338 hsh.append(getattr(self, attr.name)) 339 return tuple(hsh)
340 341 @classmethod
342 - def get_games(cls):
343 """Get games for which this block is supported.""" 344 return list(cls._games.iterkeys())
345 346 @classmethod
347 - def get_versions(cls, game):
348 """Get versions supported for C{game}.""" 349 return cls._games[game]
350 351 @classmethod
352 - def _get_attribute_list(cls):
353 """Calculate the list of all attributes of this structure.""" 354 # string of attributes of base classes of cls 355 attrs = [] 356 for base in cls.__bases__: 357 try: 358 attrs.extend(base._get_attribute_list()) 359 except AttributeError: # when base class is "object" 360 pass 361 attrs.extend(cls._attrs) 362 return attrs
363 364 @classmethod
365 - def _get_names(cls):
366 """Calculate the list of all attributes names in this structure. 367 Skips duplicate names.""" 368 # string of attributes of base classes of cls 369 names = [] 370 #for base in cls.__bases__: 371 # try: 372 # names.extend(base._get_names()) 373 # except AttributeError: # when base class is "object" 374 # pass 375 for attr in cls._attrs: 376 if attr.name in names: 377 continue 378 else: 379 names.append(attr.name) 380 return names
381
382 - def _get_filtered_attribute_list(self, data=None):
383 """Generator for listing all 'active' attributes, that is, 384 attributes whose condition evaluates ``True``, whose version 385 interval contaions C{version}, and whose user version is 386 C{user_version}. ``None`` for C{version} or C{user_version} means 387 that these checks are ignored. Duplicate names are skipped as 388 well. 389 390 Note: Use data instead of version and user_version (old way will be 391 deprecated).""" 392 names = [] 393 if data: 394 version = data.version 395 user_version = data.user_version 396 else: 397 version = None 398 user_version = None 399 for attr in self._attribute_list: 400 #print(attr.name, version, attr.ver1, attr.ver2) # debug 401 402 # check version 403 if not (version is None): 404 if (not (attr.ver1 is None)) and version < attr.ver1: 405 continue 406 if (not (attr.ver2 is None)) and version > attr.ver2: 407 continue 408 #print("version check passed") # debug 409 410 # check user version 411 if not(attr.userver is None or user_version is None) \ 412 and user_version != attr.userver: 413 continue 414 #print("user version check passed") # debug 415 416 # check condition 417 if not (attr.cond is None) and not attr.cond.eval(self): 418 continue 419 #print("condition passed") # debug 420 421 # skip dupiclate names 422 if attr.name in names: 423 continue 424 #print("duplicate check passed") # debug 425 426 names.append(attr.name) 427 # passed all tests 428 # so yield the attribute 429 yield attr
430
431 - def get_attribute(self, name):
432 """Get a basic attribute.""" 433 return getattr(self, "_" + name + "_value_").get_value()
434 435 # important note: to apply partial(set_attribute, name = 'xyz') the 436 # name argument must be last
437 - def set_attribute(self, value, name):
438 """Set the value of a basic attribute.""" 439 getattr(self, "_" + name + "_value_").set_value(value)
440
441 - def tree(self):
442 """A generator for parsing all blocks in the tree (starting from and 443 including C{self}). By default, there is no tree structure, so returns 444 self.""" 445 # return self 446 yield self
447 448 # DetailNode 449
450 - def get_detail_child_nodes(self, edge_filter=EdgeFilter()):
451 """Yield children of this structure.""" 452 return (item for item in self._items)
453
454 - def get_detail_child_names(self, edge_filter=EdgeFilter()):
455 """Yield name of each child.""" 456 return (name for name in self._names)
457