hpcc/src/lib.rs

161 lines
5.5 KiB
Rust

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)
)
}