dissect.cstruct 4.8.dev8__tar.gz → 5.0.dev0__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 (85) hide show
  1. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/PKG-INFO +1 -1
  2. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/dissect/cstruct/cstruct.py +32 -9
  3. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/dissect/cstruct/exceptions.py +4 -4
  4. dissect_cstruct-5.0.dev0/dissect/cstruct/expression.py +222 -0
  5. dissect_cstruct-5.0.dev0/dissect/cstruct/lexer.py +574 -0
  6. dissect_cstruct-5.0.dev0/dissect/cstruct/parser.py +546 -0
  7. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/dissect/cstruct/utils.py +24 -10
  8. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/dissect.cstruct.egg-info/PKG-INFO +1 -1
  9. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/dissect.cstruct.egg-info/SOURCES.txt +4 -0
  10. dissect_cstruct-5.0.dev0/dissect.cstruct.egg-info/scm_file_list.json +78 -0
  11. dissect_cstruct-5.0.dev0/dissect.cstruct.egg-info/scm_version.json +8 -0
  12. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/pyproject.toml +0 -1
  13. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/tests/_docs/conf.py +0 -1
  14. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/tests/test_basic.py +38 -19
  15. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/tests/test_benchmark.py +26 -0
  16. dissect_cstruct-5.0.dev0/tests/test_expression.py +271 -0
  17. dissect_cstruct-5.0.dev0/tests/test_lexer.py +202 -0
  18. dissect_cstruct-5.0.dev0/tests/test_parser.py +546 -0
  19. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/tests/test_tools_stubgen.py +10 -10
  20. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/tests/test_types_pointer.py +1 -1
  21. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/tests/test_types_structure.py +13 -9
  22. dissect_cstruct-4.8.dev8/dissect/cstruct/expression.py +0 -304
  23. dissect_cstruct-4.8.dev8/dissect/cstruct/parser.py +0 -713
  24. dissect_cstruct-4.8.dev8/tests/test_expression.py +0 -129
  25. dissect_cstruct-4.8.dev8/tests/test_parser.py +0 -296
  26. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/.git-blame-ignore-revs +0 -0
  27. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/.gitattributes +0 -0
  28. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/CHANGELOG.md +0 -0
  29. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/COPYRIGHT +0 -0
  30. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/LICENSE +0 -0
  31. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/MANIFEST.in +0 -0
  32. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/README.md +0 -0
  33. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/dissect/cstruct/__init__.py +0 -0
  34. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/dissect/cstruct/bitbuffer.py +0 -0
  35. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/dissect/cstruct/compiler.py +0 -0
  36. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/dissect/cstruct/tools/__init__.py +0 -0
  37. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/dissect/cstruct/tools/stubgen.py +0 -0
  38. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/dissect/cstruct/types/__init__.py +0 -0
  39. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/dissect/cstruct/types/base.py +0 -0
  40. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/dissect/cstruct/types/char.py +0 -0
  41. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/dissect/cstruct/types/enum.py +0 -0
  42. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/dissect/cstruct/types/flag.py +0 -0
  43. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/dissect/cstruct/types/int.py +0 -0
  44. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/dissect/cstruct/types/leb128.py +0 -0
  45. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/dissect/cstruct/types/packed.py +0 -0
  46. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/dissect/cstruct/types/pointer.py +0 -0
  47. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/dissect/cstruct/types/structure.py +0 -0
  48. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/dissect/cstruct/types/void.py +0 -0
  49. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/dissect/cstruct/types/wchar.py +0 -0
  50. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/dissect.cstruct.egg-info/dependency_links.txt +0 -0
  51. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/dissect.cstruct.egg-info/entry_points.txt +0 -0
  52. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/dissect.cstruct.egg-info/requires.txt +0 -0
  53. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/dissect.cstruct.egg-info/top_level.txt +0 -0
  54. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/examples/disk.py +0 -0
  55. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/examples/mirai.py +0 -0
  56. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/examples/pe.py +0 -0
  57. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/examples/protobuf.py +0 -0
  58. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/examples/secdesc.py +0 -0
  59. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/setup.cfg +0 -0
  60. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/tests/__init__.py +0 -0
  61. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/tests/_data/testdef.txt +0 -0
  62. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/tests/_docs/Makefile +0 -0
  63. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/tests/_docs/__init__.py +0 -0
  64. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/tests/_docs/index.rst +0 -0
  65. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/tests/conftest.py +0 -0
  66. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/tests/test_align.py +0 -0
  67. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/tests/test_annotations.py +0 -0
  68. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/tests/test_bitbuffer.py +0 -0
  69. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/tests/test_bitfield.py +0 -0
  70. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/tests/test_compiler.py +0 -0
  71. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/tests/test_ctypes.py +0 -0
  72. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/tests/test_types_base.py +0 -0
  73. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/tests/test_types_char.py +0 -0
  74. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/tests/test_types_custom.py +0 -0
  75. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/tests/test_types_enum.py +0 -0
  76. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/tests/test_types_flag.py +0 -0
  77. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/tests/test_types_int.py +0 -0
  78. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/tests/test_types_leb128.py +0 -0
  79. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/tests/test_types_packed.py +0 -0
  80. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/tests/test_types_union.py +0 -0
  81. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/tests/test_types_void.py +0 -0
  82. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/tests/test_types_wchar.py +0 -0
  83. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/tests/test_utils.py +0 -0
  84. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/tests/utils.py +0 -0
  85. {dissect_cstruct-4.8.dev8 → dissect_cstruct-5.0.dev0}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dissect.cstruct
3
- Version: 4.8.dev8
3
+ Version: 5.0.dev0
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-Expression: Apache-2.0
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import copy
3
4
  import ctypes as _ctypes
4
5
  import struct
5
6
  import sys
@@ -9,7 +10,7 @@ from typing import TYPE_CHECKING, Any, BinaryIO, TypeVar, cast
9
10
 
10
11
  from dissect.cstruct.exceptions import ResolveError
11
12
  from dissect.cstruct.expression import Expression
12
- from dissect.cstruct.parser import CStyleParser, TokenParser
13
+ from dissect.cstruct.parser import CStyleParser
13
14
  from dissect.cstruct.types import (
14
15
  LEB128,
15
16
  BaseArray,
@@ -25,6 +26,7 @@ from dissect.cstruct.types import (
25
26
  Void,
26
27
  Wchar,
27
28
  )
29
+ from dissect.cstruct.types.base import MetaType
28
30
 
29
31
  if TYPE_CHECKING:
30
32
  from collections.abc import Iterable
@@ -54,7 +56,6 @@ class cstruct:
54
56
  self.endian = endian
55
57
 
56
58
  self.consts = {}
57
- self.lookups = {}
58
59
  self.includes = []
59
60
  # fmt: off
60
61
  self.typedefs = {
@@ -206,6 +207,24 @@ class cstruct:
206
207
 
207
208
  raise AttributeError(f"Invalid attribute: {attr}")
208
209
 
210
+ def __copy__(self) -> cstruct:
211
+ cs = cstruct(endian=self.endian, pointer=self.pointer.__name__)
212
+ cs._anonymous_count = self._anonymous_count
213
+ cs.consts = self.consts.copy()
214
+ cs.includes = self.includes.copy()
215
+
216
+ # Update typedefs to point to the new cstruct instance
217
+ for name, type in self.typedefs.items():
218
+ if name in cs.typedefs:
219
+ continue
220
+
221
+ if isinstance(type, MetaType):
222
+ new_type = copy.copy(type)
223
+ new_type.cs = cs
224
+ cs.typedefs[name] = new_type
225
+
226
+ return cs
227
+
209
228
  def _next_anonymous(self) -> str:
210
229
  name = f"__anonymous_{self._anonymous_count}__"
211
230
  self._anonymous_count += 1
@@ -253,11 +272,7 @@ class cstruct:
253
272
  Definitions can be parsed using different parsers. Currently, there's
254
273
  only one supported parser - DEF_CSTYLE. Parsers can add types and
255
274
  modify this cstruct instance. Arguments can be passed to parsers
256
- using kwargs.
257
-
258
- The CSTYLE parser was recently replaced with token based parser,
259
- instead of a strictly regex based one. The old parser is still available
260
- by using DEF_LEGACY.
275
+ using ``kwargs``.
261
276
 
262
277
  Args:
263
278
  definition: The definition to parse.
@@ -267,9 +282,9 @@ class cstruct:
267
282
  deftype = deftype or cstruct.DEF_CSTYLE
268
283
 
269
284
  if deftype == cstruct.DEF_CSTYLE:
270
- TokenParser(self, **kwargs).parse(definition)
271
- elif deftype == cstruct.DEF_LEGACY:
272
285
  CStyleParser(self, **kwargs).parse(definition)
286
+ else:
287
+ raise ValueError(f"Unknown definition type: {deftype}")
273
288
 
274
289
  return self
275
290
 
@@ -328,6 +343,14 @@ class cstruct:
328
343
 
329
344
  raise ResolveError(f"Recursion limit exceeded while resolving type {name}")
330
345
 
346
+ def copy(self) -> cstruct:
347
+ """Create a copy of this cstruct instance.
348
+
349
+ Returns:
350
+ A new cstruct instance with the same types and settings as this one.
351
+ """
352
+ return copy.copy(self)
353
+
331
354
  def _make_type(
332
355
  self,
333
356
  name: str,
@@ -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
@@ -0,0 +1,222 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from dissect.cstruct.exceptions import ExpressionParserError
6
+ from dissect.cstruct.lexer import IDENTIFIER_TYPES, Lexer, TokenCursor, TokenType
7
+ from dissect.cstruct.utils import offsetof, sizeof
8
+
9
+ if TYPE_CHECKING:
10
+ from collections.abc import Callable
11
+
12
+ from dissect.cstruct import cstruct
13
+ from dissect.cstruct.lexer import Token
14
+
15
+ BINARY_OPERATORS: dict[TokenType, Callable[[int, int], int]] = {
16
+ TokenType.PIPE: lambda a, b: a | b,
17
+ TokenType.CARET: lambda a, b: a ^ b,
18
+ TokenType.AMPERSAND: lambda a, b: a & b,
19
+ TokenType.LSHIFT: lambda a, b: a << b,
20
+ TokenType.RSHIFT: lambda a, b: a >> b,
21
+ TokenType.PLUS: lambda a, b: a + b,
22
+ TokenType.MINUS: lambda a, b: a - b,
23
+ TokenType.STAR: lambda a, b: a * b,
24
+ TokenType.SLASH: lambda a, b: a // b,
25
+ TokenType.PERCENT: lambda a, b: a % b,
26
+ }
27
+
28
+ UNARY_OPERATORS: dict[TokenType, Callable[[int], int]] = {
29
+ TokenType.UNARY_MINUS: lambda a: -a,
30
+ TokenType.TILDE: lambda a: ~a,
31
+ }
32
+
33
+ OPERATORS = set(BINARY_OPERATORS.keys()) | set(UNARY_OPERATORS.keys())
34
+
35
+ FUNCTION_TOKENS = {
36
+ TokenType.SIZEOF: 1,
37
+ TokenType.OFFSETOF: 2,
38
+ }
39
+
40
+ PRECEDENCE_LEVELS = {
41
+ TokenType.PIPE: 0,
42
+ TokenType.CARET: 1,
43
+ TokenType.AMPERSAND: 2,
44
+ TokenType.LSHIFT: 3,
45
+ TokenType.RSHIFT: 3,
46
+ TokenType.PLUS: 4,
47
+ TokenType.MINUS: 4,
48
+ TokenType.STAR: 5,
49
+ TokenType.SLASH: 5,
50
+ TokenType.PERCENT: 5,
51
+ TokenType.UNARY_MINUS: 6,
52
+ TokenType.TILDE: 6,
53
+ # Functions
54
+ TokenType.SIZEOF: 7,
55
+ TokenType.OFFSETOF: 7,
56
+ }
57
+
58
+
59
+ def precedence(o1: TokenType, o2: TokenType) -> bool:
60
+ # Unary (prefix) operators are right-associative, so use strict greater-than
61
+ if o2 in UNARY_OPERATORS:
62
+ return PRECEDENCE_LEVELS[o1] > PRECEDENCE_LEVELS[o2]
63
+ return PRECEDENCE_LEVELS[o1] >= PRECEDENCE_LEVELS[o2]
64
+
65
+
66
+ class Expression(TokenCursor):
67
+ """Expression parser for calculations in definitions."""
68
+
69
+ def __init__(self, expression: str):
70
+ self.expression = expression
71
+
72
+ tokens = Lexer(expression).tokenize()
73
+ super().__init__(tokens)
74
+ self._stack: list[TokenType] = []
75
+ self._queue: list[int | str] = []
76
+
77
+ def __repr__(self) -> str:
78
+ return self.expression
79
+
80
+ def _reset(self) -> None:
81
+ """Reset the expression state for a new input."""
82
+ self._reset_cursor()
83
+ self._stack = []
84
+ self._queue = []
85
+
86
+ def _error(self, msg: str, *, token: Token | None = None) -> ExpressionParserError:
87
+ return ExpressionParserError(f"line {(token if token is not None else self._current()).line}: {msg}")
88
+
89
+ def _evaluate_expression(self, cs: cstruct) -> None:
90
+ operator = self._stack.pop(-1)
91
+ result = 0
92
+
93
+ if operator in UNARY_OPERATORS:
94
+ if len(self._queue) < 1:
95
+ raise ExpressionParserError("Invalid expression: not enough operands")
96
+
97
+ result = UNARY_OPERATORS[operator](self._queue.pop(-1))
98
+ elif operator in BINARY_OPERATORS:
99
+ if len(self._queue) < 2:
100
+ raise ExpressionParserError("Invalid expression: not enough operands")
101
+
102
+ right = self._queue.pop(-1)
103
+ left = self._queue.pop(-1)
104
+ result = BINARY_OPERATORS[operator](left, right)
105
+ elif operator in FUNCTION_TOKENS:
106
+ num_args = FUNCTION_TOKENS[operator]
107
+ if len(self._queue) < num_args:
108
+ raise ExpressionParserError("Invalid expression: not enough operands")
109
+
110
+ args = [self._queue.pop(-1) for _ in range(num_args)][::-1]
111
+ if operator == TokenType.SIZEOF:
112
+ type_ = cs.resolve(args[0])
113
+ result = sizeof(type_)
114
+ elif operator == TokenType.OFFSETOF:
115
+ type_ = cs.resolve(args[0])
116
+ result = offsetof(type_, args[1])
117
+
118
+ self._queue.append(result)
119
+
120
+ def evaluate(self, cs: cstruct, context: dict[str, int] | None = None) -> int:
121
+ """Evaluates an expression using a Shunting-Yard implementation."""
122
+ self._reset()
123
+ context = context or {}
124
+
125
+ while (token := self._current()).type != TokenType.EOF:
126
+ if token.type == TokenType.NUMBER:
127
+ self._queue.append(int(self._take().value, 0))
128
+
129
+ elif token.type in OPERATORS:
130
+ while (
131
+ len(self._stack) != 0
132
+ and self._stack[-1] != TokenType.LPAREN
133
+ and precedence(self._stack[-1], token.type)
134
+ ):
135
+ self._evaluate_expression(cs)
136
+
137
+ self._stack.append(self._take().type)
138
+
139
+ elif token.type in FUNCTION_TOKENS:
140
+ func = self._take().type
141
+ self._stack.append(func)
142
+
143
+ self._expect(TokenType.LPAREN)
144
+
145
+ num_args = FUNCTION_TOKENS[func]
146
+ while num_args > 1:
147
+ self._queue.append(self._collect_until(TokenType.COMMA))
148
+ self._expect(TokenType.COMMA)
149
+ num_args -= 1
150
+
151
+ self._queue.append(self._collect_until(TokenType.RPAREN))
152
+ self._expect(TokenType.RPAREN)
153
+
154
+ # Evaluate immediately
155
+ self._evaluate_expression(cs)
156
+
157
+ elif token.type in IDENTIFIER_TYPES:
158
+ ident = self._take().value
159
+
160
+ obj = None
161
+ for i, part in enumerate(ident.split(".")):
162
+ if i == 0:
163
+ if part in context:
164
+ obj = context[part]
165
+ elif part in cs.consts:
166
+ obj = cs.consts[part]
167
+ elif part in cs.typedefs:
168
+ obj = cs.resolve(part)
169
+ else:
170
+ raise self._error(f"Unknown identifier: '{ident}'", token=token)
171
+ else:
172
+ if isinstance(obj, dict) and part in obj:
173
+ obj = obj[part]
174
+ elif hasattr(obj, part):
175
+ obj = getattr(obj, part)
176
+ else:
177
+ raise self._error(f"Unknown identifier: '{ident}'", token=token)
178
+
179
+ try:
180
+ self._queue.append(int(obj))
181
+ except (ValueError, TypeError):
182
+ raise self._error(f"Identifier '{ident}' does not resolve to an integer", token=token)
183
+
184
+ elif token.type == TokenType.LPAREN:
185
+ if self._previous().type == TokenType.NUMBER:
186
+ raise self._error(
187
+ f"Parser expected sizeof or an arethmethic operator instead got: '{self._previous().value}'",
188
+ token=self._previous(),
189
+ )
190
+
191
+ self._stack.append(self._take().type)
192
+
193
+ elif token.type == TokenType.RPAREN:
194
+ if self._previous().type == TokenType.LPAREN:
195
+ raise self._error(
196
+ "Parser expected an expression, instead received empty parenthesis.",
197
+ token=self._previous(),
198
+ )
199
+
200
+ if len(self._stack) == 0:
201
+ raise self._error("Mismatched parentheses")
202
+
203
+ while self._stack[-1] != TokenType.LPAREN:
204
+ self._evaluate_expression(cs)
205
+ if len(self._stack) == 0:
206
+ raise self._error("Mismatched parentheses")
207
+
208
+ self._stack.pop(-1) # Pop the '('
209
+ self._take()
210
+
211
+ else:
212
+ raise self._error(f"Unmatched token: '{token.value}'", token=token)
213
+
214
+ while len(self._stack) != 0:
215
+ if TokenType.LPAREN in self._stack:
216
+ raise self._error("Mismatched parentheses")
217
+ self._evaluate_expression(cs)
218
+
219
+ if len(self._queue) != 1:
220
+ raise self._error("Invalid expression: too many operands")
221
+
222
+ return self._queue[0]