///|
/// Iterable and comprehension evaluation helpers.
///|
fn lookup_iterable_field(
dict : Array[(String, Value)],
name : String,
) -> Value? {
for pair in dict {
if pair.0 == name {
return Some(pair.1)
}
}
None
}
///|
fn set_iterable_field(
dict : Array[(String, Value)],
name : String,
value : Value,
) -> Unit {
for i = 0; i < dict.length(); i = i + 1 {
if dict[i].0 == name {
dict[i] = (name, value)
return
}
}
dict.push((name, value))
}
///|
fn iterable_values(value : Value) -> Result[Array[Value], RuntimeError] {
match value {
Value::List(values) => Ok(values)
Value::Tuple(values) => Ok(values)
Value::Set(values) => Ok(values)
Value::Dict(values) => {
let keys : Array[Value] = []
for pair in values {
keys.push(pair.0)
}
Ok(keys)
}
Value::Str(text) => {
let items : Array[Value] = []
for ch in text.to_array() {
items.push(Value::Str(char_to_string(ch)))
}
Ok(items)
}
Value::Bytes(bytes) | Value::ByteArray(bytes) | Value::MemoryView(bytes) => {
let items : Array[Value] = []
for byte in bytes {
items.push(Value::Int(@bigint.BigInt::from_int(byte)))
}
Ok(items)
}
Value::Instance(inst) =>
if inst.class.name == "range" {
let start = match get_named_value(inst.dict, "start") {
Some(Value::Int(v)) => v
_ => 0N
}
let stop = match get_named_value(inst.dict, "stop") {
Some(Value::Int(v)) => v
_ => 0N
}
let step = match get_named_value(inst.dict, "step") {
Some(Value::Int(v)) => v
_ => 1N
}
let len_big = range_length_bigint(start, stop, step)
let len = match bigint_to_int_checked(len_big) {
Ok(v) => v
Err(err) => return Err(err)
}
let out : Array[Value] = []
for i = 0; i < len; i = i + 1 {
out.push(
Value::Int(
range_element_at(start, step, @bigint.BigInt::from_int(i)),
),
)
}
Ok(out)
} else if inst.class.name == "dict_keys" ||
inst.class.name == "dict_values" ||
inst.class.name == "dict_items" {
let dict_value = lookup_iterable_field(inst.dict, "dict")
match dict_value {
Some(Value::Dict(pairs)) => {
let out : Array[Value] = []
for pair in pairs {
if inst.class.name == "dict_keys" {
out.push(pair.0)
} else if inst.class.name == "dict_values" {
out.push(pair.1)
} else {
out.push(Value::Tuple([pair.0, pair.1]))
}
}
Ok(out)
}
_ =>
Err(
make_runtime_error(
RuntimeErrorKind::Type,
"object is not iterable".to_string(),
),
)
}
} else if inst.class.name == "iterator" {
let items = match lookup_iterable_field(inst.dict, "items") {
Some(Value::List(items)) => items
_ => []
}
let pos = match lookup_iterable_field(inst.dict, "pos") {
Some(Value::Int(v)) => v
Some(Value::Bool(v)) => if v { 1N } else { 0N }
_ => 0N
}
let out : Array[Value] = []
let start = match bigint_to_int_checked(pos) {
Ok(v) => v
Err(err) => return Err(err)
}
let mut i = start
while i < items.length() {
out.push(items[i])
i = i + 1
}
set_iterable_field(
inst.dict,
"pos",
Value::Int(@bigint.BigInt::from_int(items.length())),
)
Ok(out)
} else if inst.class.name == "generator" {
let out : Array[Value] = []
while true {
match generator_next(Value::Instance(inst), None) {
Ok(v) => out.push(v)
Err(err) =>
if err.exc_type == "StopIteration" {
break
} else {
return Err(err)
}
}
}
Ok(out)
} else {
Err(
make_runtime_error(
RuntimeErrorKind::Type,
"object is not iterable".to_string(),
),
)
}
_ =>
Err(
make_runtime_error(
RuntimeErrorKind::Type,
"object is not iterable".to_string(),
),
)
}
}
///|
fn eval_comp_filters(
filters : Array[Expr],
locals : Array[(String, Value)],
globals : Array[(String, Value)],
builtins : Array[(String, Value)],
io : MockIO,
) -> Result[Bool, RuntimeError] {
for filter in filters {
let cond = match eval_expr_with_env(filter, locals, globals, builtins, io) {
Ok(value) => value
Err(err) => return Err(err)
}
let ok = match truthy_from_value_with_env(cond, globals, builtins, io) {
Ok(v) => v
Err(err) => return Err(err)
}
if !ok {
return Ok(false)
}
}
Ok(true)
}
///|
fn eval_list_comp_walk(
index : Int32,
elt : Expr,
clauses : Array[CompClause],
filters : Array[Expr],
locals : Array[(String, Value)],
globals : Array[(String, Value)],
builtins : Array[(String, Value)],
io : MockIO,
results : Array[Value],
) -> Result[Unit, RuntimeError] {
if index >= clauses.length() {
let ok = match
eval_comp_filters(
unowned_comprehension_filters(clauses, filters),
locals,
globals,
builtins,
io,
) {
Ok(value) => value
Err(err) => return Err(err)
}
if ok {
let value = match eval_expr_with_env(elt, locals, globals, builtins, io) {
Ok(value) => value
Err(err) => return Err(err)
}
results.push(value)
}
return Ok(())
}
let clause = clauses[index]
let iter_value = match
eval_expr_with_env(clause.iter, locals, globals, builtins, io) {
Ok(value) => value
Err(err) => return Err(err)
}
if clause.is_async {
let aiter_value = match
get_attr_from_value(iter_value, "__aiter__", globals, builtins, io) {
Ok(v) => v
Err(err) => return Err(err)
}
let iterator = match
call_callable_with_env(aiter_value, [], [], globals, builtins, io) {
Ok(v) => v
Err(err) => return Err(err)
}
while true {
let anext_value = match
get_attr_from_value(iterator, "__anext__", globals, builtins, io) {
Ok(v) => v
Err(err) => return Err(err)
}
let next_awaitable = match
call_callable_with_env(anext_value, [], [], globals, builtins, io) {
Ok(v) => v
Err(err) => return Err(err)
}
let item = match coroutine_await(next_awaitable) {
Ok(v) => v
Err(err) =>
if err.exc_type == "StopAsyncIteration" {
break
} else {
return Err(err)
}
}
let _ = match
assign_target_with_env(
clause.target,
item,
locals,
globals,
builtins,
io,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
let keep = match
eval_comp_filters(clause.filters, locals, globals, builtins, io) {
Ok(value) => value
Err(err) => return Err(err)
}
if !keep {
continue
}
let _ = match
eval_list_comp_walk(
index + 1,
elt,
clauses,
filters,
locals,
globals,
builtins,
io,
results,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
} else {
let iterator = match
iter_value_to_iterator(iter_value, globals, builtins, io) {
Ok(v) => v
Err(err) => return Err(err)
}
while true {
let item = match iterator_next(iterator, None, globals, builtins, io) {
Ok(v) => v
Err(err) =>
if err.exc_type == "StopIteration" {
break
} else {
return Err(err)
}
}
let _ = match
assign_target_with_env(
clause.target,
item,
locals,
globals,
builtins,
io,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
let keep = match
eval_comp_filters(clause.filters, locals, globals, builtins, io) {
Ok(value) => value
Err(err) => return Err(err)
}
if !keep {
continue
}
let _ = match
eval_list_comp_walk(
index + 1,
elt,
clauses,
filters,
locals,
globals,
builtins,
io,
results,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
}
Ok(())
}
///|
fn eval_dict_comp_walk(
index : Int32,
key : Expr,
value : Expr,
clauses : Array[CompClause],
filters : Array[Expr],
locals : Array[(String, Value)],
globals : Array[(String, Value)],
builtins : Array[(String, Value)],
io : MockIO,
results : Array[(Value, Value)],
) -> Result[Unit, RuntimeError] {
if index >= clauses.length() {
let ok = match
eval_comp_filters(
unowned_comprehension_filters(clauses, filters),
locals,
globals,
builtins,
io,
) {
Ok(value) => value
Err(err) => return Err(err)
}
if ok {
let k = match eval_expr_with_env(key, locals, globals, builtins, io) {
Ok(value) => value
Err(err) => return Err(err)
}
let v = match eval_expr_with_env(value, locals, globals, builtins, io) {
Ok(value) => value
Err(err) => return Err(err)
}
let _ = match dict_set_item(results, k, v) {
Ok(value) => value
Err(err) => return Err(err)
}
}
return Ok(())
}
let clause = clauses[index]
let iter_value = match
eval_expr_with_env(clause.iter, locals, globals, builtins, io) {
Ok(value) => value
Err(err) => return Err(err)
}
if clause.is_async {
let aiter_value = match
get_attr_from_value(iter_value, "__aiter__", globals, builtins, io) {
Ok(v) => v
Err(err) => return Err(err)
}
let iterator = match
call_callable_with_env(aiter_value, [], [], globals, builtins, io) {
Ok(v) => v
Err(err) => return Err(err)
}
while true {
let anext_value = match
get_attr_from_value(iterator, "__anext__", globals, builtins, io) {
Ok(v) => v
Err(err) => return Err(err)
}
let next_awaitable = match
call_callable_with_env(anext_value, [], [], globals, builtins, io) {
Ok(v) => v
Err(err) => return Err(err)
}
let item = match coroutine_await(next_awaitable) {
Ok(v) => v
Err(err) =>
if err.exc_type == "StopAsyncIteration" {
break
} else {
return Err(err)
}
}
let _ = match
assign_target_with_env(
clause.target,
item,
locals,
globals,
builtins,
io,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
let keep = match
eval_comp_filters(clause.filters, locals, globals, builtins, io) {
Ok(value) => value
Err(err) => return Err(err)
}
if !keep {
continue
}
let _ = match
eval_dict_comp_walk(
index + 1,
key,
value,
clauses,
filters,
locals,
globals,
builtins,
io,
results,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
} else {
let iterator = match
iter_value_to_iterator(iter_value, globals, builtins, io) {
Ok(v) => v
Err(err) => return Err(err)
}
while true {
let item = match iterator_next(iterator, None, globals, builtins, io) {
Ok(v) => v
Err(err) =>
if err.exc_type == "StopIteration" {
break
} else {
return Err(err)
}
}
let _ = match
assign_target_with_env(
clause.target,
item,
locals,
globals,
builtins,
io,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
let keep = match
eval_comp_filters(clause.filters, locals, globals, builtins, io) {
Ok(value) => value
Err(err) => return Err(err)
}
if !keep {
continue
}
let _ = match
eval_dict_comp_walk(
index + 1,
key,
value,
clauses,
filters,
locals,
globals,
builtins,
io,
results,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
}
Ok(())
}
///|
// The flat filter view is retained for AST consumers and lexical validators;
// execution uses per-clause filters to avoid moving side effects inward.
fn unowned_comprehension_filters(
clauses : Array[CompClause],
filters : Array[Expr],
) -> Array[Expr] {
if clauses.any(fn(clause) { !clause.filters.is_empty() }) {
[]
} else {
filters
}
}