1use 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
15pub 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}