use std::num::TryFromIntError;
use std::ops::Range;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "dsl-schema", derive(schemars::JsonSchema))]
pub enum Slice {
Positive {
offset: usize,
len: usize,
},
Negative {
offset_from_end: usize,
len: usize,
},
}
impl Slice {
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> usize {
match self {
Slice::Positive { len, .. } => *len,
Slice::Negative { len, .. } => *len,
}
}
pub fn len_mut(&mut self) -> &mut usize {
match self {
Slice::Positive { len, .. } => len,
Slice::Negative { len, .. } => len,
}
}
pub fn positive_offset(&self) -> usize {
let Slice::Positive { offset, len: _ } = self.clone() else {
panic!("cannot use positive_offset() on a negative slice");
};
offset
}
pub fn end_position(&self) -> usize {
let Slice::Positive { offset, len } = self.clone() else {
panic!("cannot use end_position() on a negative slice");
};
offset.saturating_add(len)
}
pub fn offsetted(self, position: usize) -> Self {
let Slice::Positive { offset, len } = self else {
panic!("cannot use offsetted() on a negative slice");
};
let (offset, len) = if position <= offset {
(offset - position, len)
} else {
let n_past_offset = position - offset;
(0, len.saturating_sub(n_past_offset))
};
Slice::Positive { offset, len }
}
pub fn restrict_to_bounds(self, n_rows: usize) -> Self {
match self {
Slice::Positive { offset, len } => {
let offset = offset.min(n_rows);
let len = len.min(n_rows - offset);
Slice::Positive { offset, len }
},
Slice::Negative {
offset_from_end,
len,
} => {
if n_rows >= offset_from_end {
let offset = n_rows - offset_from_end;
let len = len.min(n_rows - offset);
Slice::Positive { offset, len }
} else {
let stop_at_n_from_end = offset_from_end.saturating_sub(len);
let len = n_rows.saturating_sub(stop_at_n_from_end);
Slice::Positive { offset: 0, len }
}
},
}
}
}
impl From<(usize, usize)> for Slice {
fn from((offset, len): (usize, usize)) -> Self {
Slice::Positive { offset, len }
}
}
impl From<(i64, usize)> for Slice {
fn from((offset, len): (i64, usize)) -> Self {
if offset >= 0 {
Slice::Positive {
offset: usize::try_from(offset).unwrap(),
len,
}
} else {
Slice::Negative {
offset_from_end: usize::try_from(-offset).unwrap(),
len,
}
}
}
}
impl TryFrom<Slice> for (i64, usize) {
type Error = TryFromIntError;
fn try_from(value: Slice) -> Result<Self, Self::Error> {
match value {
Slice::Positive { offset, len } => Ok((i64::try_from(offset)?, len)),
Slice::Negative {
offset_from_end,
len,
} => Ok((-i64::try_from(offset_from_end)?, len)),
}
}
}
impl From<Slice> for Range<usize> {
fn from(value: Slice) -> Self {
match value {
Slice::Positive { offset, len } => offset..offset.checked_add(len).unwrap(),
Slice::Negative { .. } => panic!("cannot convert negative slice into range"),
}
}
}
#[cfg(test)]
mod tests {
use super::Slice;
#[test]
fn test_slice_offset() {
assert_eq!(
Slice::Positive { offset: 3, len: 10 }.offsetted(1),
Slice::Positive { offset: 2, len: 10 }
);
assert_eq!(
Slice::Positive { offset: 3, len: 10 }.offsetted(5),
Slice::Positive { offset: 0, len: 8 }
);
}
#[test]
fn test_slice_restrict_to_bounds() {
assert_eq!(
Slice::Positive { offset: 3, len: 10 }.restrict_to_bounds(7),
Slice::Positive { offset: 3, len: 4 },
);
assert_eq!(
Slice::Positive { offset: 3, len: 10 }.restrict_to_bounds(0),
Slice::Positive { offset: 0, len: 0 },
);
assert_eq!(
Slice::Positive { offset: 3, len: 10 }.restrict_to_bounds(1),
Slice::Positive { offset: 1, len: 0 },
);
assert_eq!(
Slice::Positive { offset: 2, len: 0 }.restrict_to_bounds(10),
Slice::Positive { offset: 2, len: 0 },
);
assert_eq!(
Slice::Negative {
offset_from_end: 3,
len: 1
}
.restrict_to_bounds(4),
Slice::Positive { offset: 1, len: 1 },
);
assert_eq!(
Slice::Negative {
offset_from_end: 3,
len: 1
}
.restrict_to_bounds(1),
Slice::Positive { offset: 0, len: 0 },
);
}
}