diff --git a/codespan/src/span.rs b/codespan/src/span.rs index de80dcdf..a6c13106 100644 --- a/codespan/src/span.rs +++ b/codespan/src/span.rs @@ -44,11 +44,15 @@ impl Span { Span::new(0, s.len() as u32) } - /// Combine two spans by taking the start of the first span and the end of - /// the other span. + /// Combine two spans by taking the start of the earlier span + /// and the end of the later span. + /// + /// Note: this will work even if the two spans are disjoint. + /// If this doesn't make sense in your application, you should handle it yourself. + /// In that case, you can use `Span::disjoint` as a convenience function. /// /// ```rust - /// use codespan::{ByteIndex, Span}; + /// use codespan::Span; /// /// let span1 = Span::new(0, 4); /// let span2 = Span::new(10, 16); @@ -56,7 +60,28 @@ impl Span { /// assert_eq!(Span::merge(span1, span2), Span::new(0, 16)); /// ``` pub fn merge(self, other: Span) -> Span { - Span::new(self.start(), other.end()) + use std::cmp::{max, min}; + + let start = min(self.start, other.start); + let end = max(self.end, other.end); + Span::new(start, end) + } + + /// A helper function to tell whether two spans do not overlap. + /// + /// ``` + /// use codespan::Span; + /// let span1 = Span::new(0, 4); + /// let span2 = Span::new(10, 16); + /// assert!(span1.disjoint(span2)); + /// ``` + pub fn disjoint(self, other: Span) -> bool { + let (first, last) = if self.end < other.end { + (self, other) + } else { + (other, self) + }; + first.end <= last.start } /// Get the starting byte index. @@ -111,3 +136,63 @@ where Span::new(range.start, range.end) } } + +#[cfg(test)] +mod test { + #[test] + fn test_merge() { + use super::Span; + + // overlap + let a = Span::from(1..5); + let b = Span::from(3..10); + assert_eq!(a.merge(b), Span::from(1..10)); + assert_eq!(b.merge(a), Span::from(1..10)); + + // subset + let two_four = (2..4).into(); + assert_eq!(a.merge(two_four), (1..5).into()); + assert_eq!(two_four.merge(a), (1..5).into()); + + // disjoint + let ten_twenty = (10..20).into(); + assert_eq!(a.merge(ten_twenty), (1..20).into()); + assert_eq!(ten_twenty.merge(a), (1..20).into()); + + // identity + assert_eq!(a.merge(a), a); + } + + #[test] + fn test_disjoint() { + use super::Span; + + // overlap + let a = Span::from(1..5); + let b = Span::from(3..10); + assert!(!a.disjoint(b)); + assert!(!b.disjoint(a)); + + // subset + let two_four = (2..4).into(); + assert!(!a.disjoint(two_four)); + assert!(!two_four.disjoint(a)); + + // disjoint + let ten_twenty = (10..20).into(); + assert!(a.disjoint(ten_twenty)); + assert!(ten_twenty.disjoint(a)); + + // identity + assert!(!a.disjoint(a)); + + // off by one (upper bound) + let c = Span::from(5..10); + assert!(a.disjoint(c)); + assert!(c.disjoint(a)); + // off by one (lower bound) + let d = Span::from(0..1); + assert!(a.disjoint(d)); + assert!(d.disjoint(a)); + } +}