codegraph_c/
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 CParser {
17    config: ParserConfig,
18    metrics: Mutex<ParserMetrics>,
19}
20
21impl CParser {
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 CParser {
66    fn default() -> Self {
67        Self::new()
68    }
69}
70
71impl CodeParser for CParser {
72    fn language(&self) -> &str {
73        "c"
74    }
75
76    fn file_extensions(&self) -> &[&str] {
77        &[".c", ".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        // Use tolerant mode with kernel code preprocessing for robust parsing
115        // This handles kernel macros, GCC extensions, and partial syntax errors
116        let options = extractor::ExtractionOptions::for_kernel_code();
117        let result = extractor::extract_with_options(source, file_path, &self.config, &options)?;
118
119        let mut file_info = self.ir_to_graph(&result.ir, graph, file_path)?;
120
121        file_info.parse_time = start.elapsed();
122        file_info.line_count = source.lines().count();
123        file_info.byte_count = source.len();
124
125        Ok(file_info)
126    }
127
128    fn config(&self) -> &ParserConfig {
129        &self.config
130    }
131
132    fn metrics(&self) -> ParserMetrics {
133        self.metrics.lock().unwrap().clone()
134    }
135
136    fn reset_metrics(&mut self) {
137        *self.metrics.lock().unwrap() = ParserMetrics::default();
138    }
139
140    fn parse_files(
141        &self,
142        paths: &[PathBuf],
143        graph: &mut CodeGraph,
144    ) -> Result<ProjectInfo, ParserError> {
145        if self.config.parallel {
146            self.parse_files_parallel(paths, graph)
147        } else {
148            self.parse_files_sequential(paths, graph)
149        }
150    }
151}
152
153impl CParser {
154    /// Parse files sequentially
155    fn parse_files_sequential(
156        &self,
157        paths: &[PathBuf],
158        graph: &mut CodeGraph,
159    ) -> Result<ProjectInfo, ParserError> {
160        let mut files = Vec::new();
161        let mut failed_files = Vec::new();
162        let mut total_functions = 0;
163        let mut total_classes = 0;
164        let mut total_parse_time = Duration::ZERO;
165
166        for path in paths {
167            match self.parse_file(path, graph) {
168                Ok(info) => {
169                    total_functions += info.functions.len();
170                    total_classes += info.classes.len();
171                    total_parse_time += info.parse_time;
172                    files.push(info);
173                }
174                Err(e) => {
175                    failed_files.push((path.clone(), e.to_string()));
176                }
177            }
178        }
179
180        Ok(ProjectInfo {
181            files,
182            failed_files,
183            total_functions,
184            total_classes,
185            total_parse_time,
186        })
187    }
188
189    /// Parse files in parallel using rayon
190    fn parse_files_parallel(
191        &self,
192        paths: &[PathBuf],
193        graph: &mut CodeGraph,
194    ) -> Result<ProjectInfo, ParserError> {
195        use rayon::prelude::*;
196
197        let graph_mutex = Mutex::new(graph);
198
199        // Configure thread pool if parallel_workers is specified
200        let pool = if let Some(num_threads) = self.config.parallel_workers {
201            rayon::ThreadPoolBuilder::new()
202                .num_threads(num_threads)
203                .build()
204                .map_err(|e| {
205                    ParserError::GraphError(format!("Failed to create thread pool: {e}"))
206                })?
207        } else {
208            rayon::ThreadPoolBuilder::new().build().map_err(|e| {
209                ParserError::GraphError(format!("Failed to create thread pool: {e}"))
210            })?
211        };
212
213        let results: Vec<_> = pool.install(|| {
214            paths
215                .par_iter()
216                .map(|path| {
217                    let mut graph = graph_mutex.lock().unwrap();
218                    match self.parse_file(path, &mut graph) {
219                        Ok(info) => Ok(info),
220                        Err(e) => Err((path.clone(), e.to_string())),
221                    }
222                })
223                .collect()
224        });
225
226        let mut files = Vec::new();
227        let mut failed_files = Vec::new();
228        let mut total_functions = 0;
229        let mut total_classes = 0;
230        let mut total_parse_time = Duration::ZERO;
231
232        for result in results {
233            match result {
234                Ok(info) => {
235                    total_functions += info.functions.len();
236                    total_classes += info.classes.len();
237                    total_parse_time += info.parse_time;
238                    files.push(info);
239                }
240                Err((path, error)) => {
241                    failed_files.push((path, error));
242                }
243            }
244        }
245
246        Ok(ProjectInfo {
247            files,
248            failed_files,
249            total_functions,
250            total_classes,
251            total_parse_time,
252        })
253    }
254}
255
256#[cfg(test)]
257mod tests {
258    use super::*;
259
260    #[test]
261    fn test_language() {
262        let parser = CParser::new();
263        assert_eq!(parser.language(), "c");
264    }
265
266    #[test]
267    fn test_file_extensions() {
268        let parser = CParser::new();
269        assert_eq!(parser.file_extensions(), &[".c", ".h"]);
270    }
271
272    #[test]
273    fn test_can_parse() {
274        let parser = CParser::new();
275        assert!(parser.can_parse(Path::new("main.c")));
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.cpp")));
279    }
280}