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