1use std::ops::Range;
2
3use crate::IdxSize;
4
5#[derive(Debug, Clone, PartialEq, Eq, Hash)]
6#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
7#[cfg_attr(feature = "dsl-schema", derive(schemars::JsonSchema))]
8pub enum Slice {
9 Positive {
11 offset: usize,
12 len: usize,
13 },
14 Negative {
15 offset_from_end: usize,
16 len: usize,
17 },
18}
19
20impl Slice {
21 #[allow(clippy::len_without_is_empty)]
22 pub fn len(&self) -> usize {
23 match self {
24 Slice::Positive { len, .. } => *len,
25 Slice::Negative { len, .. } => *len,
26 }
27 }
28
29 pub fn len_mut(&mut self) -> &mut usize {
30 match self {
31 Slice::Positive { len, .. } => len,
32 Slice::Negative { len, .. } => len,
33 }
34 }
35
36 pub fn positive_offset(&self) -> usize {
41 let Slice::Positive { offset, len: _ } = self.clone() else {
42 panic!("cannot use positive_offset() on a negative slice");
43 };
44
45 offset
46 }
47
48 pub fn end_position(&self) -> usize {
53 let Slice::Positive { offset, len } = self.clone() else {
54 panic!("cannot use end_position() on a negative slice");
55 };
56
57 offset.saturating_add(len)
58 }
59
60 pub fn offsetted(self, position: usize) -> Self {
65 let Slice::Positive { offset, len } = self else {
66 panic!("cannot use offsetted() on a negative slice");
67 };
68
69 let (offset, len) = if position <= offset {
70 (offset - position, len)
71 } else {
72 let n_past_offset = position - offset;
73 (0, len.saturating_sub(n_past_offset))
74 };
75
76 Slice::Positive { offset, len }
77 }
78
79 pub fn restrict_to_bounds(self, n_rows: usize) -> Self {
82 match self {
83 Slice::Positive { offset, len } => {
84 let offset = offset.min(n_rows);
85 let len = len.min(n_rows - offset);
86 Slice::Positive { offset, len }
87 },
88 Slice::Negative {
89 offset_from_end,
90 len,
91 } => {
92 if n_rows >= offset_from_end {
93 let offset = n_rows - offset_from_end;
95 let len = len.min(n_rows - offset);
96 Slice::Positive { offset, len }
97 } else {
98 let stop_at_n_from_end = offset_from_end.saturating_sub(len);
100 let len = n_rows.saturating_sub(stop_at_n_from_end);
101
102 Slice::Positive { offset: 0, len }
103 }
104 },
105 }
106 }
107
108 pub fn to_signed_offset_len(&self) -> (i64, IdxSize) {
109 let (offset, len) = match self {
110 Slice::Positive { offset, len } => (i64::try_from(*offset).unwrap(), *len),
111 Slice::Negative {
112 offset_from_end,
113 len,
114 } => (-i64::try_from(*offset_from_end).unwrap(), *len),
115 };
116 (offset, len.min(IdxSize::MAX as usize) as IdxSize)
117 }
118}
119
120impl From<(usize, usize)> for Slice {
121 fn from((offset, len): (usize, usize)) -> Self {
122 Slice::Positive { offset, len }
123 }
124}
125
126impl From<(i64, usize)> for Slice {
127 fn from((offset, len): (i64, usize)) -> Self {
128 if offset >= 0 {
129 Slice::Positive {
130 offset: usize::try_from(offset).unwrap(),
131 len,
132 }
133 } else {
134 Slice::Negative {
135 offset_from_end: usize::try_from(-offset).unwrap(),
136 len,
137 }
138 }
139 }
140}
141
142impl From<Slice> for (i128, i128) {
143 fn from(value: Slice) -> Self {
144 match value {
145 Slice::Positive { offset, len } => (
146 i128::try_from(offset).unwrap(),
147 i128::try_from(len).unwrap(),
148 ),
149 Slice::Negative {
150 offset_from_end,
151 len,
152 } => (
153 -i128::try_from(offset_from_end).unwrap(),
154 i128::try_from(len).unwrap(),
155 ),
156 }
157 }
158}
159
160impl From<Slice> for Range<usize> {
161 fn from(value: Slice) -> Self {
162 match value {
163 Slice::Positive { offset, len } => {
164 offset
165 ..offset
166 .checked_add(len)
167 .or((len == usize::MAX).then_some(usize::MAX))
169 .unwrap()
170 },
171 Slice::Negative { .. } => panic!("cannot convert negative slice into range"),
172 }
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::Slice;
179
180 #[test]
181 fn test_slice_offset() {
182 assert_eq!(
183 Slice::Positive { offset: 3, len: 10 }.offsetted(1),
184 Slice::Positive { offset: 2, len: 10 }
185 );
186 assert_eq!(
187 Slice::Positive { offset: 3, len: 10 }.offsetted(5),
188 Slice::Positive { offset: 0, len: 8 }
189 );
190 }
191
192 #[test]
193 fn test_slice_restrict_to_bounds() {
194 assert_eq!(
195 Slice::Positive { offset: 3, len: 10 }.restrict_to_bounds(7),
196 Slice::Positive { offset: 3, len: 4 },
197 );
198 assert_eq!(
199 Slice::Positive { offset: 3, len: 10 }.restrict_to_bounds(0),
200 Slice::Positive { offset: 0, len: 0 },
201 );
202 assert_eq!(
203 Slice::Positive { offset: 3, len: 10 }.restrict_to_bounds(1),
204 Slice::Positive { offset: 1, len: 0 },
205 );
206 assert_eq!(
207 Slice::Positive { offset: 2, len: 0 }.restrict_to_bounds(10),
208 Slice::Positive { offset: 2, len: 0 },
209 );
210 assert_eq!(
211 Slice::Negative {
212 offset_from_end: 3,
213 len: 2
214 }
215 .restrict_to_bounds(4),
216 Slice::Positive { offset: 1, len: 2 },
217 );
218 assert_eq!(
219 Slice::Negative {
220 offset_from_end: 3,
221 len: 2
222 }
223 .restrict_to_bounds(3),
224 Slice::Positive { offset: 0, len: 2 },
225 );
226 assert_eq!(
227 Slice::Negative {
228 offset_from_end: 3,
229 len: 2
230 }
231 .restrict_to_bounds(2),
232 Slice::Positive { offset: 0, len: 1 },
233 );
234 assert_eq!(
235 Slice::Negative {
236 offset_from_end: 3,
237 len: 2
238 }
239 .restrict_to_bounds(1),
240 Slice::Positive { offset: 0, len: 0 },
241 );
242 }
243}