| """sstruct.py -- SuperStruct |
| |
| Higher level layer on top of the struct module, enabling to |
| bind names to struct elements. The interface is similar to |
| struct, except the objects passed and returned are not tuples |
| (or argument lists), but dictionaries or instances. |
| |
| Just like struct, we use format strings to describe a data |
| structure, except we use one line per element. Lines are |
| separated by newlines or semi-colons. Each line contains |
| either one of the special struct characters ('@', '=', '<', |
| '>' or '!') or a 'name:formatchar' combo (eg. 'myFloat:f'). |
| Repetitions, like the struct module offers them are not useful |
| in this context, except for fixed length strings (eg. 'myInt:5h' |
| is not allowed but 'myString:5s' is). The 'x' format character |
| (pad byte) is treated as 'special', since it is by definition |
| anonymous. Extra whitespace is allowed everywhere. |
| |
| The sstruct module offers one feature that the "normal" struct |
| module doesn't: support for fixed point numbers. These are spelled |
| as "n.mF", where n is the number of bits before the point, and m |
| the number of bits after the point. Fixed point numbers get |
| converted to floats. |
| |
| pack(format, object): |
| 'object' is either a dictionary or an instance (or actually |
| anything that has a __dict__ attribute). If it is a dictionary, |
| its keys are used for names. If it is an instance, it's |
| attributes are used to grab struct elements from. Returns |
| a string containing the data. |
| |
| unpack(format, data, object=None) |
| If 'object' is omitted (or None), a new dictionary will be |
| returned. If 'object' is a dictionary, it will be used to add |
| struct elements to. If it is an instance (or in fact anything |
| that has a __dict__ attribute), an attribute will be added for |
| each struct element. In the latter two cases, 'object' itself |
| is returned. |
| |
| unpack2(format, data, object=None) |
| Convenience function. Same as unpack, except data may be longer |
| than needed. The returned value is a tuple: (object, leftoverdata). |
| |
| calcsize(format) |
| like struct.calcsize(), but uses our own format strings: |
| it returns the size of the data in bytes. |
| """ |
| |
| # XXX I would like to support pascal strings, too, but I'm not |
| # sure if that's wise. Would be nice if struct supported them |
| # "properly", but that would certainly break calcsize()... |
| |
| __version__ = "1.2" |
| __copyright__ = "Copyright 1998, Just van Rossum <just@letterror.com>" |
| |
| import struct |
| import re |
| import types |
| |
| |
| error = "sstruct.error" |
| |
| def pack(format, object): |
| formatstring, names, fixes = getformat(format) |
| elements = [] |
| if type(object) is not types.DictType: |
| object = object.__dict__ |
| for name in names: |
| value = object[name] |
| if fixes.has_key(name): |
| # fixed point conversion |
| value = int(round(value*fixes[name])) |
| elements.append(value) |
| data = apply(struct.pack, (formatstring,) + tuple(elements)) |
| return data |
| |
| def unpack(format, data, object=None): |
| if object is None: |
| object = {} |
| formatstring, names, fixes = getformat(format) |
| if type(object) is types.DictType: |
| dict = object |
| else: |
| dict = object.__dict__ |
| elements = struct.unpack(formatstring, data) |
| for i in range(len(names)): |
| name = names[i] |
| value = elements[i] |
| if fixes.has_key(name): |
| # fixed point conversion |
| value = value / fixes[name] |
| dict[name] = value |
| return object |
| |
| def unpack2(format, data, object=None): |
| length = calcsize(format) |
| return unpack(format, data[:length], object), data[length:] |
| |
| def calcsize(format): |
| formatstring, names, fixes = getformat(format) |
| return struct.calcsize(formatstring) |
| |
| |
| # matches "name:formatchar" (whitespace is allowed) |
| _elementRE = re.compile( |
| "\s*" # whitespace |
| "([A-Za-z_][A-Za-z_0-9]*)" # name (python identifier) |
| "\s*:\s*" # whitespace : whitespace |
| "([cbBhHiIlLfd]|[0-9]+[ps]|" # formatchar... |
| "([0-9]+)\.([0-9]+)(F))" # ...formatchar |
| "\s*" # whitespace |
| "(#.*)?$" # [comment] + end of string |
| ) |
| |
| # matches the special struct format chars and 'x' (pad byte) |
| _extraRE = re.compile("\s*([x@=<>!])\s*(#.*)?$") |
| |
| # matches an "empty" string, possibly containing whitespace and/or a comment |
| _emptyRE = re.compile("\s*(#.*)?$") |
| |
| _fixedpointmappings = { |
| 8: "b", |
| 16: "h", |
| 32: "l"} |
| |
| _formatcache = {} |
| |
| def getformat(format): |
| try: |
| formatstring, names, fixes = _formatcache[format] |
| except KeyError: |
| lines = re.split("[\n;]", format) |
| formatstring = "" |
| names = [] |
| fixes = {} |
| for line in lines: |
| if _emptyRE.match(line): |
| continue |
| m = _extraRE.match(line) |
| if m: |
| formatchar = m.group(1) |
| if formatchar <> 'x' and formatstring: |
| raise error, "a special format char must be first" |
| else: |
| m = _elementRE.match(line) |
| if not m: |
| raise error, "syntax error in format: '%s'" % line |
| name = m.group(1) |
| names.append(name) |
| formatchar = m.group(2) |
| if m.group(3): |
| # fixed point |
| before = int(m.group(3)) |
| after = int(m.group(4)) |
| bits = before + after |
| if bits not in [8, 16, 32]: |
| raise error, "fixed point must be 8, 16 or 32 bits long" |
| formatchar = _fixedpointmappings[bits] |
| assert m.group(5) == "F" |
| fixes[name] = float(1 << after) |
| formatstring = formatstring + formatchar |
| _formatcache[format] = formatstring, names, fixes |
| return formatstring, names, fixes |
| |
| def _test(): |
| format = """ |
| # comments are allowed |
| > # big endian (see documentation for struct) |
| # empty lines are allowed: |
| |
| ashort: h |
| along: l |
| abyte: b # a byte |
| achar: c |
| astr: 5s |
| afloat: f; adouble: d # multiple "statements" are allowed |
| afixed: 16.16F |
| """ |
| |
| print 'size:', calcsize(format) |
| |
| class foo: |
| pass |
| |
| i = foo() |
| |
| i.ashort = 0x7fff |
| i.along = 0x7fffffff |
| i.abyte = 0x7f |
| i.achar = "a" |
| i.astr = "12345" |
| i.afloat = 0.5 |
| i.adouble = 0.5 |
| i.afixed = 1.5 |
| |
| data = pack(format, i) |
| print 'data:', `data` |
| print unpack(format, data) |
| i2 = foo() |
| unpack(format, data, i2) |
| print vars(i2) |
| |
| if __name__ == "__main__": |
| _test() |