use crate::{Environment, eval::apply::eval_apply, is_number_eq, value::Value};
pub(crate) fn builtin(args: &[Value], env: Environment) -> Result<Value, std::sync::Arc<str>> {
let [val_expr, idx_expr] = if let [v, i] = args {
[v, i]
} else {
return Err(std::sync::Arc::from(format!(
"Error[ksl::builtin::Index]: Expected 2 parameters, but {} were passed.",
args.len()
)));
};
let idx_val = eval_apply(idx_expr, env.clone())?;
let target = eval_apply(val_expr, env)?;
let n = if let Value::Number(n) = idx_val {
if !is_number_eq(n.round(), n) || (n < 1.0 && !is_number_eq(n, -1.0)) {
return Err(std::sync::Arc::from(format!(
"Error[ksl::builtin::Index]: Index must be an integer starting from 1 or -1, but got {}.",
n
)));
}
n
} else {
return Err(std::sync::Arc::from(format!(
"Error[ksl::builtin::Index]: Unexpected value: `{}`.",
idx_val
)));
};
let is_last = is_number_eq(n, -1.0);
match target {
Value::List(elements) => {
if elements.is_empty() {
return Err(std::sync::Arc::from(
"Error[ksl::builtin::Index]: Empty list.",
));
}
let idx = if is_last {
elements.len() - 1
} else {
(n as usize) - 1
};
elements.get(idx).cloned().ok_or_else(|| {
std::sync::Arc::from(format!(
"Error[ksl::builtin::Index]: Index `{}` out of bounds `{}`.",
n,
elements.len()
))
})
}
Value::String(s) => {
if s.is_empty() {
return Err(std::sync::Arc::from(
"Error[ksl::builtin::Index]: Empty String.",
));
}
let char_opt = if is_last {
s.chars().last()
} else {
s.chars().nth((n as usize) - 1)
};
char_opt
.map(|c| Value::String(std::sync::Arc::from(c.to_string())))
.ok_or(std::sync::Arc::from(format!(
"Error[ksl::builtin::Index]: Index `{}` out of bounds `{}`.",
n,
s.chars().count()
)))
}
e => Err(std::sync::Arc::from(format!(
"Error[ksl::builtin::Index]: Unexpected value: `{}`.",
e
))),
}
}