#![allow(dead_code)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum Direction {
Forward,
Backward,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) struct HelixRange {
anchor: usize,
head: usize,
}
impl HelixRange {
pub(super) fn new(anchor: usize, head: usize) -> Self {
Self { anchor, head }
}
pub(super) fn point(head: usize) -> Self {
Self::new(head, head)
}
pub(super) fn start(&self) -> usize {
self.anchor.min(self.head)
}
pub(super) fn end(&self) -> usize {
self.anchor.max(self.head)
}
pub(super) fn len(&self) -> usize {
self.end() - self.start()
}
pub(super) fn is_empty(&self) -> bool {
self.anchor == self.head
}
pub(super) fn direction(&self) -> Direction {
if self.head < self.anchor {
Direction::Backward
} else {
Direction::Forward
}
}
pub(super) fn flip(self) -> Self {
Self {
anchor: self.head,
head: self.anchor,
}
}
pub(super) fn with_direction(self, direction: Direction) -> Self {
if self.direction() == direction {
self
} else {
self.flip()
}
}
pub(super) fn extend(self, from: usize, to: usize) -> Self {
debug_assert!(from <= to);
if self.anchor <= self.head {
Self {
anchor: self.anchor.min(from),
head: self.head.max(to),
}
} else {
Self {
anchor: self.anchor.max(to),
head: self.head.min(from),
}
}
}
pub(super) fn contains(&self, pos: usize) -> bool {
self.start() <= pos && pos < self.end()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn contains() {
let range = HelixRange::new(10, 12);
assert!(!range.contains(9));
assert!(range.contains(10));
assert!(range.contains(11));
assert!(!range.contains(12));
assert!(!range.contains(13));
let range = HelixRange::new(9, 6);
assert!(!range.contains(9));
assert!(range.contains(7));
assert!(range.contains(6));
}
#[test]
fn point_constructs_empty_range_at_head() {
let range = HelixRange::point(5);
assert_eq!(range.start(), 5);
assert_eq!(range.end(), 5);
assert!(range.is_empty());
}
#[test]
fn new_preserves_anchor_and_head_order() {
let forward = HelixRange::new(2, 5);
assert_eq!(forward.direction(), Direction::Forward);
let backward = HelixRange::new(5, 2);
assert_eq!(backward.direction(), Direction::Backward);
}
#[test]
fn start_returns_lower_of_anchor_and_head() {
assert_eq!(HelixRange::new(2, 5).start(), 2);
assert_eq!(HelixRange::new(5, 2).start(), 2);
}
#[test]
fn end_returns_higher_of_anchor_and_head() {
assert_eq!(HelixRange::new(2, 5).end(), 5);
assert_eq!(HelixRange::new(5, 2).end(), 5);
}
#[test]
fn start_and_end_agree_for_empty_range() {
let range = HelixRange::point(7);
assert_eq!(range.start(), range.end());
}
#[test]
fn len_is_zero_for_empty_range() {
assert_eq!(HelixRange::point(7).len(), 0);
}
#[test]
fn len_ignores_direction() {
assert_eq!(HelixRange::new(2, 5).len(), 3);
assert_eq!(HelixRange::new(5, 2).len(), 3);
}
#[test]
fn is_empty_true_when_anchor_equals_head() {
assert!(HelixRange::new(5, 5).is_empty());
assert!(HelixRange::point(0).is_empty());
}
#[test]
fn is_empty_false_for_nonzero_width() {
assert!(!HelixRange::new(2, 5).is_empty());
assert!(!HelixRange::new(5, 2).is_empty());
}
#[test]
fn direction_forward_when_head_greater_than_anchor() {
assert_eq!(HelixRange::new(2, 5).direction(), Direction::Forward);
}
#[test]
fn direction_backward_when_head_less_than_anchor() {
assert_eq!(HelixRange::new(5, 2).direction(), Direction::Backward);
}
#[test]
fn direction_forward_for_empty_range() {
assert_eq!(HelixRange::point(5).direction(), Direction::Forward);
}
#[test]
fn flip_swaps_anchor_and_head() {
let flipped = HelixRange::new(2, 5).flip();
assert_eq!(flipped, HelixRange::new(5, 2));
}
#[test]
fn flip_twice_returns_original() {
let range = HelixRange::new(2, 5);
assert_eq!(range.flip().flip(), range);
}
#[test]
fn flip_of_empty_range_is_unchanged() {
let range = HelixRange::point(5);
assert_eq!(range.flip(), range);
}
#[test]
fn with_direction_noop_when_already_forward() {
let range = HelixRange::new(2, 5);
assert_eq!(range.with_direction(Direction::Forward), range);
}
#[test]
fn with_direction_noop_when_already_backward() {
let range = HelixRange::new(5, 2);
assert_eq!(range.with_direction(Direction::Backward), range);
}
#[test]
fn with_direction_flips_forward_to_backward() {
let range = HelixRange::new(2, 5);
assert_eq!(
range.with_direction(Direction::Backward),
HelixRange::new(5, 2)
);
}
#[test]
fn with_direction_flips_backward_to_forward() {
let range = HelixRange::new(5, 2);
assert_eq!(
range.with_direction(Direction::Forward),
HelixRange::new(2, 5)
);
}
#[test]
fn with_direction_on_empty_range_stays_forward() {
let range = HelixRange::point(5);
assert_eq!(range.with_direction(Direction::Forward), range);
assert_eq!(range.with_direction(Direction::Backward), range);
}
#[test]
fn extend_forward_shrinks_anchor_left() {
let range = HelixRange::new(5, 8);
assert_eq!(range.extend(2, 3), HelixRange::new(2, 8));
}
#[test]
fn extend_forward_grows_head_right() {
let range = HelixRange::new(2, 5);
assert_eq!(range.extend(6, 8), HelixRange::new(2, 8));
}
#[test]
fn extend_forward_grows_both_sides() {
let range = HelixRange::new(4, 6);
assert_eq!(range.extend(2, 8), HelixRange::new(2, 8));
}
#[test]
fn extend_forward_noop_when_range_already_covers() {
let range = HelixRange::new(1, 9);
assert_eq!(range.extend(3, 5), range);
}
#[test]
fn extend_backward_preserves_direction() {
let range = HelixRange::new(8, 2);
let result = range.extend(4, 6);
assert_eq!(result.direction(), Direction::Backward);
}
#[test]
fn extend_backward_grows_head_left() {
let range = HelixRange::new(8, 5);
assert_eq!(range.extend(2, 3), HelixRange::new(8, 2));
}
#[test]
fn extend_backward_grows_anchor_right() {
let range = HelixRange::new(5, 2);
assert_eq!(range.extend(6, 8), HelixRange::new(8, 2));
}
#[test]
fn extend_from_empty_range_stays_forward() {
let range = HelixRange::point(5);
let result = range.extend(3, 7);
assert_eq!(result.direction(), Direction::Forward);
assert_eq!(result, HelixRange::new(3, 7));
}
#[test]
fn extend_with_zero_width_target_is_safe() {
let range = HelixRange::new(2, 5);
assert_eq!(range.extend(3, 3), range);
}
#[test]
fn contains_false_for_empty_range() {
let range = HelixRange::point(5);
assert!(!range.contains(5));
assert!(!range.contains(4));
assert!(!range.contains(6));
}
#[test]
fn contains_is_direction_agnostic() {
let forward = HelixRange::new(2, 5);
let backward = HelixRange::new(5, 2);
for pos in 0..=6 {
assert_eq!(
forward.contains(pos),
backward.contains(pos),
"mismatch at {pos}"
);
}
}
}