Skip to content

Commit 5bafce7

Browse files
committed
style: remove some duplicate code
1 parent c7da813 commit 5bafce7

6 files changed

Lines changed: 37 additions & 48 deletions

File tree

libdestruct/backing/fake_resolver.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88

99
from libdestruct.backing.resolver import Resolver
1010

11+
_PAGE_SIZE = 0x1000
12+
_ZERO_PAGE = b"\x00" * _PAGE_SIZE
13+
1114

1215
class FakeResolver(Resolver):
1316
"""A class that can resolve elements in a simulated memory storage."""
@@ -53,11 +56,11 @@ def resolve(self: FakeResolver, size: int, _: int) -> bytes:
5356
result = b""
5457

5558
while size:
56-
page = self.memory.get(page_address, b"\x00" * 0x1000)
57-
page_size = min(size, 0x1000 - page_offset)
59+
page = self.memory.get(page_address, _ZERO_PAGE)
60+
page_size = min(size, _PAGE_SIZE - page_offset)
5861
result += page[page_offset : page_offset + page_size]
5962
size -= page_size
60-
page_address += 0x1000
63+
page_address += _PAGE_SIZE
6164
page_offset = 0
6265

6366
return result
@@ -70,11 +73,11 @@ def modify(self: FakeResolver, size: int, _: int, value: bytes) -> None:
7073
page_offset = address & 0xFFF
7174

7275
while size:
73-
page = self.memory.get(page_address, b"\x00" * 0x1000)
74-
page_size = min(size, 0x1000 - page_offset)
76+
page = self.memory.get(page_address, _ZERO_PAGE)
77+
page_size = min(size, _PAGE_SIZE - page_offset)
7578
page = page[:page_offset] + value[:page_size] + page[page_offset + page_size :]
7679
self.memory[page_address] = page
7780
size -= page_size
7881
value = value[page_size:]
79-
page_address += 0x1000
82+
page_address += _PAGE_SIZE
8083
page_offset = 0

libdestruct/c/c_integer_types.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,18 @@ class c_ulong(_c_integer):
127127

128128
signed: bool = False
129129
"""Whether the long is signed."""
130+
131+
132+
_SIGNED_INTEGER_BY_SIZE: dict[int, type[_c_integer]] = {
133+
1: c_char,
134+
2: c_short,
135+
4: c_int,
136+
8: c_long,
137+
}
138+
139+
140+
def signed_integer_for_size(size: int) -> type[_c_integer]:
141+
"""Return the signed C integer type for the given byte size (1, 2, 4, or 8)."""
142+
if size not in _SIGNED_INTEGER_BY_SIZE:
143+
raise ValueError("The size of the field must be 1, 2, 4, or 8 bytes.")
144+
return _SIGNED_INTEGER_BY_SIZE[size]

libdestruct/c/c_str.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@ def count(self: c_str) -> int:
2626

2727
def get(self: c_str, index: int = -1) -> bytes:
2828
"""Return the character at the given index."""
29-
if (index != -1 and index < 0) or index >= self.count():
29+
length = self.count()
30+
if (index != -1 and index < 0) or index >= length:
3031
raise IndexError("String index out of range.")
3132

3233
if index == -1:
33-
return self.resolver.resolve(self.count(), 0)
34+
return self.resolver.resolve(length, 0)
3435

3536
return bytes([self.resolver.resolve(index + 1, 0)[-1]])
3637

libdestruct/common/enum/int_enum_field.py

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from typing import TYPE_CHECKING
1010

11-
from libdestruct.c.c_integer_types import c_char, c_int, c_long, c_short
11+
from libdestruct.c.c_integer_types import signed_integer_for_size
1212
from libdestruct.common.enum.enum import enum
1313
from libdestruct.common.enum.enum_field import EnumField
1414

@@ -43,20 +43,7 @@ def __init__(
4343
self.backing_type = backing_type
4444
return
4545

46-
if not 0 < size <= 8:
47-
raise ValueError("The size of the field must be between 1 and 8 bytes.")
48-
49-
match size:
50-
case 1:
51-
self.backing_type = c_char
52-
case 2:
53-
self.backing_type = c_short
54-
case 4:
55-
self.backing_type = c_int
56-
case 8:
57-
self.backing_type = c_long
58-
case _:
59-
raise ValueError("The size of the field must be a power of 2.")
46+
self.backing_type = signed_integer_for_size(size)
6047

6148
def inflate(self: IntEnumField, resolver: Resolver) -> int:
6249
"""Inflate the field.

libdestruct/common/flags/int_flag_field.py

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from typing import TYPE_CHECKING
1010

11-
from libdestruct.c.c_integer_types import c_char, c_int, c_long, c_short
11+
from libdestruct.c.c_integer_types import signed_integer_for_size
1212
from libdestruct.common.flags.flags import flags
1313
from libdestruct.common.flags.flags_field import FlagsField
1414

@@ -36,20 +36,7 @@ def __init__(
3636
self.backing_type = backing_type
3737
return
3838

39-
if not 0 < size <= 8:
40-
raise ValueError("The size of the field must be between 1 and 8 bytes.")
41-
42-
match size:
43-
case 1:
44-
self.backing_type = c_char
45-
case 2:
46-
self.backing_type = c_short
47-
case 4:
48-
self.backing_type = c_int
49-
case 8:
50-
self.backing_type = c_long
51-
case _:
52-
raise ValueError("The size of the field must be a power of 2.")
39+
self.backing_type = signed_integer_for_size(size)
5340

5441
def inflate(self: IntFlagField, resolver: Resolver) -> flags:
5542
"""Inflate the field."""

libdestruct/common/struct/struct_impl.py

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,13 @@ class struct_impl(struct):
3939

4040
def __init__(self: struct_impl, resolver: Resolver | None = None, **kwargs: ...) -> None:
4141
"""Initialize the struct implementation."""
42-
# If we have kwargs and the resolver is None, we provide a fake resolver
4342
if kwargs and resolver is None:
4443
resolver = FakeResolver()
4544

4645
if not isinstance(resolver, Resolver):
4746
raise TypeError("The resolver must be a Resolver instance.")
4847

49-
# struct overrides the __init__ method, so we need to call the parent class __init__ method
48+
# struct.__init__ raises by design; bypass it and call obj.__init__ directly.
5049
obj.__init__(self, resolver)
5150

5251
object.__setattr__(self, "_struct_name", self.__class__.__name__)
@@ -81,9 +80,7 @@ def __setattr__(self: struct_impl, name: str, value: object) -> None:
8180
object.__setattr__(self, name, value)
8281

8382
def __new__(cls: struct_impl, *args: ..., **kwargs: ...) -> Self:
84-
"""Create a new struct."""
85-
# Skip the __new__ method of the parent class
86-
# struct_impl -> struct -> obj becomes struct_impl -> obj
83+
"""Create a new struct, bypassing struct.__new__ which is for the user-facing factory."""
8784
return obj.__new__(cls)
8885

8986
def _inflate_struct_attributes(
@@ -152,13 +149,12 @@ def _inflate_struct_attributes(
152149
max_alignment = max(max_alignment, aligned)
153150
current_offset = _align_offset(current_offset, max_alignment)
154151

155-
# For VLA structs, size must be computed dynamically since the count
156-
# can change at runtime. Detect VLA by duck-typing: vla_impl has a
157-
# _count_member attribute that plain array_impl does not.
152+
# VLA detection uses duck-typing on _count_member to avoid a circular
153+
# import between struct_impl and vla_impl (vla_impl extends array_impl,
154+
# which imports struct).
158155
members = object.__getattribute__(self, "_members")
159-
last_member = list(members.values())[-1] if members else None
156+
last_name, last_member = next(reversed(members.items()), (None, None))
160157
if last_member is not None and hasattr(last_member, "_count_member"):
161-
last_name = list(members.keys())[-1]
162158
object.__setattr__(self, "_vla_fixed_offset", self._member_offsets[last_name])
163159
else:
164160
object.__setattr__(self, "size", current_offset)

0 commit comments

Comments
 (0)