use rustpython_parser::ast;
use crate::{
error::{EvalError, EvalResult, InterpreterError},
eval::{eval_body, eval_expr},
state::InterpreterState,
tools::Tools,
value::{ExceptionValue, Value},
};
pub async fn eval_try(
state: &mut InterpreterState,
node: &ast::StmtTry,
tools: &Tools,
) -> EvalResult {
let mut result = Value::None;
let mut pending_error: Option<EvalError> = None;
match eval_body(state, &node.body, tools).await {
Ok(val) => {
result = val;
match eval_body(state, &node.orelse, tools).await {
Ok(val) => result = val,
Err(e) => pending_error = Some(e),
}
}
Err(EvalError::Signal(sig)) => {
pending_error = Some(EvalError::Signal(sig));
}
Err(EvalError::Exception(exc)) => {
if let Some((value, new_error)) =
try_match_handlers(state, &exc, &node.handlers, tools).await?
{
result = value;
pending_error = new_error;
} else {
pending_error = Some(EvalError::Exception(exc));
}
}
Err(EvalError::Interpreter(ie)) => {
let exc = interpreter_error_to_exception(&ie);
if let Some((value, new_error)) =
try_match_handlers(state, &exc, &node.handlers, tools).await?
{
result = value;
pending_error = new_error;
} else {
pending_error = Some(EvalError::Interpreter(ie));
}
}
}
if !node.finalbody.is_empty() {
match eval_body(state, &node.finalbody, tools).await {
Ok(_) => {
}
Err(finally_err) => {
pending_error = Some(finally_err);
}
}
}
if let Some(err) = pending_error {
return Err(err);
}
Ok(result)
}
async fn try_match_handlers(
state: &mut InterpreterState,
exc: &ExceptionValue,
handlers: &[ast::ExceptHandler],
tools: &Tools,
) -> Result<Option<(Value, Option<EvalError>)>, EvalError> {
for handler in handlers {
let ast::ExceptHandler::ExceptHandler(h) = handler;
if !matches_handler(state, exc, h, tools).await? {
continue;
}
if let Some(ref name) = h.name {
state
.set_variable(name.as_str(), Value::Exception(exc.clone()))
.map_err(EvalError::Interpreter)?;
}
state.active_exception_stack.push(exc.clone());
let body_result = eval_body(state, &h.body, tools).await;
state.active_exception_stack.pop();
let (value, new_error) = match body_result {
Ok(val) => (val, None),
Err(err) => (Value::None, Some(err)),
};
if let Some(ref name) = h.name {
let _ = state.delete_variable(name.as_str());
}
return Ok(Some((value, new_error)));
}
Ok(None)
}
async fn matches_handler(
state: &mut InterpreterState,
exc: &ExceptionValue,
handler: &ast::ExceptHandlerExceptHandler,
tools: &Tools,
) -> Result<bool, EvalError> {
let Some(type_expr) = &handler.type_ else {
return Ok(true);
};
let type_val = eval_expr(state, type_expr, tools).await?;
if let Value::Tuple(types) = &type_val {
for type_item in types {
if matches_exception_type(exc, type_item) {
return Ok(true);
}
}
return Ok(false);
}
Ok(matches_exception_type(exc, &type_val))
}
fn matches_exception_type(exc: &ExceptionValue, type_val: &Value) -> bool {
let type_name = match type_val {
Value::ExceptionType(n) | Value::Class(n) => n.clone(),
Value::String(s) => s.to_string(),
_ => format!("{type_val}"),
};
if type_name == "Exception" {
return true;
}
if exc.type_name == type_name {
return true;
}
match type_name.as_str() {
"ArithmeticError" => {
matches!(exc.type_name.as_str(), "ZeroDivisionError" | "OverflowError")
}
"LookupError" => matches!(exc.type_name.as_str(), "KeyError" | "IndexError"),
"OSError" => matches!(exc.type_name.as_str(), "FileNotFoundError" | "IOError"),
_ => false,
}
}
pub(crate) fn interpreter_error_to_exception_pub(err: &InterpreterError) -> ExceptionValue {
interpreter_error_to_exception(err)
}
fn interpreter_error_to_exception(err: &InterpreterError) -> ExceptionValue {
match err {
InterpreterError::TypeError(msg) => {
ExceptionValue::new("TypeError", strip_line_marker(msg))
}
InterpreterError::ValueError(msg) => {
ExceptionValue::new("ValueError", strip_line_marker(msg))
}
InterpreterError::NameError(msg) => {
ExceptionValue::new("NameError", strip_line_marker(msg))
}
InterpreterError::AttributeError(msg) => {
ExceptionValue::new("AttributeError", strip_line_marker(msg))
}
InterpreterError::AssertionError(msg) => {
ExceptionValue::new("AssertionError", strip_line_marker(msg))
}
InterpreterError::PythonException { type_name, message, .. } => {
ExceptionValue::new(type_name.clone(), strip_line_marker(message))
}
InterpreterError::Runtime(msg) => {
ExceptionValue::new("RuntimeError", strip_line_marker(msg))
}
_ => ExceptionValue::new("Exception", strip_line_marker(&format!("{err}"))),
}
}
fn strip_line_marker(msg: &str) -> String {
if let Some(idx) = msg.rfind(" (at line ") {
if msg[idx..].ends_with(')') {
return msg[..idx].to_string();
}
}
msg.to_string()
}
pub async fn eval_raise(
state: &mut InterpreterState,
node: &ast::StmtRaise,
tools: &Tools,
) -> EvalResult {
let Some(exc_expr) = &node.exc else {
return state.active_exception_stack.last().cloned().map_or_else(
|| Err(InterpreterError::Runtime("No active exception to re-raise".into()).into()),
|exc| Err(EvalError::Exception(exc)),
);
};
let exc_val = eval_expr(state, exc_expr, tools).await?;
let cause = if let Some(ref cause_expr) = node.cause {
let cause_val = eval_expr(state, cause_expr, tools).await?;
match cause_val {
Value::Exception(e) => Some(Box::new(e)),
_ => None,
}
} else {
None
};
let implicit_context = if cause.is_none() {
state.active_exception_stack.last().cloned().map(Box::new)
} else {
None
};
let attached_cause = cause.or(implicit_context);
match exc_val {
Value::Exception(mut exc) => {
exc.cause = attached_cause;
Err(EvalError::Exception(exc))
}
Value::ExceptionType(type_name) => {
let mut exc = ExceptionValue::new(type_name, String::new());
if let Some(c) = attached_cause {
exc = exc.with_cause(*c);
}
Err(EvalError::Exception(exc))
}
_ => {
let type_name = format!("{exc_val}");
if is_exception_type_name(&type_name) {
let mut exc = ExceptionValue::new(type_name, String::new());
if let Some(c) = attached_cause {
exc = exc.with_cause(*c);
}
Err(EvalError::Exception(exc))
} else {
Err(InterpreterError::TypeError(format!(
"exceptions must derive from BaseException, not '{}'",
exc_val.type_name()
))
.into())
}
}
}
}
fn is_exception_type_name(name: &str) -> bool {
crate::eval::functions::is_exception_type_name(name)
}
pub async fn eval_assert(
state: &mut InterpreterState,
node: &ast::StmtAssert,
tools: &Tools,
) -> EvalResult {
let test = eval_expr(state, &node.test, tools).await?;
if !crate::eval::op::truthy(state, &test, tools).await? {
let message = if let Some(ref msg_expr) = node.msg {
let msg = eval_expr(state, msg_expr, tools).await?;
format!("{msg}")
} else {
String::new()
};
return Err(EvalError::Exception(ExceptionValue::new("AssertionError", message)));
}
Ok(Value::None)
}