Coverage for src/renaissance/integrations/clang/clang_adapter.py: 87%

31 statements  

« prev     ^ index     » next       coverage.py v7.16.1, created at 2026-10-02 13:12 +0000

1"""AI: Adapter that parses C/C++ source with libclang into the internal LST representation.""" 

2 

3from clang import cindex 

4 

5from renaissance.integrations.tree_sitter.lst import LST, LSTNode 

6from renaissance.utils.ast_utils import detect_placeholder 

7 

8 

9class ClangAdapter: 

10 """AI: Adapter that parses C/C++ source with libclang into the internal LST representation.""" 

11 

12 def __init__(self, clang_path: str | None = None, args: list | None = None): 

13 """AI: Configure the libclang native library location and default parse arguments.""" 

14 if clang_path and cindex.Config.library_path is None: 

15 cindex.Config.set_library_path(clang_path) 

16 self.args = args or ["-std=c++17"] 

17 

18 def parse(self, file_path: str) -> LST: 

19 """AI: Parse the C/C++ source file at file_path into an LST.""" 

20 index = cindex.Index.create() 

21 translation_unit = index.parse(file_path, args=self.args) 

22 return LST(self._convert_node(translation_unit.cursor)) 

23 

24 def load_from_text(self, text: str, file_name: str): 

25 """AI: Parse in-memory C/C++ source text as the given file_name into an LST.""" 

26 index = cindex.Index.create() 

27 translation_unit = index.parse(file_name, unsaved_files=[(file_name, text)], args=[]) 

28 return LST(self._convert_node(translation_unit.cursor)) 

29 

30 def to_lst(self, source_code: str) -> LST: 

31 """AI: Parse a C/C++ source code string into an LST.""" 

32 # source_code= replace_dollar(source_code) 

33 return self.load_from_text(source_code, "no_src.cpp") 

34 

35 def _convert_node(self, cursor: cindex.Cursor, parent: LSTNode | None = None) -> LSTNode: 

36 try: 

37 kind = cursor.kind.name 

38 except Exception as e: 

39 print(e.__cause__) 

40 kind = "invalid kind" 

41 signature = cursor.spelling or cursor.displayname or kind 

42 

43 is_ph, coerced_type, ph_name = detect_placeholder(signature, kind) 

44 

45 node = LSTNode( 

46 node_type=coerced_type if is_ph else kind, 

47 properties={ 

48 "spelling": cursor.spelling, 

49 "type": str(cursor.type.spelling), 

50 "location": str(cursor.location), 

51 "is_definition": cursor.is_definition(), 

52 "name": ph_name, 

53 **( 

54 { 

55 "placeholder": True, 

56 "placeholder_name": ph_name, 

57 "original_node_type": cursor.kind.name, 

58 } 

59 if is_ph 

60 else {} 

61 ), 

62 }, 

63 signature=signature, 

64 offset=cursor.extent.start.offset, 

65 parent=parent, 

66 ) 

67 

68 for child in cursor.get_children(): 

69 child_node = self._convert_node(child, parent=node) 

70 node.add_child(child_node) 

71 return node