rosy 0.0.9

Ruby bindings for Rust.
Documentation
//! Ruby arrays.

use std::{
    cmp::Ordering,
    fmt,
    iter::FromIterator,
    marker::PhantomData,
    ops::{Add, Range},
};
use crate::{
    object::{NonNullObject, Ty},
    prelude::*,
    ruby,
};

/// An instance of Ruby's `Array` class.
///
/// # Performance
///
/// Although caution must be taken with [`as_slice`](#method.as_slice) and its
/// mutable counterpart, it is _much_ faster to iterate over the inner slice of
/// objects in an array than it is to iterate over the array directly.
///
/// # Examples
///
/// Ruby arrays can be treated as somewhat like a [`Vec`] without the borrow
/// checker.
///
/// ```
/// # rosy::vm::init().unwrap();
/// # rosy::protected(|| {
/// use rosy::prelude::*;
///
/// let s = String::from("hellooo");
///
/// let a = Array::from_slice(&[s, s, s]);
/// assert_eq!(a.len(), 3);
///
/// for obj in a {
///     assert_eq!(obj, s);
/// }
/// # }).unwrap();
/// ```
///
/// Because the `Iterator` for `Array` performs a volatile read of the
/// array length each time, a
/// [buffer overrun](https://en.wikipedia.org/wiki/Buffer_overrun) will never
/// occur.
///
/// ```
/// # rosy::vm::init().unwrap();
/// # rosy::protected(|| {
/// # use rosy::prelude::*;
/// # let s = String::from("hellooo");
/// # let a = Array::from_slice(&[s, s, s]);
/// assert_eq!(a.len(), 3);
/// let mut num_iter = 0;
///
/// for _ in a {
///     // `unsafe` required because `pop` raises an exception if `a` is frozen
///     unsafe { a.pop() };
///     num_iter += 1;
/// }
///
/// assert_eq!(num_iter, 2);
/// # }).unwrap();
/// ```
///
/// Just like [`Vec`], one can even safely [`collect`] an iterator into an
/// `Array`:
///
/// ```
/// # rosy::vm::init().unwrap();
/// # rosy::protected(|| {
/// # use rosy::prelude::*;
/// let array: Array = (0..10).collect();
///
/// for (i, obj) in array.into_iter().enumerate() {
///     assert_eq!(obj, i);
/// }
/// # }).unwrap();
/// ```
///
/// [`Vec`]: https://doc.rust-lang.org/std/vec/struct.Vec.html
/// [`collect`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.collect
#[repr(transparent)]
pub struct Array<O = AnyObject> {
    inner: NonNullObject,
    _marker: PhantomData<*mut O>,
}

impl<O> Clone for Array<O> {
    #[inline]
    fn clone(&self) -> Self { *self }
}

impl<O> Copy for Array<O> {}

impl<O: Object> AsRef<AnyObject> for Array<O> {
    #[inline]
    fn as_ref(&self) -> &AnyObject { self.inner.as_ref() }
}

impl<O: Object> From<Array<O>> for AnyObject {
    #[inline]
    fn from(object: Array<O>) -> AnyObject { object.inner.into() }
}

impl<O: Object> PartialEq<AnyObject> for Array<O> {
    #[inline]
    fn eq(&self, obj: &AnyObject) -> bool {
        self.as_any_object() == obj
    }
}

unsafe impl<O: Object> Object for Array<O> {
    #[inline]
    fn unique_id() -> Option<u128> {
        let base = O::unique_id()?;
        let this = Ty::ARRAY.id() as u128;
        Some(base.rotate_right(3) ^ this)
    }

    #[inline]
    fn cast<A: Object>(obj: A) -> Option<Self> {
        // Either:
        // - `A` is of type `Self`
        // - `Self` is `Array<AnyObject>` and `obj` is an array
        let is_valid = Self::unique_id() == A::unique_id() ||
            (O::unique_id() == AnyObject::unique_id() && obj.is_ty(Ty::ARRAY));
        if is_valid {
            unsafe { Some(Self::cast_unchecked(obj)) }
        } else {
            None
        }
    }

    #[inline]
    fn ty(self) -> Ty { Ty::ARRAY }

    #[inline]
    fn is_ty(self, ty: Ty) -> bool { ty == Ty::ARRAY }
}

impl<O: Object> fmt::Debug for Array<O> {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_tuple("Array")
            .field(&self.inner)
            .finish()
    }
}

impl<O: Object> fmt::Display for Array<O> {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.as_any_object().fmt(f)
    }
}

// Safe because this is part of the contract of implementing `Object`.
impl<O: Object> From<&[O]> for Array<O> {
    #[inline]
    fn from(slice: &[O]) -> Self {
        let ptr = slice.as_ptr() as *const ruby::VALUE;
        let len = slice.len();
        unsafe { Array::from_raw(ruby::rb_ary_new_from_values(len as _, ptr)) }
    }
}

impl<O, A> PartialEq<[A]> for Array<O>
    where O: Object + PartialEq<A>,
{
    #[inline]
    fn eq(&self, other: &[A]) -> bool {
        unsafe { self.as_slice().eq(other) }
    }
}

impl<O, A> PartialEq<Vec<A>> for Array<O>
    where O: Object + PartialEq<A>,
{
    #[inline]
    fn eq(&self, other: &Vec<A>) -> bool {
        self == other.as_slice()
    }
}

impl<A> PartialEq<[A]> for AnyObject
    where AnyObject: PartialEq<A>
{
    #[inline]
    fn eq(&self, other: &[A]) -> bool {
        if let Some(array) = self.to_array() {
            array == *other
        } else {
            false
        }
    }
}

impl<A> PartialEq<&[A]> for AnyObject
    where AnyObject: PartialEq<A>
{
    #[inline]
    fn eq(&self, other: &&[A]) -> bool {
        PartialEq::<[A]>::eq(self, *other)
    }
}

impl<A> PartialEq<Vec<A>> for AnyObject
    where AnyObject: PartialEq<A>
{
    #[inline]
    fn eq(&self, other: &Vec<A>) -> bool {
        self == other.as_slice()
    }
}

impl<A> PartialEq<&Vec<A>> for AnyObject
    where AnyObject: PartialEq<A>
{
    #[inline]
    fn eq(&self, other: &&Vec<A>) -> bool {
        self == other.as_slice()
    }
}

impl<T: Object, U: Object> PartialEq<Array<U>> for Array<T> {
    #[inline]
    fn eq(&self, other: &Array<U>) -> bool {
        self.partial_cmp(other) == Some(Ordering::Equal)
    }
}

impl<T: Object, U: Object> PartialOrd<Array<U>> for Array<T> {
    #[inline]
    fn partial_cmp(&self, other: &Array<U>) -> Option<Ordering> {
        let value = unsafe { ruby::rb_ary_cmp(self.raw(), other.raw()) };
        if value == crate::util::NIL_VALUE {
            return None;
        }
        Some(crate::util::value_to_fixnum(value).cmp(&0))
    }
}

impl<O: Object, A: Into<O>> FromIterator<A> for Array<O> {
    #[inline]
    fn from_iter<I: IntoIterator<Item = A>>(iter: I) -> Self {
        let iter = iter.into_iter();
        let (size, _) = iter.size_hint();
        let array = Self::with_capacity(size);
        for obj in iter {
            unsafe { array.push(obj.into()) };
        }
        array
    }
}

impl<O: Object> IntoIterator for Array<O> {
    type Item = O;
    type IntoIter = Iter<O>;

    #[inline]
    fn into_iter(self) -> Self::IntoIter {
        Iter { array: self, current: 0 }
    }
}

// Allows for `a1 + a2` in Rust
impl<O: Object> Add for Array<O> {
    type Output = Self;

    #[inline]
    fn add(self, other: Self) -> Self::Output {
        self.plus(other)
    }
}

impl<O: Object> Array<O> {
    #[inline]
    pub(crate) fn rarray(self) -> *mut ruby::RArray {
        self.as_any_object()._ptr() as _
    }

    /// Creates a new empty instance.
    #[inline]
    pub fn new() -> Self {
        unsafe { Self::from_raw(ruby::rb_ary_new()) }
    }

    /// Creates a new instance from the elements in `slice`.
    #[inline]
    pub fn from_slice<'s, T>(slice: &'s [T]) -> Self
        where &'s [T]: Into<Self>
    {
        slice.into()
    }

    /// Creates a new instance with `capacity` amount of storage.
    #[inline]
    pub fn with_capacity(capacity: usize) -> Self {
        unsafe { Self::from_raw(ruby::rb_ary_new_capa(capacity as _)) }
    }

    /// Duplicates the contents of `self` into a new instance.
    #[inline]
    pub fn duplicate(self) -> Self {
        unsafe { Self::from_raw(ruby::rb_ary_dup(self.raw())) }
    }

    /// Returns the number of elements in `self`.
    ///
    /// # Examples
    ///
    /// ```
    /// # rosy::vm::init().unwrap();
    /// # rosy::protected(|| {
    /// use rosy::{Array, String};
    ///
    /// let s = String::from("hi");
    /// let a = Array::from_slice(&[s, s, s]);
    ///
    /// assert_eq!(a.len(), 3);
    /// # }).unwrap();
    /// ```
    #[inline]
    pub fn len(self) -> usize {
        unsafe { (*self.rarray()).len() }
    }

    /// Returns whether `self` is empty.
    #[inline]
    pub fn is_empty(self) -> bool {
        self.len() == 0
    }

    /// Returns a pointer to the first object in `self`.
    #[inline]
    pub fn as_ptr(self) -> *const O {
        unsafe { (*self.rarray()).start() as *const O }
    }

    /// Returns a mutable pointer to the first object in `self`.
    #[inline]
    pub fn as_ptr_mut(self) -> *mut O {
        unsafe { (*self.rarray()).start_mut() as *mut O }
    }

    /// Returns a slice to the underlying objects of `self`.
    ///
    /// # Safety
    ///
    /// Care must be taken to ensure that the length of `self` is not changed
    /// through the VM or otherwise.
    #[inline]
    pub unsafe fn as_slice(&self) -> &[O] {
        std::slice::from_raw_parts(self.as_ptr(), self.len())
    }

    /// Returns a mutable slice to the underlying objects of `self`.
    ///
    /// # Safety
    ///
    /// The caller must ensure that `self` is not frozen or else a `FrozenError`
    /// exception will be raised. Care must also be taken to ensure that the
    /// length of `self` is not changed through the VM or otherwise.
    #[inline]
    pub unsafe fn as_slice_mut(&mut self) -> &mut [O] {
        ruby::rb_ary_modify(self.raw());
        std::slice::from_raw_parts_mut(self.as_ptr_mut(), self.len())
    }

    /// Returns the output at `index` or `None` if `index` is out-of-bounds.
    #[inline]
    pub fn get<I: ArrayIndex<O>>(self, index: I) -> Option<I::Output> {
        index.get(self)
    }

    /// Returns the output at `index` without safety checks on `index`.
    #[inline]
    pub unsafe fn get_unchecked<I: ArrayIndex<O>>(self, index: I) -> I::Output {
        index.get_unchecked(self)
    }

    /// Returns the subsequence of `self` at `range`.
    ///
    /// **Note:** This respects the semantics of `rb_ary_subseq`, where indexing
    /// out of the array with a range greater than the actual slice in `self`
    /// will just return all remaining elements. Use [`get`](#method.get) for
    /// slice indexing semantics.
    #[inline]
    pub fn subseq(self, range: Range<usize>) -> Option<Self> {
        let start = range.start;
        let len = range.end - range.start;
        unsafe {
            match ruby::rb_ary_subseq(self.raw(), start as _, len as _) {
                crate::util::NIL_VALUE => None,
                raw => Some(Self::from_raw(raw)),
            }
        }
    }

    /// Returns the first object in `self`.
    #[inline]
    pub fn first(self) -> Option<O> {
        unsafe { self.as_slice().first().map(|&obj| obj) }
    }

    /// Returns the last element in `self`.
    #[inline]
    pub fn last(self) -> Option<O> {
        unsafe { self.as_slice().last().map(|&obj| obj) }
    }

    /// Removes all elements from `self`.
    ///
    /// # Safety
    ///
    /// The caller must ensure that `self` is not frozen or else a `FrozenError`
    /// exception will be raised.
    #[inline]
    pub unsafe fn clear(self) {
        ruby::rb_ary_clear(self.raw())
    }

    /// Appends all of the elements in `slice` to `self`.
    ///
    /// # Safety
    ///
    /// The caller must ensure that `self` is not frozen or else a `FrozenError`
    /// exception will be raised.
    #[inline]
    pub unsafe fn extend_from_slice(self, slice: &[O]) {
        let ptr = slice.as_ptr() as *const ruby::VALUE;
        let len = slice.len();
        ruby::rb_ary_cat(self.raw(), ptr, len as _);
    }

    /// Returns the result of performing `self + other`.
    #[inline]
    pub fn plus(self, other: Self) -> Self {
        unsafe { Array::from_raw(ruby::rb_ary_plus(self.raw(), other.raw())) }
    }

    /// Pushes `obj` onto the end of `self`.
    ///
    /// # Safety
    ///
    /// The caller must ensure that `self` is not:
    /// - Frozen, or else a `FrozenError` exception will be raised
    /// - `Array<AnyObject>` that references `Array<ConcreteObject>` where `obj`
    ///   is not the same type as `ConcreteObject`
    #[inline]
    pub unsafe fn push(self, obj: O) -> AnyObject {
        AnyObject::from_raw(ruby::rb_ary_push(self.raw(), obj.raw()))
    }

    /// Pops the last element from `self`.
    ///
    /// # Safety
    ///
    /// The caller must ensure that `self` is not frozen or else a `FrozenError`
    /// exception will be raised.
    ///
    /// # Examples
    ///
    /// ```
    /// # rosy::vm::init().unwrap();
    /// # rosy::protected(|| {
    /// use rosy::{Array, String};
    ///
    /// let s = String::from("Hi");
    /// let a = Array::from_slice(&[s]);
    ///
    /// unsafe {
    ///     assert!(!a.pop().is_nil());
    ///     assert!(a.pop().is_nil());
    /// }
    /// # }).unwrap();
    /// ```
    #[inline]
    pub unsafe fn pop(self) -> AnyObject {
        AnyObject::from_raw(ruby::rb_ary_pop(self.raw()))
    }

    /// Returns whether `self` contains `obj`.
    ///
    /// This is equivalent to the `include?` method.
    ///
    /// # Examples
    ///
    /// ```
    /// # rosy::vm::init().unwrap();
    /// # rosy::protected(|| {
    /// use rosy::{Array, String};
    ///
    /// let array = Array::from_slice(&[
    ///     String::from("yo"),
    ///     String::from("hi"),
    /// ]);
    ///
    /// assert!(array.contains("hi"));
    /// # }).unwrap();
    /// ```
    #[inline]
    pub fn contains(self, obj: impl Into<O>) -> bool {
        unsafe { ruby::rb_ary_includes(self.raw(), obj.into().raw()) != 0 }
    }

    /// Removes _all_ items in `self` that are equal to `obj`.
    ///
    /// This is equivalent to the `delete` method.
    ///
    /// # Safety
    ///
    /// The caller must ensure that `self` is not frozen or else a `FrozenError`
    /// exception will be raised.
    #[inline]
    pub unsafe fn remove_all(self, obj: impl Into<O>) -> Option<O> {
        match ruby::rb_ary_delete(self.raw(), obj.into().raw()) {
            crate::util::NIL_VALUE => None,
            raw => Some(O::from_raw(raw)),
        }
    }

    /// Reverses the contents of `self` in-palace.
    ///
    /// This is equivalent to the `reverse!` method.
    ///
    /// # Safety
    ///
    /// The caller must ensure that `self` is not frozen or else a `FrozenError`
    /// exception will be raised.
    #[inline]
    pub unsafe fn reverse(self) {
        ruby::rb_ary_reverse(self.raw());
    }

    /// Returns an instance with its contents sorted.
    #[inline]
    #[must_use]
    pub fn sorted(self) -> Self {
        unsafe { Array::from_raw(ruby::rb_ary_sort(self.raw())) }
    }

    /// Sorts the contents of `self` in-place without checking whether `self` is
    /// frozen.
    ///
    /// # Safety
    ///
    /// The caller must ensure that `self` is not frozen or else a `FrozenError`
    /// exception will be raised.
    #[inline]
    pub unsafe fn sort(self) {
        ruby::rb_ary_sort_bang(self.raw());
    }

    /// Joins the contents of `self` with `separator`.
    ///
    /// # Examples
    ///
    /// ```
    /// # rosy::vm::init().unwrap();
    /// # rosy::protected(|| {
    /// use rosy::{Array, String};
    ///
    /// let s = String::from("-");
    /// let a = Array::from_slice(&[s, s, s]);
    ///
    /// assert_eq!(a.join("."), "-.-.-");
    /// # }).unwrap();
    /// ```
    #[inline]
    pub fn join(self, separator: impl Into<String>) -> String {
        let separator = separator.into().raw();
        unsafe { String::from_raw(ruby::rb_ary_join(self.raw(), separator)) }
    }
}

/// A type that can be used to index into an [`Array`](struct.Array.html).
pub trait ArrayIndex<O: Object> {
    /// The output returned from indexing.
    type Output;

    /// Returns the output at `self`, or `None` if `self` is out-of-bounds.
    fn get(self, array: Array<O>) -> Option<Self::Output>;

    /// Returns the output at `self` without safety checks.
    unsafe fn get_unchecked(self, array: Array<O>) -> Self::Output;
}

impl<O: Object> ArrayIndex<O> for usize {
    type Output = O;

    #[inline]
    fn get(self, array: Array<O>) -> Option<Self::Output> {
        unsafe { array.as_slice().get(self).map(|&obj| obj) }
    }

    #[inline]
    unsafe fn get_unchecked(self, array: Array<O>) -> Self::Output {
        *array.as_slice().get_unchecked(self)
    }
}

impl<O: Object> ArrayIndex<O> for Range<usize> {
    type Output = Array<O>;

    #[inline]
    fn get(self, array: Array<O>) -> Option<Self::Output> {
        if self.start > self.end || self.end > array.len() {
            None
        } else {
            array.subseq(self)
        }
    }

    #[inline]
    unsafe fn get_unchecked(self, array: Array<O>) -> Self::Output {
        array.subseq(self).unwrap_or_else(|| {
            std::hint::unreachable_unchecked()
        })
    }
}

/// An iterator over the elements of an [`Array`](struct.Array.html).
#[derive(Clone, Debug)]
pub struct Iter<O: Object> {
    array: Array<O>,
    current: usize,
}

impl<O: Object> Iterator for Iter<O> {
    type Item = O;

    #[inline]
    fn next(&mut self) -> Option<O> {
        let obj = self.array.get(self.current)?;
        self.current += 1;
        Some(obj)
    }

    #[inline]
    fn size_hint(&self) -> (usize, Option<usize>) {
        // Because `array` may be resized during the iteration, the lower and
        // upper bound may be different than the yielded number of elements;
        // however, it is safe for an `Iterator` implementation to do so
        let len = self.array.len();
        (len, Some(len))
    }

    #[inline]
    fn count(self) -> usize {
        self.array.len()
    }

    #[inline]
    fn last(self) -> Option<O> {
        self.array.last()
    }
}