Coverage for dataclasses_struct/field.py: 99%
97 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-15 09:30 +1200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-15 09:30 +1200
1import abc
2import ctypes
3from types import GenericAlias
4from typing import Any, ClassVar, Generic, Literal, TypeVar, Union
6T = TypeVar("T")
9class Field(abc.ABC, Generic[T]):
10 is_native: bool = True
11 is_std: bool = True
12 field_type: Union[type[T], tuple[type[T], ...], GenericAlias]
14 @abc.abstractmethod
15 def format(self) -> str: ...
17 def validate_default(self, val: T) -> None:
18 pass
20 def __repr__(self) -> str:
21 return f"{type(self).__name__}"
24class BoolField(Field[bool]):
25 field_type = bool
27 def format(self) -> str:
28 return "?"
31class CharField(Field[bytes]):
32 field_type = bytes
34 def format(self) -> str:
35 return "c"
37 def validate_default(self, val: bytes) -> None:
38 if len(val) != 1:
39 raise ValueError("value must be a single byte")
42class IntField(Field[int]):
43 field_type = int
45 def __init__(
46 self,
47 fmt: str,
48 signed: bool,
49 size: int,
50 ):
51 if signed and fmt.isupper():
52 raise ValueError(
53 "signed integer should have lowercase format string"
54 )
56 self.signed = signed
57 self.size = size
58 self._format = fmt
60 nbits = self.size * 8
61 if signed:
62 exp = 1 << (nbits - 1)
63 self.min_ = -exp
64 self.max_ = exp - 1
65 else:
66 self.min_ = 0
67 self.max_ = (1 << nbits) - 1
69 def format(self) -> str:
70 return self._format
72 def validate_default(self, val: int) -> None:
73 if not (self.min_ <= val <= self.max_):
74 sign = "signed" if self.signed else "unsigned"
75 n = self.size * 8
76 raise ValueError(f"value out of range for {n}-bit {sign} integer")
78 def __repr__(self) -> str:
79 sign = "signed" if self.signed else "unsigned"
80 return f"{super().__repr__()}({sign}, {self.size * 8}-bit)"
83class StdIntField(IntField):
84 is_native = False
85 _unsigned_formats: ClassVar = {
86 1: "B",
87 2: "H",
88 4: "I",
89 8: "Q",
90 }
92 def __init__(self, signed: bool, size: Literal[1, 2, 4, 8]):
93 fmt = self._unsigned_formats[size]
94 if signed:
95 fmt = fmt.lower()
96 super().__init__(fmt, signed, size)
99class SignedStdIntField(StdIntField):
100 def __init__(self, size: Literal[1, 2, 4, 8]):
101 super().__init__(True, size)
104class UnsignedStdIntField(StdIntField):
105 def __init__(self, size: Literal[1, 2, 4, 8]):
106 super().__init__(False, size)
109class FloatingPointField(Field[float]):
110 field_type = (int, float)
112 def __init__(self, format: str):
113 self._format = format
115 def format(self) -> str:
116 return self._format
119class NativeIntField(IntField):
120 is_std = False
122 def __init__(self, fmt: str, ctype_name: str):
123 size = ctypes.sizeof(getattr(ctypes, f"c_{ctype_name}"))
124 signed = not ctype_name.startswith("u")
125 super().__init__(fmt, signed, size)
128class SizeField(IntField):
129 is_std = False
131 def __init__(self, signed: bool):
132 fmt = "n" if signed else "N"
133 size = ctypes.sizeof(ctypes.c_ssize_t if signed else ctypes.c_size_t)
134 super().__init__(fmt, signed, size)
136 def validate_default(self, val: int) -> None:
137 if not (self.min_ <= val <= self.max_):
138 sign = "signed" if self.signed else "unsigned"
139 raise ValueError(f"value out of range for {sign} size type")
142class PointerField(IntField):
143 is_std = False
145 def __init__(self):
146 super().__init__("P", False, ctypes.sizeof(ctypes.c_void_p))
148 def format(self) -> str:
149 return "P"
151 def validate_default(self, val: int) -> None:
152 if not (self.min_ <= val <= self.max_):
153 raise ValueError("value out of range for system pointer")
156builtin_fields: dict[type[Any], Field[Any]] = {
157 int: NativeIntField("i", "int"),
158 float: FloatingPointField("d"),
159 bool: BoolField(),
160 bytes: CharField(),
161}