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.
- dissect/cstruct/__init__.py +2 -2
- dissect/cstruct/bitbuffer.py +3 -3
- dissect/cstruct/compiler.py +7 -7
- dissect/cstruct/cstruct.py +196 -75
- dissect/cstruct/{exceptions.py → exception.py} +4 -4
- dissect/cstruct/expression.py +188 -271
- dissect/cstruct/lexer.py +584 -0
- dissect/cstruct/parser.py +444 -593
- dissect/cstruct/tools/stubgen.py +18 -22
- dissect/cstruct/types/base.py +74 -31
- dissect/cstruct/types/char.py +12 -8
- dissect/cstruct/types/enum.py +33 -13
- dissect/cstruct/types/int.py +9 -7
- dissect/cstruct/types/leb128.py +6 -4
- dissect/cstruct/types/packed.py +19 -10
- dissect/cstruct/types/pointer.py +74 -56
- dissect/cstruct/types/structure.py +128 -28
- dissect/cstruct/types/void.py +6 -4
- dissect/cstruct/types/wchar.py +19 -14
- dissect/cstruct/{utils.py → util.py} +60 -35
- {dissect_cstruct-4.8.dev9.dist-info → dissect_cstruct-5.0.dev6.dist-info}/METADATA +1 -1
- dissect_cstruct-5.0.dev6.dist-info/RECORD +32 -0
- {dissect_cstruct-4.8.dev9.dist-info → dissect_cstruct-5.0.dev6.dist-info}/scm_file_list.json +4 -2
- dissect_cstruct-5.0.dev6.dist-info/scm_version.json +8 -0
- dissect_cstruct-4.8.dev9.dist-info/RECORD +0 -31
- dissect_cstruct-4.8.dev9.dist-info/scm_version.json +0 -8
- {dissect_cstruct-4.8.dev9.dist-info → dissect_cstruct-5.0.dev6.dist-info}/WHEEL +0 -0
- {dissect_cstruct-4.8.dev9.dist-info → dissect_cstruct-5.0.dev6.dist-info}/entry_points.txt +0 -0
- {dissect_cstruct-4.8.dev9.dist-info → dissect_cstruct-5.0.dev6.dist-info}/licenses/COPYRIGHT +0 -0
- {dissect_cstruct-4.8.dev9.dist-info → dissect_cstruct-5.0.dev6.dist-info}/licenses/LICENSE +0 -0
- {dissect_cstruct-4.8.dev9.dist-info → dissect_cstruct-5.0.dev6.dist-info}/top_level.txt +0 -0
dissect/cstruct/parser.py
CHANGED
|
@@ -2,23 +2,25 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import ast
|
|
4
4
|
import re
|
|
5
|
-
from typing import TYPE_CHECKING
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
6
|
|
|
7
7
|
from dissect.cstruct import compiler
|
|
8
|
-
from dissect.cstruct.
|
|
8
|
+
from dissect.cstruct.exception import (
|
|
9
9
|
ExpressionParserError,
|
|
10
|
-
|
|
10
|
+
LexerError,
|
|
11
11
|
ParserError,
|
|
12
12
|
)
|
|
13
13
|
from dissect.cstruct.expression import Expression
|
|
14
|
-
from dissect.cstruct.
|
|
14
|
+
from dissect.cstruct.lexer import IDENTIFIER_TYPES, TokenCursor, TokenType, format_error_context, tokenize
|
|
15
|
+
from dissect.cstruct.types import BaseArray, BaseType, Field, Structure
|
|
15
16
|
|
|
16
17
|
if TYPE_CHECKING:
|
|
17
18
|
from dissect.cstruct import cstruct
|
|
18
|
-
from dissect.cstruct.
|
|
19
|
+
from dissect.cstruct.lexer import Token
|
|
20
|
+
from dissect.cstruct.types import BaseType, Enum, Flag
|
|
19
21
|
|
|
20
22
|
|
|
21
|
-
class Parser:
|
|
23
|
+
class Parser(TokenCursor):
|
|
22
24
|
"""Base class for definition parsers.
|
|
23
25
|
|
|
24
26
|
Args:
|
|
@@ -26,688 +28,537 @@ class Parser:
|
|
|
26
28
|
"""
|
|
27
29
|
|
|
28
30
|
def __init__(self, cs: cstruct):
|
|
29
|
-
|
|
31
|
+
super().__init__()
|
|
32
|
+
self.cs = cs
|
|
30
33
|
|
|
31
34
|
def parse(self, data: str) -> None:
|
|
32
35
|
"""This function should parse definitions to cstruct types.
|
|
33
36
|
|
|
34
37
|
Args:
|
|
35
|
-
data: Data to parse definitions from
|
|
38
|
+
data: Data to parse definitions from.
|
|
36
39
|
"""
|
|
37
40
|
raise NotImplementedError
|
|
38
41
|
|
|
39
42
|
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
def _join_line_continuations(string: str) -> str:
|
|
44
|
+
# Join lines ending with backslash
|
|
45
|
+
return re.sub(r"\\\n", "", string)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class CStyleParser(Parser):
|
|
49
|
+
"""Recursive descent parser for C-like structure definitions.
|
|
42
50
|
|
|
43
51
|
Args:
|
|
44
52
|
cs: An instance of cstruct.
|
|
45
53
|
compiled: Whether structs should be compiled or not.
|
|
54
|
+
align: Whether to use aligned struct reads.
|
|
46
55
|
"""
|
|
47
56
|
|
|
48
57
|
def __init__(self, cs: cstruct, compiled: bool = True, align: bool = False):
|
|
49
58
|
super().__init__(cs)
|
|
50
|
-
|
|
51
59
|
self.compiled = compiled
|
|
52
60
|
self.align = align
|
|
53
|
-
self.
|
|
54
|
-
self._conditionals = []
|
|
55
|
-
self._conditionals_depth = 0
|
|
56
|
-
|
|
57
|
-
@staticmethod
|
|
58
|
-
def _tokencollection() -> TokenCollection:
|
|
59
|
-
TOK = TokenCollection()
|
|
60
|
-
TOK.add(r"#\[(?P<values>[^\]]+)\](?=\s*)", "CONFIG_FLAG")
|
|
61
|
-
TOK.add(r"#define\s+(?P<name>[^\s]+)(?P<value>[^\r\n]*)", "DEFINE")
|
|
62
|
-
TOK.add(r"#undef\s+(?P<name>[^\s]+)\s*", "UNDEF")
|
|
63
|
-
TOK.add(r"#ifdef\s+(?P<name>[^\s]+)\s*", "IFDEF")
|
|
64
|
-
TOK.add(r"#ifndef\s+(?P<name>[^\s]+)\s*", "IFNDEF")
|
|
65
|
-
TOK.add(r"#else\s*", "ELSE")
|
|
66
|
-
TOK.add(r"#endif\s*", "ENDIF")
|
|
67
|
-
TOK.add(r"typedef(?=\s)", "TYPEDEF")
|
|
68
|
-
TOK.add(r"(?:struct|union)(?=\s|{)", "STRUCT")
|
|
69
|
-
TOK.add(
|
|
70
|
-
r"(?P<enumtype>enum|flag)\s+(?P<name>[^\s:{]+)?\s*(:\s"
|
|
71
|
-
r"*(?P<type>[^{]+?)\s*)?\{(?P<values>[^}]+)\}\s*(?=;)",
|
|
72
|
-
"ENUM",
|
|
73
|
-
)
|
|
74
|
-
TOK.add(r"(?<=})\s*(?P<defs>(?:[a-zA-Z0-9_]+\s*,\s*)+[a-zA-Z0-9_]+)\s*(?=;)", "DEFS")
|
|
75
|
-
TOK.add(r"(?P<name>\**?\s*[a-zA-Z0-9_]+)(?:\s*:\s*(?P<bits>\d+))?(?:\[(?P<count>[^;]*)\])?\s*(?=;)", "NAME")
|
|
76
|
-
TOK.add(r"#include\s+(?P<name>[^\s]+)\s*", "INCLUDE")
|
|
77
|
-
TOK.add(r"[a-zA-Z_][a-zA-Z0-9_]*", "IDENTIFIER")
|
|
78
|
-
TOK.add(r"[{}]", "BLOCK")
|
|
79
|
-
TOK.add(r"\$(?P<name>[^\s]+) = (?P<value>{[^}]+})\w*[\r\n]+", "LOOKUP")
|
|
80
|
-
TOK.add(r";", "EOL")
|
|
81
|
-
TOK.add(r"\s+", None)
|
|
82
|
-
TOK.add(r".", None)
|
|
83
|
-
|
|
84
|
-
return TOK
|
|
85
|
-
|
|
86
|
-
def _identifier(self, tokens: TokenConsumer) -> str:
|
|
87
|
-
idents = []
|
|
88
|
-
while tokens.next == self.TOK.IDENTIFIER:
|
|
89
|
-
idents.append(tokens.consume())
|
|
90
|
-
return " ".join([i.value for i in idents])
|
|
91
|
-
|
|
92
|
-
def _conditional(self, tokens: TokenConsumer) -> None:
|
|
93
|
-
token = tokens.consume()
|
|
94
|
-
pattern = self.TOK.patterns[token.token]
|
|
95
|
-
match = pattern.match(token.value).groupdict()
|
|
96
|
-
|
|
97
|
-
value = match["name"]
|
|
98
|
-
|
|
99
|
-
if token.token == self.TOK.IFDEF:
|
|
100
|
-
self._conditionals.append(value in self.cstruct.consts)
|
|
101
|
-
elif token.token == self.TOK.IFNDEF:
|
|
102
|
-
self._conditionals.append(value not in self.cstruct.consts)
|
|
103
|
-
|
|
104
|
-
def _check_conditional(self, tokens: TokenConsumer) -> bool:
|
|
105
|
-
"""Check and handle conditionals. Return a boolean indicating if we need to continue to the next token."""
|
|
106
|
-
if self._conditionals and self._conditionals_depth == len(self._conditionals):
|
|
107
|
-
# If we have a conditional and the depth matches, handle it accordingly
|
|
108
|
-
if tokens.next == self.TOK.ELSE:
|
|
109
|
-
# Flip the last conditional
|
|
110
|
-
tokens.consume()
|
|
111
|
-
self._conditionals[-1] = not self._conditionals[-1]
|
|
112
|
-
return True
|
|
113
|
-
|
|
114
|
-
if tokens.next == self.TOK.ENDIF:
|
|
115
|
-
# Pop the last conditional
|
|
116
|
-
tokens.consume()
|
|
117
|
-
self._conditionals.pop()
|
|
118
|
-
self._conditionals_depth -= 1
|
|
119
|
-
return True
|
|
120
|
-
|
|
121
|
-
if tokens.next in (self.TOK.IFDEF, self.TOK.IFNDEF):
|
|
122
|
-
# If we encounter a new conditional, increase the depth
|
|
123
|
-
self._conditionals_depth += 1
|
|
124
|
-
|
|
125
|
-
if tokens.next == self.TOK.ENDIF:
|
|
126
|
-
# Similarly, decrease the depth if needed
|
|
127
|
-
self._conditionals_depth -= 1
|
|
128
|
-
|
|
129
|
-
if self._conditionals and not self._conditionals[-1]:
|
|
130
|
-
# If the last conditional evaluated to False, skip the next token
|
|
131
|
-
tokens.consume()
|
|
132
|
-
return True
|
|
61
|
+
self._data = None
|
|
133
62
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
self._conditional(tokens)
|
|
137
|
-
return True
|
|
138
|
-
|
|
139
|
-
return False
|
|
63
|
+
self._flags: list[str] = []
|
|
64
|
+
self._conditional_stack: list[tuple[Token, bool]] = []
|
|
140
65
|
|
|
141
|
-
def
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
66
|
+
def reset(self) -> None:
|
|
67
|
+
"""Reset the parser state for a new input."""
|
|
68
|
+
self._reset_tokens([])
|
|
69
|
+
self._flags = []
|
|
70
|
+
self._conditional_stack = []
|
|
145
71
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
except (ValueError, SyntaxError):
|
|
150
|
-
pass
|
|
72
|
+
def parse(self, data: str) -> None:
|
|
73
|
+
"""Parse C-like definitions from the input data."""
|
|
74
|
+
self.reset()
|
|
151
75
|
|
|
152
|
-
|
|
153
|
-
try:
|
|
154
|
-
value = Expression(value).evaluate(self.cstruct)
|
|
155
|
-
except (ExpressionParserError, ExpressionTokenizerError):
|
|
156
|
-
pass
|
|
76
|
+
data = _join_line_continuations(data)
|
|
157
77
|
|
|
158
|
-
|
|
78
|
+
# Keep a reference for error messages
|
|
79
|
+
self._data = data
|
|
80
|
+
self._reset_tokens(tokenize(data))
|
|
81
|
+
self._parse()
|
|
159
82
|
|
|
160
|
-
def
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
83
|
+
def _match(self, *types: TokenType) -> Token | None:
|
|
84
|
+
"""Consume and return the current token if it matches any of the given types, otherwise return None."""
|
|
85
|
+
if self._current().type in types:
|
|
86
|
+
return self._take()
|
|
87
|
+
return None
|
|
164
88
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
raise ParserError(f"line {self._lineno(const)}: constant {match['name']!r} not defined")
|
|
89
|
+
def _at(self, *types: TokenType) -> bool:
|
|
90
|
+
"""Return whether the current token matches any of the given types."""
|
|
91
|
+
return self._tokens[self._pos].type in types
|
|
169
92
|
|
|
170
|
-
def
|
|
171
|
-
|
|
172
|
-
|
|
93
|
+
def _error(self, msg: str, *, token: Token | None = None) -> ParserError:
|
|
94
|
+
lineno = (token if token is not None else self._tokens[self._pos]).line
|
|
95
|
+
if self._data is None:
|
|
96
|
+
return ParserError(f"line {lineno}: {msg}")
|
|
173
97
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
d = pattern.match(etok.value + ";").groupdict()
|
|
177
|
-
enumtype = d["enumtype"]
|
|
98
|
+
content = format_error_context(self._data, lineno)
|
|
99
|
+
return ParserError(f"line {lineno}: {msg}\n{content}")
|
|
178
100
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
101
|
+
def _in_false_branch(self) -> bool:
|
|
102
|
+
"""Return whether we're currently in a false conditional branch."""
|
|
103
|
+
return bool(self._conditional_stack) and not self._conditional_stack[-1][1]
|
|
182
104
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
for v in line.split(","):
|
|
186
|
-
key, _, val = v.partition("=")
|
|
187
|
-
key = key.strip()
|
|
188
|
-
val = val.strip()
|
|
189
|
-
if not key:
|
|
190
|
-
continue
|
|
105
|
+
def _handle_preprocessor(self) -> bool:
|
|
106
|
+
"""Handle preprocessor directives.
|
|
191
107
|
|
|
192
|
-
|
|
108
|
+
Returns ``True`` if a directive was processed, indicating the caller should continue its loop.
|
|
109
|
+
"""
|
|
110
|
+
token = self._current()
|
|
193
111
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
else:
|
|
198
|
-
nextval = val + 1
|
|
112
|
+
if token.type in (TokenType.PP_IFDEF, TokenType.PP_IFNDEF, TokenType.PP_ELSE, TokenType.PP_ENDIF):
|
|
113
|
+
self._handle_conditional()
|
|
114
|
+
return True
|
|
199
115
|
|
|
200
|
-
|
|
116
|
+
if token.type == TokenType.PP_DEFINE:
|
|
117
|
+
self._parse_define()
|
|
118
|
+
return True
|
|
201
119
|
|
|
202
|
-
if
|
|
203
|
-
|
|
120
|
+
if token.type == TokenType.PP_UNDEF:
|
|
121
|
+
self._parse_undef()
|
|
122
|
+
return True
|
|
204
123
|
|
|
205
|
-
|
|
124
|
+
if token.type == TokenType.PP_INCLUDE:
|
|
125
|
+
self._parse_include()
|
|
126
|
+
return True
|
|
206
127
|
|
|
207
|
-
|
|
208
|
-
if not enum.__name__:
|
|
209
|
-
self.cstruct.consts.update(enum.__members__)
|
|
210
|
-
else:
|
|
211
|
-
self.cstruct.add_type(enum.__name__, enum)
|
|
128
|
+
return False
|
|
212
129
|
|
|
213
|
-
|
|
130
|
+
def _parse(self) -> None:
|
|
131
|
+
"""Parse top-level definitions from the token stream."""
|
|
132
|
+
while (token := self._current()).type != TokenType.EOF:
|
|
133
|
+
if self._handle_preprocessor():
|
|
134
|
+
continue
|
|
214
135
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
136
|
+
if token.type == TokenType.PP_FLAGS:
|
|
137
|
+
self._parse_config_flags()
|
|
138
|
+
elif token.type == TokenType.TYPEDEF:
|
|
139
|
+
self._parse_typedef()
|
|
140
|
+
elif token.type in (TokenType.STRUCT, TokenType.UNION):
|
|
141
|
+
self._parse_struct_or_union()
|
|
218
142
|
|
|
219
|
-
|
|
143
|
+
# Skip variable declarations after struct/union definitions
|
|
144
|
+
while not self._at(TokenType.SEMICOLON, TokenType.EOF):
|
|
145
|
+
self._pos += 1
|
|
220
146
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
type_ = self._struct(tokens)
|
|
225
|
-
if not type_.__anonymous__:
|
|
226
|
-
names.append(type_.__name__)
|
|
147
|
+
self._expect(TokenType.SEMICOLON)
|
|
148
|
+
elif token.type in (TokenType.ENUM, TokenType.FLAG):
|
|
149
|
+
type_ = self._parse_enum_or_flag()
|
|
227
150
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
type_.__name__ = name
|
|
233
|
-
type_.__qualname__ = name
|
|
151
|
+
# If it's an anonymous enum/flag, add its members to the constants for convenience
|
|
152
|
+
if not type_.__name__:
|
|
153
|
+
for k, v in type_.__members__.items():
|
|
154
|
+
self.cs.add_const(k, v)
|
|
234
155
|
|
|
235
|
-
|
|
236
|
-
if bits is not None:
|
|
237
|
-
raise ParserError(f"line {self._lineno(tokens.previous)}: typedefs cannot have bitfields")
|
|
238
|
-
self.cstruct.add_type(name, type_)
|
|
239
|
-
|
|
240
|
-
def _struct(self, tokens: TokenConsumer, register: bool = False) -> type[Structure]:
|
|
241
|
-
stype = tokens.consume()
|
|
242
|
-
|
|
243
|
-
factory = self.cstruct._make_union if stype.value.startswith("union") else self.cstruct._make_struct
|
|
244
|
-
|
|
245
|
-
st = None
|
|
246
|
-
names = []
|
|
247
|
-
registered = False
|
|
248
|
-
|
|
249
|
-
if tokens.next == self.TOK.IDENTIFIER:
|
|
250
|
-
ident = tokens.consume()
|
|
251
|
-
if register:
|
|
252
|
-
# Pre-register an empty struct for self-referencing
|
|
253
|
-
# We update this instance later with the fields
|
|
254
|
-
st = factory(ident.value, [], align=self.align)
|
|
255
|
-
if self.compiled and "nocompile" not in tokens.flags:
|
|
256
|
-
st = compiler.compile(st)
|
|
257
|
-
self.cstruct.add_type(ident.value, st)
|
|
258
|
-
registered = True
|
|
156
|
+
self._expect(TokenType.SEMICOLON)
|
|
259
157
|
else:
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if tokens.next == self.TOK.NAME:
|
|
263
|
-
# As part of a struct field
|
|
264
|
-
# struct type_name field_name;
|
|
265
|
-
if not names:
|
|
266
|
-
raise ParserError(f"line {self._lineno(tokens.next)}: unexpected anonymous struct")
|
|
267
|
-
return self.cstruct.resolve(names[0])
|
|
268
|
-
|
|
269
|
-
if tokens.next != self.TOK.BLOCK:
|
|
270
|
-
raise ParserError(f"line {self._lineno(tokens.next)}: expected start of block '{tokens.next}'")
|
|
271
|
-
|
|
272
|
-
fields = []
|
|
273
|
-
tokens.consume()
|
|
274
|
-
while len(tokens):
|
|
275
|
-
if tokens.next == self.TOK.BLOCK and tokens.next.value == "}":
|
|
276
|
-
tokens.consume()
|
|
277
|
-
break
|
|
158
|
+
raise self._error(f"unexpected token {token.value!r}")
|
|
278
159
|
|
|
279
|
-
|
|
280
|
-
|
|
160
|
+
if self._conditional_stack:
|
|
161
|
+
raise self._error("unclosed conditional statement", token=self._conditional_stack[-1][0])
|
|
281
162
|
|
|
282
|
-
|
|
283
|
-
fields.append(field)
|
|
163
|
+
# Preprocessor directives
|
|
284
164
|
|
|
285
|
-
|
|
286
|
-
|
|
165
|
+
def _parse_define(self) -> None:
|
|
166
|
+
"""Parse a define directive and add the constant."""
|
|
167
|
+
self._expect(TokenType.PP_DEFINE)
|
|
287
168
|
|
|
288
|
-
|
|
289
|
-
# Otherwise we're part of a typedef or field definition
|
|
290
|
-
if tokens.next == self.TOK.EOL:
|
|
291
|
-
tokens.eol()
|
|
169
|
+
name_token = self._expect(TokenType.IDENTIFIER)
|
|
292
170
|
|
|
293
|
-
|
|
171
|
+
# If there's a value, it's emitted as a single raw STRING token
|
|
172
|
+
value = ""
|
|
173
|
+
if (token := self._current()).type == TokenType.STRING and token.line == name_token.line:
|
|
174
|
+
value = self._take().value
|
|
294
175
|
|
|
295
|
-
if
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
176
|
+
if value:
|
|
177
|
+
if value[0] in ('"', "'"):
|
|
178
|
+
quote = value[0]
|
|
179
|
+
if value[-1] != quote:
|
|
180
|
+
raise self._error("unterminated string literal", token=token)
|
|
300
181
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
# This is pretty dirty
|
|
309
|
-
if register:
|
|
310
|
-
if not names and not registered:
|
|
311
|
-
raise ParserError(f"line {self._lineno(stype)}: struct has no name")
|
|
312
|
-
|
|
313
|
-
for name in names:
|
|
314
|
-
self.cstruct.add_type(name, st)
|
|
315
|
-
|
|
316
|
-
tokens.reset_flags()
|
|
317
|
-
return st
|
|
318
|
-
|
|
319
|
-
def _lookup(self, tokens: TokenConsumer) -> None:
|
|
320
|
-
# Just like enums, we cheat and have the entire lookup in the token
|
|
321
|
-
ltok = tokens.consume()
|
|
322
|
-
|
|
323
|
-
pattern = self.TOK.patterns[self.TOK.LOOKUP]
|
|
324
|
-
# Dirty trick because the regex expects a ; but we don't want it to be part of the value
|
|
325
|
-
m = pattern.match(ltok.value + ";")
|
|
326
|
-
d = ast.literal_eval(m.group(2))
|
|
327
|
-
self.cstruct.lookups[m.group(1)] = {self.cstruct.consts[k]: v for k, v in d.items()}
|
|
328
|
-
|
|
329
|
-
def _parse_field(self, tokens: TokenConsumer) -> Field:
|
|
330
|
-
type_ = None
|
|
331
|
-
if tokens.next == self.TOK.IDENTIFIER:
|
|
332
|
-
type_ = self.cstruct.resolve(self._identifier(tokens))
|
|
333
|
-
elif tokens.next == self.TOK.STRUCT:
|
|
334
|
-
type_ = self._struct(tokens)
|
|
335
|
-
|
|
336
|
-
if tokens.next != self.TOK.NAME:
|
|
337
|
-
return Field(None, type_, None)
|
|
338
|
-
|
|
339
|
-
if tokens.next != self.TOK.NAME:
|
|
340
|
-
raise ParserError(f"line {self._lineno(tokens.next)}: expected name, got {tokens.next!r}")
|
|
341
|
-
nametok = tokens.consume()
|
|
342
|
-
|
|
343
|
-
type_, name, bits = self._parse_field_type(type_, nametok.value)
|
|
344
|
-
|
|
345
|
-
tokens.eol()
|
|
346
|
-
return Field(name.strip(), type_, bits)
|
|
347
|
-
|
|
348
|
-
def _parse_field_type(self, type_: type[BaseType], name: str) -> tuple[type[BaseType], str, int | None]:
|
|
349
|
-
pattern = self.TOK.patterns[self.TOK.NAME]
|
|
350
|
-
# Dirty trick because the regex expects a ; but we don't want it to be part of the value
|
|
351
|
-
d = pattern.match(name + ";").groupdict()
|
|
352
|
-
|
|
353
|
-
name = d["name"]
|
|
354
|
-
count_expression = d["count"]
|
|
355
|
-
|
|
356
|
-
while name.startswith("*"):
|
|
357
|
-
name = name[1:]
|
|
358
|
-
type_ = self.cstruct._make_pointer(type_)
|
|
359
|
-
|
|
360
|
-
if count_expression is not None:
|
|
361
|
-
# Poor mans multi-dimensional array by abusing the eager regex match of count
|
|
362
|
-
counts = count_expression.split("][") if "][" in count_expression else [count_expression]
|
|
363
|
-
|
|
364
|
-
for count in reversed(counts):
|
|
365
|
-
if count == "":
|
|
366
|
-
count = None
|
|
367
|
-
else:
|
|
368
|
-
count = Expression(count)
|
|
369
|
-
try:
|
|
370
|
-
count = count.evaluate(self.cstruct)
|
|
371
|
-
except Exception:
|
|
372
|
-
pass
|
|
373
|
-
|
|
374
|
-
if issubclass(type_, BaseArray) and count is None:
|
|
375
|
-
raise ParserError("Depth required for multi-dimensional array")
|
|
376
|
-
|
|
377
|
-
type_ = self.cstruct._make_array(type_, count)
|
|
378
|
-
|
|
379
|
-
return type_, name.strip(), int(d["bits"]) if d["bits"] else None
|
|
380
|
-
|
|
381
|
-
def _names(self, tokens: TokenConsumer) -> list[str]:
|
|
382
|
-
names = []
|
|
383
|
-
while True:
|
|
384
|
-
if tokens.next == self.TOK.EOL:
|
|
385
|
-
tokens.eol()
|
|
386
|
-
break
|
|
182
|
+
# Remove the surrounding quotes and flatten escape sequences
|
|
183
|
+
value = value[1:-1].encode().decode("unicode_escape")
|
|
184
|
+
elif value[:2].lower() in ("b'", 'b"'):
|
|
185
|
+
quote = value[1]
|
|
186
|
+
if value[-1] != quote:
|
|
187
|
+
raise self._error("unterminated bytes literal", token=token)
|
|
387
188
|
|
|
388
|
-
|
|
389
|
-
|
|
189
|
+
# Remove the leading b and surrounding quotes and flatten escape sequences
|
|
190
|
+
value = value[2:-1].encode().decode("unicode_escape")
|
|
191
|
+
value = ast.literal_eval(f"b{value!r}")
|
|
192
|
+
else:
|
|
193
|
+
try:
|
|
194
|
+
# Lazy mode, try to evaluate as a Python literal first (for simple constants)
|
|
195
|
+
value = ast.literal_eval(value)
|
|
196
|
+
except (ValueError, SyntaxError):
|
|
197
|
+
# Try to evaluate it as an expression in the context of current constants
|
|
198
|
+
if isinstance(value, str):
|
|
199
|
+
try:
|
|
200
|
+
value = Expression(value).evaluate(self.cs)
|
|
201
|
+
except (LexerError, ExpressionParserError):
|
|
202
|
+
# If evaluation fails, just keep it as a string (e.g. for macro-like constants)
|
|
203
|
+
pass
|
|
204
|
+
|
|
205
|
+
self.cs.add_const(name_token.value, value)
|
|
206
|
+
|
|
207
|
+
def _parse_undef(self) -> None:
|
|
208
|
+
"""Parse an undef directive and remove the constant."""
|
|
209
|
+
self._expect(TokenType.PP_UNDEF)
|
|
210
|
+
|
|
211
|
+
name_token = self._expect(TokenType.IDENTIFIER)
|
|
212
|
+
try:
|
|
213
|
+
self.cs.del_const(name_token.value)
|
|
214
|
+
except KeyError:
|
|
215
|
+
raise self._error(f"constant {name_token.value!r} not defined", token=name_token) from None
|
|
216
|
+
|
|
217
|
+
def _parse_include(self) -> None:
|
|
218
|
+
"""Parse an include directive and add the included file to the includes list."""
|
|
219
|
+
self._expect(TokenType.PP_INCLUDE)
|
|
220
|
+
self.cs.includes.append(self._expect(TokenType.STRING).value)
|
|
221
|
+
|
|
222
|
+
def _parse_config_flags(self) -> None:
|
|
223
|
+
"""Parse configuration flags from a directive like ``#[flag1, flag2, ...]``."""
|
|
224
|
+
self._flags.extend(flag.strip() for flag in self._expect(TokenType.PP_FLAGS).value.split(","))
|
|
225
|
+
|
|
226
|
+
def _handle_conditional(self) -> None:
|
|
227
|
+
"""Handle conditional directives: ``#ifdef``, ``#ifndef``, ``#else``, ``#endif``."""
|
|
228
|
+
if (token := self._take()).type not in (
|
|
229
|
+
TokenType.PP_IFDEF,
|
|
230
|
+
TokenType.PP_IFNDEF,
|
|
231
|
+
TokenType.PP_ELSE,
|
|
232
|
+
TokenType.PP_ENDIF,
|
|
233
|
+
):
|
|
234
|
+
raise self._error("expected conditional directive")
|
|
235
|
+
|
|
236
|
+
if token.type == TokenType.PP_IFDEF:
|
|
237
|
+
name = self._expect(TokenType.IDENTIFIER).value
|
|
238
|
+
if self._conditional_stack and not self._conditional_stack[-1][1]:
|
|
239
|
+
# Parent is false, so this child is always false
|
|
240
|
+
self._conditional_stack.append((token, False))
|
|
241
|
+
else:
|
|
242
|
+
self._conditional_stack.append((token, name in self.cs.consts))
|
|
243
|
+
|
|
244
|
+
elif token.type == TokenType.PP_IFNDEF:
|
|
245
|
+
name = self._expect(TokenType.IDENTIFIER).value
|
|
246
|
+
if self._conditional_stack and not self._conditional_stack[-1][1]:
|
|
247
|
+
self._conditional_stack.append((token, False))
|
|
248
|
+
else:
|
|
249
|
+
self._conditional_stack.append((token, name not in self.cs.consts))
|
|
250
|
+
|
|
251
|
+
elif token.type == TokenType.PP_ELSE:
|
|
252
|
+
if not self._conditional_stack:
|
|
253
|
+
raise self._error("#else without matching #ifdef/#ifndef", token=token)
|
|
254
|
+
|
|
255
|
+
# Only flip if parent is true (or there's no parent)
|
|
256
|
+
parent_active = len(self._conditional_stack) < 2 or self._conditional_stack[-2][1]
|
|
257
|
+
if parent_active:
|
|
258
|
+
self._conditional_stack[-1] = (self._conditional_stack[-1][0], not self._conditional_stack[-1][1])
|
|
259
|
+
|
|
260
|
+
elif token.type == TokenType.PP_ENDIF:
|
|
261
|
+
if not self._conditional_stack:
|
|
262
|
+
raise self._error("#endif without matching #ifdef/#ifndef", token=token)
|
|
263
|
+
self._conditional_stack.pop()
|
|
264
|
+
|
|
265
|
+
# If we ended up in a false branch, skip ahead to the matching #else/#endif
|
|
266
|
+
if self._in_false_branch():
|
|
267
|
+
self._skip_false_branch()
|
|
268
|
+
|
|
269
|
+
def _skip_false_branch(self) -> None:
|
|
270
|
+
"""Skip all tokens in a false conditional branch until the matching ``#else`` or ``#endif``."""
|
|
271
|
+
depth = 0
|
|
272
|
+
while self._current().type != TokenType.EOF:
|
|
273
|
+
token_type = self._current().type
|
|
274
|
+
|
|
275
|
+
if token_type in (TokenType.PP_IFDEF, TokenType.PP_IFNDEF):
|
|
276
|
+
depth += 1
|
|
277
|
+
self._pos += 1
|
|
278
|
+
# Skip the identifier argument
|
|
279
|
+
if self._current().type != TokenType.EOF:
|
|
280
|
+
self._pos += 1
|
|
281
|
+
elif token_type == TokenType.PP_ENDIF:
|
|
282
|
+
if depth == 0:
|
|
283
|
+
return
|
|
284
|
+
depth -= 1
|
|
285
|
+
self._pos += 1
|
|
286
|
+
elif token_type == TokenType.PP_ELSE:
|
|
287
|
+
if depth == 0:
|
|
288
|
+
return
|
|
289
|
+
self._pos += 1
|
|
290
|
+
else:
|
|
291
|
+
self._pos += 1
|
|
390
292
|
|
|
391
|
-
|
|
392
|
-
if ntoken in (self.TOK.NAME, self.TOK.IDENTIFIER):
|
|
393
|
-
names.append(ntoken.value.strip())
|
|
394
|
-
elif ntoken == self.TOK.DEFS:
|
|
395
|
-
names.extend([name.strip() for name in ntoken.value.strip().split(",")])
|
|
396
|
-
|
|
397
|
-
return names
|
|
398
|
-
|
|
399
|
-
def _include(self, tokens: TokenConsumer) -> None:
|
|
400
|
-
include = tokens.consume()
|
|
401
|
-
pattern = self.TOK.patterns[self.TOK.INCLUDE]
|
|
402
|
-
match = pattern.match(include.value).groupdict()
|
|
403
|
-
|
|
404
|
-
self.cstruct.includes.append(match["name"].strip().strip("'\""))
|
|
405
|
-
|
|
406
|
-
@staticmethod
|
|
407
|
-
def _remove_comments(string: str) -> str:
|
|
408
|
-
# https://stackoverflow.com/a/18381470
|
|
409
|
-
pattern = r"(\".*?\"|\'.*?\')|(/\*.*?\*/|//[^\r\n]*$)"
|
|
410
|
-
# first group captures quoted strings (double or single)
|
|
411
|
-
# second group captures comments (//single-line or /* multi-line */)
|
|
412
|
-
regex = re.compile(pattern, re.MULTILINE | re.DOTALL)
|
|
413
|
-
|
|
414
|
-
def _replacer(match: re.Match) -> str:
|
|
415
|
-
# if the 2nd group (capturing comments) is not None,
|
|
416
|
-
# it means we have captured a non-quoted (real) comment string.
|
|
417
|
-
if comment := match.group(2):
|
|
418
|
-
return "\n" * comment.count("\n") # so we will return empty to remove the comment
|
|
419
|
-
# otherwise, we will return the 1st group
|
|
420
|
-
return match.group(1) # captured quoted-string
|
|
421
|
-
|
|
422
|
-
return regex.sub(_replacer, string)
|
|
423
|
-
|
|
424
|
-
@staticmethod
|
|
425
|
-
def _lineno(tok: Token) -> int:
|
|
426
|
-
"""Quick and dirty line number calculator."""
|
|
427
|
-
match = tok.match
|
|
428
|
-
return match.string.count("\n", 0, match.start()) + 1
|
|
429
|
-
|
|
430
|
-
def _config_flag(self, tokens: TokenConsumer) -> None:
|
|
431
|
-
flag_token = tokens.consume()
|
|
432
|
-
pattern = self.TOK.patterns[self.TOK.CONFIG_FLAG]
|
|
433
|
-
tok_dict = pattern.match(flag_token.value).groupdict()
|
|
434
|
-
tokens.flags.extend(tok_dict["values"].split(","))
|
|
293
|
+
# Type definitions
|
|
435
294
|
|
|
436
|
-
def
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
295
|
+
def _parse_typedef(self) -> None:
|
|
296
|
+
"""Parse a typedef definition."""
|
|
297
|
+
self._expect(TokenType.TYPEDEF)
|
|
298
|
+
|
|
299
|
+
base_type = self._parse_type_spec()
|
|
300
|
+
|
|
301
|
+
# Parse one or more typedef names with modifiers (pointers, arrays)
|
|
302
|
+
while self._at(TokenType.IDENTIFIER, TokenType.STAR):
|
|
303
|
+
type_, name, bits = self._parse_field_name(base_type)
|
|
304
|
+
if bits is not None:
|
|
305
|
+
raise self._error("typedefs cannot have bitfields")
|
|
306
|
+
|
|
307
|
+
# For convenience, we assign the typedef name to anonymous structs/unions
|
|
308
|
+
if issubclass(base_type, Structure) and base_type.__anonymous__:
|
|
309
|
+
base_type.__anonymous__ = False
|
|
310
|
+
base_type.__name__ = name
|
|
311
|
+
base_type.__qualname__ = name
|
|
312
|
+
|
|
313
|
+
self.cs.add_type(name, type_)
|
|
314
|
+
|
|
315
|
+
if not self._match(TokenType.COMMA):
|
|
449
316
|
break
|
|
450
317
|
|
|
451
|
-
|
|
452
|
-
continue
|
|
318
|
+
self._match(TokenType.SEMICOLON)
|
|
453
319
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
elif token == self.TOK.DEFINE:
|
|
457
|
-
self._constant(tokens)
|
|
458
|
-
elif token == self.TOK.UNDEF:
|
|
459
|
-
self._undef(tokens)
|
|
460
|
-
elif token == self.TOK.TYPEDEF:
|
|
461
|
-
self._typedef(tokens)
|
|
462
|
-
elif token == self.TOK.STRUCT:
|
|
463
|
-
self._struct(tokens, register=True)
|
|
464
|
-
elif token == self.TOK.ENUM:
|
|
465
|
-
self._enum(tokens)
|
|
466
|
-
elif token == self.TOK.LOOKUP:
|
|
467
|
-
self._lookup(tokens)
|
|
468
|
-
elif token == self.TOK.INCLUDE:
|
|
469
|
-
self._include(tokens)
|
|
470
|
-
else:
|
|
471
|
-
raise ParserError(f"line {self._lineno(token)}: unexpected token {token!r}")
|
|
320
|
+
def _parse_struct_or_union(self) -> type[Structure]:
|
|
321
|
+
"""Parse a struct or union definition.
|
|
472
322
|
|
|
473
|
-
|
|
474
|
-
|
|
323
|
+
If ``register`` is ``True``, the struct will be registered with its name (which is required).
|
|
324
|
+
Otherwise, the struct will not be registered and can only be used as an inline type for fields.
|
|
325
|
+
"""
|
|
326
|
+
start_token = self._expect(TokenType.STRUCT, TokenType.UNION)
|
|
475
327
|
|
|
328
|
+
is_union = start_token.type == TokenType.UNION
|
|
329
|
+
factory = self.cs._make_union if is_union else self.cs._make_struct
|
|
476
330
|
|
|
477
|
-
|
|
478
|
-
|
|
331
|
+
type = None
|
|
332
|
+
name = None
|
|
479
333
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
"""
|
|
334
|
+
if not self._at(TokenType.LBRACE):
|
|
335
|
+
if not self._at(TokenType.IDENTIFIER):
|
|
336
|
+
raise self._error("expected struct name or '{'", token=start_token)
|
|
484
337
|
|
|
485
|
-
|
|
486
|
-
self.compiled = compiled
|
|
487
|
-
super().__init__(cs)
|
|
338
|
+
name = self._take().value
|
|
488
339
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
v = ast.literal_eval(v)
|
|
497
|
-
except (ValueError, SyntaxError):
|
|
498
|
-
pass
|
|
499
|
-
|
|
500
|
-
self.cstruct.consts[d["name"]] = v
|
|
501
|
-
|
|
502
|
-
def _enums(self, data: str) -> None:
|
|
503
|
-
r = re.finditer(
|
|
504
|
-
r"(?P<enumtype>enum|flag)\s+(?P<name>[^\s:{]+)\s*(:\s*(?P<type>[^\s]+)\s*)?\{(?P<values>[^}]+)\}\s*;",
|
|
505
|
-
data,
|
|
506
|
-
)
|
|
507
|
-
for t in r:
|
|
508
|
-
d = t.groupdict()
|
|
509
|
-
enumtype = d["enumtype"]
|
|
510
|
-
|
|
511
|
-
nextval = 0
|
|
512
|
-
if enumtype == "flag":
|
|
513
|
-
nextval = 1
|
|
514
|
-
|
|
515
|
-
values = {}
|
|
516
|
-
for line in d["values"].split("\n"):
|
|
517
|
-
line, _, _ = line.partition("//")
|
|
518
|
-
for v in line.split(","):
|
|
519
|
-
key, _, val = v.partition("=")
|
|
520
|
-
key = key.strip()
|
|
521
|
-
val = val.strip()
|
|
522
|
-
if not key:
|
|
523
|
-
continue
|
|
524
|
-
|
|
525
|
-
val = nextval if not val else Expression(val).evaluate(self.cstruct)
|
|
526
|
-
|
|
527
|
-
if enumtype == "flag":
|
|
528
|
-
high_bit = val.bit_length() - 1
|
|
529
|
-
nextval = 2 ** (high_bit + 1)
|
|
530
|
-
else:
|
|
531
|
-
nextval = val + 1
|
|
532
|
-
|
|
533
|
-
values[key] = val
|
|
534
|
-
|
|
535
|
-
if not d["type"]:
|
|
536
|
-
d["type"] = "uint32"
|
|
537
|
-
|
|
538
|
-
factory = self.cstruct._make_enum
|
|
539
|
-
if enumtype == "flag":
|
|
540
|
-
factory = self.cstruct._make_flag
|
|
541
|
-
|
|
542
|
-
enum = factory(d["name"], self.cstruct.resolve(d["type"]), values)
|
|
543
|
-
self.cstruct.add_type(enum.__name__, enum)
|
|
544
|
-
|
|
545
|
-
def _structs(self, data: str) -> None:
|
|
546
|
-
r = re.finditer(
|
|
547
|
-
r"(#(?P<flags>(?:compile))\s+)?"
|
|
548
|
-
r"((?P<typedef>typedef)\s+)?"
|
|
549
|
-
r"(?P<type>[^\s]+)\s+"
|
|
550
|
-
r"(?P<name>[^\s]+)?"
|
|
551
|
-
r"(?P<fields>"
|
|
552
|
-
r"\s*{[^}]+\}(?P<defs>\s+[^;\n]+)?"
|
|
553
|
-
r")?\s*;",
|
|
554
|
-
data,
|
|
555
|
-
)
|
|
556
|
-
for t in r:
|
|
557
|
-
d = t.groupdict()
|
|
558
|
-
|
|
559
|
-
if d["name"]:
|
|
560
|
-
name = d["name"]
|
|
561
|
-
elif d["defs"]:
|
|
562
|
-
name = d["defs"].strip().split(",")[0].strip()
|
|
340
|
+
# struct name { ... }
|
|
341
|
+
if self._at(TokenType.LBRACE):
|
|
342
|
+
# Named struct/union, empty pre-register for self-referencing
|
|
343
|
+
type = factory(name, [], align=self.align)
|
|
344
|
+
if self.compiled and "nocompile" not in self._flags:
|
|
345
|
+
type = compiler.compile(type)
|
|
346
|
+
self.cs.add_type(name, type)
|
|
563
347
|
else:
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
if d["type"] == "struct":
|
|
567
|
-
data = self._parse_fields(d["fields"][1:-1].strip())
|
|
568
|
-
st = self.cstruct._make_struct(name, data)
|
|
569
|
-
if d["flags"] == "compile" or self.compiled:
|
|
570
|
-
st = compiler.compile(st)
|
|
571
|
-
elif d["typedef"] == "typedef":
|
|
572
|
-
st = d["type"]
|
|
573
|
-
else:
|
|
574
|
-
continue
|
|
348
|
+
# struct typename ... (type reference)
|
|
349
|
+
return self.cs.resolve(name)
|
|
575
350
|
|
|
576
|
-
|
|
577
|
-
|
|
351
|
+
# Parse body
|
|
352
|
+
self._expect(TokenType.LBRACE)
|
|
353
|
+
fields = self._parse_field_list()
|
|
354
|
+
self._expect(TokenType.RBRACE)
|
|
578
355
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
self.cstruct.add_type(td, st)
|
|
356
|
+
if type is None:
|
|
357
|
+
is_anonymous = name is None
|
|
358
|
+
name = name or self.cs._next_anonymous()
|
|
583
359
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
360
|
+
type = factory(name, fields, align=self.align, anonymous=is_anonymous)
|
|
361
|
+
if self.compiled and "nocompile" not in self._flags:
|
|
362
|
+
type = compiler.compile(type)
|
|
363
|
+
else:
|
|
364
|
+
type.__fields__.extend(fields)
|
|
365
|
+
type.commit()
|
|
589
366
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
d = f.groupdict()
|
|
593
|
-
if d["type"].startswith("//"):
|
|
594
|
-
continue
|
|
367
|
+
self._flags.clear()
|
|
368
|
+
return type
|
|
595
369
|
|
|
596
|
-
|
|
370
|
+
def _parse_enum_or_flag(self) -> type[Enum | Flag]:
|
|
371
|
+
"""Parse an enum or flag definition."""
|
|
372
|
+
start_token = self._expect(TokenType.ENUM, TokenType.FLAG)
|
|
597
373
|
|
|
598
|
-
|
|
374
|
+
is_flag = start_token.type == TokenType.FLAG
|
|
599
375
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
if d["count"] == "":
|
|
604
|
-
count = None
|
|
605
|
-
else:
|
|
606
|
-
count = Expression(d["count"])
|
|
607
|
-
try:
|
|
608
|
-
count = count.evaluate(self.cstruct)
|
|
609
|
-
except Exception:
|
|
610
|
-
pass
|
|
376
|
+
name = None
|
|
377
|
+
if self._at(TokenType.IDENTIFIER):
|
|
378
|
+
name = self._take().value
|
|
611
379
|
|
|
612
|
-
|
|
380
|
+
# Optional base type
|
|
381
|
+
base_type_str = "uint32"
|
|
382
|
+
if self._match(TokenType.COLON):
|
|
383
|
+
parts = []
|
|
384
|
+
while (token := self._match(TokenType.IDENTIFIER)) is not None:
|
|
385
|
+
parts.append(token.value)
|
|
386
|
+
base_type_str = " ".join(parts)
|
|
613
387
|
|
|
614
|
-
|
|
615
|
-
d["name"] = d["name"][1:]
|
|
616
|
-
type_ = self.cstruct._make_pointer(type_)
|
|
388
|
+
self._expect(TokenType.LBRACE)
|
|
617
389
|
|
|
618
|
-
|
|
619
|
-
|
|
390
|
+
next_value = 1 if is_flag else 0
|
|
391
|
+
values: dict[str, int] = {}
|
|
620
392
|
|
|
621
|
-
|
|
393
|
+
while not self._at(TokenType.RBRACE):
|
|
394
|
+
if self._handle_preprocessor():
|
|
395
|
+
continue
|
|
396
|
+
self._assert_not_eof()
|
|
397
|
+
|
|
398
|
+
# For historical reasons, we allow enum/flag member names to start with a digit
|
|
399
|
+
# E.g. `32BIT`
|
|
400
|
+
member_name = ""
|
|
401
|
+
if token := self._match(TokenType.NUMBER):
|
|
402
|
+
member_name += token.value
|
|
403
|
+
if token := self._match(TokenType.IDENTIFIER):
|
|
404
|
+
member_name += token.value
|
|
405
|
+
else:
|
|
406
|
+
member_name = self._expect(TokenType.IDENTIFIER).value
|
|
622
407
|
|
|
623
|
-
|
|
624
|
-
|
|
408
|
+
if self._match(TokenType.EQUALS):
|
|
409
|
+
expression = self._collect_until(TokenType.COMMA, TokenType.RBRACE)
|
|
410
|
+
value = Expression(expression).evaluate(self.cs, values)
|
|
411
|
+
else:
|
|
412
|
+
value = next_value
|
|
625
413
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
414
|
+
if is_flag:
|
|
415
|
+
high_bit = value.bit_length() - 1
|
|
416
|
+
next_value = 2 ** (high_bit + 1)
|
|
417
|
+
else:
|
|
418
|
+
next_value = value + 1
|
|
629
419
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
self._enums(data)
|
|
633
|
-
self._structs(data)
|
|
634
|
-
self._lookups(data, self.cstruct.consts)
|
|
420
|
+
values[member_name] = value
|
|
421
|
+
self._match(TokenType.COMMA) # optional trailing comma
|
|
635
422
|
|
|
423
|
+
self._expect(TokenType.RBRACE)
|
|
636
424
|
|
|
637
|
-
|
|
638
|
-
|
|
425
|
+
factory = self.cs._make_flag if is_flag else self.cs._make_enum
|
|
426
|
+
type_ = factory(name or "", self.cs.resolve(base_type_str), values)
|
|
639
427
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
428
|
+
if name is not None:
|
|
429
|
+
# Register the enum/flag type if it has a name
|
|
430
|
+
# Anonymous enums/flags are handled in the top level parse loop
|
|
431
|
+
self.cs.add_type(type_.__name__, type_)
|
|
644
432
|
|
|
645
|
-
|
|
646
|
-
if isinstance(other, Token):
|
|
647
|
-
other = other.token
|
|
433
|
+
return type_
|
|
648
434
|
|
|
649
|
-
|
|
435
|
+
# Field parsing
|
|
650
436
|
|
|
651
|
-
def
|
|
652
|
-
|
|
437
|
+
def _parse_field_list(self) -> list[Field]:
|
|
438
|
+
"""Parse a list of fields inside a struct/union body until the closing brace."""
|
|
439
|
+
fields: list[Field] = []
|
|
653
440
|
|
|
654
|
-
|
|
655
|
-
|
|
441
|
+
while not self._at(TokenType.RBRACE):
|
|
442
|
+
if self._handle_preprocessor():
|
|
443
|
+
continue
|
|
444
|
+
self._assert_not_eof()
|
|
656
445
|
|
|
446
|
+
fields.append(self._parse_field())
|
|
657
447
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
self.patterns: dict[str, re.Pattern] = {}
|
|
448
|
+
# Handle multiple fields declared in the same line, e.g., `int x, y, z;` or `struct { ... } a, b;`
|
|
449
|
+
while self._match(TokenType.COMMA):
|
|
450
|
+
type_, name, bits = self._parse_field_name(fields[-1].type)
|
|
451
|
+
fields.append(Field(name, type_, bits))
|
|
663
452
|
|
|
664
|
-
|
|
665
|
-
try:
|
|
666
|
-
return self.lookup[attr]
|
|
667
|
-
except AttributeError:
|
|
668
|
-
pass
|
|
453
|
+
self._expect(TokenType.SEMICOLON)
|
|
669
454
|
|
|
670
|
-
return
|
|
455
|
+
return fields
|
|
671
456
|
|
|
672
|
-
def
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
self.lookup[name] = name
|
|
677
|
-
self.patterns[name] = re.compile(regex)
|
|
678
|
-
self.tokens.append((regex, lambda s, t: Token(name, t, s.match)))
|
|
457
|
+
def _parse_field(self) -> Field:
|
|
458
|
+
"""Parse a single field declaration."""
|
|
459
|
+
# Regular field: `type name`
|
|
460
|
+
type_ = self._parse_type_spec()
|
|
679
461
|
|
|
462
|
+
# Handle the case where a semicolon follows immediately (e.g., anonymous struct/unions)
|
|
463
|
+
if self._at(TokenType.SEMICOLON):
|
|
464
|
+
return Field(None, type_, None)
|
|
680
465
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
self.tokens = tokens
|
|
684
|
-
self.flags = []
|
|
685
|
-
self.previous = None
|
|
466
|
+
type_, name, bits = self._parse_field_name(type_)
|
|
467
|
+
return Field(name, type_, bits)
|
|
686
468
|
|
|
687
|
-
def
|
|
688
|
-
|
|
469
|
+
def _parse_field_name(self, base_type: type[BaseType]) -> tuple[type[BaseType], str, int | None]:
|
|
470
|
+
"""Parses ``'*'* IDENTIFIER ('[' expr? ']')* (':' NUMBER)?``."""
|
|
471
|
+
type_ = base_type
|
|
689
472
|
|
|
690
|
-
|
|
691
|
-
|
|
473
|
+
# Pointer stars
|
|
474
|
+
while self._match(TokenType.STAR):
|
|
475
|
+
type_ = self.cs._make_pointer(type_)
|
|
692
476
|
|
|
693
|
-
|
|
694
|
-
|
|
477
|
+
# Field name
|
|
478
|
+
name = self._expect(*IDENTIFIER_TYPES).value
|
|
695
479
|
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
480
|
+
# Array dimensions
|
|
481
|
+
type_ = self._parse_array_dimensions(type_)
|
|
482
|
+
|
|
483
|
+
# Bitfield
|
|
484
|
+
bits = None
|
|
485
|
+
if self._match(TokenType.COLON):
|
|
486
|
+
bits = int(self._expect(TokenType.NUMBER).value, 0)
|
|
702
487
|
|
|
703
|
-
|
|
704
|
-
self.previous = self.tokens.pop(0)
|
|
705
|
-
return self.previous
|
|
488
|
+
return type_, name.strip(), bits
|
|
706
489
|
|
|
707
|
-
def
|
|
708
|
-
|
|
490
|
+
def _parse_array_dimensions(self, base_type: type[BaseType]) -> type[BaseType]:
|
|
491
|
+
"""Parse array dimensions following a field name, e.g., ``field[10][20]``."""
|
|
492
|
+
dimensions: list[int | Expression] = []
|
|
493
|
+
|
|
494
|
+
while self._match(TokenType.LBRACKET):
|
|
495
|
+
if self._at(TokenType.RBRACKET):
|
|
496
|
+
dimensions.append(None)
|
|
497
|
+
else:
|
|
498
|
+
expression = self._collect_until(TokenType.RBRACKET)
|
|
499
|
+
count = Expression(expression)
|
|
500
|
+
try:
|
|
501
|
+
count = count.evaluate(self.cs)
|
|
502
|
+
except Exception:
|
|
503
|
+
pass
|
|
504
|
+
dimensions.append(count)
|
|
505
|
+
self._expect(TokenType.RBRACKET)
|
|
506
|
+
|
|
507
|
+
type_ = base_type
|
|
508
|
+
for count in reversed(dimensions):
|
|
509
|
+
if issubclass(type_, BaseArray) and count is None:
|
|
510
|
+
raise ParserError("Depth required for multi-dimensional array")
|
|
511
|
+
type_ = self.cs._make_array(type_, count)
|
|
512
|
+
|
|
513
|
+
return type_
|
|
514
|
+
|
|
515
|
+
# Type resolution
|
|
516
|
+
|
|
517
|
+
def _parse_type_spec(self) -> type[BaseType]:
|
|
518
|
+
"""Parse a type specifier, handling multi-word types like ``unsigned long long``.
|
|
519
|
+
|
|
520
|
+
Uses lookahead to disambiguate type words from field names: if the next identifier is followed by a
|
|
521
|
+
field delimiter (any of ``;[:,}``) it is the field name, not part of the type — unless the current accumulated
|
|
522
|
+
parts don't form a valid type yet.
|
|
523
|
+
"""
|
|
524
|
+
first = self._current()
|
|
525
|
+
|
|
526
|
+
# Handle struct/union/enum/flag inline definitions as type specifiers
|
|
527
|
+
if first.type in (TokenType.STRUCT, TokenType.UNION):
|
|
528
|
+
return self._parse_struct_or_union()
|
|
529
|
+
|
|
530
|
+
if first.type in (TokenType.ENUM, TokenType.FLAG):
|
|
531
|
+
return self._parse_enum_or_flag()
|
|
532
|
+
|
|
533
|
+
# Otherwise, accumulate identifiers for the type specifier until we hit a non-identifier or a field delimiter
|
|
534
|
+
parts = [self._expect(TokenType.IDENTIFIER).value]
|
|
535
|
+
|
|
536
|
+
while self._at(TokenType.IDENTIFIER):
|
|
537
|
+
next_after = self._peek(1)
|
|
538
|
+
|
|
539
|
+
if next_after.type in (
|
|
540
|
+
TokenType.SEMICOLON,
|
|
541
|
+
TokenType.LBRACKET,
|
|
542
|
+
TokenType.COLON,
|
|
543
|
+
TokenType.COMMA,
|
|
544
|
+
TokenType.RBRACE,
|
|
545
|
+
):
|
|
546
|
+
# This identifier is followed by a field delimiter, it should be the field name,
|
|
547
|
+
# UNLESS the current parts don't form a valid type yet.
|
|
548
|
+
if " ".join(parts) in self.cs.types:
|
|
549
|
+
break
|
|
550
|
+
|
|
551
|
+
# Current parts don't resolve, consume and hope this completes the type name
|
|
552
|
+
# (will error on resolve if not).
|
|
553
|
+
parts.append(self._take().value)
|
|
554
|
+
elif next_after.type == TokenType.STAR:
|
|
555
|
+
# Field name starts with * (pointer). This identifier is the last type word, consume it and then stop.
|
|
556
|
+
parts.append(self._take().value)
|
|
557
|
+
break
|
|
558
|
+
elif next_after.type == TokenType.IDENTIFIER:
|
|
559
|
+
# More identifiers follow, consume this one as part of the type.
|
|
560
|
+
parts.append(self._take().value)
|
|
561
|
+
else:
|
|
562
|
+
break
|
|
709
563
|
|
|
710
|
-
|
|
711
|
-
token = self.consume()
|
|
712
|
-
if token.token != "EOL":
|
|
713
|
-
raise ParserError(f"line {self._lineno(token)}: expected EOL")
|
|
564
|
+
return self.cs.resolve(" ".join(parts))
|