Skip to main content

ryo_source/pure/
parallel.rs

1//! Parallel processing for PureFile.
2//!
3//! PureFile is `Send + Sync`, enabling true parallel processing:
4//!
5//! - Share with `Arc<PureFile>` across threads
6//! - Use rayon parallel iterators
7//! - Parallel analysis of multiple files
8//! - COW mutations across threads
9//!
10//! # Examples
11//!
12//! ```ignore
13//! use std::sync::Arc;
14//! use ryo_source::pure::{PureFile, PureParallel};
15//!
16//! // Parse multiple files
17//! let sources = vec!["fn a() {}", "fn b() {}", "fn c() {}"];
18//! let files: Vec<PureFile> = PureParallel::parse_all(&sources);
19//!
20//! // Analyze all in parallel
21//! let fn_counts: Vec<usize> = PureParallel::analyze_all(&files, |f| f.functions().len());
22//! ```
23
24use super::analysis::{PureDefRefs, PureSymbolTable};
25use super::ast::PureFile;
26use super::rename::{PureRename, PureRenameResult};
27
28/// Parallel processing utilities for PureFile.
29///
30/// All operations leverage PureFile's thread-safety for true parallelism.
31pub struct PureParallel;
32
33impl PureParallel {
34    /// Parse multiple sources in parallel.
35    ///
36    /// Uses rayon to parse sources concurrently.
37    /// Returns successfully parsed files (errors are filtered out).
38    #[cfg(feature = "parallel")]
39    pub fn parse_all(sources: &[&str]) -> Vec<PureFile> {
40        use rayon::prelude::*;
41
42        sources
43            .par_iter()
44            .filter_map(|src| PureFile::from_source(src).ok())
45            .collect()
46    }
47
48    /// Parse multiple sources, returning results for all.
49    #[cfg(feature = "parallel")]
50    pub fn try_parse_all(sources: &[&str]) -> Vec<Result<PureFile, crate::SourceError>> {
51        use rayon::prelude::*;
52
53        sources
54            .par_iter()
55            .map(|src| PureFile::from_source(src))
56            .collect()
57    }
58
59    /// Analyze multiple files in parallel.
60    ///
61    /// Applies the analyzer function to each file concurrently.
62    #[cfg(feature = "parallel")]
63    pub fn analyze_all<F, T>(files: &[PureFile], analyzer: F) -> Vec<T>
64    where
65        F: Fn(&PureFile) -> T + Sync,
66        T: Send,
67    {
68        use rayon::prelude::*;
69
70        files.par_iter().map(|f| analyzer(f)).collect()
71    }
72
73    /// Analyze shared files in parallel.
74    ///
75    /// Uses Arc for zero-copy sharing across threads.
76    #[cfg(feature = "parallel")]
77    pub fn analyze_shared<F, T>(files: &[std::sync::Arc<PureFile>], analyzer: F) -> Vec<T>
78    where
79        F: Fn(&PureFile) -> T + Sync,
80        T: Send,
81    {
82        use rayon::prelude::*;
83
84        files.par_iter().map(|f| analyzer(f)).collect()
85    }
86
87    /// Build symbol tables for all files in parallel.
88    #[cfg(feature = "parallel")]
89    pub fn build_symbol_tables(files: &[PureFile]) -> Vec<PureSymbolTable> {
90        use rayon::prelude::*;
91
92        files.par_iter().map(|f| PureDefRefs::analyze(f)).collect()
93    }
94
95    /// Apply rename to multiple files in parallel (COW pattern).
96    ///
97    /// Each file is cloned and renamed independently.
98    #[cfg(feature = "parallel")]
99    pub fn rename_all(
100        files: &[PureFile],
101        old_name: &str,
102        new_name: &str,
103    ) -> Vec<(PureFile, PureRenameResult)> {
104        use rayon::prelude::*;
105
106        files
107            .par_iter()
108            .map(|f| PureRename::apply_cow(f, old_name, new_name))
109            .collect()
110    }
111
112    /// Apply different renames to each file in parallel.
113    ///
114    /// Each (file, old, new) tuple is processed independently.
115    #[cfg(feature = "parallel")]
116    pub fn rename_each<'a>(
117        tasks: &[(&'a PureFile, &'a str, &'a str)],
118    ) -> Vec<(PureFile, PureRenameResult)> {
119        use rayon::prelude::*;
120
121        tasks
122            .par_iter()
123            .map(|(f, old, new)| PureRename::apply_cow(f, old, new))
124            .collect()
125    }
126
127    /// Transform files in parallel with custom transformer.
128    ///
129    /// The transformer receives a mutable clone of each file.
130    #[cfg(feature = "parallel")]
131    pub fn transform_all<F, T>(files: &[PureFile], transformer: F) -> Vec<(PureFile, T)>
132    where
133        F: Fn(&mut PureFile) -> T + Sync,
134        T: Send,
135    {
136        use rayon::prelude::*;
137
138        files
139            .par_iter()
140            .map(|f| {
141                let mut cloned = f.clone();
142                let result = transformer(&mut cloned);
143                (cloned, result)
144            })
145            .collect()
146    }
147
148    // Non-rayon versions (always available)
149
150    /// Parse multiple sources sequentially.
151    pub fn parse_all_seq(sources: &[&str]) -> Vec<PureFile> {
152        sources
153            .iter()
154            .filter_map(|src| PureFile::from_source(src).ok())
155            .collect()
156    }
157
158    /// Analyze multiple files sequentially.
159    pub fn analyze_all_seq<F, T>(files: &[PureFile], analyzer: F) -> Vec<T>
160    where
161        F: Fn(&PureFile) -> T,
162    {
163        files.iter().map(analyzer).collect()
164    }
165
166    /// Build symbol tables sequentially.
167    pub fn build_symbol_tables_seq(files: &[PureFile]) -> Vec<PureSymbolTable> {
168        files.iter().map(PureDefRefs::analyze).collect()
169    }
170
171    /// Rename in all files sequentially (COW pattern).
172    pub fn rename_all_seq(
173        files: &[PureFile],
174        old_name: &str,
175        new_name: &str,
176    ) -> Vec<(PureFile, PureRenameResult)> {
177        files
178            .iter()
179            .map(|f| PureRename::apply_cow(f, old_name, new_name))
180            .collect()
181    }
182}
183
184#[cfg(test)]
185mod tests {
186    use super::*;
187    use std::sync::Arc;
188
189    #[test]
190    fn test_parse_all_seq() {
191        let sources = vec!["fn a() {}", "fn b() {}", "fn c() {}"];
192        let files = PureParallel::parse_all_seq(&sources);
193
194        assert_eq!(files.len(), 3);
195        assert_eq!(files[0].functions().len(), 1);
196        assert_eq!(files[0].functions()[0].name, "a");
197    }
198
199    #[test]
200    fn test_analyze_all_seq() {
201        let sources = vec![
202            "fn a() {} fn a2() {}",
203            "fn b() {}",
204            "fn c() {} fn c2() {} fn c3() {}",
205        ];
206        let files = PureParallel::parse_all_seq(&sources);
207
208        let counts = PureParallel::analyze_all_seq(&files, |f| f.functions().len());
209
210        assert_eq!(counts, vec![2, 1, 3]);
211    }
212
213    #[test]
214    fn test_build_symbol_tables_seq() {
215        let sources = vec!["fn foo() { let x = 1; }"];
216        let files = PureParallel::parse_all_seq(&sources);
217
218        let tables = PureParallel::build_symbol_tables_seq(&files);
219
220        assert_eq!(tables.len(), 1);
221        assert!(tables[0].get("foo").is_some());
222    }
223
224    #[test]
225    fn test_rename_all_seq() {
226        let sources = vec!["fn foo() {}", "fn foo() { foo(); }"];
227        let files = PureParallel::parse_all_seq(&sources);
228
229        let results = PureParallel::rename_all_seq(&files, "foo", "bar");
230
231        assert_eq!(results.len(), 2);
232        assert!(results[0].0.functions()[0].name == "bar");
233        assert!(results[1].0.functions()[0].name == "bar");
234        assert_eq!(results[0].1.count, 1);
235        assert_eq!(results[1].1.count, 2);
236    }
237
238    #[test]
239    fn test_thread_safety_with_arc() {
240        use std::thread;
241
242        let file = PureFile::from_source("fn test() { let x = 1; let y = x + 1; }").unwrap();
243        let shared = Arc::new(file);
244
245        let handles: Vec<_> = (0..4)
246            .map(|_| {
247                let f = Arc::clone(&shared);
248                thread::spawn(move || {
249                    let table = PureDefRefs::analyze(&f);
250                    table.functions().len()
251                })
252            })
253            .collect();
254
255        for handle in handles {
256            assert_eq!(handle.join().unwrap(), 1);
257        }
258    }
259
260    #[test]
261    fn test_parallel_cow_mutations() {
262        use std::thread;
263
264        let file = PureFile::from_source("fn alpha() {} fn beta() {} fn gamma() {}").unwrap();
265        let shared = Arc::new(file);
266
267        let renames = vec![("alpha", "first"), ("beta", "second"), ("gamma", "third")];
268
269        let handles: Vec<_> = renames
270            .into_iter()
271            .map(|(old, new)| {
272                let f = Arc::clone(&shared);
273                thread::spawn(move || {
274                    let (renamed, result) = PureRename::apply_cow(&f, old, new);
275                    (renamed.functions().len(), result.count)
276                })
277            })
278            .collect();
279
280        for handle in handles {
281            let (fn_count, rename_count) = handle.join().unwrap();
282            assert_eq!(fn_count, 3);
283            assert_eq!(rename_count, 1);
284        }
285
286        // Original unchanged
287        assert!(shared.functions().iter().any(|f| f.name == "alpha"));
288    }
289
290    // Rayon-specific tests
291    #[cfg(feature = "parallel")]
292    mod rayon_tests {
293        use super::*;
294
295        #[test]
296        fn test_parse_all_parallel() {
297            let sources: Vec<&str> = (0..100)
298                .map(|i| {
299                    // Create unique source for each
300                    Box::leak(format!("fn func_{i}() {{}}").into_boxed_str()) as &str
301                })
302                .collect();
303
304            let files = PureParallel::parse_all(&sources);
305            assert_eq!(files.len(), 100);
306        }
307
308        #[test]
309        fn test_analyze_all_parallel() {
310            let sources: Vec<&str> = (0..50)
311                .map(|i| Box::leak(format!("fn f{i}() {{}}").into_boxed_str()) as &str)
312                .collect();
313
314            let files = PureParallel::parse_all(&sources);
315            let counts = PureParallel::analyze_all(&files, |f| f.functions().len());
316
317            assert!(counts.iter().all(|&c| c == 1));
318        }
319
320        #[test]
321        fn test_build_symbol_tables_parallel() {
322            let sources: Vec<&str> = (0..20)
323                .map(|i| {
324                    Box::leak(format!("fn func{i}() {{ let x = 1; }}").into_boxed_str()) as &str
325                })
326                .collect();
327
328            let files = PureParallel::parse_all(&sources);
329            let tables = PureParallel::build_symbol_tables(&files);
330
331            assert_eq!(tables.len(), 20);
332            for (i, table) in tables.iter().enumerate() {
333                assert!(table.get(&format!("func{i}")).is_some());
334            }
335        }
336
337        #[test]
338        fn test_rename_all_parallel() {
339            let sources = vec![
340                "fn foo() {}",
341                "fn foo() { foo(); }",
342                "fn foo() { foo(); foo(); }",
343            ];
344            let files = PureParallel::parse_all(&sources);
345
346            let results = PureParallel::rename_all(&files, "foo", "bar");
347
348            assert_eq!(results.len(), 3);
349            assert_eq!(results[0].1.count, 1);
350            assert_eq!(results[1].1.count, 2);
351            assert_eq!(results[2].1.count, 3);
352        }
353
354        #[test]
355        fn test_transform_all_parallel() {
356            let sources = vec!["fn alpha() {}", "fn beta() {}", "fn gamma() {}"];
357            let files = PureParallel::parse_all(&sources);
358
359            let results = PureParallel::transform_all(&files, |f| {
360                let fn_count = f.functions().len();
361                // Mutate the file
362                if let Some(func) = f.items.iter_mut().find_map(|item| {
363                    if let crate::pure::PureItem::Fn(f) = item {
364                        Some(f)
365                    } else {
366                        None
367                    }
368                }) {
369                    func.name = format!("{}_transformed", func.name);
370                }
371                fn_count
372            });
373
374            assert_eq!(results.len(), 3);
375            assert!(results[0].0.functions()[0].name.ends_with("_transformed"));
376        }
377    }
378}