blob: d57e956a520cc8b698bf6bfdc8307875f13bc9c9 [file] [log] [blame]
Just7842e561999-12-16 21:34:53 +00001"""sstruct.py -- SuperStruct
2
3Higher level layer on top of the struct module, enabling to
4bind names to struct elements. The interface is similar to
5struct, except the objects passed and returned are not tuples
6(or argument lists), but dictionaries or instances.
7
8Just like struct, we use format strings to describe a data
9structure, except we use one line per element. Lines are
10separated by newlines or semi-colons. Each line contains
11either one of the special struct characters ('@', '=', '<',
12'>' or '!') or a 'name:formatchar' combo (eg. 'myFloat:f').
13Repetitions, like the struct module offers them are not useful
14in this context, except for fixed length strings (eg. 'myInt:5h'
15is not allowed but 'myString:5s' is). The 'x' format character
16(pad byte) is treated as 'special', since it is by definition
17anonymous. Extra whitespace is allowed everywhere.
18
19The sstruct module offers one feature that the "normal" struct
20module doesn't: support for fixed point numbers. These are spelled
21as "n.mF", where n is the number of bits before the point, and m
22the number of bits after the point. Fixed point numbers get
23converted to floats.
24
25pack(format, object):
26 'object' is either a dictionary or an instance (or actually
27 anything that has a __dict__ attribute). If it is a dictionary,
28 its keys are used for names. If it is an instance, it's
29 attributes are used to grab struct elements from. Returns
30 a string containing the data.
31
32unpack(format, data, object=None)
33 If 'object' is omitted (or None), a new dictionary will be
34 returned. If 'object' is a dictionary, it will be used to add
35 struct elements to. If it is an instance (or in fact anything
36 that has a __dict__ attribute), an attribute will be added for
37 each struct element. In the latter two cases, 'object' itself
38 is returned.
39
40unpack2(format, data, object=None)
41 Convenience function. Same as unpack, except data may be longer
42 than needed. The returned value is a tuple: (object, leftoverdata).
43
44calcsize(format)
45 like struct.calcsize(), but uses our own format strings:
46 it returns the size of the data in bytes.
47"""
48
49# XXX I would like to support pascal strings, too, but I'm not
50# sure if that's wise. Would be nice if struct supported them
51# "properly", but that would certainly break calcsize()...
52
53__version__ = "1.2"
54__copyright__ = "Copyright 1998, Just van Rossum <just@letterror.com>"
55
56import struct
57import re
Just7842e561999-12-16 21:34:53 +000058
59
60error = "sstruct.error"
61
62def pack(format, object):
63 formatstring, names, fixes = getformat(format)
64 elements = []
Behdad Esfahbod002c32f2013-11-27 04:48:20 -050065 if not isinstance(object, dict):
Just7842e561999-12-16 21:34:53 +000066 object = object.__dict__
67 for name in names:
68 value = object[name]
Behdad Esfahbodbc5e1cb2013-11-27 02:33:03 -050069 if name in fixes:
Just7842e561999-12-16 21:34:53 +000070 # fixed point conversion
71 value = int(round(value*fixes[name]))
72 elements.append(value)
Behdad Esfahbod66214cb2013-11-27 02:18:18 -050073 data = struct.pack(*(formatstring,) + tuple(elements))
Just7842e561999-12-16 21:34:53 +000074 return data
75
76def unpack(format, data, object=None):
77 if object is None:
78 object = {}
79 formatstring, names, fixes = getformat(format)
Behdad Esfahbod002c32f2013-11-27 04:48:20 -050080 if isinstance(object, dict):
81 d = object
Just7842e561999-12-16 21:34:53 +000082 else:
Behdad Esfahbod002c32f2013-11-27 04:48:20 -050083 d = object.__dict__
Just7842e561999-12-16 21:34:53 +000084 elements = struct.unpack(formatstring, data)
85 for i in range(len(names)):
86 name = names[i]
87 value = elements[i]
Behdad Esfahbodbc5e1cb2013-11-27 02:33:03 -050088 if name in fixes:
Just7842e561999-12-16 21:34:53 +000089 # fixed point conversion
90 value = value / fixes[name]
Behdad Esfahbod002c32f2013-11-27 04:48:20 -050091 d[name] = value
Just7842e561999-12-16 21:34:53 +000092 return object
93
94def unpack2(format, data, object=None):
95 length = calcsize(format)
96 return unpack(format, data[:length], object), data[length:]
97
98def calcsize(format):
99 formatstring, names, fixes = getformat(format)
100 return struct.calcsize(formatstring)
101
102
103# matches "name:formatchar" (whitespace is allowed)
104_elementRE = re.compile(
105 "\s*" # whitespace
106 "([A-Za-z_][A-Za-z_0-9]*)" # name (python identifier)
107 "\s*:\s*" # whitespace : whitespace
108 "([cbBhHiIlLfd]|[0-9]+[ps]|" # formatchar...
109 "([0-9]+)\.([0-9]+)(F))" # ...formatchar
110 "\s*" # whitespace
111 "(#.*)?$" # [comment] + end of string
112 )
113
114# matches the special struct format chars and 'x' (pad byte)
115_extraRE = re.compile("\s*([x@=<>!])\s*(#.*)?$")
116
117# matches an "empty" string, possibly containing whitespace and/or a comment
118_emptyRE = re.compile("\s*(#.*)?$")
119
120_fixedpointmappings = {
121 8: "b",
122 16: "h",
123 32: "l"}
124
125_formatcache = {}
126
127def getformat(format):
128 try:
129 formatstring, names, fixes = _formatcache[format]
130 except KeyError:
131 lines = re.split("[\n;]", format)
132 formatstring = ""
133 names = []
134 fixes = {}
135 for line in lines:
136 if _emptyRE.match(line):
137 continue
138 m = _extraRE.match(line)
139 if m:
140 formatchar = m.group(1)
Behdad Esfahbod180ace62013-11-27 02:40:30 -0500141 if formatchar != 'x' and formatstring:
Behdad Esfahbodcd5aad92013-11-27 02:42:28 -0500142 raise error("a special format char must be first")
Just7842e561999-12-16 21:34:53 +0000143 else:
144 m = _elementRE.match(line)
145 if not m:
Behdad Esfahbodcd5aad92013-11-27 02:42:28 -0500146 raise error("syntax error in format: '%s'" % line)
Just7842e561999-12-16 21:34:53 +0000147 name = m.group(1)
148 names.append(name)
149 formatchar = m.group(2)
150 if m.group(3):
151 # fixed point
152 before = int(m.group(3))
153 after = int(m.group(4))
154 bits = before + after
155 if bits not in [8, 16, 32]:
Behdad Esfahbodcd5aad92013-11-27 02:42:28 -0500156 raise error("fixed point must be 8, 16 or 32 bits long")
Just7842e561999-12-16 21:34:53 +0000157 formatchar = _fixedpointmappings[bits]
158 assert m.group(5) == "F"
159 fixes[name] = float(1 << after)
160 formatstring = formatstring + formatchar
161 _formatcache[format] = formatstring, names, fixes
162 return formatstring, names, fixes
163
164def _test():
165 format = """
166 # comments are allowed
167 > # big endian (see documentation for struct)
168 # empty lines are allowed:
169
170 ashort: h
171 along: l
172 abyte: b # a byte
173 achar: c
174 astr: 5s
175 afloat: f; adouble: d # multiple "statements" are allowed
176 afixed: 16.16F
177 """
178
Behdad Esfahbod3ec6a252013-11-27 04:57:33 -0500179 print('size:', calcsize(format))
Just7842e561999-12-16 21:34:53 +0000180
181 class foo:
182 pass
183
184 i = foo()
185
186 i.ashort = 0x7fff
187 i.along = 0x7fffffff
188 i.abyte = 0x7f
189 i.achar = "a"
190 i.astr = "12345"
191 i.afloat = 0.5
192 i.adouble = 0.5
193 i.afixed = 1.5
194
195 data = pack(format, i)
Behdad Esfahbod3ec6a252013-11-27 04:57:33 -0500196 print('data:', repr(data))
197 print(unpack(format, data))
Just7842e561999-12-16 21:34:53 +0000198 i2 = foo()
199 unpack(format, data, i2)
Behdad Esfahbod3ec6a252013-11-27 04:57:33 -0500200 print(vars(i2))
Just7842e561999-12-16 21:34:53 +0000201
202if __name__ == "__main__":
203 _test()