use std::ops::Range;
use ftree::FenwickTree;
use serde::Deserialize;
#[derive(Copy, Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum InsertionPoint {
BeforeStart,
AfterEnd,
}
#[derive(Clone, Debug)]
pub struct Replacement {
start: usize,
end: usize,
insertion_point: InsertionPoint,
value: String,
precedence: usize,
}
impl Replacement {
pub fn new(
start: usize,
end: usize,
insertion_point: InsertionPoint,
value: String,
precedence: usize,
) -> Self {
Replacement {
start,
end,
insertion_point,
value,
precedence,
}
}
pub fn start(&self) -> usize {
self.start
}
pub fn end(&self) -> usize {
self.end
}
pub fn insertion_point(&self) -> InsertionPoint {
self.insertion_point
}
pub fn value(&self) -> &str {
self.value.as_ref()
}
pub fn precedence(&self) -> usize {
self.precedence
}
}
#[derive(Clone, Debug)]
pub struct Fixer {
value: String,
tree: FenwickTree<i32>,
}
#[allow(unused)]
impl Fixer {
pub fn new(value: String) -> Self {
Fixer {
tree: FenwickTree::from_iter(vec![0; value.len() + 1]),
value,
}
}
pub fn apply_replacement(&mut self, replacement: &Replacement) {
let old_start = replacement.start;
let old_end = replacement.end;
let new_start = self.transform(old_start);
let new_end = self.transform(old_end);
let rep_len =
i32::try_from(replacement.value().len()).expect("replacement length fits into i32");
let range = i32::try_from(old_end - old_start).expect("range fits into i32");
let shift = rep_len - range;
let insert_at = match replacement.insertion_point() {
InsertionPoint::BeforeStart => old_start,
InsertionPoint::AfterEnd => old_end + 1,
};
assert!(
insert_at <= self.tree().len(),
"attempt to insert out-of-bounds"
);
self.tree.add_at(insert_at, shift);
self.value
.replace_range(new_start..new_end, &replacement.value);
}
pub fn apply_replacements(&mut self, mut reps: Vec<Replacement>) {
reps.sort_by_key(|r| r.precedence());
reps.iter().rev().for_each(|r| self.apply_replacement(r));
}
pub fn value(&self) -> &str {
&self.value
}
pub fn adjust_range(&self, range: Range<usize>) -> Range<usize> {
self.transform(range.start)..self.transform(range.end)
}
pub fn tree(&self) -> &FenwickTree<i32> {
&self.tree
}
pub fn transform(&self, index: usize) -> usize {
usize::try_from(
i32::try_from(index).expect("index fits into i32") + self.tree.prefix_sum(index, 0i32),
)
.expect("updated index fits into usize")
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use crate::fix::Fixer;
use crate::fix::InsertionPoint;
use crate::fix::Replacement;
#[test]
fn test_fixer_insertion() {
let value = String::from("hello");
let insertion = String::from("world");
let rep = Replacement::new(
value.len(),
value.len(),
InsertionPoint::AfterEnd,
insertion,
2,
);
let rep2 = Replacement::new(5, 5, InsertionPoint::BeforeStart, String::from(" "), 1);
let mut fixer = Fixer::new(value);
let mut fixer2 = fixer.clone();
fixer.apply_replacement(&rep);
fixer.apply_replacement(&rep2);
assert_eq!(fixer.value(), "hello world");
fixer2.apply_replacements(vec![rep, rep2]);
assert_eq!(fixer2.value(), "hello world");
}
#[test]
fn test_fixer_deletion() {
let value = String::from("My grammar is perfect.");
let del = String::from("");
let del2 = String::from("bad");
let rep = Replacement::new(11, 14, InsertionPoint::BeforeStart, del, 2);
let rep2 = Replacement::new(14, 21, InsertionPoint::AfterEnd, del2, 1);
let mut fixer = Fixer::new(value);
let mut fixer2 = fixer.clone();
fixer.apply_replacement(&rep);
fixer.apply_replacement(&rep2);
assert_eq!(fixer.value(), "My grammar bad.");
fixer2.apply_replacements(vec![rep2, rep]);
assert_eq!(fixer2.value(), "My grammar bad.");
}
#[test]
fn test_fixer_indel() {
let value = String::from("This statement is false.");
let del = String::from("");
let ins = String::from("true");
let rep = Replacement::new(18, 23, InsertionPoint::BeforeStart, del, 2);
let rep2 = Replacement::new(18, 18, InsertionPoint::AfterEnd, ins, 1);
let mut fixer = Fixer::new(value);
let mut fixer2 = fixer.clone();
fixer.apply_replacement(&rep);
fixer.apply_replacement(&rep2);
assert_eq!(fixer.value(), "This statement is true.");
fixer2.apply_replacements(vec![rep2, rep]);
assert_eq!(fixer2.value(), "This statement is true.");
}
#[test]
#[should_panic]
fn test_out_of_bounds_insert() {
let value = String::from("012345");
let ins = String::from("6");
let rep = Replacement::new(7, 7, InsertionPoint::AfterEnd, ins, 1);
let mut fixer = Fixer::new(value);
fixer.apply_replacement(&rep);
}
}