use indexmap::IndexMap;
use crate::{
error::{EvalError, EvalResult, InterpreterError},
eval::control_flow::iterate_value,
state::InterpreterState,
tools::Tools,
value::Value,
};
pub fn has_function(name: &str) -> bool {
matches!(name, "wraps" | "reduce" | "partial")
}
pub async fn call(
state: &mut InterpreterState,
func: &str,
args: &[Value],
kwargs: &IndexMap<String, Value>,
tools: &Tools,
) -> EvalResult {
match func {
"partial" => {
let Some(target) = args.first().cloned() else {
return Err(InterpreterError::TypeError(
"partial() requires at least one positional argument".into(),
)
.into());
};
Ok(Value::Partial(Box::new(crate::value::PartialData {
func: target,
args: args[1..].to_vec(),
keywords: kwargs.clone(),
})))
}
"wraps" => {
if args.is_empty() {
return Err(InterpreterError::TypeError(
"wraps() missing required argument".into(),
)
.into());
}
let key = "__functools_wraps_identity__";
if !state.lambda_bodies.contains_key(key) {
state.lambda_bodies.insert(
key.to_string(),
std::sync::Arc::new(rustpython_parser::ast::Expr::Name(
rustpython_parser::ast::ExprName {
id: rustpython_parser::ast::Identifier::new("x"),
ctx: rustpython_parser::ast::ExprContext::Load,
range: rustpython_parser::text_size::TextRange::default(),
},
)),
);
}
Ok(Value::Lambda(std::sync::Arc::new(crate::value::LambdaDef {
params: crate::value::FunctionParams {
args: vec![crate::value::Param { name: "x".to_string() }],
defaults: Vec::new(),
default_values: Vec::new(),
vararg: None,
kwonlyargs: Vec::new(),
kw_defaults: Vec::new(),
kw_default_values: Vec::new(),
kwarg: None,
},
lambda_id: key.to_string(),
source: "lambda x: x".to_string(),
closure: std::collections::BTreeMap::new(),
assigned_names: Vec::new(),
is_module_level: true,
})))
}
"reduce" => {
if args.is_empty() {
return Err(InterpreterError::TypeError(
"reduce() requires a function argument".into(),
)
.into());
}
let func_val = args[0].clone();
let iterable = args.get(1).ok_or_else(|| {
EvalError::from(InterpreterError::TypeError(
"reduce() requires an iterable argument".into(),
))
})?;
let items = iterate_value(iterable)?;
let initial = args.get(2).cloned();
let mut iter = items.into_iter();
let mut acc = match initial {
Some(init) => init,
None => match iter.next() {
Some(first) => first,
None => {
return Err(InterpreterError::TypeError(
"reduce() of empty sequence with no initial value".into(),
)
.into());
}
},
};
for item in iter {
let call_args = vec![acc, item];
acc = crate::eval::modules::call_callable(
state,
&func_val,
&call_args,
&IndexMap::new(),
tools,
)
.await?;
}
Ok(acc)
}
_ => Err(InterpreterError::AttributeError(format!(
"module 'functools' has no attribute '{func}'"
))
.into()),
}
}
pub struct FunctoolsModule;
#[async_trait::async_trait]
impl crate::eval::modules::Module for FunctoolsModule {
fn name(&self) -> &'static str {
"functools"
}
fn has_function(&self, name: &str) -> bool {
has_function(name)
}
async fn call(
&self,
state: &mut crate::state::InterpreterState,
func: &str,
args: &[Value],
kwargs: &indexmap::IndexMap<String, Value>,
tools: &crate::tools::Tools,
) -> EvalResult {
call(state, func, args, kwargs, tools).await
}
}