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

31 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-15 10:34 +0000

1# Copyright (C) 2024-2026 TNO 

2# 

3# Licensed under the terms specified in the LICENSE file in the root directory 

4# of this repository, also available at: 

5# https://github.com/TNO/Renaissance.Py/blob/main/LICENSE 

6 

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

8 

9from clang import cindex 

10 

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

12from renaissance.utils.ast_utils import detect_placeholder 

13 

14 

15class ClangAdapter: 

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

17 

18 def __init__(self, clang_path: str | None = None, args: list | None = None) -> None: 

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

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

21 cindex.Config.set_library_path(clang_path) 

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

23 

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

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

26 index = cindex.Index.create() 

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

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

29 

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

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

32 index = cindex.Index.create() 

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

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

35 

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

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

38 # source_code= replace_dollar(source_code) 

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

40 

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

42 try: 

43 kind = cursor.kind.name 

44 except Exception as e: 

45 print(e.__cause__) 

46 kind = "invalid kind" 

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

48 

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

50 

51 node = LSTNode( 

52 node_type=coerced_type if is_ph else kind, 

53 properties={ 

54 "spelling": cursor.spelling, 

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

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

57 "is_definition": cursor.is_definition(), 

58 "name": ph_name, 

59 **( 

60 { 

61 "placeholder": True, 

62 "placeholder_name": ph_name, 

63 "original_node_type": cursor.kind.name, 

64 } 

65 if is_ph 

66 else {} 

67 ), 

68 }, 

69 signature=signature, 

70 offset=cursor.extent.start.offset, 

71 parent=parent, 

72 ) 

73 

74 for child in cursor.get_children(): 

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

76 node.add_child(child_node) 

77 return node