Ran update-copyright.py.
[igor.git] / igor / struct.py
1 # Copyright (C) 2012 W. Trevor King <wking@tremily.us>
2 #
3 # This file is part of igor.
4 #
5 # igor is free software: you can redistribute it and/or modify it under the
6 # terms of the GNU Lesser General Public License as published by the Free
7 # Software Foundation, either version 3 of the License, or (at your option) any
8 # later version.
9 #
10 # igor is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
13 # details.
14 #
15 # You should have received a copy of the GNU Lesser General Public License
16 # along with igor.  If not, see <http://www.gnu.org/licenses/>.
17
18 """Structure and Field classes for declaring structures
19
20 There are a few formats that can be used to represent the same data, a
21 binary packed format with all the data in a buffer, a linearized
22 format with each field in a single Python list, and a nested format
23 with each field in a hierarchy of Python dictionaries.
24 """
25
26 from __future__ import absolute_import
27 import io as _io
28 import logging as _logging
29 import pprint as _pprint
30 import struct as _struct
31
32 import numpy as _numpy
33
34 from . import LOG as _LOG
35
36
37 _buffer = buffer  # save builtin buffer for clobbered situations
38
39
40 class Field (object):
41     """Represent a Structure field.
42
43     The format argument can be a format character from the ``struct``
44     documentation (e.g., ``c`` for ``char``, ``h`` for ``short``, ...)
45     or ``Structure`` instance (for building nested structures).
46
47     Examples
48     --------
49
50     >>> from pprint import pprint
51     >>> import numpy
52
53     Example of an unsigned short integer field:
54
55     >>> time = Field(
56     ...     'I', 'time', default=0, help='POSIX time')
57     >>> time.arg_count
58     1
59     >>> list(time.pack_data(1))
60     [1]
61     >>> list(time.pack_item(2))
62     [2]
63     >>> time.unpack_data([3])
64     3
65     >>> time.unpack_item([4])
66     4
67
68     Example of a multi-dimensional float field:
69
70     >>> data = Field(
71     ...     'f', 'data', help='example data', count=(2,3,4))
72     >>> data.arg_count
73     24
74     >>> list(data.indexes())  # doctest: +ELLIPSIS
75     [[0, 0, 0], [0, 0, 1], [0, 0, 2], [0, 0, 3], [0, 1, 0], ..., [1, 2, 3]]
76     >>> list(data.pack_data(
77     ...     [[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]],
78     ...      [[12, 13, 14, 15], [16, 17, 18, 19], [20, 21, 22, 23]]])
79     ...     )  # doctest: +ELLIPSIS
80     [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ..., 19, 20, 21, 22, 23]
81     >>> list(data.pack_item(3))
82     [3]
83     >>> data.unpack_data(range(data.arg_count))
84     array([[[ 0,  1,  2,  3],
85             [ 4,  5,  6,  7],
86             [ 8,  9, 10, 11]],
87     <BLANKLINE>
88            [[12, 13, 14, 15],
89             [16, 17, 18, 19],
90             [20, 21, 22, 23]]])
91     >>> data.unpack_item([3])
92     3
93
94     Example of a nested structure field:
95
96     >>> run = Structure('run', fields=[time, data])
97     >>> runs = Field(run, 'runs', help='pair of runs', count=2)
98     >>> runs.arg_count  # = 2 * (1 + 24)
99     50
100     >>> data1 = numpy.arange(data.arg_count).reshape(data.count)
101     >>> data2 = data1 + data.arg_count
102     >>> list(runs.pack_data(
103     ...     [{'time': 100, 'data': data1},
104     ...      {'time': 101, 'data': data2}])
105     ...     )  # doctest: +ELLIPSIS
106     [100, 0, 1, 2, ..., 22, 23, 101, 24, 25, ..., 46, 47]
107     >>> list(runs.pack_item({'time': 100, 'data': data1})
108     ...     )  # doctest: +ELLIPSIS
109     [100, 0, 1, 2, ..., 22, 23]
110     >>> pprint(runs.unpack_data(range(runs.arg_count)))
111     [{'data': array([[[ 1,  2,  3,  4],
112             [ 5,  6,  7,  8],
113             [ 9, 10, 11, 12]],
114     <BLANKLINE>
115            [[13, 14, 15, 16],
116             [17, 18, 19, 20],
117             [21, 22, 23, 24]]]),
118       'time': 0},
119      {'data': array([[[26, 27, 28, 29],
120             [30, 31, 32, 33],
121             [34, 35, 36, 37]],
122     <BLANKLINE>
123            [[38, 39, 40, 41],
124             [42, 43, 44, 45],
125             [46, 47, 48, 49]]]),
126       'time': 25}]
127     >>> pprint(runs.unpack_item(range(runs.structure_count)))
128     {'data': array([[[ 1,  2,  3,  4],
129             [ 5,  6,  7,  8],
130             [ 9, 10, 11, 12]],
131     <BLANKLINE>
132            [[13, 14, 15, 16],
133             [17, 18, 19, 20],
134             [21, 22, 23, 24]]]),
135      'time': 0}
136
137     If you don't give enough values for an array field, the remaining
138     values are filled in with their defaults.
139
140     >>> list(data.pack_data(
141     ...     [[[0, 1, 2, 3], [4, 5, 6]], [[10]]]))  # doctest: +ELLIPSIS
142     Traceback (most recent call last):
143       ...
144     ValueError: no default for <Field data ...>
145     >>> data.default = 0
146     >>> list(data.pack_data(
147     ...     [[[0, 1, 2, 3], [4, 5, 6]], [[10]]]))
148     [0, 1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
149
150     See Also
151     --------
152     Structure
153     """
154     def __init__(self, format, name, default=None, help=None, count=1):
155         self.format = format
156         self.name = name
157         self.default = default
158         self.help = help
159         self.count = count
160         self.setup()
161
162     def setup(self):
163         """Setup any dynamic properties of a field.
164
165         Use this method to recalculate dynamic properities after
166         changing the basic properties set during initialization.
167         """
168         _LOG.debug('setup {}'.format(self))
169         self.item_count = _numpy.prod(self.count)  # number of item repeats
170         if isinstance(self.format, Structure):
171             self.structure_count = sum(
172                 f.arg_count for f in self.format.fields)
173             self.arg_count = self.item_count * self.structure_count
174         elif self.format == 'x':
175             self.arg_count = 0  # no data in padding bytes
176         else:
177             self.arg_count = self.item_count  # struct.Struct format args
178
179     def __str__(self):
180         return self.__repr__()
181
182     def __repr__(self):
183         return '<{} {} {}>'.format(
184             self.__class__.__name__, self.name, id(self))
185
186     def indexes(self):
187         """Iterate through indexes to a possibly multi-dimensional array"""
188         assert self.item_count > 1, self
189         try:
190             i = [0] * len(self.count)
191         except TypeError:  # non-iterable count
192             for i in range(self.count):
193                 yield i
194         else:
195             for i in range(self.item_count):
196                 index = []
197                 for j,c in enumerate(reversed(self.count)):
198                     index.insert(0, i % c)
199                     i /= c
200                 yield index
201
202     def pack_data(self, data=None):
203         """Linearize a single field's data to a flat list.
204
205         If the field is repeated (count > 1), the incoming data should
206         be iterable with each iteration returning a single item.
207         """
208         if self.item_count > 1:
209             if data is None:
210                 data = []
211             if hasattr(data, 'flat'):  # take advantage of numpy's ndarray.flat
212                 items = 0
213                 for item in data.flat:
214                     items += 1
215                     for arg in self.pack_item(item):
216                         yield arg
217                 if items < self.item_count:
218                     if f.default is None:
219                         raise ValueError(
220                             'no default for {}.{}'.format(self, f))
221                     for i in range(self.item_count - items):
222                         yield f.default
223             else:
224                 for index in self.indexes():
225                     try:
226                         if isinstance(index, int):
227                             item = data[index]
228                         else:
229                             item = data
230                             for i in index:
231                                 item = item[i]
232                     except IndexError:
233                         item = None
234                     for arg in self.pack_item(item):
235                         yield arg
236         elif self.item_count:
237             for arg in self.pack_item(data):
238                 yield arg
239
240     def pack_item(self, item=None):
241         """Linearize a single count of the field's data to a flat iterable
242         """
243         if isinstance(self.format, Structure):
244             for i in self.format._pack_item(item):
245                 yield i
246         elif item is None:
247             if self.default is None:
248                 raise ValueError('no default for {}'.format(self))
249             yield self.default
250         else:
251             yield item
252
253     def unpack_data(self, data):
254         """Inverse of .pack_data"""
255         _LOG.debug('unpack {} for {} {}'.format(data, self, self.format))
256         iterator = iter(data)
257         try:
258             items = [iterator.next() for i in range(self.arg_count)]
259         except StopIteration:
260             raise ValueError('not enough data to unpack {}'.format(self))
261         try:
262             iterator.next()
263         except StopIteration:
264             pass
265         else:
266             raise ValueError('too much data to unpack {}'.format(self))
267         if isinstance(self.format, Structure):
268             # break into per-structure clumps
269             s = self.structure_count
270             items = zip(*[items[i::s] for i in range(s)])
271         else:
272             items = [[i] for i in items]
273         unpacked = [self.unpack_item(i) for i in items]
274         if self.arg_count:
275             count = self.count
276         else:
277             count = 0  # padding bytes, etc.
278         if count == 1:
279             return unpacked[0]
280         if isinstance(self.format, Structure):
281             try:
282                 len(self.count)
283             except TypeError:
284                 pass
285             else:
286                 raise NotImplementedError('reshape Structure field')
287         else:
288             unpacked = _numpy.array(unpacked)
289             _LOG.debug('reshape {} data from {} to {}'.format(
290                     self, unpacked.shape, count))
291             unpacked = unpacked.reshape(count)
292         return unpacked
293
294     def unpack_item(self, item):
295         """Inverse of .unpack_item"""
296         if isinstance(self.format, Structure):
297             return self.format._unpack_item(item)
298         else:
299             assert len(item) == 1, item
300             return item[0]
301
302
303 class DynamicField (Field):
304     """Represent a DynamicStructure field with a dynamic definition.
305
306     Adds the methods ``.pre_pack``, ``pre_unpack``, and
307     ``post_unpack``, all of which are called when a ``DynamicField``
308     is used by a ``DynamicStructure``.  Each method takes the
309     arguments ``(parents, data)``, where ``parents`` is a list of
310     ``DynamicStructure``\s that own the field and ``data`` is a dict
311     hierarchy of the structure data.
312
313     See the ``DynamicStructure`` docstring for the exact timing of the
314     method calls.
315
316     See Also
317     --------
318     Field, DynamicStructure
319     """
320     def pre_pack(self, parents, data):
321         "Prepare to pack."
322         pass
323
324     def pre_unpack(self, parents, data):
325         "React to previously unpacked data"
326         pass
327
328     def post_unpack(self, parents, data):
329         "React to our own data"
330         pass
331
332     def _get_structure_data(self, parents, data, structure):
333         """Extract the data belonging to a particular ancestor structure.
334         """
335         d = data
336         s = parents[0]
337         if s == structure:
338             return d
339         for p in parents[1:]:
340             for f in s.fields:
341                 if f.format == p:
342                     s = p
343                     d = d[f.name]
344                     break
345             assert s == p, (s, p)
346             if p == structure:
347                 break
348         return d
349
350
351 class Structure (_struct.Struct):
352     r"""Represent a C structure.
353
354     A convenient wrapper around struct.Struct that uses Fields and
355     adds dict-handling methods for transparent name assignment.
356
357     See Also
358     --------
359     Field
360
361     Examples
362     --------
363
364     >>> import array
365     >>> from pprint import pprint
366
367     Represent the C structures::
368
369         struct run {
370           unsigned int time;
371           short data[2][3];
372         }
373
374         struct experiment {
375           unsigned short version;
376           struct run runs[2];
377         }
378
379     As:
380
381     >>> time = Field('I', 'time', default=0, help='POSIX time')
382     >>> data = Field(
383     ...     'h', 'data', default=0, help='example data', count=(2,3))
384     >>> run = Structure('run', fields=[time, data])
385     >>> version = Field(
386     ...     'H', 'version', default=1, help='example version')
387     >>> runs = Field(run, 'runs', help='pair of runs', count=2)
388     >>> experiment = Structure('experiment', fields=[version, runs])
389
390     The structures automatically calculate the flattened data format:
391
392     >>> run.format
393     '@Ihhhhhh'
394     >>> run.size  # 4 + 2*3*2
395     16
396     >>> experiment.format
397     '@HIhhhhhhIhhhhhh'
398     >>> experiment.size  # 2 + 2 + 2*(4 + 2*3*2)
399     36
400
401     The first two elements in the above size calculation are 2 (for
402     the unsigned short, 'H') and 2 (padding so the unsigned int aligns
403     with a 4-byte block).  If you select a byte ordering that doesn't
404     mess with alignment and recalculate the format, the padding goes
405     away and you get:
406
407     >>> experiment.set_byte_order('>')
408     >>> experiment.get_format()
409     '>HIhhhhhhIhhhhhh'
410     >>> experiment.size
411     34
412
413     You can read data out of any object supporting the buffer
414     interface:
415
416     >>> b = array.array('B', range(experiment.size))
417     >>> d = experiment.unpack_from(buffer=b)
418     >>> pprint(d)
419     {'runs': [{'data': array([[1543, 2057, 2571],
420            [3085, 3599, 4113]]),
421                'time': 33752069},
422               {'data': array([[5655, 6169, 6683],
423            [7197, 7711, 8225]]),
424                'time': 303240213}],
425      'version': 1}
426     >>> [hex(x) for x in d['runs'][0]['data'].flat]
427     ['0x607L', '0x809L', '0xa0bL', '0xc0dL', '0xe0fL', '0x1011L']
428
429     You can also read out from strings:
430
431     >>> d = experiment.unpack(b.tostring())
432     >>> pprint(d)
433     {'runs': [{'data': array([[1543, 2057, 2571],
434            [3085, 3599, 4113]]),
435                'time': 33752069},
436               {'data': array([[5655, 6169, 6683],
437            [7197, 7711, 8225]]),
438                'time': 303240213}],
439      'version': 1}
440
441     If you don't give enough values for an array field, the remaining
442     values are filled in with their defaults.
443
444     >>> experiment.pack_into(buffer=b, data=d)
445     >>> b.tostring()[:17]
446     '\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10'
447     >>> b.tostring()[17:]
448     '\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !'
449     >>> run0 = d['runs'].pop(0)
450     >>> b = experiment.pack(data=d)
451     >>> b[:17]
452     '\x00\x01\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f '
453     >>> b[17:]
454     '!\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
455
456     If you set ``count=0``, the field is ignored.
457
458     >>> experiment2 = Structure('experiment', fields=[
459     ...     version, Field('f', 'ignored', count=0), runs], byte_order='>')
460     >>> experiment2.format
461     '>HIhhhhhhIhhhhhh'
462     >>> d = experiment2.unpack(b)
463     >>> pprint(d)
464     {'ignored': array([], dtype=float64),
465      'runs': [{'data': array([[5655, 6169, 6683],
466            [7197, 7711, 8225]]),
467                'time': 303240213},
468               {'data': array([[0, 0, 0],
469            [0, 0, 0]]), 'time': 0}],
470      'version': 1}
471     >>> del d['ignored']
472     >>> b2 = experiment2.pack(d)
473     >>> b2 == b
474     True
475     """
476     _byte_order_symbols = '@=<>!'
477
478     def __init__(self, name, fields, byte_order='@'):
479         # '=' for native byte order, standard size and alignment
480         # See http://docs.python.org/library/struct for details
481         self.name = name
482         self.fields = fields
483         self.byte_order = byte_order
484         self.setup()
485
486     def __str__(self):
487         return self.name
488
489     def __repr__(self):
490         return '<{} {} {}>'.format(
491             self.__class__.__name__, self.name, id(self))
492
493     def setup(self):
494         """Setup any dynamic properties of a structure.
495
496         Use this method to recalculate dynamic properities after
497         changing the basic properties set during initialization.
498         """
499         _LOG.debug('setup {!r}'.format(self))
500         self.set_byte_order(self.byte_order)
501         self.get_format()
502
503     def set_byte_order(self, byte_order):
504         """Allow changing the format byte_order on the fly.
505         """
506         _LOG.debug('set byte order for {!r} to {}'.format(self, byte_order))
507         self.byte_order = byte_order
508         for field in self.fields:
509             if isinstance(field.format, Structure):
510                 field.format.set_byte_order(byte_order)
511
512     def get_format(self):
513         format = self.byte_order + ''.join(self.sub_format())
514         # P format only allowed for native byte ordering
515         # Convert P to I for ILP32 compatibility when running on a LP64.
516         format = format.replace('P', 'I')
517         try:
518             super(Structure, self).__init__(format=format)
519         except _struct.error as e:
520             raise ValueError((e, format))
521         return format
522
523     def sub_format(self):
524         _LOG.debug('calculate sub-format for {!r}'.format(self))
525         for field in self.fields:
526             if isinstance(field.format, Structure):
527                 field_format = list(
528                     field.format.sub_format()) * field.item_count
529             else:
530                 field_format = [field.format]*field.item_count
531             for fmt in field_format:
532                 yield fmt
533
534     def _pack_item(self, item=None):
535         """Linearize a single count of the structure's data to a flat iterable
536         """
537         if item is None:
538             item = {}
539         for f in self.fields:
540             try:
541                 data = item[f.name]
542             except TypeError:
543                 raise ValueError((f.name, item))
544             except KeyError:
545                 data = None
546             for arg in f.pack_data(data):
547                 yield arg
548
549     def _unpack_item(self, args):
550         """Inverse of ._unpack_item"""
551         data = {}
552         iterator = iter(args)
553         for f in self.fields:
554             try:
555                 items = [iterator.next() for i in range(f.arg_count)]
556             except StopIteration:
557                 raise ValueError('not enough data to unpack {}.{}'.format(
558                         self, f))
559             data[f.name] = f.unpack_data(items)
560         try:
561             iterator.next()
562         except StopIteration:
563             pass
564         else:
565             raise ValueError('too much data to unpack {}'.format(self))
566         return data
567
568     def pack(self, data):
569         args = list(self._pack_item(data))
570         try:
571             return super(Structure, self).pack(*args)
572         except:
573             raise ValueError(self.format)
574
575     def pack_into(self, buffer, offset=0, data={}):
576         args = list(self._pack_item(data))
577         return super(Structure, self).pack_into(
578             buffer, offset, *args)
579
580     def unpack(self, *args, **kwargs):
581         args = super(Structure, self).unpack(*args, **kwargs)
582         return self._unpack_item(args)
583
584     def unpack_from(self, buffer, offset=0, *args, **kwargs):
585         _LOG.debug(
586             'unpack {!r} for {!r} ({}, offset={}) with {} ({})'.format(
587                 buffer, self, len(buffer), offset, self.format, self.size))
588         args = super(Structure, self).unpack_from(
589             buffer, offset, *args, **kwargs)
590         return self._unpack_item(args)
591
592     def get_field(self, name):
593         return [f for f in self.fields if f.name == name][0]
594
595
596 class DebuggingStream (object):
597     def __init__(self, stream):
598         self.stream = stream
599
600     def read(self, size):
601         data = self.stream.read(size)
602         _LOG.debug('read {} from {}: ({}) {!r}'.format(
603                 size, self.stream, len(data), data))
604         return data
605
606
607 class DynamicStructure (Structure):
608     r"""Represent a C structure field with a dynamic definition.
609
610     Any dynamic fields have their ``.pre_pack`` called before any
611     structure packing is done.  ``.pre_unpack`` is called for a
612     particular field just before that field's ``.unpack_data`` call.
613     ``.post_unpack`` is called for a particular field just after
614     ``.unpack_data``.  If ``.post_unpack`` returns ``True``, the same
615     field is unpacked again.
616
617     Examples
618     --------
619
620     >>> from pprint import pprint
621
622     This allows you to define structures where some portion of the
623     global structure depends on earlier data.  For example, in the
624     quasi-C structure::
625
626         struct vector {
627           unsigned int length;
628           short data[length];
629         }
630
631     You can generate a Python version of this structure in two ways,
632     with a dynamic ``length``, or with a dynamic ``data``.  In both
633     cases, the required methods are the same, the only difference is
634     where you attach them.
635
636     >>> def packer(self, parents, data):
637     ...     vector_structure = parents[-1]
638     ...     vector_data = self._get_structure_data(
639     ...         parents, data, vector_structure)
640     ...     length = len(vector_data['data'])
641     ...     vector_data['length'] = length
642     ...     data_field = vector_structure.get_field('data')
643     ...     data_field.count = length
644     ...     data_field.setup()
645     >>> def unpacker(self, parents, data):
646     ...     vector_structure = parents[-1]
647     ...     vector_data = self._get_structure_data(
648     ...         parents, data, vector_structure)
649     ...     length = vector_data['length']
650     ...     data_field = vector_structure.get_field('data')
651     ...     data_field.count = length
652     ...     data_field.setup()
653
654     >>> class DynamicLengthField (DynamicField):
655     ...     def pre_pack(self, parents, data):
656     ...         packer(self, parents, data)
657     ...     def post_unpack(self, parents, data):
658     ...         unpacker(self, parents, data)
659     >>> dynamic_length_vector = DynamicStructure('vector',
660     ...     fields=[
661     ...         DynamicLengthField('I', 'length'),
662     ...         Field('h', 'data', count=0),
663     ...         ],
664     ...     byte_order='>')
665     >>> class DynamicDataField (DynamicField):
666     ...     def pre_pack(self, parents, data):
667     ...         packer(self, parents, data)
668     ...     def pre_unpack(self, parents, data):
669     ...         unpacker(self, parents, data)
670     >>> dynamic_data_vector = DynamicStructure('vector',
671     ...     fields=[
672     ...         Field('I', 'length'),
673     ...         DynamicDataField('h', 'data', count=0),
674     ...         ],
675     ...     byte_order='>')
676
677     >>> b = '\x00\x00\x00\x02\x01\x02\x03\x04'
678     >>> d = dynamic_length_vector.unpack(b)
679     >>> pprint(d)
680     {'data': array([258, 772]), 'length': 2}
681     >>> d = dynamic_data_vector.unpack(b)
682     >>> pprint(d)
683     {'data': array([258, 772]), 'length': 2}
684
685     >>> d['data'] = [1,2,3,4]
686     >>> dynamic_length_vector.pack(d)
687     '\x00\x00\x00\x04\x00\x01\x00\x02\x00\x03\x00\x04'
688     >>> dynamic_data_vector.pack(d)
689     '\x00\x00\x00\x04\x00\x01\x00\x02\x00\x03\x00\x04'
690
691     The implementation is a good deal more complicated than the one
692     for ``Structure``, because we must make multiple calls to
693     ``struct.Struct.unpack`` to unpack the data.
694     """
695     #def __init__(self, *args, **kwargs):
696     #     pass #self.parent = ..
697
698     def _pre_pack(self, parents=None, data=None):
699         if parents is None:
700             parents = [self]
701         else:
702             parents = parents + [self]
703         for f in self.fields:
704             if hasattr(f, 'pre_pack'):
705                 _LOG.debug('pre-pack {}'.format(f))
706                 f.pre_pack(parents=parents, data=data)
707             if isinstance(f.format, DynamicStructure):
708                 _LOG.debug('pre-pack {!r}'.format(f.format))
709                 f._pre_pack(parents=parents, data=data)
710
711     def pack(self, data):
712         self._pre_pack(data=data)
713         self.setup()
714         return super(DynamicStructure, self).pack(data)
715
716     def pack_into(self, buffer, offset=0, data={}):
717         self._pre_pack(data=data)
718         self.setup()
719         return super(DynamicStructure, self).pack_into(
720             buffer=buffer, offset=offset, data=data)
721
722     def unpack_stream(self, stream, parents=None, data=None, d=None):
723         # `d` is the working data directory
724         if data is None:
725             parents = [self]
726             data = d = {}
727             if _LOG.level <= _logging.DEBUG:
728                 stream = DebuggingStream(stream)
729         else:
730             parents = parents + [self]
731
732         for f in self.fields:
733             _LOG.debug('parsing {!r}.{} (count={}, item_count={})'.format(
734                     self, f, f.count, f.item_count))
735             if _LOG.level <= _logging.DEBUG:
736                 _LOG.debug('data:\n{}'.format(_pprint.pformat(data)))
737             if hasattr(f, 'pre_unpack'):
738                 _LOG.debug('pre-unpack {}'.format(f))
739                 f.pre_unpack(parents=parents, data=data)
740
741             if hasattr(f, 'unpack'):  # override default unpacking
742                 _LOG.debug('override unpack for {}'.format(f))
743                 d[f.name] = f.unpack(stream)
744                 continue
745
746             # setup for unpacking loop
747             if isinstance(f.format, Structure):
748                 f.format.set_byte_order(self.byte_order)
749                 f.setup()
750                 f.format.setup()
751                 if isinstance(f.format, DynamicStructure):
752                     if f.item_count == 1:
753                         # TODO, fix in case we *want* an array
754                         d[f.name] = {}
755                         f.format.unpack_stream(
756                             stream, parents=parents, data=data, d=d[f.name])
757                     else:
758                         d[f.name] = []
759                         for i in range(f.item_count):
760                             x = {}
761                             d[f.name].append(x)
762                             f.format.unpack_stream(
763                                 stream, parents=parents, data=data, d=x)
764                     if hasattr(f, 'post_unpack'):
765                         _LOG.debug('post-unpack {}'.format(f))
766                         repeat = f.post_unpack(parents=parents, data=data)
767                         if repeat:
768                             raise NotImplementedError(
769                                 'cannot repeat unpack for dynamic structures')
770                     continue
771             if isinstance(f.format, Structure):
772                 _LOG.debug('parsing {} bytes for {}'.format(
773                         f.format.size, f.format.format))
774                 bs = [stream.read(f.format.size) for i in range(f.item_count)]
775                 def unpack():
776                     f.format.set_byte_order(self.byte_order)
777                     f.setup()
778                     f.format.setup()
779                     x = [f.format.unpack_from(b) for b in bs]
780                     if len(x) == 1:  # TODO, fix in case we *want* an array
781                         x = x[0]
782                     return x
783             else:
784                 field_format = self.byte_order + f.format*f.item_count
785                 field_format = field_format.replace('P', 'I')
786                 try:
787                     size = _struct.calcsize(field_format)
788                 except _struct.error as e:
789                     _LOG.error(e)
790                     _LOG.error('{}.{}: {}'.format(self, f, field_format))
791                     raise
792                 _LOG.debug('parsing {} bytes for preliminary {}'.format(
793                         size, field_format))
794                 raw = stream.read(size)
795                 if len(raw) < size:
796                     raise ValueError(
797                         'not enough data to unpack {}.{} ({} < {})'.format(
798                             self, f, len(raw), size))
799                 def unpack():
800                     field_format = self.byte_order + f.format*f.item_count
801                     field_format = field_format.replace('P', 'I')
802                     _LOG.debug('parse previous bytes using {}'.format(
803                             field_format))
804                     struct = _struct.Struct(field_format)
805                     items = struct.unpack(raw)
806                     return f.unpack_data(items)
807
808             # unpacking loop
809             repeat = True
810             while repeat:
811                 d[f.name] = unpack()
812                 if hasattr(f, 'post_unpack'):
813                     _LOG.debug('post-unpack {}'.format(f))
814                     repeat = f.post_unpack(parents=parents, data=data)
815                 else:
816                     repeat = False
817                 if repeat:
818                     _LOG.debug('repeat unpack for {}'.format(f))
819
820         return data
821
822     def unpack(self, string):
823         stream = _io.BytesIO(string)
824         return self.unpack_stream(stream)
825
826     def unpack_from(self, buffer, offset=0, *args, **kwargs):
827         args = super(Structure, self).unpack_from(
828             buffer, offset, *args, **kwargs)
829         return self._unpack_item(args)