Coverage for src/c2puml/core/parser_tokenizer.py: 88%

876 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-09-14 18:23 +0000

1#!/usr/bin/env python3 

2""" 

3Tokenizer module for C to PlantUML converter - Helper library for tokenizing C/C++ code 

4""" 

5 

6import logging 

7import re 

8from dataclasses import dataclass 

9from enum import Enum 

10from typing import List, Optional, Tuple 

11 

12 

13class TokenType(Enum): 

14 """Token types for C/C++ lexical analysis""" 

15 

16 # Keywords 

17 STRUCT = "STRUCT" 

18 ENUM = "ENUM" 

19 UNION = "UNION" 

20 TYPEDEF = "TYPEDEF" 

21 STATIC = "STATIC" 

22 EXTERN = "EXTERN" 

23 INLINE = "INLINE" 

24 LOCAL_INLINE = "LOCAL_INLINE" 

25 CONST = "CONST" 

26 VOID = "VOID" 

27 

28 # Data types 

29 CHAR = "CHAR" 

30 INT = "INT" 

31 FLOAT = "FLOAT" 

32 DOUBLE = "DOUBLE" 

33 LONG = "LONG" 

34 SHORT = "SHORT" 

35 UNSIGNED = "UNSIGNED" 

36 SIGNED = "SIGNED" 

37 

38 # Operators and punctuation 

39 LBRACE = "LBRACE" # { 

40 RBRACE = "RBRACE" # } 

41 LPAREN = "LPAREN" # ( 

42 RPAREN = "RPAREN" # ) 

43 LBRACKET = "LBRACKET" # [ 

44 RBRACKET = "RBRACKET" # ] 

45 SEMICOLON = "SEMICOLON" # ; 

46 COMMA = "COMMA" # , 

47 ASSIGN = "ASSIGN" # = 

48 ASTERISK = "ASTERISK" # * 

49 AMPERSAND = "AMPERSAND" # & 

50 ARROW = "ARROW" # -> 

51 

52 # Literals and identifiers 

53 IDENTIFIER = "IDENTIFIER" 

54 NUMBER = "NUMBER" 

55 STRING = "STRING" 

56 CHAR_LITERAL = "CHAR_LITERAL" 

57 

58 # Preprocessor 

59 INCLUDE = "INCLUDE" 

60 DEFINE = "DEFINE" 

61 PREPROCESSOR = "PREPROCESSOR" 

62 

63 # Special 

64 COMMENT = "COMMENT" 

65 WHITESPACE = "WHITESPACE" 

66 NEWLINE = "NEWLINE" 

67 EOF = "EOF" 

68 UNKNOWN = "UNKNOWN" 

69 

70 

71@dataclass 

72class Token: 

73 """Represents a single token in C/C++ code""" 

74 

75 type: TokenType 

76 value: str 

77 line: int 

78 column: int 

79 

80 def __repr__(self) -> str: 

81 return f"Token({self.type.name}, '{self.value}', {self.line}:{self.column})" 

82 

83 

84class CTokenizer: 

85 """Tokenizer for C/C++ source code""" 

86 

87 # Keywords mapping 

88 KEYWORDS = { 

89 "struct": TokenType.STRUCT, 

90 "enum": TokenType.ENUM, 

91 "union": TokenType.UNION, 

92 "typedef": TokenType.TYPEDEF, 

93 "static": TokenType.STATIC, 

94 "extern": TokenType.EXTERN, 

95 "inline": TokenType.INLINE, 

96 "local_inline": TokenType.LOCAL_INLINE, 

97 "const": TokenType.CONST, 

98 "void": TokenType.VOID, 

99 "char": TokenType.CHAR, 

100 "int": TokenType.INT, 

101 "float": TokenType.FLOAT, 

102 "double": TokenType.DOUBLE, 

103 "long": TokenType.LONG, 

104 "short": TokenType.SHORT, 

105 "unsigned": TokenType.UNSIGNED, 

106 "signed": TokenType.SIGNED, 

107 } 

108 

109 # Single character tokens 

110 SINGLE_CHAR_TOKENS = { 

111 "{": TokenType.LBRACE, 

112 "}": TokenType.RBRACE, 

113 "(": TokenType.LPAREN, 

114 ")": TokenType.RPAREN, 

115 "[": TokenType.LBRACKET, 

116 "]": TokenType.RBRACKET, 

117 ";": TokenType.SEMICOLON, 

118 ",": TokenType.COMMA, 

119 "=": TokenType.ASSIGN, 

120 "*": TokenType.ASTERISK, 

121 "&": TokenType.AMPERSAND, 

122 } 

123 

124 # Two character tokens 

125 TWO_CHAR_TOKENS = { 

126 "->": TokenType.ARROW, 

127 } 

128 

129 def __init__(self): 

130 self.logger = logging.getLogger(__name__) 

131 

132 # Compiled regex patterns for efficiency 

133 self.patterns = { 

134 "identifier": re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*"), 

135 "number": re.compile( 

136 r"0[xX][0-9a-fA-F]+[uUlL]*|0[bB][01]+[uUlL]*|0[0-7]+[uUlL]*|" 

137 r"\d+\.\d*([eE][+-]?\d+)?[fFlL]*|\d+([eE][+-]?\d+)?[fFlL]*|\d+[uUlL]*" 

138 ), 

139 "string": re.compile(r'"([^"\\]|\\.)*"'), 

140 "char": re.compile(r"'([^'\\]|\\.)'"), 

141 "comment_single": re.compile(r"//.*"), 

142 "comment_multi": re.compile(r"/\*.*?\*/", re.DOTALL), 

143 "preprocessor": re.compile( 

144 r"#(include|define|ifdef|ifndef|if|endif|elif|else|pragma|error|warning)\b.*" 

145 ), 

146 "whitespace": re.compile(r"[ \t]+"), 

147 "newline": re.compile(r"\n"), 

148 } 

149 

150 def tokenize(self, content: str) -> List[Token]: 

151 """Tokenize C/C++ source code content""" 

152 tokens = [] 

153 lines = content.splitlines() 

154 total_lines = len(lines) 

155 line_num = 1 

156 in_multiline_string = False 

157 multiline_string_value = "" 

158 multiline_string_start_line = 0 

159 multiline_string_start_col = 0 

160 in_multiline_comment = False 

161 multiline_comment_value = "" 

162 multiline_comment_start_line = 0 

163 multiline_comment_start_col = 0 

164 

165 for idx, line in enumerate(lines): 

166 if in_multiline_string: 

167 multiline_string_value += "\n" + line 

168 if '"' in line: 

169 # End of multiline string 

170 in_multiline_string = False 

171 tokens.append( 

172 Token( 

173 TokenType.STRING, 

174 multiline_string_value, 

175 multiline_string_start_line, 

176 multiline_string_start_col, 

177 ) 

178 ) 

179 elif in_multiline_comment: 

180 # Continue multi-line comment 

181 multiline_comment_value += "\n" + line 

182 comment_end = line.find("*/") 

183 if comment_end != -1: 

184 # End of multi-line comment 

185 in_multiline_comment = False 

186 multiline_comment_value = multiline_comment_value[ 

187 : multiline_comment_value.rfind("*/") + 2 

188 ] 

189 tokens.append( 

190 Token( 

191 TokenType.COMMENT, 

192 multiline_comment_value, 

193 multiline_comment_start_line, 

194 multiline_comment_start_col, 

195 ) 

196 ) 

197 else: 

198 line_tokens = self._tokenize_line(line, line_num) 

199 # Check if a string starts but does not end on this line 

200 if ( 

201 line_tokens 

202 and line_tokens[-1].type == TokenType.STRING 

203 and not line_tokens[-1].value.endswith('"') 

204 ): 

205 in_multiline_string = True 

206 multiline_string_value = line_tokens[-1].value 

207 multiline_string_start_line = line_tokens[-1].line 

208 multiline_string_start_col = line_tokens[-1].column 

209 tokens.extend(line_tokens[:-1]) 

210 # Check if a multi-line comment starts but does not end on this line 

211 elif ( 

212 line_tokens 

213 and line_tokens[-1].type == TokenType.COMMENT 

214 and line_tokens[-1].value.startswith("/*") 

215 and not line_tokens[-1].value.endswith("*/") 

216 ): 

217 in_multiline_comment = True 

218 multiline_comment_value = line_tokens[-1].value 

219 multiline_comment_start_line = line_tokens[-1].line 

220 multiline_comment_start_col = line_tokens[-1].column 

221 tokens.extend(line_tokens[:-1]) 

222 else: 

223 tokens.extend(line_tokens) 

224 

225 if line_num < total_lines: 

226 tokens.append(Token(TokenType.NEWLINE, "\n", line_num, len(line))) 

227 line_num += 1 

228 

229 if in_multiline_string: 

230 tokens.append( 

231 Token( 

232 TokenType.STRING, 

233 multiline_string_value, 

234 multiline_string_start_line, 

235 multiline_string_start_col, 

236 ) 

237 ) 

238 if in_multiline_comment: 

239 tokens.append( 

240 Token( 

241 TokenType.COMMENT, 

242 multiline_comment_value, 

243 multiline_comment_start_line, 

244 multiline_comment_start_col, 

245 ) 

246 ) 

247 

248 # Post-process tokens to merge multi-line macros 

249 tokens = self._merge_multiline_macros(tokens, lines) 

250 

251 tokens.append( 

252 Token(TokenType.EOF, "", total_lines, len(lines[-1]) if lines else 0) 

253 ) 

254 

255 return tokens 

256 

257 def _tokenize_line(self, line: str, line_num: int) -> List[Token]: 

258 """Tokenize a single line of code""" 

259 tokens = [] 

260 pos = 0 

261 

262 while pos < len(line): 

263 # Skip whitespace but track it 

264 if match := self.patterns["whitespace"].match(line, pos): 

265 tokens.append(Token(TokenType.WHITESPACE, match.group(), line_num, pos)) 

266 pos = match.end() 

267 continue 

268 

269 # Comments 

270 if match := self.patterns["comment_single"].match(line, pos): 

271 tokens.append(Token(TokenType.COMMENT, match.group(), line_num, pos)) 

272 pos = len(line) # Rest of line is comment 

273 continue 

274 

275 # Multi-line comments - check for /* at start of line or after whitespace 

276 if line[pos:].startswith("/*"): 

277 # Find the end of the comment 

278 comment_end = line.find("*/", pos) 

279 if comment_end != -1: 

280 # Comment ends on this line 

281 comment_text = line[pos : comment_end + 2] 

282 tokens.append(Token(TokenType.COMMENT, comment_text, line_num, pos)) 

283 pos = comment_end + 2 

284 continue 

285 else: 

286 # Comment continues to next line - create a partial comment token 

287 comment_text = line[pos:] 

288 tokens.append(Token(TokenType.COMMENT, comment_text, line_num, pos)) 

289 pos = len(line) 

290 continue 

291 

292 # Preprocessor directives 

293 if match := self.patterns["preprocessor"].match(line, pos): 

294 value = match.group() 

295 if value.startswith("#include"): 

296 tokens.append(Token(TokenType.INCLUDE, value, line_num, pos)) 

297 elif value.startswith("#define"): 

298 tokens.append(Token(TokenType.DEFINE, value, line_num, pos)) 

299 else: 

300 tokens.append(Token(TokenType.PREPROCESSOR, value, line_num, pos)) 

301 pos = len(line) # Rest of line is preprocessor 

302 continue 

303 

304 # String literals 

305 if ( 

306 line[pos] == '"' 

307 or ( 

308 pos > 0 

309 and line[pos - 1] in ["L", "u", "U", "R"] 

310 and line[pos] == '"' 

311 ) 

312 or (pos > 1 and line[pos - 2 : pos] == "u8" and line[pos] == '"') 

313 ): 

314 # Handle string literals with possible prefixes 

315 string_start = pos 

316 if line[pos - 2 : pos] == "u8": 

317 string_start -= 2 

318 elif line[pos - 1] in ["L", "u", "U", "R"]: 

319 string_start -= 1 

320 pos += 1 # Skip opening quote 

321 while pos < len(line): 

322 if line[pos] == '"': 

323 # Found closing quote 

324 string_text = line[string_start : pos + 1] 

325 tokens.append( 

326 Token(TokenType.STRING, string_text, line_num, string_start) 

327 ) 

328 pos += 1 

329 break 

330 elif line[pos] == "\\": 

331 pos += 2 

332 else: 

333 pos += 1 

334 else: 

335 string_text = line[string_start:] 

336 tokens.append( 

337 Token(TokenType.STRING, string_text, line_num, string_start) 

338 ) 

339 pos = len(line) 

340 continue 

341 

342 # Character literals 

343 if match := self.patterns["char"].match(line, pos): 

344 tokens.append( 

345 Token(TokenType.CHAR_LITERAL, match.group(), line_num, pos) 

346 ) 

347 pos = match.end() 

348 continue 

349 

350 # Numbers 

351 if match := self.patterns["number"].match(line, pos): 

352 tokens.append(Token(TokenType.NUMBER, match.group(), line_num, pos)) 

353 pos = match.end() 

354 continue 

355 

356 # Single character tokens 

357 if line[pos] in self.SINGLE_CHAR_TOKENS: 

358 token_type = self.SINGLE_CHAR_TOKENS[line[pos]] 

359 tokens.append(Token(token_type, line[pos], line_num, pos)) 

360 pos += 1 

361 continue 

362 

363 # Multi-character operators (<<, >>, ->) 

364 if line[pos : pos + 2] in ["<<", ">>", "->"]: 

365 op = line[pos : pos + 2] 

366 if op == "->": 

367 tokens.append(Token(TokenType.ARROW, op, line_num, pos)) 

368 else: 

369 tokens.append( 

370 Token( 

371 ( 

372 TokenType.OPERATOR 

373 if hasattr(TokenType, "OPERATOR") 

374 else TokenType.UNKNOWN 

375 ), 

376 op, 

377 line_num, 

378 pos, 

379 ) 

380 ) 

381 pos += 2 

382 continue 

383 

384 # Identifiers and keywords 

385 if match := self.patterns["identifier"].match(line, pos): 

386 value = match.group() 

387 token_type = self.KEYWORDS.get(value.lower(), TokenType.IDENTIFIER) 

388 tokens.append(Token(token_type, value, line_num, pos)) 

389 pos = match.end() 

390 continue 

391 

392 # Unknown character (always one at a time) 

393 tokens.append(Token(TokenType.UNKNOWN, line[pos], line_num, pos)) 

394 pos += 1 

395 

396 return tokens 

397 

398 def filter_tokens( 

399 self, tokens: List[Token], exclude_types: Optional[List[TokenType]] = None 

400 ) -> List[Token]: 

401 """Filter tokens by type""" 

402 if exclude_types is None: 

403 exclude_types = [ 

404 TokenType.WHITESPACE, 

405 TokenType.COMMENT, 

406 TokenType.NEWLINE, 

407 TokenType.EOF, 

408 ] 

409 

410 return [token for token in tokens if token.type not in exclude_types] 

411 

412 def _merge_multiline_macros( 

413 self, tokens: List[Token], lines: List[str] 

414 ) -> List[Token]: 

415 """Merge multi-line macro tokens that span multiple lines with backslashes""" 

416 merged_tokens = [] 

417 i = 0 

418 

419 while i < len(tokens): 

420 token = tokens[i] 

421 

422 if token.type == TokenType.DEFINE and token.value.rstrip().endswith("\\"): 

423 # Found a multi-line macro, merge with subsequent lines 

424 macro_content = token.value 

425 current_line = token.line 

426 

427 # Continue merging lines until we find one that doesn't end with backslash 

428 while macro_content.rstrip().endswith("\\"): 

429 # Remove the backslash and add a newline 

430 macro_content = macro_content.rstrip()[:-1] + "\n" 

431 current_line += 1 

432 

433 # Find the next line content 

434 if current_line <= len(lines): 

435 next_line = lines[current_line - 1] 

436 macro_content += next_line 

437 else: 

438 break 

439 

440 # Create a new token with the merged content 

441 merged_tokens.append( 

442 Token(TokenType.DEFINE, macro_content, token.line, token.column) 

443 ) 

444 else: 

445 merged_tokens.append(token) 

446 

447 i += 1 

448 

449 return merged_tokens 

450 

451 

452class StructureFinder: 

453 """Helper class to find C/C++ structures in token streams""" 

454 

455 def __init__(self, tokens: List[Token]): 

456 self.tokens = tokens 

457 self.pos = 0 

458 self.logger = logging.getLogger(__name__) 

459 

460 def _skip_whitespace(self) -> None: 

461 """Advance self.pos over consecutive WHITESPACE tokens.""" 

462 while self.pos < len(self.tokens) and self._current_token_is(TokenType.WHITESPACE): 

463 self.pos += 1 

464 

465 def find_structs(self) -> List[Tuple[int, int, str]]: 

466 """Find struct definitions in token stream 

467 

468 Returns: 

469 List of tuples (start_pos, end_pos, struct_name) 

470 """ 

471 structs = [] 

472 self.pos = 0 

473 

474 while self.pos < len(self.tokens): 

475 if self._current_token_is(TokenType.STRUCT): 

476 struct_info = self._parse_struct() 

477 if struct_info: 

478 structs.append(struct_info) 

479 elif self._current_token_is(TokenType.TYPEDEF): 

480 typedef_struct = self._parse_typedef_struct() 

481 if typedef_struct: 

482 structs.append(typedef_struct) 

483 else: 

484 self.pos += 1 

485 

486 return structs 

487 

488 def find_enums(self) -> List[Tuple[int, int, str]]: 

489 """Find enum definitions in token stream""" 

490 enums = [] 

491 self.pos = 0 

492 

493 while self.pos < len(self.tokens): 

494 if self._current_token_is(TokenType.ENUM): 

495 enum_info = self._parse_enum() 

496 if enum_info: 

497 enums.append(enum_info) 

498 elif self._current_token_is(TokenType.TYPEDEF): 

499 typedef_enum = self._parse_typedef_enum() 

500 if typedef_enum: 

501 enums.append(typedef_enum) 

502 else: 

503 self.pos += 1 

504 

505 return enums 

506 

507 def find_functions(self) -> List[Tuple[int, int, str, str, bool, bool]]: 

508 """Find all function declarations and definitions in the token stream 

509 

510 Returns: 

511 List of tuples (start_pos, end_pos, func_name, return_type, is_declaration, is_inline) 

512 """ 

513 functions = [] 

514 self.pos = 0 

515 

516 while self.pos < len(self.tokens): 

517 result = self._parse_function() 

518 if result: 

519 functions.append(result) 

520 

521 return functions 

522 

523 def find_unions(self) -> List[Tuple[int, int, str]]: 

524 """Find union definitions in token stream""" 

525 unions = [] 

526 self.pos = 0 

527 

528 while self.pos < len(self.tokens): 

529 if self._current_token_is(TokenType.UNION): 

530 union_info = self._parse_union() 

531 if union_info: 

532 unions.append(union_info) 

533 elif self._current_token_is(TokenType.TYPEDEF): 

534 typedef_union = self._parse_typedef_union() 

535 if typedef_union: 

536 unions.append(typedef_union) 

537 else: 

538 self.pos += 1 

539 

540 return unions 

541 

542 def _current_token_is(self, token_type: TokenType) -> bool: 

543 """Check if current token is of specified type""" 

544 return self.pos < len(self.tokens) and self.tokens[self.pos].type == token_type 

545 

546 def _peek_token(self, offset: int = 1) -> Optional[Token]: 

547 """Peek at token at current position + offset""" 

548 peek_pos = self.pos + offset 

549 return self.tokens[peek_pos] if peek_pos < len(self.tokens) else None 

550 

551 def _advance(self) -> Optional[Token]: 

552 """Advance to next token and return current""" 

553 if self.pos < len(self.tokens): 

554 token = self.tokens[self.pos] 

555 self.pos += 1 

556 return token 

557 return None 

558 

559 def _find_matching_brace(self, start_pos: int) -> Optional[int]: 

560 """Find matching closing brace starting from open brace position. 

561 

562 Delegates to shared parse_utils implementation to ensure one source of 

563 truth for balanced scanning across components. 

564 """ 

565 from .parse_utils import find_matching_brace as _find_brace 

566 

567 return _find_brace(self.tokens, start_pos) 

568 

569 def _parse_struct(self) -> Optional[Tuple[int, int, str]]: 

570 """Parse struct definition starting at current position""" 

571 start_pos = self.pos 

572 

573 # Consume 'struct' keyword 

574 if not self._current_token_is(TokenType.STRUCT): 

575 return None 

576 self._advance() 

577 

578 # Check if this struct is inside a cast expression by looking backwards 

579 check_pos = start_pos - 1 

580 while check_pos >= 0: 

581 if self.tokens[check_pos].type == TokenType.LPAREN: 

582 # Found opening parenthesis before struct - this is likely a cast expression 

583 return None 

584 elif self.tokens[check_pos].type in [TokenType.STRUCT, TokenType.TYPEDEF]: 

585 # Found another struct or typedef - this is not a cast expression 

586 break 

587 elif self.tokens[check_pos].type not in [TokenType.WHITESPACE, TokenType.COMMENT, TokenType.NEWLINE]: 

588 # Found some other token - this is not a cast expression 

589 break 

590 check_pos -= 1 

591 

592 # Skip whitespace 

593 self._skip_whitespace() 

594 

595 # Check if this is a cast expression: (struct type*) 

596 if self._current_token_is(TokenType.LPAREN): 

597 # Look ahead to see if this is a cast expression 

598 check_pos = self.pos + 1 

599 while check_pos < len(self.tokens): 

600 if self.tokens[check_pos].type == TokenType.RPAREN: 

601 # Found closing parenthesis - this is likely a cast expression 

602 return None 

603 elif self.tokens[check_pos].type == TokenType.LBRACE: 

604 # Found opening brace - this is a struct definition 

605 break 

606 elif self.tokens[check_pos].type == TokenType.SEMICOLON: 

607 # Found semicolon - this is a variable declaration 

608 return None 

609 check_pos += 1 

610 

611 # Get struct tag name (optional for anonymous structs) 

612 struct_tag = "" 

613 if self._current_token_is(TokenType.IDENTIFIER): 

614 struct_tag = self._advance().value 

615 

616 # Look for opening brace or semicolon 

617 while self.pos < len(self.tokens): 

618 if self._current_token_is(TokenType.LBRACE): 

619 # Found opening brace - this is a struct definition 

620 break 

621 elif self._current_token_is(TokenType.SEMICOLON): 

622 # Found semicolon before opening brace - this is a variable declaration 

623 return None 

624 self.pos += 1 

625 

626 if not self._current_token_is(TokenType.LBRACE): 

627 # This is a variable declaration 

628 return None 

629 

630 # Find matching closing brace 

631 brace_pos = self.pos 

632 end_brace_pos = self._find_matching_brace(brace_pos) 

633 

634 if end_brace_pos is None: 

635 return None 

636 

637 # Look for struct name after closing brace 

638 name_pos = end_brace_pos + 1 

639 struct_name = struct_tag # Default to tag name 

640 

641 # Check if this is a typedef struct by looking backwards 

642 is_typedef = False 

643 check_pos = start_pos - 1 

644 while check_pos >= 0: 

645 if self.tokens[check_pos].type == TokenType.TYPEDEF: 

646 is_typedef = True 

647 break 

648 elif self.tokens[check_pos].type in [ 

649 TokenType.STRUCT, 

650 TokenType.LBRACE, 

651 TokenType.RBRACE, 

652 ]: 

653 break 

654 check_pos -= 1 

655 

656 if is_typedef: 

657 # For typedef struct, look for the typedef name after the closing brace 

658 while name_pos < len(self.tokens): 

659 if self.tokens[name_pos].type == TokenType.IDENTIFIER: 

660 struct_name = self.tokens[name_pos].value 

661 break 

662 elif self.tokens[name_pos].type == TokenType.SEMICOLON: 

663 break 

664 name_pos += 1 

665 else: 

666 # Check if there's a variable name after the brace 

667 while name_pos < len(self.tokens): 

668 if self.tokens[name_pos].type == TokenType.IDENTIFIER: 

669 # This is a variable name 

670 struct_name = "" 

671 break 

672 elif self.tokens[name_pos].type == TokenType.SEMICOLON: 

673 break 

674 name_pos += 1 

675 

676 # Find semicolon (for struct definitions) 

677 self.pos = end_brace_pos + 1 

678 while self.pos < len(self.tokens) and not self._current_token_is( 

679 TokenType.SEMICOLON 

680 ): 

681 self.pos += 1 

682 

683 end_pos = self.pos 

684 return (start_pos, end_pos, struct_name) 

685 

686 def _parse_typedef_struct(self) -> Optional[Tuple[int, int, str]]: 

687 """Parse typedef struct definition""" 

688 start_pos = self.pos 

689 

690 # Consume 'typedef' 

691 if not self._current_token_is(TokenType.TYPEDEF): 

692 return None 

693 self._advance() 

694 

695 # Look for 'struct' 

696 if not self._current_token_is(TokenType.STRUCT): 

697 # Not a typedef struct, reset position 

698 self.pos = start_pos + 1 

699 return None 

700 

701 # Skip 'struct' 

702 self._advance() 

703 

704 # Skip whitespace 

705 self._skip_whitespace() 

706 

707 # Get struct tag name (optional) 

708 struct_tag = "" 

709 if self._current_token_is(TokenType.IDENTIFIER): 

710 struct_tag = self._advance().value 

711 

712 # Skip whitespace 

713 self._skip_whitespace() 

714 

715 # Check if this is a forward declaration (no braces) 

716 if not self._current_token_is(TokenType.LBRACE): 

717 # This is a forward declaration, skip it 

718 self.pos = start_pos + 1 

719 return None 

720 

721 # Find matching closing brace 

722 end_brace_pos = self._find_matching_brace(self.pos) 

723 if end_brace_pos is None: 

724 self.pos = start_pos + 1 

725 return None 

726 

727 # Look for typedef name after closing brace 

728 typedef_name = "" 

729 name_pos = end_brace_pos + 1 

730 while name_pos < len(self.tokens): 

731 if self.tokens[name_pos].type == TokenType.IDENTIFIER: 

732 typedef_name = self.tokens[name_pos].value 

733 break 

734 elif self.tokens[name_pos].type == TokenType.SEMICOLON: 

735 break 

736 name_pos += 1 

737 

738 # Find semicolon 

739 while ( 

740 name_pos < len(self.tokens) 

741 and not self.tokens[name_pos].type == TokenType.SEMICOLON 

742 ): 

743 name_pos += 1 

744 

745 end_pos = name_pos 

746 return (start_pos, end_pos, typedef_name) 

747 

748 def _parse_enum(self) -> Optional[Tuple[int, int, str]]: 

749 """Parse enum definition starting at current position""" 

750 start_pos = self.pos 

751 

752 # Consume 'enum' keyword 

753 if not self._current_token_is(TokenType.ENUM): 

754 return None 

755 self._advance() 

756 

757 # Skip whitespace 

758 self._skip_whitespace() 

759 

760 # Get enum tag name (optional for anonymous enums) 

761 enum_tag = "" 

762 if self._current_token_is(TokenType.IDENTIFIER): 

763 enum_tag = self._advance().value 

764 

765 # Find opening brace 

766 while self.pos < len(self.tokens) and not self._current_token_is( 

767 TokenType.LBRACE 

768 ): 

769 self.pos += 1 

770 

771 if not self._current_token_is(TokenType.LBRACE): 

772 return None 

773 

774 # Find matching closing brace 

775 brace_pos = self.pos 

776 end_brace_pos = self._find_matching_brace(brace_pos) 

777 

778 if end_brace_pos is None: 

779 return None 

780 

781 # Look for enum name after closing brace 

782 name_pos = end_brace_pos + 1 

783 enum_name = enum_tag # Default to tag name 

784 

785 # Check if this is a typedef enum by looking backwards 

786 is_typedef = False 

787 check_pos = start_pos - 1 

788 while check_pos >= 0: 

789 if self.tokens[check_pos].type == TokenType.TYPEDEF: 

790 is_typedef = True 

791 break 

792 elif self.tokens[check_pos].type in [ 

793 TokenType.ENUM, 

794 TokenType.LBRACE, 

795 TokenType.RBRACE, 

796 ]: 

797 break 

798 check_pos -= 1 

799 

800 if is_typedef: 

801 # For typedef enum, look for the typedef name after the closing brace 

802 while name_pos < len(self.tokens): 

803 if self.tokens[name_pos].type == TokenType.IDENTIFIER: 

804 enum_name = self.tokens[name_pos].value 

805 break 

806 elif self.tokens[name_pos].type == TokenType.SEMICOLON: 

807 break 

808 name_pos += 1 

809 elif not enum_tag: 

810 # Anonymous enum - check if there's a variable name after the brace 

811 while name_pos < len(self.tokens): 

812 if self.tokens[name_pos].type == TokenType.IDENTIFIER: 

813 # This is a variable name 

814 enum_name = "" 

815 break 

816 elif self.tokens[name_pos].type == TokenType.SEMICOLON: 

817 break 

818 name_pos += 1 

819 

820 # Find semicolon 

821 self.pos = end_brace_pos + 1 

822 while self.pos < len(self.tokens) and not self._current_token_is( 

823 TokenType.SEMICOLON 

824 ): 

825 self.pos += 1 

826 

827 end_pos = self.pos 

828 return (start_pos, end_pos, enum_name) 

829 

830 def _parse_typedef_enum(self) -> Optional[Tuple[int, int, str]]: 

831 """Parse typedef enum definition""" 

832 start_pos = self.pos 

833 

834 # Consume 'typedef' 

835 if not self._current_token_is(TokenType.TYPEDEF): 

836 return None 

837 self._advance() 

838 

839 # Look for 'enum' 

840 if not self._current_token_is(TokenType.ENUM): 

841 # Not a typedef enum, reset position 

842 self.pos = start_pos + 1 

843 return None 

844 

845 # Parse the enum part - this will return the tag name (e.g., StatusEnum_tag) 

846 enum_info = self._parse_enum() 

847 if not enum_info: 

848 self.pos = start_pos + 1 

849 return None 

850 

851 # For typedef enums, we want to return the tag name, not the typedef name 

852 # The typedef name will be handled separately in the parser 

853 return enum_info 

854 

855 def _parse_function(self) -> Optional[Tuple[int, int, str, str, bool, bool]]: 

856 """Parse function declaration/definition 

857 

858 Returns: 

859 Tuple of (start_pos, end_pos, func_name, return_type, is_declaration, is_inline) 

860 """ 

861 start_pos = self.pos 

862 

863 # Look for function pattern: [modifiers] return_type function_name (params) 

864 while self.pos < len(self.tokens): 

865 token = self.tokens[self.pos] 

866 

867 # If we hit a parenthesis, check if this is a function 

868 if token.type == TokenType.LPAREN: 

869 # Look backwards for function name 

870 if ( 

871 self.pos > 0 

872 and self.tokens[self.pos - 1].type == TokenType.IDENTIFIER 

873 ): 

874 func_name = self.tokens[self.pos - 1].value 

875 func_name_pos = self.pos - 1 

876 

877 # Look backwards from function name to find return type 

878 # Start from just before the function name 

879 return_type_end = func_name_pos - 1 

880 return_type_start = return_type_end 

881 

882 # Skip backwards over whitespace and comments 

883 while return_type_start >= 0: 

884 token_type = self.tokens[return_type_start].type 

885 if token_type in [ 

886 TokenType.WHITESPACE, 

887 TokenType.COMMENT, 

888 TokenType.NEWLINE, 

889 ]: 

890 return_type_start -= 1 

891 else: 

892 break 

893 

894 # If we found a non-whitespace token, that's the end of the return type 

895 # Find the start by looking backwards from there 

896 if return_type_start >= 0: 

897 return_type_end = return_type_start 

898 return_type_start = return_type_end 

899 

900 # Define modifiers set (used in token type checking below) 

901 

902 # Collect all tokens that are part of the return type (including modifiers) 

903 return_type_tokens = [] 

904 

905 # Look back at most 10 tokens to capture multi-token return types 

906 max_lookback = max(0, func_name_pos - 10) 

907 current_pos = return_type_start 

908 

909 # Collect tokens backwards until we hit a limit or non-return-type token 

910 while current_pos >= max_lookback: 

911 token_type = self.tokens[current_pos].type 

912 if token_type in [ 

913 TokenType.IDENTIFIER, 

914 TokenType.INT, 

915 TokenType.VOID, 

916 TokenType.CHAR, 

917 TokenType.FLOAT, 

918 TokenType.DOUBLE, 

919 TokenType.LONG, 

920 TokenType.SHORT, 

921 TokenType.UNSIGNED, 

922 TokenType.SIGNED, 

923 TokenType.ASTERISK, 

924 TokenType.CONST, 

925 TokenType.STATIC, 

926 TokenType.EXTERN, 

927 TokenType.INLINE, 

928 TokenType.LOCAL_INLINE, 

929 ]: 

930 return_type_tokens.insert(0, self.tokens[current_pos]) 

931 current_pos -= 1 

932 elif token_type in [ 

933 TokenType.WHITESPACE, 

934 TokenType.COMMENT, 

935 TokenType.NEWLINE, 

936 ]: 

937 # Skip whitespace and continue looking 

938 current_pos -= 1 

939 else: 

940 break 

941 

942 # Extract return type 

943 if return_type_tokens: 

944 return_type = " ".join( 

945 t.value for t in return_type_tokens 

946 ).strip() 

947 

948 # Check if function is inline 

949 is_inline = any( 

950 token.type in [TokenType.INLINE, TokenType.LOCAL_INLINE] 

951 for token in return_type_tokens 

952 ) 

953 # Fallback: detect inline by textual prefix in return_type 

954 # This covers cases where macros like LOCAL_INLINE were not token-mapped earlier 

955 if not is_inline: 

956 rt_upper = return_type.upper() 

957 if rt_upper.startswith("LOCAL_INLINE ") or rt_upper.startswith("INLINE "): 

958 is_inline = True 

959 

960 # Find end of function (either ; for declaration or { for definition) 

961 end_pos = self._find_function_end(self.pos) 

962 if end_pos: 

963 # Determine if this is a declaration or definition 

964 is_declaration = self._is_function_declaration(end_pos) 

965 self.pos = end_pos + 1 

966 return ( 

967 start_pos, 

968 end_pos, 

969 func_name, 

970 return_type, 

971 is_declaration, 

972 is_inline, 

973 ) 

974 

975 self.pos += 1 

976 

977 # Prevent infinite loops - if we've gone too far, this isn't a function 

978 if self.pos - start_pos > 50: 

979 break 

980 

981 # Reset position if no function found 

982 self.pos = start_pos + 1 

983 return None 

984 

985 def _is_function_declaration(self, end_pos: int) -> bool: 

986 """Check if the function at end_pos is a declaration (ends with ;) or definition (ends with })""" 

987 if end_pos >= len(self.tokens): 

988 return False 

989 

990 # Look backwards from end_pos to find the last significant token 

991 pos = end_pos 

992 while pos >= 0: 

993 token_type = self.tokens[pos].type 

994 if token_type not in [ 

995 TokenType.WHITESPACE, 

996 TokenType.COMMENT, 

997 TokenType.NEWLINE, 

998 ]: 

999 return token_type == TokenType.SEMICOLON 

1000 pos -= 1 

1001 

1002 return False 

1003 

1004 def _find_function_end(self, start_pos: int) -> Optional[int]: 

1005 """Find end of function declaration or definition""" 

1006 pos = start_pos 

1007 

1008 # Find matching closing parenthesis 

1009 if pos >= len(self.tokens) or self.tokens[pos].type != TokenType.LPAREN: 

1010 return None 

1011 

1012 depth = 1 

1013 pos += 1 

1014 

1015 while pos < len(self.tokens) and depth > 0: 

1016 if self.tokens[pos].type == TokenType.LPAREN: 

1017 depth += 1 

1018 elif self.tokens[pos].type == TokenType.RPAREN: 

1019 depth -= 1 

1020 pos += 1 

1021 

1022 if depth > 0: 

1023 return None 

1024 

1025 # Look for either ; (declaration) or { (definition) 

1026 while pos < len(self.tokens): 

1027 if self.tokens[pos].type == TokenType.SEMICOLON: 

1028 return pos 

1029 elif self.tokens[pos].type == TokenType.LBRACE: 

1030 # Function definition - find matching brace (delegate) 

1031 end_brace = self._find_matching_brace(pos) 

1032 return end_brace if end_brace else pos 

1033 pos += 1 

1034 

1035 return None 

1036 

1037 def _parse_union(self) -> Optional[Tuple[int, int, str]]: 

1038 """Parse union definition""" 

1039 if not self._current_token_is(TokenType.UNION): 

1040 return None 

1041 

1042 start_pos = self.pos 

1043 self._advance() # Consumes 'union' 

1044 

1045 # Skip whitespace 

1046 while self.pos < len(self.tokens) and self._current_token_is( 

1047 TokenType.WHITESPACE 

1048 ): 

1049 self.pos += 1 

1050 

1051 # Get union tag name (optional for anonymous unions) 

1052 union_tag = "" 

1053 if self._current_token_is(TokenType.IDENTIFIER): 

1054 union_tag = self._advance().value 

1055 

1056 # Find opening brace 

1057 while self.pos < len(self.tokens) and not self._current_token_is( 

1058 TokenType.LBRACE 

1059 ): 

1060 self.pos += 1 

1061 

1062 if self.pos >= len(self.tokens): 

1063 return None 

1064 

1065 # Find matching closing brace 

1066 end_pos = self._find_matching_brace(self.pos) 

1067 if end_pos is None: 

1068 return None 

1069 

1070 # Look for union name after closing brace (for typedefs or named unions) 

1071 union_name = union_tag # Default to tag name 

1072 

1073 # Skip to semicolon 

1074 self.pos = end_pos + 1 

1075 while self.pos < len(self.tokens) and not self._current_token_is( 

1076 TokenType.SEMICOLON 

1077 ): 

1078 if self._current_token_is(TokenType.IDENTIFIER): 

1079 union_name = self._advance().value 

1080 break 

1081 self.pos += 1 

1082 

1083 return (start_pos, end_pos, union_name) 

1084 

1085 def _parse_typedef_union(self) -> Optional[Tuple[int, int, str]]: 

1086 """Parse typedef union definition""" 

1087 if not self._current_token_is(TokenType.TYPEDEF): 

1088 return None 

1089 

1090 start_pos = self.pos 

1091 self._advance() # Consumes 'typedef' 

1092 

1093 # Skip whitespace 

1094 while self.pos < len(self.tokens) and self._current_token_is( 

1095 TokenType.WHITESPACE 

1096 ): 

1097 self.pos += 1 

1098 

1099 # Check if next token is 'union' 

1100 if not self._current_token_is(TokenType.UNION): 

1101 return None 

1102 

1103 self._advance() # Consumes 'union' 

1104 

1105 # Skip whitespace 

1106 while self.pos < len(self.tokens) and self._current_token_is( 

1107 TokenType.WHITESPACE 

1108 ): 

1109 self.pos += 1 

1110 

1111 # Get union tag name (optional) 

1112 union_tag = "" 

1113 if self._current_token_is(TokenType.IDENTIFIER): 

1114 union_tag = self._advance().value 

1115 

1116 # Find opening brace 

1117 while self.pos < len(self.tokens) and not self._current_token_is( 

1118 TokenType.LBRACE 

1119 ): 

1120 self.pos += 1 

1121 

1122 if self.pos >= len(self.tokens): 

1123 return None 

1124 

1125 # Find matching closing brace 

1126 end_pos = self._find_matching_brace(self.pos) 

1127 if end_pos is None: 

1128 return None 

1129 

1130 # Look for typedef name after closing brace 

1131 typedef_name = "" 

1132 self.pos = end_pos + 1 

1133 while self.pos < len(self.tokens) and not self._current_token_is( 

1134 TokenType.SEMICOLON 

1135 ): 

1136 if self._current_token_is(TokenType.IDENTIFIER): 

1137 typedef_name = self._advance().value 

1138 break 

1139 self.pos += 1 

1140 

1141 return (start_pos, end_pos, typedef_name) 

1142 

1143 

1144def extract_token_range(tokens: List[Token], start: int, end: int) -> str: 

1145 """Extract raw text from token range, excluding whitespace, comments, and newlines""" 

1146 if start >= len(tokens) or end >= len(tokens) or start > end: 

1147 return "" 

1148 return " ".join( 

1149 token.value 

1150 for token in tokens[start : end + 1] 

1151 if token.type 

1152 not in [TokenType.WHITESPACE, TokenType.COMMENT, TokenType.NEWLINE] 

1153 ) 

1154 

1155 

1156def find_struct_fields( 

1157 tokens: List[Token], struct_start: int, struct_end: int 

1158) -> List[Tuple[str, str]]: 

1159 """Extract field information from struct token range 

1160 Returns: 

1161 List of tuples (field_name, field_type) 

1162 """ 

1163 fields = [] 

1164 pos = struct_start 

1165 while pos <= struct_end and tokens[pos].type != TokenType.LBRACE: 

1166 pos += 1 

1167 if pos > struct_end: 

1168 return fields 

1169 pos += 1 # Skip opening brace 

1170 

1171 # Find the closing brace position of the main struct body 

1172 closing_brace_pos = pos 

1173 brace_count = 1 # Start at 1 because we're already past the opening brace 

1174 while closing_brace_pos <= struct_end: 

1175 if tokens[closing_brace_pos].type == TokenType.LBRACE: 

1176 brace_count += 1 

1177 elif tokens[closing_brace_pos].type == TokenType.RBRACE: 

1178 brace_count -= 1 

1179 if brace_count == 0: 

1180 # This is the closing brace of the main struct body 

1181 break 

1182 closing_brace_pos += 1 

1183 

1184 # Only parse fields up to the closing brace 

1185 while pos < closing_brace_pos and tokens[pos].type != TokenType.RBRACE: 

1186 field_tokens = [] 

1187 # Collect tokens until we find the semicolon that ends this field 

1188 # For nested structures, we need to handle braces properly 

1189 brace_count = 0 

1190 field_start_pos = pos 

1191 

1192 # First pass: collect tokens until we find the semicolon outside of braces 

1193 while pos < closing_brace_pos: 

1194 if tokens[pos].type == TokenType.LBRACE: 

1195 brace_count += 1 

1196 elif tokens[pos].type == TokenType.RBRACE: 

1197 brace_count -= 1 

1198 # Only stop if we're at the main closing brace 

1199 if pos == closing_brace_pos: 

1200 break 

1201 elif tokens[pos].type == TokenType.SEMICOLON and brace_count == 0: 

1202 # This is the semicolon that ends the field 

1203 break 

1204 

1205 if tokens[pos].type not in [TokenType.WHITESPACE, TokenType.COMMENT, TokenType.NEWLINE]: 

1206 field_tokens.append(tokens[pos]) 

1207 pos += 1 

1208 

1209 # For nested structures, we need to continue collecting tokens until we find the field name 

1210 # and the semicolon that ends the entire field 

1211 if (len(field_tokens) >= 3 and 

1212 field_tokens[0].type in [TokenType.STRUCT, TokenType.UNION] and 

1213 field_tokens[1].type == TokenType.LBRACE): 

1214 # This might be a nested structure, continue collecting until we find the field name 

1215 temp_pos = pos 

1216 brace_count = 0 # Track nested braces to find the correct field boundary 

1217 while temp_pos < len(tokens): 

1218 if tokens[temp_pos].type == TokenType.LBRACE: 

1219 brace_count += 1 

1220 elif tokens[temp_pos].type == TokenType.RBRACE: 

1221 brace_count -= 1 

1222 elif tokens[temp_pos].type == TokenType.SEMICOLON and brace_count == 0: 

1223 # Found the semicolon that ends the field (not inside nested braces) 

1224 break 

1225 

1226 if tokens[temp_pos].type not in [TokenType.WHITESPACE, TokenType.COMMENT, TokenType.NEWLINE]: 

1227 field_tokens.append(tokens[temp_pos]) 

1228 temp_pos += 1 

1229 pos = temp_pos 

1230 

1231 # Parse field from collected tokens 

1232 if len(field_tokens) >= 2: 

1233 # Check if this is a nested struct field 

1234 if ( 

1235 len(field_tokens) >= 3 

1236 and field_tokens[0].type == TokenType.STRUCT 

1237 and field_tokens[1].type == TokenType.LBRACE 

1238 ): 

1239 # This is a nested struct - find the field name after the closing brace 

1240 # Look for the pattern: struct { ... } field_name; 

1241 field_name = None 

1242 # Find the LAST closing brace and then the field name 

1243 # This handles deeply nested structures correctly 

1244 for i in range(len(field_tokens) - 1, -1, -1): 

1245 if field_tokens[i].type == TokenType.RBRACE and i + 1 < len(field_tokens): 

1246 # The field name should be the next identifier after the closing brace 

1247 for j in range(i + 1, len(field_tokens)): 

1248 if field_tokens[j].type == TokenType.IDENTIFIER: 

1249 field_name = field_tokens[j].value 

1250 break 

1251 if field_name: 

1252 break 

1253 

1254 if field_name: 

1255 # Extract the content between braces for anonymous processor using special format 

1256 content = _extract_brace_content(field_tokens) 

1257 if content: 

1258 # Preserve content for anonymous processor using special format 

1259 import base64 

1260 encoded_content = base64.b64encode(content.encode()).decode() 

1261 field_type = f"struct {{ /*ANON:{encoded_content}:{field_name}*/ ... }}" 

1262 else: 

1263 field_type = "struct { ... }" 

1264 

1265 if field_name not in ["[", "]", ";", "}"]: 

1266 fields.append((field_name, field_type)) 

1267 # Skip parsing the nested struct's fields as separate fields 

1268 # Let the normal flow handle semicolon advancement 

1269 else: 

1270 # Anonymous nested struct without a field name 

1271 content = _extract_brace_content(field_tokens) 

1272 if content: 

1273 import base64 

1274 encoded_content = base64.b64encode(content.encode()).decode() 

1275 # Use generic field name for anonymous struct 

1276 generic_name = "__anonymous_struct__" 

1277 field_type = f"struct {{ /*ANON:{encoded_content}:{generic_name}*/ ... }}" 

1278 else: 

1279 generic_name = "__anonymous_struct__" 

1280 field_type = "struct { ... }" 

1281 fields.append((generic_name, field_type)) 

1282 # Check if this is a nested union field 

1283 elif ( 

1284 len(field_tokens) >= 3 

1285 and field_tokens[0].type == TokenType.UNION 

1286 and field_tokens[1].type == TokenType.LBRACE 

1287 ): 

1288 # This is a nested union - find the field name after the closing brace 

1289 # Look for the pattern: union { ... } field_name; 

1290 field_name = None 

1291 # Find the LAST closing brace and then the field name 

1292 # This handles deeply nested structures correctly 

1293 for i in range(len(field_tokens) - 1, -1, -1): 

1294 if field_tokens[i].type == TokenType.RBRACE and i + 1 < len(field_tokens): 

1295 # The field name should be the next identifier after the closing brace 

1296 for j in range(i + 1, len(field_tokens)): 

1297 if field_tokens[j].type == TokenType.IDENTIFIER: 

1298 field_name = field_tokens[j].value 

1299 break 

1300 if field_name: 

1301 break 

1302 

1303 if field_name: 

1304 # Extract the content between braces for anonymous processor 

1305 content = _extract_brace_content(field_tokens) 

1306 if content: 

1307 # Preserve content for anonymous processor using special format 

1308 import base64 

1309 encoded_content = base64.b64encode(content.encode()).decode() 

1310 field_type = f"union {{ /*ANON:{encoded_content}:{field_name}*/ ... }}" 

1311 else: 

1312 field_type = "union { ... }" 

1313 

1314 if field_name not in ["[", "]", ";", "}"]: 

1315 fields.append((field_name, field_type)) 

1316 # Skip parsing the nested union's fields as separate fields 

1317 # Let the normal flow handle semicolon advancement 

1318 else: 

1319 # Anonymous nested union without a field name 

1320 content = _extract_brace_content(field_tokens) 

1321 if content: 

1322 import base64 

1323 encoded_content = base64.b64encode(content.encode()).decode() 

1324 generic_name = "__anonymous_union__" 

1325 field_type = f"union {{ /*ANON:{encoded_content}:{generic_name}*/ ... }}" 

1326 else: 

1327 generic_name = "__anonymous_union__" 

1328 field_type = "union { ... }" 

1329 fields.append((generic_name, field_type)) 

1330 # Function pointer array field: type (*name[size])(params) 

1331 elif ( 

1332 len(field_tokens) >= 8 

1333 and field_tokens[1].type == TokenType.LPAREN 

1334 and field_tokens[2].type == TokenType.ASTERISK 

1335 and any(t.type == TokenType.LBRACKET for t in field_tokens) 

1336 and any(t.type == TokenType.RBRACKET for t in field_tokens) 

1337 ): 

1338 # Find the function pointer name (between * and [) 

1339 # Look for the identifier between * and [ 

1340 name_start = 3 # After the * 

1341 name_end = None 

1342 for i in range(name_start, len(field_tokens)): 

1343 if field_tokens[i].type == TokenType.LBRACKET: 

1344 name_end = i 

1345 break 

1346 

1347 if name_end is not None: 

1348 field_name = " ".join( 

1349 t.value for t in field_tokens[name_start:name_end] 

1350 ) 

1351 

1352 # Format the type properly - preserve spaces between tokens but not around brackets/parentheses 

1353 formatted_tokens = [] 

1354 for j, token in enumerate(field_tokens): 

1355 if token.type in [ 

1356 TokenType.LPAREN, 

1357 TokenType.RPAREN, 

1358 TokenType.LBRACKET, 

1359 TokenType.RBRACKET, 

1360 ]: 

1361 # Don't add spaces around brackets/parentheses 

1362 formatted_tokens.append(token.value) 

1363 elif j > 0 and field_tokens[j - 1].type not in [ 

1364 TokenType.LPAREN, 

1365 TokenType.RPAREN, 

1366 TokenType.LBRACKET, 

1367 TokenType.RBRACKET, 

1368 ]: 

1369 # Add space before token if previous token wasn't a bracket/parenthesis 

1370 formatted_tokens.append(" " + token.value) 

1371 else: 

1372 # No space before token 

1373 formatted_tokens.append(token.value) 

1374 field_type = "".join(formatted_tokens) 

1375 

1376 # Validate and add the field 

1377 if ( 

1378 field_name 

1379 and field_name.strip() 

1380 and field_type.strip() 

1381 and field_name not in ["[", "]", ";", "}"] 

1382 ): 

1383 stripped_name = field_name.strip() 

1384 stripped_type = field_type.strip() 

1385 if stripped_name and stripped_type: 

1386 fields.append((stripped_name, stripped_type)) 

1387 # Function pointer field: type (*name)(params) or type (*name[size])(params) 

1388 elif ( 

1389 len(field_tokens) >= 5 

1390 and field_tokens[1].type == TokenType.LPAREN and field_tokens[2].type == TokenType.ASTERISK 

1391 ): 

1392 # Find the opening parenthesis and asterisk pattern 

1393 func_ptr_start = None 

1394 for i in range(len(field_tokens) - 1): 

1395 if field_tokens[i].type == TokenType.LPAREN and field_tokens[i + 1].type == TokenType.ASTERISK: 

1396 func_ptr_start = i 

1397 break 

1398 

1399 if func_ptr_start is not None: 

1400 # Extract the type (everything before the opening parenthesis) 

1401 type_tokens = field_tokens[:func_ptr_start] 

1402 field_type = " ".join(t.value for t in type_tokens) 

1403 

1404 # Find the closing parenthesis after the function name 

1405 paren_count = 0 

1406 name_end = None 

1407 for i in range(func_ptr_start, len(field_tokens)): 

1408 if field_tokens[i].type == TokenType.LPAREN: 

1409 paren_count += 1 

1410 elif field_tokens[i].type == TokenType.RPAREN: 

1411 paren_count -= 1 

1412 if paren_count == 0 and i > func_ptr_start + 1: 

1413 name_end = i 

1414 break 

1415 

1416 if name_end is not None: 

1417 # Extract function name (between * and closing parenthesis) 

1418 name_tokens = field_tokens[func_ptr_start + 2:name_end] 

1419 field_name = " ".join(t.value for t in name_tokens) 

1420 

1421 # Extract the parameter list as part of the type 

1422 param_tokens = field_tokens[name_end + 1:] 

1423 param_type = " ".join(t.value for t in param_tokens) 

1424 

1425 # Combine type and parameter list (without the function name in the type) 

1426 # The function name is already extracted as field_name, so we don't include it in the type 

1427 func_ptr_start_tokens = field_tokens[func_ptr_start:func_ptr_start + 2] # ( * 

1428 func_ptr_end_tokens = field_tokens[name_end:name_end + 1] # ) 

1429 full_type = field_type + " " + " ".join(t.value for t in func_ptr_start_tokens) + " " + " ".join(t.value for t in func_ptr_end_tokens) + " " + param_type 

1430 

1431 if ( 

1432 field_name 

1433 and field_name.strip() 

1434 and full_type.strip() 

1435 and field_name not in ["[", "]", ";", "}"] 

1436 ): 

1437 stripped_name = field_name.strip() 

1438 stripped_type = full_type.strip() 

1439 if stripped_name and stripped_type: 

1440 fields.append((stripped_name, stripped_type)) 

1441 # Array field(s): support multi-dimensional arrays and normalize numeric suffixes (e.g., 2U -> 2) 

1442 elif ( 

1443 len(field_tokens) >= 4 

1444 and field_tokens[-1].type == TokenType.RBRACKET 

1445 and any(t.type == TokenType.LBRACKET for t in field_tokens) 

1446 ): 

1447 # Robust multi-dimensional array handling using shared utils 

1448 from .parse_utils import ( 

1449 collect_array_dimensions_from_tokens, 

1450 join_type_with_dims, 

1451 normalize_dim_value, 

1452 ) 

1453 

1454 # Find the first '[' scanning forward (start of dimensions) 

1455 first_lbracket_idx = None 

1456 for idx in range(0, len(field_tokens)): 

1457 if field_tokens[idx].type == TokenType.LBRACKET: 

1458 first_lbracket_idx = idx 

1459 break 

1460 if first_lbracket_idx is None: 

1461 pass 

1462 else: 

1463 # Find field name just before this '[' 

1464 name_idx = None 

1465 for j in range(first_lbracket_idx - 1, -1, -1): 

1466 if field_tokens[j].type == TokenType.IDENTIFIER: 

1467 name_idx = j 

1468 break 

1469 if name_idx is not None: 

1470 field_name = field_tokens[name_idx].value 

1471 base_type_tokens = field_tokens[:name_idx] 

1472 base_type = " ".join(t.value for t in base_type_tokens).strip() 

1473 

1474 dims, _next = collect_array_dimensions_from_tokens(field_tokens, first_lbracket_idx) 

1475 # Normalize pure numeric suffixes like 5U/6UL to base number for dims 

1476 normalized_dims = [normalize_dim_value(d) for d in dims] 

1477 

1478 field_type = join_type_with_dims(base_type, normalized_dims) 

1479 if ( 

1480 field_name 

1481 and field_name.strip() 

1482 and field_type.strip() 

1483 and field_name not in ["[", "]", ";", "}"] 

1484 ): 

1485 stripped_name = field_name.strip() 

1486 stripped_type = field_type.strip() 

1487 if stripped_name and stripped_type: 

1488 fields.append((stripped_name, stripped_type)) 

1489 else: 

1490 # Regular field: type name 

1491 # Check if this field declaration contains commas (multiple fields of same type) 

1492 comma_positions = [] 

1493 paren_count = 0 

1494 brace_count = 0 

1495 

1496 # Find comma positions that are outside of parentheses and braces 

1497 for i, token in enumerate(field_tokens): 

1498 if token.type == TokenType.LPAREN: 

1499 paren_count += 1 

1500 elif token.type == TokenType.RPAREN: 

1501 paren_count -= 1 

1502 elif token.type == TokenType.LBRACE: 

1503 brace_count += 1 

1504 elif token.type == TokenType.RBRACE: 

1505 brace_count -= 1 

1506 elif token.type == TokenType.COMMA and paren_count == 0 and brace_count == 0: 

1507 comma_positions.append(i) 

1508 

1509 if comma_positions: 

1510 # Multiple fields of the same type: "int x, y, z;" 

1511 # Extract the type (everything before the first field name) 

1512 first_field_start = None 

1513 for i in range(len(field_tokens)): 

1514 if field_tokens[i].type == TokenType.IDENTIFIER: 

1515 first_field_start = i 

1516 break 

1517 

1518 if first_field_start is not None: 

1519 type_tokens = field_tokens[:first_field_start] 

1520 field_type = " ".join(t.value for t in type_tokens) 

1521 

1522 # Split fields on commas 

1523 field_starts = [first_field_start] + [pos + 1 for pos in comma_positions] 

1524 field_ends = comma_positions + [len(field_tokens)] 

1525 

1526 for start, end in zip(field_starts, field_ends): 

1527 if start < end: 

1528 field_name_tokens = field_tokens[start:end] 

1529 field_name = " ".join(t.value for t in field_name_tokens) 

1530 

1531 if ( 

1532 field_name 

1533 and field_name.strip() 

1534 and field_type.strip() 

1535 and field_name not in ["[", "]", ";", "}"] 

1536 ): 

1537 stripped_name = field_name.strip() 

1538 stripped_type = field_type.strip() 

1539 if stripped_name and stripped_type: 

1540 fields.append((stripped_name, stripped_type)) 

1541 else: 

1542 # Single field: type name 

1543 field_name = field_tokens[-1].value 

1544 field_type = " ".join(t.value for t in field_tokens[:-1]) 

1545 if ( 

1546 field_name not in ["[", "]", ";", "}"] 

1547 and field_name 

1548 and field_name.strip() 

1549 and field_type.strip() 

1550 ): 

1551 # Additional validation to ensure we don't have empty strings 

1552 stripped_name = field_name.strip() 

1553 stripped_type = field_type.strip() 

1554 if stripped_name and stripped_type: 

1555 fields.append((stripped_name, stripped_type)) 

1556 if pos < closing_brace_pos: 

1557 pos += 1 # Skip semicolon 

1558 return fields 

1559 

1560 

1561def find_enum_values(tokens: List[Token], enum_start: int, enum_end: int) -> List[str]: 

1562 """Extract enum values from enum token range""" 

1563 values = [] 

1564 pos = enum_start 

1565 while pos <= enum_end and tokens[pos].type != TokenType.LBRACE: 

1566 pos += 1 

1567 if pos > enum_end: 

1568 return values 

1569 pos += 1 # Skip opening brace 

1570 current_value = [] 

1571 while pos <= enum_end and tokens[pos].type != TokenType.RBRACE: 

1572 token = tokens[pos] 

1573 if token.type == TokenType.COMMA: 

1574 if current_value: 

1575 filtered_value = [ 

1576 t 

1577 for t in current_value 

1578 if t.type not in [TokenType.WHITESPACE, TokenType.COMMENT] 

1579 ] 

1580 if filtered_value: 

1581 value_str = " ".join(t.value for t in filtered_value).strip() 

1582 if value_str: 

1583 values.append(value_str) 

1584 current_value = [] 

1585 elif token.type not in [TokenType.WHITESPACE, TokenType.COMMENT]: 

1586 current_value.append(token) 

1587 pos += 1 

1588 if current_value: 

1589 filtered_value = [ 

1590 t 

1591 for t in current_value 

1592 if t.type not in [TokenType.WHITESPACE, TokenType.COMMENT] 

1593 ] 

1594 if filtered_value: 

1595 value_str = " ".join(t.value for t in filtered_value).strip() 

1596 if value_str: 

1597 values.append(value_str) 

1598 return values 

1599 

1600 

1601def _extract_brace_content(field_tokens: List[Token]) -> str: 

1602 """Extract the content between braces from field tokens. 

1603  

1604 Args: 

1605 field_tokens: List of tokens representing a field with anonymous structure 

1606  

1607 Returns: 

1608 String content between the braces, or empty string if not found 

1609 """ 

1610 content_tokens = [] 

1611 in_braces = False 

1612 brace_count = 0 

1613 

1614 for token in field_tokens: 

1615 if token.type == TokenType.LBRACE: 

1616 if not in_braces: 

1617 in_braces = True 

1618 brace_count = 1 

1619 else: 

1620 brace_count += 1 

1621 content_tokens.append(token) 

1622 elif token.type == TokenType.RBRACE: 

1623 if in_braces: 

1624 brace_count -= 1 

1625 if brace_count == 0: 

1626 # Found the closing brace 

1627 break 

1628 else: 

1629 content_tokens.append(token) 

1630 elif in_braces: 

1631 content_tokens.append(token) 

1632 

1633 # Convert tokens back to text preserving spacing 

1634 if content_tokens: 

1635 result = "" 

1636 for i, token in enumerate(content_tokens): 

1637 result += token.value 

1638 # Add space after most tokens except when next token is punctuation 

1639 if (i < len(content_tokens) - 1 and 

1640 token.type not in [TokenType.WHITESPACE, TokenType.NEWLINE] and 

1641 content_tokens[i + 1].type not in [TokenType.LBRACKET, TokenType.RBRACKET, 

1642 TokenType.SEMICOLON, TokenType.COMMA, 

1643 TokenType.WHITESPACE, TokenType.NEWLINE]): 

1644 result += " " 

1645 return result 

1646 return ""