Coverage for src/rejuvenation/batch_process_examples.py: 94%

66 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# use clang to load and walk a compilation database 

8"""AI: Example script demonstrating batch AST processing across a Clang compilation database.""" 

9 

10import textwrap 

11from collections.abc import Callable, Iterable 

12from dataclasses import dataclass 

13 

14from renaissance.integrations.clang import ClangASTNode 

15from renaissance.integrations.clang.clang_json_ast_node import ClangJsonASTNode 

16from renaissance.recipes import CleanupRefactoring 

17from renaissance.syntax_tree import ( 

18 ASTFactory, 

19 ASTNode, 

20 ASTProcessor, 

21 BatchASTProcessor, 

22 TextUtils, 

23) 

24from renaissance.syntax_tree.recipe_ast_processor import ( 

25 RecipeASTProcessor, 

26 after_step, 

27 final_action, 

28 recipe_step, 

29) 

30from renaissance.syntax_tree.semantic_kind import SemanticKind 

31 

32example_1 = textwrap.dedent(""" 

33 void x(int a) {} 

34 void x1(int a) {} 

35 void x2(int a) {} 

36 

37 void f1(int a){ 

38 int unused = 0; 

39 int unused2 = 0; //must be removed 

40 if (a==1) { 

41 int unused = 0; 

42 int unused2 = 0; //should be kept 

43 int c = unused2; 

44 x1(c); 

45 } 

46 } 

47 """) 

48 

49example_2 = textwrap.dedent(""" 

50 void x(int a) {} 

51 void x1(int a) {} 

52 void x2(int a) {} 

53 void f2(int a){ 

54 int unused = 0; 

55 if (a==1) { 

56 int unused = 0; 

57 int another_unused = 0; 

58 int used2 = 0; //should be kept 

59 int c = used2; 

60 x2(c); 

61 } 

62 } 

63 """) 

64 

65 

66# generate a simple code base provider in real life use a compilation database 

67def simple_codebase_provider() -> Iterable[tuple[ASTFactory, ASTNode]]: 

68 """AI: Yield (factory, ATU) pairs for the two example C snippets, once per Clang integration.""" 

69 for impl_type in [ClangASTNode, ClangJsonASTNode]: 

70 factory = ASTFactory(impl_type) 

71 atu1 = factory.create_from_text(example_1, impl_type.__name__ + "1.c") 

72 yield factory, atu1 

73 atu2 = factory.create_from_text(example_2, impl_type.__name__ + "2.c") 

74 yield factory, atu2 

75 

76 

77def print_results(title, batch_processor) -> None: 

78 """AI: Print the given title followed by each in-memory file's path and rewritten content.""" 

79 print(title + ":") 

80 for file, code in batch_processor.in_memory_files.items(): 

81 print(TextUtils.shift_right(file, 4) + "\n") 

82 print(TextUtils.shift_right(code, 8) + "\n") 

83 

84 

85def batch_remove_unused_variable_once_example() -> None: 

86 """Demonstrate a batch processing example using different AST node implementations. 

87 

88 It iterates over a list of AST node implementations (`ClangASTNode` and `ClangJsonASTNode`), 

89 and for each implementation, it generates a codebase provider that yields tuples of 

90 `ASTFactory` and `ASTNode` created from example source texts (`example_1` and `example_2`). 

91 The function then creates a `BatchASTProcessor` with in-memory storage enabled and processes 

92 the codebase using the `CleanupRefactoring.remove_unused_variables` refactoring operation. 

93 Finally, it prints the rewritten code stored in memory. 

94 """ 

95 # generate a batch processor for testing purposes we store into memory 

96 batch_processor = BatchASTProcessor(in_memory=True) 

97 batch_processor.once(simple_codebase_provider, CleanupRefactoring.remove_unused_variables) 

98 # print the rewritten code normally you would write to a file 

99 print_results("example batch remove unused variable once", batch_processor) 

100 

101 

102def batch_repeat_example() -> None: 

103 """Demonstrates the use of a batch processor to perform multiple refactoring operations on a codebase. 

104 

105 This example creates an in-memory batch processor and applies two refactoring operations: 

106 1. CleanupRefactoring.remove_unused_variables: Removes unused variables from the codebase. 

107 2. remove_function: Removes all function calls from the codebase. 

108 The results of the refactoring operations are printed to the console. 

109 

110 Repeat is in action here: 

111 the first time the codebase is processed, the unused variables are removed. 

112 and the function calls are removed. 

113 the second time the codebase is processed, the new unused variables are removed again. 

114 

115 Note: 

116 In a real-world scenario, the rewritten code would typically be written to a file instead of being printed. 

117 

118 """ 

119 # generate a batch processor for testing purposes we store into memory 

120 batch_processor = BatchASTProcessor(in_memory=True) 

121 

122 # remove a function to create more unused variables 

123 def remove_function(ast_processor: ASTProcessor) -> None: 

124 [ast_processor.insert_before("// ", node, False, False) for node in ast_processor.find_semantic_kind(SemanticKind.CALL)] 

125 

126 # batch_processor.repeat(simple_codebase_provider, [remove_function]) 

127 batch_processor.repeat( 

128 simple_codebase_provider, 

129 [CleanupRefactoring.remove_unused_variables, remove_function], 

130 ) 

131 # print the rewritten code normally you would write to a file 

132 print_results("example batch repeat", batch_processor) 

133 

134 

135@dataclass 

136class CallInfo: 

137 """AI: Record the callee name and call-site text for one collected function call.""" 

138 

139 callee: str 

140 calls: str 

141 

142 

143class AnalysisRecipe: 

144 """AI: Recipe that collects function-call analysis results across the processed AST.""" 

145 

146 def __init__(self) -> None: 

147 """AI: Initialize an empty recipe for collecting function-call analysis results.""" 

148 self._calls = [] 

149 

150 @recipe_step(order=0) 

151 def store_function_call(self, ast_processor: ASTProcessor) -> Callable[[], None] | None: 

152 """AI: Collect all function-call nodes found by the processor and queue them for single-threaded storage.""" 

153 # find all function calls and store them, this routing is invoked in parallel! 

154 calls: list[CallInfo] = [] 

155 [AnalysisRecipe._add_function_call(node, calls) for node in ast_processor.find_semantic_kind(SemanticKind.CALL)] 

156 # the resulting lambda is invoked single threaded 

157 # this kind of mechanism is mainly used to store results from multiple processors 

158 # for refactoring operations this is not needed as a refactoring operation is single threaded 

159 if calls: 

160 return lambda: self._calls.extend(calls) 

161 return None 

162 

163 @after_step("store_function_call") 

164 def just_show_the_method(self) -> None: 

165 """AI: Print a marker showing this hook ran after store_function_call.""" 

166 print("called after store_function_call") 

167 

168 @final_action() 

169 def final_action(self) -> None: 

170 """AI: Print all collected function calls after the recipe finishes.""" 

171 print("Calls:") 

172 for call in self._calls: 

173 print(" " + call.callee + " -- calls --> " + call.calls) 

174 

175 @staticmethod 

176 def _add_function_call(call: ASTNode, calls: list[CallInfo]) -> None: 

177 callee = call.get_ancestor("(?i)Function_?Decl") 

178 if callee: 

179 calls.append(CallInfo(callee.name, call.children[0].name)) 

180 

181 

182def batch_recipe_example() -> None: 

183 """AI: Run the analysis recipe over the example codebase and print discovered calls.""" 

184 print("example batch analysis using recipe:\n") 

185 recipe_ast_processor = RecipeASTProcessor(AnalysisRecipe(), simple_codebase_provider, r".*", in_memory=True) 

186 recipe_ast_processor.run() 

187 

188 

189if __name__ == "__main__": 

190 # a list of example to show batch processing of a code base 

191 batch_remove_unused_variable_once_example() 

192 batch_repeat_example() 

193 batch_recipe_example()