use std::{
fmt,
marker::PhantomData,
ops::Bound,
os::raw::c_int,
};
use crate::{
prelude::*,
object::NonNullObject,
ruby,
};
mod into_bounds;
pub use into_bounds::*;
#[repr(transparent)]
pub struct Range<S = AnyObject, E = S> {
inner: NonNullObject,
_marker: PhantomData<(S, E)>,
}
impl<S, E> Clone for Range<S, E> {
fn clone(&self) -> Self { *self }
}
impl<S, E> Copy for Range<S, E> {}
impl<S: Object, E: Object> AsRef<AnyObject> for Range<S, E> {
#[inline]
fn as_ref(&self) -> &AnyObject { self.inner.as_ref() }
}
impl<S: Object, E: Object> From<Range<S, E>> for AnyObject {
#[inline]
fn from(object: Range<S, E>) -> AnyObject { object.inner.into() }
}
impl<S: Object, E: Object> PartialEq<AnyObject> for Range<S, E> {
#[inline]
fn eq(&self, obj: &AnyObject) -> bool {
self.as_any_object() == obj
}
}
impl<S: Object, E: Object> fmt::Debug for Range<S, E> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("Range")
.field(&self.inner)
.finish()
}
}
unsafe impl<S: Object, E: Object> Object for Range<S, E> {
}
impl<S: Object, E: Object> IntoBounds<S, E> for Range<S, E> {
#[inline]
fn into_bounds(self) -> (S, Bound<E>) {
unsafe {
let mut start: ruby::VALUE = 0;
let mut end: ruby::VALUE = 0;
let mut excl: c_int = 0;
ruby::rb_range_values(self.raw(), &mut start, &mut end, &mut excl);
let start = S::from_raw(start);
let end = if end == crate::util::NIL_VALUE {
Bound::Unbounded
} else {
let end = E::from_raw(end);
if excl != 0 {
Bound::Excluded(end)
} else {
Bound::Included(end)
}
};
(start, end)
}
}
}
impl Range {
pub fn from_bounds(
start: AnyObject,
end: AnyObject,
exclusive: bool,
) -> Result<Self> {
unsafe {
crate::protected_no_panic(|| {
Self::from_bounds_unchecked(start, end, exclusive)
})
}
}
#[inline]
pub unsafe fn from_bounds_unchecked(
start: AnyObject,
end: AnyObject,
exclusive: bool,
) -> Self {
let raw = ruby::rb_range_new(start.raw(), end.raw(), exclusive as _);
Self::from_raw(raw)
}
}
impl<S: Object, E: Object> Range<S, E> {
#[inline]
pub fn new<R, A, B>(range: R) -> Result<Self>
where
R: IntoBounds<A, B>,
A: Into<S>,
B: Into<E>,
{
let (start, end) = range.into_bounds();
let start = start.into().into_any_object();
let (end, exclusive) = match end {
Bound::Included(end) => (end.into().into_any_object(), false),
Bound::Excluded(end) => (end.into().into_any_object(), true),
Bound::Unbounded => (AnyObject::nil(), true),
};
unsafe {
let range = Range::from_bounds(start, end, exclusive)?;
Ok(Self::cast_unchecked(range))
}
}
#[inline]
pub unsafe fn new_unchecked<R, A, B>(range: R) -> Self
where
R: IntoBounds<A, B>,
A: Into<S>,
B: Into<E>,
{
let (start, end) = range.into_bounds();
let start = start.into().into_any_object();
let (end, exclusive) = match end {
Bound::Included(end) => (end.into().into_any_object(), false),
Bound::Excluded(end) => (end.into().into_any_object(), true),
Bound::Unbounded => (AnyObject::nil(), true),
};
let range = Range::from_bounds_unchecked(start, end, exclusive);
Self::cast_unchecked(range)
}
#[inline]
pub fn into_any_range(self) -> Range {
unsafe { Range::cast_unchecked(self) }
}
#[inline]
pub fn into_bounds(self) -> (S, Bound<E>) {
IntoBounds::into_bounds(self)
}
}