1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
/// Contains all possible operations, as interpreted from commands via input.
/// `Invalid` is a catch-all for any unrecognized commands.
#[derive(Clone, Debug, PartialEq)]
pub enum Operation<'a> {
    /// The append command, denotated by `1` in the program's input. The associated `&'a str`
    /// contains the string data to be appended to the buffer.
    /// ```
    /// use simple_text_editor::ops::*;
    /// let op = "1 append this text".into();
    /// match op {
    ///     Operation::Append(val) => {
    ///         assert_eq!(val, "append this text");
    ///     }
    ///     _ => panic!("should have matched Operation::Append"),
    /// }
    /// ```
    Append(&'a str),
    /// The delete command, denotated by `2` in the program's input. The associated `usize` is the
    /// number of characters to be delete from the back of the buffer.
    /// ```
    /// use simple_text_editor::ops::*;
    /// let op = "2 5".into();
    /// match op {
    ///     Operation::Delete(n) => {
    ///         assert_eq!(n, 5);
    ///     }
    ///     _ => panic!("should have matched Operation::Delete"),
    /// }
    /// ```
    Delete(usize),
    /// The print command, denotated by `3` in the program's input. The associated `usize` is the
    /// 1-based index at which the character from the buffer should be printed.
    /// ```
    /// use simple_text_editor::ops::*;
    /// let op = "3 1".into();
    /// match op {
    ///     Operation::Print(i) => {
    ///         assert_eq!(i, 1);
    ///     }
    ///     _ => panic!("should have matched Operation::Print"),
    /// }
    /// ```
    Print(usize),
    /// The undo command, denotated by `4` in the program's input. There is no associated data, and
    /// thus simply pops a command from a maintained stack of undo-eligible operations, either being
    /// append or delete.
    /// ```
    /// use simple_text_editor::ops::*;
    /// let op = "4".into();
    /// match op {
    ///     Operation::Undo => {
    ///         assert!(true);
    ///     }
    ///     _ => panic!("should have matched Operation::Undo"),
    /// }
    /// ```
    Undo,
    /// Invalid is a catch-all for any unrecognized commands, and is ignored by the program.
    /// ```
    /// use simple_text_editor::ops::*;
    /// let op = "__BADOPERATION__".into();
    /// match op {
    ///     Operation::Invalid => {
    ///         assert!(true);
    ///     }
    ///     _ => panic!("should have matched Operation::Invalid"),
    /// }
    /// ```
    Invalid,
}

/// Convert a line of input into an Operation.
/// ```
/// use simple_text_editor::ops::*;
/// let op = "1 abc".into();
/// assert!(matches!(op, Operation::Append("abc")));
/// ```
impl<'a> From<&'a str> for Operation<'a> {
    fn from(input: &'a str) -> Self {
        let input = input.trim_start();
        if input.is_empty() {
            return Operation::Invalid;
        }

        let mut chars = input.chars();
        match chars.next().map(|c| (c, chars.as_str())) {
            Some(('1', val)) => Operation::Append(remove_sep_space(val)),
            Some((op @ '2', val)) | Some((op @ '3', val)) => {
                parse_delete_or_print(op, remove_sep_space(val))
            }
            Some(('4', _)) => Operation::Undo,
            _ => Operation::Invalid,
        }
    }
}

/// Remove the whitespace between op and value, but not any leading whitespace of the value
fn remove_sep_space(val: &str) -> &str {
    let mut chars = val.chars();
    if let Some((_, rest)) = chars.next().map(|c| (c, chars.as_str())) {
        rest
    } else {
        val
    }
}

/// Combine parsing logic from input when dealing with delete or print operations.
fn parse_delete_or_print(op: char, value_to_parse: &str) -> Operation {
    if let Ok(val) = value_to_parse.trim().parse::<usize>() {
        match op {
            '2' => Operation::Delete(val),
            '3' => Operation::Print(val),
            _ => Operation::Invalid,
        }
    } else {
        Operation::Invalid
    }
}

/// Parse all operations from combined input, including the command count (first line).
/// ```
/// use simple_text_editor::ops::*;
/// assert_eq!(
///     parse(
///         r#"4
/// 1 abc
/// 2 1
/// 3 1
/// 4
/// "#
///     ),
///     Some((
///         4 as usize,
///         vec![
///             Operation::Append("abc"),
///             Operation::Delete(1),
///             Operation::Print(1),
///             Operation::Undo,
///         ]
///     ))
/// );
/// ```
pub fn parse(input: &str) -> Option<(usize, Vec<Operation>)> {
    let lines = input.lines();
    let num_ops = lines.clone().take(1).next().unwrap_or_default().parse();
    if num_ops.is_err() {
        return None;
    }
    let ops = lines.skip(1).map(|line| line.into()).collect();
    Some((num_ops.unwrap(), ops))
}

/// Contains all possible operations which are eligible for "undo".
/// `Append` and `Delete` maintain the inverse of their `Operation` type counterparts,
/// and hold the data necessary to undo the operation on the buffer.
#[derive(Debug)]
pub enum UndoableOperation {
    /// The undo operation for a previously executed append command. The associated `usize` is the
    /// count of characters which had been appended, and should now be deleted from the back of
    /// the buffer.
    Append(usize),
    /// The undo operation for a previously executed delete command. The associated `Vec<u8>` is the
    /// set of characters which had been previously popped from the back of the buffer, and should
    /// now be re-appended. Note, the characters are in reverse-order, which is the natural order
    /// after they had been popped from the buffer. As an optimization, the program will lazily
    /// re-order these to be pushed onto the buffer so they are accurately re-appended.
    Delete(Vec<u8>),
}