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