fbuild 1.1.0__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.
Potentially problematic release.
This version of fbuild might be problematic. Click here for more details.
- fbuild/__init__.py +0 -0
- fbuild/assets/example.txt +1 -0
- fbuild/build/__init__.py +117 -0
- fbuild/build/archive_creator.py +186 -0
- fbuild/build/binary_generator.py +444 -0
- fbuild/build/build_component_factory.py +131 -0
- fbuild/build/build_state.py +325 -0
- fbuild/build/build_utils.py +98 -0
- fbuild/build/compilation_executor.py +422 -0
- fbuild/build/compiler.py +165 -0
- fbuild/build/compiler_avr.py +574 -0
- fbuild/build/configurable_compiler.py +612 -0
- fbuild/build/configurable_linker.py +637 -0
- fbuild/build/flag_builder.py +186 -0
- fbuild/build/library_dependency_processor.py +185 -0
- fbuild/build/linker.py +708 -0
- fbuild/build/orchestrator.py +67 -0
- fbuild/build/orchestrator_avr.py +656 -0
- fbuild/build/orchestrator_esp32.py +797 -0
- fbuild/build/orchestrator_teensy.py +543 -0
- fbuild/build/source_compilation_orchestrator.py +220 -0
- fbuild/build/source_scanner.py +516 -0
- fbuild/cli.py +566 -0
- fbuild/cli_utils.py +312 -0
- fbuild/config/__init__.py +16 -0
- fbuild/config/board_config.py +457 -0
- fbuild/config/board_loader.py +92 -0
- fbuild/config/ini_parser.py +209 -0
- fbuild/config/mcu_specs.py +88 -0
- fbuild/daemon/__init__.py +34 -0
- fbuild/daemon/client.py +929 -0
- fbuild/daemon/compilation_queue.py +293 -0
- fbuild/daemon/daemon.py +474 -0
- fbuild/daemon/daemon_context.py +196 -0
- fbuild/daemon/error_collector.py +263 -0
- fbuild/daemon/file_cache.py +332 -0
- fbuild/daemon/lock_manager.py +270 -0
- fbuild/daemon/logging_utils.py +149 -0
- fbuild/daemon/messages.py +301 -0
- fbuild/daemon/operation_registry.py +288 -0
- fbuild/daemon/process_tracker.py +366 -0
- fbuild/daemon/processors/__init__.py +12 -0
- fbuild/daemon/processors/build_processor.py +157 -0
- fbuild/daemon/processors/deploy_processor.py +327 -0
- fbuild/daemon/processors/monitor_processor.py +146 -0
- fbuild/daemon/request_processor.py +401 -0
- fbuild/daemon/status_manager.py +216 -0
- fbuild/daemon/subprocess_manager.py +316 -0
- fbuild/deploy/__init__.py +17 -0
- fbuild/deploy/deployer.py +67 -0
- fbuild/deploy/deployer_esp32.py +314 -0
- fbuild/deploy/monitor.py +495 -0
- fbuild/interrupt_utils.py +34 -0
- fbuild/packages/__init__.py +53 -0
- fbuild/packages/archive_utils.py +1098 -0
- fbuild/packages/arduino_core.py +412 -0
- fbuild/packages/cache.py +249 -0
- fbuild/packages/downloader.py +366 -0
- fbuild/packages/framework_esp32.py +538 -0
- fbuild/packages/framework_teensy.py +346 -0
- fbuild/packages/github_utils.py +96 -0
- fbuild/packages/header_trampoline_cache.py +394 -0
- fbuild/packages/library_compiler.py +203 -0
- fbuild/packages/library_manager.py +549 -0
- fbuild/packages/library_manager_esp32.py +413 -0
- fbuild/packages/package.py +163 -0
- fbuild/packages/platform_esp32.py +383 -0
- fbuild/packages/platform_teensy.py +312 -0
- fbuild/packages/platform_utils.py +131 -0
- fbuild/packages/platformio_registry.py +325 -0
- fbuild/packages/sdk_utils.py +231 -0
- fbuild/packages/toolchain.py +436 -0
- fbuild/packages/toolchain_binaries.py +196 -0
- fbuild/packages/toolchain_esp32.py +484 -0
- fbuild/packages/toolchain_metadata.py +185 -0
- fbuild/packages/toolchain_teensy.py +404 -0
- fbuild/platform_configs/esp32.json +150 -0
- fbuild/platform_configs/esp32c2.json +144 -0
- fbuild/platform_configs/esp32c3.json +143 -0
- fbuild/platform_configs/esp32c5.json +151 -0
- fbuild/platform_configs/esp32c6.json +151 -0
- fbuild/platform_configs/esp32p4.json +149 -0
- fbuild/platform_configs/esp32s3.json +151 -0
- fbuild/platform_configs/imxrt1062.json +56 -0
- fbuild-1.1.0.dist-info/METADATA +447 -0
- fbuild-1.1.0.dist-info/RECORD +93 -0
- fbuild-1.1.0.dist-info/WHEEL +5 -0
- fbuild-1.1.0.dist-info/entry_points.txt +5 -0
- fbuild-1.1.0.dist-info/licenses/LICENSE +21 -0
- fbuild-1.1.0.dist-info/top_level.txt +2 -0
- fbuild_lint/__init__.py +0 -0
- fbuild_lint/ruff_plugins/__init__.py +0 -0
- 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
|