dissect.cstruct 4.8.dev9__py3-none-any.whl → 5.0.dev6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. dissect/cstruct/__init__.py +2 -2
  2. dissect/cstruct/bitbuffer.py +3 -3
  3. dissect/cstruct/compiler.py +7 -7
  4. dissect/cstruct/cstruct.py +196 -75
  5. dissect/cstruct/{exceptions.py → exception.py} +4 -4
  6. dissect/cstruct/expression.py +188 -271
  7. dissect/cstruct/lexer.py +584 -0
  8. dissect/cstruct/parser.py +444 -593
  9. dissect/cstruct/tools/stubgen.py +18 -22
  10. dissect/cstruct/types/base.py +74 -31
  11. dissect/cstruct/types/char.py +12 -8
  12. dissect/cstruct/types/enum.py +33 -13
  13. dissect/cstruct/types/int.py +9 -7
  14. dissect/cstruct/types/leb128.py +6 -4
  15. dissect/cstruct/types/packed.py +19 -10
  16. dissect/cstruct/types/pointer.py +74 -56
  17. dissect/cstruct/types/structure.py +128 -28
  18. dissect/cstruct/types/void.py +6 -4
  19. dissect/cstruct/types/wchar.py +19 -14
  20. dissect/cstruct/{utils.py → util.py} +60 -35
  21. {dissect_cstruct-4.8.dev9.dist-info → dissect_cstruct-5.0.dev6.dist-info}/METADATA +1 -1
  22. dissect_cstruct-5.0.dev6.dist-info/RECORD +32 -0
  23. {dissect_cstruct-4.8.dev9.dist-info → dissect_cstruct-5.0.dev6.dist-info}/scm_file_list.json +4 -2
  24. dissect_cstruct-5.0.dev6.dist-info/scm_version.json +8 -0
  25. dissect_cstruct-4.8.dev9.dist-info/RECORD +0 -31
  26. dissect_cstruct-4.8.dev9.dist-info/scm_version.json +0 -8
  27. {dissect_cstruct-4.8.dev9.dist-info → dissect_cstruct-5.0.dev6.dist-info}/WHEEL +0 -0
  28. {dissect_cstruct-4.8.dev9.dist-info → dissect_cstruct-5.0.dev6.dist-info}/entry_points.txt +0 -0
  29. {dissect_cstruct-4.8.dev9.dist-info → dissect_cstruct-5.0.dev6.dist-info}/licenses/COPYRIGHT +0 -0
  30. {dissect_cstruct-4.8.dev9.dist-info → dissect_cstruct-5.0.dev6.dist-info}/licenses/LICENSE +0 -0
  31. {dissect_cstruct-4.8.dev9.dist-info → dissect_cstruct-5.0.dev6.dist-info}/top_level.txt +0 -0
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from dissect.cstruct.bitbuffer import BitBuffer
4
4
  from dissect.cstruct.cstruct import cstruct, ctypes, ctypes_type
5
- from dissect.cstruct.exceptions import (
5
+ from dissect.cstruct.exception import (
6
6
  Error,
7
7
  NullPointerDereference,
8
8
  ParserError,
@@ -28,7 +28,7 @@ from dissect.cstruct.types import (
28
28
  Wchar,
29
29
  WcharArray,
30
30
  )
31
- from dissect.cstruct.utils import (
31
+ from dissect.cstruct.util import (
32
32
  dumpstruct,
33
33
  hexdump,
34
34
  p8,
@@ -9,7 +9,7 @@ if TYPE_CHECKING:
9
9
  class BitBuffer:
10
10
  """Implements a bit buffer that can read and write bit fields."""
11
11
 
12
- def __init__(self, stream: BinaryIO, endian: str):
12
+ def __init__(self, stream: BinaryIO, *, endian: str):
13
13
  self.stream = stream
14
14
  self.endian = endian
15
15
 
@@ -24,7 +24,7 @@ class BitBuffer:
24
24
 
25
25
  self._type = field_type
26
26
  self._remaining = field_type.size * 8
27
- self._buffer = field_type._read(self.stream)
27
+ self._buffer = field_type._read(self.stream, endian=self.endian)
28
28
 
29
29
  if isinstance(self._buffer, bytes):
30
30
  if self.endian == "<":
@@ -71,7 +71,7 @@ class BitBuffer:
71
71
 
72
72
  def flush(self) -> None:
73
73
  if self._type is not None:
74
- self._type._write(self.stream, self._buffer)
74
+ self._type._write(self.stream, self._buffer, endian=self.endian)
75
75
  self._type = None
76
76
  self._remaining = 0
77
77
  self._buffer = 0
@@ -119,7 +119,7 @@ class _ReadSourceGenerator:
119
119
  """
120
120
 
121
121
  if any(field.bits for field in self.fields):
122
- preamble += "bit_reader = BitBuffer(stream, cls.cs.endian)\n"
122
+ preamble += "bit_reader = BitBuffer(stream, endian=endian)\n"
123
123
 
124
124
  read_code = "\n".join(self._generate_fields())
125
125
 
@@ -132,7 +132,7 @@ class _ReadSourceGenerator:
132
132
 
133
133
  code = indent(dedent(preamble).lstrip() + read_code + dedent(outro), " ")
134
134
 
135
- return f"def _read(cls, stream, context=None):\n{code}"
135
+ return f"def _read(cls, stream, *, context=None, endian):\n{code}"
136
136
 
137
137
  def _generate_fields(self) -> Iterator[str]:
138
138
  current_offset = 0
@@ -229,7 +229,7 @@ class _ReadSourceGenerator:
229
229
  def _generate_structure(self, field: Field) -> Iterator[str]:
230
230
  template = f"""
231
231
  {"_s = stream.tell()" if field.type.dynamic else ""}
232
- r["{field._name}"] = {self._map_field(field)}._read(stream, context=r)
232
+ r["{field._name}"] = {self._map_field(field)}._read(stream, context=r, endian=endian)
233
233
  {f's["{field._name}"] = stream.tell() - _s' if field.type.dynamic else ""}
234
234
  """
235
235
 
@@ -238,7 +238,7 @@ class _ReadSourceGenerator:
238
238
  def _generate_array(self, field: Field) -> Iterator[str]:
239
239
  template = f"""
240
240
  {"_s = stream.tell()" if field.type.dynamic else ""}
241
- r["{field._name}"] = {self._map_field(field)}._read(stream, context=r)
241
+ r["{field._name}"] = {self._map_field(field)}._read(stream, context=r, endian=endian)
242
242
  {f's["{field._name}"] = stream.tell() - _s' if field.type.dynamic else ""}
243
243
  """
244
244
 
@@ -311,7 +311,7 @@ class _ReadSourceGenerator:
311
311
  item_parser = parser_template.format(type="_et", getter=f"_b[i:i + {field_type.type.size}]")
312
312
  list_comp = f"[{item_parser} for i in range(0, {count}, {field_type.type.size})]"
313
313
  elif issubclass(field_type.type, Pointer):
314
- item_parser = "_et.__new__(_et, e, stream, r)"
314
+ item_parser = "_et.__new__(_et, e, stream, context=r, endian=endian)"
315
315
  list_comp = f"[{item_parser} for e in {getter}]"
316
316
  else:
317
317
  item_parser = parser_template.format(type="_et", getter="e")
@@ -322,7 +322,7 @@ class _ReadSourceGenerator:
322
322
  parser = f"type.__call__({self._map_field(field)}, {getter})"
323
323
  elif issubclass(field_type, Pointer):
324
324
  reads.append(f"_pt = {self._map_field(field)}")
325
- parser = f"_pt.__new__(_pt, {getter}, stream, r)"
325
+ parser = f"_pt.__new__(_pt, {getter}, stream, context=r, endian=endian)"
326
326
  else:
327
327
  parser = parser_template.format(type=self._map_field(field), getter=getter)
328
328
 
@@ -335,7 +335,7 @@ class _ReadSourceGenerator:
335
335
  if fmt == "x" or (len(fmt) == 2 and fmt[1] == "x"):
336
336
  unpack = ""
337
337
  else:
338
- unpack = f'data = _struct(cls.cs.endian, "{fmt}").unpack(buf)\n'
338
+ unpack = f'data = _struct(endian, "{fmt}").unpack(buf)\n'
339
339
 
340
340
  template = f"""
341
341
  buf = stream.read({size})
@@ -2,15 +2,18 @@ from __future__ import annotations
2
2
 
3
3
  import copy
4
4
  import ctypes as _ctypes
5
+ import inspect
5
6
  import struct
6
7
  import sys
7
8
  import types
9
+ import warnings
10
+ from contextlib import contextmanager
8
11
  from pathlib import Path
9
- from typing import TYPE_CHECKING, Any, BinaryIO, TypeVar, cast
12
+ from typing import TYPE_CHECKING, Any, BinaryIO, Literal, TypeVar, cast
10
13
 
11
- from dissect.cstruct.exceptions import ResolveError
14
+ from dissect.cstruct.exception import ResolveError
12
15
  from dissect.cstruct.expression import Expression
13
- from dissect.cstruct.parser import CStyleParser, TokenParser
16
+ from dissect.cstruct.parser import CStyleParser
14
17
  from dissect.cstruct.types import (
15
18
  LEB128,
16
19
  BaseArray,
@@ -26,10 +29,10 @@ from dissect.cstruct.types import (
26
29
  Void,
27
30
  Wchar,
28
31
  )
29
- from dissect.cstruct.types.base import MetaType
32
+ from dissect.cstruct.types.base import normalize_endianness
30
33
 
31
34
  if TYPE_CHECKING:
32
- from collections.abc import Iterable
35
+ from collections.abc import Iterable, Iterator
33
36
  from typing import TypeAlias
34
37
 
35
38
  from dissect.cstruct.types import (
@@ -40,26 +43,30 @@ if TYPE_CHECKING:
40
43
 
41
44
  T = TypeVar("T", bound=BaseType)
42
45
 
46
+ AllowedEndianness: TypeAlias = Literal["little", "big", "network", "<", ">", "!", "@", "="]
47
+ Endianness: TypeAlias = Literal["<", ">", "!", "@", "="]
48
+
43
49
 
44
50
  class cstruct:
45
51
  """Main class of cstruct. All types are registered in here.
46
52
 
47
53
  Args:
48
- endian: The endianness to use when parsing.
54
+ endian: The endianness to use when parsing (little, big, network, <, >, !, @ or =).
49
55
  pointer: The pointer type to use for pointers.
50
56
  """
51
57
 
52
58
  DEF_CSTYLE = 1
53
59
  DEF_LEGACY = 2
54
60
 
55
- def __init__(self, load: str = "", *, endian: str = "<", pointer: str | None = None):
56
- self.endian = endian
61
+ def __init__(self, load: str = "", *, endian: AllowedEndianness = "<", pointer: str | None = None):
62
+ self.endian = normalize_endianness(endian)
63
+
64
+ self.consts: dict[str, int | str | bytes] = {}
65
+ self.types: dict[str, type[BaseType]] = {}
66
+ self.includes: list[str] = []
57
67
 
58
- self.consts = {}
59
- self.lookups = {}
60
- self.includes = []
61
68
  # fmt: off
62
- self.typedefs = {
69
+ initial_types = {
63
70
  # Internal types
64
71
  "int8": self._make_packed_type("int8", "b", int),
65
72
  "uint8": self._make_packed_type("uint8", "B", int),
@@ -103,6 +110,22 @@ class cstruct:
103
110
  "signed long long": "int64",
104
111
  "unsigned long long": "uint64",
105
112
 
113
+ # Other convenience types
114
+ "u8": "uint8",
115
+ "u16": "uint16",
116
+ "u32": "uint32",
117
+ "u64": "uint64",
118
+ "u128": "uint128",
119
+ "__u8": "uint8",
120
+ "__u16": "uint16",
121
+ "__u32": "uint32",
122
+ "__u64": "uint64",
123
+ "__u128": "uint128",
124
+ "uchar": "uint8",
125
+ "ushort": "uint16",
126
+ "uint": "uint32",
127
+ "ulong": "uint32",
128
+
106
129
  # Windows types
107
130
  "BYTE": "uint8",
108
131
  "CHAR": "char",
@@ -170,24 +193,12 @@ class cstruct:
170
193
  "_DWORD": "uint32",
171
194
  "_QWORD": "uint64",
172
195
  "_OWORD": "uint128",
173
-
174
- # Other convenience types
175
- "u1": "uint8",
176
- "u2": "uint16",
177
- "u4": "uint32",
178
- "u8": "uint64",
179
- "u16": "uint128",
180
- "__u8": "uint8",
181
- "__u16": "uint16",
182
- "__u32": "uint32",
183
- "__u64": "uint64",
184
- "uchar": "uint8",
185
- "ushort": "uint16",
186
- "uint": "uint32",
187
- "ulong": "uint32",
188
196
  }
189
197
  # fmt: on
190
198
 
199
+ for name, type_ in initial_types.items():
200
+ self.add_type(name, type_)
201
+
191
202
  pointer = pointer or ("uint64" if sys.maxsize > 2**32 else "uint32")
192
203
  self.pointer: type[BaseType] = self.resolve(pointer)
193
204
  self._anonymous_count = 0
@@ -202,28 +213,25 @@ class cstruct:
202
213
  pass
203
214
 
204
215
  try:
205
- return self.resolve(self.typedefs[attr])
216
+ return self.types[attr]
206
217
  except KeyError:
207
218
  pass
208
219
 
209
- raise AttributeError(f"Invalid attribute: {attr}")
220
+ raise AttributeError(f"'{type(self).__name__}' object has no attribute {attr!r}")
210
221
 
211
222
  def __copy__(self) -> cstruct:
212
223
  cs = cstruct(endian=self.endian, pointer=self.pointer.__name__)
213
224
  cs._anonymous_count = self._anonymous_count
214
- cs.consts = self.consts.copy()
215
- cs.lookups = self.lookups.copy()
216
225
  cs.includes = self.includes.copy()
217
226
 
218
- # Update typedefs to point to the new cstruct instance
219
- for name, type in self.typedefs.items():
220
- if name in cs.typedefs:
221
- continue
227
+ # Update types to point to the new cstruct instance
228
+ for name, type_ in self.types.items():
229
+ new_type = copy.copy(type_)
230
+ new_type.cs = cs
231
+ cs.add_type(name, new_type, replace=True)
222
232
 
223
- if isinstance(type, MetaType):
224
- new_type = copy.copy(type)
225
- new_type.cs = cs
226
- cs.typedefs[name] = new_type
233
+ for name, value in self.consts.items():
234
+ cs.add_const(name, value)
227
235
 
228
236
  return cs
229
237
 
@@ -232,22 +240,34 @@ class cstruct:
232
240
  self._anonymous_count += 1
233
241
  return name
234
242
 
243
+ def _add_attr(self, name: str, value: Any) -> None:
244
+ # Names that collide with the cstruct class (e.g. a struct named ``load``) are not set as attributes
245
+ # to avoid breaking the instance. They remain accessible through ``resolve`` and the type dicts.
246
+ if name in _RESERVED_NAMES:
247
+ return
248
+ setattr(self, name, value)
249
+
235
250
  def add_type(self, name: str, type_: type[BaseType] | str, replace: bool = False) -> None:
236
- """Add a type or type reference.
251
+ """Add a type or type alias.
237
252
 
238
253
  Only use this method when creating type aliases or adding already bound types.
254
+ All types will be resolved to their actual type objects prior to being added.
239
255
 
240
256
  Args:
241
257
  name: Name of the type to be added.
242
258
  type_: The type to be added. Can be a str reference to another type or a compatible type class.
259
+ If a str is given, it will be resolved to the actual type object.
260
+ replace: Whether to replace the type if it already exists.
243
261
 
244
262
  Raises:
245
263
  ValueError: If the type already exists.
246
264
  """
247
- if not replace and (name in self.typedefs and self.resolve(self.typedefs[name]) != self.resolve(type_)):
265
+ typeobj = self.resolve(type_)
266
+ if not replace and (existing := self.types.get(name)) is not None and existing is not typeobj:
248
267
  raise ValueError(f"Duplicate type: {name}")
249
268
 
250
- self.typedefs[name] = type_
269
+ self.types[name] = typeobj
270
+ self._add_attr(name, typeobj)
251
271
 
252
272
  addtype = add_type
253
273
 
@@ -266,19 +286,56 @@ class cstruct:
266
286
  alignment: The alignment of the type.
267
287
  **kwargs: Additional attributes to add to the type.
268
288
  """
289
+ # In cstruct 5.0 we changed the function signature of _read and _write
290
+ # Check if the function signature is compatible, and warn if not
291
+ for type_to_check in (type_, type_.ArrayType):
292
+ type_name = type_.__name__ + (f".{type_.ArrayType.__name__}" if type_to_check is type_.ArrayType else "")
293
+
294
+ for method in ("_read", "_read_array", "_read_0", "_write", "_write_array", "_write_0"):
295
+ if not hasattr(type_to_check, method):
296
+ continue
297
+
298
+ signature = inspect.signature(getattr(type_to_check, method))
299
+
300
+ # Only warn if the method doesn't accept an endian parameter
301
+ if "endian" not in signature.parameters:
302
+ warnings.warn(
303
+ f"Custom type {type_name} is missing the 'endian' keyword-only parameter in its {method} method. " # noqa: E501
304
+ "Please refer to the changelog of dissect.cstruct 5.0 for more information.",
305
+ stacklevel=2,
306
+ )
307
+
269
308
  self.add_type(name, self._make_type(name, (type_,), size, alignment=alignment, attrs=kwargs))
270
309
 
310
+ def add_const(self, name: str, value: Any) -> None:
311
+ """Add a constant value.
312
+
313
+ Args:
314
+ name: Name of the constant to be added.
315
+ value: The value of the constant.
316
+ """
317
+ self.consts[name] = value
318
+ self._add_attr(name, value)
319
+
320
+ def del_const(self, name: str) -> None:
321
+ """Delete a constant value.
322
+
323
+ Args:
324
+ name: Name of the constant to be deleted.
325
+
326
+ Raises:
327
+ KeyError: If the constant does not exist.
328
+ """
329
+ del self.consts[name]
330
+ self.__dict__.pop(name, None)
331
+
271
332
  def load(self, definition: str, deftype: int | None = None, **kwargs) -> cstruct:
272
333
  """Parse structures from the given definitions using the given definition type.
273
334
 
274
335
  Definitions can be parsed using different parsers. Currently, there's
275
336
  only one supported parser - DEF_CSTYLE. Parsers can add types and
276
337
  modify this cstruct instance. Arguments can be passed to parsers
277
- using kwargs.
278
-
279
- The CSTYLE parser was recently replaced with token based parser,
280
- instead of a strictly regex based one. The old parser is still available
281
- by using DEF_LEGACY.
338
+ using ``kwargs``.
282
339
 
283
340
  Args:
284
341
  definition: The definition to parse.
@@ -288,9 +345,9 @@ class cstruct:
288
345
  deftype = deftype or cstruct.DEF_CSTYLE
289
346
 
290
347
  if deftype == cstruct.DEF_CSTYLE:
291
- TokenParser(self, **kwargs).parse(definition)
292
- elif deftype == cstruct.DEF_LEGACY:
293
348
  CStyleParser(self, **kwargs).parse(definition)
349
+ else:
350
+ raise ValueError(f"Unknown definition type: {deftype}")
294
351
 
295
352
  return self
296
353
 
@@ -307,6 +364,42 @@ class cstruct:
307
364
  with Path(path).open() as fh:
308
365
  self.load(fh.read(), deftype, **kwargs)
309
366
 
367
+ def cdef(self) -> str:
368
+ """Render all constants, structure, union, enum and flag definitions back to their C-style definitions.
369
+
370
+ Note that the result is semantically equivalent to the original definitions, but not necessarily identical.
371
+
372
+ Returns:
373
+ The C-style definitions as a string.
374
+ """
375
+ empty = cstruct()
376
+
377
+ blocks = []
378
+
379
+ defines = []
380
+ for name, value in self.consts.items():
381
+ if name in empty.consts:
382
+ continue
383
+
384
+ defines.append(f"#define {name} {value!r}")
385
+
386
+ if defines:
387
+ blocks.append("\n".join(defines))
388
+
389
+ for name, typedef in self.types.items():
390
+ if name in empty.types or not isinstance(typedef, type):
391
+ continue
392
+
393
+ if not issubclass(typedef, (Structure, Enum, Flag)):
394
+ continue
395
+
396
+ if typedef.__name__ == name:
397
+ blocks.append(typedef.cdef())
398
+ else:
399
+ blocks.append(f"typedef {typedef.__name__} {name};")
400
+
401
+ return "\n\n".join(blocks)
402
+
310
403
  def read(self, name: str, stream: BinaryIO) -> Any:
311
404
  """Parse data using a given type.
312
405
 
@@ -334,20 +427,13 @@ class cstruct:
334
427
  Raises:
335
428
  ResolveError: If the type can't be resolved.
336
429
  """
337
- type_name = name
338
- if not isinstance(type_name, str):
339
- return type_name
430
+ if not isinstance(name, str):
431
+ return name
340
432
 
341
- for _ in range(10):
342
- if type_name not in self.typedefs:
343
- raise ResolveError(f"Unknown type {name}")
344
-
345
- type_name = self.typedefs[type_name]
346
-
347
- if not isinstance(type_name, str):
348
- return type_name
349
-
350
- raise ResolveError(f"Recursion limit exceeded while resolving type {name}")
433
+ try:
434
+ return self.types[name]
435
+ except KeyError:
436
+ raise ResolveError(f"Unknown type {name}") from None
351
437
 
352
438
  def copy(self) -> cstruct:
353
439
  """Create a copy of this cstruct instance.
@@ -357,6 +443,28 @@ class cstruct:
357
443
  """
358
444
  return copy.copy(self)
359
445
 
446
+ @contextmanager
447
+ def endianness(self, endian: AllowedEndianness) -> Iterator[cstruct]:
448
+ """Context manager for temporarily changing the endianness of this cstruct instance."""
449
+ original = self.endian
450
+ self.endian = normalize_endianness(endian)
451
+ try:
452
+ yield self
453
+ finally:
454
+ self.endian = original
455
+
456
+ @contextmanager
457
+ def big_endian(self) -> Iterator[cstruct]:
458
+ """Context manager for temporarily changing the endianness to big endian."""
459
+ with self.endianness(">") as cs:
460
+ yield cs
461
+
462
+ @contextmanager
463
+ def little_endian(self) -> Iterator[cstruct]:
464
+ """Context manager for temporarily changing the endianness to little endian."""
465
+ with self.endianness("<") as cs:
466
+ yield cs
467
+
360
468
  def _make_type(
361
469
  self,
362
470
  name: str,
@@ -524,6 +632,21 @@ class cstruct:
524
632
  # signed long long: TypeAlias = int64
525
633
  # unsigned long long: TypeAlias = uint64
526
634
 
635
+ u8: TypeAlias = uint8
636
+ u16: TypeAlias = uint16
637
+ u32: TypeAlias = uint32
638
+ u64: TypeAlias = uint64
639
+ u128: TypeAlias = uint128
640
+ __u8: TypeAlias = uint8
641
+ __u16: TypeAlias = uint16
642
+ __u32: TypeAlias = uint32
643
+ __u64: TypeAlias = uint64
644
+ __u128: TypeAlias = uint128
645
+ uchar: TypeAlias = uint8
646
+ ushort: TypeAlias = uint16
647
+ uint: TypeAlias = uint32
648
+ ulong: TypeAlias = uint32
649
+
527
650
  BYTE: TypeAlias = uint8
528
651
  CHAR: TypeAlias = char
529
652
  SHORT: TypeAlias = int16
@@ -589,19 +712,17 @@ class cstruct:
589
712
  _QWORD: TypeAlias = uint64
590
713
  _OWORD: TypeAlias = uint128
591
714
 
592
- u1: TypeAlias = uint8
593
- u2: TypeAlias = uint16
594
- u4: TypeAlias = uint32
595
- u8: TypeAlias = uint64
596
- u16: TypeAlias = uint128
597
- __u8: TypeAlias = uint8
598
- __u16: TypeAlias = uint16
599
- __u32: TypeAlias = uint32
600
- __u64: TypeAlias = uint64
601
- uchar: TypeAlias = uint8
602
- ushort: TypeAlias = uint16
603
- uint: TypeAlias = uint32
604
- ulong: TypeAlias = uint32
715
+
716
+ # Attribute names that types and constants may never shadow: the public API and methods of the cstruct
717
+ # class itself, plus the instance attributes created in __init__.
718
+ _RESERVED_NAMES = frozenset(dir(cstruct)) | {
719
+ "endian",
720
+ "consts",
721
+ "types",
722
+ "includes",
723
+ "pointer",
724
+ "_anonymous_count",
725
+ }
605
726
 
606
727
 
607
728
  def ctypes(structure: type[Structure]) -> type[_ctypes.Structure]:
@@ -5,6 +5,10 @@ class Error(Exception):
5
5
  pass
6
6
 
7
7
 
8
+ class LexerError(Error):
9
+ pass
10
+
11
+
8
12
  class ParserError(Error):
9
13
  pass
10
14
 
@@ -23,7 +27,3 @@ class ArraySizeError(Error):
23
27
 
24
28
  class ExpressionParserError(Error):
25
29
  pass
26
-
27
-
28
- class ExpressionTokenizerError(Error):
29
- pass