parser-ci-AP 0.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.
- parser_ci_AP/__init__.py +55 -0
- parser_ci_AP/define_info.py +116 -0
- parser_ci_AP/errors_info.py +32 -0
- parser_ci_AP/function_info.py +278 -0
- parser_ci_AP/logger.py +3 -0
- parser_ci_AP/project.py +272 -0
- parser_ci_AP/project_file.py +277 -0
- parser_ci_AP/type_info.py +269 -0
- parser_ci_AP/variable_info.py +189 -0
- parser_ci_ap-0.1.0.dist-info/METADATA +88 -0
- parser_ci_ap-0.1.0.dist-info/RECORD +14 -0
- parser_ci_ap-0.1.0.dist-info/WHEEL +5 -0
- parser_ci_ap-0.1.0.dist-info/licenses/LICENSE +0 -0
- parser_ci_ap-0.1.0.dist-info/top_level.txt +1 -0
parser_ci_AP/__init__.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from .project import Project
|
|
2
|
+
from .project_file import AbstractProjectFile
|
|
3
|
+
from .type_info import BaseTypeInfo
|
|
4
|
+
from .function_info import FunctionInfo
|
|
5
|
+
from .variable_info import VariableInfo
|
|
6
|
+
from .define_info import DefineInfo
|
|
7
|
+
from .errors_info import DiagnosticInfo
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"Project",
|
|
11
|
+
"AbstractProjectFile",
|
|
12
|
+
"BaseTypeInfo",
|
|
13
|
+
"FunctionInfo",
|
|
14
|
+
"VariableInfo",
|
|
15
|
+
"DefineInfo",
|
|
16
|
+
"DiagnosticInfo"
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Что может парсер
|
|
22
|
+
|
|
23
|
+
# На уровне проекта:
|
|
24
|
+
# 1) Перечислить все пользовательские типы данных (структуры, перечисления, typedef и т.д.);
|
|
25
|
+
# 2) Перечислить все файлы проекта;
|
|
26
|
+
# 3) Подсчитать количество строк (всего / без комментариев / только функциональных);
|
|
27
|
+
# 4) Перечислить все define-ы (макросы) проекта;
|
|
28
|
+
# 5) Перечислить все глобальные переменные проекта;
|
|
29
|
+
# 6) Перечислить все статические переменные проекта;
|
|
30
|
+
# 7) Перечислить все extern-переменные проекта;
|
|
31
|
+
# 8) Перечислить все функции проекта (глобальные и static отдельно);
|
|
32
|
+
# 9) Разделить все define-ы на системные и пользовательские;
|
|
33
|
+
|
|
34
|
+
# На уровне файла:
|
|
35
|
+
# 1) Перечислить все функции в файле (включая static и обычные);
|
|
36
|
+
# 2) Перечислить все зависимости (файлы, подключённые через #include);
|
|
37
|
+
# 3) Перечислить все переменные в файле (глобальные, static, extern);
|
|
38
|
+
# 4) Перечислить все define-ы в файле;
|
|
39
|
+
# 5) Перечислить все пользовательские типы данных в файле;
|
|
40
|
+
# 6) Подсчитать количество строк (всего / только функциональных);
|
|
41
|
+
# 7) Диагностика кода клангом
|
|
42
|
+
|
|
43
|
+
# На уровне функции:
|
|
44
|
+
# 1) Перечислить аргументы функции;
|
|
45
|
+
# 2) Перечислить локальные переменные;
|
|
46
|
+
# 3) Имя функции;
|
|
47
|
+
# 4) Получить шапку функции (прототип или полное объявление);
|
|
48
|
+
# 5) Извлечь из проекта весь контекст функции, чтобы можно было запустить её изолированно;
|
|
49
|
+
# 6) Перечислить глобальные переменные, используемые внутри функции;
|
|
50
|
+
# 7) Найти вызовы других функций внутри тела;
|
|
51
|
+
# 8) Определить тип возвращаемого значения;
|
|
52
|
+
# 9) Получить выражение, возвращаемое из функции (например: return x + 1 → "x + 1");
|
|
53
|
+
# 10) Число строк кода
|
|
54
|
+
|
|
55
|
+
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from typing import Optional, List
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from clang.cindex import Cursor
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DefineInfo:
|
|
7
|
+
def __init__(self, cursor: Cursor, encoding: str):
|
|
8
|
+
self._cursor = cursor
|
|
9
|
+
self.name: str = cursor.spelling
|
|
10
|
+
self.location: Path = (
|
|
11
|
+
Path(cursor.location.file.name).resolve()
|
|
12
|
+
if cursor.location.file
|
|
13
|
+
else Path()
|
|
14
|
+
)
|
|
15
|
+
try:
|
|
16
|
+
self._usr: str = cursor.get_usr()
|
|
17
|
+
except (AttributeError, TypeError, UnicodeDecodeError):
|
|
18
|
+
self._usr = f"{self.name}@{self.location}"
|
|
19
|
+
self.encoding = encoding
|
|
20
|
+
|
|
21
|
+
self._raw_line: str
|
|
22
|
+
self.params: Optional[List[str]]
|
|
23
|
+
self.value: str
|
|
24
|
+
self.comment: str
|
|
25
|
+
self.is_system: bool
|
|
26
|
+
|
|
27
|
+
self._raw_line = self._read_raw_line()
|
|
28
|
+
self.params = self._extract_params()
|
|
29
|
+
self.value = self._extract_value()
|
|
30
|
+
self.comment = self._extract_comment_after()
|
|
31
|
+
self.is_system = self._detect_system_define() # Дефайны которые не используются кодом напрямую
|
|
32
|
+
|
|
33
|
+
def _detect_system_define(self) -> bool:
|
|
34
|
+
"""
|
|
35
|
+
Определяет, является ли define системным:
|
|
36
|
+
- начинается с '_'
|
|
37
|
+
- или у него отсутствует значение (value)
|
|
38
|
+
"""
|
|
39
|
+
return self.name.startswith("_") or not self.value
|
|
40
|
+
|
|
41
|
+
def _read_raw_line(self) -> str:
|
|
42
|
+
"""
|
|
43
|
+
Считывает строку исходника, содержащую #define.
|
|
44
|
+
"""
|
|
45
|
+
try:
|
|
46
|
+
line_num = self._cursor.location.line
|
|
47
|
+
with open(self.location, "r", encoding=self.encoding, errors="replace") as f:
|
|
48
|
+
lines = f.readlines()
|
|
49
|
+
return lines[line_num - 1].strip()
|
|
50
|
+
except Exception as e:
|
|
51
|
+
return f"[!] Ошибка чтения строки define {self.name}: {e}"
|
|
52
|
+
|
|
53
|
+
def _extract_params(self) -> Optional[List[str]]:
|
|
54
|
+
"""
|
|
55
|
+
Пытается извлечь параметры, если define является макросом с параметрами.
|
|
56
|
+
Пример: #define MAX(a, b) ((a) > (b) ? (a) : (b))
|
|
57
|
+
"""
|
|
58
|
+
try:
|
|
59
|
+
after_name = self._raw_line.split(self.name, 1)[1].lstrip()
|
|
60
|
+
if after_name.startswith("("):
|
|
61
|
+
end_idx = after_name.find(")")
|
|
62
|
+
if end_idx != -1:
|
|
63
|
+
param_str = after_name[1:end_idx]
|
|
64
|
+
params = [p.strip() for p in param_str.split(",") if p.strip()]
|
|
65
|
+
return params
|
|
66
|
+
except Exception:
|
|
67
|
+
pass
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
def _extract_value(self) -> str:
|
|
71
|
+
"""
|
|
72
|
+
Извлекает значение define (до начала комментария, если он есть).
|
|
73
|
+
"""
|
|
74
|
+
try:
|
|
75
|
+
parts = self._raw_line.split(None, 2)
|
|
76
|
+
if len(parts) < 3:
|
|
77
|
+
return ""
|
|
78
|
+
|
|
79
|
+
value_part = parts[2]
|
|
80
|
+
# обрезаем комментарий (если есть)
|
|
81
|
+
for comment_start in ["//", "/*"]:
|
|
82
|
+
if comment_start in value_part:
|
|
83
|
+
value_part = value_part.split(comment_start, 1)[0].strip()
|
|
84
|
+
return value_part.strip()
|
|
85
|
+
except Exception:
|
|
86
|
+
return ""
|
|
87
|
+
|
|
88
|
+
def _extract_comment_after(self) -> str:
|
|
89
|
+
"""
|
|
90
|
+
Извлекает комментарий после значения define (если он есть).
|
|
91
|
+
"""
|
|
92
|
+
try:
|
|
93
|
+
for marker in ["//", "/*"]:
|
|
94
|
+
if marker in self._raw_line:
|
|
95
|
+
return self._raw_line.split(marker, 1)[1].strip()
|
|
96
|
+
return ""
|
|
97
|
+
except Exception:
|
|
98
|
+
return ""
|
|
99
|
+
|
|
100
|
+
def describe(self) -> str:
|
|
101
|
+
loc_str = f"{self.location}:{self._cursor.location.line}"
|
|
102
|
+
system_flag = "[system] " if self.is_system else ""
|
|
103
|
+
if self.params:
|
|
104
|
+
return f"{system_flag}#define {self.name}({', '.join(self.params)}) {self.value} // from {loc_str}"
|
|
105
|
+
return f"{system_flag}#define {self.name} {self.value} // from {loc_str}"
|
|
106
|
+
|
|
107
|
+
def __str__(self) -> str:
|
|
108
|
+
return self.describe()
|
|
109
|
+
|
|
110
|
+
def __eq__(self, other):
|
|
111
|
+
if not isinstance(other, DefineInfo):
|
|
112
|
+
return False
|
|
113
|
+
return self._usr == other._usr
|
|
114
|
+
|
|
115
|
+
def __hash__(self):
|
|
116
|
+
return hash(self._usr)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from clang.cindex import Diagnostic
|
|
2
|
+
|
|
3
|
+
class DiagnosticInfo:
|
|
4
|
+
def __init__(self, diag: Diagnostic):
|
|
5
|
+
self.severity = diag.severity # 1=warning, 2=error, 3=fatal, ...
|
|
6
|
+
self.spelling = diag.spelling
|
|
7
|
+
self.location = f"{diag.location.file}:{diag.location.line}"
|
|
8
|
+
self.category = diag.category_name
|
|
9
|
+
self.option = diag.option
|
|
10
|
+
|
|
11
|
+
def __eq__(self, other):
|
|
12
|
+
if not isinstance(other, DiagnosticInfo):
|
|
13
|
+
return False
|
|
14
|
+
return (
|
|
15
|
+
self.severity == other.severity and
|
|
16
|
+
self.spelling == other.spelling and
|
|
17
|
+
self.location == other.location and
|
|
18
|
+
self.category == other.category and
|
|
19
|
+
self.option == other.option
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
def __hash__(self):
|
|
23
|
+
return hash((
|
|
24
|
+
self.severity,
|
|
25
|
+
self.spelling,
|
|
26
|
+
self.location,
|
|
27
|
+
self.category,
|
|
28
|
+
self.option
|
|
29
|
+
))
|
|
30
|
+
|
|
31
|
+
def __str__(self):
|
|
32
|
+
return f"[{self.severity}] {self.location}: {self.spelling}"
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
from clang.cindex import Cursor, CursorKind, StorageClass
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import re
|
|
4
|
+
from typing import Set, Optional
|
|
5
|
+
|
|
6
|
+
from .variable_info import VariableInfo
|
|
7
|
+
|
|
8
|
+
class FunctionInfo:
|
|
9
|
+
def __init__(self, cursor: Cursor, encoding: str):
|
|
10
|
+
self._cursor = cursor
|
|
11
|
+
self.name = cursor.spelling
|
|
12
|
+
self.location = Path(cursor.location.file.name).resolve() if cursor.location.file else Path()
|
|
13
|
+
try:
|
|
14
|
+
self._usr = cursor.get_usr() # Уникальный идентификатор функции
|
|
15
|
+
except Exception:
|
|
16
|
+
self._usr = f"{self.name}@{self.location}"
|
|
17
|
+
self.encoding = encoding
|
|
18
|
+
|
|
19
|
+
self.return_var: Optional[ReturnInfo] = None
|
|
20
|
+
self.args: list[VariableInfo] = []
|
|
21
|
+
self.source_text: str
|
|
22
|
+
self.is_static: bool = False
|
|
23
|
+
self.comment: str
|
|
24
|
+
self.lines_functional: int
|
|
25
|
+
self.local_variables: list[VariableInfo] = []
|
|
26
|
+
self.called_funcs: Set[str] = set()
|
|
27
|
+
self.lines_pure_code: int
|
|
28
|
+
|
|
29
|
+
self.source_text = self._extract_source_text()
|
|
30
|
+
self.comment = self._extract_comments()
|
|
31
|
+
self._count_functional_lines()
|
|
32
|
+
self._extract_args()
|
|
33
|
+
self.external_variables = set()
|
|
34
|
+
self._extract_external_variables()
|
|
35
|
+
self.return_var = self._extract_return_variable()
|
|
36
|
+
self._detect_static()
|
|
37
|
+
self._count_pure_code_lines()
|
|
38
|
+
|
|
39
|
+
self._extract_from_cursor()
|
|
40
|
+
|
|
41
|
+
def _count_pure_code_lines(self):
|
|
42
|
+
"""
|
|
43
|
+
Подсчитывает количество строк, содержащих только чистый код:
|
|
44
|
+
- без комментариев (// и /* */)
|
|
45
|
+
- без пустых строк
|
|
46
|
+
"""
|
|
47
|
+
code = self.source_text
|
|
48
|
+
|
|
49
|
+
# Удаляем многострочные комментарии
|
|
50
|
+
code = re.sub(r"/\*.*?\*/", "", code, flags=re.DOTALL)
|
|
51
|
+
|
|
52
|
+
# Удаляем однострочные комментарии
|
|
53
|
+
code = re.sub(r"//.*", "", code)
|
|
54
|
+
|
|
55
|
+
# Оставляем только строки с кодом
|
|
56
|
+
self.lines_pure_code = sum(1 for line in code.splitlines() if line.strip())
|
|
57
|
+
|
|
58
|
+
def _extract_return_variable(self) -> Optional["ReturnInfo"]:
|
|
59
|
+
"""
|
|
60
|
+
Извлекает информацию о возвращаемом значении функции:
|
|
61
|
+
- тип возврата
|
|
62
|
+
- выражение после return (если есть)
|
|
63
|
+
"""
|
|
64
|
+
return_type = self._cursor.result_type.spelling
|
|
65
|
+
return_expr = None
|
|
66
|
+
|
|
67
|
+
def visit(node: Cursor):
|
|
68
|
+
nonlocal return_expr
|
|
69
|
+
if node.kind.name == "RETURN_STMT":
|
|
70
|
+
tokens = [t.spelling for t in node.get_tokens()]
|
|
71
|
+
if len(tokens) > 1:
|
|
72
|
+
return_expr = " ".join(tokens[1:]) # Пропускаем 'return'
|
|
73
|
+
for child in node.get_children():
|
|
74
|
+
visit(child)
|
|
75
|
+
|
|
76
|
+
visit(self._cursor)
|
|
77
|
+
|
|
78
|
+
if return_expr is None:
|
|
79
|
+
return None
|
|
80
|
+
return ReturnInfo(return_type, return_expr)
|
|
81
|
+
|
|
82
|
+
def _detect_static(self):
|
|
83
|
+
"""
|
|
84
|
+
Определяет, является ли функция статической, используя только canonical-курсоры.
|
|
85
|
+
"""
|
|
86
|
+
try:
|
|
87
|
+
canonical = self._cursor.canonical
|
|
88
|
+
if canonical and canonical.storage_class == StorageClass.STATIC:
|
|
89
|
+
self.is_static = True
|
|
90
|
+
except Exception:
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
return False
|
|
94
|
+
|
|
95
|
+
def _extract_args(self):
|
|
96
|
+
"""
|
|
97
|
+
Извлекает аргументы функции как объекты VariableInfo.
|
|
98
|
+
"""
|
|
99
|
+
self.args = []
|
|
100
|
+
for arg in self._cursor.get_arguments():
|
|
101
|
+
if arg.kind == CursorKind.PARM_DECL and arg.spelling:
|
|
102
|
+
self.args.append(VariableInfo(arg, self.encoding))
|
|
103
|
+
|
|
104
|
+
def _extract_source_text(self):
|
|
105
|
+
extent = self._cursor.extent
|
|
106
|
+
start = extent.start
|
|
107
|
+
end = extent.end
|
|
108
|
+
|
|
109
|
+
def extract_lines(lines):
|
|
110
|
+
lines = lines[start.line - 1:end.line]
|
|
111
|
+
lines[0] = lines[0][start.column - 1:]
|
|
112
|
+
lines[-1] = lines[-1][:end.column - 1]
|
|
113
|
+
return "".join(lines).strip()
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
with open(self.location, "r", encoding=self.encoding, errors="replace") as f:
|
|
117
|
+
return extract_lines(f.readlines())
|
|
118
|
+
except Exception as e:
|
|
119
|
+
return f"[!] Ошибка извлечения текста функции {self.name}: {e}"
|
|
120
|
+
|
|
121
|
+
def _extract_comments(self) -> str:
|
|
122
|
+
"""
|
|
123
|
+
Пытается извлечь комментарии, расположенные перед началом функции.
|
|
124
|
+
Поддерживает:
|
|
125
|
+
- однострочные // и ///
|
|
126
|
+
- многострочные /* ... */
|
|
127
|
+
Останавливается при встрече пустой строки.
|
|
128
|
+
"""
|
|
129
|
+
try:
|
|
130
|
+
with open(self.location, "r", encoding=self.encoding, errors="replace") as f:
|
|
131
|
+
lines = f.readlines()
|
|
132
|
+
|
|
133
|
+
start_line = self._cursor.extent.start.line
|
|
134
|
+
comments = []
|
|
135
|
+
inside_block = False
|
|
136
|
+
|
|
137
|
+
for i in range(start_line - 2, -1, -1):
|
|
138
|
+
line = lines[i].rstrip()
|
|
139
|
+
|
|
140
|
+
if not line.strip() and not inside_block:
|
|
141
|
+
break # пустая строка прерывает комментарий
|
|
142
|
+
|
|
143
|
+
# Обрабатываем многострочные комментарии (/* ... */)
|
|
144
|
+
if "*/" in line:
|
|
145
|
+
inside_block = True
|
|
146
|
+
comments.insert(0, line)
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
if inside_block:
|
|
150
|
+
comments.insert(0, line)
|
|
151
|
+
if "/*" in line:
|
|
152
|
+
inside_block = False
|
|
153
|
+
continue
|
|
154
|
+
|
|
155
|
+
# Однострочные комментарии
|
|
156
|
+
stripped = line.strip()
|
|
157
|
+
if stripped.startswith("//") or stripped.startswith("///"):
|
|
158
|
+
comments.insert(0, line)
|
|
159
|
+
continue
|
|
160
|
+
|
|
161
|
+
break # не комментарий и не внутри блока — прерываем
|
|
162
|
+
|
|
163
|
+
return "\n".join(comments).strip()
|
|
164
|
+
|
|
165
|
+
except Exception as e:
|
|
166
|
+
return f"[!] Ошибка при извлечении комментариев функции {self.name}: {e}"
|
|
167
|
+
|
|
168
|
+
def _count_functional_lines(self):
|
|
169
|
+
"""
|
|
170
|
+
Подсчитывает количество строк с функциональным кодом.
|
|
171
|
+
Убирает комментарии `//`, `/* */` и пустые строки.
|
|
172
|
+
"""
|
|
173
|
+
code = self.source_text
|
|
174
|
+
|
|
175
|
+
# Удаляем многострочные комментарии (включая переносы строк)
|
|
176
|
+
code = re.sub(r'/\*.*?\*/', '', code, flags=re.DOTALL)
|
|
177
|
+
|
|
178
|
+
# Удаляем однострочные комментарии
|
|
179
|
+
code = re.sub(r'//.*', '', code)
|
|
180
|
+
|
|
181
|
+
# Разбиваем на строки и фильтруем только те, что содержат код
|
|
182
|
+
lines = code.splitlines()
|
|
183
|
+
functional_lines = [line for line in lines if line.strip()] # только непустые
|
|
184
|
+
|
|
185
|
+
self.lines_functional = len(functional_lines)
|
|
186
|
+
|
|
187
|
+
def _extract_from_cursor(self):
|
|
188
|
+
|
|
189
|
+
def visit(node: Cursor):
|
|
190
|
+
if node.kind == CursorKind.VAR_DECL:
|
|
191
|
+
self.local_variables.append(VariableInfo(node, self.encoding))
|
|
192
|
+
elif node.kind == CursorKind.CALL_EXPR:
|
|
193
|
+
called_name = node.spelling or node.displayname or "<unnamed_call>"
|
|
194
|
+
self.called_funcs.add(called_name)
|
|
195
|
+
|
|
196
|
+
for child in node.get_children():
|
|
197
|
+
visit(child)
|
|
198
|
+
|
|
199
|
+
visit(self._cursor)
|
|
200
|
+
|
|
201
|
+
def _extract_external_variables(self):
|
|
202
|
+
"""
|
|
203
|
+
Находит все переменные, которые используются в функции, но не являются:
|
|
204
|
+
- её аргументами
|
|
205
|
+
- локальными переменными
|
|
206
|
+
|
|
207
|
+
Это потенциально глобальные или внешние переменные.
|
|
208
|
+
"""
|
|
209
|
+
local_and_args = {var.name for var in self.local_variables}
|
|
210
|
+
local_and_args.update(arg.name for arg in self.args)
|
|
211
|
+
|
|
212
|
+
external_vars = set()
|
|
213
|
+
|
|
214
|
+
def visit(node: Cursor):
|
|
215
|
+
if node.kind == CursorKind.DECL_REF_EXPR:
|
|
216
|
+
name = node.spelling
|
|
217
|
+
if name and name not in local_and_args:
|
|
218
|
+
external_vars.add(name)
|
|
219
|
+
|
|
220
|
+
for child in node.get_children():
|
|
221
|
+
visit(child)
|
|
222
|
+
|
|
223
|
+
visit(self._cursor)
|
|
224
|
+
|
|
225
|
+
self.external_variables = external_vars
|
|
226
|
+
|
|
227
|
+
def describe(self):
|
|
228
|
+
out = [f"Function: {self.name} ({self.location})"]
|
|
229
|
+
out.append(f" Return type: {self.return_var}")
|
|
230
|
+
out.append(f" Static: {'yes' if self.is_static else 'no'}")
|
|
231
|
+
out.append(f" Functional lines: {self.lines_functional}")
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
if self.local_variables:
|
|
235
|
+
out.append(" Local variables:")
|
|
236
|
+
for var in self.local_variables:
|
|
237
|
+
out.append(f" - {var['type']} {var['name']}")
|
|
238
|
+
|
|
239
|
+
out.append(" Arguments:")
|
|
240
|
+
for arg in self.args:
|
|
241
|
+
out.append(f" - {arg['type']} {arg['name']}")
|
|
242
|
+
|
|
243
|
+
if self.called_funcs:
|
|
244
|
+
out.append(" Called functions:")
|
|
245
|
+
for name in self.called_funcs:
|
|
246
|
+
out.append(f" - {name}")
|
|
247
|
+
|
|
248
|
+
if self.external_variables:
|
|
249
|
+
out.append(f" External variables:")
|
|
250
|
+
for name in self.external_variables:
|
|
251
|
+
out.append(f" - {name}")
|
|
252
|
+
|
|
253
|
+
return "\n".join(out)
|
|
254
|
+
|
|
255
|
+
def __str__(self):
|
|
256
|
+
return self.describe()
|
|
257
|
+
|
|
258
|
+
def __eq__(self, other):
|
|
259
|
+
if not isinstance(other, FunctionInfo):
|
|
260
|
+
return False
|
|
261
|
+
return self._usr == other._usr
|
|
262
|
+
|
|
263
|
+
def __hash__(self):
|
|
264
|
+
return hash(self._usr)
|
|
265
|
+
|
|
266
|
+
class ReturnInfo:
|
|
267
|
+
def __init__(self, return_type: str, expression: Optional[str] = None):
|
|
268
|
+
self.type = return_type
|
|
269
|
+
self.expression = expression
|
|
270
|
+
|
|
271
|
+
def describe(self) -> str:
|
|
272
|
+
desc = f"Return type: {self.type}"
|
|
273
|
+
if self.expression:
|
|
274
|
+
desc += f", returns: {self.expression}"
|
|
275
|
+
return desc
|
|
276
|
+
|
|
277
|
+
def __str__(self):
|
|
278
|
+
return self.describe()
|
parser_ci_AP/logger.py
ADDED