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
@@ -1,304 +1,221 @@
1
1
  from __future__ import annotations
2
2
 
3
- import string
4
- from typing import TYPE_CHECKING, ClassVar
3
+ from typing import TYPE_CHECKING
5
4
 
6
- from dissect.cstruct.exceptions import ExpressionParserError, ExpressionTokenizerError
5
+ from dissect.cstruct.exception import ExpressionParserError
6
+ from dissect.cstruct.lexer import IDENTIFIER_TYPES, Lexer, TokenCursor, TokenType
7
+ from dissect.cstruct.util import offsetof, sizeof
7
8
 
8
9
  if TYPE_CHECKING:
9
10
  from collections.abc import Callable
10
11
 
11
12
  from dissect.cstruct import cstruct
12
-
13
-
14
- HEXBIN_SUFFIX = {"x", "X", "b", "B"}
15
-
16
-
17
- class ExpressionTokenizer:
18
- def __init__(self, expression: str):
19
- self.expression = expression
20
- self.pos = 0
21
- self.tokens = []
22
-
23
- def equal(self, token: str, expected: str | set[str]) -> bool:
24
- if isinstance(expected, set):
25
- return token in expected
26
- return token == expected
27
-
28
- def alnum(self, token: str) -> bool:
29
- return token.isalnum()
30
-
31
- def alpha(self, token: str) -> bool:
32
- return token.isalpha()
33
-
34
- def digit(self, token: str) -> bool:
35
- return token.isdigit()
36
-
37
- def hexdigit(self, token: str) -> bool:
38
- return token in string.hexdigits
39
-
40
- def operator(self, token: str) -> bool:
41
- return token in {"*", "/", "+", "-", "%", "&", "^", "|", "(", ")", "~"}
42
-
43
- def match(
44
- self,
45
- func: Callable[[str], bool] | None = None,
46
- expected: str | None = None,
47
- consume: bool = True,
48
- append: bool = True,
49
- ) -> bool:
50
- if self.eol():
51
- return False
52
-
53
- token = self.get_token()
54
-
55
- if expected and self.equal(token, expected):
56
- if append:
57
- self.tokens.append(token)
58
- if consume:
59
- self.consume()
60
- return True
61
-
62
- if func and func(token):
63
- if append:
64
- self.tokens.append(token)
65
- if consume:
66
- self.consume()
67
- return True
68
-
69
- return False
70
-
71
- def consume(self) -> None:
72
- self.pos += 1
73
-
74
- def eol(self) -> bool:
75
- return self.pos >= len(self.expression)
76
-
77
- def get_token(self) -> str:
78
- if self.eol():
79
- raise ExpressionTokenizerError(f"Out of bounds index: {self.pos}, length: {len(self.expression)}")
80
- return self.expression[self.pos]
81
-
82
- def tokenize(self) -> list[str]:
83
- token = ""
84
-
85
- # Loop over expression runs in linear time
86
- while not self.eol():
87
- # If token is a single character operand add it to tokens
88
- if self.match(self.operator):
89
- continue
90
-
91
- # If token is a single digit, keep looping over expression and build the number
92
- if self.match(self.digit, consume=False, append=False):
93
- token += self.get_token()
94
- self.consume()
95
-
96
- # Support for binary and hexadecimal notation
97
- if self.match(expected=HEXBIN_SUFFIX, consume=False, append=False):
98
- token += self.get_token()
99
- self.consume()
100
-
101
- while self.match(self.hexdigit, consume=False, append=False):
102
- token += self.get_token()
103
- self.consume()
104
- if self.eol():
105
- break
106
-
107
- # Checks for suffixes in numbers
108
- if self.match(expected={"u", "U"}, consume=False, append=False):
109
- self.consume()
110
- self.match(expected={"l", "L"}, append=False)
111
- self.match(expected={"l", "L"}, append=False)
112
-
113
- elif self.match(expected={"l", "L"}, append=False):
114
- self.match(expected={"l", "L"}, append=False)
115
- self.match(expected={"u", "U"}, append=False)
116
- else:
117
- pass
118
-
119
- # Number cannot end on x or b in the case of binary or hexadecimal notation
120
- if len(token) == 2 and token[-1] in HEXBIN_SUFFIX:
121
- raise ExpressionTokenizerError("Invalid binary or hex notation")
122
-
123
- if len(token) > 1 and token[0] == "0" and token[1] not in HEXBIN_SUFFIX:
124
- token = token[:1] + "o" + token[1:]
125
- self.tokens.append(token)
126
- token = ""
127
-
128
- # If token is alpha or underscore we need to build the identifier
129
- elif self.match(self.alpha, consume=False, append=False) or self.match(
130
- expected="_", consume=False, append=False
131
- ):
132
- while self.match(self.alnum, consume=False, append=False) or self.match(
133
- expected="_", consume=False, append=False
134
- ):
135
- token += self.get_token()
136
- self.consume()
137
- if self.eol():
138
- break
139
- self.tokens.append(token)
140
- token = ""
141
- # If token is length 2 operand make sure next character is part of length 2 operand append to tokens
142
- elif self.match(expected=">", append=False) and self.match(expected=">", append=False):
143
- self.tokens.append(">>")
144
- elif self.match(expected="<", append=False) and self.match(expected="<", append=False):
145
- self.tokens.append("<<")
146
- elif self.match(expected={" ", "\n", "\t"}, append=False):
147
- continue
148
- else:
149
- raise ExpressionTokenizerError(
150
- f"Tokenizer does not recognize following token '{self.expression[self.pos]}'"
151
- )
152
- return self.tokens
153
-
154
-
155
- class Expression:
13
+ from dissect.cstruct.lexer import Token
14
+
15
+ _MISSING = object()
16
+
17
+ BINARY_OPERATORS: dict[TokenType, Callable[[int, int], int]] = {
18
+ TokenType.PIPE: lambda a, b: a | b,
19
+ TokenType.CARET: lambda a, b: a ^ b,
20
+ TokenType.AMPERSAND: lambda a, b: a & b,
21
+ TokenType.LSHIFT: lambda a, b: a << b,
22
+ TokenType.RSHIFT: lambda a, b: a >> b,
23
+ TokenType.PLUS: lambda a, b: a + b,
24
+ TokenType.MINUS: lambda a, b: a - b,
25
+ TokenType.STAR: lambda a, b: a * b,
26
+ TokenType.SLASH: lambda a, b: a // b,
27
+ TokenType.PERCENT: lambda a, b: a % b,
28
+ }
29
+
30
+ UNARY_OPERATORS: dict[TokenType, Callable[[int], int]] = {
31
+ TokenType.UNARY_MINUS: lambda a: -a,
32
+ TokenType.TILDE: lambda a: ~a,
33
+ }
34
+
35
+ OPERATORS = set(BINARY_OPERATORS.keys()) | set(UNARY_OPERATORS.keys())
36
+
37
+ FUNCTION_TOKENS = {
38
+ TokenType.SIZEOF: 1,
39
+ TokenType.OFFSETOF: 2,
40
+ }
41
+
42
+ PRECEDENCE_LEVELS = {
43
+ TokenType.PIPE: 0,
44
+ TokenType.CARET: 1,
45
+ TokenType.AMPERSAND: 2,
46
+ TokenType.LSHIFT: 3,
47
+ TokenType.RSHIFT: 3,
48
+ TokenType.PLUS: 4,
49
+ TokenType.MINUS: 4,
50
+ TokenType.STAR: 5,
51
+ TokenType.SLASH: 5,
52
+ TokenType.PERCENT: 5,
53
+ TokenType.UNARY_MINUS: 6,
54
+ TokenType.TILDE: 6,
55
+ # Functions
56
+ TokenType.SIZEOF: 7,
57
+ TokenType.OFFSETOF: 7,
58
+ }
59
+
60
+
61
+ def precedence(o1: TokenType, o2: TokenType) -> bool:
62
+ # Unary (prefix) operators are right-associative, so use strict greater-than
63
+ if o2 in UNARY_OPERATORS:
64
+ return PRECEDENCE_LEVELS[o1] > PRECEDENCE_LEVELS[o2]
65
+ return PRECEDENCE_LEVELS[o1] >= PRECEDENCE_LEVELS[o2]
66
+
67
+
68
+ class Expression(TokenCursor):
156
69
  """Expression parser for calculations in definitions."""
157
70
 
158
- binary_operators: ClassVar[dict[str, Callable[[int, int], int]]] = {
159
- "|": lambda a, b: a | b,
160
- "^": lambda a, b: a ^ b,
161
- "&": lambda a, b: a & b,
162
- "<<": lambda a, b: a << b,
163
- ">>": lambda a, b: a >> b,
164
- "+": lambda a, b: a + b,
165
- "-": lambda a, b: a - b,
166
- "*": lambda a, b: a * b,
167
- "/": lambda a, b: a // b,
168
- "%": lambda a, b: a % b,
169
- }
170
-
171
- unary_operators: ClassVar[dict[str, Callable[[int], int]]] = {
172
- "u": lambda a: -a,
173
- "~": lambda a: ~a,
174
- }
175
-
176
- precedence_levels: ClassVar[dict[str, int]] = {
177
- "|": 0,
178
- "^": 1,
179
- "&": 2,
180
- "<<": 3,
181
- ">>": 3,
182
- "+": 4,
183
- "-": 4,
184
- "*": 5,
185
- "/": 5,
186
- "%": 5,
187
- "u": 6,
188
- "~": 6,
189
- "sizeof": 6,
190
- }
191
-
192
71
  def __init__(self, expression: str):
193
72
  self.expression = expression
194
- self.tokens = ExpressionTokenizer(expression).tokenize()
195
- self.stack = []
196
- self.queue = []
73
+
74
+ tokens = Lexer(expression).tokenize()
75
+ super().__init__(tokens)
76
+ self._stack: list[TokenType] = []
77
+ self._queue: list[int | str] = []
197
78
 
198
79
  def __repr__(self) -> str:
199
80
  return self.expression
200
81
 
201
- def precedence(self, o1: str, o2: str) -> bool:
202
- return self.precedence_levels[o1] >= self.precedence_levels[o2]
82
+ def _reset(self) -> None:
83
+ """Reset the expression state for a new input."""
84
+ self._reset_cursor()
85
+ self._stack = []
86
+ self._queue = []
203
87
 
204
- def evaluate_exp(self) -> None:
205
- operator = self.stack.pop(-1)
206
- res = 0
88
+ def _error(self, msg: str, *, token: Token | None = None) -> ExpressionParserError:
89
+ return ExpressionParserError(f"line {(token if token is not None else self._current()).line}: {msg}")
207
90
 
208
- if len(self.queue) < 1:
209
- raise ExpressionParserError("Invalid expression: not enough operands")
91
+ def _evaluate_expression(self, cs: cstruct) -> None:
92
+ operator = self._stack.pop(-1)
93
+ result = 0
210
94
 
211
- right = self.queue.pop(-1)
212
- if operator in self.unary_operators:
213
- res = self.unary_operators[operator](right)
214
- else:
215
- if len(self.queue) < 1:
95
+ if operator in UNARY_OPERATORS:
96
+ if len(self._queue) < 1:
216
97
  raise ExpressionParserError("Invalid expression: not enough operands")
217
98
 
218
- left = self.queue.pop(-1)
219
- res = self.binary_operators[operator](left, right)
99
+ result = UNARY_OPERATORS[operator](self._queue.pop(-1))
100
+ elif operator in BINARY_OPERATORS:
101
+ if len(self._queue) < 2:
102
+ raise ExpressionParserError("Invalid expression: not enough operands")
103
+
104
+ right = self._queue.pop(-1)
105
+ left = self._queue.pop(-1)
106
+ result = BINARY_OPERATORS[operator](left, right)
107
+ elif operator in FUNCTION_TOKENS:
108
+ num_args = FUNCTION_TOKENS[operator]
109
+ if len(self._queue) < num_args:
110
+ raise ExpressionParserError("Invalid expression: not enough operands")
220
111
 
221
- self.queue.append(res)
112
+ args = [self._queue.pop(-1) for _ in range(num_args)][::-1]
113
+ if operator == TokenType.SIZEOF:
114
+ type_ = cs.resolve(args[0])
115
+ result = sizeof(type_)
116
+ elif operator == TokenType.OFFSETOF:
117
+ type_ = cs.resolve(args[0])
118
+ result = offsetof(type_, args[1])
222
119
 
223
- def is_number(self, token: str) -> bool:
224
- return token.isnumeric() or (len(token) > 2 and token[0] == "0" and token[1] in ("x", "X", "b", "B", "o", "O"))
120
+ self._queue.append(result)
225
121
 
226
122
  def evaluate(self, cs: cstruct, context: dict[str, int] | None = None) -> int:
227
123
  """Evaluates an expression using a Shunting-Yard implementation."""
228
- self.stack = []
229
- self.queue = []
230
- operators = set(self.binary_operators.keys()) | set(self.unary_operators.keys())
231
-
124
+ self._reset()
232
125
  context = context or {}
233
- tmp_expression = self.tokens
234
-
235
- # Unary minus tokens; we change the semantic of '-' depending on the previous token
236
- for i in range(len(self.tokens)):
237
- if self.tokens[i] == "-":
238
- if i == 0:
239
- self.tokens[i] = "u"
240
- continue
241
- if self.tokens[i - 1] in operators or self.tokens[i - 1] == "u" or self.tokens[i - 1] == "(":
242
- self.tokens[i] = "u"
243
- continue
244
-
245
- i = 0
246
- while i < len(tmp_expression):
247
- current_token = tmp_expression[i]
248
- if self.is_number(current_token):
249
- self.queue.append(int(current_token, 0))
250
- elif current_token in context:
251
- self.queue.append(int(context[current_token]))
252
- elif current_token in cs.consts:
253
- self.queue.append(int(cs.consts[current_token]))
254
- elif current_token in self.unary_operators:
255
- self.stack.append(current_token)
256
- elif current_token == "sizeof":
257
- if len(tmp_expression) < i + 3 or (tmp_expression[i + 1] != "(" or tmp_expression[i + 3] != ")"):
258
- raise ExpressionParserError("Invalid sizeof operation")
259
- self.queue.append(len(cs.resolve(tmp_expression[i + 2])))
260
- i += 3
261
- elif current_token in operators:
126
+
127
+ while (token := self._current()).type != TokenType.EOF:
128
+ if token.type == TokenType.NUMBER:
129
+ self._queue.append(int(self._take().value, 0))
130
+
131
+ elif token.type in OPERATORS:
262
132
  while (
263
- len(self.stack) != 0 and self.stack[-1] != "(" and (self.precedence(self.stack[-1], current_token))
133
+ len(self._stack) != 0
134
+ and self._stack[-1] != TokenType.LPAREN
135
+ and precedence(self._stack[-1], token.type)
264
136
  ):
265
- self.evaluate_exp()
266
- self.stack.append(current_token)
267
- elif current_token == "(":
268
- if i > 0:
269
- previous_token = tmp_expression[i - 1]
270
- if self.is_number(previous_token):
271
- raise ExpressionParserError(
272
- f"Parser expected sizeof or an arethmethic operator instead got: '{previous_token}'"
273
- )
274
-
275
- self.stack.append(current_token)
276
- elif current_token == ")":
277
- if i > 0:
278
- previous_token = tmp_expression[i - 1]
279
- if previous_token == "(":
280
- raise ExpressionParserError(
281
- f"Parser expected an expression, instead received empty parenthesis. Index: {i}"
282
- )
283
-
284
- if len(self.stack) == 0:
285
- raise ExpressionParserError("Invalid expression")
286
-
287
- while self.stack[-1] != "(":
288
- self.evaluate_exp()
289
-
290
- self.stack.pop(-1)
291
- else:
292
- raise ExpressionParserError(f"Unmatched token: '{current_token}'")
293
- i += 1
137
+ self._evaluate_expression(cs)
138
+
139
+ self._stack.append(self._take().type)
140
+
141
+ elif token.type in FUNCTION_TOKENS:
142
+ func = self._take().type
143
+ self._stack.append(func)
144
+
145
+ self._expect(TokenType.LPAREN)
146
+
147
+ num_args = FUNCTION_TOKENS[func]
148
+ while num_args > 1:
149
+ self._queue.append(self._collect_until(TokenType.COMMA))
150
+ self._expect(TokenType.COMMA)
151
+ num_args -= 1
152
+
153
+ self._queue.append(self._collect_until(TokenType.RPAREN))
154
+ self._expect(TokenType.RPAREN)
155
+
156
+ # Evaluate immediately
157
+ self._evaluate_expression(cs)
158
+
159
+ elif token.type in IDENTIFIER_TYPES:
160
+ ident = self._take().value
161
+
162
+ obj = None
163
+ for i, part in enumerate(ident.split(".")):
164
+ if i == 0:
165
+ if part in context:
166
+ obj = context[part]
167
+ elif part in cs.consts:
168
+ obj = cs.consts[part]
169
+ elif part in cs.types:
170
+ obj = cs.types[part]
171
+ else:
172
+ raise self._error(f"Unknown identifier: '{ident}'", token=token)
173
+ else:
174
+ obj = obj.get(part, _MISSING) if isinstance(obj, dict) else getattr(obj, part, _MISSING)
175
+ if obj is _MISSING:
176
+ raise self._error(f"Unknown identifier: '{ident}'", token=token)
177
+
178
+ try:
179
+ self._queue.append(int(obj))
180
+ except (ValueError, TypeError):
181
+ raise self._error(f"Identifier '{ident}' does not resolve to an integer", token=token)
182
+
183
+ elif token.type == TokenType.LPAREN:
184
+ if self._previous().type == TokenType.NUMBER:
185
+ raise self._error(
186
+ f"Parser expected sizeof or an arethmethic operator instead got: '{self._previous().value}'",
187
+ token=self._previous(),
188
+ )
189
+
190
+ self._stack.append(self._take().type)
191
+
192
+ elif token.type == TokenType.RPAREN:
193
+ if self._previous().type == TokenType.LPAREN:
194
+ raise self._error(
195
+ "Parser expected an expression, instead received empty parenthesis.",
196
+ token=self._previous(),
197
+ )
198
+
199
+ if len(self._stack) == 0:
200
+ raise self._error("Mismatched parentheses")
201
+
202
+ while self._stack[-1] != TokenType.LPAREN:
203
+ self._evaluate_expression(cs)
204
+ if len(self._stack) == 0:
205
+ raise self._error("Mismatched parentheses")
206
+
207
+ self._stack.pop(-1) # Pop the '('
208
+ self._take()
294
209
 
295
- while len(self.stack) != 0:
296
- if self.stack[-1] == "(":
297
- raise ExpressionParserError("Invalid expression")
210
+ else:
211
+ raise self._error(f"Unmatched token: '{token.value}'", token=token)
298
212
 
299
- self.evaluate_exp()
213
+ while len(self._stack) != 0:
214
+ if TokenType.LPAREN in self._stack:
215
+ raise self._error("Mismatched parentheses")
216
+ self._evaluate_expression(cs)
300
217
 
301
- if len(self.queue) != 1:
302
- raise ExpressionParserError("Invalid expression")
218
+ if len(self._queue) != 1:
219
+ raise self._error("Invalid expression: too many operands")
303
220
 
304
- return self.queue[0]
221
+ return self._queue[0]