#![expect(
clippy::cast_precision_loss,
reason = "Python built-ins (sum, min, max, `**`, int(float)) convert Int ↔ \
Float; precision loss above 2^53 is CPython's behaviour and we \
faithfully reproduce it. Scoped to this module since the numeric \
built-ins are here"
)]
use crate::{
error::{EvalError, InterpreterError},
value::Value,
};
pub(crate) mod methods;
pub(crate) mod params;
mod builtins;
mod call;
mod definitions;
pub(crate) mod dispatch;
mod generators;
pub(crate) mod helpers;
mod method_dispatch;
pub use builtins::is_exception_type_name;
pub use call::eval_call;
pub(crate) use definitions::{
VariableCheckpoint, collect_assigned_names, contains_yield_stmts, extract_function_source,
};
pub use definitions::{build_function_params, eval_function_def, eval_lambda_def};
pub(crate) use dispatch::{call_lambda, call_user_function, call_value_as_function};
pub(crate) use generators::dispatch_generator_method;
pub(crate) use method_dispatch::{
CallArgs, MethodOutcome, arg1, bind_method_params, reject_kwargs, require_param,
};
pub(crate) use params::{bind_params, evaluate_param_defaults, execute_body};
pub(crate) fn to_index(i: i64) -> Result<usize, EvalError> {
usize::try_from(i)
.map_err(|_| InterpreterError::Runtime("index overflow or negative".into()).into())
}
pub(crate) fn to_len_i64(len: usize) -> Result<i64, EvalError> {
i64::try_from(len)
.map_err(|_| InterpreterError::Runtime("collection length overflows i64".into()).into())
}
pub(crate) fn to_u32(n: i64) -> Result<u32, EvalError> {
u32::try_from(n).map_err(|_| {
InterpreterError::Runtime(
"exponent/shift count out of u32 range (internal invariant)".into(),
)
.into()
})
}
#[expect(
clippy::cast_possible_truncation,
reason = "truncation toward zero IS Python's int(float) semantic; the \
saturation branches above handle out-of-range inputs"
)]
pub(crate) fn float_to_int(f: f64) -> i64 {
if f.is_nan() {
0
} else if f >= i64::MAX as f64 {
i64::MAX
} else if f <= i64::MIN as f64 {
i64::MIN
} else {
f.trunc() as i64
}
}
pub async fn resolve_proxy(value: &Value) -> Result<Value, EvalError> {
if let Value::LazyProxy(proxy) = value {
proxy.resolve().await.map_err(|e| {
EvalError::Interpreter(InterpreterError::Tool {
tool_name: proxy.tool_name.clone(),
message: e.message,
})
})
} else {
Ok(value.clone())
}
}
pub(crate) fn check_arg_count(
name: &str,
args: &[Value],
min: usize,
max: usize,
) -> Result<(), EvalError> {
if args.len() < min || args.len() > max {
if min == max {
return Err(InterpreterError::TypeError(format!(
"{name}() takes exactly {min} argument(s) ({} given)",
args.len()
))
.into());
}
return Err(InterpreterError::TypeError(format!(
"{name}() takes {min} to {max} arguments ({} given)",
args.len()
))
.into());
}
Ok(())
}
pub(crate) fn value_to_i64(val: &Value) -> Result<i64, EvalError> {
match val {
Value::Int(i) => Ok(*i),
Value::Float(f) => Ok(float_to_int(*f)),
Value::Bool(b) => Ok(i64::from(*b)),
_ => {
Err(InterpreterError::TypeError(format!("expected integer, got '{}'", val.type_name()))
.into())
}
}
}