autosar-calltree 0.3.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.
@@ -0,0 +1,415 @@
1
+ """
2
+ Traditional C function parser.
3
+
4
+ This module handles parsing of traditional C function declarations and definitions,
5
+ extracting function information including parameters, return types, and function calls.
6
+ """
7
+
8
+ import re
9
+ from pathlib import Path
10
+ from typing import List, Optional
11
+
12
+ from ..database.models import FunctionInfo, FunctionType, Parameter
13
+
14
+
15
+ class CParser:
16
+ """Parser for traditional C function declarations and definitions."""
17
+
18
+ # C keywords to exclude from function call extraction
19
+ C_KEYWORDS = {
20
+ "if",
21
+ "else",
22
+ "while",
23
+ "for",
24
+ "do",
25
+ "switch",
26
+ "case",
27
+ "default",
28
+ "return",
29
+ "break",
30
+ "continue",
31
+ "goto",
32
+ "sizeof",
33
+ "typedef",
34
+ "struct",
35
+ "union",
36
+ "enum",
37
+ "const",
38
+ "volatile",
39
+ "static",
40
+ "extern",
41
+ "auto",
42
+ "register",
43
+ "inline",
44
+ "__inline",
45
+ "__inline__",
46
+ "restrict",
47
+ "__restrict",
48
+ "__restrict__",
49
+ "_Bool",
50
+ "_Complex",
51
+ "_Imaginary",
52
+ "_Alignas",
53
+ "_Alignof",
54
+ "_Atomic",
55
+ "_Static_assert",
56
+ "_Noreturn",
57
+ "_Thread_local",
58
+ "_Generic",
59
+ }
60
+
61
+ # Common AUTOSAR types
62
+ AUTOSAR_TYPES = {
63
+ "uint8",
64
+ "uint16",
65
+ "uint32",
66
+ "uint64",
67
+ "sint8",
68
+ "sint16",
69
+ "sint32",
70
+ "sint64",
71
+ "boolean",
72
+ "Boolean",
73
+ "float32",
74
+ "float64",
75
+ "Std_ReturnType",
76
+ "StatusType",
77
+ }
78
+
79
+ def __init__(self):
80
+ """Initialize the C parser."""
81
+ # Import AutosarParser to handle AUTOSAR macros
82
+ from .autosar_parser import AutosarParser
83
+
84
+ self.autosar_parser = AutosarParser()
85
+
86
+ # Pattern for traditional C function declarations/definitions
87
+ # Matches: [static] [inline] return_type function_name(params)
88
+ self.function_pattern = re.compile(
89
+ r"^\s*" # Start of line with optional whitespace
90
+ r"(?P<static>static\s+)?" # Optional static keyword
91
+ r"(?P<inline>inline|__inline__|__inline\s+)?" # Optional inline
92
+ r"(?P<return_type>[\w\s\*]+?)\s+" # Return type (can include spaces, pointers)
93
+ r"(?P<function_name>[a-zA-Z_][a-zA-Z0-9_]*)\s*" # Function name
94
+ r"\((?P<params>[^)]*)\)", # Parameters in parentheses
95
+ re.MULTILINE,
96
+ )
97
+
98
+ # Pattern to match function bodies { ... }
99
+ self.function_body_pattern = re.compile(
100
+ r"\{(?:[^{}]|(?:\{(?:[^{}]|(?:\{[^{}]*\}))*\}))*\}", re.DOTALL
101
+ )
102
+
103
+ # Pattern for function calls: identifier(
104
+ self.function_call_pattern = re.compile(r"\b([a-zA-Z_][a-zA-Z0-9_]*)\s*\(")
105
+
106
+ # Pattern for RTE calls
107
+ self.rte_call_pattern = re.compile(r"\bRte_[a-zA-Z_][a-zA-Z0-9_]*\s*\(")
108
+
109
+ def parse_file(self, file_path: Path) -> List[FunctionInfo]:
110
+ """
111
+ Parse a C source file and extract all function definitions.
112
+
113
+ Tries AUTOSAR macros first, then falls back to traditional C parsing.
114
+
115
+ Args:
116
+ file_path: Path to the C source file
117
+
118
+ Returns:
119
+ List of FunctionInfo objects for all functions found
120
+ """
121
+ try:
122
+ content = file_path.read_text(encoding="utf-8", errors="ignore")
123
+ except Exception:
124
+ return []
125
+
126
+ # Remove comments to avoid false positives
127
+ content = self._remove_comments(content)
128
+
129
+ functions = []
130
+
131
+ # Quick check: only process AUTOSAR if file contains FUNC macros
132
+ if "FUNC(" in content or "FUNC_P2" in content:
133
+ # Try to find AUTOSAR functions line by line
134
+ lines = content.split("\n")
135
+ for line_num, line in enumerate(lines, 1):
136
+ # Only check lines that look like AUTOSAR declarations
137
+ if "FUNC" in line and "(" in line:
138
+ autosar_func = self.autosar_parser.parse_function_declaration(
139
+ line, file_path, line_num
140
+ )
141
+ if autosar_func:
142
+ # Extract function body and calls for AUTOSAR functions too
143
+ # Find the position of this line in the content
144
+ line_start = content.find(line)
145
+ if line_start != -1:
146
+ # Position after the function declaration line
147
+ body_start = line_start + len(line)
148
+ function_body = self._extract_function_body(
149
+ content, body_start
150
+ )
151
+ if function_body:
152
+ called_functions = self._extract_function_calls(
153
+ function_body
154
+ )
155
+ autosar_func.calls = called_functions
156
+ functions.append(autosar_func)
157
+
158
+ # Then, parse traditional C functions
159
+ for match in self.function_pattern.finditer(content):
160
+ func_info = self._parse_function_match(match, content, file_path)
161
+ if func_info:
162
+ # Check if this function was already found as AUTOSAR
163
+ is_duplicate = any(
164
+ f.name == func_info.name and f.line_number == func_info.line_number
165
+ for f in functions
166
+ )
167
+ if not is_duplicate:
168
+ functions.append(func_info)
169
+
170
+ return functions
171
+
172
+ def _remove_comments(self, content: str) -> str:
173
+ """
174
+ Remove C-style comments from source code.
175
+
176
+ Args:
177
+ content: Source code content
178
+
179
+ Returns:
180
+ Content with comments removed
181
+ """
182
+ # Remove multi-line comments /* ... */
183
+ content = re.sub(r"/\*.*?\*/", "", content, flags=re.DOTALL)
184
+ # Remove single-line comments // ...
185
+ content = re.sub(r"//.*?$", "", content, flags=re.MULTILINE)
186
+ return content
187
+
188
+ def _parse_function_match(
189
+ self, match: re.Match, content: str, file_path: Path
190
+ ) -> Optional[FunctionInfo]:
191
+ """
192
+ Parse a regex match into a FunctionInfo object.
193
+
194
+ Args:
195
+ match: Regex match object for function declaration
196
+ content: Full file content
197
+ file_path: Path to source file
198
+
199
+ Returns:
200
+ FunctionInfo object or None if parsing fails
201
+ """
202
+ static_keyword = match.group("static")
203
+ match.group("inline")
204
+ return_type = match.group("return_type").strip()
205
+ function_name = match.group("function_name")
206
+ params_str = match.group("params")
207
+
208
+ # Skip if this looks like a macro or preprocessor directive
209
+ if return_type.startswith("#"):
210
+ return None
211
+
212
+ # Skip if return type or function name is a C keyword (control structures)
213
+ if return_type in self.C_KEYWORDS or function_name in self.C_KEYWORDS:
214
+ return None
215
+
216
+ # Skip common control structures
217
+ if function_name in ["if", "for", "while", "switch", "case", "else"]:
218
+ return None
219
+
220
+ # Determine function type - all traditional C functions use TRADITIONAL_C
221
+ # (static is tracked separately via is_static parameter)
222
+ func_type = FunctionType.TRADITIONAL_C
223
+
224
+ # Parse parameters
225
+ parameters = self._parse_parameters(params_str)
226
+
227
+ # Try to find function body
228
+ body_start = match.end()
229
+ function_body = self._extract_function_body(content, body_start)
230
+
231
+ # Extract function calls from body
232
+ called_functions = []
233
+ if function_body:
234
+ called_functions = self._extract_function_calls(function_body)
235
+
236
+ # Determine line number
237
+ line_number = content[: match.start()].count("\n") + 1
238
+
239
+ return FunctionInfo(
240
+ name=function_name,
241
+ return_type=return_type,
242
+ parameters=parameters,
243
+ function_type=func_type,
244
+ file_path=Path(file_path),
245
+ line_number=line_number,
246
+ calls=called_functions,
247
+ is_static=bool(static_keyword),
248
+ )
249
+
250
+ def _parse_parameters(self, params_str: str) -> List[Parameter]:
251
+ """
252
+ Parse function parameters from parameter string.
253
+
254
+ Args:
255
+ params_str: String containing function parameters
256
+
257
+ Returns:
258
+ List of Parameter objects
259
+ """
260
+ params_str = params_str.strip()
261
+
262
+ # Handle void or empty parameters
263
+ if not params_str or params_str == "void":
264
+ return []
265
+
266
+ parameters = []
267
+ # Split by comma, but respect nested parentheses and brackets
268
+ param_parts = self._smart_split(params_str, ",")
269
+
270
+ for param in param_parts:
271
+ param = param.strip()
272
+ if not param:
273
+ continue
274
+
275
+ # Parse parameter: type [*] name [array]
276
+ # Examples: "uint8 value", "uint16* ptr", "const ConfigType* config"
277
+ param_match = re.match(
278
+ r"^(?P<type>[\w\s\*]+?)\s*(?P<name>[a-zA-Z_][a-zA-Z0-9_]*)?"
279
+ r"(?P<array>\[[^\]]*\])?$",
280
+ param,
281
+ )
282
+
283
+ if param_match:
284
+ param_type = param_match.group("type").strip()
285
+ param_name = param_match.group("name") or ""
286
+ is_pointer = "*" in param_type
287
+ # Note: arrays detected but not separately tracked in current Parameter model
288
+
289
+ # Clean up type (remove extra spaces and trailing *)
290
+ param_type = re.sub(r"\s+", " ", param_type).strip()
291
+ param_type = param_type.rstrip("*").strip()
292
+
293
+ parameters.append(
294
+ Parameter(
295
+ name=param_name, param_type=param_type, is_pointer=is_pointer
296
+ )
297
+ )
298
+
299
+ return parameters
300
+
301
+ def _smart_split(self, text: str, delimiter: str) -> List[str]:
302
+ """
303
+ Split text by delimiter, respecting nested parentheses/brackets.
304
+
305
+ Args:
306
+ text: Text to split
307
+ delimiter: Delimiter character
308
+
309
+ Returns:
310
+ List of split parts
311
+ """
312
+ parts = []
313
+ current = []
314
+ depth = 0
315
+
316
+ for char in text:
317
+ if char in "([{":
318
+ depth += 1
319
+ current.append(char)
320
+ elif char in ")]}":
321
+ depth -= 1
322
+ current.append(char)
323
+ elif char == delimiter and depth == 0:
324
+ parts.append("".join(current))
325
+ current = []
326
+ else:
327
+ current.append(char)
328
+
329
+ if current:
330
+ parts.append("".join(current))
331
+
332
+ return parts
333
+
334
+ def _extract_function_body(self, content: str, start_pos: int) -> Optional[str]:
335
+ """
336
+ Extract function body starting from a position.
337
+
338
+ Args:
339
+ content: Full file content
340
+ start_pos: Position to start searching for body
341
+
342
+ Returns:
343
+ Function body string or None if not found
344
+ """
345
+ # Skip whitespace and look for opening brace
346
+ remaining = content[start_pos:].lstrip()
347
+ if not remaining.startswith("{"):
348
+ return None
349
+
350
+ # Match balanced braces
351
+ brace_count = 0
352
+ body_chars = []
353
+
354
+ for char in remaining:
355
+ body_chars.append(char)
356
+ if char == "{":
357
+ brace_count += 1
358
+ elif char == "}":
359
+ brace_count -= 1
360
+ if brace_count == 0:
361
+ break
362
+
363
+ if brace_count == 0:
364
+ return "".join(body_chars)
365
+
366
+ return None
367
+
368
+ def _extract_function_calls(self, function_body: str) -> List[str]:
369
+ """
370
+ Extract function calls from a function body.
371
+
372
+ Args:
373
+ function_body: Function body text
374
+
375
+ Returns:
376
+ List of called function names
377
+ """
378
+ called_functions = set()
379
+
380
+ # Find all potential function calls
381
+ for match in self.function_call_pattern.finditer(function_body):
382
+ function_name = match.group(1)
383
+
384
+ # Skip C keywords
385
+ if function_name in self.C_KEYWORDS:
386
+ continue
387
+
388
+ # Skip AUTOSAR types (might be casts)
389
+ if function_name in self.AUTOSAR_TYPES:
390
+ continue
391
+
392
+ called_functions.add(function_name)
393
+
394
+ # Also extract RTE calls explicitly
395
+ for match in self.rte_call_pattern.finditer(function_body):
396
+ rte_function = match.group(0).rstrip("(").strip()
397
+ called_functions.add(rte_function)
398
+
399
+ return sorted(list(called_functions))
400
+
401
+ def parse_function_declaration(self, declaration: str) -> Optional[FunctionInfo]:
402
+ """
403
+ Parse a single function declaration string.
404
+
405
+ Args:
406
+ declaration: Function declaration as a string
407
+
408
+ Returns:
409
+ FunctionInfo object or None if parsing fails
410
+ """
411
+ match = self.function_pattern.search(declaration)
412
+ if not match:
413
+ return None
414
+
415
+ return self._parse_function_match(match, declaration, Path("unknown"))
@@ -0,0 +1,5 @@
1
+ """Version information for autosar-calltree package."""
2
+
3
+ __version__ = "0.3.0"
4
+ __author__ = "melodypapa"
5
+ __email__ = "melodypapa@outlook.com"