diff --git a/packtype/types/assembly.py b/packtype/types/assembly.py index 684eb6b..3d81d64 100644 --- a/packtype/types/assembly.py +++ b/packtype/types/assembly.py @@ -47,7 +47,7 @@ def _pt_lookup(self, field: type[Base] | Base) -> str: @property @functools.lru_cache # noqa: B019 - def _pt_fields(self) -> dict: + def _pt_fields(self) -> dict[Base, str]: return {getattr(self, x): x for x in self._PT_DEF.keys()} @@ -136,8 +136,10 @@ def __str__(self) -> str: for fname in self._PT_DEF.keys(): finst = getattr(self, fname) lsb, msb = self._PT_RANGES[fname] + width = msb - lsb + 1 lines.append( - f" - [{msb:{max_bits}}:{lsb:{max_bits}}] {fname:{max_name}} = 0x{int(finst):X}" + f" |- [{msb:{max_bits}}:{lsb:{max_bits}}] {fname:{max_name}} " + f"= 0x{int(finst):0{(width + 3) // 4}X}" ) return "\n".join(lines) diff --git a/packtype/types/scalar.py b/packtype/types/scalar.py index 1d16b65..02f8fce 100644 --- a/packtype/types/scalar.py +++ b/packtype/types/scalar.py @@ -15,7 +15,13 @@ def _pt_name(cls) -> str: if cls._PT_ATTACHED_TO is not None: return cls._PT_ATTACHED_TO._pt_lookup(cls) else: - return NumericPrimitive._pt_name(cls) + return f"{['Unsigned', 'Signed'][cls._PT_SIGNED]} Scalar[{cls._PT_WIDTH}]" + + def __str__(self) -> str: + return f"{type(self)._pt_name()}: 0x{int(self):0{(self._PT_WIDTH + 3) // 4}X}" + + def __repr__(self): + return str(self) class Scalar(NumericPrimitive): diff --git a/packtype/types/union.py b/packtype/types/union.py index 807dac1..a0da4bc 100644 --- a/packtype/types/union.py +++ b/packtype/types/union.py @@ -3,6 +3,7 @@ # import functools +import textwrap from .array import ArraySpec from .assembly import Assembly @@ -30,6 +31,19 @@ def __init__( # NOTE: Fields are not constructed at this point, instead they are filled # in lazily as they are requested by the consumer + def __str__(self) -> str: + parts = {} + for finst, fname in self._pt_fields.items(): + parts[fname] = str(finst) + max_prefix = max(map(len, parts.keys())) + return f"{type(self).__name__}: 0x{int(self):X} (union):\n" + "\n".join( + (f" |- {p:{max_prefix}s} -> " + textwrap.indent(v, " " * (max_prefix + 8)).lstrip()) + for p, v in parts.items() + ) + + def __repr__(self) -> str: + return self.__str__() + def __getattribute__(self, fname: str): # Attempt to resolve the attribute from existing properties try: diff --git a/tests/pysyntax/test_struct.py b/tests/pysyntax/test_struct.py index 35aba2e..6fb3034 100644 --- a/tests/pysyntax/test_struct.py +++ b/tests/pysyntax/test_struct.py @@ -94,10 +94,18 @@ class TestStruct: cd: Scalar[3] ef: Scalar[9] - inst = TestStruct._pt_unpack((39 << 15) | (5 << 12) | 123) - assert inst.ab.value == 123 - assert inst.cd.value == 5 - assert inst.ef.value == 39 + value = (39 << 15) | (5 << 12) | 123 + inst = TestStruct._pt_unpack(value) + assert inst.ab.value == (ab_value := 123) + assert inst.cd.value == (cd_value := 5) + assert inst.ef.value == (ef_value := 39) + + assert str(inst) == ( + f"TestStruct: 0x{value:06X}\n" + f" |- [11: 0] ab = 0x{ab_value:03X}\n" + f" |- [14:12] cd = 0x{cd_value:01X}\n" + f" |- [23:15] ef = 0x{ef_value:03X}" + ) def test_struct_unpacking_from_msb(): diff --git a/tests/pysyntax/test_union.py b/tests/pysyntax/test_union.py index 08b4d8d..145df90 100644 --- a/tests/pysyntax/test_union.py +++ b/tests/pysyntax/test_union.py @@ -140,12 +140,27 @@ class Packet: raw: Scalar[32] header: Header - inst = Packet._pt_unpack((0x7 << 28) | (0x5 << 24) | (0x75 << 16) | 0x1234) - assert int(inst.raw) == (0x7 << 28) | (0x5 << 24) | (0x75 << 16) | 0x1234 - assert int(inst.header.address) == 0x1234 - assert int(inst.header.length) == 0x75 - assert int(inst.header.mode) == 0x5 - assert int(inst.header.flags) == 0x7 + hdr_addr_val = 0x1234 + hdr_len_val = 0x75 + hdr_mode_val = 0x5 + hdr_flags_val = 0x7 + value = (hdr_flags_val << 28) | (hdr_mode_val << 24) | (hdr_len_val << 16) | hdr_addr_val + inst = Packet._pt_unpack(value) + assert int(inst.raw) == value + assert int(inst.header.address) == hdr_addr_val + assert int(inst.header.length) == hdr_len_val + assert int(inst.header.mode) == hdr_mode_val + assert int(inst.header.flags) == hdr_flags_val + + assert str(inst) == ( + f"Packet: 0x{value:08X} (union):\n" + f" |- raw -> Unsigned Scalar[32]: 0x{value:08X}\n" + f" |- header -> Header: 0x{value:08X}\n" + f" |- [15: 0] address = 0x{hdr_addr_val:04X}\n" + f" |- [23:16] length = 0x{hdr_len_val:02X}\n" + f" |- [27:24] mode = 0x{hdr_mode_val:01X}\n" + f" |- [31:28] flags = 0x{hdr_flags_val:01X}" + ) def test_union_bad_widths():