1use clang::{Clang, Entity, EntityKind, Index};
2use std::path::Path;
3
4pub mod ast_visitor;
5pub mod annotations;
6pub mod header_cache;
7pub mod safety_annotations;
8pub mod template_context;
9pub mod type_annotations;
10pub mod external_annotations;
11
12pub use ast_visitor::{CppAst, Function, Statement, Expression, MethodQualifier, MoveKind};
13pub use header_cache::HeaderCache;
14pub use template_context::TemplateContext;
15#[allow(unused_imports)]
16pub use ast_visitor::{Variable, SourceLocation};
17
18use std::fs;
19use std::io::{BufRead, BufReader};
20
21#[allow(dead_code)]
22pub fn parse_cpp_file(path: &Path) -> Result<CppAst, String> {
23 parse_cpp_file_with_includes(path, &[])
24}
25
26#[allow(dead_code)]
27pub fn parse_cpp_file_with_includes(path: &Path, include_paths: &[std::path::PathBuf]) -> Result<CppAst, String> {
28 parse_cpp_file_with_includes_and_defines(path, include_paths, &[])
29}
30
31pub fn parse_cpp_file_with_includes_and_defines(path: &Path, include_paths: &[std::path::PathBuf], defines: &[String]) -> Result<CppAst, String> {
32 let clang = Clang::new()
34 .map_err(|e| format!("Failed to initialize Clang: {:?}", e))?;
35
36 let index = Index::new(&clang, false, false);
37
38 let mut args = vec![
40 "-std=c++20".to_string(),
41 "-xc++".to_string(),
42 "-fno-delayed-template-parsing".to_string(),
44 "-fparse-all-comments".to_string(),
45 "-Wno-everything".to_string(),
47 "-Wno-error".to_string(),
49 ];
50
51 for include_path in include_paths {
53 args.push(format!("-I{}", include_path.display()));
54 }
55
56 for define in defines {
58 args.push(format!("-D{}", define));
59 }
60
61 let tu = index
63 .parser(path)
64 .arguments(&args.iter().map(|s| s.as_str()).collect::<Vec<_>>())
65 .detailed_preprocessing_record(true)
66 .skip_function_bodies(false) .incomplete(true) .parse()
69 .map_err(|e| format!("Failed to parse file: {:?}", e))?;
70
71 let diagnostics = tu.get_diagnostics();
73 let mut has_fatal = false;
74 if !diagnostics.is_empty() {
75 for diag in &diagnostics {
76 if diag.get_severity() >= clang::diagnostic::Severity::Fatal {
78 has_fatal = true;
79 eprintln!("Fatal error: {}", diag.get_text());
80 } else if diag.get_severity() >= clang::diagnostic::Severity::Error {
81 eprintln!("Warning (suppressed error): {}", diag.get_text());
83 }
84 }
85 }
86
87 if has_fatal {
88 return Err("Fatal parsing errors encountered".to_string());
89 }
90
91 let mut ast = CppAst::new();
93 let root = tu.get_entity();
94 let main_file_path = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
95 visit_entity(&root, &mut ast, &main_file_path);
96
97 Ok(ast)
98}
99
100fn visit_entity(entity: &Entity, ast: &mut CppAst, main_file: &Path) {
101 use crate::debug_println;
102 if matches!(entity.get_kind(), EntityKind::FunctionDecl | EntityKind::Method | EntityKind::FunctionTemplate | EntityKind::ClassTemplate) {
104 let template_kind = entity.get_template_kind();
106 let template_args = entity.get_template_arguments();
107 debug_println!("DEBUG PARSE: Visiting entity: kind={:?}, name={:?}, is_definition={}, template_kind={:?}, has_template_args={}",
108 entity.get_kind(), entity.get_name(), entity.is_definition(), template_kind, template_args.is_some());
109 }
110
111 let _main_file = main_file; match entity.get_kind() {
118 EntityKind::FunctionDecl | EntityKind::Method => {
119 debug_println!("DEBUG PARSE: Found FunctionDecl: name={:?}, is_definition={}, kind={:?}",
120 entity.get_name(), entity.is_definition(), entity.get_kind());
121 if entity.is_definition() {
123 let func = ast_visitor::extract_function(entity);
124 ast.functions.push(func);
125 }
126 }
127 EntityKind::FunctionTemplate => {
128 debug_println!("DEBUG PARSE: Found FunctionTemplate: {:?}, is_definition={}",
131 entity.get_name(), entity.is_definition());
132
133 if entity.is_definition() {
137 debug_println!("DEBUG PARSE: Extracting template function from FunctionTemplate entity");
138 let func = ast_visitor::extract_function(entity);
139 debug_println!("DEBUG PARSE: Extracted template function: {}", func.name);
140 ast.functions.push(func);
141 }
142 }
143 EntityKind::ClassTemplate => {
144 debug_println!("DEBUG PARSE: Found ClassTemplate: {:?}, is_definition={}",
146 entity.get_name(), entity.is_definition());
147
148 if entity.is_definition() {
152 debug_println!("DEBUG PARSE: Extracting template class from ClassTemplate entity");
153 let class = ast_visitor::extract_class(entity);
154 debug_println!("DEBUG PARSE: Extracted template class: {}", class.name);
155 ast.classes.push(class);
156 }
157 }
158 EntityKind::ClassDecl | EntityKind::StructDecl => {
159 debug_println!("DEBUG PARSE: Found ClassDecl/StructDecl: {:?}, is_definition={}",
161 entity.get_name(), entity.is_definition());
162
163 if entity.is_definition() {
164 debug_println!("DEBUG PARSE: Extracting regular class from ClassDecl entity");
165 let class = ast_visitor::extract_class(entity);
166 debug_println!("DEBUG PARSE: Extracted class: {}", class.name);
167 ast.classes.push(class);
168 }
169 }
170 EntityKind::CallExpr => {
171 }
175 EntityKind::VarDecl => {
176 let var = ast_visitor::extract_variable(entity);
178 ast.global_variables.push(var);
179 }
180 _ => {}
181 }
182
183 for child in entity.get_children() {
185 visit_entity(&child, ast, main_file);
186 }
187}
188
189#[allow(dead_code)]
191pub fn check_file_safety_annotation(path: &Path) -> Result<bool, String> {
192 let file = fs::File::open(path)
193 .map_err(|e| format!("Failed to open file for safety check: {}", e))?;
194
195 let reader = BufReader::new(file);
196
197 for (line_num, line_result) in reader.lines().enumerate() {
199 if line_num > 20 {
200 break; }
202
203 let line = line_result.map_err(|e| format!("Failed to read line: {}", e))?;
204 let trimmed = line.trim();
205
206 if trimmed.is_empty() {
208 continue;
209 }
210
211 if trimmed.starts_with("//") {
213 if trimmed.contains("@safe") {
214 return Ok(true);
215 }
216 } else if trimmed.starts_with("/*") {
217 if line.contains("@safe") {
219 return Ok(true);
220 }
221 } else if !trimmed.starts_with("#") {
222 break;
224 }
225 }
226
227 Ok(false) }
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233 use tempfile::NamedTempFile;
234 use std::io::Write;
235
236 fn create_temp_cpp_file(content: &str) -> NamedTempFile {
237 let mut file = NamedTempFile::with_suffix(".cpp").unwrap();
238 file.write_all(content.as_bytes()).unwrap();
239 file.flush().unwrap();
240 file
241 }
242
243 #[allow(dead_code)]
244 fn is_libclang_available() -> bool {
245 true }
249
250 #[test]
251 fn test_parse_simple_function() {
252 let code = r#"
253 void test_function() {
254 int x = 42;
255 }
256 "#;
257
258 let temp_file = create_temp_cpp_file(code);
259 let result = parse_cpp_file(temp_file.path());
260
261 match result {
262 Ok(ast) => {
263 assert_eq!(ast.functions.len(), 1);
264 assert_eq!(ast.functions[0].name, "test_function");
265 }
266 Err(e) if e.contains("already exists") => {
267 eprintln!("Skipping test: Clang already initialized by another test");
269 }
270 Err(e) if e.contains("Failed to initialize Clang") => {
271 eprintln!("Skipping test: libclang not available");
273 }
274 Err(e) => {
275 panic!("Unexpected error: {}", e);
276 }
277 }
278 }
279
280 #[test]
281 fn test_parse_function_with_parameters() {
282 let code = r#"
283 int add(int a, int b) {
284 return a + b;
285 }
286 "#;
287
288 let temp_file = create_temp_cpp_file(code);
289 let result = parse_cpp_file(temp_file.path());
290
291 match result {
292 Ok(ast) => {
293 assert_eq!(ast.functions.len(), 1);
294 assert_eq!(ast.functions[0].name, "add");
295 assert_eq!(ast.functions[0].parameters.len(), 2);
296 }
297 Err(e) if e.contains("already exists") => {
298 eprintln!("Skipping test: Clang already initialized by another test");
299 }
300 Err(e) if e.contains("Failed to initialize Clang") => {
301 eprintln!("Skipping test: libclang not available");
302 }
303 Err(e) => {
304 panic!("Unexpected error: {}", e);
305 }
306 }
307 }
308
309 #[test]
310 fn test_parse_global_variable() {
311 let code = r#"
312 int global_var = 100;
313
314 void func() {}
315 "#;
316
317 let temp_file = create_temp_cpp_file(code);
318 let result = parse_cpp_file(temp_file.path());
319
320 match result {
321 Ok(ast) => {
322 assert_eq!(ast.global_variables.len(), 1);
323 assert_eq!(ast.global_variables[0].name, "global_var");
324 }
325 Err(e) if e.contains("already exists") => {
326 eprintln!("Skipping test: Clang already initialized by another test");
327 }
328 Err(e) if e.contains("Failed to initialize Clang") => {
329 eprintln!("Skipping test: libclang not available");
330 }
331 Err(e) => {
332 panic!("Unexpected error: {}", e);
333 }
334 }
335 }
336
337 #[test]
338 fn test_parse_invalid_file() {
339 let temp_dir = tempfile::tempdir().unwrap();
340 let invalid_path = temp_dir.path().join("nonexistent.cpp");
341
342 let result = parse_cpp_file(&invalid_path);
343 assert!(result.is_err());
344 }
345}