Coverage for src / renaissance / integrations / clang / c_pattern_factory.py: 72%
136 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-09-09 14:04 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-09-09 14:04 +0000
1import re
2from collections.abc import Sequence
4from more_itertools import first
5from more_itertools.more import last
7from renaissance.integrations.clang.cpp_utils import CPPUtils
8from renaissance.integrations.types import (
9 Call,
10 CompoundStatement,
11 Declaration,
12 FunctionDef,
13 InclusionDirective,
14 MacroDef,
15 ParenthesizedExpression,
16 Type,
17 TypedefDef,
18 VariableDef,
19)
20from renaissance.syntax_tree.ast_factory import ASTFactory
21from renaissance.syntax_tree.ast_finder import find_ast_type
22from renaissance.syntax_tree.ast_node import ASTNode
23from renaissance.syntax_tree.ast_shower import ASTShower
25SHOW_NODE = False
28def derive_header_text(language: str, ref_node: ASTNode | None):
29 # collect includes #defines and var decl from the refNode
30 header = "\n"
31 if ref_node:
32 language = ref_node.filename.split(".")[-1]
33 offset = min(
34 (n.offset for n in ref_node.children if n.is_part_of_translation_unit() and n.ast_type == InclusionDirective),
35 default=0,
36 )
38 header = CPatternFactory.remove_indent(ref_node.content(0, offset))
39 header += "\n".join(
40 n.text + ";"
41 for n in ref_node.children
42 if n.is_part_of_translation_unit()
43 and isinstance(n.ast_type(), (FunctionDef, VariableDef | TypedefDef, MacroDef))
44 and len(find_ast_type(n, CompoundStatement)) == 0
45 )
46 # and isinstance(n.ast_type, (Declaration, MacroDefinition))
47 # and len(find_ast_type(n, CompoundStatement)) == 0
48 header += "\n"
50 return header, language
53class CPatternFactory:
54 reserved_function_name = "__rejuvenation__reserved__function__name__"
55 reserved_variable_name = "__rejuvenation__reserved__variable__name__"
57 def __init__(
58 self,
59 factory: ASTFactory,
60 ref_node: ASTNode | None = None,
61 language: str = "c",
62 ):
63 self.factory = factory
64 self.header, self.language = derive_header_text(language, ref_node)
66 @staticmethod
67 def remove_indent(text: str) -> str:
68 split = [len(line) - len(line.lstrip()) for line in text.splitlines() if line.strip()]
69 indent = split[0] if split else 0
70 return "\n".join([line[indent:] for line in text.splitlines()])
72 def create_expression(self, text: str, extra_declarations=None) -> ASTNode:
73 if extra_declarations is None:
74 extra_declarations = []
75 keywords = CPatternFactory._get_keywords_from_text(text)
76 keywords = [k for k in keywords if not any(k in ed for ed in extra_declarations)]
77 full_text = (
78 self.header
79 + "\n".join(extra_declarations)
80 + "\n"
81 + "\n".join(CPatternFactory._to_declaration(keywords))
82 + f"\nvoid {CPatternFactory.reserved_function_name}() {{ int {CPatternFactory.reserved_variable_name} = ({text}); }}"
83 )
84 root = self._create(full_text)
85 # return the first expression found in the tree as a ASTNode
86 return last(n.children[0] for n in find_ast_type(root.children[-1], ParenthesizedExpression) if n.is_part_of_translation_unit)
88 def create_declarations(
89 self,
90 text: str,
91 types=None,
92 parameters=None,
93 extra_declarations=None,
94 declarations=None,
95 ):
96 if declarations is None:
97 declarations = []
98 if extra_declarations is None:
99 extra_declarations = []
100 if parameters is None:
101 parameters = []
102 if types is None:
103 types = []
104 keywords = CPatternFactory._get_keywords_from_text(text)
105 keywords = [
106 k
107 for k in keywords
108 if not any(k in ed for ed in extra_declarations)
109 and not any(k in ed for ed in parameters)
110 and not any(k in ed for ed in types)
111 and not any(k in ed for ed in declarations)
112 ]
113 return self._create_body(text, types, [*parameters, *keywords], extra_declarations, Declaration)
115 def create_declaration(
116 self,
117 text: str,
118 types=None,
119 parameters=None,
120 extra_declarations=None,
121 declarations=None,
122 ) -> ASTNode:
123 if declarations is None:
124 declarations = []
125 if extra_declarations is None:
126 extra_declarations = []
127 if parameters is None:
128 parameters = []
129 if types is None:
130 types = []
131 result = self.create_declarations(text, types, parameters, extra_declarations, declarations)
132 assert len(result) > 0, "At least one declaration is expected"
133 return result[0]
135 def create_statements(
136 self,
137 text: str,
138 types=None,
139 extra_declarations=None,
140 kind: type[Type] = Type,
141 ) -> Sequence[ASTNode]:
142 # create a reference for all used variables excluding the specified types
143 if extra_declarations is None:
144 extra_declarations = []
145 if types is None:
146 types = []
147 parameters = [
148 par
149 for par in CPatternFactory._get_keywords_from_text(text)
150 if par not in types and not any(par in ed for ed in extra_declarations)
151 ]
152 return self._create_body(text, types, parameters, extra_declarations, kind)
154 def create(self, text: str, kind: type[Type] = None) -> ASTNode:
155 """Creates an object using the factory from the provided text.
156 The object is created by the factory using the provided text and the header of the provided reference node.
157 It is up to the user to pick the right node for pattern matching.
159 Args:
160 text (str): The input text used to create the object.
161 kind (str, optional): The kind of the node to be returned. Defaults to None.
163 Returns:
164 object: The object created by the factory.
166 """
167 # print(self.header + text)
168 root = self.factory.create_from_text(self.header + text, "test." + self.language)
169 if kind:
170 return first(find_ast_type(root.children[-1], kind))
171 return root
173 def create_statement(
174 self,
175 text: str,
176 types=None,
177 extra_declarations=None,
178 kind: str = Type,
179 ) -> ASTNode:
180 if extra_declarations is None:
181 extra_declarations = []
182 if types is None:
183 types = []
184 statements = list(self.create_statements(text, types, extra_declarations, kind))
185 assert len(statements) == 1, "Only one statement is expected"
186 return statements[0]
188 def _create_body(
189 self,
190 text: str,
191 types: Sequence[str],
192 parameters: Sequence[str],
193 extra_declarations: Sequence[str],
194 kind: type[Type],
195 ) -> list[ASTNode]:
196 full_text = (
197 self.header
198 + "\n".join(CPatternFactory._to_typedef(types))
199 + "\n\n".join(CPatternFactory._to_declaration(parameters))
200 + "\n\n".join(extra_declarations)
201 + "\n"
202 "\nvoid " + CPatternFactory.reserved_function_name + "(){\n" + text + "\n}"
203 )
204 root = self._create(full_text)
206 # from the children of the compound statement that contains the text, get for each child the first
207 # node of the specified kind
209 body = first(find_ast_type(root.children[-1], CompoundStatement)).children
210 return list(n for n in body if n.is_part_of_translation_unit and first(find_ast_type(n, kind)))
212 def _create(self, text: str) -> ASTNode:
213 atu = self.factory.create_from_text(text, "test." + self.language)
214 if SHOW_NODE:
215 ASTShower.show_node(atu)
216 return atu
218 @staticmethod
219 def _get_keywords_from_text(text: str) -> Sequence[str]:
220 # regex to get keywords that start with one of two dollars followed by a \\w+
221 pattern = re.compile(r"\${0,2}[a-zA-Z]\w*")
222 return list(k for k in set(re.findall(pattern, text)) if k not in CPPUtils.RESERVED_KEYWORDS)
224 @staticmethod
225 def _get_dollar_keywords_from_text(text: str) -> Sequence[str]:
226 # regex to get keywords that start with one of two dollars followed by a \\w+
227 pattern = re.compile(r"\${1,2}[a-zA-Z]\w*")
228 return list(set(re.findall(pattern, text)))
230 @staticmethod
231 def _get_non_dollar_keywords_from_text(text: str) -> Sequence[str]:
232 pattern = re.compile(r"[^$][a-zA-Z]\w*")
233 return list(set(re.findall(pattern, text)))
235 @staticmethod
236 def _to_declaration(keywords: Sequence[str], prefix: str = "int ", postfix: str = ";") -> Sequence[str]:
237 return [prefix + keyword + postfix for keyword in keywords]
239 @staticmethod
240 def _to_typedef(keywords: Sequence[str], prefix: str = "typedef int ", postfix: str = ";") -> Sequence[str]:
241 return [prefix + keyword + postfix for keyword in keywords]
244class CPPPatternFactory(CPatternFactory):
245 def __init__(self, factory: ASTFactory, ref_node: ASTNode | None = None):
246 super().__init__(factory, ref_node, "cpp")
248 def create_constructor_call(self, pattern: str):
249 class_and_args = re.match(R"([$\w]+)\(([^)]+)\)", pattern.replace(" ", ""))
250 if class_and_args:
251 class_name = class_and_args.group(1)
252 args = class_and_args.group(2).split(",")
253 return self._create_constructor_call(class_name, args)
254 return None
256 def _create_constructor_call(self, class_name: str, args=None):
257 if args is None:
258 args = []
259 arg_call_string = ",".join(args)
260 arg_decl_string = ",".join("int " + arg for arg in args)
261 code = f"""
262 class {class_name}{{
263 public:
264 {class_name}({arg_decl_string}) {{}}
265 }};
266 class derived : public {class_name}{{
267 public:
268 derived({arg_decl_string}) : {class_name}({arg_call_string}) {{ }}
269 }};
270 """
271 root: ASTNode = self.factory.create_from_text(code, "test." + self.language)
272 target_class = root.children[-1]
273 # this should yield something like:
274 # (TYPE_REF, $var, test.cpp[237:241]): |$var|
275 # (CALL_EXPR, , test.cpp[237:266]): |$var($container,$headerCount)|
276 # (DECL_REF_EXPR, $container, test.cpp[242:252]): |$container|
277 # (DECL_REF_EXPR, $headerCount, test.cpp[253:265]): |$headerCount|
278 if SHOW_NODE:
279 ASTShower.show_node(target_class)
280 # search the call expr and the preceding type ref
281 call_expr = last(find_ast_type(target_class, Call))
282 # include the preceding type ref
283 assert isinstance(call_expr, ASTNode), "No call expression found"
284 type_ref = call_expr.preceding_sibling
285 assert isinstance(type_ref, ASTNode), "No type ref found"
286 # return the constrained pattern where the first node must be of type TypeRef
288 return call_expr