fbuild 1.2.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. fbuild/__init__.py +390 -0
  2. fbuild/assets/example.txt +1 -0
  3. fbuild/build/__init__.py +117 -0
  4. fbuild/build/archive_creator.py +186 -0
  5. fbuild/build/binary_generator.py +444 -0
  6. fbuild/build/build_component_factory.py +131 -0
  7. fbuild/build/build_info_generator.py +624 -0
  8. fbuild/build/build_state.py +325 -0
  9. fbuild/build/build_utils.py +93 -0
  10. fbuild/build/compilation_executor.py +422 -0
  11. fbuild/build/compiler.py +165 -0
  12. fbuild/build/compiler_avr.py +574 -0
  13. fbuild/build/configurable_compiler.py +664 -0
  14. fbuild/build/configurable_linker.py +637 -0
  15. fbuild/build/flag_builder.py +214 -0
  16. fbuild/build/library_dependency_processor.py +185 -0
  17. fbuild/build/linker.py +708 -0
  18. fbuild/build/orchestrator.py +67 -0
  19. fbuild/build/orchestrator_avr.py +651 -0
  20. fbuild/build/orchestrator_esp32.py +878 -0
  21. fbuild/build/orchestrator_rp2040.py +719 -0
  22. fbuild/build/orchestrator_stm32.py +696 -0
  23. fbuild/build/orchestrator_teensy.py +580 -0
  24. fbuild/build/source_compilation_orchestrator.py +218 -0
  25. fbuild/build/source_scanner.py +516 -0
  26. fbuild/cli.py +717 -0
  27. fbuild/cli_utils.py +314 -0
  28. fbuild/config/__init__.py +16 -0
  29. fbuild/config/board_config.py +542 -0
  30. fbuild/config/board_loader.py +92 -0
  31. fbuild/config/ini_parser.py +369 -0
  32. fbuild/config/mcu_specs.py +88 -0
  33. fbuild/daemon/__init__.py +42 -0
  34. fbuild/daemon/async_client.py +531 -0
  35. fbuild/daemon/client.py +1505 -0
  36. fbuild/daemon/compilation_queue.py +293 -0
  37. fbuild/daemon/configuration_lock.py +865 -0
  38. fbuild/daemon/daemon.py +585 -0
  39. fbuild/daemon/daemon_context.py +293 -0
  40. fbuild/daemon/error_collector.py +263 -0
  41. fbuild/daemon/file_cache.py +332 -0
  42. fbuild/daemon/firmware_ledger.py +546 -0
  43. fbuild/daemon/lock_manager.py +508 -0
  44. fbuild/daemon/logging_utils.py +149 -0
  45. fbuild/daemon/messages.py +957 -0
  46. fbuild/daemon/operation_registry.py +288 -0
  47. fbuild/daemon/port_state_manager.py +249 -0
  48. fbuild/daemon/process_tracker.py +366 -0
  49. fbuild/daemon/processors/__init__.py +18 -0
  50. fbuild/daemon/processors/build_processor.py +248 -0
  51. fbuild/daemon/processors/deploy_processor.py +664 -0
  52. fbuild/daemon/processors/install_deps_processor.py +431 -0
  53. fbuild/daemon/processors/locking_processor.py +777 -0
  54. fbuild/daemon/processors/monitor_processor.py +285 -0
  55. fbuild/daemon/request_processor.py +457 -0
  56. fbuild/daemon/shared_serial.py +819 -0
  57. fbuild/daemon/status_manager.py +238 -0
  58. fbuild/daemon/subprocess_manager.py +316 -0
  59. fbuild/deploy/__init__.py +21 -0
  60. fbuild/deploy/deployer.py +67 -0
  61. fbuild/deploy/deployer_esp32.py +310 -0
  62. fbuild/deploy/docker_utils.py +315 -0
  63. fbuild/deploy/monitor.py +519 -0
  64. fbuild/deploy/qemu_runner.py +603 -0
  65. fbuild/interrupt_utils.py +34 -0
  66. fbuild/ledger/__init__.py +52 -0
  67. fbuild/ledger/board_ledger.py +560 -0
  68. fbuild/output.py +352 -0
  69. fbuild/packages/__init__.py +66 -0
  70. fbuild/packages/archive_utils.py +1098 -0
  71. fbuild/packages/arduino_core.py +412 -0
  72. fbuild/packages/cache.py +256 -0
  73. fbuild/packages/concurrent_manager.py +510 -0
  74. fbuild/packages/downloader.py +518 -0
  75. fbuild/packages/fingerprint.py +423 -0
  76. fbuild/packages/framework_esp32.py +538 -0
  77. fbuild/packages/framework_rp2040.py +349 -0
  78. fbuild/packages/framework_stm32.py +459 -0
  79. fbuild/packages/framework_teensy.py +346 -0
  80. fbuild/packages/github_utils.py +96 -0
  81. fbuild/packages/header_trampoline_cache.py +394 -0
  82. fbuild/packages/library_compiler.py +203 -0
  83. fbuild/packages/library_manager.py +549 -0
  84. fbuild/packages/library_manager_esp32.py +725 -0
  85. fbuild/packages/package.py +163 -0
  86. fbuild/packages/platform_esp32.py +383 -0
  87. fbuild/packages/platform_rp2040.py +400 -0
  88. fbuild/packages/platform_stm32.py +581 -0
  89. fbuild/packages/platform_teensy.py +312 -0
  90. fbuild/packages/platform_utils.py +131 -0
  91. fbuild/packages/platformio_registry.py +369 -0
  92. fbuild/packages/sdk_utils.py +231 -0
  93. fbuild/packages/toolchain.py +436 -0
  94. fbuild/packages/toolchain_binaries.py +196 -0
  95. fbuild/packages/toolchain_esp32.py +489 -0
  96. fbuild/packages/toolchain_metadata.py +185 -0
  97. fbuild/packages/toolchain_rp2040.py +436 -0
  98. fbuild/packages/toolchain_stm32.py +417 -0
  99. fbuild/packages/toolchain_teensy.py +404 -0
  100. fbuild/platform_configs/esp32.json +150 -0
  101. fbuild/platform_configs/esp32c2.json +144 -0
  102. fbuild/platform_configs/esp32c3.json +143 -0
  103. fbuild/platform_configs/esp32c5.json +151 -0
  104. fbuild/platform_configs/esp32c6.json +151 -0
  105. fbuild/platform_configs/esp32p4.json +149 -0
  106. fbuild/platform_configs/esp32s3.json +151 -0
  107. fbuild/platform_configs/imxrt1062.json +56 -0
  108. fbuild/platform_configs/rp2040.json +70 -0
  109. fbuild/platform_configs/rp2350.json +76 -0
  110. fbuild/platform_configs/stm32f1.json +59 -0
  111. fbuild/platform_configs/stm32f4.json +63 -0
  112. fbuild/py.typed +0 -0
  113. fbuild-1.2.8.dist-info/METADATA +468 -0
  114. fbuild-1.2.8.dist-info/RECORD +121 -0
  115. fbuild-1.2.8.dist-info/WHEEL +5 -0
  116. fbuild-1.2.8.dist-info/entry_points.txt +5 -0
  117. fbuild-1.2.8.dist-info/licenses/LICENSE +21 -0
  118. fbuild-1.2.8.dist-info/top_level.txt +2 -0
  119. fbuild_lint/__init__.py +0 -0
  120. fbuild_lint/ruff_plugins/__init__.py +0 -0
  121. fbuild_lint/ruff_plugins/keyboard_interrupt_checker.py +158 -0
@@ -0,0 +1,516 @@
1
+ """
2
+ Source file discovery and Arduino .ino preprocessing.
3
+
4
+ This module handles:
5
+ - Scanning project directories for source files (.ino, .cpp, .c, .h)
6
+ - Converting .ino files to .cpp with Arduino preprocessing
7
+ - Discovering Arduino core source files
8
+ - Discovering board variant source files
9
+ - Building source file collections for compilation
10
+ """
11
+
12
+ import re
13
+ from pathlib import Path
14
+ from typing import List, Optional
15
+ from dataclasses import dataclass
16
+
17
+
18
+ @dataclass
19
+ class SourceCollection:
20
+ """Collection of source files categorized by type."""
21
+
22
+ sketch_sources: List[Path] # User sketch .cpp/.c files
23
+ core_sources: List[Path] # Arduino core .cpp/.c files
24
+ variant_sources: List[Path] # Board variant .cpp/.c files
25
+ headers: List[Path] # All .h files for dependency tracking
26
+
27
+ def all_sources(self) -> List[Path]:
28
+ """Get all source files combined."""
29
+ return self.sketch_sources + self.core_sources + self.variant_sources
30
+
31
+
32
+ class SourceScanner:
33
+ """
34
+ Scans for source files and preprocesses Arduino .ino files.
35
+
36
+ The scanner:
37
+ 1. Finds all .ino/.cpp/.c files in the project
38
+ 2. Converts .ino files to .cpp with Arduino preprocessing
39
+ 3. Scans Arduino core directory for core library sources
40
+ 4. Scans variant directory for board-specific sources
41
+ 5. Returns organized SourceCollection for compilation
42
+ """
43
+
44
+ def __init__(self, project_dir: Path, build_dir: Path):
45
+ """
46
+ Initialize source scanner.
47
+
48
+ Args:
49
+ project_dir: Root project directory
50
+ build_dir: Build output directory (for generated .cpp files)
51
+ """
52
+ self.project_dir = Path(project_dir)
53
+ self.build_dir = Path(build_dir)
54
+
55
+ def scan(
56
+ self,
57
+ src_dir: Optional[Path] = None,
58
+ core_dir: Optional[Path] = None,
59
+ variant_dir: Optional[Path] = None
60
+ ) -> SourceCollection:
61
+ """
62
+ Scan for all source files.
63
+
64
+ Args:
65
+ src_dir: Project source directory (defaults to project_dir/src)
66
+ core_dir: Arduino core directory (e.g., cores/arduino)
67
+ variant_dir: Board variant directory (e.g., variants/standard)
68
+
69
+ Returns:
70
+ SourceCollection with all discovered sources
71
+ """
72
+ # Default source directory
73
+ if src_dir is None:
74
+ src_dir = self.project_dir / 'src'
75
+ else:
76
+ src_dir = Path(src_dir)
77
+
78
+ # Scan sketch sources
79
+ sketch_sources = self._scan_sketch_sources(src_dir)
80
+
81
+ # Scan core sources if provided
82
+ core_sources = []
83
+ if core_dir:
84
+ core_sources = self._scan_core_sources(Path(core_dir))
85
+
86
+ # Scan variant sources if provided
87
+ variant_sources = []
88
+ if variant_dir:
89
+ variant_sources = self._scan_variant_sources(Path(variant_dir))
90
+
91
+ # Find all headers
92
+ headers = self._find_headers(src_dir)
93
+
94
+ return SourceCollection(
95
+ sketch_sources=sketch_sources,
96
+ core_sources=core_sources,
97
+ variant_sources=variant_sources,
98
+ headers=headers
99
+ )
100
+
101
+ def _scan_sketch_sources(self, src_dir: Path) -> List[Path]:
102
+ """
103
+ Scan sketch directory for source files.
104
+
105
+ Finds .ino, .cpp, and .c files. Preprocesses .ino files to .cpp.
106
+
107
+ Args:
108
+ src_dir: Source directory to scan
109
+
110
+ Returns:
111
+ List of source file paths (with .ino converted to .cpp)
112
+ """
113
+ if not src_dir.exists():
114
+ return []
115
+
116
+ # Directories to exclude from scanning
117
+ excluded_dirs = {'.zap', '.pio', 'build', '.git', '__pycache__', 'node_modules', '.fbuild'}
118
+
119
+ sources = []
120
+
121
+ # Find all .ino files and preprocess them
122
+ ino_files = sorted(src_dir.glob('*.ino'))
123
+ if ino_files:
124
+ # Preprocess .ino files (may be multiple, concatenate them)
125
+ cpp_file = self._preprocess_ino_files(ino_files)
126
+ sources.append(cpp_file)
127
+
128
+ # Find existing .cpp and .c files in the root directory only
129
+ for pattern in ['*.cpp', '*.c']:
130
+ sources.extend(sorted(src_dir.glob(pattern)))
131
+
132
+ # Recursively find sources in subdirectories (excluding certain directories)
133
+ for subdir in src_dir.iterdir():
134
+ if subdir.is_dir() and subdir.name not in excluded_dirs:
135
+ for pattern in ['**/*.cpp', '**/*.c']:
136
+ sources.extend(sorted(subdir.glob(pattern)))
137
+
138
+ return sources
139
+
140
+ def _scan_core_sources(self, core_dir: Path) -> List[Path]:
141
+ """
142
+ Scan Arduino core directory for source files.
143
+
144
+ Args:
145
+ core_dir: Arduino core directory (e.g., cores/arduino)
146
+
147
+ Returns:
148
+ List of core source file paths
149
+ """
150
+ if not core_dir.exists():
151
+ return []
152
+
153
+ sources = []
154
+ for pattern in ['*.cpp', '*.c']:
155
+ sources.extend(sorted(core_dir.glob(pattern)))
156
+
157
+ return sources
158
+
159
+ def _scan_variant_sources(self, variant_dir: Path) -> List[Path]:
160
+ """
161
+ Scan board variant directory for source files.
162
+
163
+ Args:
164
+ variant_dir: Variant directory (e.g., variants/standard)
165
+
166
+ Returns:
167
+ List of variant source file paths
168
+ """
169
+ if not variant_dir.exists():
170
+ return []
171
+
172
+ sources = []
173
+ for pattern in ['*.cpp', '*.c']:
174
+ sources.extend(sorted(variant_dir.glob(pattern)))
175
+
176
+ return sources
177
+
178
+ def _find_headers(self, src_dir: Path) -> List[Path]:
179
+ """
180
+ Find all header files in source directory.
181
+
182
+ Args:
183
+ src_dir: Source directory to scan
184
+
185
+ Returns:
186
+ List of header file paths
187
+ """
188
+ if not src_dir.exists():
189
+ return []
190
+
191
+ # Directories to exclude from scanning
192
+ excluded_dirs = {'.zap', '.pio', 'build', '.git', '__pycache__', 'node_modules', '.fbuild'}
193
+
194
+ headers: set[Path] = set()
195
+
196
+ # Find headers in the root directory
197
+ for pattern in ['*.h', '*.hpp']:
198
+ headers.update(src_dir.glob(pattern))
199
+
200
+ # Recursively find headers in subdirectories (excluding certain directories)
201
+ for subdir in src_dir.iterdir():
202
+ if subdir.is_dir() and subdir.name not in excluded_dirs:
203
+ for pattern in ['**/*.h', '**/*.hpp']:
204
+ headers.update(subdir.glob(pattern))
205
+
206
+ return sorted(list(headers))
207
+
208
+ def _preprocess_ino_files(self, ino_files: List[Path]) -> Path:
209
+ """
210
+ Preprocess .ino files to .cpp with Arduino conventions.
211
+
212
+ Arduino preprocessing rules:
213
+ 1. Concatenate multiple .ino files (alphabetically)
214
+ 2. Add #include <Arduino.h> at the top
215
+ 3. Extract function prototypes from definitions
216
+ 4. Remove existing forward declarations from body
217
+ 5. Add all function prototypes before first function definition
218
+ 6. Add #line directives to preserve original line numbers
219
+
220
+ Args:
221
+ ino_files: List of .ino files to preprocess
222
+
223
+ Returns:
224
+ Path to generated .cpp file
225
+ """
226
+ # Create output .cpp file
227
+ output_file = self.build_dir / 'sketch.cpp'
228
+ output_file.parent.mkdir(parents=True, exist_ok=True)
229
+
230
+ # Concatenate all .ino files
231
+ combined_content = []
232
+ for ino_file in sorted(ino_files):
233
+ combined_content.append(ino_file.read_text(encoding='utf-8'))
234
+
235
+ content = '\n\n'.join(combined_content)
236
+
237
+ # Extract function prototypes and clean content
238
+ prototypes, cleaned_content = self._extract_and_clean_functions(content)
239
+
240
+ # Find where to insert prototypes (after last #include)
241
+ lines = cleaned_content.split('\n')
242
+ last_include_idx = -1
243
+ for i, line in enumerate(lines):
244
+ stripped = line.strip()
245
+ if stripped.startswith('#include'):
246
+ last_include_idx = i
247
+
248
+ # Build final .cpp file
249
+ cpp_lines = ['#include <Arduino.h>', '']
250
+
251
+ # If there were other includes, add them
252
+ if last_include_idx >= 0:
253
+ for i in range(last_include_idx + 1):
254
+ if i == 0 and lines[i].strip() == '#include <Arduino.h>':
255
+ continue # Skip duplicate Arduino.h
256
+ if lines[i].strip():
257
+ cpp_lines.append(lines[i])
258
+ cpp_lines.append('')
259
+
260
+ # Add function prototypes section
261
+ cpp_lines.append('// Function prototypes')
262
+ for prototype in prototypes:
263
+ cpp_lines.append(prototype)
264
+ cpp_lines.append('')
265
+
266
+ # Add #line directive to preserve line numbers
267
+ # Use the first .ino file name for reference
268
+ ino_filename = ino_files[0].name if ino_files else 'sketch.ino'
269
+ cpp_lines.append(f'#line 1 "{ino_filename}"')
270
+
271
+ # Add the rest of the cleaned content
272
+ start_idx = last_include_idx + 1 if last_include_idx >= 0 else 0
273
+ cpp_lines.extend(lines[start_idx:])
274
+
275
+ cpp_content = '\n'.join(cpp_lines)
276
+ output_file.write_text(cpp_content, encoding='utf-8')
277
+
278
+ return output_file
279
+
280
+ def _extract_and_clean_functions(self, content: str) -> tuple[List[str], str]:
281
+ """
282
+ Extract function prototypes and remove existing forward declarations.
283
+
284
+ Handles:
285
+ - Single-line function signatures
286
+ - Multi-line function signatures
287
+ - Existing forward declarations (removes them from body)
288
+
289
+ Args:
290
+ content: Sketch source code
291
+
292
+ Returns:
293
+ Tuple of (prototypes list, cleaned content)
294
+ """
295
+ prototypes = []
296
+ lines = content.split('\n')
297
+ cleaned_lines = []
298
+
299
+ in_multiline_comment = False
300
+ in_function_signature = False
301
+ current_signature = []
302
+
303
+ i = 0
304
+ while i < len(lines):
305
+ line = lines[i]
306
+ stripped = line.strip()
307
+
308
+ # Track multiline comments
309
+ if '/*' in stripped:
310
+ in_multiline_comment = True
311
+ if '*/' in stripped:
312
+ in_multiline_comment = False
313
+ cleaned_lines.append(line)
314
+ i += 1
315
+ continue
316
+
317
+ # Keep comments and preprocessor directives as-is
318
+ if in_multiline_comment:
319
+ cleaned_lines.append(line)
320
+ i += 1
321
+ continue
322
+ if stripped.startswith('//'):
323
+ cleaned_lines.append(line)
324
+ i += 1
325
+ continue
326
+ if stripped.startswith('#'):
327
+ cleaned_lines.append(line)
328
+ i += 1
329
+ continue
330
+
331
+ # Check for function signature start
332
+ if not in_function_signature and stripped:
333
+ # Pattern to detect start of function signature
334
+ # Matches return_type function_name(
335
+ sig_start_pattern = r'^([a-zA-Z_][\w\s\*&:<>,]*?)\s+([a-zA-Z_]\w*)\s*\('
336
+ match = re.match(sig_start_pattern, stripped)
337
+
338
+ if match:
339
+ in_function_signature = True
340
+ current_signature = [line]
341
+
342
+ # Check if signature completes on same line
343
+ if '{' in stripped:
344
+ # Complete signature on one line
345
+ in_function_signature = False
346
+ prototype = self._extract_prototype_from_signature(''.join(current_signature))
347
+ if prototype:
348
+ prototypes.append(prototype)
349
+ cleaned_lines.append(line)
350
+ current_signature = []
351
+ elif stripped.endswith(';'):
352
+ # This is a forward declaration - skip it
353
+ in_function_signature = False
354
+ prototype = self._extract_prototype_from_declaration(stripped)
355
+ if prototype:
356
+ prototypes.append(prototype)
357
+ current_signature = []
358
+ # Don't add to cleaned_lines (remove it)
359
+ i += 1
360
+ continue
361
+ else:
362
+ cleaned_lines.append(line)
363
+ i += 1
364
+ continue
365
+
366
+ # Continue collecting multi-line signature
367
+ if in_function_signature:
368
+ current_signature.append(line)
369
+
370
+ if '{' in stripped:
371
+ # Signature complete
372
+ in_function_signature = False
373
+ full_signature = '\n'.join(current_signature)
374
+ prototype = self._extract_prototype_from_signature(full_signature)
375
+ if prototype:
376
+ prototypes.append(prototype)
377
+ # Add all signature lines to cleaned content
378
+ cleaned_lines.extend(current_signature)
379
+ current_signature = []
380
+ elif stripped.endswith(';'):
381
+ # Forward declaration on multiple lines - skip it
382
+ in_function_signature = False
383
+ full_signature = '\n'.join(current_signature)
384
+ prototype = self._extract_prototype_from_declaration(full_signature.replace('\n', ' '))
385
+ if prototype:
386
+ prototypes.append(prototype)
387
+ current_signature = []
388
+ # Don't add to cleaned_lines
389
+
390
+ i += 1
391
+ continue
392
+
393
+ cleaned_lines.append(line)
394
+ i += 1
395
+
396
+ return prototypes, '\n'.join(cleaned_lines)
397
+
398
+ def _extract_prototype_from_signature(self, signature: str) -> str:
399
+ """
400
+ Extract prototype from function definition signature.
401
+
402
+ Args:
403
+ signature: Function signature (may be multi-line) ending with {
404
+
405
+ Returns:
406
+ Function prototype string or empty string
407
+ """
408
+ # Remove the opening brace and everything after
409
+ sig = signature.split('{')[0].strip()
410
+
411
+ # Normalize whitespace
412
+ sig = re.sub(r'\s+', ' ', sig)
413
+
414
+ # Pattern to match function signature
415
+ pattern = r'^([a-zA-Z_][\w\s\*&:<>,]*?)\s+([a-zA-Z_]\w*)\s*\((.*?)\)\s*$'
416
+ match = re.match(pattern, sig)
417
+
418
+ if match:
419
+ return_type = match.group(1).strip()
420
+ func_name = match.group(2).strip()
421
+ params = match.group(3).strip()
422
+
423
+ # Skip common false positives
424
+ if func_name in ['if', 'while', 'for', 'switch', 'catch']:
425
+ return ''
426
+
427
+ return f"{return_type} {func_name}({params});"
428
+
429
+ return ''
430
+
431
+ def _extract_prototype_from_declaration(self, declaration: str) -> str:
432
+ """
433
+ Extract prototype from existing forward declaration.
434
+
435
+ Args:
436
+ declaration: Forward declaration line ending with ;
437
+
438
+ Returns:
439
+ Function prototype string or empty string (already ends with ;)
440
+ """
441
+ # Clean up the declaration
442
+ decl = declaration.strip()
443
+ if not decl.endswith(';'):
444
+ decl += ';'
445
+
446
+ # Normalize whitespace
447
+ decl = re.sub(r'\s+', ' ', decl)
448
+
449
+ # Verify it looks like a function declaration
450
+ pattern = r'^([a-zA-Z_][\w\s\*&:<>,]*?)\s+([a-zA-Z_]\w*)\s*\((.*?)\)\s*;$'
451
+ match = re.match(pattern, decl)
452
+
453
+ if match:
454
+ return decl
455
+
456
+ return ''
457
+
458
+ def _extract_function_prototypes(self, content: str) -> List[str]:
459
+ """
460
+ Extract function prototypes from Arduino sketch.
461
+
462
+ Finds function definitions and generates forward declarations.
463
+
464
+ Args:
465
+ content: Sketch source code
466
+
467
+ Returns:
468
+ List of function prototype strings
469
+ """
470
+ prototypes = []
471
+
472
+ # Pattern to match function definitions
473
+ # Matches: return_type function_name(params) {
474
+ # Ignores: preprocessor directives, class methods, etc.
475
+ pattern = r'^([a-zA-Z_][\w\s\*&:<>,]*?)\s+([a-zA-Z_]\w*)\s*\((.*?)\)\s*\{'
476
+
477
+ lines = content.split('\n')
478
+ in_multiline_comment = False
479
+
480
+ for line in lines:
481
+ # Track multiline comments
482
+ if '/*' in line:
483
+ in_multiline_comment = True
484
+ if '*/' in line:
485
+ in_multiline_comment = False
486
+ continue
487
+
488
+ # Skip comments and preprocessor directives
489
+ if in_multiline_comment:
490
+ continue
491
+ if line.strip().startswith('//'):
492
+ continue
493
+ if line.strip().startswith('#'):
494
+ continue
495
+
496
+ # Try to match function definition
497
+ match = re.match(pattern, line.strip())
498
+ if match:
499
+ return_type = match.group(1).strip()
500
+ func_name = match.group(2).strip()
501
+ params = match.group(3).strip()
502
+
503
+ # Skip common false positives
504
+ if func_name in ['if', 'while', 'for', 'switch']:
505
+ continue
506
+
507
+ # Generate prototype
508
+ prototype = f"{return_type} {func_name}({params});"
509
+ prototypes.append(prototype)
510
+
511
+ return prototypes
512
+
513
+
514
+ class SourceScannerError(Exception):
515
+ """Raised when source scanning fails."""
516
+ pass