use std::marker::PhantomData;
enum State {
None,
Escaping,
Escaped,
EscapedDouble,
}
pub(crate) struct Escaped<'a> {
current_pointer: *const u8,
end_pointer: *const u8,
state: State,
lifetime: PhantomData<&'a ()>,
}
impl<'a> Escaped<'a> {
pub(crate) fn new(bytes: &'a [u8]) -> Self {
let pointer = bytes.as_ptr();
Self {
current_pointer: pointer,
end_pointer: unsafe { pointer.add(bytes.len()) },
state: State::None,
lifetime: PhantomData,
}
}
}
impl<'a> Iterator for Escaped<'a> {
type Item = u8;
fn next(&mut self) -> Option<Self::Item> {
if self.current_pointer >= self.end_pointer {
return None;
}
let b = unsafe { *self.current_pointer };
match self.state {
State::None => {
match (b, unsafe { self.current_pointer.add(1) } < self.end_pointer) {
(b'#' | b':' | b';' | b'\\', _) => {
self.state = State::Escaped;
Some(b'\\')
}
(b'/', true) => {
if unsafe { *self.current_pointer.add(1) } == b'/' {
self.state = State::EscapedDouble;
Some(b'\\')
} else {
self.current_pointer = unsafe { self.current_pointer.add(1) };
Some(b)
}
}
_ => {
self.current_pointer = unsafe { self.current_pointer.add(1) };
Some(b)
}
}
}
State::Escaping => {
self.state = State::Escaped;
Some(b'\\')
}
State::Escaped => {
self.state = State::None;
self.current_pointer = unsafe { self.current_pointer.add(1) };
Some(b)
}
State::EscapedDouble => {
self.state = State::Escaping;
self.current_pointer = unsafe { self.current_pointer.add(1) };
Some(b)
}
}
}
}
#[cfg(test)]
mod tests {
use super::Escaped;
#[test]
fn empty() {
let escaped = Escaped::new(b"");
assert_eq!(escaped.collect::<Vec<_>>(), b"");
}
#[test]
fn no_escapes() {
let escaped = Escaped::new(b"foo");
assert_eq!(escaped.collect::<Vec<_>>(), b"foo");
}
#[test]
fn escapes_number_sign() {
let escaped = Escaped::new(b"#");
assert_eq!(escaped.collect::<Vec<_>>(), b"\\#");
}
#[test]
fn escapes_colon() {
let escaped = Escaped::new(b":");
assert_eq!(escaped.collect::<Vec<_>>(), b"\\:");
}
#[test]
fn escapes_semicolon() {
let escaped = Escaped::new(b";");
assert_eq!(escaped.collect::<Vec<_>>(), b"\\;");
}
#[test]
fn escapes_backslash() {
let escaped = Escaped::new(b"\\");
assert_eq!(escaped.collect::<Vec<_>>(), b"\\\\");
}
#[test]
fn no_escape_single_forward_slash() {
let escaped = Escaped::new(b"/foo");
assert_eq!(escaped.collect::<Vec<_>>(), b"/foo");
}
#[test]
fn no_escape_single_forward_slash_at_end() {
let escaped = Escaped::new(b"/");
assert_eq!(escaped.collect::<Vec<_>>(), b"/");
}
#[test]
fn escapes_double_forward_slash() {
let escaped = Escaped::new(b"//");
assert_eq!(escaped.collect::<Vec<_>>(), b"\\/\\/");
}
#[test]
fn escapes_triple_forward_slash() {
let escaped = Escaped::new(b"///");
assert_eq!(escaped.collect::<Vec<_>>(), b"\\/\\//");
}
#[test]
fn escapes_multiple() {
let escaped = Escaped::new(b"foo//bar#baz;qux:quux\\");
assert_eq!(
escaped.collect::<Vec<_>>(),
b"foo\\/\\/bar\\#baz\\;qux\\:quux\\\\"
);
}
#[test]
fn escapes_back_to_back() {
let escaped = Escaped::new(b"#:;\\////");
assert_eq!(escaped.collect::<Vec<_>>(), b"\\#\\:\\;\\\\\\/\\/\\/\\/");
}
}