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 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 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 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 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 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}