use std::collections::HashMap;
use indexmap::IndexMap;
use rustpython_parser::ast;
use crate::{
error::{ControlFlow, EvalError, EvalResult, InterpreterError},
eval::{eval_expr, eval_stmt},
state::InterpreterState,
tools::Tools,
value::{FunctionParams, Value, ValueKey},
};
async fn eval_default_source(
state: &mut InterpreterState,
tools: &Tools,
source: &str,
) -> Result<Value, EvalError> {
let stmts = crate::parser::parse(source).map_err(|e| {
EvalError::from(InterpreterError::Runtime(format!(
"failed to parse default expression '{source}': {e}"
)))
})?;
let first = stmts.into_iter().next().ok_or_else(|| {
EvalError::from(InterpreterError::Runtime(format!(
"default expression '{source}' produced no statements"
)))
})?;
let ast::Stmt::Expr(expr_stmt) = first else {
return Err(InterpreterError::Runtime(format!(
"default expression '{source}' did not parse as a bare expression"
))
.into());
};
eval_expr(state, &expr_stmt.value, tools).await
}
pub(crate) async fn evaluate_param_defaults(
state: &mut InterpreterState,
params: &mut FunctionParams,
tools: &Tools,
) -> Result<(), EvalError> {
if params.default_values.is_empty() && !params.defaults.is_empty() {
let mut values = Vec::with_capacity(params.defaults.len());
for src in ¶ms.defaults {
values.push(eval_default_source(state, tools, src).await?);
}
params.default_values = values;
}
if params.kw_default_values.is_empty() && !params.kw_defaults.is_empty() {
let mut values = Vec::with_capacity(params.kw_defaults.len());
for opt_src in ¶ms.kw_defaults {
let v = match opt_src {
Some(src) => Some(eval_default_source(state, tools, src).await?),
None => None,
};
values.push(v);
}
params.kw_default_values = values;
}
Ok(())
}
pub(crate) async fn bind_params(
params: &FunctionParams,
args: &[Value],
kwargs: &IndexMap<String, Value>,
state: &mut InterpreterState,
tools: &Tools,
) -> Result<HashMap<String, Value>, EvalError> {
let mut scope = HashMap::new();
let num_params = params.args.len();
let num_defaults = params.defaults.len();
let first_default = num_params.saturating_sub(num_defaults);
for (i, param) in params.args.iter().enumerate() {
if i < args.len() {
scope.insert(param.name.clone(), args[i].clone());
} else if let Some(val) = kwargs.get(¶m.name) {
scope.insert(param.name.clone(), val.clone());
} else {
let default_idx = i.checked_sub(first_default);
if let Some(di) = default_idx {
let default_val = if di < params.default_values.len() {
params.default_values[di].clone()
} else if di < params.defaults.len() {
eval_default_source(state, tools, ¶ms.defaults[di])
.await
.unwrap_or(Value::None)
} else {
return Err(InterpreterError::TypeError(format!(
"missing required argument: '{}'",
param.name
))
.into());
};
scope.insert(param.name.clone(), default_val);
} else {
return Err(InterpreterError::TypeError(format!(
"missing required argument: '{}'",
param.name
))
.into());
}
}
}
if let Some(ref vararg_name) = params.vararg {
let extra: Vec<Value> = args.iter().skip(num_params).cloned().collect();
scope.insert(vararg_name.clone(), Value::Tuple(extra));
}
for (i, kw_param) in params.kwonlyargs.iter().enumerate() {
if let Some(val) = kwargs.get(&kw_param.name) {
scope.insert(kw_param.name.clone(), val.clone());
} else if let Some(Some(default_val)) = params.kw_default_values.get(i) {
scope.insert(kw_param.name.clone(), default_val.clone());
} else if let Some(Some(default_src)) = params.kw_defaults.get(i) {
let default_val =
eval_default_source(state, tools, default_src).await.unwrap_or(Value::None);
scope.insert(kw_param.name.clone(), default_val);
} else {
return Err(InterpreterError::TypeError(format!(
"missing required keyword argument: '{}'",
kw_param.name
))
.into());
}
}
if let Some(ref kwarg_name) = params.kwarg {
let mut extra_kwargs = IndexMap::new();
let param_names: Vec<&str> =
params.args.iter().chain(params.kwonlyargs.iter()).map(|p| p.name.as_str()).collect();
for (k, v) in kwargs {
if !param_names.contains(&k.as_str()) {
extra_kwargs.insert(ValueKey::String(k.clone().into()), v.clone());
}
}
scope.insert(kwarg_name.clone(), Value::Dict(extra_kwargs));
}
Ok(scope)
}
pub(crate) async fn execute_body(
state: &mut InterpreterState,
body: &[ast::Stmt],
tools: &Tools,
) -> EvalResult {
let mut result = Value::None;
for stmt in body {
match eval_stmt(state, stmt, tools).await {
Ok(val) => result = val,
Err(EvalError::Signal(ControlFlow::Return(val))) => return Ok(*val),
Err(e) => return Err(e),
}
}
Ok(result)
}