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