rosy/range/
mod.rs

1//! Ruby ranges.
2
3use std::{
4    fmt,
5    marker::PhantomData,
6    ops::Bound,
7    os::raw::c_int,
8};
9use crate::{
10    prelude::*,
11    object::NonNullObject,
12    ruby,
13};
14
15mod into_bounds;
16pub use into_bounds::*;
17
18/// An instance of Ruby's `Range` type.
19///
20/// This type supports being instantiated from [`Range`], [`RangeInclusive`],
21/// and [`RangeTo`].
22///
23/// # `a..b` and `a...b` versus `a..b` and `a..=b`
24///
25/// In Rust (and many other languages), `a..b` denotes an _inclusive_ range.
26/// However, in Ruby this syntax denotes an _exclusive_ range.
27///
28/// In Rust, `a..=b` denotes an _exclusive_ range whereas in Ruby, `...` denotes
29/// an _inclusive_ range.
30///
31/// # Examples
32///
33/// An exclusive range can be instantiated quite simply:
34///
35/// ```
36/// # rosy::vm::init().unwrap();
37/// use rosy::{Range, Integer, Object};
38///
39/// let range = Range::<Integer>::new(0..10).unwrap();
40/// assert_eq!(range.to_s(), "0...10");
41/// ```
42///
43/// The start and end bounds can be retrieved via `into_bounds`:
44///
45/// ```
46/// # rosy::vm::init().unwrap();
47/// # use rosy::{Range, Integer, Object};
48/// use std::ops::Bound;
49///
50/// let range = Range::<Integer>::new(1..=10).unwrap();
51///
52/// let (start, end) = range.into_bounds();
53///
54/// assert_eq!(start, 1);
55/// assert_eq!(end, Bound::Included(Integer::from(10)));
56/// ```
57///
58/// [`Range`]: https://doc.rust-lang.org/std/ops/struct.Range.html
59/// [`RangeInclusive`]: https://doc.rust-lang.org/std/ops/struct.RangeInclusive.html
60/// [`RangeTo`]: https://doc.rust-lang.org/std/ops/struct.RangeTo.html
61#[repr(transparent)]
62pub struct Range<S = AnyObject, E = S> {
63    inner: NonNullObject,
64    _marker: PhantomData<(S, E)>,
65}
66
67impl<S, E> Clone for Range<S, E> {
68    fn clone(&self) -> Self { *self }
69}
70
71impl<S, E> Copy for Range<S, E> {}
72
73impl<S: Object, E: Object> AsRef<AnyObject> for Range<S, E> {
74    #[inline]
75    fn as_ref(&self) -> &AnyObject { self.inner.as_ref() }
76}
77
78impl<S: Object, E: Object> From<Range<S, E>> for AnyObject {
79    #[inline]
80    fn from(object: Range<S, E>) -> AnyObject { object.inner.into() }
81}
82
83impl<S: Object, E: Object> PartialEq<AnyObject> for Range<S, E> {
84    #[inline]
85    fn eq(&self, obj: &AnyObject) -> bool {
86        self.as_any_object() == obj
87    }
88}
89
90impl<S: Object, E: Object> fmt::Debug for Range<S, E> {
91    #[inline]
92    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
93        f.debug_tuple("Range")
94            .field(&self.inner)
95            .finish()
96    }
97}
98
99unsafe impl<S: Object, E: Object> Object for Range<S, E> {
100}
101
102impl<S: Object, E: Object> IntoBounds<S, E> for Range<S, E> {
103    #[inline]
104    fn into_bounds(self) -> (S, Bound<E>) {
105        unsafe {
106            let mut start: ruby::VALUE = 0;
107            let mut end: ruby::VALUE = 0;
108            let mut excl: c_int = 0;
109            ruby::rb_range_values(self.raw(), &mut start, &mut end, &mut excl);
110
111            let start = S::from_raw(start);
112
113            let end = if end == crate::util::NIL_VALUE {
114                Bound::Unbounded
115            } else {
116                let end = E::from_raw(end);
117                if excl != 0 {
118                    Bound::Excluded(end)
119                } else {
120                    Bound::Included(end)
121                }
122            };
123
124            (start, end)
125        }
126    }
127}
128
129impl Range {
130    /// Creates a new instance from the given bounds, returning an exception if
131    /// one is raised.
132    ///
133    /// If `end` is `nil`, an infinite (unbounded) range is produced.
134    pub fn from_bounds(
135        start: AnyObject,
136        end: AnyObject,
137        exclusive: bool,
138    ) -> Result<Self> {
139        unsafe {
140            crate::protected_no_panic(|| {
141                Self::from_bounds_unchecked(start, end, exclusive)
142            })
143        }
144    }
145
146    /// Creates a new instance from the given bounds, without checking for
147    /// exceptions.
148    ///
149    /// If `end` is `nil`, an infinite (unbounded) range is produced.
150    ///
151    /// # Safety
152    ///
153    /// An exception may be raised if `start` and `end` cannot be compared.
154    #[inline]
155    pub unsafe fn from_bounds_unchecked(
156        start: AnyObject,
157        end: AnyObject,
158        exclusive: bool,
159    ) -> Self {
160        let raw = ruby::rb_range_new(start.raw(), end.raw(), exclusive as _);
161        Self::from_raw(raw)
162    }
163}
164
165impl<S: Object, E: Object> Range<S, E> {
166    /// Creates a new instance from `range`, returning an exception if one is
167    /// raised.
168    #[inline]
169    pub fn new<R, A, B>(range: R) -> Result<Self>
170    where
171        R: IntoBounds<A, B>,
172        A: Into<S>,
173        B: Into<E>,
174    {
175        let (start, end) = range.into_bounds();
176        let start = start.into().into_any_object();
177        let (end, exclusive) = match end {
178            Bound::Included(end) => (end.into().into_any_object(), false),
179            Bound::Excluded(end) => (end.into().into_any_object(), true),
180            Bound::Unbounded => (AnyObject::nil(), true),
181        };
182        unsafe {
183            let range = Range::from_bounds(start, end, exclusive)?;
184            Ok(Self::cast_unchecked(range))
185        }
186    }
187
188    /// Creates a new instance from `range`, without checking for exceptions.
189    ///
190    /// # Safety
191    ///
192    /// An exception may be raised if `S` and `E` cannot be compared.
193    #[inline]
194    pub unsafe fn new_unchecked<R, A, B>(range: R) -> Self
195    where
196        R: IntoBounds<A, B>,
197        A: Into<S>,
198        B: Into<E>,
199    {
200        let (start, end) = range.into_bounds();
201        let start = start.into().into_any_object();
202        let (end, exclusive) = match end {
203            Bound::Included(end) => (end.into().into_any_object(), false),
204            Bound::Excluded(end) => (end.into().into_any_object(), true),
205            Bound::Unbounded => (AnyObject::nil(), true),
206        };
207        let range = Range::from_bounds_unchecked(start, end, exclusive);
208        Self::cast_unchecked(range)
209    }
210
211    /// Returns a range over `AnyObject`s.
212    #[inline]
213    pub fn into_any_range(self) -> Range {
214        unsafe { Range::cast_unchecked(self) }
215    }
216
217    /// Returns the start (inclusive) and end bounds of `self`.
218    #[inline]
219    pub fn into_bounds(self) -> (S, Bound<E>) {
220        IntoBounds::into_bounds(self)
221    }
222}