use super::analysis::{PureDefRefs, PureSymbolTable};
use super::ast::PureFile;
use super::rename::{PureRename, PureRenameResult};
pub struct PureParallel;
impl PureParallel {
#[cfg(feature = "parallel")]
pub fn parse_all(sources: &[&str]) -> Vec<PureFile> {
use rayon::prelude::*;
sources
.par_iter()
.filter_map(|src| PureFile::from_source(src).ok())
.collect()
}
#[cfg(feature = "parallel")]
pub fn try_parse_all(sources: &[&str]) -> Vec<Result<PureFile, crate::SourceError>> {
use rayon::prelude::*;
sources
.par_iter()
.map(|src| PureFile::from_source(src))
.collect()
}
#[cfg(feature = "parallel")]
pub fn analyze_all<F, T>(files: &[PureFile], analyzer: F) -> Vec<T>
where
F: Fn(&PureFile) -> T + Sync,
T: Send,
{
use rayon::prelude::*;
files.par_iter().map(|f| analyzer(f)).collect()
}
#[cfg(feature = "parallel")]
pub fn analyze_shared<F, T>(files: &[std::sync::Arc<PureFile>], analyzer: F) -> Vec<T>
where
F: Fn(&PureFile) -> T + Sync,
T: Send,
{
use rayon::prelude::*;
files.par_iter().map(|f| analyzer(f)).collect()
}
#[cfg(feature = "parallel")]
pub fn build_symbol_tables(files: &[PureFile]) -> Vec<PureSymbolTable> {
use rayon::prelude::*;
files.par_iter().map(|f| PureDefRefs::analyze(f)).collect()
}
#[cfg(feature = "parallel")]
pub fn rename_all(
files: &[PureFile],
old_name: &str,
new_name: &str,
) -> Vec<(PureFile, PureRenameResult)> {
use rayon::prelude::*;
files
.par_iter()
.map(|f| PureRename::apply_cow(f, old_name, new_name))
.collect()
}
#[cfg(feature = "parallel")]
pub fn rename_each<'a>(
tasks: &[(&'a PureFile, &'a str, &'a str)],
) -> Vec<(PureFile, PureRenameResult)> {
use rayon::prelude::*;
tasks
.par_iter()
.map(|(f, old, new)| PureRename::apply_cow(f, old, new))
.collect()
}
#[cfg(feature = "parallel")]
pub fn transform_all<F, T>(files: &[PureFile], transformer: F) -> Vec<(PureFile, T)>
where
F: Fn(&mut PureFile) -> T + Sync,
T: Send,
{
use rayon::prelude::*;
files
.par_iter()
.map(|f| {
let mut cloned = f.clone();
let result = transformer(&mut cloned);
(cloned, result)
})
.collect()
}
pub fn parse_all_seq(sources: &[&str]) -> Vec<PureFile> {
sources
.iter()
.filter_map(|src| PureFile::from_source(src).ok())
.collect()
}
pub fn analyze_all_seq<F, T>(files: &[PureFile], analyzer: F) -> Vec<T>
where
F: Fn(&PureFile) -> T,
{
files.iter().map(analyzer).collect()
}
pub fn build_symbol_tables_seq(files: &[PureFile]) -> Vec<PureSymbolTable> {
files.iter().map(PureDefRefs::analyze).collect()
}
pub fn rename_all_seq(
files: &[PureFile],
old_name: &str,
new_name: &str,
) -> Vec<(PureFile, PureRenameResult)> {
files
.iter()
.map(|f| PureRename::apply_cow(f, old_name, new_name))
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
#[test]
fn test_parse_all_seq() {
let sources = vec!["fn a() {}", "fn b() {}", "fn c() {}"];
let files = PureParallel::parse_all_seq(&sources);
assert_eq!(files.len(), 3);
assert_eq!(files[0].functions().len(), 1);
assert_eq!(files[0].functions()[0].name, "a");
}
#[test]
fn test_analyze_all_seq() {
let sources = vec![
"fn a() {} fn a2() {}",
"fn b() {}",
"fn c() {} fn c2() {} fn c3() {}",
];
let files = PureParallel::parse_all_seq(&sources);
let counts = PureParallel::analyze_all_seq(&files, |f| f.functions().len());
assert_eq!(counts, vec![2, 1, 3]);
}
#[test]
fn test_build_symbol_tables_seq() {
let sources = vec!["fn foo() { let x = 1; }"];
let files = PureParallel::parse_all_seq(&sources);
let tables = PureParallel::build_symbol_tables_seq(&files);
assert_eq!(tables.len(), 1);
assert!(tables[0].get("foo").is_some());
}
#[test]
fn test_rename_all_seq() {
let sources = vec!["fn foo() {}", "fn foo() { foo(); }"];
let files = PureParallel::parse_all_seq(&sources);
let results = PureParallel::rename_all_seq(&files, "foo", "bar");
assert_eq!(results.len(), 2);
assert!(results[0].0.functions()[0].name == "bar");
assert!(results[1].0.functions()[0].name == "bar");
assert_eq!(results[0].1.count, 1);
assert_eq!(results[1].1.count, 2);
}
#[test]
fn test_thread_safety_with_arc() {
use std::thread;
let file = PureFile::from_source("fn test() { let x = 1; let y = x + 1; }").unwrap();
let shared = Arc::new(file);
let handles: Vec<_> = (0..4)
.map(|_| {
let f = Arc::clone(&shared);
thread::spawn(move || {
let table = PureDefRefs::analyze(&f);
table.functions().len()
})
})
.collect();
for handle in handles {
assert_eq!(handle.join().unwrap(), 1);
}
}
#[test]
fn test_parallel_cow_mutations() {
use std::thread;
let file = PureFile::from_source("fn alpha() {} fn beta() {} fn gamma() {}").unwrap();
let shared = Arc::new(file);
let renames = vec![("alpha", "first"), ("beta", "second"), ("gamma", "third")];
let handles: Vec<_> = renames
.into_iter()
.map(|(old, new)| {
let f = Arc::clone(&shared);
thread::spawn(move || {
let (renamed, result) = PureRename::apply_cow(&f, old, new);
(renamed.functions().len(), result.count)
})
})
.collect();
for handle in handles {
let (fn_count, rename_count) = handle.join().unwrap();
assert_eq!(fn_count, 3);
assert_eq!(rename_count, 1);
}
assert!(shared.functions().iter().any(|f| f.name == "alpha"));
}
#[cfg(feature = "parallel")]
mod rayon_tests {
use super::*;
#[test]
fn test_parse_all_parallel() {
let sources: Vec<&str> = (0..100)
.map(|i| {
Box::leak(format!("fn func_{i}() {{}}").into_boxed_str()) as &str
})
.collect();
let files = PureParallel::parse_all(&sources);
assert_eq!(files.len(), 100);
}
#[test]
fn test_analyze_all_parallel() {
let sources: Vec<&str> = (0..50)
.map(|i| Box::leak(format!("fn f{i}() {{}}").into_boxed_str()) as &str)
.collect();
let files = PureParallel::parse_all(&sources);
let counts = PureParallel::analyze_all(&files, |f| f.functions().len());
assert!(counts.iter().all(|&c| c == 1));
}
#[test]
fn test_build_symbol_tables_parallel() {
let sources: Vec<&str> = (0..20)
.map(|i| {
Box::leak(format!("fn func{i}() {{ let x = 1; }}").into_boxed_str()) as &str
})
.collect();
let files = PureParallel::parse_all(&sources);
let tables = PureParallel::build_symbol_tables(&files);
assert_eq!(tables.len(), 20);
for (i, table) in tables.iter().enumerate() {
assert!(table.get(&format!("func{i}")).is_some());
}
}
#[test]
fn test_rename_all_parallel() {
let sources = vec![
"fn foo() {}",
"fn foo() { foo(); }",
"fn foo() { foo(); foo(); }",
];
let files = PureParallel::parse_all(&sources);
let results = PureParallel::rename_all(&files, "foo", "bar");
assert_eq!(results.len(), 3);
assert_eq!(results[0].1.count, 1);
assert_eq!(results[1].1.count, 2);
assert_eq!(results[2].1.count, 3);
}
#[test]
fn test_transform_all_parallel() {
let sources = vec!["fn alpha() {}", "fn beta() {}", "fn gamma() {}"];
let files = PureParallel::parse_all(&sources);
let results = PureParallel::transform_all(&files, |f| {
let fn_count = f.functions().len();
if let Some(func) = f.items.iter_mut().find_map(|item| {
if let crate::pure::PureItem::Fn(f) = item {
Some(f)
} else {
None
}
}) {
func.name = format!("{}_transformed", func.name);
}
fn_count
});
assert_eq!(results.len(), 3);
assert!(results[0].0.functions()[0].name.ends_with("_transformed"));
}
}
}