1use std::num::TryFromIntError;
2use std::ops::Range;
3
4#[derive(Debug, Clone, PartialEq, Eq, Hash)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6pub enum Slice {
7 Positive {
9 offset: usize,
10 len: usize,
11 },
12 Negative {
13 offset_from_end: usize,
14 len: usize,
15 },
16}
17
18impl Slice {
19 #[allow(clippy::len_without_is_empty)]
20 pub fn len(&self) -> usize {
21 match self {
22 Slice::Positive { len, .. } => *len,
23 Slice::Negative { len, .. } => *len,
24 }
25 }
26
27 pub fn len_mut(&mut self) -> &mut usize {
28 match self {
29 Slice::Positive { len, .. } => len,
30 Slice::Negative { len, .. } => len,
31 }
32 }
33
34 pub fn end_position(&self) -> usize {
39 let Slice::Positive { offset, len } = self.clone() else {
40 panic!("cannot use end_position() on a negative slice");
41 };
42
43 offset.saturating_add(len)
44 }
45
46 pub fn offsetted(self, position: usize) -> Self {
51 let Slice::Positive { offset, len } = self else {
52 panic!("cannot use offsetted() on a negative slice");
53 };
54
55 let (offset, len) = if position <= offset {
56 (offset - position, len)
57 } else {
58 let n_past_offset = position - offset;
59 (0, len.saturating_sub(n_past_offset))
60 };
61
62 Slice::Positive { offset, len }
63 }
64
65 pub fn restrict_to_bounds(self, n_rows: usize) -> Self {
68 match self {
69 Slice::Positive { offset, len } => {
70 let offset = offset.min(n_rows);
71 let len = len.min(n_rows - offset);
72 Slice::Positive { offset, len }
73 },
74 Slice::Negative {
75 offset_from_end,
76 len,
77 } => {
78 if n_rows >= offset_from_end {
79 let offset = n_rows - offset_from_end;
81 let len = len.min(n_rows - offset);
82 Slice::Positive { offset, len }
83 } else {
84 let stop_at_n_from_end = offset_from_end.saturating_sub(len);
86 let len = n_rows.saturating_sub(stop_at_n_from_end);
87
88 Slice::Positive { offset: 0, len }
89 }
90 },
91 }
92 }
93}
94
95impl From<(usize, usize)> for Slice {
96 fn from((offset, len): (usize, usize)) -> Self {
97 Slice::Positive { offset, len }
98 }
99}
100
101impl From<(i64, usize)> for Slice {
102 fn from((offset, len): (i64, usize)) -> Self {
103 if offset >= 0 {
104 Slice::Positive {
105 offset: usize::try_from(offset).unwrap(),
106 len,
107 }
108 } else {
109 Slice::Negative {
110 offset_from_end: usize::try_from(-offset).unwrap(),
111 len,
112 }
113 }
114 }
115}
116
117impl TryFrom<Slice> for (i64, usize) {
118 type Error = TryFromIntError;
119
120 fn try_from(value: Slice) -> Result<Self, Self::Error> {
121 match value {
122 Slice::Positive { offset, len } => Ok((i64::try_from(offset)?, len)),
123 Slice::Negative {
124 offset_from_end,
125 len,
126 } => Ok((-i64::try_from(offset_from_end)?, len)),
127 }
128 }
129}
130
131impl From<Slice> for Range<usize> {
132 fn from(value: Slice) -> Self {
133 match value {
134 Slice::Positive { offset, len } => offset..offset.checked_add(len).unwrap(),
135 Slice::Negative { .. } => panic!("cannot convert negative slice into range"),
136 }
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::Slice;
143
144 #[test]
145 fn test_slice_offset() {
146 assert_eq!(
147 Slice::Positive { offset: 3, len: 10 }.offsetted(1),
148 Slice::Positive { offset: 2, len: 10 }
149 );
150 assert_eq!(
151 Slice::Positive { offset: 3, len: 10 }.offsetted(5),
152 Slice::Positive { offset: 0, len: 8 }
153 );
154 }
155
156 #[test]
157 fn test_slice_restrict_to_bounds() {
158 assert_eq!(
159 Slice::Positive { offset: 3, len: 10 }.restrict_to_bounds(7),
160 Slice::Positive { offset: 3, len: 4 },
161 );
162 assert_eq!(
163 Slice::Positive { offset: 3, len: 10 }.restrict_to_bounds(0),
164 Slice::Positive { offset: 0, len: 0 },
165 );
166 assert_eq!(
167 Slice::Positive { offset: 3, len: 10 }.restrict_to_bounds(1),
168 Slice::Positive { offset: 1, len: 0 },
169 );
170 assert_eq!(
171 Slice::Positive { offset: 2, len: 0 }.restrict_to_bounds(10),
172 Slice::Positive { offset: 2, len: 0 },
173 );
174 assert_eq!(
175 Slice::Negative {
176 offset_from_end: 3,
177 len: 1
178 }
179 .restrict_to_bounds(4),
180 Slice::Positive { offset: 1, len: 1 },
181 );
182 assert_eq!(
183 Slice::Negative {
184 offset_from_end: 3,
185 len: 1
186 }
187 .restrict_to_bounds(1),
188 Slice::Positive { offset: 0, len: 0 },
189 );
190 }
191}