[plum] Tutorial: Dimensioned Array Structure Member¶
This tutorial shows how to use the dimmed_member()
member definition
within a structure definition to associate a dimensions member and an array
member to one another. During unpack operations, the association causes the
the unpacked dimension member to control the unpacking of the array member.
The association allows the dimension member to be left unspecified and
have it automatically derived from the array value.
The examples shown on this page require the following setup:
>>> from plum.array import ArrayX
>>> from plum.bigendian import uint8
>>> from plum.structure import dimmed_member, member, Structure
One-Dimensional Arrays¶
To demonstrate, let’s start by examining the example below. The example shows
the simplest use case, an array with a single dimension. Since its a single
dimension, let’s choose the array dimension member name to be count
with
the format as uint8
. After the type annotation, use the Member
class to define a member to hold the dimensions giving it an integer
transform for the fmt
.
For the associated array member, use the dimmed_member()
function to
associate it to the dims member and specify an undimensioned array transform
for the array member’s fmt
.
The bookend
member exists in the example to show that when the structure
is unpacked, the dimensions count
member restricts the array and leaves the
last byte for the bookend
:
>>> array = ArrayX(fmt=uint8) # greedy array
>>>
>>> class Struct1(Structure):
... count: int = member(fmt=uint8, compute=True)
... array: list = dimmed_member(dims=count, fmt=array)
... bookend: int = member(fmt=uint8)
...
>>> Struct1.unpack(b'\x02\x01\x02\x99').dump()
+--------+---------+-------+-------+---------------------+
| Offset | Access | Value | Bytes | Format |
+--------+---------+-------+-------+---------------------+
| | | | | Struct1 (Structure) |
| 0 | count | 2 | 02 | uint8 |
| | array | | | List[int] |
| 1 | [0] | 1 | 01 | uint8 |
| 2 | [1] | 2 | 02 | uint8 |
| 3 | bookend | 153 | 99 | uint8 |
+--------+---------+-------+-------+---------------------+
The association also allows the size member to be left unspecified when instantiating the structure. In that case, the size member takes on the length of the array:
>>> buffer, dump = Struct1(array=[1, 2], bookend=0x99).ipack_and_dump()
>>> buffer.hex()
'02010299'
>>> print(dump)
+--------+---------+-------+-------+---------------------+
| Offset | Access | Value | Bytes | Format |
+--------+---------+-------+-------+---------------------+
| | | | | Struct1 (Structure) |
| 0 | count | 2 | 02 | uint8 |
| | array | | | List[int] |
| 1 | [0] | 1 | 01 | uint8 |
| 2 | [1] | 2 | 02 | uint8 |
| 3 | bookend | 153 | 99 | uint8 |
+--------+---------+-------+-------+---------------------+
The association causes the count
member to be updated when setting
a new array
value using the array
attribute:
>>> struct1 = Struct1(array=[1, 2], bookend=123)
>>> struct1.count
2
>>>
>>> struct1.array = [1, 2, 3, 4, 5]
>>> struct1.count
5
Multi-Dimensional Arrays¶
Implementing multiple dimensions only adds slightly more complexity.
Instead of supplying a simple integer transform for the dimensions member’s
fmt
, create a single dimension array subclass to hold the dimensions
(e.g. for a two dimensional array, specify dims=(2,)
, for three
dimensions dims=(3,)
, etc). Everything else remains the same:
>>> dims = ArrayX(fmt=uint8, dims=(2,))
>>>
>>> class Struct2(Structure):
... dims: list = member(fmt=dims, compute=True)
... array: list = dimmed_member(dims=dims, fmt=array)
...
>>> Struct2(array=[[1, 2], [3, 4]]).dump()
+--------+---------+-------+-------+---------------------+
| Offset | Access | Value | Bytes | Format |
+--------+---------+-------+-------+---------------------+
| | | | | Struct2 (Structure) |
| | dims | | | List[int] |
| 0 | [0] | 2 | 02 | uint8 |
| 1 | [1] | 2 | 02 | uint8 |
| | array | | | List[int] |
| | [0] | | | |
| 2 | [0] | 1 | 01 | uint8 |
| 3 | [1] | 2 | 02 | uint8 |
| | [1] | | | |
| 4 | [0] | 3 | 03 | uint8 |
| 5 | [1] | 4 | 04 | uint8 |
+--------+---------+-------+-------+---------------------+
Ignoring Members¶
Both member()
and dimmed_member()
functions support marking the
member to be ignored during comparisons:
>>> class Struct1Ignore(Structure):
... count: int = member(fmt=uint8, compute=True, ignore=True)
... array: list = dimmed_member(dims=count, fmt=array, ignore=True)
... bookend: int = member(fmt=uint8)
...
>>> struct1 = Struct1Ignore.unpack(b'\x02\x01\x02\x99')
>>> struct1.dump()
+--------+---------+-------+-------+---------------------------+
| Offset | Access | Value | Bytes | Format |
+--------+---------+-------+-------+---------------------------+
| | | | | Struct1Ignore (Structure) |
| 0 | count | 2 | 02 | uint8 |
| | array | | | List[int] |
| 1 | [0] | 1 | 01 | uint8 |
| 2 | [1] | 2 | 02 | uint8 |
| 3 | bookend | 153 | 99 | uint8 |
+--------+---------+-------+-------+---------------------------+
>>>
>>> struct1 == Struct1Ignore(array=[], bookend=0x99)
True
Defaulting Array Member¶
Specifying a default value for the array in its member definition allows the structure to be instantiated or packed without a value for the array member:
>>> class Struct1(Structure):
... count: int = member(fmt=uint8, compute=True)
... array: list = dimmed_member(dims=count, fmt=array, default=(0, 1))
... bookend: int = member(fmt=uint8)
...
>>> struct = Struct1(bookend=0x99)
>>> struct.dump()
+--------+---------+-------+-------+---------------------+
| Offset | Access | Value | Bytes | Format |
+--------+---------+-------+-------+---------------------+
| | | | | Struct1 (Structure) |
| 0 | count | 2 | 02 | uint8 |
| | array | | | List[int] |
| 1 | [0] | 0 | 00 | uint8 |
| 2 | [1] | 1 | 01 | uint8 |
| 3 | bookend | 153 | 99 | uint8 |
+--------+---------+-------+-------+---------------------+
>>>
>>> struct.ipack()
b'\x02\x00\x01\x99'