use rustpython_parser::ast;
use crate::{
error::{ControlFlow, EvalError, EvalResult, InterpreterError},
eval::{eval_body, eval_expr, eval_stmt, functions::resolve_proxy, statements::assign_target},
state::InterpreterState,
tools::Tools,
value::Value,
};
pub async fn eval_if(
state: &mut InterpreterState,
node: &ast::StmtIf,
tools: &Tools,
) -> EvalResult {
let test = eval_expr(state, &node.test, tools).await?;
let test = resolve_proxy(&test).await?;
let cond = match crate::eval::op::try_truthy_sync(&test) {
Some(b) => b,
None => crate::eval::op::truthy(state, &test, tools).await?,
};
if cond {
eval_body(state, &node.body, tools).await
} else {
eval_body(state, &node.orelse, tools).await
}
}
pub async fn eval_for(
state: &mut InterpreterState,
node: &ast::StmtFor,
tools: &Tools,
) -> EvalResult {
let iterable = eval_expr(state, &node.iter, tools).await?;
let iterable = resolve_proxy(&iterable).await?;
let mut result = Value::None;
let mut broke = false;
if let Value::Range { start, stop, step } = iterable {
let pos = step > 0;
let mut i = start;
loop {
let in_range = (pos && i < stop) || (step < 0 && i > stop);
if !in_range {
break;
}
let body = ForBodyArgs { state, item: Value::Int(i), node, tools };
let cont = run_for_body(body, &mut result, &mut broke).await?;
if broke {
break;
}
let _ = cont;
let Some(next) = i.checked_add(step) else { break };
i = next;
}
} else {
let items = crate::eval::op::iter(state, &iterable, tools).await?;
for item in items {
let body = ForBodyArgs { state, item, node, tools };
let cont = run_for_body(body, &mut result, &mut broke).await?;
if broke {
break;
}
let _ = cont;
}
}
if !broke {
for stmt in &node.orelse {
result = eval_stmt(state, stmt, tools).await?;
}
}
Ok(result)
}
struct ForBodyArgs<'a> {
state: &'a mut InterpreterState,
item: Value,
node: &'a ast::StmtFor,
tools: &'a Tools,
}
async fn run_for_body(
args: ForBodyArgs<'_>,
result: &mut Value,
broke: &mut bool,
) -> Result<bool, EvalError> {
let ForBodyArgs { state, item, node, tools } = args;
assign_target(state, &node.target, item, tools).await?;
for stmt in &node.body {
match eval_stmt(state, stmt, tools).await {
Ok(val) => {
*result = val;
}
Err(EvalError::Signal(ControlFlow::Continue)) => return Ok(true),
Err(EvalError::Signal(ControlFlow::Break)) => {
*broke = true;
return Ok(false);
}
Err(e) => return Err(e),
}
}
Ok(true)
}
pub async fn eval_while(
state: &mut InterpreterState,
node: &ast::StmtWhile,
tools: &Tools,
) -> EvalResult {
let max_iters = state.config.max_while_iterations;
let mut iteration_count = 0u64;
let mut result = Value::None;
let mut broke = false;
loop {
iteration_count += 1;
if iteration_count > max_iters {
return Err(InterpreterError::LimitExceeded(format!(
"while loop exceeded maximum iterations ({max_iters})"
))
.into());
}
let test = eval_expr(state, &node.test, tools).await?;
let test = resolve_proxy(&test).await?;
let cond = match crate::eval::op::try_truthy_sync(&test) {
Some(b) => b,
None => crate::eval::op::truthy(state, &test, tools).await?,
};
if !cond {
break;
}
let mut skip_rest = false;
for stmt in &node.body {
match eval_stmt(state, stmt, tools).await {
Ok(val) => {
result = val;
}
Err(EvalError::Signal(ControlFlow::Continue)) => {
skip_rest = true;
break;
}
Err(EvalError::Signal(ControlFlow::Break)) => {
broke = true;
skip_rest = true;
break;
}
Err(e) => return Err(e),
}
}
if broke {
break;
}
let _ = skip_rest;
}
if !broke {
for stmt in &node.orelse {
result = eval_stmt(state, stmt, tools).await?;
}
}
Ok(result)
}
pub fn iterate_value(val: &Value) -> Result<Vec<Value>, EvalError> {
crate::types::dispatch_iter(val)
}
pub async fn eval_with(
state: &mut InterpreterState,
node: &ast::StmtWith,
tools: &Tools,
) -> EvalResult {
let mut managers: Vec<Value> = Vec::with_capacity(node.items.len());
for item in &node.items {
let cm = eval_expr(state, &item.context_expr, tools).await?;
let cm = resolve_proxy(&cm).await?;
let enter_result = call_context_method(state, &cm, "__enter__", &[], tools).await?;
if let Some(var_expr) = &item.optional_vars {
assign_target(state, var_expr, enter_result, tools).await?;
}
managers.push(cm);
}
let body_result = eval_body(state, &node.body, tools).await;
let signal_to_propagate = match &body_result {
Err(EvalError::Signal(_)) => body_result.as_ref().err().cloned(),
_ => None,
};
let mut current_error: Option<EvalError> = match &body_result {
Err(EvalError::Signal(_)) => None,
_ => body_result.as_ref().err().cloned(),
};
for cm in managers.into_iter().rev() {
let (is_suppressible, exit_args) = build_exit_args(current_error.as_ref());
match call_context_method(state, &cm, "__exit__", &exit_args, tools).await {
Ok(v) => {
if v.is_truthy() && is_suppressible {
current_error = None;
}
}
Err(exit_err) => {
current_error = Some(exit_err);
}
}
}
if let Some(sig) = signal_to_propagate {
return Err(sig);
}
if let Some(err) = current_error {
return Err(err);
}
body_result.map_or_else(|_| Ok(Value::None), Ok)
}
fn build_exit_args(current_error: Option<&EvalError>) -> (bool, Vec<Value>) {
let nones = vec![Value::None, Value::None, Value::None];
let Some(err) = current_error else {
return (false, nones);
};
match err {
EvalError::Signal(_) => (false, nones),
EvalError::Exception(exc) => (
true,
vec![
Value::ExceptionType(exc.type_name.clone()),
Value::Exception(exc.clone()),
Value::None,
],
),
EvalError::Interpreter(ie) => {
let exc = crate::eval::exceptions::interpreter_error_to_exception_pub(ie);
(
true,
vec![
Value::ExceptionType(exc.type_name.clone()),
Value::Exception(exc),
Value::None,
],
)
}
}
}
async fn call_context_method(
state: &mut InterpreterState,
receiver: &Value,
method: &str,
args: &[Value],
tools: &Tools,
) -> EvalResult {
if let Some(result) =
crate::eval::modules::contextlib_mod::try_contextlib_method(state, receiver, method, args)
{
return result;
}
if !matches!(receiver, Value::Instance(_)) {
return Err(InterpreterError::AttributeError(format!(
"'{}' object has no attribute '{method}'",
receiver.type_name()
))
.into());
}
let kwargs = indexmap::IndexMap::new();
let call = crate::eval::functions::CallArgs { positional: args, keyword: &kwargs };
let (returned, _self) =
crate::eval::classes::instance_method_call(state, receiver.clone(), method, call, tools)
.await?;
Ok(returned)
}