[plum.int] Tutorial: Creating and Using Integer Enumeration Types¶
The plum.int.enum
subpackage provides a plum type class for packing
and unpacking integer enumeration members. Compared to normal integers,
enumerations offer better representations within the bytes summary dumps as
well as the ability to restrict a value to a specific set of valid values.
This tutorial teaches how to create and use integer enumeration plum types.
Creating an Integer Enumeration Type¶
To create a plum integer enumeration type, subclass the Enum
class (similar to
subclassing IntEnum
from the enum
standard library module).
Use arguments to control the number of bytes, byte order, and sign:
>>> from plum.int.enum import Enum
>>>
>>> class Register(Enum, nbytes=2, signed=False, byteorder='big'):
... PC = 0
... SP = 1
... R0 = 2
... R1 = 3
...
Besides having plum behaviors (discussed in next sections), the enumeration behaves
as subclasses of enum.IntEnum
do. For example:
>>> Register.PC == 0
True
>>> Register(0)
<Register.PC: 0>
>>> Register['PC']
<Register.PC: 0>
Unpacking Bytes¶
plum
integer enumeration types convert bytes into an enumeration member instance
when used with the various plum unpacking mechanisms. For example (using the
enumeration from the previous section):
>>> from plum import unpack, Buffer
>>>
>>> # utility function
>>> unpack(Register, b'\x00\x00')
<Register.PC: 0>
>>>
>>> # class method
>>> Register.unpack(b'\x00\x01')
<Register.SP: 1>
>>>
>>> # bytes buffer
>>> with Buffer(b'\x00\x00\x00\x01') as buffer:
... a = buffer.unpack(Register)
... b = buffer.unpack(Register)
...
>>> a, b
(<Register.PC: 0>, <Register.SP: 1>)
Packing Bytes¶
plum
integer enumeration types convert integers (or anything int-like) into bytes
when used with the various plum packing mechanisms. For example (using the
enumeration from the previous sections):
>>> from plum import pack
>>>
>>> # utility function
>>> pack(Register, Register.PC)
bytearray(b'\x00\x00')
>>>
>>> # class method
>>> Register.pack(0)
bytearray(b'\x00\x00')
>>>
>>> # instance method
>>> Register.PC.pack()
bytearray(b'\x00\x00')
Strict vs. Tolerant¶
By default, plum
enumeration types require valid values when packing,
otherwise the operation raises an exception. For example (using the
enumeration from the previous sections):
>>> Register.pack(0xff)
Traceback (most recent call last):
...
plum._exceptions.PackError:
<BLANKLINE>
+--------+-------+-------+----------+
| Offset | Value | Bytes | Type |
+--------+-------+-------+----------+
| | 255 | | Register |
+--------+-------+-------+----------+
<BLANKLINE>
ValueError occurred during pack operation:
<BLANKLINE>
255 is not a valid Register
Similarly, plum
enumeration types require valid values when unpacking:
>>> Register.unpack(b'\x00\xff')
Traceback (most recent call last):
...
plum._exceptions.UnpackError:
<BLANKLINE>
+--------+-------+-------+----------+
| Offset | Value | Bytes | Type |
+--------+-------+-------+----------+
| 0 | | 00 ff | Register |
+--------+-------+-------+----------+
<BLANKLINE>
ValueError occurred during unpack operation:
<BLANKLINE>
255 is not a valid Register
>>>
To tolerate values not defined within the enumeration, set the strict
argument to False
when creating the enumeration type. For example:
>>> from plum.int.enum import Enum
>>>
>>> class Register(Enum, nbytes=2, signed=False, byteorder='big', strict=False):
... PC = 0
... SP = 1
... R0 = 2
... R1 = 3
...
>>> Register.pack(0xff)
bytearray(b'\x00\xff')
>>> Register.unpack(b'\x00\xff')
255
Inheriting Enumeration Members¶
To create a plum integer enumeration type with members inherited from another
enumeration, subclass the Enum
class and pass the enumeration with
the members to copy using the source
argument. For example:
>>> from enum import IntEnum
>>> from plum.int.enum import Enum
>>>
>>> class Pet(IntEnum):
... """Normal enumeration without pack/unpack properties."""
... CAT = 0
... DOG = 1
...
>>>
>>> class Pet8(Enum, nbytes=1, source=Pet):
... """Plum enumeration with pack/unpack properties"""
... LIZARD = 2 # optional additional member
...
The resulting plum
enumeration contains members from the source plus
members defined in the class body:
>>> for member in Pet8:
... print(repr(member))
...
<Pet8.CAT: 0>
<Pet8.DOG: 1>
<Pet8.LIZARD: 2>
>>>
>>> Pet8.DOG.dump()
+--------+----------+-------+------+
| Offset | Value | Bytes | Type |
+--------+----------+-------+------+
| 0 | Pet8.DOG | 01 | Pet8 |
+--------+----------+-------+------+