use indexmap::IndexMap;
use crate::{
error::{EvalError, EvalResult, InterpreterError},
state::InterpreterState,
tools::Tools,
value::{ExceptionValue, Value},
};
pub(super) fn bytes_fromhex(args: &[Value]) -> EvalResult {
let Some(Value::String(s)) = args.first() else {
return Err(InterpreterError::TypeError("fromhex() requires a str argument".into()).into());
};
let cleaned: String = s.chars().filter(|c| !c.is_ascii_whitespace()).collect();
if cleaned.len() % 2 != 0 {
return Err(InterpreterError::ValueError(
"non-hexadecimal number found in fromhex() arg".into(),
)
.into());
}
let mut out = Vec::with_capacity(cleaned.len() / 2);
let bytes = cleaned.as_bytes();
for pair in bytes.chunks_exact(2) {
let hi = hex_digit(pair[0])?;
let lo = hex_digit(pair[1])?;
out.push((hi << 4) | lo);
}
Ok(Value::Bytes(out))
}
fn hex_digit(b: u8) -> Result<u8, EvalError> {
match b {
b'0'..=b'9' => Ok(b - b'0'),
b'a'..=b'f' => Ok(b - b'a' + 10),
b'A'..=b'F' => Ok(b - b'A' + 10),
_ => Err(InterpreterError::ValueError(format!(
"non-hexadecimal character {:?} in fromhex() arg",
b as char
))
.into()),
}
}
pub(super) async fn dict_fromkeys(
state: &mut InterpreterState,
args: &[Value],
tools: &Tools,
) -> EvalResult {
let Some(iterable) = args.first() else {
return Err(
InterpreterError::TypeError("fromkeys() requires at least 1 argument".into()).into()
);
};
let value = args.get(1).cloned().unwrap_or(Value::None);
let items = crate::eval::op::iter(state, iterable, tools).await?;
let mut map = IndexMap::new();
for item in items {
let key = crate::eval::op::key(state, &item, tools).await?;
map.insert(key, value.clone());
}
Ok(Value::Dict(map))
}
pub(super) fn list_sort_type_error(type_name: &str) -> EvalError {
InterpreterError::AttributeError(format!("'{type_name}' object has no attribute 'sort'")).into()
}
pub(super) async fn apply_key_fn(
state: &mut InterpreterState,
item: &Value,
key_fn: Option<&Value>,
tools: &Tools,
) -> EvalResult {
match key_fn {
Some(func) => {
super::dispatch::call_value_as_function(state, func, std::slice::from_ref(item), tools)
.await
}
None => Ok(item.clone()),
}
}
pub(crate) struct SortRequest<'a> {
pub items: Vec<Value>,
pub key_fn: Option<&'a Value>,
pub reverse: bool,
}
pub(crate) async fn dsu_sort(
state: &mut InterpreterState,
tools: &Tools,
req: SortRequest<'_>,
) -> Result<Vec<Value>, EvalError> {
let SortRequest { items, key_fn, reverse } = req;
let mut decorated: Vec<(Value, Value)> = Vec::with_capacity(items.len());
for item in items {
let key = apply_key_fn(state, &item, key_fn, tools).await?;
decorated.push((key, item));
}
let any_instance = decorated.iter().any(|(k, _)| matches!(k, Value::Instance(_)));
if any_instance {
let mut sorted_dec: Vec<(Value, Value)> = Vec::with_capacity(decorated.len());
for entry in decorated {
let mut insert_at = sorted_dec.len();
for (i, existing) in sorted_dec.iter().enumerate() {
if crate::eval::op::lt(state, &entry.0, &existing.0, tools).await? {
insert_at = i;
break;
}
}
sorted_dec.insert(insert_at, entry);
}
let mut sorted: Vec<Value> = sorted_dec.into_iter().map(|(_, v)| v).collect();
if reverse {
sorted.reverse();
}
return Ok(sorted);
}
decorated.sort_by(|a, b| {
if crate::eval::operations::compare_lt(&a.0, &b.0).unwrap_or(false) {
std::cmp::Ordering::Less
} else if crate::eval::operations::compare_lt(&b.0, &a.0).unwrap_or(false) {
std::cmp::Ordering::Greater
} else {
std::cmp::Ordering::Equal
}
});
let mut sorted: Vec<Value> = decorated.into_iter().map(|(_, v)| v).collect();
if reverse {
sorted.reverse();
}
Ok(sorted)
}
pub(super) fn check_isinstance(state: &InterpreterState, obj: &Value, type_name: &str) -> bool {
if type_name == "object" {
return true;
}
if let Value::Instance(inst) = obj {
if inst.class_name == type_name {
return true;
}
if let Some(class) = state.classes.get(&inst.class_name) {
return class.mro.iter().any(|ancestor| ancestor == type_name);
}
return false;
}
obj.type_name() == type_name
|| match (obj, type_name) {
(Value::Bool(_), "int") | (Value::Counter(_), "dict") => true,
(Value::Exception(e), tn) => e.type_name == tn || tn == "Exception",
_ => false,
}
}
pub(super) fn type_arg_name(value: &Value) -> String {
match value {
Value::Class(n) | Value::Type(n) | Value::BuiltinName(n) | Value::ExceptionType(n) => {
n.clone()
}
Value::ModuleFunction { name, .. } => name.clone(),
other => format!("{other}"),
}
}
pub(super) fn pow_three_arg(
base: &Value,
exp: &Value,
modulus: &Value,
) -> Result<Value, EvalError> {
let base_i = match base {
Value::Int(b) => *b,
Value::Bool(b) => i64::from(*b),
_ => {
return Err(InterpreterError::TypeError(
"pow() 3rd argument not allowed unless all arguments are integers".into(),
)
.into());
}
};
let exp_i = match exp {
Value::Int(e) => *e,
Value::Bool(e) => i64::from(*e),
_ => {
return Err(InterpreterError::TypeError(
"pow() 3rd argument not allowed unless all arguments are integers".into(),
)
.into());
}
};
let mod_i = match modulus {
Value::Int(m) => *m,
Value::Bool(m) => i64::from(*m),
_ => {
return Err(InterpreterError::TypeError(
"pow() 3rd argument not allowed unless all arguments are integers".into(),
)
.into());
}
};
if mod_i == 0 {
return Err(EvalError::Exception(ExceptionValue::new(
"ValueError",
"pow() 3rd argument cannot be 0",
)));
}
if exp_i < 0 {
return Err(EvalError::Exception(ExceptionValue::new(
"ValueError",
"pow() 2nd argument cannot be negative when 3rd argument specified",
)));
}
let m = mod_i.unsigned_abs();
let mut result: u128 = 1;
let mut b: u128 = u128::from(base_i.rem_euclid(mod_i).unsigned_abs());
let exp_u = u64::try_from(exp_i).map_err(|err| {
EvalError::from(InterpreterError::Runtime(format!("pow() exponent out of range: {err}")))
})?;
let mut e: u64 = exp_u;
let mod_u: u128 = m.into();
while e > 0 {
if e & 1 == 1 {
result = result * b % mod_u;
}
e >>= 1;
b = b * b % mod_u;
}
let signed = i64::try_from(result).map_err(|err| {
EvalError::from(InterpreterError::Runtime(format!("pow() result out of i64 range: {err}")))
})?;
if mod_i < 0 && signed != 0 { Ok(Value::Int(signed + mod_i)) } else { Ok(Value::Int(signed)) }
}