use crate::rust_gen::context::CodeGenContext;
use anyhow::{bail, Result};
use syn::parse_quote;
pub fn convert_set_constructor(ctx: &mut CodeGenContext, args: &[syn::Expr]) -> Result<syn::Expr> {
ctx.needs_hashset = true;
if args.is_empty() {
Ok(parse_quote! { std::collections::HashSet::<i32>::new() })
} else if args.len() == 1 {
let arg = &args[0];
if let syn::Expr::Tuple(tuple) = arg {
let elems = &tuple.elems;
Ok(parse_quote! {
vec![#elems].into_iter().collect::<std::collections::HashSet<_>>()
})
} else {
Ok(parse_quote! {
#arg.into_iter().collect::<std::collections::HashSet<_>>()
})
}
} else {
bail!("set() takes at most 1 argument ({} given)", args.len())
}
}
pub fn convert_frozenset_constructor(
ctx: &mut CodeGenContext,
args: &[syn::Expr],
) -> Result<syn::Expr> {
ctx.needs_hashset = true;
if args.is_empty() {
Ok(parse_quote! { std::sync::Arc::new(std::collections::HashSet::<i32>::new()) })
} else if args.len() == 1 {
let arg = &args[0];
if let syn::Expr::Tuple(tuple) = arg {
let elems = &tuple.elems;
Ok(parse_quote! {
std::sync::Arc::new(vec![#elems].into_iter().collect::<std::collections::HashSet<_>>())
})
} else {
Ok(parse_quote! {
std::sync::Arc::new(#arg.into_iter().collect::<std::collections::HashSet<_>>())
})
}
} else {
bail!(
"frozenset() takes at most 1 argument ({} given)",
args.len()
)
}
}
pub fn convert_counter_builtin(ctx: &mut CodeGenContext, args: &[syn::Expr]) -> Result<syn::Expr> {
ctx.needs_hashmap = true;
if args.is_empty() {
Ok(parse_quote! { HashMap::new() })
} else if args.len() == 1 {
let arg = &args[0];
Ok(parse_quote! {
#arg.into_iter().fold(HashMap::new(), |mut acc, item| {
*acc.entry(item).or_insert(0) += 1;
acc
})
})
} else {
bail!("Counter() takes at most 1 argument ({} given)", args.len())
}
}
pub fn convert_defaultdict_builtin(
ctx: &mut CodeGenContext,
_args: &[syn::Expr],
) -> Result<syn::Expr> {
ctx.needs_hashmap = true;
Ok(parse_quote! { HashMap::new() })
}
pub fn convert_dict_builtin(ctx: &mut CodeGenContext, args: &[syn::Expr]) -> Result<syn::Expr> {
ctx.needs_hashmap = true;
if args.is_empty() {
Ok(parse_quote! { std::collections::HashMap::new() })
} else if args.len() == 1 {
let arg = &args[0];
Ok(parse_quote! {
#arg.into_iter().collect::<std::collections::HashMap<_, _>>()
})
} else {
bail!("dict() takes at most 1 argument ({} given)", args.len())
}
}
pub fn convert_deque_builtin(ctx: &mut CodeGenContext, args: &[syn::Expr]) -> Result<syn::Expr> {
ctx.needs_vecdeque = true;
if args.is_empty() {
Ok(parse_quote! { VecDeque::new() })
} else if args.len() == 1 {
let arg = &args[0];
if ctx.type_mapper.nasa_mode {
ctx.needs_depyler_value_enum = true;
Ok(parse_quote! {
VecDeque::from(#arg.into_iter().map(DepylerValue::from).collect::<Vec<_>>())
})
} else {
Ok(parse_quote! {
VecDeque::from(#arg)
})
}
} else {
bail!("deque() takes at most 1 argument ({} given)", args.len())
}
}
pub fn already_collected(expr: &syn::Expr) -> bool {
if let syn::Expr::MethodCall(method_call) = expr {
method_call.method == "collect"
} else {
false
}
}
pub fn is_range_expr(expr: &syn::Expr) -> bool {
matches!(expr, syn::Expr::Range(_))
}
pub fn is_iterator_expr(expr: &syn::Expr) -> bool {
if let syn::Expr::MethodCall(method_call) = expr {
let method_name = method_call.method.to_string();
matches!(
method_name.as_str(),
"iter"
| "iter_mut"
| "into_iter"
| "zip"
| "map"
| "filter"
| "enumerate"
| "chain"
| "flat_map"
| "take"
| "skip"
| "collect"
)
} else {
false
}
}
pub fn is_csv_reader_var(expr: &syn::Expr) -> bool {
if let syn::Expr::Path(path) = expr {
if let Some(ident) = path.path.get_ident() {
let var_name = ident.to_string();
return var_name == "reader"
|| var_name.contains("csv")
|| var_name.ends_with("_reader")
|| var_name.starts_with("reader_");
}
}
false
}
pub fn convert_list_builtin(ctx: &mut CodeGenContext, args: &[syn::Expr]) -> Result<syn::Expr> {
if args.is_empty() {
return Ok(parse_quote! { Vec::new() });
}
if args.len() != 1 {
bail!("list() takes at most 1 argument ({} given)", args.len());
}
let arg = &args[0];
if already_collected(arg) {
return Ok(arg.clone());
}
if is_range_expr(arg) {
return Ok(parse_quote! {
(#arg).collect::<Vec<_>>()
});
}
if is_iterator_expr(arg) {
return Ok(parse_quote! {
#arg.collect::<Vec<_>>()
});
}
if is_csv_reader_var(arg) {
ctx.needs_csv = true;
return Ok(parse_quote! {
#arg.deserialize::<HashMap<String, String>>().collect::<Vec<_>>()
});
}
Ok(parse_quote! {
#arg.into_iter().collect::<Vec<_>>()
})
}
#[cfg(test)]
mod tests {
use super::*;
fn make_ctx() -> CodeGenContext<'static> {
CodeGenContext::default()
}
#[test]
fn test_already_collected_true() {
let expr: syn::Expr = parse_quote! { items.collect::<Vec<_>>() };
assert!(already_collected(&expr));
}
#[test]
fn test_already_collected_false() {
let expr: syn::Expr = parse_quote! { items.iter() };
assert!(!already_collected(&expr));
}
#[test]
fn test_is_range_expr_true() {
let expr: syn::Expr = parse_quote! { 0..5 };
assert!(is_range_expr(&expr));
}
#[test]
fn test_is_range_expr_false() {
let expr: syn::Expr = parse_quote! { vec![1, 2, 3] };
assert!(!is_range_expr(&expr));
}
#[test]
fn test_is_iterator_expr_zip() {
let expr: syn::Expr = parse_quote! { a.iter().zip(b.iter()) };
assert!(is_iterator_expr(&expr));
}
#[test]
fn test_is_iterator_expr_map() {
let expr: syn::Expr = parse_quote! { items.map(|x| x + 1) };
assert!(is_iterator_expr(&expr));
}
#[test]
fn test_is_csv_reader_var_true() {
let expr: syn::Expr = parse_quote! { reader };
assert!(is_csv_reader_var(&expr));
let expr2: syn::Expr = parse_quote! { csv_reader };
assert!(is_csv_reader_var(&expr2));
}
#[test]
fn test_is_csv_reader_var_false() {
let expr: syn::Expr = parse_quote! { items };
assert!(!is_csv_reader_var(&expr));
}
#[test]
fn test_convert_set_constructor_empty() {
let mut ctx = make_ctx();
let result = convert_set_constructor(&mut ctx, &[]).unwrap();
let code = quote::quote!(#result).to_string();
assert!(code.contains("HashSet"));
assert!(code.contains("new"));
assert!(ctx.needs_hashset);
}
#[test]
fn test_convert_set_constructor_with_iterable() {
let mut ctx = make_ctx();
let arg: syn::Expr = parse_quote! { items };
let result = convert_set_constructor(&mut ctx, &[arg]).unwrap();
let code = quote::quote!(#result).to_string();
assert!(code.contains("collect"));
assert!(code.contains("HashSet"));
}
#[test]
fn test_convert_set_constructor_with_tuple() {
let mut ctx = make_ctx();
let arg: syn::Expr = parse_quote! { (1, 2, 3) };
let result = convert_set_constructor(&mut ctx, &[arg]).unwrap();
let code = quote::quote!(#result).to_string();
assert!(code.contains("collect"));
assert!(code.contains("HashSet"));
}
#[test]
fn test_convert_set_constructor_too_many_args() {
let mut ctx = make_ctx();
let arg1: syn::Expr = parse_quote! { a };
let arg2: syn::Expr = parse_quote! { b };
let result = convert_set_constructor(&mut ctx, &[arg1, arg2]);
assert!(result.is_err());
}
#[test]
fn test_convert_frozenset_constructor_empty() {
let mut ctx = make_ctx();
let result = convert_frozenset_constructor(&mut ctx, &[]).unwrap();
let code = quote::quote!(#result).to_string();
assert!(code.contains("Arc"));
assert!(code.contains("HashSet"));
assert!(ctx.needs_hashset);
}
#[test]
fn test_convert_frozenset_constructor_with_iterable() {
let mut ctx = make_ctx();
let arg: syn::Expr = parse_quote! { items };
let result = convert_frozenset_constructor(&mut ctx, &[arg]).unwrap();
let code = quote::quote!(#result).to_string();
assert!(code.contains("Arc"));
assert!(code.contains("collect"));
}
#[test]
fn test_convert_frozenset_constructor_with_tuple() {
let mut ctx = make_ctx();
let arg: syn::Expr = parse_quote! { (1, 2, 3) };
let result = convert_frozenset_constructor(&mut ctx, &[arg]).unwrap();
let code = quote::quote!(#result).to_string();
assert!(code.contains("collect"));
assert!(code.contains("Arc"));
}
#[test]
fn test_convert_frozenset_constructor_too_many_args() {
let mut ctx = make_ctx();
let arg1: syn::Expr = parse_quote! { a };
let arg2: syn::Expr = parse_quote! { b };
let result = convert_frozenset_constructor(&mut ctx, &[arg1, arg2]);
assert!(result.is_err());
}
#[test]
fn test_convert_counter_builtin_empty() {
let mut ctx = make_ctx();
let result = convert_counter_builtin(&mut ctx, &[]).unwrap();
let code = quote::quote!(#result).to_string();
assert!(code.contains("HashMap :: new"));
assert!(ctx.needs_hashmap);
}
#[test]
fn test_convert_counter_builtin_with_iterable() {
let mut ctx = make_ctx();
let arg: syn::Expr = parse_quote! { items };
let result = convert_counter_builtin(&mut ctx, &[arg]).unwrap();
let code = quote::quote!(#result).to_string();
assert!(code.contains("fold"));
assert!(code.contains("entry"));
assert!(code.contains("or_insert"));
}
#[test]
fn test_convert_counter_builtin_too_many_args() {
let mut ctx = make_ctx();
let arg1: syn::Expr = parse_quote! { a };
let arg2: syn::Expr = parse_quote! { b };
let result = convert_counter_builtin(&mut ctx, &[arg1, arg2]);
assert!(result.is_err());
}
#[test]
fn test_convert_defaultdict_builtin_empty() {
let mut ctx = make_ctx();
let result = convert_defaultdict_builtin(&mut ctx, &[]).unwrap();
let code = quote::quote!(#result).to_string();
assert!(code.contains("HashMap :: new"));
assert!(ctx.needs_hashmap);
}
#[test]
fn test_convert_defaultdict_builtin_with_factory() {
let mut ctx = make_ctx();
let arg: syn::Expr = parse_quote! { int };
let result = convert_defaultdict_builtin(&mut ctx, &[arg]).unwrap();
let code = quote::quote!(#result).to_string();
assert!(code.contains("HashMap :: new"));
}
#[test]
fn test_convert_dict_builtin_empty() {
let mut ctx = make_ctx();
let result = convert_dict_builtin(&mut ctx, &[]).unwrap();
let code = quote::quote!(#result).to_string();
assert!(code.contains("HashMap"));
assert!(code.contains("new"));
assert!(ctx.needs_hashmap);
}
#[test]
fn test_convert_dict_builtin_with_mapping() {
let mut ctx = make_ctx();
let arg: syn::Expr = parse_quote! { items };
let result = convert_dict_builtin(&mut ctx, &[arg]).unwrap();
let code = quote::quote!(#result).to_string();
assert!(code.contains("collect"));
assert!(code.contains("HashMap"));
}
#[test]
fn test_convert_dict_builtin_too_many_args() {
let mut ctx = make_ctx();
let arg1: syn::Expr = parse_quote! { a };
let arg2: syn::Expr = parse_quote! { b };
let result = convert_dict_builtin(&mut ctx, &[arg1, arg2]);
assert!(result.is_err());
}
#[test]
fn test_convert_deque_builtin_empty() {
let mut ctx = make_ctx();
let result = convert_deque_builtin(&mut ctx, &[]).unwrap();
let code = quote::quote!(#result).to_string();
assert!(code.contains("VecDeque"));
assert!(code.contains("new"));
assert!(ctx.needs_vecdeque);
}
#[test]
fn test_convert_deque_builtin_with_iterable() {
let mut ctx = make_ctx();
let arg: syn::Expr = parse_quote! { items };
let result = convert_deque_builtin(&mut ctx, &[arg]).unwrap();
let code = quote::quote!(#result).to_string();
assert!(code.contains("VecDeque"));
assert!(code.contains("from"));
}
#[test]
fn test_convert_deque_builtin_too_many_args() {
let mut ctx = make_ctx();
let arg1: syn::Expr = parse_quote! { a };
let arg2: syn::Expr = parse_quote! { b };
let result = convert_deque_builtin(&mut ctx, &[arg1, arg2]);
assert!(result.is_err());
}
#[test]
fn test_convert_list_builtin_empty() {
let mut ctx = make_ctx();
let result = convert_list_builtin(&mut ctx, &[]).unwrap();
let code = quote::quote!(#result).to_string();
assert!(code.contains("Vec :: new"));
}
#[test]
fn test_convert_list_builtin_with_iterable() {
let mut ctx = make_ctx();
let arg: syn::Expr = parse_quote! { items };
let result = convert_list_builtin(&mut ctx, &[arg]).unwrap();
let code = quote::quote!(#result).to_string();
assert!(code.contains("collect"));
assert!(code.contains("Vec"));
}
#[test]
fn test_convert_list_builtin_with_range() {
let mut ctx = make_ctx();
let arg: syn::Expr = parse_quote! { 0..10 };
let result = convert_list_builtin(&mut ctx, &[arg]).unwrap();
let code = quote::quote!(#result).to_string();
assert!(code.contains("collect"));
}
#[test]
fn test_convert_list_builtin_already_collected() {
let mut ctx = make_ctx();
let arg: syn::Expr = parse_quote! { items.iter().collect::<Vec<_>>() };
let result = convert_list_builtin(&mut ctx, &[arg.clone()]).unwrap();
let code = quote::quote!(#result).to_string();
assert!(code.contains("collect"));
}
#[test]
fn test_convert_list_builtin_with_iterator() {
let mut ctx = make_ctx();
let arg: syn::Expr = parse_quote! { items.iter().filter(|x| *x > 0) };
let result = convert_list_builtin(&mut ctx, &[arg]).unwrap();
let code = quote::quote!(#result).to_string();
assert!(code.contains("filter"));
assert!(code.contains("collect"));
}
#[test]
fn test_convert_list_builtin_with_csv_reader() {
let mut ctx = make_ctx();
let arg: syn::Expr = parse_quote! { reader };
let result = convert_list_builtin(&mut ctx, &[arg]).unwrap();
let code = quote::quote!(#result).to_string();
assert!(code.contains("deserialize"));
assert!(ctx.needs_csv);
}
#[test]
fn test_convert_list_builtin_too_many_args() {
let mut ctx = make_ctx();
let arg1: syn::Expr = parse_quote! { a };
let arg2: syn::Expr = parse_quote! { b };
let result = convert_list_builtin(&mut ctx, &[arg1, arg2]);
assert!(result.is_err());
}
#[test]
fn test_is_iterator_expr_enumerate() {
let expr: syn::Expr = parse_quote! { items.enumerate() };
assert!(is_iterator_expr(&expr));
}
#[test]
fn test_is_iterator_expr_chain() {
let expr: syn::Expr = parse_quote! { a.chain(b) };
assert!(is_iterator_expr(&expr));
}
#[test]
fn test_is_iterator_expr_take() {
let expr: syn::Expr = parse_quote! { items.take(5) };
assert!(is_iterator_expr(&expr));
}
#[test]
fn test_is_iterator_expr_skip() {
let expr: syn::Expr = parse_quote! { items.skip(5) };
assert!(is_iterator_expr(&expr));
}
#[test]
fn test_is_iterator_expr_flat_map() {
let expr: syn::Expr = parse_quote! { items.flat_map(|x| x) };
assert!(is_iterator_expr(&expr));
}
#[test]
fn test_is_iterator_expr_iter() {
let expr: syn::Expr = parse_quote! { items.iter() };
assert!(is_iterator_expr(&expr));
}
#[test]
fn test_is_iterator_expr_into_iter() {
let expr: syn::Expr = parse_quote! { items.into_iter() };
assert!(is_iterator_expr(&expr));
}
#[test]
fn test_is_iterator_expr_non_iterator() {
let expr: syn::Expr = parse_quote! { items.push(1) };
assert!(!is_iterator_expr(&expr));
}
#[test]
fn test_is_csv_reader_var_ends_with_reader() {
let expr: syn::Expr = parse_quote! { csv_file_reader };
assert!(is_csv_reader_var(&expr));
}
#[test]
fn test_is_csv_reader_var_starts_with_reader() {
let expr: syn::Expr = parse_quote! { reader_csv };
assert!(is_csv_reader_var(&expr));
}
#[test]
fn test_is_csv_reader_var_method_call() {
let expr: syn::Expr = parse_quote! { file.reader() };
assert!(!is_csv_reader_var(&expr));
}
#[test]
fn test_is_range_expr_inclusive() {
let expr: syn::Expr = parse_quote! { 0..=5 };
assert!(is_range_expr(&expr));
}
#[test]
fn test_is_range_expr_half_open() {
let expr: syn::Expr = parse_quote! { start..end };
assert!(is_range_expr(&expr));
}
#[test]
fn test_already_collected_nested() {
let expr: syn::Expr = parse_quote! { a.iter().filter(|x| true).collect::<Vec<_>>() };
assert!(already_collected(&expr));
}
#[test]
fn test_already_collected_literal() {
let expr: syn::Expr = parse_quote! { 42 };
assert!(!already_collected(&expr));
}
}