dissect.cstruct 4.5.dev4__tar.gz → 4.6__tar.gz

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 (78) hide show
  1. dissect_cstruct-4.6/CHANGELOG.md +54 -0
  2. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/PKG-INFO +2 -1
  3. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/compiler.py +8 -8
  4. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/cstruct.py +8 -4
  5. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/expression.py +6 -7
  6. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/parser.py +101 -14
  7. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/tools/stubgen.py +3 -0
  8. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/types/__init__.py +2 -1
  9. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/types/base.py +5 -1
  10. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/types/structure.py +104 -24
  11. dissect_cstruct-4.6/dissect/cstruct/types/void.py +44 -0
  12. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/utils.py +4 -5
  13. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect.cstruct.egg-info/PKG-INFO +2 -1
  14. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect.cstruct.egg-info/SOURCES.txt +1 -0
  15. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/pyproject.toml +3 -3
  16. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_align.py +1 -1
  17. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_annotations.py +1 -1
  18. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_basic.py +46 -2
  19. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_compiler.py +3 -23
  20. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_expression.py +3 -3
  21. dissect_cstruct-4.6/tests/test_parser.py +296 -0
  22. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_tools_stubgen.py +20 -0
  23. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_types_base.py +9 -0
  24. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_types_structure.py +69 -1
  25. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_types_void.py +9 -7
  26. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tox.ini +4 -2
  27. dissect_cstruct-4.5.dev4/dissect/cstruct/types/void.py +0 -27
  28. dissect_cstruct-4.5.dev4/tests/test_parser.py +0 -131
  29. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/.git-blame-ignore-revs +0 -0
  30. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/.gitattributes +0 -0
  31. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/COPYRIGHT +0 -0
  32. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/LICENSE +0 -0
  33. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/MANIFEST.in +0 -0
  34. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/README.md +0 -0
  35. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/__init__.py +0 -0
  36. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/bitbuffer.py +0 -0
  37. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/exceptions.py +0 -0
  38. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/tools/__init__.py +0 -0
  39. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/types/char.py +0 -0
  40. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/types/enum.py +0 -0
  41. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/types/flag.py +0 -0
  42. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/types/int.py +0 -0
  43. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/types/leb128.py +0 -0
  44. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/types/packed.py +0 -0
  45. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/types/pointer.py +0 -0
  46. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect/cstruct/types/wchar.py +0 -0
  47. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect.cstruct.egg-info/dependency_links.txt +0 -0
  48. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect.cstruct.egg-info/entry_points.txt +0 -0
  49. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect.cstruct.egg-info/requires.txt +0 -0
  50. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/dissect.cstruct.egg-info/top_level.txt +0 -0
  51. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/examples/disk.py +0 -0
  52. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/examples/mirai.py +0 -0
  53. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/examples/pe.py +0 -0
  54. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/examples/protobuf.py +0 -0
  55. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/examples/secdesc.py +0 -0
  56. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/setup.cfg +0 -0
  57. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/__init__.py +0 -0
  58. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/_data/testdef.txt +0 -0
  59. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/_docs/Makefile +0 -0
  60. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/_docs/__init__.py +0 -0
  61. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/_docs/conf.py +0 -0
  62. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/_docs/index.rst +0 -0
  63. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/conftest.py +0 -0
  64. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_bitbuffer.py +0 -0
  65. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_bitfield.py +0 -0
  66. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_ctypes.py +0 -0
  67. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_types_char.py +0 -0
  68. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_types_custom.py +0 -0
  69. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_types_enum.py +0 -0
  70. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_types_flag.py +0 -0
  71. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_types_int.py +0 -0
  72. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_types_leb128.py +0 -0
  73. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_types_packed.py +0 -0
  74. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_types_pointer.py +0 -0
  75. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_types_union.py +0 -0
  76. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_types_wchar.py +0 -0
  77. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/test_utils.py +0 -0
  78. {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6}/tests/utils.py +0 -0
@@ -0,0 +1,54 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ### Changed
10
+
11
+ - Optimize storage of field sizes.
12
+ - Rename `_sizes` property of `Structure` to `__sizes__`.
13
+ - Rename `_values` property of `Structure` to `__values__`.
14
+ - Added `load` argument to `cstruct` class, allowing direct initialization with a definition (i.e. `cstruct(cdef)` instead of `cstruct().load(cdef)`. Other arguments to `cstruct` are now keyword only.
15
+
16
+ ## [4.5] - 20-05-2025
17
+
18
+ ### Added
19
+
20
+ - Introduce experimental tool `cstruct-stubgen` to generate type stubs for cstruct definitions.
21
+
22
+ ### Fixed
23
+
24
+ - Generated classes are now hashable.
25
+ - Suppress spurious `TypeError: Dynamic size` errors when using cstruct interactively.
26
+
27
+ ## [4.4] - 03-10-2025
28
+
29
+ ### Fixed
30
+
31
+ - Resolve documentation warnings.
32
+
33
+ ### Changed
34
+
35
+ - Use the Ruff linter.
36
+
37
+ ## [4.3] - 11-18-2024
38
+
39
+ ### Fixed
40
+
41
+ - All cstruct types are now correctly default-initialized using the `__default__` member.
42
+
43
+ ## [4.2] - 10-10-2024
44
+
45
+ ### Fixed
46
+
47
+ - The string representation of enums now outputs the name of the constants.
48
+
49
+ ## [4.1] - 10-09-2024
50
+
51
+ ### Fixed
52
+
53
+ - Declaring an array of a nested struct type now works as intended.
54
+
@@ -1,12 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dissect.cstruct
3
- Version: 4.5.dev4
3
+ Version: 4.6
4
4
  Summary: A Dissect module implementing a parser for C-like structures: structure parsing in Python made easy
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License: Apache License 2.0
7
7
  Project-URL: homepage, https://dissect.tools
8
8
  Project-URL: documentation, https://docs.dissect.tools/en/latest/projects/dissect.cstruct
9
9
  Project-URL: repository, https://github.com/fox-it/dissect.cstruct
10
+ Project-URL: changelog, https://github.com/fox-it/dissect.cstruct/blob/main/CHANGELOG.md
10
11
  Classifier: Development Status :: 5 - Production/Stable
11
12
  Classifier: Environment :: Console
12
13
  Classifier: Intended Audience :: Developers
@@ -21,6 +21,7 @@ from dissect.cstruct.types import (
21
21
  Structure,
22
22
  Union,
23
23
  Void,
24
+ VoidArray,
24
25
  Wchar,
25
26
  WcharArray,
26
27
  )
@@ -48,6 +49,7 @@ SUPPORTED_TYPES = (
48
49
  Void,
49
50
  Wchar,
50
51
  WcharArray,
52
+ VoidArray,
51
53
  )
52
54
 
53
55
  log = logging.getLogger(__name__)
@@ -121,8 +123,7 @@ class _ReadSourceGenerator:
121
123
 
122
124
  outro = """
123
125
  obj = type.__call__(cls, **r)
124
- obj._sizes = s
125
- obj._values = r
126
+ obj.__dynamic_sizes__ = s
126
127
 
127
128
  return obj
128
129
  """
@@ -225,18 +226,18 @@ class _ReadSourceGenerator:
225
226
 
226
227
  def _generate_structure(self, field: Field) -> Iterator[str]:
227
228
  template = f"""
228
- _s = stream.tell()
229
+ {"_s = stream.tell()" if field.type.dynamic else ""}
229
230
  r["{field._name}"] = {self._map_field(field)}._read(stream, context=r)
230
- s["{field._name}"] = stream.tell() - _s
231
+ {f's["{field._name}"] = stream.tell() - _s' if field.type.dynamic else ""}
231
232
  """
232
233
 
233
234
  yield dedent(template)
234
235
 
235
236
  def _generate_array(self, field: Field) -> Iterator[str]:
236
237
  template = f"""
237
- _s = stream.tell()
238
+ {"_s = stream.tell()" if field.type.dynamic else ""}
238
239
  r["{field._name}"] = {self._map_field(field)}._read(stream, context=r)
239
- s["{field._name}"] = stream.tell() - _s
240
+ {f's["{field._name}"] = stream.tell() - _s' if field.type.dynamic else ""}
240
241
  """
241
242
 
242
243
  yield dedent(template)
@@ -324,7 +325,6 @@ class _ReadSourceGenerator:
324
325
  parser = parser_template.format(type=self._map_field(field), getter=getter)
325
326
 
326
327
  reads.append(f'r["{field._name}"] = {parser}')
327
- reads.append(f's["{field._name}"] = {field_type.size}')
328
328
  reads.append("") # Generates a newline in the resulting code
329
329
 
330
330
  size += field_type.size
@@ -365,7 +365,7 @@ def _generate_struct_info(cs: cstruct, fields: list[Field], align: bool = False)
365
365
  read_type = _get_read_type(cs, field.type)
366
366
 
367
367
  # Drop voids
368
- if issubclass(read_type, Void):
368
+ if issubclass(read_type, (Void, VoidArray)):
369
369
  continue
370
370
 
371
371
  # Array of more complex types are handled elsewhere
@@ -48,11 +48,12 @@ class cstruct:
48
48
  DEF_CSTYLE = 1
49
49
  DEF_LEGACY = 2
50
50
 
51
- def __init__(self, endian: str = "<", pointer: str | None = None):
51
+ def __init__(self, load: str = "", *, endian: str = "<", pointer: str | None = None):
52
52
  self.endian = endian
53
53
 
54
54
  self.consts = {}
55
55
  self.lookups = {}
56
+ self.includes = []
56
57
  # fmt: off
57
58
  self.typedefs = {
58
59
  # Internal types
@@ -187,6 +188,9 @@ class cstruct:
187
188
  self.pointer: type[BaseType] = self.resolve(pointer)
188
189
  self._anonymous_count = 0
189
190
 
191
+ if load:
192
+ self.load(load)
193
+
190
194
  def __getattr__(self, attr: str) -> Any:
191
195
  try:
192
196
  return self.consts[attr]
@@ -368,14 +372,14 @@ class cstruct:
368
372
  "null_terminated": null_terminated,
369
373
  }
370
374
 
371
- return cast(type[Array], self._make_type(name, bases, size, alignment=type_.alignment, attrs=attrs))
375
+ return cast("type[Array]", self._make_type(name, bases, size, alignment=type_.alignment, attrs=attrs))
372
376
 
373
377
  def _make_int_type(self, name: str, size: int, signed: bool, *, alignment: int | None = None) -> type[Int]:
374
- return cast(type[Int], self._make_type(name, (Int,), size, alignment=alignment, attrs={"signed": signed}))
378
+ return cast("type[Int]", self._make_type(name, (Int,), size, alignment=alignment, attrs={"signed": signed}))
375
379
 
376
380
  def _make_packed_type(self, name: str, packchar: str, base: type, *, alignment: int | None = None) -> type[Packed]:
377
381
  return cast(
378
- type[Packed],
382
+ "type[Packed]",
379
383
  self._make_type(
380
384
  name,
381
385
  (base, Packed),
@@ -141,7 +141,7 @@ class ExpressionTokenizer:
141
141
  self.tokens.append(">>")
142
142
  elif self.match(expected="<", append=False) and self.match(expected="<", append=False):
143
143
  self.tokens.append("<<")
144
- elif self.match(expected={" ", "\t"}, append=False):
144
+ elif self.match(expected={" ", "\n", "\t"}, append=False):
145
145
  continue
146
146
  else:
147
147
  raise ExpressionTokenizerError(
@@ -187,8 +187,7 @@ class Expression:
187
187
  "sizeof": 6,
188
188
  }
189
189
 
190
- def __init__(self, cstruct: cstruct, expression: str):
191
- self.cstruct = cstruct
190
+ def __init__(self, expression: str):
192
191
  self.expression = expression
193
192
  self.tokens = ExpressionTokenizer(expression).tokenize()
194
193
  self.stack = []
@@ -222,7 +221,7 @@ class Expression:
222
221
  def is_number(self, token: str) -> bool:
223
222
  return token.isnumeric() or (len(token) > 2 and token[0] == "0" and token[1] in ("x", "X", "b", "B", "o", "O"))
224
223
 
225
- def evaluate(self, context: dict[str, int] | None = None) -> int:
224
+ def evaluate(self, cs: cstruct, context: dict[str, int] | None = None) -> int:
226
225
  """Evaluates an expression using a Shunting-Yard implementation."""
227
226
 
228
227
  self.stack = []
@@ -249,14 +248,14 @@ class Expression:
249
248
  self.queue.append(int(current_token, 0))
250
249
  elif current_token in context:
251
250
  self.queue.append(int(context[current_token]))
252
- elif current_token in self.cstruct.consts:
253
- self.queue.append(int(self.cstruct.consts[current_token]))
251
+ elif current_token in cs.consts:
252
+ self.queue.append(int(cs.consts[current_token]))
254
253
  elif current_token in self.unary_operators:
255
254
  self.stack.append(current_token)
256
255
  elif current_token == "sizeof":
257
256
  if len(tmp_expression) < i + 3 or (tmp_expression[i + 1] != "(" or tmp_expression[i + 3] != ")"):
258
257
  raise ExpressionParserError("Invalid sizeof operation")
259
- self.queue.append(len(self.cstruct.resolve(tmp_expression[i + 2])))
258
+ self.queue.append(len(cs.resolve(tmp_expression[i + 2])))
260
259
  i += 3
261
260
  elif current_token in operators:
262
261
  while (
@@ -49,12 +49,19 @@ class TokenParser(Parser):
49
49
  self.compiled = compiled
50
50
  self.align = align
51
51
  self.TOK = self._tokencollection()
52
+ self._conditionals = []
53
+ self._conditionals_depth = 0
52
54
 
53
55
  @staticmethod
54
56
  def _tokencollection() -> TokenCollection:
55
57
  TOK = TokenCollection()
56
58
  TOK.add(r"#\[(?P<values>[^\]]+)\](?=\s*)", "CONFIG_FLAG")
57
- TOK.add(r"#define\s+(?P<name>[^\s]+)\s+(?P<value>[^\r\n]+)\s*", "DEFINE")
59
+ TOK.add(r"#define\s+(?P<name>[^\s]+)(?P<value>[^\r\n]*)", "DEFINE")
60
+ TOK.add(r"#undef\s+(?P<name>[^\s]+)\s*", "UNDEF")
61
+ TOK.add(r"#ifdef\s+(?P<name>[^\s]+)\s*", "IFDEF")
62
+ TOK.add(r"#ifndef\s+(?P<name>[^\s]+)\s*", "IFNDEF")
63
+ TOK.add(r"#else\s*", "ELSE")
64
+ TOK.add(r"#endif\s*", "ENDIF")
58
65
  TOK.add(r"typedef(?=\s)", "TYPEDEF")
59
66
  TOK.add(r"(?:struct|union)(?=\s|{)", "STRUCT")
60
67
  TOK.add(
@@ -63,7 +70,8 @@ class TokenParser(Parser):
63
70
  "ENUM",
64
71
  )
65
72
  TOK.add(r"(?<=})\s*(?P<defs>(?:[a-zA-Z0-9_]+\s*,\s*)+[a-zA-Z0-9_]+)\s*(?=;)", "DEFS")
66
- TOK.add(r"(?P<name>\**?\s*[a-zA-Z0-9_]+)(?:\s*:\s*(?P<bits>\d+))?(?:\[(?P<count>[^;\n]*)\])?\s*(?=;)", "NAME")
73
+ TOK.add(r"(?P<name>\**?\s*[a-zA-Z0-9_]+)(?:\s*:\s*(?P<bits>\d+))?(?:\[(?P<count>[^;]*)\])?\s*(?=;)", "NAME")
74
+ TOK.add(r"#include\s+(?P<name>[^\s]+)\s*", "INCLUDE")
67
75
  TOK.add(r"[a-zA-Z_][a-zA-Z0-9_]*", "IDENTIFIER")
68
76
  TOK.add(r"[{}]", "BLOCK")
69
77
  TOK.add(r"\$(?P<name>[^\s]+) = (?P<value>{[^}]+})\w*[\r\n]+", "LOOKUP")
@@ -79,12 +87,61 @@ class TokenParser(Parser):
79
87
  idents.append(tokens.consume())
80
88
  return " ".join([i.value for i in idents])
81
89
 
90
+ def _conditional(self, tokens: TokenConsumer) -> None:
91
+ token = tokens.consume()
92
+ pattern = self.TOK.patterns[token.token]
93
+ match = pattern.match(token.value).groupdict()
94
+
95
+ value = match["name"]
96
+
97
+ if token.token == self.TOK.IFDEF:
98
+ self._conditionals.append(value in self.cstruct.consts)
99
+ elif token.token == self.TOK.IFNDEF:
100
+ self._conditionals.append(value not in self.cstruct.consts)
101
+
102
+ def _check_conditional(self, tokens: TokenConsumer) -> bool:
103
+ """Check and handle conditionals. Return a boolean indicating if we need to continue to the next token."""
104
+ if self._conditionals and self._conditionals_depth == len(self._conditionals):
105
+ # If we have a conditional and the depth matches, handle it accordingly
106
+ if tokens.next == self.TOK.ELSE:
107
+ # Flip the last conditional
108
+ tokens.consume()
109
+ self._conditionals[-1] = not self._conditionals[-1]
110
+ return True
111
+
112
+ if tokens.next == self.TOK.ENDIF:
113
+ # Pop the last conditional
114
+ tokens.consume()
115
+ self._conditionals.pop()
116
+ self._conditionals_depth -= 1
117
+ return True
118
+
119
+ if tokens.next in (self.TOK.IFDEF, self.TOK.IFNDEF):
120
+ # If we encounter a new conditional, increase the depth
121
+ self._conditionals_depth += 1
122
+
123
+ if tokens.next == self.TOK.ENDIF:
124
+ # Similarly, decrease the depth if needed
125
+ self._conditionals_depth -= 1
126
+
127
+ if self._conditionals and not self._conditionals[-1]:
128
+ # If the last conditional evaluated to False, skip the next token
129
+ tokens.consume()
130
+ return True
131
+
132
+ if tokens.next in (self.TOK.IFDEF, self.TOK.IFNDEF):
133
+ # If the next token is a conditional, process it
134
+ self._conditional(tokens)
135
+ return True
136
+
137
+ return False
138
+
82
139
  def _constant(self, tokens: TokenConsumer) -> None:
83
140
  const = tokens.consume()
84
141
  pattern = self.TOK.patterns[self.TOK.DEFINE]
85
142
  match = pattern.match(const.value).groupdict()
86
143
 
87
- value = match["value"]
144
+ value = match["value"].strip()
88
145
  try:
89
146
  value = ast.literal_eval(value)
90
147
  except (ValueError, SyntaxError):
@@ -92,12 +149,22 @@ class TokenParser(Parser):
92
149
 
93
150
  if isinstance(value, str):
94
151
  try:
95
- value = Expression(self.cstruct, value).evaluate()
152
+ value = Expression(value).evaluate(self.cstruct)
96
153
  except (ExpressionParserError, ExpressionTokenizerError):
97
154
  pass
98
155
 
99
156
  self.cstruct.consts[match["name"]] = value
100
157
 
158
+ def _undef(self, tokens: TokenConsumer) -> None:
159
+ const = tokens.consume()
160
+ pattern = self.TOK.patterns[self.TOK.UNDEF]
161
+ match = pattern.match(const.value).groupdict()
162
+
163
+ if match["name"] in self.cstruct.consts:
164
+ del self.cstruct.consts[match["name"]]
165
+ else:
166
+ raise ParserError(f"line {self._lineno(const)}: constant {match['name']!r} not defined")
167
+
101
168
  def _enum(self, tokens: TokenConsumer) -> None:
102
169
  # We cheat with enums because the entire enum is in the token
103
170
  etok = tokens.consume()
@@ -120,7 +187,7 @@ class TokenParser(Parser):
120
187
  if not key:
121
188
  continue
122
189
 
123
- val = nextval if not val else Expression(self.cstruct, val).evaluate(values)
190
+ val = nextval if not val else Expression(val).evaluate(self.cstruct, values)
124
191
 
125
192
  if enumtype == "flag":
126
193
  high_bit = val.bit_length() - 1
@@ -193,7 +260,7 @@ class TokenParser(Parser):
193
260
  if tokens.next == self.TOK.NAME:
194
261
  # As part of a struct field
195
262
  # struct type_name field_name;
196
- if not len(names):
263
+ if not names:
197
264
  raise ParserError(f"line {self._lineno(tokens.next)}: unexpected anonymous struct")
198
265
  return self.cstruct.resolve(names[0])
199
266
 
@@ -207,6 +274,9 @@ class TokenParser(Parser):
207
274
  tokens.consume()
208
275
  break
209
276
 
277
+ if self._check_conditional(tokens):
278
+ continue
279
+
210
280
  field = self._parse_field(tokens)
211
281
  fields.append(field)
212
282
 
@@ -265,7 +335,7 @@ class TokenParser(Parser):
265
335
  return Field(None, type_, None)
266
336
 
267
337
  if tokens.next != self.TOK.NAME:
268
- raise ParserError(f"line {self._lineno(tokens.next)}: expected name")
338
+ raise ParserError(f"line {self._lineno(tokens.next)}: expected name, got {tokens.next!r}")
269
339
  nametok = tokens.consume()
270
340
 
271
341
  type_, name, bits = self._parse_field_type(type_, nametok.value)
@@ -293,9 +363,9 @@ class TokenParser(Parser):
293
363
  if count == "":
294
364
  count = None
295
365
  else:
296
- count = Expression(self.cstruct, count)
366
+ count = Expression(count)
297
367
  try:
298
- count = count.evaluate()
368
+ count = count.evaluate(self.cstruct)
299
369
  except Exception:
300
370
  pass
301
371
 
@@ -313,17 +383,24 @@ class TokenParser(Parser):
313
383
  tokens.eol()
314
384
  break
315
385
 
316
- if tokens.next not in (self.TOK.NAME, self.TOK.DEFS):
386
+ if tokens.next not in (self.TOK.NAME, self.TOK.DEFS, self.TOK.IDENTIFIER):
317
387
  break
318
388
 
319
389
  ntoken = tokens.consume()
320
- if ntoken == self.TOK.NAME:
390
+ if ntoken in (self.TOK.NAME, self.TOK.IDENTIFIER):
321
391
  names.append(ntoken.value.strip())
322
392
  elif ntoken == self.TOK.DEFS:
323
393
  names.extend([name.strip() for name in ntoken.value.strip().split(",")])
324
394
 
325
395
  return names
326
396
 
397
+ def _include(self, tokens: TokenConsumer) -> None:
398
+ include = tokens.consume()
399
+ pattern = self.TOK.patterns[self.TOK.INCLUDE]
400
+ match = pattern.match(include.value).groupdict()
401
+
402
+ self.cstruct.includes.append(match["name"].strip().strip("'\""))
403
+
327
404
  @staticmethod
328
405
  def _remove_comments(string: str) -> str:
329
406
  # https://stackoverflow.com/a/18381470
@@ -370,10 +447,15 @@ class TokenParser(Parser):
370
447
  if token is None:
371
448
  break
372
449
 
450
+ if self._check_conditional(tokens):
451
+ continue
452
+
373
453
  if token == self.TOK.CONFIG_FLAG:
374
454
  self._config_flag(tokens)
375
455
  elif token == self.TOK.DEFINE:
376
456
  self._constant(tokens)
457
+ elif token == self.TOK.UNDEF:
458
+ self._undef(tokens)
377
459
  elif token == self.TOK.TYPEDEF:
378
460
  self._typedef(tokens)
379
461
  elif token == self.TOK.STRUCT:
@@ -382,9 +464,14 @@ class TokenParser(Parser):
382
464
  self._enum(tokens)
383
465
  elif token == self.TOK.LOOKUP:
384
466
  self._lookup(tokens)
467
+ elif token == self.TOK.INCLUDE:
468
+ self._include(tokens)
385
469
  else:
386
470
  raise ParserError(f"line {self._lineno(token)}: unexpected token {token!r}")
387
471
 
472
+ if self._conditionals:
473
+ raise ParserError(f"line {self._lineno(tokens.previous)}: unclosed conditional statement")
474
+
388
475
 
389
476
  class CStyleParser(Parser):
390
477
  """Definition parser for C-like structure syntax.
@@ -434,7 +521,7 @@ class CStyleParser(Parser):
434
521
  if not key:
435
522
  continue
436
523
 
437
- val = nextval if not val else Expression(self.cstruct, val).evaluate()
524
+ val = nextval if not val else Expression(val).evaluate(self.cstruct)
438
525
 
439
526
  if enumtype == "flag":
440
527
  high_bit = val.bit_length() - 1
@@ -515,9 +602,9 @@ class CStyleParser(Parser):
515
602
  if d["count"] == "":
516
603
  count = None
517
604
  else:
518
- count = Expression(self.cstruct, d["count"])
605
+ count = Expression(d["count"])
519
606
  try:
520
- count = count.evaluate()
607
+ count = count.evaluate(self.cstruct)
521
608
  except Exception:
522
609
  pass
523
610
 
@@ -92,6 +92,9 @@ def generate_cstruct_stub(cs: cstruct, module_prefix: str = "", cls_name: str =
92
92
  stub = f"{name}: TypeAlias = {typedef.__name__}"
93
93
  elif issubclass(typedef, (types.Enum, types.Flag)):
94
94
  stub = generate_enum_stub(typedef, cs_prefix=cs_prefix, module_prefix=module_prefix)
95
+ elif issubclass(typedef, types.Pointer):
96
+ typehint = generate_typehint(typedef, prefix=cs_prefix, module_prefix=module_prefix)
97
+ stub = f"{name}: TypeAlias = {typehint}"
95
98
  elif issubclass(typedef, types.Structure):
96
99
  stub = generate_structure_stub(typedef, cs_prefix=cs_prefix, module_prefix=module_prefix)
97
100
  elif issubclass(typedef, types.BaseType):
@@ -7,7 +7,7 @@ from dissect.cstruct.types.leb128 import LEB128
7
7
  from dissect.cstruct.types.packed import Packed
8
8
  from dissect.cstruct.types.pointer import Pointer
9
9
  from dissect.cstruct.types.structure import Field, Structure, Union
10
- from dissect.cstruct.types.void import Void
10
+ from dissect.cstruct.types.void import Void, VoidArray
11
11
  from dissect.cstruct.types.wchar import Wchar, WcharArray
12
12
 
13
13
  __all__ = [
@@ -27,6 +27,7 @@ __all__ = [
27
27
  "Structure",
28
28
  "Union",
29
29
  "Void",
30
+ "VoidArray",
30
31
  "Wchar",
31
32
  "WcharArray",
32
33
  ]
@@ -54,6 +54,10 @@ class MetaType(type):
54
54
  """Create a new array with the given number of entries."""
55
55
  return cls.cs._make_array(cls, num_entries)
56
56
 
57
+ def __bool__(cls) -> bool:
58
+ """Type class is always truthy."""
59
+ return True
60
+
57
61
  def __len__(cls) -> int:
58
62
  """Return the byte size of the type."""
59
63
  # Python 3.9 compat thing for bound type vars
@@ -258,7 +262,7 @@ class BaseArray(BaseType):
258
262
  num = EOF
259
263
  elif isinstance(cls.num_entries, Expression):
260
264
  try:
261
- num = max(0, cls.num_entries.evaluate(context))
265
+ num = max(0, cls.num_entries.evaluate(cls.cs, context))
262
266
  except Exception:
263
267
  if cls.num_entries.expression != "EOF":
264
268
  raise