use std::cmp::Ordering;
use std::rc::Rc;
use num_bigint::BigInt;
use num_traits::FromPrimitive;
use crate::dict::Set;
use crate::error::{Error, Kind, Result};
use crate::hash::Key;
use crate::int::{DivideByZero, Int};
use crate::object::Object;
use crate::slice::Indices;
use crate::text::{Str, StrBuf};
const REPEAT_LIMIT: u64 = 1 << 40;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Compare {
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
}
impl Compare {
#[must_use]
pub const fn symbol(self) -> &'static str {
match self {
Compare::Eq => "==",
Compare::Ne => "!=",
Compare::Lt => "<",
Compare::Le => "<=",
Compare::Gt => ">",
Compare::Ge => ">=",
}
}
#[must_use]
const fn decide(self, ordering: Ordering) -> bool {
match self {
Compare::Eq => ordering.is_eq(),
Compare::Ne => ordering.is_ne(),
Compare::Lt => ordering.is_lt(),
Compare::Le => ordering.is_le(),
Compare::Gt => ordering.is_gt(),
Compare::Ge => ordering.is_ge(),
}
}
#[must_use]
const fn is_equality(self) -> bool {
matches!(self, Compare::Eq | Compare::Ne)
}
}
pub fn add(left: &Object, right: &Object) -> Result<Object> {
if let Some(pair) = promote(left, right)? {
return Ok(match pair {
Pair::Ints(a, b) => Object::Int(a.add(b)),
Pair::Floats(a, b) => Object::Float(a + b),
});
}
match (left, right) {
(Object::Str(a), Object::Str(b)) => {
let mut buf = StrBuf::new();
buf.push_string(a);
buf.push_string(b);
Ok(Object::Str(Rc::new(buf.finish())))
}
(Object::Bytes(a), Object::Bytes(b)) => {
let mut joined = a.to_vec();
joined.extend_from_slice(b);
Ok(Object::Bytes(joined.into()))
}
(Object::Tuple(a), Object::Tuple(b)) => {
let mut joined = a.to_vec();
joined.extend_from_slice(b);
Ok(Object::tuple(joined))
}
(Object::List(a), Object::List(b)) => {
let mut joined = a.borrow().clone();
joined.extend(b.borrow().iter().cloned());
Ok(Object::list(joined))
}
_ => Err(concat_error(left, right)),
}
}
pub fn sub(left: &Object, right: &Object) -> Result<Object> {
if let Some(pair) = promote(left, right)? {
return Ok(match pair {
Pair::Ints(a, b) => Object::Int(a.sub(b)),
Pair::Floats(a, b) => Object::Float(a - b),
});
}
if let (Object::Set(a), Object::Set(b)) = (left, right) {
let (a, b) = (a.borrow(), b.borrow());
return Ok(Object::set(
a.iter().filter(|key| !b.contains(key)).cloned().collect(),
));
}
Err(unsupported("-", left, right))
}
pub fn mul(left: &Object, right: &Object) -> Result<Object> {
if let Some(pair) = promote(left, right)? {
return Ok(match pair {
Pair::Ints(a, b) => Object::Int(a.mul(b)),
Pair::Floats(a, b) => Object::Float(a * b),
});
}
let (sequence, count) = match (index(left), index(right)) {
(_, Some(count)) => (left, count?),
(Some(count), None) => (right, count?),
(None, None) => return Err(repeat_error(left, right)),
};
repeat(sequence, count)?.ok_or_else(|| repeat_error(left, right))
}
pub fn true_div(left: &Object, right: &Object) -> Result<Object> {
let Some(pair) = promote(left, right)? else {
return Err(unsupported("/", left, right));
};
match pair {
Pair::Ints(a, b) => match a.true_div(b) {
Ok(Some(value)) => Ok(Object::Float(value)),
Ok(None) => Err(Error::overflow(
"integer division result too large for a float",
)),
Err(DivideByZero) => Err(divide_by_zero()),
},
Pair::Floats(a, b) => {
if b == 0.0 {
return Err(divide_by_zero());
}
Ok(Object::Float(a / b))
}
}
}
pub fn floor_div(left: &Object, right: &Object) -> Result<Object> {
let Some(pair) = promote(left, right)? else {
return Err(unsupported("//", left, right));
};
match pair {
Pair::Ints(a, b) => a
.floor_div(b)
.map(Object::Int)
.map_err(|DivideByZero| divide_by_zero()),
Pair::Floats(a, b) => Ok(Object::Float(float_div_mod(a, b)?.0)),
}
}
pub fn modulo(left: &Object, right: &Object) -> Result<Object> {
let Some(pair) = promote(left, right)? else {
return Err(unsupported("%", left, right));
};
match pair {
Pair::Ints(a, b) => a
.modulo(b)
.map(Object::Int)
.map_err(|DivideByZero| divide_by_zero()),
Pair::Floats(a, b) => Ok(Object::Float(float_div_mod(a, b)?.1)),
}
}
pub fn div_mod(left: &Object, right: &Object) -> Result<Object> {
let Some(pair) = promote(left, right)? else {
return Err(unsupported("divmod()", left, right));
};
let (quotient, remainder) = match pair {
Pair::Ints(a, b) => {
let (q, r) = a.div_mod(b).map_err(|DivideByZero| divide_by_zero())?;
(Object::Int(q), Object::Int(r))
}
Pair::Floats(a, b) => {
let (q, r) = float_div_mod(a, b)?;
(Object::Float(q), Object::Float(r))
}
};
Ok(Object::tuple(vec![quotient, remainder]))
}
pub fn pow(base: &Object, exponent: &Object) -> Result<Object> {
let Some(pair) = promote(base, exponent)? else {
return Err(unsupported("** or pow()", base, exponent));
};
match pair {
Pair::Ints(a, b) if !b.is_negative() => match a.pow(b) {
Some(value) => Ok(Object::Int(value)),
None => Err(Error::new(Kind::MemoryError, "")),
},
Pair::Ints(a, b) => float_pow(to_float(a)?, to_float(b)?),
Pair::Floats(a, b) => float_pow(a, b),
}
}
pub fn lshift(left: &Object, right: &Object) -> Result<Object> {
shift(left, right, "<<", Int::shl)
}
pub fn rshift(left: &Object, right: &Object) -> Result<Object> {
shift(left, right, ">>", Int::shr)
}
pub fn bit_and(left: &Object, right: &Object) -> Result<Object> {
bitwise(left, right, "&", Int::bitand, |a, b| {
a.iter().filter(|key| b.contains(key)).cloned().collect()
})
}
pub fn bit_or(left: &Object, right: &Object) -> Result<Object> {
bitwise(left, right, "|", Int::bitor, |a, b| {
a.iter().chain(b.iter()).cloned().collect()
})
}
pub fn bit_xor(left: &Object, right: &Object) -> Result<Object> {
bitwise(left, right, "^", Int::bitxor, |a, b| {
a.iter()
.filter(|key| !b.contains(key))
.chain(b.iter().filter(|key| !a.contains(key)))
.cloned()
.collect()
})
}
pub fn neg(value: &Object) -> Result<Object> {
match number(value) {
Some(Num::Int(value)) => Ok(Object::Int(value.neg())),
Some(Num::Float(value)) => Ok(Object::Float(-value)),
None => Err(bad_unary("-", value)),
}
}
pub fn pos(value: &Object) -> Result<Object> {
match number(value) {
Some(Num::Int(value)) => Ok(Object::Int(value.clone())),
Some(Num::Float(value)) => Ok(Object::Float(value)),
None => Err(bad_unary("+", value)),
}
}
pub fn invert(value: &Object) -> Result<Object> {
match number(value) {
Some(Num::Int(value)) => Ok(Object::Int(value.invert())),
_ => Err(bad_unary("~", value)),
}
}
pub fn abs(value: &Object) -> Result<Object> {
match number(value) {
Some(Num::Int(value)) => Ok(Object::Int(value.abs())),
Some(Num::Float(value)) => Ok(Object::Float(value.abs())),
None => Err(Error::type_error(format!(
"bad operand type for abs(): '{}'",
value.type_name()
))),
}
}
#[must_use]
pub fn not(value: &Object) -> Object {
Object::Bool(!value.truthy())
}
pub fn compare(op: Compare, left: &Object, right: &Object) -> Result<Object> {
if op.is_equality() {
let equal = left.equals(right);
return Ok(Object::Bool(equal == (op == Compare::Eq)));
}
order(op, left, right).map(Object::Bool)
}
pub fn contains(container: &Object, value: &Object) -> Result<Object> {
let found = match container {
Object::Str(text) => {
let Object::Str(needle) = value else {
return Err(Error::type_error(format!(
"'in <string>' requires string as left operand, not {}",
value.type_name()
)));
};
substring(text, needle)
}
Object::Bytes(haystack) => match value {
Object::Bytes(needle) => subslice(haystack, needle),
Object::Int(_) | Object::Bool(_) => {
let Some(Num::Int(byte)) = number(value) else {
unreachable!("an int and a bool are both numbers")
};
let byte = byte
.to_i64()
.and_then(|value| u8::try_from(value).ok())
.ok_or_else(|| Error::value_error("byte must be in range(0, 256)"))?;
haystack.contains(&byte)
}
other => {
return Err(Error::type_error(format!(
"a bytes-like object is required, not '{}'",
other.type_name()
)));
}
},
Object::Tuple(items) => items.iter().any(|item| item.same_value(value)),
Object::List(items) => items.borrow().iter().any(|item| item.same_value(value)),
Object::Dict(entries) => entries.borrow().contains(&key(value, "dict key")?),
Object::Set(members) => members.borrow().contains(&key(value, "set element")?),
other => {
return Err(Error::type_error(format!(
"argument of type '{}' is not a container or iterable",
other.type_name()
)));
}
};
Ok(Object::Bool(found))
}
enum Num<'a> {
Int(&'a Int),
Float(f64),
}
enum Pair<'a> {
Ints(&'a Int, &'a Int),
Floats(f64, f64),
}
static FALSE: Int = Int::Small(0);
static TRUE: Int = Int::Small(1);
fn number(value: &Object) -> Option<Num<'_>> {
match value {
Object::Bool(true) => Some(Num::Int(&TRUE)),
Object::Bool(false) => Some(Num::Int(&FALSE)),
Object::Int(value) => Some(Num::Int(value)),
Object::Float(value) => Some(Num::Float(*value)),
_ => None,
}
}
fn promote<'a>(left: &'a Object, right: &'a Object) -> Result<Option<Pair<'a>>> {
let (Some(left), Some(right)) = (number(left), number(right)) else {
return Ok(None);
};
Ok(Some(match (left, right) {
(Num::Int(a), Num::Int(b)) => Pair::Ints(a, b),
(Num::Float(a), Num::Float(b)) => Pair::Floats(a, b),
(Num::Int(a), Num::Float(b)) => Pair::Floats(to_float(a)?, b),
(Num::Float(a), Num::Int(b)) => Pair::Floats(a, to_float(b)?),
}))
}
fn to_float(value: &Int) -> Result<f64> {
value
.to_f64()
.ok_or_else(|| Error::overflow("int too large to convert to float"))
}
fn float_pow(base: f64, exponent: f64) -> Result<Object> {
if base == 0.0 && exponent < 0.0 {
return Err(Error::zero_division("zero to a negative power"));
}
if base < 0.0 && exponent.is_finite() && exponent.fract() != 0.0 {
return Err(Error::new(
Kind::NotImplementedError,
"a negative number raised to a fractional power is a complex number, \
and complex numbers are not implemented yet",
));
}
let value = base.powf(exponent);
if value.is_infinite() && base.is_finite() && exponent.is_finite() {
return Err(Error::overflow("(34, 'Result too large')"));
}
Ok(Object::Float(value))
}
fn float_div_mod(left: f64, right: f64) -> Result<(f64, f64)> {
if right == 0.0 {
return Err(divide_by_zero());
}
let mut remainder = left % right;
let mut quotient = (left - remainder) / right;
if remainder == 0.0 {
remainder = 0.0_f64.copysign(right);
} else if (right < 0.0) != (remainder < 0.0) {
remainder += right;
quotient -= 1.0;
}
let quotient = if quotient == 0.0 {
0.0_f64.copysign(left / right)
} else {
let floor = quotient.floor();
if quotient - floor > 0.5 {
floor + 1.0
} else {
floor
}
};
Ok((quotient, remainder))
}
fn shift(
left: &Object,
right: &Object,
symbol: &str,
apply: impl Fn(&Int, &Int) -> Option<Int>,
) -> Result<Object> {
let (Some(Num::Int(a)), Some(Num::Int(b))) = (number(left), number(right)) else {
return Err(unsupported(symbol, left, right));
};
if b.is_negative() {
return Err(Error::value_error("negative shift count"));
}
match apply(a, b) {
Some(value) => Ok(Object::Int(value)),
None => Err(Error::new(Kind::MemoryError, "")),
}
}
fn bitwise(
left: &Object,
right: &Object,
symbol: &str,
on_ints: impl Fn(&Int, &Int) -> Int,
on_sets: impl Fn(&Set, &Set) -> Set,
) -> Result<Object> {
if let (Some(Num::Int(a)), Some(Num::Int(b))) = (number(left), number(right)) {
let value = on_ints(a, b);
if matches!((left, right), (Object::Bool(_), Object::Bool(_))) {
return Ok(Object::Bool(!value.is_zero()));
}
return Ok(Object::Int(value));
}
if let (Object::Set(a), Object::Set(b)) = (left, right) {
let (a, b) = (a.borrow(), b.borrow());
return Ok(Object::set(on_sets(&a, &b)));
}
Err(unsupported(symbol, left, right))
}
fn index(value: &Object) -> Option<Result<usize>> {
let count = match number(value)? {
Num::Int(count) => count,
Num::Float(_) => return None,
};
if count.is_negative() {
return Some(Ok(0));
}
Some(
count
.to_usize()
.filter(|count| isize::try_from(*count).is_ok())
.ok_or_else(|| Error::overflow("cannot fit 'int' into an index-sized integer")),
)
}
fn repeat(value: &Object, count: usize) -> Result<Option<Object>> {
let repeated = match value {
Object::Str(text) => {
let points: Vec<u32> = text.code_points().collect();
room(points.len(), count, size_of::<char>())?;
let mut buf = StrBuf::new();
for _ in 0..count {
for point in &points {
buf.push_code_point(*point);
}
}
Object::Str(Rc::new(buf.finish()))
}
Object::Bytes(bytes) => {
room(bytes.len(), count, 1)?;
Object::Bytes(bytes.repeat(count).into())
}
Object::Tuple(items) => {
room(items.len(), count, size_of::<Object>())?;
Object::tuple(repeated_elements(items, count))
}
Object::List(items) => {
let items = items.borrow();
room(items.len(), count, size_of::<Object>())?;
Object::list(repeated_elements(&items, count))
}
_ => return Ok(None),
};
Ok(Some(repeated))
}
fn repeated_elements(items: &[Object], count: usize) -> Vec<Object> {
let mut repeated = Vec::with_capacity(items.len().saturating_mul(count));
for _ in 0..count {
repeated.extend(items.iter().cloned());
}
repeated
}
fn room(len: usize, count: usize, element: usize) -> Result<()> {
let bytes = u64::try_from(len)
.ok()
.and_then(|len| len.checked_mul(u64::try_from(count).ok()?))
.and_then(|total| total.checked_mul(u64::try_from(element).ok()?));
match bytes {
Some(bytes) if bytes <= REPEAT_LIMIT => Ok(()),
_ => Err(Error::new(Kind::MemoryError, "")),
}
}
fn order(op: Compare, left: &Object, right: &Object) -> Result<bool> {
if let (Some(a), Some(b)) = (number(left), number(right)) {
return Ok(numeric_order(a, b).is_some_and(|ordering| op.decide(ordering)));
}
match (left, right) {
(Object::Str(a), Object::Str(b)) => Ok(op.decide(a.code_points().cmp(b.code_points()))),
(Object::Bytes(a), Object::Bytes(b)) => Ok(op.decide(a.cmp(b))),
(Object::Tuple(a), Object::Tuple(b)) => sequence_order(op, a, b),
(Object::List(a), Object::List(b)) => sequence_order(op, &a.borrow(), &b.borrow()),
(Object::Set(a), Object::Set(b)) => {
let (a, b) = (a.borrow(), b.borrow());
let subset = a.len() <= b.len() && a.iter().all(|key| b.contains(key));
let superset = b.len() <= a.len() && b.iter().all(|key| a.contains(key));
Ok(match op {
Compare::Lt => subset && !superset,
Compare::Le => subset,
Compare::Gt => superset && !subset,
Compare::Ge => superset,
Compare::Eq | Compare::Ne => unreachable!("equality never reaches here"),
})
}
_ => Err(Error::type_error(format!(
"'{}' not supported between instances of '{}' and '{}'",
op.symbol(),
left.type_name(),
right.type_name()
))),
}
}
fn sequence_order(op: Compare, left: &[Object], right: &[Object]) -> Result<bool> {
for (a, b) in left.iter().zip(right) {
if !a.same_value(b) {
return order(op, a, b);
}
}
Ok(op.decide(left.len().cmp(&right.len())))
}
fn numeric_order(left: Num<'_>, right: Num<'_>) -> Option<Ordering> {
match (left, right) {
(Num::Int(a), Num::Int(b)) => Some(a.cmp(b)),
(Num::Float(a), Num::Float(b)) => a.partial_cmp(&b),
(Num::Int(a), Num::Float(b)) => int_cmp_float(a, b),
(Num::Float(a), Num::Int(b)) => int_cmp_float(b, a).map(Ordering::reverse),
}
}
#[expect(
clippy::cast_possible_truncation,
reason = "the cast is guarded by the range check above it, and the float is \
known to be integral there, so it is exact"
)]
fn int_cmp_float(int: &Int, float: f64) -> Option<Ordering> {
if float.is_nan() {
return None;
}
if float.is_infinite() {
return Some(if float > 0.0 {
Ordering::Less
} else {
Ordering::Greater
});
}
let whole = float.trunc();
let fraction = zero_cmp(float - whole);
if let Int::Small(value) = int
&& (-9_223_372_036_854_775_808.0..9_223_372_036_854_775_808.0).contains(&whole)
{
return Some(value.cmp(&(whole as i64)).then(fraction));
}
let whole = BigInt::from_f64(whole).expect("a finite truncated float is an integer");
Some(int.to_big().cmp(&whole).then(fraction))
}
fn zero_cmp(fraction: f64) -> Ordering {
if fraction > 0.0 {
Ordering::Less
} else if fraction < 0.0 {
Ordering::Greater
} else {
Ordering::Equal
}
}
fn substring(haystack: &Str, needle: &Str) -> bool {
if let (Str::Utf8(haystack), Str::Utf8(needle)) = (haystack, needle) {
return haystack.contains(needle.as_ref());
}
let haystack: Vec<u32> = haystack.code_points().collect();
let needle: Vec<u32> = needle.code_points().collect();
subslice(&haystack, &needle)
}
fn subslice<T: PartialEq>(haystack: &[T], needle: &[T]) -> bool {
needle.is_empty()
|| (needle.len() <= haystack.len()
&& haystack.windows(needle.len()).any(|run| run == needle))
}
pub fn key(value: &Object, role: &str) -> Result<Key> {
Key::new(value.clone()).map_err(|unhashable| {
Error::type_error(format!(
"cannot use '{}' as a {role} ({})",
unhashable.type_name,
unhashable.message()
))
})
}
fn divide_by_zero() -> Error {
Error::zero_division("division by zero")
}
fn unsupported(symbol: &str, left: &Object, right: &Object) -> Error {
Error::type_error(format!(
"unsupported operand type(s) for {symbol}: '{}' and '{}'",
left.type_name(),
right.type_name()
))
}
fn concat_error(left: &Object, right: &Object) -> Error {
let named = |kind| {
Error::type_error(format!(
"can only concatenate {kind} (not \"{}\") to {kind}",
right.type_name()
))
};
match left {
Object::Str(_) => named("str"),
Object::List(_) => named("list"),
Object::Tuple(_) => named("tuple"),
Object::Bytes(_) => {
Error::type_error(format!("can't concat {} to bytes", right.type_name()))
}
_ => unsupported("+", left, right),
}
}
fn repeat_error(left: &Object, right: &Object) -> Error {
let sequence = |value: &Object| {
matches!(
value,
Object::Str(_) | Object::Bytes(_) | Object::Tuple(_) | Object::List(_)
)
};
let culprit = if sequence(right) {
Some(left)
} else if sequence(left) {
Some(right)
} else {
None
};
match culprit {
Some(culprit) => Error::type_error(format!(
"can't multiply sequence by non-int of type '{}'",
culprit.type_name()
)),
None => unsupported("*", left, right),
}
}
fn bad_unary(symbol: &str, value: &Object) -> Error {
Error::type_error(format!(
"bad operand type for unary {symbol}: '{}'",
value.type_name()
))
}
#[cfg(test)]
mod tests {
use super::*;
fn i(value: i64) -> Object {
Object::int(value)
}
fn two_to(power: i64) -> Object {
Object::Int(
Int::Small(2)
.pow(&Int::Small(power))
.expect("a power this size fits"),
)
}
fn f(value: f64) -> Object {
Object::Float(value)
}
fn s(value: &str) -> Object {
Object::str(value)
}
fn y(value: &[u8]) -> Object {
Object::Bytes(value.into())
}
fn t(items: Vec<Object>) -> Object {
Object::tuple(items)
}
fn l(items: Vec<Object>) -> Object {
Object::list(items)
}
fn set(items: Vec<Object>) -> Object {
Object::set(
items
.into_iter()
.map(|item| Key::new(item).expect("a hashable member"))
.collect(),
)
}
fn dict(pairs: Vec<(Object, Object)>) -> Object {
Object::dict(
pairs
.into_iter()
.map(|(key, value)| (Key::new(key).expect("a hashable key"), value))
.collect(),
)
}
fn wide(points: &[u32]) -> Object {
let mut buf = StrBuf::new();
for point in points {
buf.push_code_point(*point);
}
Object::Str(Rc::new(buf.finish()))
}
fn ok(result: Result<Object>) -> String {
result.expect("an answer").repr()
}
fn bad(result: Result<Object>) -> String {
result.expect_err("an exception").to_string()
}
#[test]
fn numbers_add_across_their_types() {
assert_eq!(ok(add(&i(1), &i(2))), "3");
assert_eq!(ok(add(&Object::Bool(true), &Object::Bool(true))), "2");
assert_eq!(ok(add(&i(1), &f(0.5))), "1.5");
assert_eq!(ok(add(&f(0.5), &Object::Bool(true))), "1.5");
}
#[test]
fn sequences_join_only_with_their_own_kind() {
assert_eq!(ok(add(&s("ab"), &s("cd"))), "'abcd'");
assert_eq!(ok(add(&y(b"ab"), &y(b"cd"))), "b'abcd'");
assert_eq!(ok(add(&t(vec![i(1)]), &t(vec![i(2)]))), "(1, 2)");
assert_eq!(ok(add(&l(vec![i(1)]), &l(vec![i(2)]))), "[1, 2]");
}
#[test]
fn a_list_added_to_itself_is_it_twice_over() {
let items = l(vec![i(1)]);
assert_eq!(ok(add(&items, &items)), "[1, 1]");
}
#[test]
fn a_bad_addition_says_which_operand_it_is_about() {
assert_eq!(
bad(add(&i(1), &s("a"))),
"TypeError: unsupported operand type(s) for +: 'int' and 'str'"
);
assert_eq!(
bad(add(&s("a"), &i(1))),
"TypeError: can only concatenate str (not \"int\") to str"
);
assert_eq!(
bad(add(&s("a"), &y(b"b"))),
"TypeError: can only concatenate str (not \"bytes\") to str"
);
assert_eq!(
bad(add(&l(vec![i(1)]), &t(vec![i(2)]))),
"TypeError: can only concatenate list (not \"tuple\") to list"
);
assert_eq!(
bad(add(&t(vec![i(1)]), &l(vec![i(2)]))),
"TypeError: can only concatenate tuple (not \"list\") to tuple"
);
assert_eq!(
bad(add(&y(b"a"), &s("a"))),
"TypeError: can't concat str to bytes"
);
assert_eq!(
bad(add(&y(b"a"), &i(1))),
"TypeError: can't concat int to bytes"
);
assert_eq!(
bad(add(&i(1), &l(vec![]))),
"TypeError: unsupported operand type(s) for +: 'int' and 'list'"
);
assert_eq!(
bad(add(&set(vec![i(1)]), &set(vec![i(2)]))),
"TypeError: unsupported operand type(s) for +: 'set' and 'set'"
);
}
#[test]
fn an_integer_too_large_for_a_float_says_so_rather_than_rounding() {
let huge = two_to(2000);
for result in [
add(&huge, &f(1.0)),
mul(&huge, &f(1.0)),
floor_div(&huge, &f(1.0)),
div_mod(&huge, &f(1.0)),
pow(&huge, &f(0.5)),
] {
assert_eq!(
bad(result),
"OverflowError: int too large to convert to float"
);
}
}
#[test]
fn a_comparison_against_a_float_stays_exact_however_large_the_integer() {
let huge = two_to(2000);
assert_eq!(ok(compare(Compare::Lt, &huge, &f(1.0))), "False");
assert_eq!(ok(compare(Compare::Eq, &huge, &f(1.0))), "False");
assert_eq!(ok(compare(Compare::Gt, &huge, &f(1e308))), "True");
assert_eq!(ok(compare(Compare::Lt, &i(1), &f(1.5))), "True");
assert_eq!(ok(compare(Compare::Gt, &i(2), &f(1.5))), "True");
assert_eq!(ok(compare(Compare::Lt, &i(-2), &f(-1.5))), "True");
assert_eq!(ok(compare(Compare::Lt, &i(1), &f(f64::INFINITY))), "True");
assert_eq!(
ok(compare(Compare::Gt, &huge, &f(f64::NEG_INFINITY))),
"True"
);
}
#[test]
fn a_sequence_repeats_by_an_integer_from_either_side() {
assert_eq!(ok(mul(&s("ab"), &i(3))), "'ababab'");
assert_eq!(ok(mul(&i(3), &s("ab"))), "'ababab'");
assert_eq!(ok(mul(&Object::Bool(true), &s("ab"))), "'ab'");
assert_eq!(ok(mul(&y(b"ab"), &i(2))), "b'abab'");
assert_eq!(ok(mul(&t(vec![i(1)]), &i(3))), "(1, 1, 1)");
assert_eq!(ok(mul(&i(3), &l(vec![i(0)]))), "[0, 0, 0]");
assert_eq!(ok(mul(&wide(&[0xD800]), &i(2))), r"'\ud800\ud800'");
}
#[test]
fn a_count_below_one_gives_an_empty_sequence_rather_than_an_error() {
assert_eq!(ok(mul(&s("a"), &i(-1))), "''");
assert_eq!(ok(mul(&s("a"), &i(0))), "''");
assert_eq!(ok(mul(&l(vec![i(1)]), &i(-1))), "[]");
}
#[test]
fn a_count_that_is_not_an_integer_names_itself() {
assert_eq!(
bad(mul(&s("a"), &f(1.5))),
"TypeError: can't multiply sequence by non-int of type 'float'"
);
assert_eq!(
bad(mul(&f(1.5), &s("a"))),
"TypeError: can't multiply sequence by non-int of type 'float'"
);
assert_eq!(
bad(mul(&Object::None, &s("a"))),
"TypeError: can't multiply sequence by non-int of type 'NoneType'"
);
assert_eq!(
bad(mul(&s("a"), &Object::None)),
"TypeError: can't multiply sequence by non-int of type 'NoneType'"
);
assert_eq!(
bad(mul(&set(vec![i(1)]), &i(2))),
"TypeError: unsupported operand type(s) for *: 'set' and 'int'"
);
assert_eq!(
bad(mul(&Object::None, &Object::None)),
"TypeError: unsupported operand type(s) for *: 'NoneType' and 'NoneType'"
);
}
#[test]
fn a_repetition_no_machine_could_hold_is_refused_before_it_is_attempted() {
assert_eq!(
bad(mul(&s("a"), &two_to(63))),
"OverflowError: cannot fit 'int' into an index-sized integer"
);
assert_eq!(
bad(mul(&s("a"), &i(1_000_000_000_000_000_000))),
"MemoryError"
);
assert_eq!(
bad(mul(&l(vec![i(1)]), &i(1_000_000_000_000_000_000))),
"MemoryError"
);
}
#[test]
fn dividing_gives_a_float_however_the_operands_are_spelled() {
assert_eq!(ok(true_div(&i(1), &i(2))), "0.5");
assert_eq!(ok(true_div(&i(4), &i(2))), "2.0");
assert_eq!(ok(true_div(&f(1.0), &i(2))), "0.5");
}
#[test]
fn every_divisor_of_zero_raises_the_same_thing() {
for result in [
true_div(&i(1), &i(0)),
floor_div(&i(1), &i(0)),
modulo(&i(1), &i(0)),
div_mod(&i(1), &i(0)),
true_div(&f(1.0), &i(0)),
floor_div(&f(1.0), &i(0)),
modulo(&f(1.0), &i(0)),
div_mod(&f(7.0), &i(0)),
true_div(&f(1.0), &f(-0.0)),
] {
assert_eq!(bad(result), "ZeroDivisionError: division by zero");
}
}
#[test]
fn a_quotient_with_no_float_to_land_on_says_so() {
assert_eq!(
bad(true_div(&two_to(2000), &i(1))),
"OverflowError: integer division result too large for a float"
);
assert_eq!(ok(true_div(&i(1), &two_to(2000))), "0.0");
}
#[test]
fn flooring_goes_down_and_the_remainder_takes_the_divisors_sign() {
assert_eq!(ok(floor_div(&i(7), &i(-3))), "-3");
assert_eq!(ok(modulo(&i(7), &i(-3))), "-2");
assert_eq!(ok(modulo(&i(-7), &i(3))), "2");
assert_eq!(ok(div_mod(&i(7), &i(-3))), "(-3, -2)");
assert_eq!(ok(floor_div(&f(7.0), &f(-2.0))), "-4.0");
assert_eq!(ok(modulo(&f(-5.5), &f(2.0))), "0.5");
assert_eq!(ok(modulo(&f(5.5), &f(-2.0))), "-0.5");
assert_eq!(ok(div_mod(&f(-7.0), &f(-2.0))), "(3.0, -1.0)");
}
#[test]
fn a_float_quotient_keeps_the_bits_the_division_would_have_lost() {
assert_eq!(ok(floor_div(&f(7.0), &f(0.5))), "14.0");
assert_eq!(ok(floor_div(&f(1e308), &f(1e-10))), "inf");
}
#[test]
fn a_zero_quotient_and_a_zero_remainder_each_keep_a_sign() {
assert_eq!(ok(floor_div(&f(-0.0), &f(1.0))), "-0.0");
assert_eq!(ok(floor_div(&f(0.0), &f(-1.0))), "-0.0");
assert_eq!(ok(modulo(&f(4.0), &f(-2.0))), "-0.0");
}
#[test]
fn raising_to_a_power_leaves_the_integers_when_the_exponent_is_negative() {
assert_eq!(ok(pow(&i(2), &i(10))), "1024");
assert_eq!(ok(pow(&i(0), &i(0))), "1");
assert_eq!(ok(pow(&i(2), &i(-2))), "0.25");
assert_eq!(ok(pow(&i(-2), &i(-1))), "-0.5");
assert_eq!(ok(pow(&Object::Bool(true), &i(-1))), "1.0");
assert_eq!(ok(pow(&i(2), &f(0.5))), "1.4142135623730951");
assert_eq!(ok(pow(&f(0.0), &i(0))), "1.0");
assert_eq!(ok(pow(&i(1), &f(f64::INFINITY))), "1.0");
}
#[test]
fn zero_to_a_negative_power_is_its_own_exception() {
for result in [
pow(&i(0), &i(-1)),
pow(&f(0.0), &f(-1.0)),
pow(&f(-0.0), &i(-1)),
] {
assert_eq!(bad(result), "ZeroDivisionError: zero to a negative power");
}
}
#[test]
fn a_power_that_runs_off_the_top_of_a_double_reports_what_the_c_library_said() {
assert_eq!(
bad(pow(&f(2.0), &i(10000))),
"OverflowError: (34, 'Result too large')"
);
assert_eq!(
bad(pow(&f(1e300), &i(2))),
"OverflowError: (34, 'Result too large')"
);
assert_eq!(ok(pow(&f(f64::INFINITY), &i(2))), "inf");
}
#[test]
fn a_negative_base_to_a_fractional_power_needs_complex_numbers() {
assert_eq!(
bad(pow(&i(-2), &f(0.5))),
"NotImplementedError: a negative number raised to a fractional power is a \
complex number, and complex numbers are not implemented yet"
);
assert_eq!(ok(pow(&f(-1.0), &f(2.0))), "1.0");
}
#[test]
fn a_bad_power_names_both_of_its_spellings() {
assert_eq!(
bad(pow(&i(1), &s("a"))),
"TypeError: unsupported operand type(s) for ** or pow(): 'int' and 'str'"
);
}
#[test]
fn shifting_needs_a_count_that_is_not_negative() {
assert_eq!(
ok(lshift(&i(1), &i(100))),
"1267650600228229401496703205376"
);
assert_eq!(ok(rshift(&i(-1), &i(100))), "-1");
assert_eq!(ok(rshift(&i(1), &i(1_000_000))), "0");
assert_eq!(ok(lshift(&Object::Bool(true), &i(1))), "2");
assert_eq!(
bad(lshift(&i(1), &i(-1))),
"ValueError: negative shift count"
);
assert_eq!(
bad(rshift(&i(1), &i(-1))),
"ValueError: negative shift count"
);
assert_eq!(
bad(lshift(&i(1), &i(1_000_000_000_000_000_000))),
"MemoryError"
);
assert_eq!(
bad(lshift(&i(1), &f(1.0))),
"TypeError: unsupported operand type(s) for <<: 'int' and 'float'"
);
}
#[test]
fn two_bools_give_a_bool_and_anything_else_gives_an_int() {
assert_eq!(
ok(bit_and(&Object::Bool(true), &Object::Bool(true))),
"True"
);
assert_eq!(
ok(bit_or(&Object::Bool(true), &Object::Bool(false))),
"True"
);
assert_eq!(
ok(bit_xor(&Object::Bool(true), &Object::Bool(true))),
"False"
);
assert_eq!(ok(bit_and(&Object::Bool(true), &i(1))), "1");
assert_eq!(ok(bit_and(&i(6), &i(3))), "2");
assert_eq!(ok(rshift(&Object::Bool(true), &Object::Bool(true))), "0");
}
#[test]
fn the_bitwise_operators_are_the_set_operators_too() {
let a = set(vec![i(1), i(2)]);
assert_eq!(ok(sub(&a, &set(vec![i(2)]))), "{1}");
assert_eq!(ok(bit_or(&a, &set(vec![i(3)]))), "{1, 2, 3}");
assert_eq!(ok(bit_and(&a, &set(vec![i(2)]))), "{2}");
assert_eq!(ok(bit_xor(&a, &set(vec![i(2)]))), "{1}");
assert_eq!(ok(sub(&set(vec![i(1)]), &set(vec![f(1.0)]))), "set()");
assert_eq!(ok(bit_or(&set(vec![i(1)]), &set(vec![f(1.0)]))), "{1}");
assert_eq!(
bad(bit_or(&a, &l(vec![i(1)]))),
"TypeError: unsupported operand type(s) for |: 'set' and 'list'"
);
assert_eq!(
bad(sub(&a, &l(vec![i(1)]))),
"TypeError: unsupported operand type(s) for -: 'set' and 'list'"
);
}
#[test]
fn the_unary_operators_widen_a_bool_to_the_integer_it_is() {
assert_eq!(ok(neg(&Object::Bool(true))), "-1");
assert_eq!(ok(pos(&Object::Bool(true))), "1");
assert_eq!(ok(invert(&Object::Bool(true))), "-2");
assert_eq!(ok(neg(&f(1.5))), "-1.5");
assert_eq!(ok(invert(&i(1))), "-2");
assert_eq!(
bad(invert(&f(1.5))),
"TypeError: bad operand type for unary ~: 'float'"
);
assert_eq!(
bad(pos(&s("a"))),
"TypeError: bad operand type for unary +: 'str'"
);
assert_eq!(
bad(neg(&Object::None)),
"TypeError: bad operand type for unary -: 'NoneType'"
);
}
#[test]
fn abs_drops_the_sign_and_the_bool_with_it() {
assert_eq!(ok(abs(&i(-3))), "3");
assert_eq!(ok(abs(&i(3))), "3");
assert_eq!(ok(abs(&Object::Bool(true))), "1");
assert_eq!(ok(abs(&f(-1.5))), "1.5");
assert_eq!(ok(abs(&f(-0.0))), "0.0");
assert_eq!(
bad(abs(&s("a"))),
"TypeError: bad operand type for abs(): 'str'"
);
}
#[test]
fn not_answers_for_every_object() {
assert_eq!(not(&s("a")).repr(), "False");
assert_eq!(not(&l(vec![])).repr(), "True");
assert_eq!(not(&Object::None).repr(), "True");
assert_eq!(not(&Object::Ellipsis).repr(), "False");
}
#[test]
fn numbers_compare_across_their_types() {
assert_eq!(ok(compare(Compare::Lt, &Object::Bool(true), &i(2))), "True");
assert_eq!(ok(compare(Compare::Le, &i(1), &f(1.0))), "True");
assert_eq!(ok(compare(Compare::Eq, &f(0.0), &f(-0.0))), "True");
assert_eq!(ok(compare(Compare::Gt, &f(1.5), &i(1))), "True");
assert_eq!(ok(compare(Compare::Ne, &i(1), &f(1.0))), "False");
}
#[test]
fn a_nan_is_on_neither_side_of_anything() {
let nan = f(f64::NAN);
for op in [
Compare::Lt,
Compare::Le,
Compare::Gt,
Compare::Ge,
Compare::Eq,
] {
assert_eq!(ok(compare(op, &nan, &i(1))), "False");
assert_eq!(ok(compare(op, &nan, &nan)), "False");
}
assert_eq!(ok(compare(Compare::Ne, &nan, &nan)), "True");
}
#[test]
fn a_sequence_compares_at_the_first_place_it_differs() {
assert_eq!(
ok(compare(
Compare::Lt,
&l(vec![i(1), i(2)]),
&l(vec![i(1), i(2), i(3)])
)),
"True"
);
assert_eq!(ok(compare(Compare::Lt, &t(vec![]), &t(vec![i(1)]))), "True");
assert_eq!(ok(compare(Compare::Lt, &s("abc"), &s("abd"))), "True");
assert_eq!(ok(compare(Compare::Lt, &s("Z"), &s("a"))), "True");
assert_eq!(ok(compare(Compare::Lt, &y(&[0]), &y(&[1]))), "True");
assert_eq!(
ok(compare(
Compare::Lt,
&t(vec![i(1), s("a")]),
&t(vec![i(2), s("a")])
)),
"True"
);
assert_eq!(
bad(compare(
Compare::Lt,
&t(vec![i(1), s("a")]),
&t(vec![i(1), i(2)])
)),
"TypeError: '<' not supported between instances of 'str' and 'int'"
);
}
#[test]
fn a_sequence_checks_identity_first_and_a_nan_shows_it() {
let nan = f(f64::NAN);
assert_eq!(
ok(compare(
Compare::Eq,
&t(vec![nan.clone()]),
&t(vec![nan.clone()])
)),
"True"
);
assert_eq!(
ok(compare(
Compare::Lt,
&t(vec![nan.clone()]),
&t(vec![nan.clone()])
)),
"False"
);
assert_eq!(
ok(compare(
Compare::Le,
&t(vec![nan.clone()]),
&t(vec![nan.clone()])
)),
"True"
);
assert_eq!(
ok(compare(
Compare::Lt,
&t(vec![i(1), nan]),
&t(vec![i(1), i(2)])
)),
"False"
);
}
#[test]
fn a_set_is_ordered_by_containment_rather_than_by_size() {
assert_eq!(
ok(compare(
Compare::Lt,
&set(vec![i(1)]),
&set(vec![i(1), i(2)])
)),
"True"
);
assert_eq!(
ok(compare(Compare::Lt, &set(vec![i(1)]), &set(vec![i(2)]))),
"False"
);
assert_eq!(
ok(compare(Compare::Gt, &set(vec![i(1)]), &set(vec![i(2)]))),
"False"
);
assert_eq!(
ok(compare(Compare::Le, &set(vec![i(1)]), &set(vec![i(1)]))),
"True"
);
assert_eq!(
ok(compare(
Compare::Gt,
&set(vec![i(1), i(2)]),
&set(vec![i(1)])
)),
"True"
);
}
#[test]
fn a_pair_with_no_order_says_so_and_still_answers_equality() {
assert_eq!(
bad(compare(Compare::Lt, &i(1), &s("a"))),
"TypeError: '<' not supported between instances of 'int' and 'str'"
);
assert_eq!(
bad(compare(Compare::Le, &i(1), &s("a"))),
"TypeError: '<=' not supported between instances of 'int' and 'str'"
);
assert_eq!(
bad(compare(Compare::Lt, &Object::None, &Object::None)),
"TypeError: '<' not supported between instances of 'NoneType' and 'NoneType'"
);
assert_eq!(
bad(compare(Compare::Lt, &l(vec![i(1)]), &t(vec![i(1)]))),
"TypeError: '<' not supported between instances of 'list' and 'tuple'"
);
assert_eq!(
bad(compare(Compare::Lt, &s("a"), &y(b"a"))),
"TypeError: '<' not supported between instances of 'str' and 'bytes'"
);
assert_eq!(
bad(compare(
Compare::Lt,
&dict(vec![(i(1), i(2))]),
&dict(vec![(i(1), i(3))])
)),
"TypeError: '<' not supported between instances of 'dict' and 'dict'"
);
assert_eq!(
ok(compare(Compare::Eq, &Object::None, &Object::None)),
"True"
);
assert_eq!(ok(compare(Compare::Ne, &i(1), &s("a"))), "True");
}
#[test]
fn a_string_contains_strings_and_nothing_else() {
assert_eq!(ok(contains(&s("xaby"), &s("ab"))), "True");
assert_eq!(ok(contains(&s("abc"), &s(""))), "True");
assert_eq!(ok(contains(&s("abc"), &s("d"))), "False");
assert_eq!(
bad(contains(&s("abc"), &i(1))),
"TypeError: 'in <string>' requires string as left operand, not int"
);
}
#[test]
fn a_substring_search_works_on_a_string_that_has_no_utf8() {
let text = wide(&[u32::from('a'), 0xD800, u32::from('b')]);
assert_eq!(ok(contains(&text, &wide(&[0xD800]))), "True");
assert_eq!(ok(contains(&text, &s("ab"))), "False");
assert_eq!(ok(contains(&text, &s("b"))), "True");
}
#[test]
fn a_bytes_contains_runs_of_bytes_and_single_byte_values() {
assert_eq!(ok(contains(&y(b"abc"), &y(b"ab"))), "True");
assert_eq!(ok(contains(&y(b"abc"), &y(b"x"))), "False");
assert_eq!(ok(contains(&y(b"abc"), &i(i64::from(b'a')))), "True");
assert_eq!(ok(contains(&y(b"abc"), &i(1))), "False");
assert_eq!(
bad(contains(&y(b"abc"), &i(256))),
"ValueError: byte must be in range(0, 256)"
);
assert_eq!(
bad(contains(&y(b"abc"), &i(-1))),
"ValueError: byte must be in range(0, 256)"
);
assert_eq!(
bad(contains(&y(b"abc"), &s("a"))),
"TypeError: a bytes-like object is required, not 'str'"
);
}
#[test]
fn a_lookup_by_an_unhashable_key_names_what_it_was_being_used_as() {
assert_eq!(
bad(contains(&set(vec![i(1)]), &l(vec![]))),
"TypeError: cannot use 'list' as a set element (unhashable type: 'list')"
);
assert_eq!(
bad(contains(&dict(vec![(i(1), i(2))]), &l(vec![]))),
"TypeError: cannot use 'list' as a dict key (unhashable type: 'list')"
);
}
#[test]
fn a_container_is_searched_by_value_and_everything_else_is_not_a_container() {
assert_eq!(ok(contains(&t(vec![i(1), i(2)]), &i(1))), "True");
assert_eq!(
ok(contains(&l(vec![l(vec![i(1)])]), &l(vec![i(1)]))),
"True"
);
assert_eq!(ok(contains(&dict(vec![(i(1), i(2))]), &f(1.0))), "True");
assert_eq!(ok(contains(&dict(vec![]), &s("a"))), "False");
assert_eq!(ok(contains(&set(vec![s("a")]), &s("a"))), "True");
assert_eq!(
bad(contains(&Object::None, &i(1))),
"TypeError: argument of type 'NoneType' is not a container or iterable"
);
assert_eq!(
bad(contains(&i(2), &i(1))),
"TypeError: argument of type 'int' is not a container or iterable"
);
}
}
pub fn get_item(container: &Object, index: &Object) -> Result<Object> {
match container {
Object::List(items) => {
match subscript(index, items.borrow().len(), Seq::List, Write::No)? {
Subscript::At(at) => Ok(items.borrow()[at].clone()),
Subscript::Range(range) => {
let items = items.borrow();
Ok(Object::list(
range.offsets().map(|at| items[at].clone()).collect(),
))
}
}
}
Object::Tuple(items) => match subscript(index, items.len(), Seq::Tuple, Write::No)? {
Subscript::At(at) => Ok(items[at].clone()),
Subscript::Range(range) => Ok(Object::tuple(
range.offsets().map(|at| items[at].clone()).collect(),
)),
},
Object::Str(text) => match subscript(index, text.len(), Seq::Str, Write::No)? {
Subscript::At(at) => {
let mut out = StrBuf::new();
out.push_code_point(text.code_point_at(at).expect("the index was checked"));
Ok(Object::Str(Rc::new(out.finish())))
}
Subscript::Range(range) => {
let points: Vec<u32> = text.code_points().collect();
let mut out = StrBuf::new();
for at in range.offsets() {
out.push_code_point(points[at]);
}
Ok(Object::Str(Rc::new(out.finish())))
}
},
Object::Bytes(bytes) => match subscript(index, bytes.len(), Seq::Bytes, Write::No)? {
Subscript::At(at) => Ok(Object::int(i64::from(bytes[at]))),
Subscript::Range(range) => {
let taken: Vec<u8> = range.offsets().map(|at| bytes[at]).collect();
Ok(Object::Bytes(taken.into()))
}
},
Object::Dict(entries) => {
let key = key(index, "dict key")?;
entries
.borrow()
.get(&key)
.cloned()
.ok_or_else(|| missing_key(index))
}
other => Err(not_subscriptable(other)),
}
}
pub fn set_item(container: &Object, index: &Object, value: &Object) -> Result<()> {
match container {
Object::List(items) => {
let len = items.borrow().len();
match subscript(index, len, Seq::List, Write::Yes)? {
Subscript::At(at) => {
items.borrow_mut()[at] = value.clone();
Ok(())
}
Subscript::Range(range) => {
let replacement = elements(value).ok_or_else(|| {
Error::type_error("must assign iterable to extended slice")
})?;
let mut items = items.borrow_mut();
if range.is_contiguous() {
let start = range.start.cast_unsigned();
items.splice(start..start + range.len, replacement);
return Ok(());
}
if replacement.len() != range.len {
return Err(Error::value_error(format!(
"attempt to assign sequence of size {} to extended slice of size {}",
replacement.len(),
range.len
)));
}
for (at, value) in range.offsets().zip(replacement) {
items[at] = value;
}
Ok(())
}
}
}
Object::Dict(entries) => {
entries
.borrow_mut()
.insert(key(index, "dict key")?, value.clone());
Ok(())
}
other => Err(Error::type_error(format!(
"'{}' object does not support item assignment",
other.type_name()
))),
}
}
pub fn del_item(container: &Object, index: &Object) -> Result<()> {
match container {
Object::List(items) => {
let len = items.borrow().len();
match subscript(index, len, Seq::List, Write::Yes)? {
Subscript::At(at) => {
items.borrow_mut().remove(at);
Ok(())
}
Subscript::Range(range) => {
let mut doomed: Vec<usize> = range.offsets().collect();
doomed.sort_unstable();
let mut items = items.borrow_mut();
for at in doomed.into_iter().rev() {
items.remove(at);
}
Ok(())
}
}
}
Object::Dict(entries) => entries
.borrow_mut()
.remove(&key(index, "dict key")?)
.map(|_| ())
.ok_or_else(|| missing_key(index)),
Object::Tuple(_) | Object::Str(_) | Object::Bytes(_) | Object::Set(_) => {
Err(Error::type_error(format!(
"'{}' object doesn't support item deletion",
container.type_name()
)))
}
other => Err(Error::type_error(format!(
"'{}' object does not support item deletion",
other.type_name()
))),
}
}
#[must_use]
pub fn len(value: &Object) -> Option<usize> {
Some(match value {
Object::Str(text) => text.len(),
Object::Bytes(bytes) => bytes.len(),
Object::Tuple(items) => items.len(),
Object::List(items) => items.borrow().len(),
Object::Dict(entries) => entries.borrow().len(),
Object::Set(members) => members.borrow().len(),
_ => return None,
})
}
#[must_use]
pub fn elements(value: &Object) -> Option<Vec<Object>> {
match value {
Object::List(items) => Some(items.borrow().clone()),
Object::Tuple(items) => Some(items.to_vec()),
Object::Str(text) => Some(
text.code_points()
.map(|point| {
let mut out = StrBuf::new();
out.push_code_point(point);
Object::Str(Rc::new(out.finish()))
})
.collect(),
),
Object::Bytes(bytes) => Some(bytes.iter().map(|&b| Object::int(i64::from(b))).collect()),
Object::Dict(entries) => Some(
entries
.borrow()
.iter()
.map(|(key, _)| key.object().clone())
.collect(),
),
Object::Set(members) => Some(
members
.borrow()
.iter()
.map(|key| key.object().clone())
.collect(),
),
_ => None,
}
}
enum Subscript {
At(usize),
Range(Indices),
}
#[derive(Clone, Copy)]
enum Seq {
List,
Tuple,
Str,
Bytes,
}
impl Seq {
fn not_an_index(self, index: &Object) -> Error {
let name = index.type_name();
Error::type_error(match self {
Seq::List => format!("list indices must be integers or slices, not {name}"),
Seq::Tuple => format!("tuple indices must be integers or slices, not {name}"),
Seq::Bytes => format!("byte indices must be integers or slices, not {name}"),
Seq::Str => format!("string indices must be integers, not '{name}'"),
})
}
fn out_of_range(self, write: Write) -> Error {
Error::new(
Kind::IndexError,
match (self, write) {
(Seq::List, Write::No) => "list index out of range",
(Seq::List, Write::Yes) => "list assignment index out of range",
(Seq::Tuple, _) => "tuple index out of range",
(Seq::Str, _) => "string index out of range",
(Seq::Bytes, _) => "index out of range",
},
)
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Write {
No,
Yes,
}
fn subscript(index: &Object, len: usize, seq: Seq, write: Write) -> Result<Subscript> {
match index {
Object::Slice(slice) => Ok(Subscript::Range(slice.indices(len)?)),
Object::Bool(_) | Object::Int(_) => {
let Some(Num::Int(at)) = number(index) else {
unreachable!("an int and a bool are both numbers")
};
Ok(Subscript::At(offset(at, len, seq, write)?))
}
other => Err(seq.not_an_index(other)),
}
}
fn offset(index: &Int, len: usize, seq: Seq, write: Write) -> Result<usize> {
let too_big = || {
Error::new(
Kind::IndexError,
"cannot fit 'int' into an index-sized integer",
)
};
let at = index.to_i64().ok_or_else(too_big)?;
let at = isize::try_from(at).map_err(|_| too_big())?;
let at = if at < 0 {
at.checked_add(len.cast_signed()).ok_or_else(too_big)?
} else {
at
};
if at < 0 || at.cast_unsigned() >= len {
return Err(seq.out_of_range(write));
}
Ok(at.cast_unsigned())
}
fn missing_key(key: &Object) -> Error {
Error::raised(Kind::KeyError, vec![key.clone()])
}
fn not_subscriptable(value: &Object) -> Error {
Error::type_error(format!(
"'{}' object is not subscriptable",
value.type_name()
))
}