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 } => offset..offset.checked_add(len).unwrap(),
164 Slice::Negative { .. } => panic!("cannot convert negative slice into range"),
165 }
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::Slice;
172
173 #[test]
174 fn test_slice_offset() {
175 assert_eq!(
176 Slice::Positive { offset: 3, len: 10 }.offsetted(1),
177 Slice::Positive { offset: 2, len: 10 }
178 );
179 assert_eq!(
180 Slice::Positive { offset: 3, len: 10 }.offsetted(5),
181 Slice::Positive { offset: 0, len: 8 }
182 );
183 }
184
185 #[test]
186 fn test_slice_restrict_to_bounds() {
187 assert_eq!(
188 Slice::Positive { offset: 3, len: 10 }.restrict_to_bounds(7),
189 Slice::Positive { offset: 3, len: 4 },
190 );
191 assert_eq!(
192 Slice::Positive { offset: 3, len: 10 }.restrict_to_bounds(0),
193 Slice::Positive { offset: 0, len: 0 },
194 );
195 assert_eq!(
196 Slice::Positive { offset: 3, len: 10 }.restrict_to_bounds(1),
197 Slice::Positive { offset: 1, len: 0 },
198 );
199 assert_eq!(
200 Slice::Positive { offset: 2, len: 0 }.restrict_to_bounds(10),
201 Slice::Positive { offset: 2, len: 0 },
202 );
203 assert_eq!(
204 Slice::Negative {
205 offset_from_end: 3,
206 len: 2
207 }
208 .restrict_to_bounds(4),
209 Slice::Positive { offset: 1, len: 2 },
210 );
211 assert_eq!(
212 Slice::Negative {
213 offset_from_end: 3,
214 len: 2
215 }
216 .restrict_to_bounds(3),
217 Slice::Positive { offset: 0, len: 2 },
218 );
219 assert_eq!(
220 Slice::Negative {
221 offset_from_end: 3,
222 len: 2
223 }
224 .restrict_to_bounds(2),
225 Slice::Positive { offset: 0, len: 1 },
226 );
227 assert_eq!(
228 Slice::Negative {
229 offset_from_end: 3,
230 len: 2
231 }
232 .restrict_to_bounds(1),
233 Slice::Positive { offset: 0, len: 0 },
234 );
235 }
236}