cycles/
span.rs

1//! The `Span` type and related items.
2
3use crate::Rational;
4use std::fmt;
5
6/// A shorthand macro for constructing spans from rationals, e.g. `span!(0/1, 3/1)`.
7#[macro_export]
8macro_rules! span {
9    ($n1:literal/$d1:literal, $n2:literal/$d2:literal) => {{
10        span!($n1 / $d1, $crate::Rational::new_raw($n2, $d2))
11    }};
12    ($n1:literal/$d1:literal, $r2:expr) => {{
13        span!($crate::Rational::new_raw($n1, $d1), $r2)
14    }};
15    ($r1:expr, $n2:literal/$d2:literal) => {{
16        span!($r1, $crate::Rational::new_raw($n2, $d2))
17    }};
18    ($r1:expr, $r2:expr) => {{
19        $crate::Span::new($r1, $r2)
20    }};
21    ($n:literal / $d:literal) => {{
22        span!($crate::Rational::new_raw($n, $d))
23    }};
24    ($r:expr) => {{
25        $crate::Span::instant($r)
26    }};
27}
28
29/// A rational range over a single dimension, represented with a start and end.
30#[derive(Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
31pub struct Span {
32    pub start: Rational,
33    pub end: Rational,
34}
35
36impl Span {
37    pub fn new(start: Rational, end: Rational) -> Self {
38        Span { start, end }
39    }
40
41    pub fn instant(start @ end: Rational) -> Self {
42        Span { start, end }
43    }
44
45    pub fn len(&self) -> Rational {
46        self.end - self.start
47    }
48
49    pub fn cycles(self) -> impl Iterator<Item = Self> {
50        let Span { mut start, end } = self;
51        std::iter::from_fn(move || {
52            if start >= end {
53                None
54            } else if start >= end.floor() {
55                let span = Span { start, end };
56                start = end;
57                Some(span)
58            } else {
59                let this_end = start.floor() + 1;
60                let span = Span {
61                    start,
62                    end: this_end,
63                };
64                start = this_end;
65                Some(span)
66            }
67        })
68    }
69
70    pub fn map(self, f: impl Fn(Rational) -> Rational) -> Self {
71        span!(f(self.start), f(self.end))
72    }
73
74    pub fn map_len(self, f: impl Fn(Rational) -> Rational) -> Self {
75        let new_len = f(self.len());
76        let new_end = self.start + new_len;
77        span!(self.start, new_end)
78    }
79
80    /// Checks if point lies within the span exclusively.
81    pub fn contains(&self, point: Rational) -> bool {
82        self.start <= point && point < self.end
83    }
84
85    /// The intersecting span between `self` and `other`.
86    ///
87    /// NOTE: If either span's `start` is equal to its `end`, `None` is returned.
88    pub fn intersect(self, other: Self) -> Option<Self> {
89        let start = std::cmp::max(self.start, other.start);
90        let end = std::cmp::min(self.end, other.end);
91        if end <= start {
92            None
93        } else {
94            Some(Span { start, end })
95        }
96    }
97
98    /// The portions of `other` that do *not* intersect `self`.
99    ///
100    /// Returns preceding and succeeding diffs respectively.
101    ///
102    /// Returns `(None, None)` in the case that `other` is a subsection of `self`.
103    pub fn difference(self, other: Self) -> (Option<Self>, Option<Self>) {
104        let pre = if self.start <= other.start {
105            None
106        } else {
107            Some(Span::new(other.start, std::cmp::min(self.start, other.end)))
108        };
109        let post = if other.end <= self.end {
110            None
111        } else {
112            Some(Span::new(std::cmp::max(self.end, other.start), other.end))
113        };
114        (pre, post)
115    }
116}
117
118impl fmt::Debug for Span {
119    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
120        write!(f, "Span({}, {})", self.start, self.end)
121    }
122}
123
124impl fmt::Display for Span {
125    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
126        write!(f, "({}, {})", self.start, self.end)
127    }
128}
129
130#[test]
131fn test_span_macro() {
132    assert_eq!(
133        span!(0 / 1, 1 / 1),
134        Span::new(Rational::new(0, 1), Rational::new(1, 1))
135    );
136    assert_eq!(
137        span!(Rational::new(1, 1), 4 / 1),
138        span!(1 / 1, Rational::new(4, 1)),
139    );
140}
141
142#[test]
143fn test_span_fmt() {
144    for n in 0..10 {
145        let a = Rational::new(n, 10);
146        let b = Rational::new(n + 1, 10);
147        let span = span!(a, b);
148        println!("{:?} | {}", span, span);
149    }
150}
151
152#[test]
153fn test_span_intersect() {
154    assert_eq!(
155        span!(0 / 1, 3 / 4).intersect(span!(1 / 4, 1 / 1)),
156        Some(span!(1 / 4, 3 / 4))
157    );
158    assert_eq!(span!(0 / 1, 1 / 4).intersect(span!(3 / 4, 1 / 1)), None);
159}
160
161#[test]
162fn test_span_difference() {
163    let s1 = Span::new(Rational::new(2, 1), Rational::new(5, 1));
164
165    // Case 1: other is entirely before self
166    let other = Span::new(Rational::new(0, 1), Rational::new(1, 1));
167    assert_eq!(s1.difference(other), (Some(other), None));
168
169    // Case 2: other is entirely after self
170    let other = Span::new(Rational::new(6, 1), Rational::new(8, 1));
171    assert_eq!(s1.difference(other), (None, Some(other)));
172
173    // Case 3: other intersects self at the start
174    let other = Span::new(Rational::new(1, 1), Rational::new(3, 1));
175    assert_eq!(
176        s1.difference(other),
177        (
178            Some(Span::new(Rational::new(1, 1), Rational::new(2, 1))),
179            None
180        )
181    );
182
183    // Case 4: other intersects self at the end
184    let other = Span::new(Rational::new(4, 1), Rational::new(6, 1));
185    assert_eq!(
186        s1.difference(other),
187        (
188            None,
189            Some(Span::new(Rational::new(5, 1), Rational::new(6, 1)))
190        )
191    );
192
193    // Case 5: other is entirely within self
194    let other = Span::new(Rational::new(3, 1), Rational::new(4, 1));
195    assert_eq!(s1.difference(other), (None, None));
196
197    // Case 6: self is entirely within other
198    let other = Span::new(Rational::new(1, 1), Rational::new(6, 1));
199    assert_eq!(
200        s1.difference(other),
201        (
202            Some(Span::new(Rational::new(1, 1), Rational::new(2, 1))),
203            Some(Span::new(Rational::new(5, 1), Rational::new(6, 1)))
204        )
205    );
206}