diff --git a/src/lib.rs b/src/lib.rs index 3ef1ecf6e..dd2163137 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,5 +2,4 @@ #![allow(dead_code)] mod errors; -mod operation; mod taskdb; diff --git a/src/operation.rs b/src/operation.rs deleted file mode 100644 index e643b228f..000000000 --- a/src/operation.rs +++ /dev/null @@ -1,576 +0,0 @@ -#[derive(PartialEq, Debug, Clone)] -enum Op { - Retain(usize), - Insert(String), - Delete(usize), -} - -#[derive(PartialEq, Debug, Clone)] -struct TextOperation { - // When an operation is applied to an input string, you can think of this as if an imaginary - // cursor runs over the entire string and skips over some parts, deletes some parts and inserts - // characters at some positions. These actions (skip/delete/insert) are stored as an array in - // the "ops" property. - ops: Vec, - - // An operation's base_length is the length of every string the operation can be applied to. - base_length: usize, - - // The target_length is the length of every string that results from applying the operation on - // a valid input string. - target_length: usize, -} - -impl TextOperation { - // Create a new, empty TextOperation - pub fn new() -> Self { - Self { - ops: vec![], - base_length: 0, - target_length: 0, - } - } - - // Add a retain op to this TextOperation (used in the builder pattern) - pub fn retain(mut self: Self, n: usize) -> Self { - if n == 0 { - return self; - } - - self.base_length += n; - self.target_length += n; - - if let Some(&mut Op::Retain(ref mut l)) = self.ops.last_mut() { - *l += n; - } else { - self.ops.push(Op::Retain(n)); - } - - self - } - - // Add an insert op to this TextOperation (used in the builder pattern) - pub fn insert(mut self: Self, s: impl Into) -> Self { - let s = s.into(); - let l = s.len(); - - if l == 0 { - return self; - } - - if let Some(&Op::Delete(_)) = self.ops.last() { - // maintain the invariant that inserts never follow a delete by popping the - // delete. adding the insert, and then re-adding the delete - let del = self.ops.pop().unwrap(); - self = self.insert(s); - self.ops.push(del); - return self; - } - - self.target_length += l; - - if let Some(&mut Op::Insert(ref mut l)) = self.ops.last_mut() { - l.push_str(&s) - } else { - self.ops.push(Op::Insert(s)); - } - - self - } - - // Add a delete op to this TextOperation (used in the builder pattern) - pub fn delete(mut self: Self, n: usize) -> Self { - if n == 0 { - return self; - } - - self.base_length += n; - - if let Some(&mut Op::Delete(ref mut l)) = self.ops.last_mut() { - *l += n; - } else { - self.ops.push(Op::Delete(n)); - } - - self - } - - // Apply an operation to a string. Returns an error if there's a mismatch between the input string - // and the operation. - pub fn apply(self: &Self, input: &str) -> Result { - let strlen = input.len(); - - if strlen != self.base_length { - return Err(String::from("The operation's base length must be equal to the string's length")) - } - - let mut res = String::new(); - let mut i = 0; - for op in &self.ops { - match op { - Op::Retain(n) => { - assert!(i + n <= strlen); - res.push_str(&input[i..i + n]); - i += n; - }, - Op::Insert(s) => { - res.push_str(&s); - }, - Op::Delete(n) => { - i += n; - }, - } - } - - Ok(res) - } - - // Transform takes two operations A and B that happened concurrently and produces two - // operations A' and B' such that `apply(apply(S, A), B') = apply(apply(S, B), A')`. This - // function is the heart of OT. That is, given a state map - // - // * - // / \ - // op1 / \ op2 - // / \ - // * * - // - // this function "completes the diamond: - // - // * * - // \ / - // op2' \ / op1' - // \ / - // * - // - // such that applying op2' after op1 has the same effect as applying op1' after op2. This - // allows two different systems which have already applied op1 and op2, respectively, and thus - // reached different states, to return to the same state by applying op2' and op1', - // respectively. - pub fn transform(mut operation1: TextOperation, mut operation2: TextOperation) -> (TextOperation, TextOperation) { - // both must start at the same state, and thus have the same base length - assert_eq!(operation1.base_length, operation2.base_length); - - let (mut operation1p, mut operation2p) = (TextOperation::new(), TextOperation::new()); - let (mut ops1, mut ops2) = (operation1.ops.iter_mut(), operation2.ops.iter_mut()); - let (mut op1, mut op2) = (ops1.next(), ops2.next()); - - loop { - match (&mut op1, &mut op2) { - // end condition: both ops1 and ops2 have been processed - (None, None) => break, - - // handle inserts, preferring op1 if both are inserts - (Some(&mut Op::Insert(ref s)), _) => { - operation1p = operation1p.insert(s.clone()); - operation2p = operation2p.retain(s.len()); - op1 = ops1.next(); - continue; - }, - - (_, Some(&mut Op::Insert(ref s))) => { - operation1p = operation1p.retain(s.len()); - operation2p = operation2p.insert(s.clone()); - op2 = ops2.next(); - continue; - }, - - (None, _) | (_, None) => { - unreachable!(); - } - - (Some(&mut Op::Retain(ref mut n1)), Some(&mut Op::Retain(ref mut n2))) => { - // retain the minimum of the two, and rewrite the larger input op - // to only retain what's left - let min; - if n1 > n2 { - min = *n2; - *n1 = *n1 - *n2; - op2 = ops2.next(); - } else if n1 == n2 { - min = *n2; - op1 = ops1.next(); - op2 = ops2.next(); - } else { - min = *n1; - *n2 = *n2 - *n1; - op1 = ops1.next(); - } - operation1p = operation1p.retain(min); - operation2p = operation2p.retain(min); - }, - (Some(Op::Delete(n1)), Some(Op::Delete(n2))) => { - // Both operations delete the same string at the same position. We don't need - // to produce any operations, we just skip over the delete ops and handle the - // case that one operation deletes more than the other. - if n1 > n2 { - *n1 = *n2 - *n1; - op2 = ops2.next(); - } else if n1 == n2 { - op1 = ops1.next(); - op2 = ops2.next(); - } else { - *n2 = *n2 - *n1; - op1 = ops1.next(); - } - }, - (Some(Op::Delete(n1)), Some(Op::Retain(n2))) => { - let min; - if n1 > n2 { - min = *n2; - *n1 = *n1 - *n2; - op2 = ops2.next(); - } else if n1 == n2 { - min = *n1; - op1 = ops1.next(); - op2 = ops2.next(); - } else { - min = *n1; - *n2 = *n2 - *n1; - op1 = ops1.next(); - } - operation1p = operation1p.delete(min); - }, - (Some(Op::Retain(n1)), Some(Op::Delete(n2))) => { - let min; - if n1 > n2 { - min = *n2; - *n1 = *n1 - *n2; - op2 = ops2.next(); - } else if n1 == n2 { - min = *n1; - op1 = ops1.next(); - op2 = ops2.next(); - } else { - min = *n1; - *n2 = *n2 - *n1; - op1 = ops1.next(); - } - operation2p = operation2p.delete(min); - }, - } - } - - (operation1p, operation2p) - } -} - -#[cfg(test)] -mod test { - use super::{TextOperation, Op}; - - #[test] - fn test_build_retain() { - let op = TextOperation::new() - .retain(10); - assert_eq!(op.base_length, 10); - assert_eq!(op.target_length, 10); - assert_eq!(op.ops, vec![Op::Retain(10)]); - } - - #[test] - fn test_build_retain_null() { - let op = TextOperation::new() - .retain(0); - assert_eq!(op.base_length, 0); - assert_eq!(op.target_length, 0); - assert_eq!(op.ops, vec![]); - } - - #[test] - fn test_build_retain_merge() { - let op = TextOperation::new() - .retain(10) - .retain(20); - assert_eq!(op.base_length, 30); - assert_eq!(op.target_length, 30); - assert_eq!(op.ops, vec![Op::Retain(30)]); - } - - #[test] - fn test_build_retain_after_insert() { - let op = TextOperation::new() - .insert(String::from("hello")) - .retain(20); - assert_eq!(op.base_length, 20); - assert_eq!(op.target_length, 25); - assert_eq!(op.ops, vec![Op::Insert(String::from("hello")), Op::Retain(20)]); - } - - #[test] - fn test_build_retain_after_delete() { - let op = TextOperation::new() - .delete(10) - .retain(20); - assert_eq!(op.base_length, 30); - assert_eq!(op.target_length, 20); - assert_eq!(op.ops, vec![Op::Delete(10), Op::Retain(20)]); - } - - #[test] - fn test_build_insert() { - let op = TextOperation::new() - .insert(String::from("hello")); - assert_eq!(op.base_length, 0); - assert_eq!(op.target_length, 5); - assert_eq!(op.ops, vec![Op::Insert(String::from("hello"))]); - } - - #[test] - fn test_build_insert_null() { - let op = TextOperation::new() - .insert(String::from("")); - assert_eq!(op.base_length, 0); - assert_eq!(op.target_length,0); - assert_eq!(op.ops, vec![]); - } - - #[test] - fn test_build_insert_merge() { - let op = TextOperation::new() - .insert(String::from("hello")) - .insert(String::from(" world")); - assert_eq!(op.base_length, 0); - assert_eq!(op.target_length, 11); - assert_eq!(op.ops, vec![Op::Insert(String::from("hello world"))]); - } - - #[test] - fn test_build_insert_after_retain() { - let op = TextOperation::new() - .retain(10) - .insert(String::from("hello")); - assert_eq!(op.base_length, 10); - assert_eq!(op.target_length, 15); - assert_eq!(op.ops, vec![Op::Retain(10), Op::Insert(String::from("hello"))]); - } - - #[test] - fn test_build_insert_after_delete() { - let op = TextOperation::new() - .delete(10) - .insert(String::from("hello")); - assert_eq!(op.base_length, 10); - assert_eq!(op.target_length, 5); - assert_eq!(op.ops, vec![ - Op::Insert(String::from("hello")), - Op::Delete(10), - ]); - } - - #[test] - fn test_build_insert_after_delete_merge() { - let op = TextOperation::new() - .insert(String::from("hello")) - .delete(10) - .insert(String::from(" world")); - assert_eq!(op.base_length, 10); - assert_eq!(op.target_length, 11); - assert_eq!(op.ops, vec![ - Op::Insert(String::from("hello world")), - Op::Delete(10), - ]); - } - - #[test] - fn test_build_delete() { - let op = TextOperation::new() - .delete(17); - assert_eq!(op.base_length, 17); - assert_eq!(op.target_length, 0); - assert_eq!(op.ops, vec![Op::Delete(17)]); - } - - #[test] - fn test_build_delete_null() { - let op = TextOperation::new() - .delete(0); - assert_eq!(op.base_length, 0); - assert_eq!(op.target_length, 0); - assert_eq!(op.ops, vec![]); - } - - #[test] - fn test_build_delete_merge() { - let op = TextOperation::new() - .delete(5) - .delete(8); - assert_eq!(op.base_length, 13); - assert_eq!(op.target_length, 0); - assert_eq!(op.ops, vec![Op::Delete(13)]); - } - - #[test] - fn test_build_delete_after_insert() { - let op = TextOperation::new() - .insert(String::from("hello")) - .delete(8); - assert_eq!(op.base_length, 8); - assert_eq!(op.target_length, 5); - assert_eq!(op.ops, vec![Op::Insert(String::from("hello")), Op::Delete(8)]); - } - - #[test] - fn test_build_delete_after_retain() { - let op = TextOperation::new() - .retain(10) - .delete(8); - assert_eq!(op.base_length, 18); - assert_eq!(op.target_length, 10); - assert_eq!(op.ops, vec![Op::Retain(10), Op::Delete(8)]); - } - - #[test] - fn test_apply_retain() { - let op = TextOperation::new() - .retain(5); - assert_eq!( - op.apply("hello"), - Ok(String::from("hello"))); - } - - #[test] - fn test_apply_insert() { - let op = TextOperation::new() - .retain(5) - .insert(String::from(" cruel")) - .retain(6); - assert_eq!( - op.apply("hello world"), - Ok(String::from("hello cruel world"))); - } - - #[test] - fn test_apply_delete() { - let op = TextOperation::new() - .retain(5) - .delete(6) - .retain(6); - assert_eq!( - op.apply("hello cruel world"), - Ok(String::from("hello world"))); - } - - #[test] - fn test_apply_wrong_length() { - let op = TextOperation::new() - .retain(5) - .insert(String::from(" cruel")) - .retain(6); - assert_eq!( - op.apply("hello cruel world"), - Err(String::from("The operation's base length must be equal to the string's length"))); - } - - mod transform { - use super::{TextOperation}; - - const STARTING_STATE: &str ="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - - fn test_transform(o1: TextOperation, o2: TextOperation, exp1p: TextOperation, exp2p: TextOperation) { - // first check that the resulting operation is as expected.. - let (o1p, o2p) = TextOperation::transform(o1.clone(), o2.clone()); - assert_eq!((&o1p, &o2p), (&exp1p, &exp2p)); - - // then check that the definition of `transform` is satisfied, by applying to an - // arbitrary string - let input = &STARTING_STATE[0..o1.base_length]; - // B' composed with A - let first = o2p.apply(&o1.apply(&input).unwrap()).unwrap(); - // A' composed with B - let second = o1p.apply(&o2.apply(&input).unwrap()).unwrap(); - print!("{} -> {}\n", input, first); - assert_eq!(first, second); - } - - #[test] - fn test_transform_empty() { - test_transform( - TextOperation::new(), - TextOperation::new(), - TextOperation::new(), - TextOperation::new()); - } - - #[test] - fn test_transform_one_noop() { - test_transform( - TextOperation::new().retain(2).insert("123").retain(10), - TextOperation::new().retain(12), - TextOperation::new().retain(2).insert("123").retain(10), - TextOperation::new().retain(15)); - } - - #[test] - fn test_transform_two_inserts() { - test_transform( - TextOperation::new().insert("123"), - TextOperation::new().insert("567"), - TextOperation::new().insert("123").retain(3), - TextOperation::new().retain(3).insert("567")); - } - - #[test] - fn test_transform_two_retains() { - test_transform( - TextOperation::new().retain(10), - TextOperation::new().retain(10), - TextOperation::new().retain(10), - TextOperation::new().retain(10)); - } - - #[test] - fn test_transform_insert_two_different_spots() { - test_transform( - TextOperation::new().retain(5).insert("123").retain(10), - TextOperation::new().retain(10).insert("567").retain(5), - TextOperation::new().retain(5).insert("123").retain(13), - TextOperation::new().retain(13).insert("567").retain(5)); - } - - #[test] - fn test_transform_insert_two_different_spots_reversed() { - test_transform( - TextOperation::new().retain(10).insert("567").retain(5), - TextOperation::new().retain(5).insert("123").retain(10), - TextOperation::new().retain(13).insert("567").retain(5), - TextOperation::new().retain(5).insert("123").retain(13)); - } - - #[test] - fn test_transform_two_deletes() { - test_transform( - TextOperation::new().delete(10), - TextOperation::new().delete(10), - TextOperation::new(), - TextOperation::new()); - } - - #[test] - fn test_transform_delete_retain() { - test_transform( - TextOperation::new().retain(10), - TextOperation::new().delete(3).retain(7), - TextOperation::new().retain(7), - TextOperation::new().delete(3).retain(7)); - } - - #[test] - fn test_transform_retain_delete() { - test_transform( - TextOperation::new().delete(3).retain(7), - TextOperation::new().retain(10), - TextOperation::new().delete(3).retain(7), - TextOperation::new().retain(7)); - } - - #[test] - fn test_transform_delete_insert() { - test_transform( - TextOperation::new().delete(10), - TextOperation::new().retain(3).insert("123").retain(7), - TextOperation::new().delete(3).retain(3).delete(7), - TextOperation::new().insert("123")); - } - } -}