codegraph_cpp/
parser_impl.rs

1//! Implementation of the CodeParser trait for C++
2
3use codegraph::CodeGraph;
4use codegraph_parser_api::{
5    CodeIR, CodeParser, FileInfo, ParserConfig, ParserError, ParserMetrics, ProjectInfo,
6};
7use std::fs;
8use std::path::{Path, PathBuf};
9use std::sync::Mutex;
10use std::time::{Duration, Instant};
11
12use crate::extractor;
13use crate::mapper;
14
15/// C++ language parser implementing the CodeParser trait
16pub struct CppParser {
17    config: ParserConfig,
18    metrics: Mutex<ParserMetrics>,
19}
20
21impl CppParser {
22    pub fn new() -> Self {
23        Self {
24            config: ParserConfig::default(),
25            metrics: Mutex::new(ParserMetrics::default()),
26        }
27    }
28
29    pub fn with_config(config: ParserConfig) -> Self {
30        Self {
31            config,
32            metrics: Mutex::new(ParserMetrics::default()),
33        }
34    }
35
36    fn update_metrics(
37        &self,
38        success: bool,
39        duration: Duration,
40        entities: usize,
41        relationships: usize,
42    ) {
43        let mut metrics = self.metrics.lock().unwrap();
44        metrics.files_attempted += 1;
45        if success {
46            metrics.files_succeeded += 1;
47        } else {
48            metrics.files_failed += 1;
49        }
50        metrics.total_parse_time += duration;
51        metrics.total_entities += entities;
52        metrics.total_relationships += relationships;
53    }
54
55    fn ir_to_graph(
56        &self,
57        ir: &CodeIR,
58        graph: &mut CodeGraph,
59        file_path: &Path,
60    ) -> Result<FileInfo, ParserError> {
61        mapper::ir_to_graph(ir, graph, file_path)
62    }
63}
64
65impl Default for CppParser {
66    fn default() -> Self {
67        Self::new()
68    }
69}
70
71impl CodeParser for CppParser {
72    fn language(&self) -> &str {
73        "cpp"
74    }
75
76    fn file_extensions(&self) -> &[&str] {
77        &[".cpp", ".cc", ".cxx", ".hpp", ".hh", ".hxx", ".h"]
78    }
79
80    fn parse_file(&self, path: &Path, graph: &mut CodeGraph) -> Result<FileInfo, ParserError> {
81        let start = Instant::now();
82        let metadata =
83            fs::metadata(path).map_err(|e| ParserError::IoError(path.to_path_buf(), e))?;
84
85        if metadata.len() as usize > self.config.max_file_size {
86            return Err(ParserError::FileTooLarge(
87                path.to_path_buf(),
88                metadata.len() as usize,
89            ));
90        }
91
92        let source =
93            fs::read_to_string(path).map_err(|e| ParserError::IoError(path.to_path_buf(), e))?;
94        let result = self.parse_source(&source, path, graph);
95
96        let duration = start.elapsed();
97        if let Ok(ref info) = result {
98            self.update_metrics(true, duration, info.entity_count(), 0);
99        } else {
100            self.update_metrics(false, duration, 0, 0);
101        }
102
103        result
104    }
105
106    fn parse_source(
107        &self,
108        source: &str,
109        file_path: &Path,
110        graph: &mut CodeGraph,
111    ) -> Result<FileInfo, ParserError> {
112        let start = Instant::now();
113
114        let ir = extractor::extract(source, file_path, &self.config)?;
115
116        let mut file_info = self.ir_to_graph(&ir, graph, file_path)?;
117
118        file_info.parse_time = start.elapsed();
119        file_info.line_count = source.lines().count();
120        file_info.byte_count = source.len();
121
122        Ok(file_info)
123    }
124
125    fn config(&self) -> &ParserConfig {
126        &self.config
127    }
128
129    fn metrics(&self) -> ParserMetrics {
130        self.metrics.lock().unwrap().clone()
131    }
132
133    fn reset_metrics(&mut self) {
134        *self.metrics.lock().unwrap() = ParserMetrics::default();
135    }
136
137    fn parse_files(
138        &self,
139        paths: &[PathBuf],
140        graph: &mut CodeGraph,
141    ) -> Result<ProjectInfo, ParserError> {
142        if self.config.parallel {
143            self.parse_files_parallel(paths, graph)
144        } else {
145            self.parse_files_sequential(paths, graph)
146        }
147    }
148}
149
150impl CppParser {
151    fn parse_files_sequential(
152        &self,
153        paths: &[PathBuf],
154        graph: &mut CodeGraph,
155    ) -> Result<ProjectInfo, ParserError> {
156        let mut files = Vec::new();
157        let mut failed_files = Vec::new();
158        let mut total_functions = 0;
159        let mut total_classes = 0;
160        let mut total_parse_time = Duration::ZERO;
161
162        for path in paths {
163            match self.parse_file(path, graph) {
164                Ok(info) => {
165                    total_functions += info.functions.len();
166                    total_classes += info.classes.len();
167                    total_parse_time += info.parse_time;
168                    files.push(info);
169                }
170                Err(e) => {
171                    failed_files.push((path.clone(), e.to_string()));
172                }
173            }
174        }
175
176        Ok(ProjectInfo {
177            files,
178            failed_files,
179            total_functions,
180            total_classes,
181            total_parse_time,
182        })
183    }
184
185    fn parse_files_parallel(
186        &self,
187        paths: &[PathBuf],
188        graph: &mut CodeGraph,
189    ) -> Result<ProjectInfo, ParserError> {
190        use rayon::prelude::*;
191
192        let graph_mutex = Mutex::new(graph);
193
194        let pool = if let Some(num_threads) = self.config.parallel_workers {
195            rayon::ThreadPoolBuilder::new()
196                .num_threads(num_threads)
197                .build()
198                .map_err(|e| {
199                    ParserError::GraphError(format!("Failed to create thread pool: {e}"))
200                })?
201        } else {
202            rayon::ThreadPoolBuilder::new().build().map_err(|e| {
203                ParserError::GraphError(format!("Failed to create thread pool: {e}"))
204            })?
205        };
206
207        let results: Vec<_> = pool.install(|| {
208            paths
209                .par_iter()
210                .map(|path| {
211                    let mut graph = graph_mutex.lock().unwrap();
212                    match self.parse_file(path, &mut graph) {
213                        Ok(info) => Ok(info),
214                        Err(e) => Err((path.clone(), e.to_string())),
215                    }
216                })
217                .collect()
218        });
219
220        let mut files = Vec::new();
221        let mut failed_files = Vec::new();
222        let mut total_functions = 0;
223        let mut total_classes = 0;
224        let mut total_parse_time = Duration::ZERO;
225
226        for result in results {
227            match result {
228                Ok(info) => {
229                    total_functions += info.functions.len();
230                    total_classes += info.classes.len();
231                    total_parse_time += info.parse_time;
232                    files.push(info);
233                }
234                Err((path, error)) => {
235                    failed_files.push((path, error));
236                }
237            }
238        }
239
240        Ok(ProjectInfo {
241            files,
242            failed_files,
243            total_functions,
244            total_classes,
245            total_parse_time,
246        })
247    }
248}
249
250#[cfg(test)]
251mod tests {
252    use super::*;
253
254    #[test]
255    fn test_language() {
256        let parser = CppParser::new();
257        assert_eq!(parser.language(), "cpp");
258    }
259
260    #[test]
261    fn test_file_extensions() {
262        let parser = CppParser::new();
263        let extensions = parser.file_extensions();
264        assert!(extensions.contains(&".cpp"));
265        assert!(extensions.contains(&".hpp"));
266        assert!(extensions.contains(&".cc"));
267        assert!(extensions.contains(&".h"));
268    }
269
270    #[test]
271    fn test_can_parse() {
272        let parser = CppParser::new();
273        assert!(parser.can_parse(Path::new("main.cpp")));
274        assert!(parser.can_parse(Path::new("header.hpp")));
275        assert!(parser.can_parse(Path::new("source.cc")));
276        assert!(parser.can_parse(Path::new("header.h")));
277        assert!(!parser.can_parse(Path::new("main.rs")));
278        assert!(!parser.can_parse(Path::new("main.java")));
279    }
280}