use crate::hir::HirExpr;
use crate::rust_gen::context::{CodeGenContext, ToRustExpr};
use anyhow::{bail, Result};
use syn::parse_quote;
pub fn convert_functools_method(
method: &str,
args: &[HirExpr],
ctx: &mut CodeGenContext,
) -> Result<Option<syn::Expr>> {
let arg_exprs: Vec<syn::Expr> = args
.iter()
.map(|arg| arg.to_rust_expr(ctx))
.collect::<Result<Vec<_>>>()?;
let result = match method {
"reduce" => convert_reduce(&arg_exprs)?,
_ => bail!("functools.{} not implemented yet (available: reduce)", method),
};
Ok(Some(result))
}
fn convert_reduce(arg_exprs: &[syn::Expr]) -> Result<syn::Expr> {
if arg_exprs.len() < 2 {
bail!("functools.reduce() requires at least 2 arguments");
}
let function = &arg_exprs[0];
let iterable = &arg_exprs[1];
if arg_exprs.len() >= 3 {
let initial = &arg_exprs[2];
Ok(parse_quote! {
{
let func = #function;
let items = #iterable;
let init = #initial;
items.into_iter().fold(init, func)
}
})
} else {
Ok(parse_quote! {
{
let func = #function;
let mut items = (#iterable).into_iter();
let init = items.next().expect("reduce() of empty sequence with no initial value");
items.fold(init, func)
}
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hir::Literal;
#[test]
fn test_convert_functools_reduce_no_initial() {
let mut ctx = CodeGenContext::default();
let args = vec![
HirExpr::Var("add".to_string()),
HirExpr::Var("numbers".to_string()),
];
let result = convert_functools_method("reduce", &args, &mut ctx);
assert!(result.is_ok());
let expr = result.unwrap().unwrap();
let code = quote::quote!(#expr).to_string();
assert!(code.contains("fold"));
assert!(code.contains("next"));
}
#[test]
fn test_convert_functools_reduce_with_initial() {
let mut ctx = CodeGenContext::default();
let args = vec![
HirExpr::Var("add".to_string()),
HirExpr::Var("numbers".to_string()),
HirExpr::Literal(Literal::Int(0)),
];
let result = convert_functools_method("reduce", &args, &mut ctx);
assert!(result.is_ok());
let expr = result.unwrap().unwrap();
let code = quote::quote!(#expr).to_string();
assert!(code.contains("fold"));
assert!(!code.contains("next")); }
#[test]
fn test_convert_functools_reduce_wrong_args() {
let mut ctx = CodeGenContext::default();
let args = vec![HirExpr::Var("func".to_string())];
let result = convert_functools_method("reduce", &args, &mut ctx);
assert!(result.is_err());
let err = result.err().unwrap();
assert!(err.to_string().contains("requires at least 2 arguments"));
}
#[test]
fn test_convert_functools_reduce_lambda() {
let mut ctx = CodeGenContext::default();
let args = vec![
HirExpr::Var("lambda_fn".to_string()),
HirExpr::Var("items".to_string()),
];
let result = convert_functools_method("reduce", &args, &mut ctx);
assert!(result.is_ok());
}
#[test]
fn test_convert_functools_reduce_with_string_initial() {
let mut ctx = CodeGenContext::default();
let args = vec![
HirExpr::Var("concat".to_string()),
HirExpr::Var("strings".to_string()),
HirExpr::Literal(Literal::String("".to_string())),
];
let result = convert_functools_method("reduce", &args, &mut ctx);
assert!(result.is_ok());
}
#[test]
fn test_convert_reduce_direct_empty() {
let arg_exprs: Vec<syn::Expr> = vec![parse_quote!(f)];
let result = convert_reduce(&arg_exprs);
assert!(result.is_err());
}
#[test]
fn test_convert_reduce_direct_two_args() {
let arg_exprs: Vec<syn::Expr> = vec![parse_quote!(add), parse_quote!(nums)];
let result = convert_reduce(&arg_exprs);
assert!(result.is_ok());
}
#[test]
fn test_convert_reduce_direct_three_args() {
let arg_exprs: Vec<syn::Expr> = vec![parse_quote!(add), parse_quote!(nums), parse_quote!(0)];
let result = convert_reduce(&arg_exprs);
assert!(result.is_ok());
}
#[test]
fn test_convert_functools_unknown() {
let mut ctx = CodeGenContext::default();
let args: Vec<HirExpr> = vec![];
let result = convert_functools_method("unknown", &args, &mut ctx);
assert!(result.is_err());
let err = result.err().unwrap();
assert!(err.to_string().contains("not implemented"));
}
#[test]
fn test_convert_functools_partial() {
let mut ctx = CodeGenContext::default();
let args = vec![HirExpr::Var("f".to_string())];
let result = convert_functools_method("partial", &args, &mut ctx);
assert!(result.is_err());
}
#[test]
fn test_convert_functools_lru_cache() {
let mut ctx = CodeGenContext::default();
let args: Vec<HirExpr> = vec![];
let result = convert_functools_method("lru_cache", &args, &mut ctx);
assert!(result.is_err());
}
}