use std::io::Write; /// The parser/transformator. Given a `token` and a `suffix`, adds the `suffix` /// after the `token` to any byte buffer given to [`Parser::process`]. /// /// For example, with a `Parser::new(b"foo", b"bar"), calling /// `parser.process(b"is foo a bar?", &mut writer)` will result in /// `"is foobar a bar?"` being written to `writer`. // // NOTE: Feel free to modify this struct as needed. pub struct Parser { token: &'static [u8], suffix: &'static [u8], /// NORA: Records the position in the token across calls to `process`. pos: usize, } impl Parser { /// Create a new `Parser` which will append `suffix` after `token` whenever /// it appears in input passed to `process`. // // NOTE: This function signature should stay the same. pub fn new(token: &'static [u8], suffix: &'static [u8]) -> Self { Self { token, suffix, pos: 0, } } /// Write the bytes given in `input` to `output`, plus the bytes in `suffix` /// immediately after `token`, including across call boundaries. /// /// # Examples /// /// If the token is present in the input, the writer gets the input plus /// the suffix right after the token. For instance, here, /// `does this foo go bar?` is transformed into /// `does this foobar go bar?`. /// /// ```rust /// # use code_challenge::Parser; /// let mut parser = Parser::new(b"foo", b"bar"); /// let mut buffer = Vec::new(); /// parser.process(b"does this foo go bar?", &mut buffer).unwrap(); /// assert_eq!(b"does this foobar go bar?", buffer.as_slice()); /// ``` /// /// This works even if the token is split across multiple calls to the /// `process` method on the same instance of `Parser`. /// For instance, this is exactly the same as the previous example, /// but splits the input across multiple calls to `parser`. /// /// ```rust /// # use code_challenge::Parser; /// let mut parser = Parser::new(b"foo", b"bar"); /// let mut buffer = Vec::new(); /// parser.process(b"does this f", &mut buffer).unwrap(); /// parser.process(b"oo go bar?", &mut buffer).unwrap(); /// assert_eq!(b"does this foobar go bar?", buffer.as_slice()); /// ``` // // NOTE: This function signature should stay the same. pub fn process(&mut self, input: &[u8], output: &mut dyn Write) -> Result<(), std::io::Error> { // NORA: Create a buffer to work in for just this call. // We'll write the whole buffer at the end, unconditionally; // the only question is what's in it. let mut buffer = Vec::with_capacity(input.len() + self.suffix.len()); // NORA: Look at each byte in the input. for byte in input { // NORA: Unconditionally, write that byte into our buffer. // No requirement calls for losing any bytes. buffer.push(*byte); // NORA: Compare the current byte in the input to the current byte // in the token. if *byte == self.token[self.pos] { // NORA: If this byte is a match, progress to the next token byte. self.pos += 1; // NORA: If we've reached the end of the token, // that's a successful match. if self.pos == self.token.len() { // NORA: Write the whole suffix into the output buffer. for suffix_byte in self.suffix { buffer.push(*suffix_byte); } // NORA: And reset to zero, so we can look for a new match. self.pos = 0; } } else { // NORA: If the current input byte isn't a match for the current // token byte, we have failed to match the token; // reset to the beginning. // This prevents e.g. "oat meal" from matching "oatmeal". self.pos = 0; } } output.write_all(&buffer) } } #[test] fn test_output_unmodified() { let mut parser = Parser::new(b"lalala", b""); let mut buffer = Vec::new(); parser .process(b"does not contain the token", &mut buffer) .expect("couldn't write to buffer"); assert_eq!( "does not contain the token", String::from_utf8_lossy(&buffer) ) } #[test] fn test_output_onetoken() { let mut parser = Parser::new(b"token", b"xxx"); let mut buffer = Vec::new(); parser .process(b"does contain the token", &mut buffer) .expect("couldn't write to buffer"); assert_eq!( "does contain the tokenxxx", String::from_utf8_lossy(&buffer) ) } #[test] fn test_output_onetoken_multi() { let mut parser = Parser::new(b"token", b"xxx"); let mut buffer = Vec::new(); parser .process(b"does contain ", &mut buffer) .expect("couldn't write to buffer"); parser .process(b"the tok", &mut buffer) .expect("couldn't write to buffer"); parser .process(b"en", &mut buffer) .expect("couldn't write to buffer"); assert_eq!( "does contain the tokenxxx", String::from_utf8_lossy(&buffer) ) } #[test] fn test_output_splittoken() { let mut parser = Parser::new(b"token", b"xxx"); let mut buffer = Vec::new(); parser .process(b"doesn't contain the tok en", &mut buffer) .expect("couldn't write to buffer"); assert_eq!( "doesn't contain the tok en", String::from_utf8_lossy(&buffer) ) }