use rustpython_parser::ast::{self, Expr};
use crate::{
error::{EvalError, EvalResult, InterpreterError},
eval::{
eval_expr,
functions::{VariableCheckpoint, resolve_proxy},
literals::value_to_key,
},
state::InterpreterState,
tools::Tools,
value::{Value, ValueKey, shared_list},
};
fn collect_generator_target_names(generators: &[ast::Comprehension]) -> Vec<String> {
let mut names = Vec::new();
for g in generators {
collect_target_names(&g.target, &mut names);
}
names
}
fn collect_target_names(target: &ast::Expr, out: &mut Vec<String>) {
match target {
Expr::Name(n) => {
let s = n.id.as_str().to_string();
if !out.contains(&s) {
out.push(s);
}
}
Expr::Tuple(t) => {
for e in &t.elts {
collect_target_names(e, out);
}
}
Expr::List(l) => {
for e in &l.elts {
collect_target_names(e, out);
}
}
Expr::Starred(s) => collect_target_names(&s.value, out),
_ => {}
}
}
pub async fn eval_list_comp(
state: &mut InterpreterState,
node: &ast::ExprListComp,
tools: &Tools,
) -> EvalResult {
let checkpoint =
VariableCheckpoint::capture(state, collect_generator_target_names(&node.generators));
let mut results = Vec::new();
let outcome = eval_list_generators(ListGenContext {
state,
generators: &node.generators,
index: 0,
elt_expr: &node.elt,
results: &mut results,
tools,
})
.await;
checkpoint.restore(state);
outcome?;
Ok(Value::List(shared_list(results)))
}
pub async fn eval_generator_exp(
state: &mut InterpreterState,
node: &ast::ExprGeneratorExp,
tools: &Tools,
) -> EvalResult {
let checkpoint =
VariableCheckpoint::capture(state, collect_generator_target_names(&node.generators));
let mut results = Vec::new();
let outcome = eval_list_generators(ListGenContext {
state,
generators: &node.generators,
index: 0,
elt_expr: &node.elt,
results: &mut results,
tools,
})
.await;
checkpoint.restore(state);
outcome?;
Ok(Value::List(shared_list(results)))
}
pub async fn eval_dict_comp(
state: &mut InterpreterState,
node: &ast::ExprDictComp,
tools: &Tools,
) -> EvalResult {
let checkpoint =
VariableCheckpoint::capture(state, collect_generator_target_names(&node.generators));
let mut result_map = indexmap::IndexMap::new();
let outcome = eval_dict_generators(DictGenContext {
state,
generators: &node.generators,
index: 0,
key_expr: &node.key,
value_expr: &node.value,
result_map: &mut result_map,
tools,
})
.await;
checkpoint.restore(state);
outcome?;
Ok(Value::Dict(result_map))
}
pub async fn eval_set_comp(
state: &mut InterpreterState,
node: &ast::ExprSetComp,
tools: &Tools,
) -> EvalResult {
let checkpoint =
VariableCheckpoint::capture(state, collect_generator_target_names(&node.generators));
let mut results = Vec::new();
let outcome = eval_list_generators(ListGenContext {
state,
generators: &node.generators,
index: 0,
elt_expr: &node.elt,
results: &mut results,
tools,
})
.await;
checkpoint.restore(state);
outcome?;
let mut deduped = Vec::new();
for item in results {
let key = value_to_key(&item).ok();
let already = deduped.iter().any(|r: &Value| value_to_key(r).ok() == key);
if !already {
deduped.push(item);
}
}
Ok(Value::Set(deduped))
}
struct ListGenContext<'a> {
state: &'a mut InterpreterState,
generators: &'a [ast::Comprehension],
index: usize,
elt_expr: &'a Expr,
results: &'a mut Vec<Value>,
tools: &'a Tools,
}
fn eval_list_generators<'a>(
ctx: ListGenContext<'a>,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), EvalError>> + Send + 'a>> {
Box::pin(async move {
let ListGenContext { state, generators, index, elt_expr, results, tools } = ctx;
if index >= generators.len() {
let val = eval_expr(state, elt_expr, tools).await?;
results.push(val);
return Ok(());
}
let generator = &generators[index];
let iterable = eval_expr(state, &generator.iter, tools).await?;
let iterable = resolve_proxy(&iterable).await?;
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;
}
set_comprehension_target(state, &generator.target, &Value::Int(i)).await?;
let mut include = true;
for if_clause in &generator.ifs {
let cond = eval_expr(state, if_clause, tools).await?;
let cond = resolve_proxy(&cond).await?;
if !cond.is_truthy() {
include = false;
break;
}
}
if include {
eval_list_generators(ListGenContext {
state,
generators,
index: index + 1,
elt_expr,
results,
tools,
})
.await?;
}
let Some(next) = i.checked_add(step) else { break };
i = next;
}
return Ok(());
}
let items = crate::eval::op::iter(state, &iterable, tools).await?;
for item in items {
set_comprehension_target(state, &generator.target, &item).await?;
let mut include = true;
for if_clause in &generator.ifs {
let cond = eval_expr(state, if_clause, tools).await?;
let cond = resolve_proxy(&cond).await?;
if !cond.is_truthy() {
include = false;
break;
}
}
if include {
eval_list_generators(ListGenContext {
state,
generators,
index: index + 1,
elt_expr,
results,
tools,
})
.await?;
}
}
Ok(())
})
}
struct DictGenContext<'a> {
state: &'a mut InterpreterState,
generators: &'a [ast::Comprehension],
index: usize,
key_expr: &'a Expr,
value_expr: &'a Expr,
result_map: &'a mut indexmap::IndexMap<ValueKey, Value>,
tools: &'a Tools,
}
fn eval_dict_generators<'a>(
ctx: DictGenContext<'a>,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), EvalError>> + Send + 'a>> {
Box::pin(async move {
let DictGenContext { state, generators, index, key_expr, value_expr, result_map, tools } =
ctx;
if index >= generators.len() {
let key = eval_expr(state, key_expr, tools).await?;
let value = eval_expr(state, value_expr, tools).await?;
let key = value_to_key(&key)?;
result_map.insert(key, value);
return Ok(());
}
let generator = &generators[index];
let iterable = eval_expr(state, &generator.iter, tools).await?;
let iterable = resolve_proxy(&iterable).await?;
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;
}
set_comprehension_target(state, &generator.target, &Value::Int(i)).await?;
let mut include = true;
for if_clause in &generator.ifs {
let cond = eval_expr(state, if_clause, tools).await?;
let cond = resolve_proxy(&cond).await?;
if !cond.is_truthy() {
include = false;
break;
}
}
if include {
eval_dict_generators(DictGenContext {
state,
generators,
index: index + 1,
key_expr,
value_expr,
result_map,
tools,
})
.await?;
}
let Some(next) = i.checked_add(step) else { break };
i = next;
}
return Ok(());
}
let items = crate::eval::op::iter(state, &iterable, tools).await?;
for item in items {
set_comprehension_target(state, &generator.target, &item).await?;
let mut include = true;
for if_clause in &generator.ifs {
let cond = eval_expr(state, if_clause, tools).await?;
let cond = resolve_proxy(&cond).await?;
if !cond.is_truthy() {
include = false;
break;
}
}
if include {
eval_dict_generators(DictGenContext {
state,
generators,
index: index + 1,
key_expr,
value_expr,
result_map,
tools,
})
.await?;
}
}
Ok(())
})
}
fn set_comprehension_target<'a>(
state: &'a mut InterpreterState,
target: &'a Expr,
value: &'a Value,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), EvalError>> + Send + 'a>> {
Box::pin(async move {
match target {
Expr::Name(name_node) => {
state.variables.insert(name_node.id.as_str().to_string(), value.clone());
Ok(())
}
Expr::Tuple(tuple_node) => {
let items: Vec<Value> = match value {
Value::List(items) => items.lock().clone(),
Value::Tuple(items) => items.clone(),
Value::String(s) => {
s.chars().map(|c| Value::String(c.to_string().into())).collect()
}
_ => {
return Err(InterpreterError::TypeError(
"cannot unpack non-iterable value in comprehension".into(),
)
.into());
}
};
if tuple_node.elts.len() != items.len() {
return Err(InterpreterError::Runtime(
"cannot unpack tuple of wrong size in comprehension".into(),
)
.into());
}
for (elem, val) in tuple_node.elts.iter().zip(items.iter()) {
set_comprehension_target(state, elem, val).await?;
}
Ok(())
}
_ => Err(InterpreterError::Runtime(format!(
"unsupported comprehension target: {:?}",
std::mem::discriminant(target)
))
.into()),
}
})
}