From 5c589d1bc3dfc5d9eaebb81badf1831f63d68a5b Mon Sep 17 00:00:00 2001 From: Peter Birch Date: Thu, 18 Sep 2025 09:26:30 +0100 Subject: [PATCH 1/3] Adding __repr__ for unions --- packtype/types/assembly.py | 5 +++-- packtype/types/scalar.py | 8 +++++++- packtype/types/union.py | 14 ++++++++++++++ tests/pysyntax/test_struct.py | 16 ++++++++++++---- tests/pysyntax/test_union.py | 27 +++++++++++++++++++++------ 5 files changed, 57 insertions(+), 13 deletions(-) diff --git a/packtype/types/assembly.py b/packtype/types/assembly.py index 684eb6b..12eb206 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[str, Base]: return {getattr(self, x): x for x in self._PT_DEF.keys()} @@ -136,8 +136,9 @@ 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}} = 0x{int(finst):0{width // 4}X}" ) return "\n".join(lines) diff --git a/packtype/types/scalar.py b/packtype/types/scalar.py index 1d16b65..c4cea64 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 // 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..f6f44c2 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..268fca6 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:02X}" + ) 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(): From 73b33d85de1856bfa0efd32cdf668233f5308e16 Mon Sep 17 00:00:00 2001 From: Peter Birch Date: Thu, 18 Sep 2025 09:28:45 +0100 Subject: [PATCH 2/3] Lint fixes --- packtype/types/assembly.py | 3 ++- packtype/types/union.py | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packtype/types/assembly.py b/packtype/types/assembly.py index 12eb206..349825c 100644 --- a/packtype/types/assembly.py +++ b/packtype/types/assembly.py @@ -138,7 +138,8 @@ def __str__(self) -> str: 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):0{width // 4}X}" + f" |- [{msb:{max_bits}}:{lsb:{max_bits}}] {fname:{max_name}} " + f"= 0x{int(finst):0{width // 4}X}" ) return "\n".join(lines) diff --git a/packtype/types/union.py b/packtype/types/union.py index f6f44c2..a0da4bc 100644 --- a/packtype/types/union.py +++ b/packtype/types/union.py @@ -36,9 +36,9 @@ def __str__(self) -> str: 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()) + 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: From f377acda19fb74d26a2d7cca47d849f929ff2b5e Mon Sep 17 00:00:00 2001 From: Peter Birch Date: Thu, 18 Sep 2025 09:30:03 +0100 Subject: [PATCH 3/3] Copilot review suggestions --- packtype/types/assembly.py | 4 ++-- packtype/types/scalar.py | 2 +- tests/pysyntax/test_struct.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packtype/types/assembly.py b/packtype/types/assembly.py index 349825c..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[str, Base]: + def _pt_fields(self) -> dict[Base, str]: return {getattr(self, x): x for x in self._PT_DEF.keys()} @@ -139,7 +139,7 @@ def __str__(self) -> str: width = msb - lsb + 1 lines.append( f" |- [{msb:{max_bits}}:{lsb:{max_bits}}] {fname:{max_name}} " - f"= 0x{int(finst):0{width // 4}X}" + 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 c4cea64..02f8fce 100644 --- a/packtype/types/scalar.py +++ b/packtype/types/scalar.py @@ -18,7 +18,7 @@ def _pt_name(cls) -> str: 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 // 4}X}" + return f"{type(self)._pt_name()}: 0x{int(self):0{(self._PT_WIDTH + 3) // 4}X}" def __repr__(self): return str(self) diff --git a/tests/pysyntax/test_struct.py b/tests/pysyntax/test_struct.py index 268fca6..6fb3034 100644 --- a/tests/pysyntax/test_struct.py +++ b/tests/pysyntax/test_struct.py @@ -104,7 +104,7 @@ class TestStruct: 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:02X}" + f" |- [23:15] ef = 0x{ef_value:03X}" )