1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
//! Ruby ranges.

use std::{
    fmt,
    marker::PhantomData,
    ops::Bound,
    os::raw::c_int,
};
use crate::{
    prelude::*,
    object::NonNullObject,
    ruby,
};

mod into_bounds;
pub use into_bounds::*;

/// An instance of Ruby's `Range` type.
///
/// This type supports being instantiated from [`Range`], [`RangeInclusive`],
/// and [`RangeTo`].
///
/// # `a..b` and `a...b` versus `a..b` and `a..=b`
///
/// In Rust (and many other languages), `a..b` denotes an _inclusive_ range.
/// However, in Ruby this syntax denotes an _exclusive_ range.
///
/// In Rust, `a..=b` denotes an _exclusive_ range whereas in Ruby, `...` denotes
/// an _inclusive_ range.
///
/// # Examples
///
/// An exclusive range can be instantiated quite simply:
///
/// ```
/// # rosy::vm::init().unwrap();
/// use rosy::{Range, Integer, Object};
///
/// let range = Range::<Integer>::new(0..10).unwrap();
/// assert_eq!(range.to_s(), "0...10");
/// ```
///
/// The start and end bounds can be retrieved via `into_bounds`:
///
/// ```
/// # rosy::vm::init().unwrap();
/// # use rosy::{Range, Integer, Object};
/// use std::ops::Bound;
///
/// let range = Range::<Integer>::new(1..=10).unwrap();
///
/// let (start, end) = range.into_bounds();
///
/// assert_eq!(start, 1);
/// assert_eq!(end, Bound::Included(Integer::from(10)));
/// ```
///
/// [`Range`]: https://doc.rust-lang.org/std/ops/struct.Range.html
/// [`RangeInclusive`]: https://doc.rust-lang.org/std/ops/struct.RangeInclusive.html
/// [`RangeTo`]: https://doc.rust-lang.org/std/ops/struct.RangeTo.html
#[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 {
    /// Creates a new instance from the given bounds, returning an exception if
    /// one is raised.
    ///
    /// If `end` is `nil`, an infinite (unbounded) range is produced.
    pub fn from_bounds(
        start: AnyObject,
        end: AnyObject,
        exclusive: bool,
    ) -> Result<Self> {
        unsafe {
            crate::protected_no_panic(|| {
                Self::from_bounds_unchecked(start, end, exclusive)
            })
        }
    }

    /// Creates a new instance from the given bounds, without checking for
    /// exceptions.
    ///
    /// If `end` is `nil`, an infinite (unbounded) range is produced.
    ///
    /// # Safety
    ///
    /// An exception may be raised if `start` and `end` cannot be compared.
    #[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> {
    /// Creates a new instance from `range`, returning an exception if one is
    /// raised.
    #[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))
        }
    }

    /// Creates a new instance from `range`, without checking for exceptions.
    ///
    /// # Safety
    ///
    /// An exception may be raised if `S` and `E` cannot be compared.
    #[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)
    }

    /// Returns a range over `AnyObject`s.
    #[inline]
    pub fn into_any_range(self) -> Range {
        unsafe { Range::cast_unchecked(self) }
    }

    /// Returns the start (inclusive) and end bounds of `self`.
    #[inline]
    pub fn into_bounds(self) -> (S, Bound<E>) {
        IntoBounds::into_bounds(self)
    }
}