lambda-graphs 0.1.4__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.
- lambda_graphs/__init__.py +18 -0
- lambda_graphs/__main__.py +7 -0
- lambda_graphs/cli.py +183 -0
- lambda_graphs/codeviews/AST/AST.py +120 -0
- lambda_graphs/codeviews/AST/AST_driver.py +37 -0
- lambda_graphs/codeviews/AST/__init__.py +0 -0
- lambda_graphs/codeviews/CFG/CFG.py +42 -0
- lambda_graphs/codeviews/CFG/CFG_c.py +1182 -0
- lambda_graphs/codeviews/CFG/CFG_cpp.py +5604 -0
- lambda_graphs/codeviews/CFG/CFG_driver.py +45 -0
- lambda_graphs/codeviews/CFG/CFG_java.py +1722 -0
- lambda_graphs/codeviews/CFG/__init__.py +0 -0
- lambda_graphs/codeviews/CST/CST_driver.py +70 -0
- lambda_graphs/codeviews/CST/__init__.py +0 -0
- lambda_graphs/codeviews/DFG/DFG_driver.py +42 -0
- lambda_graphs/codeviews/DFG/__init__.py +0 -0
- lambda_graphs/codeviews/SDFG/SDFG.py +135 -0
- lambda_graphs/codeviews/SDFG/SDFG_c.py +2041 -0
- lambda_graphs/codeviews/SDFG/SDFG_cpp.py +4030 -0
- lambda_graphs/codeviews/SDFG/SDFG_java.py +1618 -0
- lambda_graphs/codeviews/SDFG/__init__.py +0 -0
- lambda_graphs/codeviews/__init__.py +0 -0
- lambda_graphs/codeviews/combined_graph/__init__.py +0 -0
- lambda_graphs/codeviews/combined_graph/combined_driver.py +163 -0
- lambda_graphs/tree_parser/__init__.py +0 -0
- lambda_graphs/tree_parser/c_parser.py +565 -0
- lambda_graphs/tree_parser/cpp_parser.py +424 -0
- lambda_graphs/tree_parser/custom_parser.py +66 -0
- lambda_graphs/tree_parser/java_parser.py +254 -0
- lambda_graphs/tree_parser/parser_driver.py +54 -0
- lambda_graphs/utils/DFG_utils.py +44 -0
- lambda_graphs/utils/__init__.py +0 -0
- lambda_graphs/utils/c_nodes.py +431 -0
- lambda_graphs/utils/cpp_nodes.py +1331 -0
- lambda_graphs/utils/java_nodes.py +756 -0
- lambda_graphs/utils/multi_file_merger.py +444 -0
- lambda_graphs/utils/postprocessor.py +83 -0
- lambda_graphs/utils/preprocessor.py +68 -0
- lambda_graphs/utils/src_parser.py +23 -0
- lambda_graphs-0.1.4.dist-info/METADATA +380 -0
- lambda_graphs-0.1.4.dist-info/RECORD +45 -0
- lambda_graphs-0.1.4.dist-info/WHEEL +5 -0
- lambda_graphs-0.1.4.dist-info/entry_points.txt +2 -0
- lambda_graphs-0.1.4.dist-info/licenses/LICENSE +21 -0
- lambda_graphs-0.1.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Multi-file merger module for combining multiple C/C++ source files into a single file.
|
|
3
|
+
|
|
4
|
+
This module uses tree-sitter to parse and extract code elements from multiple files,
|
|
5
|
+
then combines them in the correct order for graph generation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import tempfile
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Dict, List, Tuple, Optional
|
|
12
|
+
|
|
13
|
+
from tree_sitter import Parser
|
|
14
|
+
from loguru import logger
|
|
15
|
+
|
|
16
|
+
from lambda_graphs import get_language_map
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MultiFileMerger:
|
|
20
|
+
"""
|
|
21
|
+
Merges multiple C/C++ source files from a folder into a single file.
|
|
22
|
+
|
|
23
|
+
The merger extracts and orders code elements properly:
|
|
24
|
+
1. System includes
|
|
25
|
+
2. Macro definitions and preprocessor directives
|
|
26
|
+
3. Type definitions (structs, unions, enums, typedefs)
|
|
27
|
+
4. Global variable declarations
|
|
28
|
+
5. Function declarations (prototypes)
|
|
29
|
+
6. Function definitions
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, folder_path: str, language: str):
|
|
33
|
+
"""
|
|
34
|
+
Initialize the multi-file merger.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
folder_path: Path to the folder containing source files
|
|
38
|
+
language: Target language ('c' or 'cpp')
|
|
39
|
+
"""
|
|
40
|
+
self.folder_path = Path(folder_path)
|
|
41
|
+
self.language = language.lower()
|
|
42
|
+
|
|
43
|
+
if self.language not in ["c", "cpp", "java"]:
|
|
44
|
+
raise ValueError(
|
|
45
|
+
f"Unsupported language: {language}. Use 'c', 'cpp', or 'java'."
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
if not self.folder_path.exists():
|
|
49
|
+
raise FileNotFoundError(f"Folder not found: {folder_path}")
|
|
50
|
+
|
|
51
|
+
if not self.folder_path.is_dir():
|
|
52
|
+
raise ValueError(f"Path is not a directory: {folder_path}")
|
|
53
|
+
|
|
54
|
+
self.language_map = get_language_map()
|
|
55
|
+
self.parser = Parser(self.language_map[self.language])
|
|
56
|
+
|
|
57
|
+
self.system_includes: List[str] = []
|
|
58
|
+
self.local_includes: List[str] = []
|
|
59
|
+
self.macro_definitions: List[str] = []
|
|
60
|
+
self.type_definitions: List[str] = []
|
|
61
|
+
self.global_declarations: List[str] = []
|
|
62
|
+
self.function_declarations: List[str] = []
|
|
63
|
+
self.function_definitions: List[Tuple[str, str]] = []
|
|
64
|
+
|
|
65
|
+
self.seen_includes: set = set()
|
|
66
|
+
self.seen_macros: set = set()
|
|
67
|
+
self.seen_types: set = set()
|
|
68
|
+
self.seen_function_decls: set = set()
|
|
69
|
+
self.seen_function_defs: set = set()
|
|
70
|
+
self.seen_globals: set = set()
|
|
71
|
+
|
|
72
|
+
def _get_source_files(self) -> Tuple[List[Path], List[Path]]:
|
|
73
|
+
"""
|
|
74
|
+
Get all source files from the folder.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Tuple of (header_files, source_files)
|
|
78
|
+
"""
|
|
79
|
+
header_files = []
|
|
80
|
+
source_files = []
|
|
81
|
+
|
|
82
|
+
if self.language == "c":
|
|
83
|
+
source_extensions = [".c"]
|
|
84
|
+
header_extensions = [".h"]
|
|
85
|
+
elif self.language == "java":
|
|
86
|
+
source_extensions = [".java"]
|
|
87
|
+
header_extensions = []
|
|
88
|
+
else:
|
|
89
|
+
source_extensions = [".cpp", ".cc", ".cxx", ".c++"]
|
|
90
|
+
header_extensions = [".h", ".hpp", ".hh", ".hxx", ".h++"]
|
|
91
|
+
|
|
92
|
+
for root, _, files in os.walk(self.folder_path):
|
|
93
|
+
for file in sorted(files):
|
|
94
|
+
file_path = Path(root) / file
|
|
95
|
+
ext = file_path.suffix.lower()
|
|
96
|
+
|
|
97
|
+
if ext in header_extensions:
|
|
98
|
+
header_files.append(file_path)
|
|
99
|
+
elif ext in source_extensions:
|
|
100
|
+
source_files.append(file_path)
|
|
101
|
+
|
|
102
|
+
header_files.sort()
|
|
103
|
+
source_files.sort()
|
|
104
|
+
|
|
105
|
+
logger.debug(
|
|
106
|
+
f"Found {len(header_files)} header files and {len(source_files)} source files"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
return header_files, source_files
|
|
110
|
+
|
|
111
|
+
def _parse_file(self, file_path: Path) -> None:
|
|
112
|
+
"""
|
|
113
|
+
Parse a single file and extract its elements.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
file_path: Path to the source file
|
|
117
|
+
"""
|
|
118
|
+
logger.debug(f"Parsing file: {file_path}")
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
|
|
122
|
+
content = f.read()
|
|
123
|
+
except Exception as e:
|
|
124
|
+
logger.warning(f"Failed to read file {file_path}: {e}")
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
tree = self.parser.parse(bytes(content, "utf-8"))
|
|
128
|
+
root_node = tree.root_node
|
|
129
|
+
|
|
130
|
+
for child in root_node.children:
|
|
131
|
+
self._process_node(child, content)
|
|
132
|
+
|
|
133
|
+
def _process_node(self, node, content: str) -> None:
|
|
134
|
+
"""
|
|
135
|
+
Process a top-level AST node and categorize it.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
node: Tree-sitter node
|
|
139
|
+
content: Original source code
|
|
140
|
+
"""
|
|
141
|
+
node_text = content[node.start_byte : node.end_byte]
|
|
142
|
+
|
|
143
|
+
if node.type == "comment":
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
if node.type == "preproc_include":
|
|
147
|
+
self._handle_include(node_text)
|
|
148
|
+
elif node.type == "preproc_def":
|
|
149
|
+
self._handle_macro(node_text)
|
|
150
|
+
elif node.type == "preproc_function_def":
|
|
151
|
+
self._handle_macro(node_text)
|
|
152
|
+
elif node.type == "preproc_ifdef":
|
|
153
|
+
self._handle_preprocessor_conditional(node, content)
|
|
154
|
+
elif node.type == "preproc_ifndef":
|
|
155
|
+
self._handle_preprocessor_conditional(node, content)
|
|
156
|
+
elif node.type == "preproc_if":
|
|
157
|
+
self._handle_preprocessor_conditional(node, content)
|
|
158
|
+
|
|
159
|
+
elif node.type == "struct_specifier":
|
|
160
|
+
self._handle_type_definition(node_text)
|
|
161
|
+
elif node.type == "union_specifier":
|
|
162
|
+
self._handle_type_definition(node_text)
|
|
163
|
+
elif node.type == "enum_specifier":
|
|
164
|
+
self._handle_type_definition(node_text)
|
|
165
|
+
elif node.type == "type_definition":
|
|
166
|
+
self._handle_type_definition(node_text)
|
|
167
|
+
|
|
168
|
+
elif node.type == "declaration":
|
|
169
|
+
self._handle_declaration(node, node_text)
|
|
170
|
+
|
|
171
|
+
elif node.type == "function_definition":
|
|
172
|
+
self._handle_function_definition(node, node_text)
|
|
173
|
+
|
|
174
|
+
elif node.type == "class_specifier":
|
|
175
|
+
self._handle_type_definition(node_text)
|
|
176
|
+
elif node.type == "namespace_definition":
|
|
177
|
+
self._handle_type_definition(node_text)
|
|
178
|
+
elif node.type == "template_declaration":
|
|
179
|
+
self._handle_template(node, content, node_text)
|
|
180
|
+
elif node.type == "using_declaration":
|
|
181
|
+
self._handle_type_definition(node_text)
|
|
182
|
+
elif node.type == "alias_declaration":
|
|
183
|
+
self._handle_type_definition(node_text)
|
|
184
|
+
|
|
185
|
+
elif node.type == "linkage_specification":
|
|
186
|
+
self._handle_linkage_spec(node, content)
|
|
187
|
+
|
|
188
|
+
elif node.type == "declaration_list":
|
|
189
|
+
for child in node.children:
|
|
190
|
+
self._process_node(child, content)
|
|
191
|
+
|
|
192
|
+
def _handle_include(self, text: str) -> None:
|
|
193
|
+
"""Handle include directives."""
|
|
194
|
+
text = text.strip()
|
|
195
|
+
if text in self.seen_includes:
|
|
196
|
+
return
|
|
197
|
+
self.seen_includes.add(text)
|
|
198
|
+
|
|
199
|
+
if "<" in text:
|
|
200
|
+
self.system_includes.append(text)
|
|
201
|
+
else:
|
|
202
|
+
self.local_includes.append(text)
|
|
203
|
+
|
|
204
|
+
def _handle_macro(self, text: str) -> None:
|
|
205
|
+
"""Handle macro definitions."""
|
|
206
|
+
text = text.strip()
|
|
207
|
+
lines = text.split("\n")
|
|
208
|
+
first_line = lines[0]
|
|
209
|
+
parts = first_line.split()
|
|
210
|
+
if len(parts) >= 2:
|
|
211
|
+
macro_name = parts[1].split("(")[0]
|
|
212
|
+
if macro_name in self.seen_macros:
|
|
213
|
+
return
|
|
214
|
+
self.seen_macros.add(macro_name)
|
|
215
|
+
|
|
216
|
+
self.macro_definitions.append(text)
|
|
217
|
+
|
|
218
|
+
def _handle_preprocessor_conditional(self, node, content: str) -> None:
|
|
219
|
+
"""Handle preprocessor conditionals (ifdef, ifndef, if) by processing their children."""
|
|
220
|
+
for child in node.children:
|
|
221
|
+
if child.type not in [
|
|
222
|
+
"#ifndef",
|
|
223
|
+
"#ifdef",
|
|
224
|
+
"#if",
|
|
225
|
+
"#endif",
|
|
226
|
+
"#else",
|
|
227
|
+
"identifier",
|
|
228
|
+
]:
|
|
229
|
+
self._process_node(child, content)
|
|
230
|
+
|
|
231
|
+
def _handle_linkage_spec(self, node, content: str) -> None:
|
|
232
|
+
"""Handle linkage specification (extern "C" { ... })."""
|
|
233
|
+
for child in node.children:
|
|
234
|
+
if child.type == "declaration_list":
|
|
235
|
+
for subchild in child.children:
|
|
236
|
+
self._process_node(subchild, content)
|
|
237
|
+
|
|
238
|
+
def _handle_type_definition(self, text: str) -> None:
|
|
239
|
+
"""Handle struct, union, enum, typedef, class definitions."""
|
|
240
|
+
text = text.strip()
|
|
241
|
+
if text in self.seen_types:
|
|
242
|
+
return
|
|
243
|
+
self.seen_types.add(text)
|
|
244
|
+
self.type_definitions.append(text)
|
|
245
|
+
|
|
246
|
+
def _handle_declaration(self, node, text: str) -> None:
|
|
247
|
+
"""Handle variable and function declarations."""
|
|
248
|
+
text = text.strip()
|
|
249
|
+
|
|
250
|
+
is_function_decl = False
|
|
251
|
+
for child in node.children:
|
|
252
|
+
if child.type == "function_declarator":
|
|
253
|
+
is_function_decl = True
|
|
254
|
+
break
|
|
255
|
+
if child.type == "pointer_declarator":
|
|
256
|
+
for subchild in child.children:
|
|
257
|
+
if subchild.type == "function_declarator":
|
|
258
|
+
is_function_decl = True
|
|
259
|
+
break
|
|
260
|
+
|
|
261
|
+
if is_function_decl:
|
|
262
|
+
if text not in self.seen_function_decls:
|
|
263
|
+
self.seen_function_decls.add(text)
|
|
264
|
+
self.function_declarations.append(text)
|
|
265
|
+
else:
|
|
266
|
+
if text not in self.seen_globals:
|
|
267
|
+
self.seen_globals.add(text)
|
|
268
|
+
self.global_declarations.append(text)
|
|
269
|
+
|
|
270
|
+
def _handle_function_definition(self, node, text: str) -> None:
|
|
271
|
+
"""Handle function definitions."""
|
|
272
|
+
text = text.strip()
|
|
273
|
+
|
|
274
|
+
func_name = self._extract_function_name(node)
|
|
275
|
+
|
|
276
|
+
if func_name in self.seen_function_defs:
|
|
277
|
+
logger.warning(f"Duplicate function definition: {func_name}")
|
|
278
|
+
return
|
|
279
|
+
|
|
280
|
+
self.seen_function_defs.add(func_name)
|
|
281
|
+
self.function_definitions.append((func_name, text))
|
|
282
|
+
|
|
283
|
+
def _handle_template(self, node, content: str, text: str) -> None:
|
|
284
|
+
"""Handle C++ template declarations."""
|
|
285
|
+
text = text.strip()
|
|
286
|
+
|
|
287
|
+
has_function_def = False
|
|
288
|
+
for child in node.children:
|
|
289
|
+
if child.type == "function_definition":
|
|
290
|
+
has_function_def = True
|
|
291
|
+
break
|
|
292
|
+
|
|
293
|
+
if has_function_def:
|
|
294
|
+
func_name = f"template_{len(self.function_definitions)}"
|
|
295
|
+
if text not in self.seen_function_defs:
|
|
296
|
+
self.seen_function_defs.add(text)
|
|
297
|
+
self.function_definitions.append((func_name, text))
|
|
298
|
+
else:
|
|
299
|
+
if text not in self.seen_types:
|
|
300
|
+
self.seen_types.add(text)
|
|
301
|
+
self.type_definitions.append(text)
|
|
302
|
+
|
|
303
|
+
def _extract_function_name(self, node) -> str:
|
|
304
|
+
"""Extract function name from a function definition node."""
|
|
305
|
+
for child in node.children:
|
|
306
|
+
if child.type == "function_declarator":
|
|
307
|
+
for subchild in child.children:
|
|
308
|
+
if subchild.type == "identifier":
|
|
309
|
+
return subchild.text.decode("utf-8")
|
|
310
|
+
elif subchild.type == "field_identifier":
|
|
311
|
+
return subchild.text.decode("utf-8")
|
|
312
|
+
elif subchild.type == "destructor_name":
|
|
313
|
+
return subchild.text.decode("utf-8")
|
|
314
|
+
elif subchild.type == "qualified_identifier":
|
|
315
|
+
return subchild.text.decode("utf-8")
|
|
316
|
+
elif child.type == "pointer_declarator":
|
|
317
|
+
return self._extract_function_name_from_declarator(child)
|
|
318
|
+
|
|
319
|
+
return f"unknown_func_{len(self.function_definitions)}"
|
|
320
|
+
|
|
321
|
+
def _extract_function_name_from_declarator(self, node) -> str:
|
|
322
|
+
"""Recursively extract function name from declarator."""
|
|
323
|
+
for child in node.children:
|
|
324
|
+
if child.type == "function_declarator":
|
|
325
|
+
for subchild in child.children:
|
|
326
|
+
if subchild.type == "identifier":
|
|
327
|
+
return subchild.text.decode("utf-8")
|
|
328
|
+
elif child.type == "pointer_declarator":
|
|
329
|
+
return self._extract_function_name_from_declarator(child)
|
|
330
|
+
return "unknown_func"
|
|
331
|
+
|
|
332
|
+
def merge(self) -> str:
|
|
333
|
+
"""
|
|
334
|
+
Merge all source files into a single string.
|
|
335
|
+
|
|
336
|
+
Returns:
|
|
337
|
+
Merged source code
|
|
338
|
+
"""
|
|
339
|
+
header_files, source_files = self._get_source_files()
|
|
340
|
+
|
|
341
|
+
if not header_files and not source_files:
|
|
342
|
+
raise ValueError(f"No source files found in {self.folder_path}")
|
|
343
|
+
|
|
344
|
+
for header_file in header_files:
|
|
345
|
+
self._parse_file(header_file)
|
|
346
|
+
|
|
347
|
+
for source_file in source_files:
|
|
348
|
+
self._parse_file(source_file)
|
|
349
|
+
|
|
350
|
+
output_parts = []
|
|
351
|
+
|
|
352
|
+
output_parts.append(f"/* Merged source from {self.folder_path} */")
|
|
353
|
+
output_parts.append(f"/* Generated by lambda-graphs Multi-File Merger */")
|
|
354
|
+
output_parts.append("")
|
|
355
|
+
|
|
356
|
+
if self.system_includes:
|
|
357
|
+
output_parts.append("/* System Includes */")
|
|
358
|
+
for inc in self.system_includes:
|
|
359
|
+
output_parts.append(inc)
|
|
360
|
+
output_parts.append("")
|
|
361
|
+
|
|
362
|
+
if self.macro_definitions:
|
|
363
|
+
output_parts.append("/* Macro Definitions */")
|
|
364
|
+
for macro in self.macro_definitions:
|
|
365
|
+
output_parts.append(macro)
|
|
366
|
+
output_parts.append("")
|
|
367
|
+
|
|
368
|
+
if self.type_definitions:
|
|
369
|
+
output_parts.append("/* Type Definitions */")
|
|
370
|
+
for typedef in self.type_definitions:
|
|
371
|
+
output_parts.append(typedef)
|
|
372
|
+
output_parts.append("")
|
|
373
|
+
|
|
374
|
+
if self.global_declarations:
|
|
375
|
+
output_parts.append("/* Global Declarations */")
|
|
376
|
+
for decl in self.global_declarations:
|
|
377
|
+
output_parts.append(decl)
|
|
378
|
+
output_parts.append("")
|
|
379
|
+
|
|
380
|
+
if self.function_declarations:
|
|
381
|
+
output_parts.append("/* Function Declarations */")
|
|
382
|
+
for decl in self.function_declarations:
|
|
383
|
+
output_parts.append(decl)
|
|
384
|
+
output_parts.append("")
|
|
385
|
+
|
|
386
|
+
if self.function_definitions:
|
|
387
|
+
output_parts.append("/* Function Definitions */")
|
|
388
|
+
for func_name, func_code in self.function_definitions:
|
|
389
|
+
output_parts.append(f"/* Function: {func_name} */")
|
|
390
|
+
output_parts.append(func_code)
|
|
391
|
+
output_parts.append("")
|
|
392
|
+
|
|
393
|
+
return "\n".join(output_parts)
|
|
394
|
+
|
|
395
|
+
def merge_to_file(self, output_path: Optional[str] = None) -> str:
|
|
396
|
+
"""
|
|
397
|
+
Merge all source files and write to a file.
|
|
398
|
+
|
|
399
|
+
Args:
|
|
400
|
+
output_path: Optional path for the output file. If not provided,
|
|
401
|
+
a temporary file will be created.
|
|
402
|
+
|
|
403
|
+
Returns:
|
|
404
|
+
Path to the merged file
|
|
405
|
+
"""
|
|
406
|
+
merged_code = self.merge()
|
|
407
|
+
|
|
408
|
+
if output_path is None:
|
|
409
|
+
if self.language == "c":
|
|
410
|
+
suffix = ".c"
|
|
411
|
+
else:
|
|
412
|
+
suffix = ".cpp"
|
|
413
|
+
|
|
414
|
+
temp_dir = tempfile.mkdtemp(prefix="lambda_graphs_merged_")
|
|
415
|
+
output_path = os.path.join(temp_dir, f"project{suffix}")
|
|
416
|
+
|
|
417
|
+
with open(output_path, "w", encoding="utf-8") as f:
|
|
418
|
+
f.write(merged_code)
|
|
419
|
+
|
|
420
|
+
logger.info(f"Merged source written to: {output_path}")
|
|
421
|
+
|
|
422
|
+
return output_path
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def merge_files(
|
|
426
|
+
folder_path: str, language: str, output_path: Optional[str] = None
|
|
427
|
+
) -> str:
|
|
428
|
+
"""
|
|
429
|
+
Convenience function to merge a folder of source files.
|
|
430
|
+
|
|
431
|
+
Args:
|
|
432
|
+
folder_path: Path to the folder containing source files
|
|
433
|
+
language: Target language ('c' or 'cpp')
|
|
434
|
+
output_path: Optional path for the output file
|
|
435
|
+
|
|
436
|
+
Returns:
|
|
437
|
+
Path to the merged file
|
|
438
|
+
"""
|
|
439
|
+
merger = MultiFileMerger(folder_path, language)
|
|
440
|
+
return merger.merge_to_file(output_path)
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
FolderCombiner = MultiFileMerger
|
|
444
|
+
combine_folder = merge_files
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
from subprocess import check_call
|
|
6
|
+
|
|
7
|
+
import networkx as nx
|
|
8
|
+
from networkx.readwrite import json_graph
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def networkx_to_json(graph):
|
|
12
|
+
"""Convert a networkx graph to a json object"""
|
|
13
|
+
graph_json = json_graph.node_link_data(graph)
|
|
14
|
+
return graph_json
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def write_networkx_to_json(graph, filename):
|
|
18
|
+
"""Convert a networkx graph to a json object"""
|
|
19
|
+
graph_json = json_graph.node_link_data(graph)
|
|
20
|
+
if not os.getenv("GITHUB_ACTIONS"):
|
|
21
|
+
with open(filename, "w") as f:
|
|
22
|
+
json.dump(graph_json, f)
|
|
23
|
+
return graph_json
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def to_dot(graph):
|
|
27
|
+
return nx.nx_pydot.to_pydot(graph)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def write_to_dot(og_graph, filename, output_png=False, src_language=None):
|
|
31
|
+
graph = copy.deepcopy(og_graph)
|
|
32
|
+
if not os.getenv("GITHUB_ACTIONS"):
|
|
33
|
+
dot_reserved_keywords = {
|
|
34
|
+
"node",
|
|
35
|
+
"edge",
|
|
36
|
+
"graph",
|
|
37
|
+
"digraph",
|
|
38
|
+
"subgraph",
|
|
39
|
+
"strict",
|
|
40
|
+
"Node",
|
|
41
|
+
"Edge",
|
|
42
|
+
"Graph",
|
|
43
|
+
"Digraph",
|
|
44
|
+
"Subgraph",
|
|
45
|
+
"Strict",
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for node in graph.nodes:
|
|
49
|
+
if "label" in graph.nodes[node]:
|
|
50
|
+
label = graph.nodes[node]["label"]
|
|
51
|
+
|
|
52
|
+
if src_language in ["c", "cpp", "java"]:
|
|
53
|
+
label = str(label)
|
|
54
|
+
label = label.replace("\\", "\\\\")
|
|
55
|
+
label = label.replace('"', '\\"')
|
|
56
|
+
label = label.replace("\n", " ")
|
|
57
|
+
label = label.replace("\r", " ")
|
|
58
|
+
else:
|
|
59
|
+
label = re.escape(label)
|
|
60
|
+
|
|
61
|
+
if (
|
|
62
|
+
src_language in ["c", "cpp", "java"]
|
|
63
|
+
or label in dot_reserved_keywords
|
|
64
|
+
):
|
|
65
|
+
label = f'"{label}"'
|
|
66
|
+
|
|
67
|
+
graph.nodes[node]["label"] = label
|
|
68
|
+
|
|
69
|
+
for u, v, key, data in graph.edges(keys=True, data=True):
|
|
70
|
+
for attr_name in ["used_def", "used_var", "returned_value"]:
|
|
71
|
+
if attr_name in data:
|
|
72
|
+
attr_value = str(data[attr_name])
|
|
73
|
+
needs_quoting = attr_value in dot_reserved_keywords or (
|
|
74
|
+
src_language in ["c", "cpp", "java"] and "::" in attr_value
|
|
75
|
+
)
|
|
76
|
+
if needs_quoting:
|
|
77
|
+
graph.edges[u, v, key][attr_name] = f'"{attr_value}"'
|
|
78
|
+
|
|
79
|
+
nx.nx_pydot.write_dot(graph, filename)
|
|
80
|
+
if output_png:
|
|
81
|
+
check_call(
|
|
82
|
+
["dot", "-Tpng", filename, "-o", filename.rsplit(".", 1)[0] + ".png"]
|
|
83
|
+
)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import tokenize
|
|
3
|
+
from io import StringIO
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def remove_empty_lines(source):
|
|
7
|
+
temp = []
|
|
8
|
+
for x in source.split("\n"):
|
|
9
|
+
temp.append(x)
|
|
10
|
+
return "\n".join(temp)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def remove_comments(lang, source):
|
|
14
|
+
if lang in ["python"]:
|
|
15
|
+
"""
|
|
16
|
+
Returns 'source' minus comments and docstrings.
|
|
17
|
+
"""
|
|
18
|
+
io_obj = StringIO(source)
|
|
19
|
+
out = ""
|
|
20
|
+
prev_toktype = tokenize.INDENT
|
|
21
|
+
last_lineno = -1
|
|
22
|
+
last_col = 0
|
|
23
|
+
for tok in tokenize.generate_tokens(io_obj.readline):
|
|
24
|
+
token_type = tok[0]
|
|
25
|
+
token_string = tok[1]
|
|
26
|
+
start_line, start_col = tok[2]
|
|
27
|
+
end_line, end_col = tok[3]
|
|
28
|
+
ltext = tok[4]
|
|
29
|
+
if start_line > last_lineno:
|
|
30
|
+
last_col = 0
|
|
31
|
+
if start_col > last_col:
|
|
32
|
+
out += " " * (start_col - last_col)
|
|
33
|
+
if token_type == tokenize.COMMENT:
|
|
34
|
+
pass
|
|
35
|
+
elif token_type == tokenize.STRING:
|
|
36
|
+
if prev_toktype != tokenize.INDENT:
|
|
37
|
+
if prev_toktype != tokenize.NEWLINE:
|
|
38
|
+
if start_col > 0:
|
|
39
|
+
out += token_string
|
|
40
|
+
else:
|
|
41
|
+
out += token_string
|
|
42
|
+
prev_toktype = token_type
|
|
43
|
+
last_col = end_col
|
|
44
|
+
last_lineno = end_line
|
|
45
|
+
temp = []
|
|
46
|
+
for x in out.split("\n"):
|
|
47
|
+
if x.strip() != "":
|
|
48
|
+
temp.append(x)
|
|
49
|
+
return "\n".join(temp)
|
|
50
|
+
elif lang in ["ruby"]:
|
|
51
|
+
return source
|
|
52
|
+
else:
|
|
53
|
+
|
|
54
|
+
def replacer(match):
|
|
55
|
+
s = match.group(0)
|
|
56
|
+
if s.startswith("/"):
|
|
57
|
+
return " "
|
|
58
|
+
else:
|
|
59
|
+
return s
|
|
60
|
+
|
|
61
|
+
pattern = re.compile(
|
|
62
|
+
r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
|
|
63
|
+
re.DOTALL | re.MULTILINE,
|
|
64
|
+
)
|
|
65
|
+
temp = []
|
|
66
|
+
for x in re.sub(pattern, replacer, source).split("\n"):
|
|
67
|
+
temp.append(x)
|
|
68
|
+
return "\n".join(temp)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
def traverse_tree(tree, finest_granularity=None):
|
|
2
|
+
if finest_granularity is None:
|
|
3
|
+
finest_granularity = []
|
|
4
|
+
cursor = tree.walk()
|
|
5
|
+
|
|
6
|
+
reached_root = False
|
|
7
|
+
while not reached_root:
|
|
8
|
+
yield cursor.node
|
|
9
|
+
|
|
10
|
+
if cursor.goto_first_child() and cursor.node.type not in finest_granularity:
|
|
11
|
+
continue
|
|
12
|
+
|
|
13
|
+
if cursor.goto_next_sibling():
|
|
14
|
+
continue
|
|
15
|
+
|
|
16
|
+
retracing = True
|
|
17
|
+
while retracing:
|
|
18
|
+
if not cursor.goto_parent():
|
|
19
|
+
retracing = False
|
|
20
|
+
reached_root = True
|
|
21
|
+
|
|
22
|
+
if cursor.goto_next_sibling():
|
|
23
|
+
retracing = False
|