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.
- autosar_calltree/__init__.py +24 -0
- autosar_calltree/analyzers/__init__.py +5 -0
- autosar_calltree/analyzers/call_tree_builder.py +369 -0
- autosar_calltree/cli/__init__.py +5 -0
- autosar_calltree/cli/main.py +330 -0
- autosar_calltree/config/__init__.py +10 -0
- autosar_calltree/config/module_config.py +179 -0
- autosar_calltree/database/__init__.py +23 -0
- autosar_calltree/database/function_database.py +505 -0
- autosar_calltree/database/models.py +189 -0
- autosar_calltree/generators/__init__.py +5 -0
- autosar_calltree/generators/mermaid_generator.py +488 -0
- autosar_calltree/parsers/__init__.py +6 -0
- autosar_calltree/parsers/autosar_parser.py +314 -0
- autosar_calltree/parsers/c_parser.py +415 -0
- autosar_calltree/version.py +5 -0
- autosar_calltree-0.3.0.dist-info/METADATA +482 -0
- autosar_calltree-0.3.0.dist-info/RECORD +22 -0
- autosar_calltree-0.3.0.dist-info/WHEEL +5 -0
- autosar_calltree-0.3.0.dist-info/entry_points.txt +2 -0
- autosar_calltree-0.3.0.dist-info/licenses/LICENSE +21 -0
- autosar_calltree-0.3.0.dist-info/top_level.txt +1 -0
|
@@ -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"))
|