use std::{
cmp::Ordering,
convert::From,
default::Default,
iter::{FromIterator, IntoIterator, Iterator},
};
use crate::{
binding::array,
types::{Value, ValueType},
AnyException, AnyObject, Enumerator, Fixnum, NilClass, Object, RString, TryConvert,
VerifiedObject,
};
#[derive(Debug)]
#[repr(C)]
pub struct Array {
value: Value,
}
impl Array {
pub fn convert<T: Object>(object: &T) -> Result<Self, AnyException> {
let object = object.value();
crate::binding::vm::protect_value(|| crate::binding::object::to_array(object))
.map(Self::from)
.map_err(AnyException::from)
}
pub fn new() -> Self {
Self::from(array::new())
}
pub fn with_capacity(capacity: usize) -> Self {
Self::from(array::with_capacity(capacity))
}
pub fn length(&self) -> usize {
array::len(self.value()) as usize
}
pub fn at(&self, index: i64) -> AnyObject {
let result = array::entry(self.value(), index);
AnyObject::from(result)
}
pub fn join(&self, separator: RString) -> RString {
let result = array::join(self.value(), separator.value());
RString::from(result)
}
pub fn push<T: Object>(&mut self, item: T) -> Self {
let result = array::push(self.value(), item.value());
Array::from(result)
}
pub fn store<T: Object>(&mut self, index: i64, item: T) {
array::store(self.value(), index, item.value());
}
pub fn pop(&mut self) -> AnyObject {
let result = array::pop(self.value());
AnyObject::from(result)
}
pub fn unshift<T: Object>(&mut self, item: T) -> Array {
let result = array::unshift(self.value(), item.value());
Array::from(result)
}
pub fn shift(&mut self) -> AnyObject {
let result = array::shift(self.value());
AnyObject::from(result)
}
pub fn dup(&self) -> Array {
let result = array::dup(self.value());
Array::from(result)
}
pub fn to_s(&self) -> RString {
let result = array::to_s(self.value());
RString::from(result)
}
pub fn reverse(&self) -> Array {
self.dup().reverse_bang()
}
pub fn reverse_bang(&mut self) -> Array {
let result = array::reverse_bang(self.value());
Array::from(result)
}
pub fn concat(&mut self, other: &Array) -> Array {
let result = array::concat(self.value(), other.value());
Array::from(result)
}
pub fn sort(&self) -> Array {
let result = array::sort(self.value());
Array::from(result)
}
pub fn sort_bang(&mut self) -> Array {
let result = array::sort_bang(self.value());
Array::from(result)
}
pub fn to_enum(&self) -> Enumerator {
unsafe { self.send("to_enum", &[]) }
.try_convert_to::<Enumerator>()
.unwrap()
}
pub fn delete<T: Object>(&mut self, item: T) -> AnyObject {
AnyObject::from(array::delete(self.value(), item.value()))
}
pub fn delete_at(&mut self, index: i64) -> AnyObject {
AnyObject::from(array::delete_at(self.value(), index))
}
pub fn includes<T: Object>(&self, item: &T) -> bool {
array::includes(self.value(), item.value())
}
pub fn clear(&mut self) {
array::clear(self.value())
}
pub fn slice(&self, start: usize, len: usize) -> Option<Array> {
let result = array::subseq(self.value(), start, len);
if result.is_nil() {
None
} else {
Some(Array::from(result))
}
}
pub fn plus(&self, other: &Array) -> Array {
Array::from(array::plus(self.value(), other.value()))
}
pub fn compare(&self, other: &Array) -> Option<Ordering> {
let result = array::compare(self.value(), other.value());
if result.is_nil() {
None
} else {
Some(Fixnum::from(result).to_i64().cmp(&0))
}
}
pub fn replace(&mut self, other: &Array) {
array::replace(self.value(), other.value());
}
pub fn resize(&mut self, len: usize) {
array::resize(self.value(), len);
}
pub fn rotate_bang(&mut self, count: i64) -> Array {
array::rotate(self.value(), count);
Array::from(self.value())
}
pub fn assoc<T: Object>(&self, key: &T) -> Option<Array> {
let result = array::assoc(self.value(), key.value());
if result.is_nil() {
None
} else {
Some(Array::from(result))
}
}
pub fn rassoc<T: Object>(&self, value: &T) -> Option<Array> {
let result = array::rassoc(self.value(), value.value());
if result.is_nil() {
None
} else {
Some(Array::from(result))
}
}
}
impl TryConvert<AnyObject> for Array {
type Nil = NilClass;
fn try_convert(obj: AnyObject) -> Result<Self, NilClass> {
let result = array::check_array_type(obj.value());
if result.is_nil() {
Err(NilClass::from(result))
} else {
Ok(Self::from(result))
}
}
}
impl Default for Array {
fn default() -> Self {
Array::new()
}
}
impl From<Value> for Array {
fn from(value: Value) -> Self {
Array { value }
}
}
impl Into<Value> for Array {
fn into(self) -> Value {
self.value
}
}
impl Into<AnyObject> for Array {
fn into(self) -> AnyObject {
AnyObject::from(self.value)
}
}
impl Object for Array {
#[inline]
fn value(&self) -> Value {
self.value
}
}
impl VerifiedObject for Array {
fn is_correct_type<T: Object>(object: &T) -> bool {
object.value().ty() == ValueType::Array
}
fn error_message() -> &'static str {
"Error converting to Array"
}
}
impl PartialEq for Array {
fn eq(&self, other: &Self) -> bool {
self.equals(other)
}
}
pub struct ArrayIterator {
array: Array,
current_index: i64,
}
impl ArrayIterator {
fn new(array: Array) -> ArrayIterator {
ArrayIterator {
array,
current_index: 0,
}
}
}
impl Iterator for ArrayIterator {
type Item = AnyObject;
fn next(&mut self) -> Option<AnyObject> {
let item = if (self.current_index as usize) < self.len() {
Some(self.array.at(self.current_index))
} else {
None
};
self.current_index += 1;
item
}
fn size_hint(&self) -> (usize, Option<usize>) {
let total = self.len() as usize;
(total, Some(total))
}
}
impl ExactSizeIterator for ArrayIterator {
fn len(&self) -> usize {
self.array.length() as usize
}
}
impl IntoIterator for Array {
type Item = AnyObject;
type IntoIter = ArrayIterator;
fn into_iter(self) -> Self::IntoIter {
ArrayIterator::new(self)
}
}
impl FromIterator<AnyObject> for Array {
fn from_iter<I: IntoIterator<Item = AnyObject>>(iter: I) -> Self {
let mut array = Array::new();
for i in iter {
array.push(i);
}
array
}
}
#[cfg(test)]
mod tests {
use crate::{Array, Fixnum, NilClass, Object, RString, Symbol, TryConvert, VM};
use std::cmp::Ordering;
fn fixnums(values: &[i64]) -> Array {
values
.iter()
.map(|&value| Fixnum::new(value).to_any_object())
.collect()
}
#[test]
fn test_array_editing() {
crate::on_ruby_thread(|| {
let mut array = fixnums(&[1, 2, 3, 2]);
array.delete(Fixnum::new(2));
assert_eq!(array, fixnums(&[1, 3]));
assert!(array.includes(&Fixnum::new(3)));
array.resize(3);
assert!(array.at(2).is_nil());
array.delete_at(2);
assert_eq!(array, fixnums(&[1, 3]));
array.replace(&fixnums(&[4, 5, 6]));
array.rotate_bang(2);
assert_eq!(array, fixnums(&[6, 4, 5]));
assert_eq!(array.plus(&fixnums(&[7])).length(), 4);
assert_eq!(array.slice(1, 5).unwrap(), fixnums(&[4, 5]));
array.clear();
assert_eq!(array.length(), 0);
let mut frozen = fixnums(&[1]).freeze();
assert!(VM::protect(|| {
frozen.clear();
NilClass::new().into()
})
.is_err());
VM::error_pop().unwrap();
});
}
#[test]
fn test_array_comparison_and_lookup() {
crate::on_ruby_thread(|| {
assert_eq!(
fixnums(&[1, 2]).compare(&fixnums(&[1, 2, 3])),
Some(Ordering::Less)
);
assert_eq!(
fixnums(&[2]).compare(&fixnums(&[1, 9])),
Some(Ordering::Greater)
);
let pairs = VM::eval("[[1, :one], [2, :two], 3]")
.unwrap()
.try_convert_to::<Array>()
.unwrap();
assert_eq!(
pairs
.assoc(&Fixnum::new(2))
.unwrap()
.at(1)
.try_convert_to::<Symbol>(),
Ok(Symbol::new("two"))
);
assert_eq!(
pairs
.rassoc(&Symbol::new("one"))
.unwrap()
.at(0)
.try_convert_to::<Fixnum>(),
Ok(Fixnum::new(1))
);
let convertible = VM::eval("o = Object.new; def o.to_ary; [1, 2]; end; o").unwrap();
assert_eq!(Array::try_convert(convertible).unwrap(), fixnums(&[1, 2]));
assert!(Array::try_convert(RString::new_utf8("x").to_any_object()).is_err());
});
}
#[test]
fn test_array_stack_and_order_operations() {
crate::on_ruby_thread(|| {
let fixnums = |array: &Array| -> Vec<i64> {
(0..array.length() as i64)
.map(|i| array.at(i).try_convert_to::<Fixnum>().unwrap().to_i64())
.collect()
};
let mut array: Array = [3, 1, 2]
.iter()
.map(|&n| Fixnum::new(n).to_any_object())
.collect();
array.unshift(Fixnum::new(0));
assert_eq!(fixnums(&array), vec![0, 3, 1, 2]);
assert_eq!(array.shift().try_convert_to::<Fixnum>(), Ok(Fixnum::new(0)));
assert_eq!(array.pop().try_convert_to::<Fixnum>(), Ok(Fixnum::new(2)));
assert_eq!(fixnums(&array), vec![3, 1]);
let mut empty = Array::new();
assert!(empty.pop().is_nil());
assert!(empty.shift().is_nil());
let mut array: Array = [3, 1, 2]
.iter()
.map(|&n| Fixnum::new(n).to_any_object())
.collect();
assert_eq!(fixnums(&array.sort()), vec![1, 2, 3]);
assert_eq!(fixnums(&array), vec![3, 1, 2]);
assert_eq!(fixnums(&array.reverse()), vec![2, 1, 3]);
assert_eq!(fixnums(&array), vec![3, 1, 2]);
array.sort_bang();
assert_eq!(fixnums(&array), vec![1, 2, 3]);
array.reverse_bang();
assert_eq!(fixnums(&array), vec![3, 2, 1]);
let mut enumerator = array.to_enum();
assert_eq!(
enumerator.next().unwrap().try_convert_to::<Fixnum>(),
Ok(Fixnum::new(3))
);
let result = VM::protect(|| {
let mut frozen = Array::new().freeze();
frozen.push(NilClass::new());
NilClass::new().into()
});
assert!(result.is_err());
VM::clear_error_info();
});
}
}