use std::ops::Range;
use std::rc::Rc;
use crate::error::Error;
#[derive(Clone)]
struct Span {
range: Range<usize>,
data: Rc<[u8]>,
committed: bool,
}
impl std::fmt::Debug for Span {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let state = if self.is_insert() {
"inserted"
} else {
"replaced"
};
let committed = if self.committed {
"committed"
} else {
"uncommitted"
};
write!(
f,
"({}, {}: {state}, {committed})",
self.range.start, self.range.end
)
}
}
impl Span {
fn new(range: Range<usize>, data: &[u8]) -> Self {
Self {
range,
data: data.into(),
committed: false,
}
}
fn is_insert(&self) -> bool {
self.range.start == self.range.end
}
}
impl PartialEq for Span {
fn eq(&self, other: &Self) -> bool {
self.range == other.range && self.data == other.data
}
}
#[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![],
}
}
pub fn commit(&mut self) {
self.parts.iter_mut().for_each(|span| span.committed = true);
}
pub fn restore(&mut self) {
self.parts.retain(|parts| parts.committed);
}
pub fn to_vec(&self) -> Vec<u8> {
let mut prev_end = 0;
let mut s = self.parts.iter().fold(Vec::new(), |mut acc, span| {
debug_assert!(
prev_end <= span.range.start,
"expected parts in sorted order"
);
acc.extend_from_slice(&self.original[prev_end..span.range.start]);
acc.extend_from_slice(&span.data);
prev_end = span.range.end;
acc
});
s.extend_from_slice(&self.original[prev_end..]);
s
}
pub fn replace_range(&mut self, range: 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 ins_point = self.parts.partition_point(|span| {
span.range.start < range.start
|| (span.range.start == range.start && span.range.end < range.end)
});
let incoming = Span::new(range, data.as_ref());
if let Some(before) = ins_point.checked_sub(1).and_then(|i| self.parts.get(i)) {
if incoming.range.start < before.range.end {
return Err(Error::AlreadyReplaced {
is_identical: incoming == *before,
range: incoming.range,
});
}
}
if let Some(after) = self.parts.get(ins_point) {
if incoming.range.end > after.range.start || incoming.range == after.range {
return Err(Error::AlreadyReplaced {
is_identical: incoming == *after,
range: incoming.range,
});
}
}
self.parts.insert(ins_point, incoming);
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]
#[allow(clippy::reversed_empty_ranges)]
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_same_range_diff_data() {
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 {
is_identical: false,
..
},
));
}
#[test]
fn replace_same_range_same_data() {
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"lol").unwrap_err(),
Error::AlreadyReplaced {
is_identical: true,
..
},
));
}
#[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 insert_same_twice() {
let mut d = Data::new(b"foo");
d.replace_range(1..1, b"b").unwrap();
assert_eq!("fboo", str(&d.to_vec()));
assert!(matches!(
d.replace_range(1..1, b"b").unwrap_err(),
Error::AlreadyReplaced {
is_identical: true,
..
},
));
assert_eq!("fboo", str(&d.to_vec()));
}
#[test]
fn commit_restore() {
let mut d = Data::new(b", ");
assert_eq!(", ", str(&d.to_vec()));
d.replace_range(2..2, b"world").unwrap();
d.replace_range(0..0, b"hello").unwrap();
assert_eq!("hello, world", str(&d.to_vec()));
d.restore();
assert_eq!(", ", str(&d.to_vec()));
d.commit();
assert_eq!(", ", str(&d.to_vec()));
d.replace_range(2..2, b"world").unwrap();
assert_eq!(", world", str(&d.to_vec()));
d.commit();
assert_eq!(", world", str(&d.to_vec()));
d.restore();
assert_eq!(", world", str(&d.to_vec()));
d.replace_range(0..0, b"hello").unwrap();
assert_eq!("hello, world", str(&d.to_vec()));
d.commit();
assert_eq!("hello, world", str(&d.to_vec()));
d.restore();
assert_eq!("hello, world", 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);
}
}
}
}