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