use std::rc::Rc;
use crate::error::Error;
#[derive(Debug, Clone, PartialEq, Eq)]
enum State {
Initial,
Replaced(Rc<[u8]>),
Inserted(Rc<[u8]>),
}
impl State {
fn is_inserted(&self) -> bool {
matches!(*self, State::Inserted(..))
}
}
#[derive(Clone, PartialEq, Eq)]
struct Span {
start: usize,
end: usize,
data: State,
}
impl std::fmt::Debug for Span {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let state = match self.data {
State::Initial => "initial",
State::Replaced(_) => "replaced",
State::Inserted(_) => "inserted",
};
write!(f, "({}, {}: {state})", self.start, self.end)
}
}
#[derive(Debug, Clone, Default)]
pub struct Data {
original: Vec<u8>,
parts: Vec<Span>,
}
impl Data {
pub fn new(data: &[u8]) -> Self {
Data {
original: data.into(),
parts: vec![Span {
data: State::Initial,
start: 0,
end: data.len(),
}],
}
}
pub fn to_vec(&self) -> Vec<u8> {
if self.original.is_empty() {
return Vec::new();
}
self.parts.iter().fold(Vec::new(), |mut acc, d| {
match d.data {
State::Initial => acc.extend_from_slice(&self.original[d.start..d.end]),
State::Replaced(ref d) | State::Inserted(ref d) => acc.extend_from_slice(d),
};
acc
})
}
pub fn replace_range(
&mut self,
range: std::ops::Range<usize>,
data: &[u8],
) -> Result<(), Error> {
if range.start > range.end {
return Err(Error::InvalidRange(range));
}
if range.end > self.original.len() {
return Err(Error::DataLengthExceeded(range, self.original.len()));
}
let insert_only = range.start == range.end;
let Some(index_of_part_to_split) = self
.parts
.iter()
.position(|p| !p.data.is_inserted() && p.start <= range.start && p.end >= range.end)
else {
tracing::debug!(
"no single slice covering {}..{}, current slices: {:?}",
range.start,
range.end,
self.parts,
);
return Err(Error::MaybeAlreadyReplaced(range));
};
let part_to_split = &self.parts[index_of_part_to_split];
if part_to_split.start == range.start && part_to_split.end == range.end {
if let State::Replaced(ref replacement) = part_to_split.data {
if &**replacement == data {
return Ok(());
}
}
}
if part_to_split.data != State::Initial {
return Err(Error::AlreadyReplaced);
}
let mut new_parts = Vec::with_capacity(self.parts.len() + 2);
if let Some(ps) = self.parts.get(..index_of_part_to_split) {
new_parts.extend_from_slice(ps);
}
if range.start > part_to_split.start {
new_parts.push(Span {
start: part_to_split.start,
end: range.start,
data: State::Initial,
});
}
new_parts.push(Span {
start: range.start,
end: range.end,
data: if insert_only {
State::Inserted(data.into())
} else {
State::Replaced(data.into())
},
});
if range.end < part_to_split.end {
new_parts.push(Span {
start: range.end,
end: part_to_split.end,
data: State::Initial,
});
}
if let Some(ps) = self.parts.get(index_of_part_to_split + 1..) {
new_parts.extend_from_slice(ps);
}
self.parts = new_parts;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
fn str(i: &[u8]) -> &str {
::std::str::from_utf8(i).unwrap()
}
#[test]
fn insert_at_beginning() {
let mut d = Data::new(b"foo bar baz");
d.replace_range(0..0, b"oh no ").unwrap();
assert_eq!("oh no foo bar baz", str(&d.to_vec()));
}
#[test]
fn insert_at_end() {
let mut d = Data::new(b"foo bar baz");
d.replace_range(11..11, b" oh no").unwrap();
assert_eq!("foo bar baz oh no", str(&d.to_vec()));
}
#[test]
fn replace_some_stuff() {
let mut d = Data::new(b"foo bar baz");
d.replace_range(4..7, b"lol").unwrap();
assert_eq!("foo lol baz", str(&d.to_vec()));
}
#[test]
fn replace_a_single_char() {
let mut d = Data::new(b"let y = true;");
d.replace_range(4..5, b"mut y").unwrap();
assert_eq!("let mut y = true;", str(&d.to_vec()));
}
#[test]
fn replace_multiple_lines() {
let mut d = Data::new(b"lorem\nipsum\ndolor");
d.replace_range(6..11, b"lol").unwrap();
assert_eq!("lorem\nlol\ndolor", str(&d.to_vec()));
d.replace_range(12..17, b"lol").unwrap();
assert_eq!("lorem\nlol\nlol", str(&d.to_vec()));
}
#[test]
fn replace_multiple_lines_with_insert_only() {
let mut d = Data::new(b"foo!");
d.replace_range(3..3, b"bar").unwrap();
assert_eq!("foobar!", str(&d.to_vec()));
d.replace_range(0..3, b"baz").unwrap();
assert_eq!("bazbar!", str(&d.to_vec()));
d.replace_range(3..4, b"?").unwrap();
assert_eq!("bazbar?", str(&d.to_vec()));
}
#[test]
fn replace_invalid_range() {
let mut d = Data::new(b"foo!");
assert!(d.replace_range(2..1, b"bar").is_err());
assert!(d.replace_range(0..3, b"bar").is_ok());
}
#[test]
fn empty_to_vec_roundtrip() {
let s = "";
assert_eq!(s.as_bytes(), Data::new(s.as_bytes()).to_vec().as_slice());
}
#[test]
fn replace_overlapping_stuff_errs() {
let mut d = Data::new(b"foo bar baz");
d.replace_range(4..7, b"lol").unwrap();
assert_eq!("foo lol baz", str(&d.to_vec()));
assert!(matches!(
d.replace_range(4..7, b"lol2").unwrap_err(),
Error::AlreadyReplaced,
));
}
#[test]
fn broken_replacements() {
let mut d = Data::new(b"foo");
assert!(matches!(
d.replace_range(4..8, b"lol").unwrap_err(),
Error::DataLengthExceeded(std::ops::Range { start: 4, end: 8 }, 3),
));
}
#[test]
fn replace_same_twice() {
let mut d = Data::new(b"foo");
d.replace_range(0..1, b"b").unwrap();
d.replace_range(0..1, b"b").unwrap();
assert_eq!("boo", str(&d.to_vec()));
}
proptest! {
#[test]
fn new_to_vec_roundtrip(ref s in "\\PC*") {
assert_eq!(s.as_bytes(), Data::new(s.as_bytes()).to_vec().as_slice());
}
#[test]
fn replace_random_chunks(
ref data in "\\PC*",
ref replacements in prop::collection::vec(
(any::<::std::ops::Range<usize>>(), any::<Vec<u8>>()),
1..100,
)
) {
let mut d = Data::new(data.as_bytes());
for &(ref range, ref bytes) in replacements {
let _ = d.replace_range(range.clone(), bytes);
}
}
}
}