Coverage for dataclasses_struct / field.py: 99%
93 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-30 22:31 +1200
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-30 22:31 +1200
1import abc
2import ctypes
3from typing import Any, ClassVar, Generic, Literal, TypeVar, Union
5T = TypeVar("T")
8class Field(abc.ABC, Generic[T]):
9 is_native: bool = True
10 is_std: bool = True
11 field_type: Union[type[T], tuple[type[T], ...]]
13 @abc.abstractmethod
14 def format(self) -> str: ...
16 def validate_default(self, val: T) -> None:
17 pass
19 def __repr__(self) -> str:
20 return f"{type(self).__name__}"
23class BoolField(Field[bool]):
24 field_type = bool
26 def format(self) -> str:
27 return "?"
30class CharField(Field[bytes]):
31 field_type = bytes
33 def format(self) -> str:
34 return "c"
36 def validate_default(self, val: bytes) -> None:
37 if len(val) != 1:
38 raise ValueError("value must be a single byte")
41class IntField(Field[int]):
42 field_type = int
44 def __init__(
45 self,
46 fmt: str,
47 signed: bool,
48 size: int,
49 ):
50 if signed and fmt.isupper():
51 raise ValueError(
52 "signed integer should have lowercase format string"
53 )
55 self.signed = signed
56 self.size = size
57 self._format = fmt
59 nbits = self.size * 8
60 if signed:
61 exp = 1 << (nbits - 1)
62 self.min_ = -exp
63 self.max_ = exp - 1
64 else:
65 self.min_ = 0
66 self.max_ = (1 << nbits) - 1
68 def format(self) -> str:
69 return self._format
71 def validate_default(self, val: int) -> None:
72 if not (self.min_ <= val <= self.max_):
73 sign = "signed" if self.signed else "unsigned"
74 n = self.size * 8
75 raise ValueError(f"value out of range for {n}-bit {sign} integer")
77 def __repr__(self) -> str:
78 sign = "signed" if self.signed else "unsigned"
79 return f"{super().__repr__()}({sign}, {self.size * 8}-bit)"
82class StdIntField(IntField):
83 is_native = False
84 _unsigned_formats: ClassVar = {
85 1: "B",
86 2: "H",
87 4: "I",
88 8: "Q",
89 }
91 def __init__(self, signed: bool, size: Literal[1, 2, 4, 8]):
92 fmt = self._unsigned_formats[size]
93 if signed:
94 fmt = fmt.lower()
95 super().__init__(fmt, signed, size)
98class SignedStdIntField(StdIntField):
99 def __init__(self, size: Literal[1, 2, 4, 8]):
100 super().__init__(True, size)
103class UnsignedStdIntField(StdIntField):
104 def __init__(self, size: Literal[1, 2, 4, 8]):
105 super().__init__(False, size)
108class FloatingPointField(Field[float]):
109 field_type = (int, float)
111 def __init__(self, format: str):
112 self._format = format
114 def format(self) -> str:
115 return self._format
118class NativeIntField(IntField):
119 is_std = False
121 def __init__(self, fmt: str, ctype_name: str):
122 size = ctypes.sizeof(getattr(ctypes, f"c_{ctype_name}"))
123 signed = not ctype_name.startswith("u")
124 super().__init__(fmt, signed, size)
127class SizeField(IntField):
128 is_std = False
130 def __init__(self, signed: bool):
131 fmt = "n" if signed else "N"
132 size = ctypes.sizeof(ctypes.c_ssize_t if signed else ctypes.c_size_t)
133 super().__init__(fmt, signed, size)
135 def validate_default(self, val: int) -> None:
136 if not (self.min_ <= val <= self.max_):
137 sign = "signed" if self.signed else "unsigned"
138 raise ValueError(f"value out of range for {sign} size type")
141class PointerField(IntField):
142 is_std = False
144 def __init__(self):
145 super().__init__("P", False, ctypes.sizeof(ctypes.c_void_p))
147 def format(self) -> str:
148 return "P"
150 def validate_default(self, val: int) -> None:
151 if not (self.min_ <= val <= self.max_):
152 raise ValueError("value out of range for system pointer")
155builtin_fields: dict[type[Any], Field[Any]] = {
156 int: NativeIntField("i", "int"),
157 float: FloatingPointField("d"),
158 bool: BoolField(),
159 bytes: CharField(),
160}