@@ -56,9 +56,11 @@ def __init__(self, load: str = "", *, endian: str = "<", pointer: str | None = N
5656 self .endian = endian
5757
5858 self .consts = {}
59+ self .types = {}
5960 self .includes = []
61+
6062 # fmt: off
61- self . typedefs = {
63+ initial_types = {
6264 # Internal types
6365 "int8" : self ._make_packed_type ("int8" , "b" , int ),
6466 "uint8" : self ._make_packed_type ("uint8" , "B" , int ),
@@ -102,6 +104,22 @@ def __init__(self, load: str = "", *, endian: str = "<", pointer: str | None = N
102104 "signed long long" : "int64" ,
103105 "unsigned long long" : "uint64" ,
104106
107+ # Other convenience types
108+ "u8" : "uint8" ,
109+ "u16" : "uint16" ,
110+ "u32" : "uint32" ,
111+ "u64" : "uint64" ,
112+ "u128" : "uint128" ,
113+ "__u8" : "uint8" ,
114+ "__u16" : "uint16" ,
115+ "__u32" : "uint32" ,
116+ "__u64" : "uint64" ,
117+ "__u128" : "uint128" ,
118+ "uchar" : "uint8" ,
119+ "ushort" : "uint16" ,
120+ "uint" : "uint32" ,
121+ "ulong" : "uint32" ,
122+
105123 # Windows types
106124 "BYTE" : "uint8" ,
107125 "CHAR" : "char" ,
@@ -169,25 +187,12 @@ def __init__(self, load: str = "", *, endian: str = "<", pointer: str | None = N
169187 "_DWORD" : "uint32" ,
170188 "_QWORD" : "uint64" ,
171189 "_OWORD" : "uint128" ,
172-
173- # Other convenience types
174- "u8" : "uint8" ,
175- "u16" : "uint16" ,
176- "u32" : "uint32" ,
177- "u64" : "uint64" ,
178- "u128" : "uint128" ,
179- "__u8" : "uint8" ,
180- "__u16" : "uint16" ,
181- "__u32" : "uint32" ,
182- "__u64" : "uint64" ,
183- "__u128" : "uint128" ,
184- "uchar" : "uint8" ,
185- "ushort" : "uint16" ,
186- "uint" : "uint32" ,
187- "ulong" : "uint32" ,
188190 }
189191 # fmt: on
190192
193+ for name , type_ in initial_types .items ():
194+ self .add_type (name , type_ )
195+
191196 pointer = pointer or ("uint64" if sys .maxsize > 2 ** 32 else "uint32" )
192197 self .pointer : type [BaseType ] = self .resolve (pointer )
193198 self ._anonymous_count = 0
@@ -202,27 +207,29 @@ def __getattr__(self, attr: str) -> Any:
202207 pass
203208
204209 try :
205- return self .resolve ( self . typedefs [attr ])
210+ return self .types [attr ]
206211 except KeyError :
207212 pass
208213
209- raise AttributeError (f"Invalid attribute: { attr } " )
214+ raise AttributeError (f"' { type ( self ). __name__ } ' object has no attribute { attr !r } " )
210215
211216 def __copy__ (self ) -> cstruct :
212217 cs = cstruct (endian = self .endian , pointer = self .pointer .__name__ )
213218 cs ._anonymous_count = self ._anonymous_count
214- cs .consts = self .consts .copy ()
215219 cs .includes = self .includes .copy ()
216220
217- # Update typedefs to point to the new cstruct instance
218- for name , type in self .typedefs .items ():
219- if name in cs .typedefs :
221+ # Update types to point to the new cstruct instance
222+ for name , type_ in self .types .items ():
223+ if name in cs .types :
220224 continue
221225
222- if isinstance (type , MetaType ):
223- new_type = copy .copy (type )
226+ if isinstance (type_ , MetaType ):
227+ new_type = copy .copy (type_ )
224228 new_type .cs = cs
225- cs .typedefs [name ] = new_type
229+ cs .add_type (name , new_type )
230+
231+ for name , value in self .consts .items ():
232+ cs .add_const (name , value )
226233
227234 return cs
228235
@@ -231,22 +238,34 @@ def _next_anonymous(self) -> str:
231238 self ._anonymous_count += 1
232239 return name
233240
241+ def _add_attr (self , name : str , value : Any ) -> None :
242+ # Names that collide with the cstruct class (e.g. a struct named ``load``) are not set as attributes
243+ # to avoid breaking the instance. They remain accessible through ``resolve`` and the type dicts.
244+ if name in _RESERVED_NAMES :
245+ return
246+ setattr (self , name , value )
247+
234248 def add_type (self , name : str , type_ : type [BaseType ] | str , replace : bool = False ) -> None :
235- """Add a type or type reference .
249+ """Add a type or type alias .
236250
237251 Only use this method when creating type aliases or adding already bound types.
252+ All types will be resolved to their actual type objects prior to being added.
238253
239254 Args:
240255 name: Name of the type to be added.
241256 type_: The type to be added. Can be a str reference to another type or a compatible type class.
257+ If a str is given, it will be resolved to the actual type object.
258+ replace: Whether to replace the type if it already exists.
242259
243260 Raises:
244261 ValueError: If the type already exists.
245262 """
246- if not replace and (name in self .typedefs and self .resolve (self .typedefs [name ]) != self .resolve (type_ )):
263+ typeobj = self .resolve (type_ )
264+ if not replace and (existing := self .types .get (name )) is not None and existing is not typeobj :
247265 raise ValueError (f"Duplicate type: { name } " )
248266
249- self .typedefs [name ] = type_
267+ self .types [name ] = typeobj
268+ self ._add_attr (name , typeobj )
250269
251270 addtype = add_type
252271
@@ -267,6 +286,28 @@ def add_custom_type(
267286 """
268287 self .add_type (name , self ._make_type (name , (type_ ,), size , alignment = alignment , attrs = kwargs ))
269288
289+ def add_const (self , name : str , value : Any ) -> None :
290+ """Add a constant value.
291+
292+ Args:
293+ name: Name of the constant to be added.
294+ value: The value of the constant.
295+ """
296+ self .consts [name ] = value
297+ self ._add_attr (name , value )
298+
299+ def del_const (self , name : str ) -> None :
300+ """Delete a constant value.
301+
302+ Args:
303+ name: Name of the constant to be deleted.
304+
305+ Raises:
306+ KeyError: If the constant does not exist.
307+ """
308+ del self .consts [name ]
309+ self .__dict__ .pop (name , None )
310+
270311 def load (self , definition : str , deftype : int | None = None , ** kwargs ) -> cstruct :
271312 """Parse structures from the given definitions using the given definition type.
272313
@@ -329,20 +370,13 @@ def resolve(self, name: type[BaseType] | str) -> type[BaseType]:
329370 Raises:
330371 ResolveError: If the type can't be resolved.
331372 """
332- type_name = name
333- if not isinstance (type_name , str ):
334- return type_name
373+ if not isinstance (name , str ):
374+ return name
335375
336- for _ in range (10 ):
337- if type_name not in self .typedefs :
338- raise ResolveError (f"Unknown type { name } " )
339-
340- type_name = self .typedefs [type_name ]
341-
342- if not isinstance (type_name , str ):
343- return type_name
344-
345- raise ResolveError (f"Recursion limit exceeded while resolving type { name } " )
376+ try :
377+ return self .types [name ]
378+ except KeyError :
379+ raise ResolveError (f"Unknown type { name } " ) from None
346380
347381 def copy (self ) -> cstruct :
348382 """Create a copy of this cstruct instance.
@@ -519,6 +553,21 @@ class void(Void): ...
519553 # signed long long: TypeAlias = int64
520554 # unsigned long long: TypeAlias = uint64
521555
556+ u8 : TypeAlias = uint8
557+ u16 : TypeAlias = uint16
558+ u32 : TypeAlias = uint32
559+ u64 : TypeAlias = uint64
560+ u128 : TypeAlias = uint128
561+ __u8 : TypeAlias = uint8
562+ __u16 : TypeAlias = uint16
563+ __u32 : TypeAlias = uint32
564+ __u64 : TypeAlias = uint64
565+ __u128 : TypeAlias = uint128
566+ uchar : TypeAlias = uint8
567+ ushort : TypeAlias = uint16
568+ uint : TypeAlias = uint32
569+ ulong : TypeAlias = uint32
570+
522571 BYTE : TypeAlias = uint8
523572 CHAR : TypeAlias = char
524573 SHORT : TypeAlias = int16
@@ -584,20 +633,17 @@ class void(Void): ...
584633 _QWORD : TypeAlias = uint64
585634 _OWORD : TypeAlias = uint128
586635
587- u8 : TypeAlias = uint8
588- u16 : TypeAlias = uint16
589- u32 : TypeAlias = uint32
590- u64 : TypeAlias = uint64
591- u128 : TypeAlias = uint128
592- __u8 : TypeAlias = uint8
593- __u16 : TypeAlias = uint16
594- __u32 : TypeAlias = uint32
595- __u64 : TypeAlias = uint64
596- __u128 : TypeAlias = uint128
597- uchar : TypeAlias = uint8
598- ushort : TypeAlias = uint16
599- uint : TypeAlias = uint32
600- ulong : TypeAlias = uint32
636+
637+ # Attribute names that types and constants may never shadow: the public API and methods of the cstruct
638+ # class itself, plus the instance attributes created in __init__.
639+ _RESERVED_NAMES = frozenset (dir (cstruct )) | {
640+ "endian" ,
641+ "consts" ,
642+ "types" ,
643+ "includes" ,
644+ "pointer" ,
645+ "_anonymous_count" ,
646+ }
601647
602648
603649def ctypes (structure : type [Structure ]) -> type [_ctypes .Structure ]:
0 commit comments