use bigdecimal::{ToPrimitive, Zero};
use num_bigint::BigInt;
use crate::error::{DogeError, DogeResult};
use crate::value::Value;
fn normalize_index(i: i64, len: usize) -> DogeResult<usize> {
let len_i = len as i64;
let idx = if i < 0 { i + len_i } else { i };
if idx < 0 || idx >= len_i {
Err(DogeError::index_out_of_bounds(format!(
"index {i} is out of bounds for length {len}"
)))
} else {
Ok(idx as usize)
}
}
fn normalize_bigindex(i: &BigInt, len: usize) -> DogeResult<usize> {
match i.to_i64() {
Some(v) => normalize_index(v, len),
None => Err(DogeError::index_out_of_bounds(format!(
"index {i} is out of bounds for length {len}"
))),
}
}
pub fn index_get(container: &Value, index: &Value) -> DogeResult {
match (container, index) {
(Value::List(items), Value::Int(i)) => {
let items = items.borrow();
let idx = normalize_bigindex(i, items.len())?;
Ok(items[idx].clone())
}
(Value::Str(s), Value::Int(i)) => {
let chars: Vec<char> = s.chars().collect();
let idx = normalize_bigindex(i, chars.len())?;
Ok(Value::str(chars[idx].to_string()))
}
(Value::Bytes(b), Value::Int(i)) => {
let idx = normalize_bigindex(i, b.len())?;
Ok(Value::int(b[idx]))
}
(Value::Dict(d), Value::Str(k)) => d
.borrow()
.get(k.as_ref())
.cloned()
.ok_or_else(|| DogeError::key_error(format!("no such key: {k:?}"))),
(Value::List(_) | Value::Str(_) | Value::Bytes(_), _) => {
Err(DogeError::type_error(format!(
"cannot index {} with {} (need an Int)",
container.describe(),
index.describe()
)))
}
(Value::Dict(_), _) => Err(DogeError::type_error(format!(
"cannot index a Dict with {} (keys are Str)",
index.describe()
))),
(
Value::Int(_)
| Value::Float(_)
| Value::Decimal(_)
| Value::Bool(_)
| Value::None
| Value::Object(_)
| Value::Function(_)
| Value::Class(_)
| Value::BoundMethod(_)
| Value::Error(_)
| Value::Socket(_)
| Value::Pup(_)
| Value::Bowl(_),
_,
) => Err(DogeError::type_error(format!(
"cannot index {}",
container.describe()
))),
}
}
pub fn index_set(container: &Value, index: &Value, value: Value) -> DogeResult<()> {
match (container, index) {
(Value::List(items), Value::Int(i)) => {
let mut items = items.borrow_mut();
let idx = normalize_bigindex(i, items.len())?;
items[idx] = value;
Ok(())
}
(Value::Dict(d), Value::Str(k)) => {
d.borrow_mut().insert(k.to_string(), value);
Ok(())
}
(Value::Str(_), _) => Err(DogeError::type_error(
"cannot assign into a Str (Str values are immutable)",
)),
(Value::Bytes(_), _) => Err(DogeError::type_error(
"cannot assign into a Bytes (Bytes values are immutable)",
)),
(Value::List(_), _) => Err(DogeError::type_error(format!(
"cannot index a List with {} (need an Int)",
index.describe()
))),
(Value::Dict(_), _) => Err(DogeError::type_error(format!(
"cannot index a Dict with {} (keys are Str)",
index.describe()
))),
(
Value::Int(_)
| Value::Float(_)
| Value::Decimal(_)
| Value::Bool(_)
| Value::None
| Value::Object(_)
| Value::Function(_)
| Value::Class(_)
| Value::BoundMethod(_)
| Value::Error(_)
| Value::Socket(_)
| Value::Pup(_)
| Value::Bowl(_),
_,
) => Err(DogeError::type_error(format!(
"cannot index into {}",
container.describe()
))),
}
}
fn slice_bound(what: &str, v: &Value) -> DogeResult<Option<i64>> {
match v {
Value::None => Ok(None),
Value::Int(n) => Ok(Some(bigint_to_bound(n))),
_ => Err(DogeError::type_error(format!(
"a slice {what} must be an Int, not {}",
v.describe()
))),
}
}
fn bigint_to_bound(n: &BigInt) -> i64 {
n.to_i64()
.unwrap_or(if n.sign() == num_bigint::Sign::Minus {
i64::MIN
} else {
i64::MAX
})
}
fn slice_step(v: &Value) -> DogeResult<i64> {
match v {
Value::None => Ok(1),
Value::Int(n) if n.is_zero() => Err(DogeError::value_error("a slice step cannot be zero")),
Value::Int(n) => Ok(bigint_to_bound(n)),
_ => Err(DogeError::type_error(format!(
"a slice step must be an Int, not {}",
v.describe()
))),
}
}
fn slice_indices(start: Option<i64>, end: Option<i64>, step: i64, len: usize) -> Vec<usize> {
let len = len as i64;
let (lower, upper) = if step < 0 { (-1, len - 1) } else { (0, len) };
let clamp = |bound: i64| {
let bound = if bound < 0 { bound + len } else { bound };
bound.clamp(lower, upper)
};
let start = match start {
Some(s) => clamp(s),
None => {
if step < 0 {
upper
} else {
lower
}
}
};
let end = match end {
Some(e) => clamp(e),
None => {
if step < 0 {
lower
} else {
upper
}
}
};
let mut indices = Vec::new();
let mut i = start;
if step > 0 {
while i < end {
indices.push(i as usize);
i += step;
}
} else {
while i > end {
indices.push(i as usize);
i += step;
}
}
indices
}
pub fn slice_get(container: &Value, start: &Value, end: &Value, step: &Value) -> DogeResult {
let step = slice_step(step)?;
let start = slice_bound("start", start)?;
let end = slice_bound("end", end)?;
match container {
Value::List(items) => {
let items = items.borrow();
let picked = slice_indices(start, end, step, items.len())
.into_iter()
.map(|i| items[i].clone())
.collect();
Ok(Value::list(picked))
}
Value::Str(s) => {
let chars: Vec<char> = s.chars().collect();
let picked: String = slice_indices(start, end, step, chars.len())
.into_iter()
.map(|i| chars[i])
.collect();
Ok(Value::str(picked))
}
Value::Bytes(b) => {
let picked: Vec<u8> = slice_indices(start, end, step, b.len())
.into_iter()
.map(|i| b[i])
.collect();
Ok(Value::bytes(picked))
}
Value::Dict(_)
| Value::Int(_)
| Value::Float(_)
| Value::Decimal(_)
| Value::Bool(_)
| Value::None
| Value::Object(_)
| Value::Function(_)
| Value::Class(_)
| Value::BoundMethod(_)
| Value::Error(_)
| Value::Socket(_)
| Value::Pup(_)
| Value::Bowl(_) => Err(DogeError::type_error(format!(
"cannot slice {}",
container.describe()
))),
}
}
pub fn iter_value(v: &Value) -> DogeResult<Vec<Value>> {
match v {
Value::List(items) => Ok(items.borrow().clone()),
Value::Str(s) => Ok(s.chars().map(|c| Value::str(c.to_string())).collect()),
Value::Bytes(b) => Ok(b.iter().map(|&x| Value::int(x)).collect()),
Value::Dict(entries) => Ok(entries
.borrow()
.iter()
.map(|(k, _)| Value::str(k))
.collect()),
Value::Int(_)
| Value::Float(_)
| Value::Decimal(_)
| Value::Bool(_)
| Value::None
| Value::Object(_)
| Value::Function(_)
| Value::Class(_)
| Value::BoundMethod(_)
| Value::Error(_)
| Value::Socket(_)
| Value::Pup(_)
| Value::Bowl(_) => Err(DogeError::type_error(format!(
"cannot loop over {}",
v.describe()
))),
}
}
pub fn unpack_value(v: &Value, fixed: usize, rest: bool) -> DogeResult<Vec<Value>> {
let mut values = iter_value(v)
.map_err(|_| DogeError::type_error(format!("cannot unpack {}", v.describe())))?;
if rest {
if values.len() < fixed {
return Err(DogeError::value_error(format!(
"expected at least {fixed} values to unpack, but got {}",
values.len()
)));
}
let collected = values.split_off(fixed);
values.push(Value::list(collected));
} else if values.len() != fixed {
return Err(DogeError::value_error(format!(
"expected {fixed} values to unpack, but got {}",
values.len()
)));
}
Ok(values)
}