1use super::analysis::{PureDefRefs, PureSymbolTable};
25use super::ast::PureFile;
26use super::rename::{PureRename, PureRenameResult};
27
28pub struct PureParallel;
32
33impl PureParallel {
34 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 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 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 pub fn build_symbol_tables_seq(files: &[PureFile]) -> Vec<PureSymbolTable> {
168 files.iter().map(PureDefRefs::analyze).collect()
169 }
170
171 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 assert!(shared.functions().iter().any(|f| f.name == "alpha"));
288 }
289
290 #[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 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 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}