Skip to content

Commit e60a5cc

Browse files
authored
Merge pull request #20 from divmadan/devel/treelib
Introduce and utilise treelib
2 parents 81f12b9 + c813bf9 commit e60a5cc

5 files changed

Lines changed: 247 additions & 355 deletions

File tree

.github/workflows/pytest.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ jobs:
2727
- name: Install pip
2828
run: python -m pip install --upgrade pip
2929

30-
- name: Install pytest
31-
run: pip install pytest
30+
- name: Install packages
31+
run: pip install pytest treelib
3232

3333
# Install clang and it's python inteface via apt
3434
- name: Add llvm keys

clang_bind/clang_utils.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,10 @@ def get_properties_dict(self):
118118
- {property_name, property}
119119
"""
120120
return self.properties_dict
121+
122+
def get_all_functions_dict(self):
123+
return {
124+
**self.check_functions_dict,
125+
**self.get_functions_dict,
126+
**self.properties_dict,
127+
}

clang_bind/parse.py

Lines changed: 47 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,134 +1,77 @@
11
import clang.cindex as clang
2+
from treelib import Tree
23

3-
import clang_bind.utils as utils
44
from clang_bind.clang_utils import ClangUtils
55

66

7+
class Node:
8+
def __init__(self, cursor, verbose=False):
9+
self.cursor = cursor
10+
if verbose:
11+
# Add additional information about the cursor
12+
# Get values from the classes in cindex.py: `is_` methods, `get_` methods, @property values
13+
self.cursor_kind = ClangUtils(cursor.kind).get_all_functions_dict()
14+
self.cursor = ClangUtils(cursor).get_all_functions_dict()
15+
self.type = ClangUtils(cursor.type).get_all_functions_dict()
16+
17+
def __repr__(self) -> str:
18+
return f"{self.cursor.kind.name}:'{self.cursor.spelling}'"
19+
20+
721
class Parse:
822
"""
9-
Class containing functions to generate an AST of a file and parse it to retrieve relevant information.
23+
Class to parse a file and generate an AST from it.
1024
"""
1125

1226
def __init__(self, file, compiler_arguments):
1327
index = clang.Index.create()
14-
1528
"""
1629
- Why parse using the option `PARSE_DETAILED_PROCESSING_RECORD`?
1730
- Indicates that the parser should construct a detailed preprocessing record,
1831
including all macro definitions and instantiations
1932
- Required to retrieve `CursorKind.INCLUSION_DIRECTIVE`
2033
"""
21-
2234
source_ast = index.parse(
2335
path=file,
2436
args=compiler_arguments,
2537
options=clang.TranslationUnit.PARSE_DETAILED_PROCESSING_RECORD,
2638
)
27-
28-
self.root_node = {
29-
"cursor": source_ast.cursor,
30-
"filename": source_ast.spelling,
31-
"depth": 0,
32-
}
33-
34-
@staticmethod
35-
def _is_valid_child(parent_node, child_node):
36-
child = child_node.get("cursor")
37-
parent_filename = parent_node.get("filename")
38-
39-
if child.location.file and child.location.file.name == parent_filename:
40-
return True
41-
return False
39+
self.filename = source_ast.spelling
40+
self.tree = Tree()
41+
self.root_node = self.tree.create_node(
42+
identifier=Node(source_ast.cursor), tag=repr(Node(source_ast.cursor))
43+
)
4244

4345
@staticmethod
44-
def get_parsed_node(node):
45-
cursor = node.get("cursor")
46-
47-
# Objects to get various kinds of checks available in cindex.py via clang_utils.py
48-
cursor_kind_utils = ClangUtils(cursor.kind)
49-
cursor_utils = ClangUtils(cursor)
50-
cursor_type_utils = ClangUtils(cursor.type)
51-
52-
parsed_node = {
53-
"depth": node.get("depth"),
54-
"line": cursor.location.line,
55-
"column": cursor.location.column,
56-
"tokens": [x.spelling for x in cursor.get_tokens()],
57-
"cursor_kind": {
58-
**cursor_kind_utils.get_check_functions_dict(), # Functions that begin with "is_" i.e., checking functions
59-
**cursor_kind_utils.get_get_functions_dict(), # Functions that begin with "get_" i.e., getter functions
60-
**cursor_kind_utils.get_properties_dict(), # Properties
61-
},
62-
"cursor": {
63-
**cursor_utils.get_check_functions_dict(),
64-
**cursor_utils.get_get_functions_dict(),
65-
**cursor_utils.get_properties_dict(),
66-
},
67-
"type": {
68-
**cursor_type_utils.get_check_functions_dict(),
69-
**cursor_type_utils.get_get_functions_dict(),
70-
**cursor_type_utils.get_properties_dict(),
71-
},
72-
"members": [],
73-
}
74-
75-
# HACKY FIXES
76-
# get spelling from object
77-
parsed_node["cursor"]["result_type"] = parsed_node["cursor"][
78-
"result_type"
79-
].spelling
80-
# replace `AccessSpecifier.value` with just `value`
81-
parsed_node["cursor"]["access_specifier"] = parsed_node["cursor"][
82-
"access_specifier"
83-
].name
84-
# replace `TypeKind.value` with just `value`
85-
parsed_node["type"]["kind"] = parsed_node["type"]["kind"].name
86-
87-
return parsed_node
88-
89-
@classmethod
90-
def parse_node_recursive(cls, node):
46+
def is_node_from_file(node, filename):
9147
"""
92-
Generates parsed information by recursively traversing the AST
93-
94-
Parameters:
95-
- node (dict):
96-
- The node in the AST
97-
- Keys:
98-
- cursor: The cursor pointing to a node
99-
- filename:
100-
- The file's name to check if the node belongs to it
101-
- Needed to ensure that only symbols belonging to the file gets parsed, not the included files' symbols
102-
- depth: The depth of the node (root=0)
103-
104-
Returns:
105-
- parsed_info (dict):
106-
- Contains key-value pairs of various traits of a node
107-
- The key 'members' contains the node's children's `parsed_info`
48+
Check if the node belongs in the file.
10849
"""
50+
return node.location.file and node.location.file.name == filename
10951

110-
cursor = node.get("cursor")
111-
filename = node.get("filename")
112-
depth = node.get("depth")
113-
114-
parsed_info = cls.get_parsed_node(node)
115-
116-
# Get cursor's children and recursively add their info to a dictionary, as members of the parent
117-
for child in cursor.get_children():
118-
child_node = {"cursor": child, "filename": filename, "depth": depth + 1}
119-
if cls._is_valid_child(node, child_node):
120-
child_parsed_info = cls.parse_node_recursive(child_node)
121-
parsed_info["members"].append(child_parsed_info)
122-
123-
return parsed_info
124-
125-
def get_parsed_info(self):
52+
def _is_valid_child(self, child_cursor):
53+
"""
54+
Check if the child is valid (child should be in the same file as the parent).
12655
"""
127-
Returns the parsed information for a file by recursively traversing the AST
56+
return self.is_node_from_file(child_cursor, self.filename)
12857

129-
Returns:
130-
- parsed_info (dict):
131-
- Contains key-value pairs of various traits of a node
132-
- The key 'members' contains the node's children's `parsed_info`
58+
def _construct_tree(self, node):
59+
"""
60+
Recursively generates tree by traversing the AST of the node.
61+
"""
62+
cursor = node.identifier.cursor
63+
for child_cursor in cursor.get_children():
64+
if self._is_valid_child(child_cursor):
65+
child_node = self.tree.create_node(
66+
identifier=Node(child_cursor),
67+
parent=node,
68+
tag=repr(Node(child_cursor)),
69+
)
70+
self._construct_tree(child_node)
71+
72+
def get_tree(self):
73+
"""
74+
Returns the constructed tree.
13375
"""
134-
return self.parse_node_recursive(self.root_node)
76+
self._construct_tree(self.root_node)
77+
return self.tree

pytest.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[pytest]
2+
3+
testpaths = tests/test_parse.py

0 commit comments

Comments
 (0)